'use client' import Image from 'next/image' import Link from 'next/link' import { useState } from 'react' import { useSearchParams } from 'next/navigation' import { useDashboardI18n } from '@/components/I18nProvider' import { apiFetch } from '@/lib/api' import PublicShell from '@/components/layout/PublicShell' import { marketplaceUrl } from '@/lib/urls' import { BilingualField, BilingualInput, bilingualPrimary, emptyBilingual } from '@/components/ui/BilingualInput' type SignupForm = { firstName: BilingualField lastName: BilingualField email: string password: string confirmPassword: string preferredLanguage: 'en' | 'fr' | 'ar' | '' companyName: BilingualField legalForm: string registrationNumber: string streetAddress: BilingualField city: BilingualField country: BilingualField zipCode: string companyPhone: string fax: string yearsActive: string plan: 'STARTER' | 'GROWTH' | 'PRO' billingPeriod: 'MONTHLY' | 'ANNUAL' currency: 'MAD' paymentProvider: 'AMANPAY' | 'PAYPAL' } type CompletedSignup = { companyName: string email: string emailWarning?: string | null } type SignupResponse = { companyId: string companyName: string slug: string trialEndsAt: string | null nextStep: string emailDelivery?: { attempted: boolean success: boolean error: string | null } | null } const LEGAL_FORM_OPTIONS = ['SARL', 'SARL AU', 'SA', 'SAS', 'AUTO_ENTREPRENEUR', 'EI', 'OTHER'] as const const YEARS_ACTIVE_OPTIONS = ['LESS_THAN_1', 'ONE_TO_FIVE', 'MORE_THAN_5'] as const const DASHBOARD_LOGO_SRC = '/dashboard/rentalcardrive.png' export default function SignUpPage() { const { language, setLanguage } = useDashboardI18n() const searchParams = useSearchParams() const initialPlan = normalizePlan(searchParams.get('plan')) const initialBillingPeriod = normalizeBillingPeriod(searchParams.get('billing')) const initialCurrency = normalizeCurrency(searchParams.get('currency')) const [step, setStep] = useState(1) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [completedSignup, setCompletedSignup] = useState(null) const [form, setForm] = useState({ firstName: emptyBilingual(), lastName: emptyBilingual(), email: '', password: '', confirmPassword: '', preferredLanguage: '', companyName: emptyBilingual(), legalForm: '', registrationNumber: '', streetAddress: emptyBilingual(), city: emptyBilingual(), country: emptyBilingual(), zipCode: '', companyPhone: '', fax: '', yearsActive: '', plan: initialPlan, billingPeriod: initialBillingPeriod, currency: initialCurrency, paymentProvider: 'AMANPAY', }) const isArabic = language === 'ar' const dict = { en: { brand: 'RentalDriveGo', pageTitle: 'Launch your rental workspace', pageSubtitle: 'Create the user account, complete the company information, and launch your workspace.', steps: ['Account User', 'Company info', 'Plan', 'Verify'], ownerTitle: 'Account User', ownerBody: 'Create the primary user account that will manage this company workspace.', partnerTitle: 'Company info', partnerBody: 'Complete the company information required to create the workspace.', planTitle: 'Choose your plan', planBody: 'Your workspace starts immediately on a 90-day free trial in your preferred billing currency.', reviewTitle: 'Review and launch', reviewBody: 'We will create the company workspace immediately and start the 90-day trial with the plan you selected.', firstName: 'Manager/Owner first name', lastName: 'Manager/Owner last name', ownerEmail: 'Manager/Owner email', password: 'Password', confirmPassword: 'Confirm password', passwordHint: 'Use at least 8 characters for your company owner account.', companyName: 'Company Name', legalForm: 'Legal form', registrationNumber: 'Commercial Registry (RC)', streetAddress: 'Street Address', city: 'City', country: 'Country', zipCode: 'Zip code', companyPhone: 'Phone', fax: 'Fax (optional)', yearsActive: 'Years active', continue: 'Continue', back: 'Back', working: 'Working…', createWorkspace: 'Create workspace', workspaceReady: 'Workspace ready', workspaceReadyBody: 'is now active and the owner email', enterDashboard: 'Enter dashboard', signInAnotherDevice: 'Sign in on another device', reviewWorkspace: 'Workspace', reviewPlan: 'Plan', reviewOwnerEmail: 'Owner email', reviewPhone: 'Phone', invalidPassword: 'Choose a password with at least 8 characters.', passwordMismatch: 'Passwords do not match.', missingRequired: 'Fill in all required fields.', missingLanguage: 'Please select your preferred language.', preferredLanguageLabel: 'Preferred language', languageOptions: { en: 'English', fr: 'Français', ar: 'العربية' } as Record, companyFallback: 'Your company', couldNotCreate: 'Could not create workspace', legalFormOptions: { '': 'Select legal form', SARL: 'SARL', 'SARL AU': 'SARL AU', SA: 'SA', SAS: 'SAS', AUTO_ENTREPRENEUR: 'Auto-entrepreneur', EI: 'Sole proprietorship', OTHER: 'Other', } as Record, yearsActiveOptions: { '': 'Select years active', LESS_THAN_1: 'Less than 1 year', ONE_TO_FIVE: '1 to 5 years', MORE_THAN_5: 'More than 5 years', } as Record, planDescriptions: { STARTER: 'Core fleet, offers, and bookings.', GROWTH: 'Featured marketplace placement and more room to scale.', PRO: 'Full white-label and premium controls.', } as Record, billingPeriodOptions: { MONTHLY: 'Monthly', ANNUAL: 'Annual', } as Record, paymentProviderOptions: { AMANPAY: 'AmanPay', PAYPAL: 'PayPal', } as Record, }, fr: { brand: 'RentalDriveGo', pageTitle: 'Lancez votre espace de location', pageSubtitle: 'Créez le compte utilisateur, complétez les informations de l’entreprise et lancez votre espace.', steps: ['Compte utilisateur', 'Infos entreprise', 'Plan', 'Vérification'], ownerTitle: 'Compte utilisateur', ownerBody: 'Créez le compte utilisateur principal qui gérera cet espace entreprise.', partnerTitle: 'Infos entreprise', partnerBody: 'Complétez les informations de l’entreprise requises pour créer l’espace.', planTitle: 'Choisissez votre formule', planBody: 'Votre espace démarre immédiatement avec un essai gratuit de 90 jours dans la devise choisie.', reviewTitle: 'Vérification et lancement', reviewBody: 'Nous allons créer immédiatement l’espace entreprise et démarrer l’essai de 90 jours avec la formule choisie.', firstName: 'Prénom du gérant/propriétaire', lastName: 'Nom du gérant/propriétaire', ownerEmail: 'E-mail du gérant/propriétaire', password: 'Mot de passe', confirmPassword: 'Confirmer le mot de passe', passwordHint: 'Utilisez au moins 8 caractères pour le compte propriétaire.', companyName: 'Nom de l’entreprise', legalForm: 'Forme juridique', registrationNumber: 'Registre du commerce (RC)', streetAddress: 'Adresse', city: 'Ville', country: 'Pays', zipCode: 'Code postal', companyPhone: 'Téléphone', fax: 'Fax (optionnel)', yearsActive: 'Années d’activité', continue: 'Continuer', back: 'Retour', working: 'Traitement…', createWorkspace: 'Créer l’espace', workspaceReady: 'Espace prêt', workspaceReadyBody: 'est maintenant actif et l’e-mail propriétaire', enterDashboard: 'Accéder au tableau de bord', signInAnotherDevice: 'Se connecter sur un autre appareil', reviewWorkspace: 'Espace', reviewPlan: 'Formule', reviewOwnerEmail: 'E-mail propriétaire', reviewPhone: 'Téléphone', invalidPassword: 'Choisissez un mot de passe d’au moins 8 caractères.', passwordMismatch: 'Les mots de passe ne correspondent pas.', missingRequired: 'Renseignez tous les champs obligatoires.', missingLanguage: 'Veuillez sélectionner votre langue préférée.', preferredLanguageLabel: 'Langue préférée', languageOptions: { en: 'English', fr: 'Français', ar: 'العربية' } as Record, companyFallback: 'Votre entreprise', couldNotCreate: 'Impossible de créer l’espace', legalFormOptions: { '': 'Sélectionner la forme juridique', SARL: 'SARL', 'SARL AU': 'SARL AU', SA: 'SA', SAS: 'SAS', AUTO_ENTREPRENEUR: 'Auto-entrepreneur', EI: 'Entreprise individuelle', OTHER: 'Autre', } as Record, yearsActiveOptions: { '': 'Sélectionner les années d’activité', LESS_THAN_1: 'Moins de 1 an', ONE_TO_FIVE: '1 an à 5 ans', MORE_THAN_5: 'Plus de 5 ans', } as Record, planDescriptions: { STARTER: 'Flotte, offres et réservations essentielles.', GROWTH: 'Mise en avant marketplace et plus de marge pour évoluer.', PRO: 'White-label complet et contrôles premium.', } as Record, billingPeriodOptions: { MONTHLY: 'Mensuel', ANNUAL: 'Annuel', } as Record, paymentProviderOptions: { AMANPAY: 'AmanPay', PAYPAL: 'PayPal', } as Record, }, ar: { brand: 'RentalDriveGo', pageTitle: 'أطلق مساحة التأجير الخاصة بك', pageSubtitle: 'أنشئ حساب المستخدم، وأكمل معلومات الشركة، ثم أطلق مساحة العمل.', steps: ['حساب المستخدم', 'معلومات الشركة', 'الخطة', 'المراجعة'], ownerTitle: 'حساب المستخدم', ownerBody: 'أنشئ حساب المستخدم الرئيسي الذي سيدير مساحة عمل الشركة.', partnerTitle: 'معلومات الشركة', partnerBody: 'أكمل معلومات الشركة المطلوبة لإنشاء مساحة العمل.', planTitle: 'اختر خطتك', planBody: 'تبدأ المساحة فوراً بفترة تجريبية مجانية لمدة 14 يوماً بالعملة التي تفضلها.', reviewTitle: 'راجع وأطلق', reviewBody: 'سننشئ مساحة الشركة فوراً ونبدأ الفترة التجريبية لمدة 14 يوماً بالخطة التي اخترتها.', firstName: 'الاسم الأول للمدير/المالك', lastName: 'اسم العائلة للمدير/المالك', ownerEmail: 'بريد المدير/المالك الإلكتروني', password: 'كلمة المرور', confirmPassword: 'تأكيد كلمة المرور', passwordHint: 'استخدم 8 أحرف على الأقل لحساب مالك الشركة.', companyName: 'اسم الشركة', legalForm: 'الشكل القانوني', registrationNumber: 'السجل التجاري (RC)', streetAddress: 'عنوان الشارع', city: 'المدينة', country: 'الدولة', zipCode: 'الرمز البريدي', companyPhone: 'الهاتف', fax: 'الفاكس (اختياري)', yearsActive: 'سنوات النشاط', continue: 'متابعة', back: 'رجوع', working: 'جارٍ التنفيذ…', createWorkspace: 'إنشاء المساحة', workspaceReady: 'المساحة جاهزة', workspaceReadyBody: 'أصبحت الآن نشطة وتم تسجيل بريد المالك', enterDashboard: 'الدخول إلى لوحة التحكم', signInAnotherDevice: 'تسجيل الدخول على جهاز آخر', reviewWorkspace: 'المساحة', reviewPlan: 'الخطة', reviewOwnerEmail: 'بريد المالك', reviewPhone: 'الهاتف', invalidPassword: 'اختر كلمة مرور من 8 أحرف على الأقل.', passwordMismatch: 'كلمتا المرور غير متطابقتين.', missingRequired: 'أكمل جميع الحقول الإلزامية.', missingLanguage: 'يرجى اختيار اللغة المفضلة.', preferredLanguageLabel: 'اللغة المفضلة', languageOptions: { en: 'English', fr: 'Français', ar: 'العربية' } as Record, companyFallback: 'شركتك', couldNotCreate: 'تعذر إنشاء المساحة', legalFormOptions: { '': 'اختر الشكل القانوني', SARL: 'شركة ذات مسؤولية محدودة', 'SARL AU': 'شركة ذات مسؤولية محدودة لشخص واحد', SA: 'شركة مساهمة', SAS: 'شركة مساهمة مبسطة', AUTO_ENTREPRENEUR: 'مقاول ذاتي', EI: 'مؤسسة فردية', OTHER: 'أخرى', } as Record, yearsActiveOptions: { '': 'اختر سنوات النشاط', LESS_THAN_1: 'أقل من سنة', ONE_TO_FIVE: 'من سنة إلى 5 سنوات', MORE_THAN_5: 'أكثر من 5 سنوات', } as Record, planDescriptions: { STARTER: 'الأساسيات للأسطول والعروض والحجوزات.', GROWTH: 'ظهور مميز في السوق ومساحة أكبر للنمو.', PRO: 'تحكم كامل وواجهة بيضاء متقدمة.', } as Record, billingPeriodOptions: { MONTHLY: 'شهري', ANNUAL: 'سنوي', } as Record, paymentProviderOptions: { AMANPAY: 'AmanPay', PAYPAL: 'PayPal', } as Record, }, }[language] function handleStep1Continue() { if (!form.preferredLanguage) { setError(dict.missingLanguage) return } setError(null) setStep(2) } async function submitSignup() { if (form.password.length < 8) { setError(dict.invalidPassword) return } if (form.password !== form.confirmPassword) { setError(dict.passwordMismatch) return } if (!hasRequiredPartnerFields(form)) { setError(dict.missingRequired) return } setLoading(true) setError(null) try { const response = await apiFetch('/auth/company/signup', { method: 'POST', body: JSON.stringify({ firstName: form.firstName.fr || form.firstName.ar, firstNameAr: form.firstName.ar || undefined, lastName: form.lastName.fr || form.lastName.ar, lastNameAr: form.lastName.ar || undefined, email: form.email, password: form.password, preferredLanguage: form.preferredLanguage || 'en', companyName: form.companyName.fr || form.companyName.ar, companyNameAr: form.companyName.ar || undefined, legalForm: form.legalForm, registrationNumber: form.registrationNumber, streetAddress: form.streetAddress.fr || form.streetAddress.ar, streetAddressAr: form.streetAddress.ar || undefined, city: form.city.fr || form.city.ar, cityAr: form.city.ar || undefined, country: form.country.fr || form.country.ar, countryAr: form.country.ar || undefined, zipCode: form.zipCode, companyPhone: form.companyPhone, fax: form.fax || undefined, yearsActive: form.yearsActive, plan: form.plan, billingPeriod: form.billingPeriod, currency: form.currency, paymentProvider: form.paymentProvider, }), }) setCompletedSignup({ companyName: form.companyName.fr || form.companyName.ar, email: form.email, emailWarning: getEmailWarning(response.emailDelivery, language), }) } catch (err) { setError(getErrorMessage(err, dict.couldNotCreate)) } finally { setLoading(false) } } if (completedSignup) { return (
{dict.brand}

{dict.workspaceReady}

{completedSignup.companyName} {dict.workspaceReadyBody} {completedSignup.email}.

{completedSignup.emailWarning ? (
{completedSignup.emailWarning}
) : null}
{dict.enterDashboard} {dict.signInAnotherDevice}
) } return (
{dict.brand}

{dict.pageTitle}

{dict.pageSubtitle}

{dict.steps.map((label, index) => (
{label}
))}
{error ?
{error}
: null} {step === 1 ? (
setForm((current) => ({ ...current, firstName: value }))} /> setForm((f) => ({ ...f, lastName: current }))} /> setForm((current) => ({ ...current, email: normalizeEmail(value) }))} />
setForm((current) => ({ ...current, password: value }))} /> setForm((current) => ({ ...current, confirmPassword: value }))} />

