'use client'; import { type CookingTimeLevel, type CuisineTag, type ExclusionGroup, type PreferencesPatch, } from '@ketopath/shared'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useTranslations } from 'next-intl'; import { useEffect, useState } from 'react'; import { DEFAULT_PREFERENCES_VALUES, PreferencesForm, type PreferencesValues, } from '@/components/preferences/preferences-form'; import { Button } from '@/components/ui/button'; import { pushSupported, subscribe, getCurrentSubscription } from '@/lib/notifications/push-client'; import { regeneratePlan } from '../plan/actions'; import { savePreferences, type PreferencesView } from '../profile/preferences-actions'; import { ProfileForm as ProfileFormComponent } from '../profile/profile-form'; import type { ProfileForm } from '../profile/profile-form'; const STEPS = ['goal', 'profile', 'preferences', 'notifications', 'done'] as const; type StepKey = (typeof STEPS)[number]; const GOALS = ['LOSE', 'MAINTAIN', 'ENERGY'] as const; type Goal = (typeof GOALS)[number]; interface OnboardingFlowProps { initialProfile: Parameters[0]['initial']; initialPreferences: PreferencesView | null; } export function OnboardingFlow({ initialProfile, initialPreferences }: OnboardingFlowProps) { const t = useTranslations('Onboarding'); const [step, setStep] = useState(() => { if (!initialProfile) return 'goal'; if (!initialPreferences || initialPreferences.exclusions.length === 0) return 'preferences'; return 'done'; }); const [goal, setGoal] = useState(null); const stepIndex = STEPS.indexOf(step); return (
{step === 'goal' ? ( setStep('profile')} /> ) : null} {step === 'profile' ? ( setStep('preferences')} /> ) : null} {step === 'preferences' ? ( setStep('notifications')} onBack={() => setStep('profile')} /> ) : null} {step === 'notifications' ? setStep('done')} /> : null} {step === 'done' ? : null}
); } function StepGoal({ selected, onSelect, onContinue, }: { selected: Goal | null; onSelect: (g: Goal) => void; onContinue: () => void; }) { const t = useTranslations('Onboarding'); return (

{t('stepEyebrow', { n: 1, total: 5 })}

{t('goalHeading')}

{t('goalHint')}

{GOALS.map((g, i) => ( ))}
); } function StepProfile({ initial, goal, onSaved, }: { initial: Parameters[0]['initial']; goal: Goal | null; onSaved: () => void; }) { const t = useTranslations('Onboarding'); return (

{t('stepEyebrow', { n: 2, total: 5 })}

{goal ? t(`profileHeadingGoal.${goal}`) : t('profileHeading')}

{t('profileHint')}

); } function StepPreferences({ initial, onSaved, onBack, }: { initial: PreferencesValues; onSaved: () => void; onBack: () => void; }) { const t = useTranslations('Onboarding'); async function handleSubmit(patch: PreferencesPatch) { const result = await savePreferences(patch); if (result.ok) onSaved(); return result; } return (

{t('stepEyebrow', { n: 3, total: 5 })}

{t('preferencesHeading')}

{t('preferencesHint')}

); } function StepNotifications({ onContinue }: { onContinue: () => void }) { const t = useTranslations('Onboarding'); const [supported, setSupported] = useState(true); const [permission, setPermission] = useState('unknown'); const [hasSub, setHasSub] = useState(false); const [pending, setPending] = useState(false); const [error, setError] = useState(null); useEffect(() => { setSupported(pushSupported()); if (typeof Notification !== 'undefined') setPermission(Notification.permission); void getCurrentSubscription().then((s) => setHasSub(!!s)); }, []); async function activate(): Promise { setError(null); setPending(true); const result = await subscribe(); setPending(false); if (!result.ok) { setError(t('notificationsError')); return; } setHasSub(true); setPermission('granted'); } return (

{t('stepEyebrow', { n: 4, total: 5 })}

{t('notificationsHeading')}

{t('notificationsHint')}

{!supported ? (

{t('notificationsUnsupported')}

) : hasSub ? (

{t('notificationsActive')}

) : permission === 'denied' ? (

{t('notificationsBlocked')}

) : ( )} {error ? (

{error}

) : null}
); } function StepDone({ goal }: { goal: Goal | null }) { const t = useTranslations('Onboarding'); const router = useRouter(); const [planStatus, setPlanStatus] = useState<'idle' | 'pending' | 'ready' | 'error'>('idle'); const [iosInstallHint, setIosInstallHint] = useState(false); // Genera il primo piano se non esiste — best-effort. useEffect(() => { if (planStatus !== 'idle') return; setPlanStatus('pending'); regeneratePlan() .then(() => setPlanStatus('ready')) .catch(() => setPlanStatus('error')); }, [planStatus]); // iOS Safari non-standalone: serve "Aggiungi alla Home" per le push. useEffect(() => { const ua = navigator.userAgent; const isIOS = /iPad|iPhone|iPod/i.test(ua) && !/CriOS|FxiOS|EdgiOS/i.test(ua); const standalone = window.matchMedia('(display-mode: standalone)').matches || // legacy iOS: window.navigator.standalone (window.navigator as Navigator & { standalone?: boolean }).standalone === true; setIosInstallHint(isIOS && !standalone); }, []); return (

{t('stepEyebrow', { n: 5, total: 5 })}

{goal ? t(`doneHeadingGoal.${goal}`) : t('doneHeading')}

{planStatus === 'pending' ? t('doneGenerating') : planStatus === 'ready' ? t('donePlanReady') : t('doneHint')}

{iosInstallHint ? (

{t('iosInstallEyebrow')}

{t('iosInstallTitle')}

  1. {t('iosInstallStep1')}
  2. {t('iosInstallStep2')}
  3. {t('iosInstallStep3')}
) : null}
{t('skipToHome')}
); } function NextCard({ eyebrow, title, hint, href, }: { eyebrow: string; title: string; hint: string; href: string; }) { return ( {eyebrow} {title} {hint} → ); } function toFormValues(view: PreferencesView): PreferencesValues { return { exclusions: view.exclusions as ExclusionGroup[], cuisinePreferences: view.cuisinePreferences as CuisineTag[], cookingTime: view.cookingTime as CookingTimeLevel, fastingProtocol: view.fastingProtocol as PreferencesValues['fastingProtocol'], }; }