/** * Unified Signup Form with Integrated Pricing Selection * Combines free and paid signup flows in one modern interface * * Payment Flow (Simplified): * 1. User selects plan and fills in details * 2. User selects country from dropdown * 3. On submit: account created, redirected to /account/plans for payment * * NO payment method selection at signup - this happens on /account/plans */ import { useState, useEffect } from 'react'; import ReactDOM from 'react-dom'; import { Link, useNavigate } from 'react-router-dom'; import { ChevronLeftIcon, EyeCloseIcon, EyeIcon, CheckIcon, Loader2Icon, CheckCircleIcon, GlobeIcon } from '../../icons'; import Label from '../form/Label'; import Input from '../form/input/InputField'; import Checkbox from '../form/input/Checkbox'; import Button from '../ui/button/Button'; import { useAuthStore } from '../../store/authStore'; import { fetchCountries } from '../../utils/countries'; interface Plan { id: number; name: string; slug: string; price: string | number; billing_cycle: string; is_active: boolean; max_users: number; max_sites: number; max_keywords: number; max_ahrefs_queries: number; included_credits: number; features: string[]; annual_discount_percent?: number; } interface Country { code: string; name: string; } interface SignUpFormUnifiedProps { plans: Plan[]; selectedPlan: Plan | null; onPlanSelect: (plan: Plan) => void; plansLoading: boolean; } export default function SignUpFormUnified({ plans, selectedPlan, onPlanSelect, plansLoading, }: SignUpFormUnifiedProps) { const [showPassword, setShowPassword] = useState(false); const [isChecked, setIsChecked] = useState(false); const [billingPeriod, setBillingPeriod] = useState<'monthly' | 'annually'>('monthly'); const [annualDiscountPercent, setAnnualDiscountPercent] = useState(15); const [formData, setFormData] = useState({ firstName: '', lastName: '', email: '', password: '', accountName: '', billingCountry: 'US', }); // Countries for dropdown const [countries, setCountries] = useState([]); const [countriesLoading, setCountriesLoading] = useState(true); const [error, setError] = useState(''); const navigate = useNavigate(); const { register, loading } = useAuthStore(); const isPaidPlan = selectedPlan && parseFloat(String(selectedPlan.price || 0)) > 0; // Update URL when plan changes useEffect(() => { if (selectedPlan) { const url = new URL(window.location.href); url.searchParams.set('plan', selectedPlan.slug); window.history.replaceState({}, '', url.toString()); } }, [selectedPlan]); // Load annual discount percent from plans useEffect(() => { if (plans.length > 0) { const planWithDiscount = plans.find(p => p.annual_discount_percent && p.annual_discount_percent > 0); if (planWithDiscount && planWithDiscount.annual_discount_percent) { setAnnualDiscountPercent(planWithDiscount.annual_discount_percent); } } }, [plans]); // Load countries from backend and detect user's country useEffect(() => { const loadCountriesAndDetect = async () => { setCountriesLoading(true); try { const loadedCountries = await fetchCountries(); setCountries(loadedCountries); // Try to detect user's country for default selection // Note: This may fail due to CORS - that's expected and handled gracefully try { const geoResponse = await fetch('https://ipapi.co/country_code/', { signal: AbortSignal.timeout(3000), mode: 'cors', }); if (geoResponse.ok) { const countryCode = await geoResponse.text(); if (countryCode && countryCode.length === 2) { setFormData(prev => ({ ...prev, billingCountry: countryCode.trim() })); } } } catch (error) { // Silently fail - CORS or network error, keep default US // This is expected behavior and not a critical error } } finally { setCountriesLoading(false); } }; loadCountriesAndDetect(); }, []); const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(''); if (!formData.email || !formData.password || !formData.firstName || !formData.lastName) { setError('Please fill in all required fields'); return; } if (!isChecked) { setError('Please agree to the Terms and Conditions'); return; } if (!selectedPlan) { setError('Please select a plan'); return; } try { const username = formData.email.split('@')[0]; const registerPayload: any = { email: formData.email, password: formData.password, username: username, first_name: formData.firstName, last_name: formData.lastName, account_name: formData.accountName, plan_slug: selectedPlan.slug, billing_country: formData.billingCountry, }; const user = (await register(registerPayload)) as any; console.log('Registration response:', { user: user, accountStatus: user?.account?.status, planSlug: selectedPlan.slug, }); // Verify auth state is actually set in Zustand store const currentAuthState = useAuthStore.getState(); // If for some reason state wasn't set, force set it again if (!currentAuthState.isAuthenticated || !currentAuthState.user || !currentAuthState.token) { console.error('Auth state not properly set after registration, forcing update...'); const tokenData = user?.tokens || {}; const accessToken = user?.access || tokenData.access || localStorage.getItem('access_token'); const refreshToken = user?.refresh || tokenData.refresh || localStorage.getItem('refresh_token'); useAuthStore.setState({ user: user, token: accessToken, refreshToken: refreshToken, isAuthenticated: true, loading: false }); await new Promise((resolve) => setTimeout(resolve, 500)); } // Final verification before navigation const finalState = useAuthStore.getState(); if (!finalState.isAuthenticated) { throw new Error('Failed to authenticate after registration. Please try logging in manually.'); } // Simplified navigation: // - Paid plans: Go to /account/plans to select payment method and complete payment // - Free plans: Go to sites page if (isPaidPlan) { console.log('Paid plan selected, redirecting to /account/plans for payment'); navigate('/account/plans', { replace: true }); } else { console.log('Free plan selected, redirecting to /sites'); navigate('/sites', { replace: true }); } } catch (err: any) { setError(err.message || 'Registration failed. Please try again.'); } }; const formatNumber = (num: number): string => { if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`; if (num >= 1000) return `${(num / 1000).toFixed(0)}K`; return num.toString(); }; const extractFeatures = (plan: Plan): string[] => { // Use features from plan's JSON field if available, otherwise build from limits if (plan.features && plan.features.length > 0) { return plan.features; } // Fallback to building from plan limits const features: string[] = []; features.push(`${plan.max_sites} ${plan.max_sites === 1 ? 'Site' : 'Sites'}`); features.push(`${plan.max_users} ${plan.max_users === 1 ? 'User' : 'Users'}`); features.push(`${formatNumber(plan.max_keywords || 0)} Keywords`); features.push(`${formatNumber(plan.included_credits || 0)} Credits/Month`); return features; }; const getDisplayPrice = (plan: Plan): number => { const monthlyPrice = typeof plan.price === 'number' ? plan.price : parseFloat(String(plan.price || 0)); if (billingPeriod === 'annually') { const discountMultiplier = 1 - (annualDiscountPercent / 100); return monthlyPrice * 12 * discountMultiplier; } return monthlyPrice; }; return (
{/* Mobile Pricing Toggle */}
Save up to {annualDiscountPercent}%

Sign Up for {selectedPlan?.name || 'IGNY8'}

Complete your registration and select a payment method.

{/* Plan Selection - Mobile */}
{plans.map((plan) => { const displayPrice = getDisplayPrice(plan); const isSelected = selectedPlan?.id === plan.id; const isFree = parseFloat(String(plan.price || 0)) === 0; return ( ); })}
{error && (
{error}
)}
setShowPassword(!showPassword)} className="absolute z-30 -translate-y-1/2 cursor-pointer right-4 top-1/2"> {showPassword ? : }
{/* Country Selection */}
{countriesLoading ? (
Loading countries...
) : ( )}

