'use client' import { useState, useEffect, useRef } from 'react' import { EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline' import { useRegister } from '../hooks/useRegister' import { useToast } from '../../components/toast/toastComponent' import TelephoneInput, { TelephoneInputHandle } from '../../components/phone/telephoneInput' import { useTranslation } from '../../i18n/useTranslation' interface RegisterFormProps { mode: 'personal' | 'company' | 'guest' setMode: (mode: 'personal' | 'company' | 'guest') => void refToken: string | null onRegistered: () => void referrerEmail?: string } interface PersonalFormData { firstName: string lastName: string email: string confirmEmail: string password: string confirmPassword: string phoneNumber: string } interface CompanyFormData { companyName: string companyEmail: string confirmCompanyEmail: string companyPhone: string contactPersonName: string contactPersonPhone: string password: string confirmPassword: string } export default function RegisterForm({ mode, setMode, refToken, onRegistered, referrerEmail }: RegisterFormProps) { // Personal form state const [personalForm, setPersonalForm] = useState({ firstName: '', lastName: '', email: '', confirmEmail: '', password: '', confirmPassword: '', phoneNumber: '' }) // Company form state const [companyForm, setCompanyForm] = useState({ companyName: '', companyEmail: '', confirmCompanyEmail: '', companyPhone: '', contactPersonName: '', contactPersonPhone: '', password: '', confirmPassword: '' }) // UI state const [showPersonalPassword, setShowPersonalPassword] = useState(false) const [showCompanyPassword, setShowCompanyPassword] = useState(false) const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [formFade, setFormFade] = useState('fade-in') // Phone input refs (to access intl-tel-input via TelephoneInput) const personalPhoneRef = useRef(null) const companyPhoneRef = useRef(null) const contactPhoneRef = useRef(null) // Hook for backend calls const { registerPersonalReferral, registerCompanyReferral, registerGuest, error: regError } = useRegister() const { showToast } = useToast() const { t } = useTranslation() // Guest form state const [guestForm, setGuestForm] = useState({ firstName: '', lastName: '', email: '', confirmEmail: '', password: '', confirmPassword: '', }) // Animate form when mode changes useEffect(() => { setFormFade('fade-out') const timeout = setTimeout(() => { setFormFade('fade-in') }, 180) return () => clearTimeout(timeout) }, [mode]) // Add fade CSS useEffect(() => { const style = document.createElement('style') style.innerHTML = ` .fade-in { opacity: 1; transform: translateY(0); transition: opacity 180ms, transform 180ms; } .fade-out { opacity: 0; transform: translateY(20px); transition: opacity 180ms, transform 180ms; } ` document.head.appendChild(style) return () => { document.head.removeChild(style) } }, []) // Reset forms when switching modes useEffect(() => { setError('') }, [mode]) // Validation helpers const validatePersonalForm = (): boolean => { if (!personalForm.firstName.trim() || !personalForm.lastName.trim() || !personalForm.email.trim() || !personalForm.confirmEmail.trim() || !personalForm.password.trim() || !personalForm.confirmPassword.trim() ) { setError(t('register.errorAllRequired')) return false } if (personalForm.email !== personalForm.confirmEmail) { setError(t('register.errorEmailMismatch')) return false } if (personalForm.password !== personalForm.confirmPassword) { setError(t('register.errorPasswordMismatch')) return false } if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/.test(personalForm.password)) { setError(t('register.errorPasswordWeak')) return false } const phoneApi = personalPhoneRef.current const dialCode = phoneApi?.getDialCode?.() const intlNumber = phoneApi?.getNumber() || '' const valid = phoneApi?.isValid() ?? false if (!dialCode) { setError(t('register.errorSelectCountryCode')) return false } if (!intlNumber) { setError(t('register.errorPhoneRequired')) return false } if (!valid) { setError(t('register.errorPhoneInvalid')) return false } setError('') return true } const validateCompanyForm = (): boolean => { if (!companyForm.companyName.trim() || !companyForm.companyEmail.trim() || !companyForm.confirmCompanyEmail.trim() || !companyForm.contactPersonName.trim() || !companyForm.password.trim() || !companyForm.confirmPassword.trim() ) { setError(t('register.errorAllRequired')) return false } if (companyForm.companyEmail !== companyForm.confirmCompanyEmail) { setError(t('register.errorEmailMismatch')) return false } if (companyForm.password !== companyForm.confirmPassword) { setError(t('register.errorPasswordMismatch')) return false } if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/.test(companyForm.password)) { setError(t('register.errorPasswordWeak')) return false } const companyApi = companyPhoneRef.current const contactApi = contactPhoneRef.current const companyDialCode = companyApi?.getDialCode?.() const contactDialCode = contactApi?.getDialCode?.() const companyNumber = companyApi?.getNumber() || '' const contactNumber = contactApi?.getNumber() || '' const companyValid = companyApi?.isValid() ?? false const contactValid = contactApi?.isValid() ?? false if (!companyDialCode || !contactDialCode) { setError(t('register.errorBothCountryCodes')) return false } if (!companyNumber || !contactNumber) { setError(t('register.errorBothPhonesRequired')) return false } if (!companyValid || !contactValid) { setError(t('register.errorBothPhonesInvalid')) return false } setError('') return true } // Submit handlers const handlePersonalSubmit = async (e: React.FormEvent) => { e.preventDefault() if (loading) return if (!validatePersonalForm()) return setLoading(true) setError('') try { const normalizedPhone = personalPhoneRef.current?.getNumber() || personalForm.phoneNumber console.log('[RegisterForm] handlePersonalSubmit normalized phone', { normalizedPhone, }) const res = await registerPersonalReferral({ refToken: refToken || '', firstName: personalForm.firstName, lastName: personalForm.lastName, email: personalForm.email, password: personalForm.password, phone: normalizedPhone, }) if (res.ok) { showToast({ variant: 'success', title: t('register.successTitle'), message: t('register.successMessage') }) onRegistered() } else { const msg = res.message || t('register.failedMessage') setError(msg) showToast({ variant: 'error', title: t('register.failedTitle'), message: msg }) } } catch (error) { const msg = t('register.failedMessage') setError(msg) showToast({ variant: 'error', title: t('register.failedTitle'), message: msg }) } finally { setLoading(false) } } const handleCompanySubmit = async (e: React.FormEvent) => { e.preventDefault() if (loading) return if (!validateCompanyForm()) return setLoading(true) setError('') try { const normalizedCompanyPhone = companyPhoneRef.current?.getNumber() || companyForm.companyPhone const normalizedContactPhone = contactPhoneRef.current?.getNumber() || companyForm.contactPersonPhone console.log('[RegisterForm] handleCompanySubmit normalized phones', { normalizedCompanyPhone, normalizedContactPhone, }) const res = await registerCompanyReferral({ refToken: refToken || '', companyEmail: companyForm.companyEmail, password: companyForm.password, companyName: companyForm.companyName, companyPhone: normalizedCompanyPhone, contactPersonName: companyForm.contactPersonName, contactPersonPhone: normalizedContactPhone, }) if (res.ok) { showToast({ variant: 'success', title: t('register.successTitle'), message: t('register.successCompanyMessage') }) onRegistered() } else { const msg = res.message || t('register.failedMessage') setError(msg) showToast({ variant: 'error', title: t('register.failedTitle'), message: msg }) } } catch (error) { const msg = t('register.failedMessage') setError(msg) showToast({ variant: 'error', title: t('register.failedTitle'), message: msg }) } finally { setLoading(false) } } // Surface hook error if present and no local error useEffect(() => { if (regError && !error) { setError(regError) showToast({ variant: 'error', title: t('register.failedTitle'), message: regError }) } }, [regError]) // eslint-disable-line react-hooks/exhaustive-deps // Input change handlers const handlePersonalChange = (e: React.ChangeEvent) => { const { name, value } = e.target setPersonalForm(prev => ({ ...prev, [name]: value })) } const handleCompanyChange = (e: React.ChangeEvent) => { const { name, value } = e.target setCompanyForm(prev => ({ ...prev, [name]: value })) } const handleGuestChange = (e: React.ChangeEvent) => { const { name, value } = e.target setGuestForm(prev => ({ ...prev, [name]: value })) } const validateGuestForm = (): boolean => { if (!guestForm.firstName.trim() || !guestForm.lastName.trim() || !guestForm.email.trim() || !guestForm.confirmEmail.trim() || !guestForm.password.trim() || !guestForm.confirmPassword.trim() ) { setError(t('register.errorAllRequired')) return false } if (guestForm.email !== guestForm.confirmEmail) { setError(t('register.errorEmailMismatch')) return false } if (guestForm.password !== guestForm.confirmPassword) { setError(t('register.errorPasswordMismatch')) return false } if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/.test(guestForm.password)) { setError(t('register.errorPasswordWeak')) return false } setError('') return true } const handleGuestSubmit = async (e: React.FormEvent) => { e.preventDefault() if (loading) return if (!validateGuestForm()) return setLoading(true) setError('') try { const res = await registerGuest({ refToken: refToken || '', firstName: guestForm.firstName, lastName: guestForm.lastName, email: guestForm.email, password: guestForm.password, }) if (res.ok) { showToast({ variant: 'success', title: t('register.successTitle'), message: t('register.successGuestMessage') }) onRegistered() } else { const msg = res.message || t('register.failedMessage') setError(msg) showToast({ variant: 'error', title: t('register.failedTitle'), message: msg }) } } catch { const msg = t('register.failedMessage') setError(msg) showToast({ variant: 'error', title: t('register.failedTitle'), message: msg }) } finally { setLoading(false) } } // Password strength indicator const getPasswordStrength = (password: string) => { let strength = 0 if (password.length >= 8) strength++ if (/[a-z]/.test(password)) strength++ if (/[A-Z]/.test(password)) strength++ if (/\d/.test(password)) strength++ if (/[\W_]/.test(password)) strength++ return strength } const renderPasswordStrength = (password: string) => { const strength = getPasswordStrength(password) const rules = [ { test: password.length >= 8, text: t('register.pwdMinLength') }, { test: /[a-z]/.test(password), text: t('register.pwdLowercase') }, { test: /[A-Z]/.test(password), text: t('register.pwdUppercase') }, { test: /\d/.test(password), text: t('register.pwdDigits') }, { test: /[\W_]/.test(password), text: t('register.pwdSpecial') } ] return (
{t('register.passwordRequirements')}
    {rules.map((rule, index) => (
  • {rule.test ? '✓' : '○'} {rule.text}
  • ))}
) } return ( // softened outer container, no own solid white card – parent provides glass card
{/* Header */}

{t('register.formTitle')}

{referrerEmail && (

{t('register.invitedBy')} {referrerEmail}!

)}
{/* Mode Toggle */}
{mode === 'guest' ? ( ) : ( <> )}
{/* Error Message */} {error && (

{error}

)} {/* Forms */}
{mode === 'personal' ? (
setPersonalForm(prev => ({ ...prev, phoneNumber: (e.target as HTMLInputElement).value })) } />
{personalForm.password && renderPasswordStrength(personalForm.password)}
) : mode === 'company' ? (
setCompanyForm(prev => ({ ...prev, companyPhone: (e.target as HTMLInputElement).value })) } />
setCompanyForm(prev => ({ ...prev, contactPersonPhone: (e.target as HTMLInputElement).value, })) } />
{companyForm.password && renderPasswordStrength(companyForm.password)}
) : (

{t('register.guestNote')}

{guestForm.password && renderPasswordStrength(guestForm.password)}
)}
{/* Login Link */}

{t('register.alreadyHaveAccount')}{' '} {t('register.loginHere')}

) }