import { useEffect, useMemo, useState } from 'react'; import type { Account } from '../lib/auth'; import { useI18n, type Key } from '../lib/i18n'; import { DEFAULT_PROFILE, PRESETS, activePreset, bucket } from '../lib/profile'; import type { Profile, ProfileKind, Verdict } from '../lib/types'; import { Icon, InfoTip, VERDICT_STYLE } from './ui'; interface Props { account: Account; /** Saved profile; null until the user finishes onboarding (guests, new accounts). */ profile: Profile | null; onChange: (p: Profile) => void; onDone: () => void; onSignOut: () => void; preview: (p: Profile) => Record; children?: React.ReactNode; } const VERDICTS: Verdict[] = ['match', 'partial', 'conflict', 'incomplete', 'fail']; export function ProfileView({ account, profile, onChange, onDone, onSignOut, preview, children }: Props) { const { t } = useI18n(); const [draft, setDraft] = useState(profile ?? DEFAULT_PROFILE[account.preset ?? 'wheelchair']); const update = (p: Profile) => { setDraft(p); if (profile) onChange(p); }; const set = (k: K, v: Profile[K]) => update({ ...draft, [k]: v }); // Debounced live preview: how many places fall into each verdict with these limits. const [counts, setCounts] = useState | null>(null); useEffect(() => { const id = setTimeout(() => setCounts(preview(draft)), 250); return () => clearTimeout(id); }, [draft, preview]); const active = activePreset(draft); const presets = PRESETS.filter((p) => p.kind === draft.kind); const kerbHint = `hint.kerb.${bucket(draft.maxKerbCm, [0, 3, 6, 10])}` as Key; const widthHint = `hint.width.${bucket(Math.round(draft.minWidthM * 100), [65, 80, 90, 100])}` as Key; const inclineHint = `hint.incline.${bucket(draft.maxInclinePct, [5, 8, 12])}` as Key; const summary = useMemo(() => { let s = t('profile.summary.text', { kerb: draft.maxKerbCm, width: Math.round(draft.minWidthM * 100), incline: draft.maxInclinePct }); if (draft.avoidCobbles) s += t('profile.summary.cobbles'); if (draft.needToilet) s += t('profile.summary.toilet'); if (draft.needRest) s += t('profile.summary.rest'); if (draft.needChanging && draft.kind === 'stroller') s += t('profile.summary.changing'); return s; }, [draft, t]); return (

{profile ? t('profile.title') : t('profile.welcome')}

{t('profile.intro')}

{account.id === 'guest' ? t('auth.role.guest') : t(`auth.${account.id}` as Key)} {t(`auth.role.${account.role}` as Key)} {account.id !== 'guest' && {t('auth.demoBadge')}}

{t('profile.step1')}

{t('profile.step1')}
{(['wheelchair', 'stroller'] as ProfileKind[]).map((k) => (
update(DEFAULT_PROFILE[k])} />
))}

{t('profile.step2')}

{t('profile.step2')}
{presets.map((p) => (
update({ ...p.values, ...keepPrefs(draft) })} />
))} {active === 'custom' && (
)}

{t('profile.step3')}

{t('profile.step3.help')}

set('maxKerbCm', v)} /> set('minWidthM', v / 100)} /> set('maxInclinePct', v)} />

{t('profile.step4')}

set('avoidCobbles', v)} /> set('needToilet', v)} /> set('needRest', v)} /> {draft.kind === 'stroller' && set('needChanging', v)} />}

{t('profile.summary')}

{summary}

{t('profile.preview')}

    {counts && VERDICTS.map((v) => (
  • {t('profile.preview.n', { n: counts[v], label: t(`verdict.count.${v}` as Key) })}
  • ))}

{t('profile.privacy')} {profile && t('profile.autosave')}

{children}
); } const keepPrefs = (p: Profile) => ({ avoidCobbles: p.avoidCobbles, needToilet: p.needToilet, needRest: p.needRest, needChanging: p.needChanging }); function Slider({ id, label, tip, min, max, step, value, unit, hint, minLabel, maxLabel, onChange }: { id: string; label: string; tip: string; min: number; max: number; step: number; value: number; unit: string; hint: string; minLabel: string; maxLabel: string; onChange: (v: number) => void; }) { return (
onChange(Number(e.target.value))} />

{hint}

); } function Check({ id, label, tip, checked, onChange }: { id: string; label: string; tip: string; checked: boolean; onChange: (v: boolean) => void }) { return (
onChange(e.target.checked)} />
); }