'use client'; import { useState, useEffect, Suspense } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import Link from 'next/link'; import Image from 'next/image'; import { register } from '@/lib/api'; import { verifyInvitation, type InvitationResponse } from '@/lib/api/invitations'; import type { OrganizationType } from '@/types/api'; function RegisterPageContent() { const router = useRouter(); const searchParams = useSearchParams(); // Step management const [step, setStep] = useState<1 | 2>(1); // Step 1 — Personal info const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); // Step 2 — Organization const [organizationName, setOrganizationName] = useState(''); const [organizationType, setOrganizationType] = useState('FREIGHT_FORWARDER'); const [siren, setSiren] = useState(''); const [siret, setSiret] = useState(''); const [street, setStreet] = useState(''); const [city, setCity] = useState(''); const [state, setState] = useState(''); const [postalCode, setPostalCode] = useState(''); const [country, setCountry] = useState('FR'); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); // Invitation state const [invitationToken, setInvitationToken] = useState(null); const [invitation, setInvitation] = useState(null); const [isVerifyingInvitation, setIsVerifyingInvitation] = useState(false); useEffect(() => { const token = searchParams.get('token'); if (token) { setIsVerifyingInvitation(true); verifyInvitation(token) .then(invitationData => { setInvitation(invitationData); setInvitationToken(token); setEmail(invitationData.email); setFirstName(invitationData.firstName); setLastName(invitationData.lastName); }) .catch(() => { setError("Le lien d'invitation est invalide ou expiré."); }) .finally(() => { setIsVerifyingInvitation(false); }); } }, [searchParams]); // ---- Step 1 validation ---- const validateStep1 = (): string | null => { if (!firstName.trim() || firstName.trim().length < 2) return 'Le prénom doit contenir au moins 2 caractères'; if (!lastName.trim() || lastName.trim().length < 2) return 'Le nom doit contenir au moins 2 caractères'; if (!email.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return "L'adresse email n'est pas valide"; if (password.length < 12) return 'Le mot de passe doit contenir au moins 12 caractères'; if (password !== confirmPassword) return 'Les mots de passe ne correspondent pas'; return null; }; const handleStep1 = (e: React.FormEvent) => { e.preventDefault(); setError(''); const err = validateStep1(); if (err) { setError(err); return; } // If invitation — submit directly (no org step) if (invitationToken) { handleFinalSubmit(); } else { setStep(2); } }; // ---- Step 2 validation ---- const validateStep2 = (): string | null => { if (!organizationName.trim()) return "Le nom de l'organisation est requis"; if (!/^[0-9]{9}$/.test(siren)) return 'Le numéro SIREN est requis (9 chiffres)'; if (siret && !/^[0-9]{14}$/.test(siret)) return 'Le numéro SIRET doit contenir 14 chiffres'; if (!street.trim() || !city.trim() || !postalCode.trim() || !country.trim()) { return "Tous les champs d'adresse sont requis"; } return null; }; const handleStep2 = (e: React.FormEvent) => { e.preventDefault(); setError(''); const err = validateStep2(); if (err) { setError(err); return; } handleFinalSubmit(); }; // ---- Final submit ---- const handleFinalSubmit = async () => { setIsLoading(true); setError(''); try { await register({ email, password, firstName, lastName, ...(invitationToken ? { invitationToken } : { organization: { name: organizationName, type: organizationType, siren, siret: siret || undefined, street, city, state: state || undefined, postalCode, country: country.toUpperCase(), }, }), }); router.push('/dashboard'); } catch (err: any) { setError(err.message || 'Erreur lors de la création du compte'); // On error at step 2, stay on step 2; at invitation (step 1), stay on step 1 } finally { setIsLoading(false); } }; // ---- Right panel content ---- const rightPanel = (

{invitation ? 'Rejoignez votre équipe' : 'Rejoignez des milliers d\'entreprises'}

Simplifiez votre logistique maritime et gagnez du temps sur chaque expédition.

Essai gratuit de 30 jours

Testez toutes les fonctionnalités sans engagement

Sécurité maximale

Vos données sont protégées et chiffrées

Support 24/7

Notre équipe est là pour vous accompagner

2k+
Entreprises
150+
Pays couverts
24/7
Support
); return (
{/* Left Side - Form */}
{/* Logo */}
Xpeditis
{/* Progress indicator (only for self-registration, 2 steps) */} {!invitation && (
= 1 ? 'bg-brand-navy text-white' : 'bg-neutral-100 text-neutral-400' }`}> {step > 1 ? ( ) : '1'}
= 1 ? 'text-brand-navy' : 'text-neutral-400'}`}> Votre compte
= 2 ? 'bg-brand-navy' : 'bg-neutral-200'}`} />
= 2 ? 'bg-brand-navy text-white' : 'bg-neutral-100 text-neutral-400' }`}> 2
= 2 ? 'text-brand-navy' : 'text-neutral-400'}`}> Votre organisation
)} {/* Header */}
{isVerifyingInvitation ? (

Vérification de l'invitation...

) : invitation ? ( <>

Accepter l'invitation

Invitation valide — créez votre mot de passe pour rejoindre l'organisation.

) : step === 1 ? ( <>

Créer un compte

Commencez votre essai gratuit dès aujourd'hui

) : ( <>

Votre organisation

Renseignez les informations de votre entreprise

)}
{/* Error Message */} {error && (

{error}

)} {/* ---- STEP 1: Personal info ---- */} {(step === 1 || invitation) && !isVerifyingInvitation && (
setFirstName(e.target.value)} className="input w-full" placeholder="Jean" disabled={isLoading || !!invitation} />
setLastName(e.target.value)} className="input w-full" placeholder="Dupont" disabled={isLoading || !!invitation} />
setEmail(e.target.value)} className="input w-full" placeholder="jean.dupont@entreprise.com" autoComplete="email" disabled={isLoading || !!invitation} />
setPassword(e.target.value)} className="input w-full" placeholder="••••••••••••" autoComplete="new-password" disabled={isLoading} />

Au moins 12 caractères

setConfirmPassword(e.target.value)} className="input w-full" placeholder="••••••••••••" autoComplete="new-password" disabled={isLoading} />

En créant un compte, vous acceptez nos{' '} Conditions d'utilisation{' '} et notre{' '} Politique de confidentialité

)} {/* ---- STEP 2: Organization info ---- */} {step === 2 && !invitation && (
setOrganizationName(e.target.value)} className="input w-full" placeholder="Acme Logistics" disabled={isLoading} />
setSiren(e.target.value.replace(/\D/g, '').slice(0, 9))} className="input w-full" placeholder="123456789" maxLength={9} disabled={isLoading} />

9 chiffres

setSiret(e.target.value.replace(/\D/g, '').slice(0, 14))} className="input w-full" placeholder="12345678900014" maxLength={14} disabled={isLoading} />

14 chiffres

setStreet(e.target.value)} className="input w-full" placeholder="123 Rue de la République" disabled={isLoading} />
setCity(e.target.value)} className="input w-full" placeholder="Paris" disabled={isLoading} />
setPostalCode(e.target.value)} className="input w-full" placeholder="75001" disabled={isLoading} />
setState(e.target.value)} className="input w-full" placeholder="Île-de-France" disabled={isLoading} />
setCountry(e.target.value.toUpperCase().slice(0, 2))} className="input w-full" placeholder="FR" maxLength={2} disabled={isLoading} />

Code ISO 2 lettres

En créant un compte, vous acceptez nos{' '} Conditions d'utilisation{' '} et notre{' '} Politique de confidentialité

)} {/* Sign In Link */}

Vous avez déjà un compte ?{' '} Se connecter

{/* Footer Links */}
Contactez-nous Confidentialité Conditions
{rightPanel}
); } export default function RegisterPage() { return ( ); }