'use client' import { useEffect, useMemo, useRef, useState, useCallback } from 'react' import { useRouter } from 'next/navigation' import PageLayout from '../../../components/PageLayout' import useAuthStore from '../../../store/authStore' import { useUserStatus } from '../../../hooks/useUserStatus' import { useToast } from '../../../components/toast/toastComponent' import { ChevronDownIcon } from '@heroicons/react/20/solid' import TelephoneInput, { TelephoneInputHandle } from '../../../components/phone/telephoneInput' interface PersonalProfileData { firstName: string lastName: string email: string phone: string dob: string nationality: string street: string postalCode: string city: string country: string accountHolder: string iban: string secondPhone: string emergencyName: string emergencyPhone: string } // Common nationalities list const NATIONALITIES = [ 'German', 'Austrian', 'Swiss', 'Italian', 'French', 'Spanish', 'Portuguese', 'Dutch', 'Belgian', 'Polish', 'Czech', 'Hungarian', 'Croatian', 'Slovenian', 'Slovak', 'British', 'Irish', 'Swedish', 'Norwegian', 'Danish', 'Finnish', 'Russian', 'Turkish', 'Greek', 'Romanian', 'Bulgarian', 'Serbian', 'Albanian', 'Bosnian', 'American', 'Canadian', 'Brazilian', 'Argentinian', 'Mexican', 'Chinese', 'Japanese', 'Indian', 'Pakistani', 'Australian', 'South African', 'Other' ] // Common countries list const COUNTRIES = [ 'Germany', 'Austria', 'Switzerland', 'Italy', 'France', 'Spain', 'Portugal', 'Netherlands', 'Belgium', 'Poland', 'Czech Republic', 'Hungary', 'Croatia', 'Slovenia', 'Slovakia', 'United Kingdom', 'Ireland', 'Sweden', 'Norway', 'Denmark', 'Finland', 'Russia', 'Turkey', 'Greece', 'Romania', 'Bulgaria', 'Serbia', 'Albania', 'Bosnia and Herzegovina', 'United States', 'Canada', 'Brazil', 'Argentina', 'Mexico', 'China', 'Japan', 'India', 'Pakistan', 'Australia', 'South Africa', 'Other' ] const initialData: PersonalProfileData = { firstName: '', lastName: '', email: '', phone: '', dob: '', nationality: '', street: '', postalCode: '', city: '', country: '', accountHolder: '', iban: '', secondPhone: '', emergencyName: '', emergencyPhone: '' } type SelectOption = { value: string; label: string } function ModernSelect({ label, placeholder = 'Select…', value, onChange, options, }: { label: string placeholder?: string value: string onChange: (next: string) => void options: SelectOption[] }) { const [open, setOpen] = useState(false) const [query, setQuery] = useState('') const btnRef = useRef(null) const [pos, setPos] = useState({ left: 16, top: 0, width: 320 }) const selected = useMemo( () => options.find(o => o.value === value) || null, [options, value] ) const filtered = useMemo(() => { const q = query.trim().toLowerCase() if (!q) return options return options.filter(o => o.label.toLowerCase().includes(q)) }, [options, query]) useEffect(() => { if (!open) return const update = () => { const el = btnRef.current if (!el) return const r = el.getBoundingClientRect() const padding = 16 const width = Math.min(r.width, window.innerWidth - padding * 2) const left = Math.max(padding, Math.min(r.left, window.innerWidth - width - padding)) const top = r.bottom + 8 setPos({ left, top, width }) } update() window.addEventListener('resize', update) window.addEventListener('scroll', update, true) return () => { window.removeEventListener('resize', update) window.removeEventListener('scroll', update, true) } }, [open]) // close on escape useEffect(() => { if (!open) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') setOpen(false) } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) }, [open]) return (
{open && ( <> {/* click-away overlay */}
setOpen(false)} aria-hidden /> {/* dropdown (fixed so it “pops out under” even on mobile) */}
setQuery(e.target.value)} placeholder="Search…" className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#8D6B1D] focus:border-transparent" autoFocus />
{filtered.length === 0 ? (
No results
) : ( filtered.map(o => { const active = o.value === value return ( ) }) )}
)}
) } export default function PersonalAdditionalInformationPage() { const router = useRouter() const user = useAuthStore(s => s.user) // NEW const isAuthReady = useAuthStore(s => (s as any).isAuthReady) // NEW const { accessToken } = useAuthStore() const { userStatus, loading: statusLoading, refreshStatus } = useUserStatus() const { showToast } = useToast() const phoneRef = useRef(null) const secondPhoneRef = useRef(null) const emergencyPhoneRef = useRef(null) const [form, setForm] = useState(initialData) const [loading, setLoading] = useState(false) const [success, setSuccess] = useState(false) const [error, setError] = useState('') // Prefill form if profile already exists useEffect(() => { let abort = false async function loadProfile() { if (!accessToken) return try { const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/me`, { method: 'GET', headers: { Authorization: `Bearer ${accessToken}` } }) if (!res.ok) return const data = await res.json().catch(() => null) const profile = data?.profile const user = data?.user if ((!profile && !user) || abort) return const toDateInput = (d?: string) => { if (!d) return '' const dt = new Date(d) if (Number.isNaN(dt.getTime())) return '' return dt.toISOString().split('T')[0] } setForm(prev => ({ ...prev, firstName: user?.firstName || profile?.first_name || prev.firstName, lastName: user?.lastName || profile?.last_name || prev.lastName, email: user?.email || prev.email, phone: user?.phone || profile?.phone || prev.phone, dob: toDateInput(profile?.date_of_birth || profile?.dateOfBirth), nationality: profile?.nationality || prev.nationality, street: profile?.address || prev.street, postalCode: profile?.zip_code || profile?.zipCode || prev.postalCode, city: profile?.city || prev.city, country: profile?.country || prev.country, accountHolder: profile?.account_holder_name || profile?.accountHolderName || prev.accountHolder, // Prefer IBAN from users table (data.user.iban), fallback to profile if any iban: (user?.iban ?? profile?.iban ?? prev.iban) as string, secondPhone: profile?.phone_secondary || profile?.phoneSecondary || prev.secondPhone, emergencyName: profile?.emergency_contact_name || profile?.emergencyContactName || prev.emergencyName, emergencyPhone: profile?.emergency_contact_phone || profile?.emergencyContactPhone || prev.emergencyPhone, })) } catch (_) { // ignore prefill errors; user can still fill manually } } loadProfile() return () => { abort = true } }, [accessToken]) const handleChange = (e: React.ChangeEvent) => { const { name, value } = e.target setForm(p => ({ ...p, [name]: value })) setError('') } const validateDateOfBirth = (dob: string) => { if (!dob) return false const birthDate = new Date(dob) const today = new Date() // Check if date is valid if (isNaN(birthDate.getTime())) return false // Check if birth date is not in the future if (birthDate > today) return false // Check minimum age (18 years) const minDate = new Date() minDate.setFullYear(today.getFullYear() - 18) if (birthDate > minDate) return false // Check maximum age (120 years) const maxDate = new Date() maxDate.setFullYear(today.getFullYear() - 120) if (birthDate < maxDate) return false return true } const validate = () => { const requiredKeys: (keyof PersonalProfileData)[] = [ 'firstName','lastName','email','phone', 'dob','nationality','street','postalCode','city','country','accountHolder','iban' ] for (const k of requiredKeys) { if (!form[k].trim()) { const msg = 'Please fill in all required fields.' setError(msg) showToast({ variant: 'error', title: 'Missing information', message: msg, }) return false } } // Date of birth validation if (!validateDateOfBirth(form.dob)) { const msg = 'Invalid date of birth. You must be at least 18 years old.' setError(msg) showToast({ variant: 'error', title: 'Invalid date of birth', message: msg, }) return false } // very loose IBAN check if (!/^([A-Z]{2}\d{2}[A-Z0-9]{10,30})$/i.test(form.iban.replace(/\s+/g,''))) { const msg = 'Invalid IBAN.' setError(msg) showToast({ variant: 'error', title: 'Invalid IBAN', message: msg, }) return false } const phoneApi = phoneRef.current const dialCode = phoneApi?.getDialCode?.() const intlNumber = phoneApi?.getNumber() || '' const valid = phoneApi?.isValid() ?? false if (!dialCode) { const msg = 'Please select a country code for your phone number.' setError(msg) showToast({ variant: 'error', title: 'Missing country code', message: msg, }) return false } if (!intlNumber) { const msg = 'Please enter your phone number.' setError(msg) showToast({ variant: 'error', title: 'Missing phone number', message: msg, }) return false } if (!valid) { const msg = 'Please enter a valid phone number.' setError(msg) showToast({ variant: 'error', title: 'Invalid phone number', message: msg, }) return false } const optionalSecond = form.secondPhone.trim() if (optionalSecond) { const secondApi = secondPhoneRef.current const ok = secondApi?.isValid?.() ?? false if (!ok) { const msg = 'Please enter a valid second phone number.' setError(msg) showToast({ variant: 'error', title: 'Invalid phone number', message: msg, }) return false } } const optionalEmergency = form.emergencyPhone.trim() if (optionalEmergency) { const emergencyApi = emergencyPhoneRef.current const ok = emergencyApi?.isValid?.() ?? false if (!ok) { const msg = 'Please enter a valid emergency phone number.' setError(msg) showToast({ variant: 'error', title: 'Invalid phone number', message: msg, }) return false } } setError('') return true } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (loading || success) return if (!validate()) return if (!accessToken) { const msg = 'Not authenticated. Please log in again.' setError(msg) showToast({ variant: 'error', title: 'Authentication error', message: msg, }) return } setLoading(true) try { const normalizedPhone = phoneRef.current?.getNumber() || form.phone const normalizedSecondPhone = secondPhoneRef.current?.getNumber() || form.secondPhone const normalizedEmergencyPhone = emergencyPhoneRef.current?.getNumber() || form.emergencyPhone // Prepare data for backend with correct field names const profileData = { firstName: form.firstName, lastName: form.lastName, phone: normalizedPhone, dateOfBirth: form.dob, nationality: form.nationality, address: form.street, // Backend expects 'address', not nested object zip_code: form.postalCode, // Backend expects 'zip_code' city: form.city, country: form.country, phoneSecondary: normalizedSecondPhone || null, // Backend expects 'phoneSecondary' emergencyContactName: form.emergencyName || null, emergencyContactPhone: normalizedEmergencyPhone || null, accountHolderName: form.accountHolder, // Backend expects 'accountHolderName' iban: form.iban.replace(/\s+/g, '') // Remove spaces from IBAN } const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/profile/personal/complete`, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify(profileData) }) if (!response.ok) { const errorData = await response.json().catch(() => ({ message: 'Save failed' })) throw new Error(errorData.message || 'Save failed') } setSuccess(true) showToast({ variant: 'success', title: 'Profile saved', message: 'Your personal profile has been saved successfully.', }) // Refresh user status to update profile completion state suppressAutoRedirectRef.current = true await refreshStatus() // Redirect back to tutorial modal after short delay setTimeout(() => { smoothReplace('/quickaction-dashboard?tutorial=true') }, 1500) } catch (error: any) { console.error('Personal profile save error:', error) const msg = error.message || 'Save failed. Please try again.' setError(msg) showToast({ variant: 'error', title: 'Save failed', message: msg, }) } finally { setLoading(false) } } const setField = (name: keyof PersonalProfileData, value: string) => { setForm(p => ({ ...p, [name]: value })) setError('') } // NEW: smooth redirect const [redirectTo, setRedirectTo] = useState(null) const redirectOnceRef = useRef(false) const suppressAutoRedirectRef = useRef(false) const smoothReplace = useCallback((to: string) => { if (redirectOnceRef.current) return redirectOnceRef.current = true setRedirectTo(to) window.setTimeout(() => router.replace(to), 200) }, [router]) // NEW: hard block if step already done OR all steps done useEffect(() => { if (statusLoading || !userStatus) return if (suppressAutoRedirectRef.current) return const allDone = !!userStatus.email_verified && !!userStatus.documents_uploaded && !!userStatus.profile_completed && !!userStatus.contract_signed if (allDone) { smoothReplace('/dashboard') // CHANGED } else if (userStatus.profile_completed) { smoothReplace('/quickaction-dashboard') // CHANGED } }, [statusLoading, userStatus, smoothReplace]) // NEW: must be logged in useEffect(() => { if (!isAuthReady) return if (!user || !accessToken) smoothReplace('/login') }, [isAuthReady, user, accessToken, smoothReplace]) return ( {/* NEW: smooth redirect overlay */} {redirectTo && (
Redirecting…
Please wait
)}
{/* Animated background (same as dashboard) */}
{/* Soft gradient blobs */}
{/* Subtle radial highlight */}

Complete Your Profile

{/* Personal Information */}

Personal Information

setField('nationality', v)} options={NATIONALITIES.map(n => ({ value: n, label: n }))} />
setField('country', v)} options={COUNTRIES.map(c => ({ value: c, label: c }))} />

{/* Bank Details */}

Bank Details


{/* Additional Information */}

Additional Information

{error && (
{error}
)} {success && (
Data saved. Redirecting shortly…
)}
) }