138 lines
4.8 KiB
TypeScript
138 lines
4.8 KiB
TypeScript
'use client';
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { useLocale, useTranslations } from 'next-intl';
|
|
import { useSnackbar } from 'notistack';
|
|
import { Box, Stack, Typography } from '@mui/material';
|
|
import { AppButton, AppIcon, PatientForm, RelationSelect, StepperHeader } from '@/components';
|
|
import BrandMark from '@/components/auth/BrandMark';
|
|
import { ROUTES } from '@/constants';
|
|
import { useCreatePatient } from '@/services/patients';
|
|
import { RELATION_CODES } from '@/services/patients/constants';
|
|
import type { CreatePatientInput, Relation } from '@/services/patients/types';
|
|
|
|
const ONBOARDING_MAX_WIDTH = 520;
|
|
|
|
// Distinct per-option glyph (the prior defect: all four relations shared the generic 'account'
|
|
// icon) — 'elderly' fits a parent, 'favorite' a spouse, 'infant' a child, 'account' one's self.
|
|
const RELATION_ICONS: Record<string, string> = {
|
|
parent: 'elderly',
|
|
spouse: 'favorite',
|
|
child: 'infant',
|
|
self: 'account',
|
|
};
|
|
|
|
type Phase = 'welcome' | 'relation' | 'patient';
|
|
|
|
/**
|
|
* The chrome-free A3 → A4 first-run journey: a one-screen welcome moment, then pick who care is
|
|
* for, then register the first patient. `FocusedLayout` (the route group above this) strips the
|
|
* bottom nav/bell so there's nothing to tab away to mid-setup. The welcome screen doesn't count
|
|
* as a stepper step; relation → patient does. The chosen relation pre-shapes the patient (hidden
|
|
* on the A4 form since it's already chosen here). On save it creates the patient and lands on
|
|
* Home (A5).
|
|
*/
|
|
export default function OnboardingScreen() {
|
|
const t = useTranslations('onboarding');
|
|
const tc = useTranslations('common');
|
|
const router = useRouter();
|
|
const locale = useLocale();
|
|
const { enqueueSnackbar } = useSnackbar();
|
|
const createPatient = useCreatePatient();
|
|
|
|
const [phase, setPhase] = useState<Phase>('welcome');
|
|
const [relation, setRelation] = useState<Relation | null>(null);
|
|
|
|
const relationOptions = RELATION_CODES.map((code) => ({
|
|
code,
|
|
label: t(`relation_${code}`),
|
|
icon: RELATION_ICONS[code] ?? 'account',
|
|
}));
|
|
|
|
const handleCreate = (input: CreatePatientInput) => {
|
|
createPatient.mutate(
|
|
{ ...input, relation },
|
|
{
|
|
onSuccess: () => {
|
|
enqueueSnackbar(t('saved'), { variant: 'success' });
|
|
router.replace(`/${locale}${ROUTES.HOME}`);
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
if (phase === 'welcome') {
|
|
return (
|
|
<Stack sx={{ alignItems: 'center', justifyContent: 'center', minHeight: '70vh', gap: 3, textAlign: 'center' }}>
|
|
<BrandMark />
|
|
<Stack sx={{ gap: 1, maxWidth: ONBOARDING_MAX_WIDTH }}>
|
|
<Typography variant="h5" component="h1">
|
|
{t('welcome_title')}
|
|
</Typography>
|
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
{t('welcome_subtitle')}
|
|
</Typography>
|
|
</Stack>
|
|
<AppButton color="primary" variant="contained" onClick={() => setPhase('relation')}>
|
|
{t('welcome_cta')}
|
|
</AppButton>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
const activeStep = phase === 'relation' ? 0 : 1;
|
|
|
|
return (
|
|
<Box sx={{ maxWidth: ONBOARDING_MAX_WIDTH, mx: 'auto', display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
<StepperHeader steps={[t('step_relation'), t('step_patient')]} activeStep={activeStep} />
|
|
|
|
{phase === 'relation' ? (
|
|
<Stack sx={{ gap: 2 }}>
|
|
<Stack sx={{ gap: 0.5 }}>
|
|
<Typography variant="h6" component="h1">
|
|
{t('relation_title')}
|
|
</Typography>
|
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
{t('relation_subtitle')}
|
|
</Typography>
|
|
</Stack>
|
|
<RelationSelect
|
|
options={relationOptions}
|
|
value={relation}
|
|
onChange={(code) => setRelation(code as Relation)}
|
|
/>
|
|
<AppButton
|
|
color="primary"
|
|
variant="contained"
|
|
fullWidth
|
|
disabled={!relation}
|
|
onClick={() => setPhase('patient')}
|
|
endIcon={<AppIcon icon="forward" size={18} aria-hidden="true" />}
|
|
>
|
|
{t('continue')}
|
|
</AppButton>
|
|
</Stack>
|
|
) : (
|
|
<Stack sx={{ gap: 2 }}>
|
|
<Stack sx={{ gap: 0.5 }}>
|
|
<Typography variant="h6" component="h1">
|
|
{t('patient_title')}
|
|
</Typography>
|
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
{t('patient_subtitle')}
|
|
</Typography>
|
|
</Stack>
|
|
<PatientForm
|
|
initial={{ relation: relation ?? undefined }}
|
|
submitLabel={t('save_continue')}
|
|
submitting={createPatient.isPending}
|
|
onSubmit={handleCreate}
|
|
onCancel={() => setPhase('relation')}
|
|
cancelLabel={tc('back')}
|
|
/>
|
|
</Stack>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|