By creating an account means you agree to the{' '} Terms and Conditions , and our{' '} Privacy Policy

Already have an account?{' '} Sign In

{/* Desktop Pricing Panel - Renders in right side */}
{/* This will be portaled to the right side */} {typeof document !== 'undefined' && document.getElementById('signup-pricing-plans') && ReactDOM.createPortal(
{/* Billing Toggle - Centered with inline discount */}

Save up to {annualDiscountPercent}%

{/* Plan Cards - Single column, stacked vertically */}
{plans.map((plan) => { const displayPrice = getDisplayPrice(plan); const features = extractFeatures(plan); const isSelected = selectedPlan?.id === plan.id; const isFree = parseFloat(String(plan.price || 0)) === 0; const isPopular = plan.slug.toLowerCase().includes('growth'); return (
onPlanSelect(plan)} className={`relative rounded-2xl p-5 cursor-pointer transition-all duration-300 ${ isSelected ? 'border-[3px] border-success-500 bg-white dark:bg-gray-800 shadow-2xl ring-4 ring-success-500/20' : isPopular ? 'border-2 border-brand-200 dark:border-brand-800 bg-white dark:bg-gray-800/50 hover:shadow-xl' : 'border-2 border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800/50 hover:shadow-lg' }`} > {isPopular && !isSelected && (
⭐ POPULAR
)} {isSelected && (
)} {/* Header: Plan name, price, and features in horizontal layout */}
{/* Plan Name & Price */}

{plan.name}

{isFree ? 'Free' : `$${displayPrice.toFixed(2)}`} {!isFree && ( {billingPeriod === 'annually' ? '/year' : '/month'} )}
{billingPeriod === 'annually' && !isFree && (

${(displayPrice / 12).toFixed(2)}/month billed annually

)}
{/* Features - 2 columns */}
{features.map((feature, idx) => (
{feature}
))}
); })}
, document.getElementById('signup-pricing-plans')! )}
); }