'use client'; import { FormEvent, FunctionComponent, useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { useLocale, useTranslations } from 'next-intl'; import { Avatar, Box, InputAdornment, Paper, Skeleton, Stack, TextField, Typography } from '@mui/material'; import { AppButton, AppIcon, AppLoading, CategoryTile } from '@/components'; import { ROUTES } from '@/constants'; import { useMe } from '@/services/auth'; import { usePatients } from '@/services/patients'; import { useServiceCategories } from '@/services/catalog'; import { pickCatalogName } from '@/services/catalog/names'; interface NudgeCardProps { icon: string; title: string; body: string; ctaLabel: string; to: string; } const NudgeCard: FunctionComponent = ({ icon, title, body, ctaLabel, to }) => ( {title} {body} {ctaLabel} ); /** * A5 — the family Home: the front door of the app. Greeting + avatar, the search bar (which hands a * query / chosen `service_category_id` toward the f6 search flow — results are not built here), the * **data-driven** service-category grid (from the cached `services/catalog` reference data), and the * complete-patient-record nudge (derived from the f2 patient cache — no extra fetch). * * First-login gate: a customer with no patients is sent into onboarding (A3). The redirect waits for * a settled list so a post-create refetch never bounces the user back to onboarding. */ export default function CustomerHomePage() { const t = useTranslations('home'); const router = useRouter(); const locale = useLocale(); const { data: me } = useMe(); const { data } = usePatients(); const isEmpty = data?.total === 0; useEffect(() => { if (isEmpty) router.replace(`/${locale}${ROUTES.ONBOARDING}`); }, [isEmpty, router, locale]); if (data == null || isEmpty) { return ; } const href = (path: string) => `/${locale}${path}`; const profileComplete = me?.hasCustomerProfile ?? false; const firstName = me?.firstName?.trim() || null; const greeting = firstName ? t('greeting_named', { name: firstName }) : t('greeting_plain'); const avatarInitial = firstName ? firstName.charAt(0).toUpperCase() : null; return ( {avatarInitial ?? } {greeting} {t('subtitle')} router.push(`${href(ROUTES.SEARCH)}?category_id=${categoryId}`)} /> {!profileComplete ? ( ) : null} ); } /** * The Home search field. Rendering + query capture live here; **execution is f6** — submitting * navigates toward the (future) search route carrying the typed query. Results/filters are DEFERRED * → frontend-phase-6-b7. */ const HomeSearchBar: FunctionComponent = () => { const t = useTranslations('home'); const router = useRouter(); const locale = useLocale(); const [query, setQuery] = useState(''); const submit = (event: FormEvent) => { event.preventDefault(); const q = query.trim(); router.push(`/${locale}${ROUTES.SEARCH}${q ? `?q=${encodeURIComponent(q)}` : ''}`); }; return ( setQuery(event.target.value)} placeholder={t('search_placeholder')} aria-label={t('search_action')} slotProps={{ input: { startAdornment: ( ), }, }} /> ); }; /** The data-driven service-category grid — one tile per `service_category`, with all four states. */ const CategoryGrid: FunctionComponent<{ onSelect: (categoryId: number) => void }> = ({ onSelect }) => { const t = useTranslations('home'); const tc = useTranslations('common'); const locale = useLocale(); const { data, isLoading, isError, refetch } = useServiceCategories(); const categories = data?.items ?? []; return ( {t('categories_title')} {isLoading ? ( {[0, 1, 2, 3].map((key) => ( ))} ) : isError ? ( {t('categories_error')} refetch()} sx={{ m: 0 }}> {tc('retry')} ) : categories.length === 0 ? ( {t('categories_empty')} ) : ( {categories.map((category) => ( onSelect(category.id)} /> ))} )} ); };