{dict.passwordHint}

{dict.preferredLanguageLabel} *
{(['en', 'fr', 'ar'] as const).map((lang) => ( ))}
) : null} {step === 2 ? (
setForm((current) => ({ ...current, companyName: value }))} /> setForm((current) => ({ ...current, legalForm: value }))} /> setForm((current) => ({ ...current, registrationNumber: normalizeTitleCase(value) }))} />
setForm((current) => ({ ...current, companyPhone: value }))} /> setForm((current) => ({ ...current, fax: value }))} />
setForm((current) => ({ ...current, streetAddress: value }))} />
setForm((current) => ({ ...current, city: value }))} /> setForm((current) => ({ ...current, country: value }))} />
setForm((current) => ({ ...current, zipCode: value.toUpperCase() }))} /> setForm((current) => ({ ...current, yearsActive: value }))} />
setStep(1)} onNext={() => setStep(3)} nextLabel={dict.continue} backLabel={dict.back} loadingLabel={dict.working} />
) : null} {step === 3 ? (
{(['STARTER', 'GROWTH', 'PRO'] as const).map((plan) => ( ))}
setForm((current) => ({ ...current, billingPeriod: value as SignupForm['billingPeriod'] }))} /> setForm((current) => ({ ...current, paymentProvider: value as SignupForm['paymentProvider'] }))} />
setStep(2)} onNext={() => setStep(4)} nextLabel={dict.continue} backLabel={dict.back} loadingLabel={dict.working} />
) : null} {step === 4 ? (

{dict.reviewWorkspace}: {form.companyName.fr || form.companyName.ar || dict.companyFallback}{form.companyName.fr && form.companyName.ar ? / {form.companyName.ar} : null}

{dict.reviewPlan}: {form.plan} · {dict.billingPeriodOptions[form.billingPeriod]} · MAD

{dict.reviewOwnerEmail}: {form.email || '—'}

{dict.reviewPhone}: {form.companyPhone || '—'}

setStep(3)} onNext={submitSignup} loading={loading} nextLabel={dict.createWorkspace} backLabel={dict.back} loadingLabel={dict.working} />
) : null}
) } function hasRequiredPartnerFields(form: SignupForm) { const bilingualFilled = (f: BilingualField) => f.fr.trim().length > 0 || f.ar.trim().length > 0 return ( bilingualFilled(form.companyName) && bilingualFilled(form.streetAddress) && bilingualFilled(form.city) && bilingualFilled(form.country) && [form.legalForm, form.registrationNumber, form.zipCode, form.companyPhone, form.yearsActive] .every((v) => v.trim().length > 0) ) } function SectionIntro({ title, body }: { title: string; body: string }) { return (

{title}

{body}

) } function getEmailWarning(emailDelivery: SignupResponse['emailDelivery'], language: 'en' | 'fr' | 'ar') { if (!emailDelivery) return null if (emailDelivery.success) return null if (emailDelivery.attempted) { if (language === 'fr') return `Le compte a été créé, mais l’e-mail de confirmation n’a pas pu être envoyé${emailDelivery.error ? ` : ${emailDelivery.error}` : '.'}` if (language === 'ar') return `تم إنشاء الحساب، لكن تعذر إرسال رسالة التأكيد${emailDelivery.error ? `: ${emailDelivery.error}` : '.'}` return `The account was created, but the confirmation email could not be delivered${emailDelivery.error ? `: ${emailDelivery.error}` : '.'}` } if (language === 'fr') return 'Le compte a été créé, mais l’envoi d’e-mails n’est pas encore configuré sur cet environnement.' if (language === 'ar') return 'تم إنشاء الحساب، لكن إرسال البريد الإلكتروني غير مهيأ بعد في هذه البيئة.' return 'The account was created, but email sending is not configured on this environment yet.' } function getErrorMessage(error: unknown, fallback: string) { if (error instanceof Error && error.message) return error.message return fallback } function normalizeTitleCase(value: string) { if (!value) return value const trimmedStart = value.replace(/^\s+/, '') if (!trimmedStart) return '' return trimmedStart.charAt(0).toUpperCase() + trimmedStart.slice(1).toLowerCase() } function normalizeEmail(value: string) { return value.replace(/\s+/g, '').toLowerCase() } function normalizeUpper(value: string) { return value.replace(/^\s+/, '').toUpperCase() } function LabeledInput({ label, value, onChange, type = 'text', required = false, maxLength, }: { label: string value: string onChange: (value: string) => void type?: string required?: boolean maxLength?: number }) { return ( ) } function LabeledSelect({ label, value, options, onChange, labels, required = false, }: { label: string value: string options: readonly string[] onChange: (value: string) => void labels?: Record required?: boolean }) { return ( ) } function NavActions({ onBack, onNext, loading = false, nextLabel = 'Continue', backLabel = 'Back', loadingLabel = 'Working…', }: { onBack?: () => void onNext: () => void loading?: boolean nextLabel?: string backLabel?: string loadingLabel?: string }) { return (
{onBack ? ( ) : null}
) } function normalizePlan(value: string | null): SignupForm['plan'] { if (value === 'GROWTH' || value === 'PRO' || value === 'STARTER') return value return 'STARTER' } function normalizeBillingPeriod(value: string | null): SignupForm['billingPeriod'] { return value === 'ANNUAL' ? 'ANNUAL' : 'MONTHLY' } function normalizeCurrency(_value: string | null): SignupForm['currency'] { return 'MAD' }