From 14af8d1f5a1ef0cbaca8241acfe8661dbca99e11 Mon Sep 17 00:00:00 2001 From: Richie Date: Mon, 30 Mar 2026 22:09:40 +1100 Subject: [PATCH] CemeteryStep: rewrite to match live site MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ToggleButtonGroups (Yes/No/Not sure) replace RadioGroups - Select dropdown replaces card selection grid - Progressive disclosure: own plot → locate it; no plot → preference? - "Not sure" option on both questions for grief-sensitive escape hatch - Component registry updated Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/memory/component-registry.md | 2 +- .../CemeteryStep/CemeteryStep.stories.tsx | 134 +++------ .../pages/CemeteryStep/CemeteryStep.tsx | 262 ++++++++---------- src/components/pages/CemeteryStep/index.ts | 2 +- 4 files changed, 152 insertions(+), 248 deletions(-) diff --git a/docs/memory/component-registry.md b/docs/memory/component-registry.md index d1b65e9..e6fa889 100644 --- a/docs/memory/component-registry.md +++ b/docs/memory/component-registry.md @@ -84,7 +84,7 @@ duplicates) and MUST update it after completing one. | VenueDetailStep | done | WizardLayout (detail-toggles) + ImageGallery + Card + Chip + Typography + Button + Divider | Wizard step 7b — venue detail. Two-panel: gallery/description/features/location (left), name/meta/price/CTA/religions (right). Informational service preview. | | VenueServicesStep | done | WizardLayout (centered-form) + AddOnOption + Card + Typography + Button + Divider | Wizard step 7c — venue services. Compact venue card, availability notices, AddOnOption toggles with "View more" for long descriptions. Follows VenueDetailStep. | | CrematoriumStep | done | WizardLayout (centered-form) + Card + Badge + ToggleButtonGroup + Typography + Button + Divider | Wizard step 8 — crematorium. Two variants: Service & Cremation (compact card + witness Yes/No toggle), Cremation Only (compact card + "Cremation Only" badge + "Included in Package" notice). Single pre-selected crematorium, no multi-select. | -| CemeteryStep | done | WizardLayout (centered-form) + Card + RadioGroup + Collapse + Divider + Button | Wizard step 9 — cemetery. Triple progressive disclosure (have plot? → choose? → grid). Dependent field resets. | +| CemeteryStep | done | WizardLayout (centered-form) + ToggleButtonGroup + Collapse + TextField (select) + Typography + Button + Divider | Wizard step 9 — cemetery. ToggleButtonGroups (Yes/No/Not sure) with progressive disclosure. Own plot → locate dropdown. No plot → preference? → select dropdown. No card grid. | | CoffinsStep | done | WizardLayout (centered-form) + Card + Badge + TextField + MenuItem + Pagination + Divider + Button | Wizard step 10 — coffin selection. 3-col card grid with category/price filters. "Most Popular" badge. Pagination. | | CoffinDetailsStep | done | WizardLayout (centered-form) + Paper + RadioGroup + Divider + Button | Wizard step 11 — coffin customisation. Profile (image + specs) + 3 option sections (handles, lining, nameplate). Branded selected state. | | AdditionalServicesStep | done | WizardLayout (centered-form) + Paper + AddOnOption + RadioGroup + Collapse + Divider + Button | Wizard step 12 — additional services. Section 1: complimentary. Section 2: paid extras. Multi-level progressive disclosure. | diff --git a/src/components/pages/CemeteryStep/CemeteryStep.stories.tsx b/src/components/pages/CemeteryStep/CemeteryStep.stories.tsx index 6518eca..25293cf 100644 --- a/src/components/pages/CemeteryStep/CemeteryStep.stories.tsx +++ b/src/components/pages/CemeteryStep/CemeteryStep.stories.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { CemeteryStep } from './CemeteryStep'; -import type { CemeteryStepValues, CemeteryStepErrors, Cemetery } from './CemeteryStep'; +import type { CemeteryStepValues, CemeteryOption } from './CemeteryStep'; import { Navigation } from '../../organisms/Navigation'; import Box from '@mui/material/Box'; @@ -34,31 +34,19 @@ const nav = ( /> ); -const sampleCemeteries: Cemetery[] = [ - { - id: 'rookwood', - name: 'Rookwood Cemetery', - location: 'Lidcombe, NSW', - price: 4500, - }, - { - id: 'northern-suburbs', - name: 'Northern Suburbs Memorial Gardens', - location: 'North Ryde, NSW', - price: 5200, - }, - { - id: 'macquarie-park', - name: 'Macquarie Park Cemetery', - location: 'Macquarie Park, NSW', - price: 4800, - }, +const sampleCemeteries: CemeteryOption[] = [ + { value: 'rookwood', label: 'Rookwood Cemetery' }, + { value: 'northern-suburbs', label: 'Northern Suburbs Memorial Gardens' }, + { value: 'macquarie-park', label: 'Macquarie Park Cemetery' }, + { value: 'pinegrove', label: 'Pinegrove Memorial Park' }, + { value: 'waverley', label: 'Waverley Cemetery' }, + { value: 'botany', label: 'Botany Cemetery' }, ]; const defaultValues: CemeteryStepValues = { - burialOwn: null, - burialCustom: null, - selectedCemeteryId: null, + ownPlot: null, + hasPreference: null, + selectedCemetery: '', }; // ─── Meta ──────────────────────────────────────────────────────────────────── @@ -75,36 +63,19 @@ const meta: Meta = { export default meta; type Story = StoryObj; -// ─── Interactive (default) ────────────────────────────────────────────────── +// ─── Default ───────────────────────────────────────────────────────────────── -/** Fully interactive — progressive disclosure flow */ +/** Initial state — no selections made */ export const Default: Story = { render: () => { const [values, setValues] = useState({ ...defaultValues }); - const [errors, setErrors] = useState({}); - - const handleContinue = () => { - const newErrors: CemeteryStepErrors = {}; - if (!values.burialOwn) newErrors.burialOwn = 'Please let us know about the burial plot.'; - if (values.burialOwn === 'no' && !values.burialCustom) - newErrors.burialCustom = "Please let us know if you'd like to choose a specific cemetery."; - if (values.burialOwn === 'no' && values.burialCustom === 'yes' && !values.selectedCemeteryId) - newErrors.selectedCemeteryId = 'Please choose a cemetery.'; - setErrors(newErrors); - if (Object.keys(newErrors).length === 0) alert('Continue'); - }; - return ( { - setValues(v); - setErrors({}); - }} - onContinue={handleContinue} + onChange={setValues} + onContinue={() => alert('Continue')} onBack={() => alert('Back')} onSaveAndExit={() => alert('Save')} - errors={errors} cemeteries={sampleCemeteries} navigation={nav} /> @@ -112,14 +83,14 @@ export const Default: Story = { }, }; -// ─── Has existing plot ────────────────────────────────────────────────────── +// ─── Owns a plot ───────────────────────────────────────────────────────────── -/** User already owns a burial plot — short confirmation */ -export const HasExistingPlot: Story = { +/** User owns a plot — shows "tell us where" + dropdown */ +export const OwnsPlot: Story = { render: () => { const [values, setValues] = useState({ ...defaultValues, - burialOwn: 'yes', + ownPlot: 'yes', }); return ( alert('Continue')} onBack={() => alert('Back')} + onSaveAndExit={() => alert('Save')} cemeteries={sampleCemeteries} navigation={nav} /> @@ -134,15 +106,15 @@ export const HasExistingPlot: Story = { }, }; -// ─── Provider arranges ────────────────────────────────────────────────────── +// ─── No plot, has preference ───────────────────────────────────────────────── -/** User wants provider to arrange — no cemetery grid */ -export const ProviderArranges: Story = { +/** No plot, has a preference — shows both questions + dropdown */ +export const HasPreference: Story = { render: () => { const [values, setValues] = useState({ ...defaultValues, - burialOwn: 'no', - burialCustom: 'no', + ownPlot: 'no', + hasPreference: 'yes', }); return ( alert('Continue')} onBack={() => alert('Back')} + onSaveAndExit={() => alert('Save')} cemeteries={sampleCemeteries} navigation={nav} /> @@ -157,15 +130,15 @@ export const ProviderArranges: Story = { }, }; -// ─── Cemetery grid visible ────────────────────────────────────────────────── +// ─── No plot, no preference ────────────────────────────────────────────────── -/** User wants to choose — cemetery grid revealed */ -export const CemeteryGridVisible: Story = { +/** No plot, no preference — provider will arrange */ +export const NoPreference: Story = { render: () => { const [values, setValues] = useState({ ...defaultValues, - burialOwn: 'no', - burialCustom: 'yes', + ownPlot: 'no', + hasPreference: 'no', }); return ( alert('Continue')} onBack={() => alert('Back')} + onSaveAndExit={() => alert('Save')} cemeteries={sampleCemeteries} navigation={nav} /> @@ -180,30 +154,7 @@ export const CemeteryGridVisible: Story = { }, }; -// ─── Cemetery selected ────────────────────────────────────────────────────── - -/** Cemetery selected */ -export const CemeterySelected: Story = { - render: () => { - const [values, setValues] = useState({ - burialOwn: 'no', - burialCustom: 'yes', - selectedCemeteryId: 'rookwood', - }); - return ( - alert('Continue')} - onBack={() => alert('Back')} - cemeteries={sampleCemeteries} - navigation={nav} - /> - ); - }, -}; - -// ─── Pre-planning ─────────────────────────────────────────────────────────── +// ─── Pre-planning ──────────────────────────────────────────────────────────── /** Pre-planning variant */ export const PrePlanning: Story = { @@ -222,22 +173,3 @@ export const PrePlanning: Story = { ); }, }; - -// ─── Validation errors ────────────────────────────────────────────────────── - -/** All errors showing */ -export const WithErrors: Story = { - render: () => { - const [values, setValues] = useState({ ...defaultValues }); - return ( - {}} - errors={{ burialOwn: 'Please let us know about the burial plot.' }} - cemeteries={sampleCemeteries} - navigation={nav} - /> - ); - }, -}; diff --git a/src/components/pages/CemeteryStep/CemeteryStep.tsx b/src/components/pages/CemeteryStep/CemeteryStep.tsx index b0cb7a9..1067349 100644 --- a/src/components/pages/CemeteryStep/CemeteryStep.tsx +++ b/src/components/pages/CemeteryStep/CemeteryStep.tsx @@ -1,13 +1,10 @@ import React from 'react'; import Box from '@mui/material/Box'; -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 TextField from '@mui/material/TextField'; +import MenuItem from '@mui/material/MenuItem'; import type { SxProps, Theme } from '@mui/material/styles'; import { WizardLayout } from '../../templates/WizardLayout'; -import { Card } from '../../atoms/Card'; +import { ToggleButtonGroup } from '../../atoms/ToggleButtonGroup'; import { Collapse } from '../../atoms/Collapse'; import { Typography } from '../../atoms/Typography'; import { Button } from '../../atoms/Button'; @@ -15,29 +12,27 @@ import { Divider } from '../../atoms/Divider'; // ─── Types ─────────────────────────────────────────────────────────────────── -/** A cemetery available for selection */ -export interface Cemetery { - id: string; - name: string; - location: string; - price?: number; +/** A cemetery option for the dropdown */ +export interface CemeteryOption { + value: string; + label: string; } /** Form values for the cemetery step */ export interface CemeteryStepValues { /** Does the family already own a burial plot? */ - burialOwn: 'yes' | 'no' | null; - /** Would they like to choose a specific cemetery? (when burialOwn=no) */ - burialCustom: 'yes' | 'no' | null; + ownPlot: 'yes' | 'no' | 'unsure' | null; + /** Do they have a preference for a cemetery? (when ownPlot ≠ yes) */ + hasPreference: 'yes' | 'no' | 'unsure' | null; /** Selected cemetery ID */ - selectedCemeteryId: string | null; + selectedCemetery: string; } /** Field-level error messages */ export interface CemeteryStepErrors { - burialOwn?: string; - burialCustom?: string; - selectedCemeteryId?: string; + ownPlot?: string; + hasPreference?: string; + selectedCemetery?: string; } /** Props for the CemeteryStep page component */ @@ -46,7 +41,7 @@ export interface CemeteryStepProps { values: CemeteryStepValues; /** Callback when any field value changes */ onChange: (values: CemeteryStepValues) => void; - /** Callback when the Continue button is clicked */ + /** Callback when Continue is clicked */ onContinue: () => void; /** Callback for back navigation */ onBack?: () => void; @@ -54,10 +49,10 @@ export interface CemeteryStepProps { onSaveAndExit?: () => void; /** Field-level validation errors */ errors?: CemeteryStepErrors; - /** Whether the Continue button is in a loading state */ + /** Whether Continue is loading */ loading?: boolean; - /** Available cemeteries */ - cemeteries: Cemetery[]; + /** Available cemeteries for the dropdown */ + cemeteries: CemeteryOption[]; /** Whether this is a pre-planning flow */ isPrePlanning?: boolean; /** Navigation bar */ @@ -81,12 +76,10 @@ export interface CemeteryStepProps { * burial-type funerals (Service & Burial, Graveside, Burial Only). * * Progressive disclosure flow: - * 1. "Do you have a burial plot?" → Yes/No - * 2. If No: "Would you like to choose a specific cemetery?" → Yes/No - * 3. If Yes to #2: Cemetery card grid - * - * If the user already owns a plot, the cemetery grid can be shown - * for confirmation (passed via showGridForExistingPlot). + * 1. "Do you already own a burial plot?" → Yes / No / Not sure + * 2a. Yes → "Tell us where it's located" + cemetery dropdown + * 2b. No/Not sure → "Do you have a preference?" → Yes / No / Not sure + * 3. Preference Yes → "Select your preferred cemetery" + dropdown * * Pure presentation component — props in, callbacks out. * @@ -108,31 +101,28 @@ export const CemeteryStep: React.FC = ({ hideHelpBar, sx, }) => { - const showCustomQuestion = values.burialOwn === 'no'; - const showCemeteryGrid = values.burialOwn === 'no' && values.burialCustom === 'yes'; + const showPreferenceQuestion = values.ownPlot === 'no' || values.ownPlot === 'unsure'; + const showCemeteryForOwnPlot = values.ownPlot === 'yes'; + const showCemeteryForPreference = showPreferenceQuestion && values.hasPreference === 'yes'; - const handleBurialOwnChange = (value: string) => { + const handleOwnPlotChange = (value: string | null) => { onChange({ ...values, - burialOwn: value as CemeteryStepValues['burialOwn'], - // Reset dependent fields when parent changes - burialCustom: null, - selectedCemeteryId: null, + ownPlot: (value ?? null) as CemeteryStepValues['ownPlot'], + // Reset dependent fields + hasPreference: null, + selectedCemetery: '', }); }; - const handleBurialCustomChange = (value: string) => { + const handlePreferenceChange = (value: string | null) => { onChange({ ...values, - burialCustom: value as CemeteryStepValues['burialCustom'], - selectedCemeteryId: null, + hasPreference: (value ?? null) as CemeteryStepValues['hasPreference'], + selectedCemetery: '', }); }; - const handleCemeterySelect = (id: string) => { - onChange({ ...values, selectedCemeteryId: id }); - }; - return ( = ({ {isPrePlanning - ? "If you haven't decided on a cemetery yet, the funeral provider can help with this later." + ? 'If you haven\u2019t decided on a cemetery yet, the funeral provider can help with this later.' : 'Choose where the burial will take place.'} @@ -165,115 +155,97 @@ export const CemeteryStep: React.FC = ({ if (!loading) onContinue(); }} > - {/* ─── Burial plot question ─── */} - - - Do you already have a burial plot? - - handleBurialOwnChange(e.target.value)} - > - } label="Yes, we have a plot" /> - } label="No, we need to find one" /> - - {errors?.burialOwn && ( - - {errors.burialOwn} - - )} - + {/* ─── Question 1: Own a burial plot? ─── */} + + + - {/* ─── Custom cemetery question (progressive disclosure) ─── */} - - - - Would you like to choose a specific cemetery? - - handleBurialCustomChange(e.target.value)} + {/* ─── Own plot: tell us where ─── */} + + + + Tell us where the burial plot is located + + + onChange({ ...values, selectedCemetery: e.target.value })} + placeholder="Select a cemetery" + fullWidth + error={!!errors?.selectedCemetery} + helperText={errors?.selectedCemetery} > - } label="Yes, I'd like to choose" /> - } - label="No, the funeral provider can arrange this" - /> - - {errors?.burialCustom && ( - - {errors.burialCustom} - - )} - + + Select a cemetery + + {cemeteries.map((c) => ( + + {c.label} + + ))} + + - {/* ─── Cemetery card grid (progressive disclosure) ─── */} - - - - Available cemeteries + {/* ─── Question 2: Have a preference? (when no plot) ─── */} + + + + + + + {/* ─── Preference: select cemetery ─── */} + + + + Select your preferred cemetery - onChange({ ...values, selectedCemetery: e.target.value })} + placeholder="Select a cemetery" + fullWidth + error={!!errors?.selectedCemetery} + helperText={errors?.selectedCemetery} > - {cemeteries.map((cemetery, index) => ( - handleCemeterySelect(cemetery.id)} - role="radio" - aria-checked={cemetery.id === values.selectedCemeteryId} - tabIndex={ - values.selectedCemeteryId === null - ? index === 0 - ? 0 - : -1 - : cemetery.id === values.selectedCemeteryId - ? 0 - : -1 - } - sx={{ p: 3 }} - > - {cemetery.name} - - {cemetery.location} - - {cemetery.price != null && ( - - ${cemetery.price.toLocaleString('en-AU')} - - )} - + + Select a cemetery + + {cemeteries.map((c) => ( + + {c.label} + ))} - - - {errors?.selectedCemeteryId && ( - - {errors.selectedCemeteryId} - - )} + diff --git a/src/components/pages/CemeteryStep/index.ts b/src/components/pages/CemeteryStep/index.ts index 279b9e4..61b0b7f 100644 --- a/src/components/pages/CemeteryStep/index.ts +++ b/src/components/pages/CemeteryStep/index.ts @@ -3,5 +3,5 @@ export type { CemeteryStepProps, CemeteryStepValues, CemeteryStepErrors, - Cemetery, + CemeteryOption, } from './CemeteryStep';