From 2004fe10c05708ea3805f52809f61c387d92aae8 Mon Sep 17 00:00:00 2001 From: Richie Date: Sun, 29 Mar 2026 14:51:09 +1100 Subject: [PATCH] Add DateTimeStep page (wizard step 6) - Centered-form layout with two fieldset sections: name + scheduling - Grief-sensitive labels: "Their first name", "About the person who died" - Pre-planning variant: softer copy, "About the person" (no "who died") - Date preference: ASAP / specific with progressive disclosure date picker - Time preference: 5-option radio (no preference, morning, midday, afternoon, evening) - Religion/service style: Autocomplete with 22 options - Save-and-exit tertiary CTA - showNameFields + showScheduling props for conditional visibility per funeral type Co-Authored-By: Claude Opus 4.6 (1M context) --- .../DateTimeStep/DateTimeStep.stories.tsx | 208 +++++++++++ .../pages/DateTimeStep/DateTimeStep.tsx | 334 ++++++++++++++++++ src/components/pages/DateTimeStep/index.ts | 8 + 3 files changed, 550 insertions(+) create mode 100644 src/components/pages/DateTimeStep/DateTimeStep.stories.tsx create mode 100644 src/components/pages/DateTimeStep/DateTimeStep.tsx create mode 100644 src/components/pages/DateTimeStep/index.ts diff --git a/src/components/pages/DateTimeStep/DateTimeStep.stories.tsx b/src/components/pages/DateTimeStep/DateTimeStep.stories.tsx new file mode 100644 index 0000000..4100f9b --- /dev/null +++ b/src/components/pages/DateTimeStep/DateTimeStep.stories.tsx @@ -0,0 +1,208 @@ +import { useState } from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { DateTimeStep } from './DateTimeStep'; +import type { DateTimeStepValues, DateTimeStepErrors } from './DateTimeStep'; +import { Navigation } from '../../organisms/Navigation'; +import Box from '@mui/material/Box'; + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +const FALogo = () => ( + + + + +); + +const nav = ( + } + items={[ + { label: 'FAQ', href: '/faq' }, + { label: 'Contact Us', href: '/contact' }, + ]} + /> +); + +const defaultValues: DateTimeStepValues = { + firstName: '', + surname: '', + funeralDate: 'asap', + funeralDateSpecific: '', + funeralTime: 'no_preference', + religion: null, +}; + +// ─── Meta ──────────────────────────────────────────────────────────────────── + +const meta: Meta = { + title: 'Pages/DateTimeStep', + component: DateTimeStep, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +// ─── Interactive (default) — at-need ──────────────────────────────────────── + +/** At-need flow — all fields visible, grief-sensitive copy */ +export const Default: Story = { + render: () => { + const [values, setValues] = useState({ ...defaultValues }); + const [errors, setErrors] = useState({}); + + const handleContinue = () => { + const newErrors: DateTimeStepErrors = {}; + if (!values.firstName) + newErrors.firstName = 'We need their first name for the funeral arrangements.'; + if (!values.surname) + newErrors.surname = 'We need their surname for the funeral arrangements.'; + if (values.funeralDate === 'specific' && !values.funeralDateSpecific) + newErrors.funeralDateSpecific = 'Please select a preferred date.'; + setErrors(newErrors); + if (Object.keys(newErrors).length === 0) + alert(`Continue: ${values.firstName} ${values.surname}`); + }; + + return ( + { + setValues(v); + setErrors({}); + }} + onContinue={handleContinue} + onBack={() => alert('Back')} + onSaveAndExit={() => alert('Save and exit')} + errors={errors} + isAtNeed + navigation={nav} + /> + ); + }, +}; + +// ─── Pre-planning ─────────────────────────────────────────────────────────── + +/** Pre-planning flow — softer copy, helper text about updating later */ +export const PrePlanning: Story = { + render: () => { + const [values, setValues] = useState({ ...defaultValues }); + return ( + alert('Continue')} + onBack={() => alert('Back')} + onSaveAndExit={() => alert('Save')} + isAtNeed={false} + navigation={nav} + /> + ); + }, +}; + +// ─── Specific date selected ───────────────────────────────────────────────── + +/** Date picker revealed when "specific date" is selected */ +export const SpecificDate: Story = { + render: () => { + const [values, setValues] = useState({ + ...defaultValues, + firstName: 'Margaret', + surname: 'Wilson', + funeralDate: 'specific', + funeralTime: 'morning', + }); + return ( + alert('Continue')} + onBack={() => alert('Back')} + isAtNeed + navigation={nav} + /> + ); + }, +}; + +// ─── Name fields only (cremation/burial only) ────────────────────────────── + +/** Scheduling hidden — only name fields shown (e.g. cremation-only at-need) */ +export const NameFieldsOnly: Story = { + render: () => { + const [values, setValues] = useState({ ...defaultValues }); + return ( + alert('Continue')} + onBack={() => alert('Back')} + showScheduling={false} + navigation={nav} + /> + ); + }, +}; + +// ─── Validation errors ────────────────────────────────────────────────────── + +/** All validation errors showing */ +export const WithErrors: Story = { + render: () => { + const [values, setValues] = useState({ ...defaultValues }); + return ( + {}} + errors={{ + firstName: 'We need their first name for the funeral arrangements.', + surname: 'We need their surname for the funeral arrangements.', + }} + onBack={() => alert('Back')} + navigation={nav} + /> + ); + }, +}; + +// ─── Loading ──────────────────────────────────────────────────────────────── + +/** Continue button in loading state */ +export const Loading: Story = { + render: () => { + const [values, setValues] = useState({ + ...defaultValues, + firstName: 'Margaret', + surname: 'Wilson', + funeralDate: 'asap', + funeralTime: 'morning', + religion: 'Anglican', + }); + return ( + {}} + loading + navigation={nav} + /> + ); + }, +}; diff --git a/src/components/pages/DateTimeStep/DateTimeStep.tsx b/src/components/pages/DateTimeStep/DateTimeStep.tsx new file mode 100644 index 0000000..ac91dc6 --- /dev/null +++ b/src/components/pages/DateTimeStep/DateTimeStep.tsx @@ -0,0 +1,334 @@ +import React from 'react'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Autocomplete from '@mui/material/Autocomplete'; +import FormControl from '@mui/material/FormControl'; +import FormLabel from '@mui/material/FormLabel'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import RadioGroup from '@mui/material/RadioGroup'; +import Radio from '@mui/material/Radio'; +import type { SxProps, Theme } from '@mui/material/styles'; +import { WizardLayout } from '../../templates/WizardLayout'; +import { Collapse } from '../../atoms/Collapse'; +import { Typography } from '../../atoms/Typography'; +import { Button } from '../../atoms/Button'; +import { Divider } from '../../atoms/Divider'; + +// ─── Types ─────────────────────────────────────────────────────────────────── + +/** Funeral date preference */ +export type FuneralDatePref = 'asap' | 'specific' | null; + +/** Time-of-day preference */ +export type FuneralTimePref = 'no_preference' | 'morning' | 'midday' | 'afternoon' | 'evening'; + +/** Form values for the date/time step */ +export interface DateTimeStepValues { + /** Deceased first name */ + firstName: string; + /** Deceased surname */ + surname: string; + /** Date preference (ASAP or specific) */ + funeralDate: FuneralDatePref; + /** Specific date string (ISO format) when funeralDate is "specific" */ + funeralDateSpecific: string; + /** Time-of-day preference */ + funeralTime: FuneralTimePref; + /** Service style / religion preference */ + religion: string | null; +} + +/** Field-level error messages */ +export interface DateTimeStepErrors { + firstName?: string; + surname?: string; + funeralDate?: string; + funeralDateSpecific?: string; + funeralTime?: string; +} + +/** Props for the DateTimeStep page component */ +export interface DateTimeStepProps { + /** Current form values */ + values: DateTimeStepValues; + /** Callback when any field value changes */ + onChange: (values: DateTimeStepValues) => void; + /** Callback when the Continue button is clicked */ + onContinue: () => void; + /** Callback for back navigation */ + onBack?: () => void; + /** Callback for save-and-exit */ + onSaveAndExit?: () => void; + /** Field-level validation errors */ + errors?: DateTimeStepErrors; + /** Whether the Continue button is in a loading state */ + loading?: boolean; + /** Whether the person has passed (at-need) or is alive (pre-planning) */ + isAtNeed?: boolean; + /** Whether deceased name fields should be shown */ + showNameFields?: boolean; + /** Whether scheduling fields should be shown */ + showScheduling?: boolean; + /** Available religion/service style options */ + religionOptions?: string[]; + /** Navigation bar — passed through to WizardLayout */ + navigation?: React.ReactNode; + /** Hide the help bar */ + hideHelpBar?: boolean; + /** MUI sx prop for the root */ + sx?: SxProps; +} + +// ─── Constants ─────────────────────────────────────────────────────────────── + +const DEFAULT_RELIGIONS = [ + 'No Religion', + 'Civil Celebrant', + 'Aboriginal', + 'Anglican', + 'Baptist', + 'Buddhism', + 'Catholic', + 'Christian', + 'Hinduism', + 'Indigenous', + 'Islam', + 'Judaism', + 'Lutheran', + 'Oriental Orthodox', + 'Eastern Orthodox', + 'Greek Orthodox', + 'Russian Orthodox', + 'Serbian Orthodox', + 'Pentecostal', + 'Presbyterian', + 'Sikhism', + 'Uniting Church', +]; + +// ─── Component ─────────────────────────────────────────────────────────────── + +/** + * Step 6 — Details & Scheduling for the FA arrangement wizard. + * + * Captures deceased details (name) and funeral date/time preferences. + * Two logical sections: "About the person" and "Service timing". + * Each wrapped in fieldset/legend for screen reader structure. + * + * Field visibility is controlled by props (showNameFields, showScheduling) + * since it depends on funeral type and pre-planning status. + * + * Grief-sensitive labels: "Their first name" not "Deceased First Name". + * Pre-planning copy: "About the person" (no "who died"). + * + * Pure presentation component — props in, callbacks out. + * + * Spec: documentation/steps/steps/06_date_time.yaml + */ +export const DateTimeStep: React.FC = ({ + values, + onChange, + onContinue, + onBack, + onSaveAndExit, + errors, + loading = false, + isAtNeed = true, + showNameFields = true, + showScheduling = true, + religionOptions = DEFAULT_RELIGIONS, + navigation, + hideHelpBar, + sx, +}) => { + const handleFieldChange = ( + field: K, + value: DateTimeStepValues[K], + ) => { + onChange({ ...values, [field]: value }); + }; + + const personSectionHeading = isAtNeed ? 'About the person who died' : 'About the person'; + + const schedulingHeading = isAtNeed + ? 'When are you hoping to have the service?' + : 'When would you like the service?'; + + return ( + + {/* Page heading */} + + A few important details + + + {!isAtNeed && ( + + If you're not sure about dates yet, that's fine. You can update this later. + + )} + + { + e.preventDefault(); + onContinue(); + }} + > + {/* ─── Section 1: About the person ─── */} + {showNameFields && ( + + + {personSectionHeading} + + + + handleFieldChange('firstName', e.target.value)} + error={!!errors?.firstName} + helperText={errors?.firstName} + autoComplete="off" + fullWidth + required + /> + handleFieldChange('surname', e.target.value)} + error={!!errors?.surname} + helperText={errors?.surname} + autoComplete="off" + fullWidth + required + /> + + + )} + + {/* ─── Section 2: Scheduling ─── */} + {showScheduling && ( + + + {schedulingHeading} + + + {/* Date preference */} + + + Preferred timing + + + handleFieldChange('funeralDate', e.target.value as FuneralDatePref) + } + > + } label="As soon as possible" /> + } + label="I have a specific date in mind" + /> + + + + {/* Specific date — progressive disclosure */} + + handleFieldChange('funeralDateSpecific', e.target.value)} + error={!!errors?.funeralDateSpecific} + helperText={errors?.funeralDateSpecific} + InputLabelProps={{ shrink: true }} + inputProps={{ min: new Date().toISOString().split('T')[0] }} + fullWidth + required + sx={{ mb: 3 }} + /> + + + {/* Time-of-day preference */} + + + Do you have a preferred time of day? + + + handleFieldChange('funeralTime', e.target.value as FuneralTimePref) + } + > + } label="No preference" /> + } label="Morning" /> + } label="Midday" /> + } label="Afternoon" /> + } label="Evening" /> + + + + )} + + {/* ─── Service style (religion) ─── */} + handleFieldChange('religion', newValue)} + renderInput={(params) => ( + + )} + sx={{ mb: 4 }} + /> + + + + {/* CTAs */} + + {onSaveAndExit ? ( + + ) : ( + + )} + + + + + ); +}; + +DateTimeStep.displayName = 'DateTimeStep'; +export default DateTimeStep; diff --git a/src/components/pages/DateTimeStep/index.ts b/src/components/pages/DateTimeStep/index.ts new file mode 100644 index 0000000..5f211fb --- /dev/null +++ b/src/components/pages/DateTimeStep/index.ts @@ -0,0 +1,8 @@ +export { DateTimeStep, default } from './DateTimeStep'; +export type { + DateTimeStepProps, + DateTimeStepValues, + DateTimeStepErrors, + FuneralDatePref, + FuneralTimePref, +} from './DateTimeStep';