From ab9c8bfa6fecc2987f5295cb9026aac986dc34a9 Mon Sep 17 00:00:00 2001 From: Richie Date: Mon, 30 Mar 2026 21:51:03 +1100 Subject: [PATCH] CrematoriumStep: rewrite to match live site variants Simplified from over-engineered multi-card selection to two clean variants based on funeral type: - Service & Cremation: compact card + witness Yes/No (ToggleButtonGroup) - Cremation Only: compact card + badge + "Included in Package" notice Removed: multi-card grid, priority dropdown, special instructions, crematoriums array prop. Crematorium is always pre-selected by provider. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/memory/component-registry.md | 2 +- .../CrematoriumStep.stories.tsx | 164 +++------ .../pages/CrematoriumStep/CrematoriumStep.tsx | 346 +++++++----------- 3 files changed, 201 insertions(+), 311 deletions(-) diff --git a/docs/memory/component-registry.md b/docs/memory/component-registry.md index e3f546d..d1b65e9 100644 --- a/docs/memory/component-registry.md +++ b/docs/memory/component-registry.md @@ -83,7 +83,7 @@ duplicates) and MUST update it after completing one. | VenueStep | done | WizardLayout (centered-form) + VenueCard + AddOnOption + Collapse + Chip + TextField + Divider + Button | Wizard step 7a — venue browsing. Click-to-navigate card grid with search/filters. Leads to VenueDetailStep. | | 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 + RadioGroup + Collapse + TextField + Divider + Button | Wizard step 8 — crematorium. Single confirmation card or multi-card grid. Witness question personalised with deceased name. Special instructions textarea. | +| 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. | | 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. | diff --git a/src/components/pages/CrematoriumStep/CrematoriumStep.stories.tsx b/src/components/pages/CrematoriumStep/CrematoriumStep.stories.tsx index ff57a58..99c718d 100644 --- a/src/components/pages/CrematoriumStep/CrematoriumStep.stories.tsx +++ b/src/components/pages/CrematoriumStep/CrematoriumStep.stories.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { CrematoriumStep } from './CrematoriumStep'; -import type { CrematoriumStepValues, CrematoriumStepErrors, Crematorium } from './CrematoriumStep'; +import type { CrematoriumStepValues, Crematorium } from './CrematoriumStep'; import { Navigation } from '../../organisms/Navigation'; import Box from '@mui/material/Box'; @@ -34,46 +34,22 @@ const nav = ( /> ); -const singleCrematorium: Crematorium[] = [ - { - id: 'warrill-park', - name: 'Warrill Park Crematorium', - location: 'Ipswich', - distance: '15 min from venue', - price: 850, - }, -]; +const sampleCrematorium: Crematorium = { + name: 'Warrill Park Crematorium', + imageUrl: 'https://images.unsplash.com/photo-1585320806297-9794b3e4eeae?w=400&h=300&fit=crop', + address: 'Anderson Day Drive, Willowbank, QLD, 4306', + distance: '152.62km from Killick Family Funerals Chapel Kingaroy', +}; -const multipleCrematoriums: Crematorium[] = [ - { - id: 'warrill-park', - name: 'Warrill Park Crematorium', - location: 'Ipswich', - distance: '15 min from venue', - price: 850, - }, - { - id: 'mt-gravatt', - name: 'Mt Gravatt Crematorium', - location: 'Mt Gravatt', - distance: '25 min from venue', - price: 920, - }, - { - id: 'pinnaroo', - name: 'Pinnaroo Valley Memorial Park', - location: 'Padstow', - distance: '35 min from venue', - price: 780, - }, -]; +const sydneyCrematorium: Crematorium = { + name: 'Macquarie Park Cemetery & Crematorium', + imageUrl: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=300&fit=crop', + address: 'Plassey Rd, Macquarie Park, NSW, 2113', + distance: '3.54km from Gladesville, Sydney', +}; const defaultValues: CrematoriumStepValues = { - selectedCrematoriumId: null, - attend: 'no', - priority: '', - hasInstructions: 'no', - specialInstructions: '', + attend: null, }; // ─── Meta ──────────────────────────────────────────────────────────────────── @@ -90,23 +66,20 @@ const meta: Meta = { export default meta; type Story = StoryObj; -// ─── Single crematorium (most common) ─────────────────────────────────────── +// ─── Service & Cremation (default) ───────────────────────────────────────── -/** Single pre-selected crematorium — confirmation pattern */ +/** Service & Cremation — witness question with personalised name */ export const Default: Story = { render: () => { - const [values, setValues] = useState({ - ...defaultValues, - selectedCrematoriumId: 'warrill-park', - }); + const [values, setValues] = useState({ ...defaultValues }); return ( alert('Continue')} onBack={() => alert('Back')} onSaveAndExit={() => alert('Save')} - crematoriums={singleCrematorium} deceasedName="Margaret" navigation={nav} /> @@ -114,82 +87,40 @@ export const Default: Story = { }, }; -// ─── Multiple crematoriums ────────────────────────────────────────────────── +// ─── Cremation Only ──────────────────────────────────────────────────────── -/** Multiple options — card grid with selection */ -export const MultipleCrematoriums: Story = { +/** Cremation Only — no witness question, "included in package" notice */ +export const CremationOnly: Story = { render: () => { const [values, setValues] = useState({ ...defaultValues }); - const [errors, setErrors] = useState({}); - - const handleContinue = () => { - if (!values.selectedCrematoriumId) { - setErrors({ selectedCrematoriumId: 'Please confirm the crematorium.' }); - return; - } - alert(`Selected: ${values.selectedCrematoriumId}`); - }; - - return ( - { - setValues(v); - setErrors({}); - }} - onContinue={handleContinue} - onBack={() => alert('Back')} - errors={errors} - crematoriums={multipleCrematoriums} - deceasedName="Margaret" - navigation={nav} - /> - ); - }, -}; - -// ─── With special instructions ────────────────────────────────────────────── - -/** Special instructions textarea revealed */ -export const WithInstructions: Story = { - render: () => { - const [values, setValues] = useState({ - ...defaultValues, - selectedCrematoriumId: 'warrill-park', - attend: 'yes', - hasInstructions: 'yes', - specialInstructions: 'Please ensure the family has 10 minutes for a private farewell.', - }); return ( alert('Continue')} onBack={() => alert('Back')} - crematoriums={singleCrematorium} - deceasedName="Margaret" + onSaveAndExit={() => alert('Save')} + isCremationOnly navigation={nav} /> ); }, }; -// ─── Pre-planning ─────────────────────────────────────────────────────────── +// ─── Pre-planning ────────────────────────────────────────────────────────── -/** Pre-planning variant — softer helper text */ +/** Pre-planning variant — softer copy */ export const PrePlanning: Story = { render: () => { - const [values, setValues] = useState({ - ...defaultValues, - selectedCrematoriumId: 'warrill-park', - }); + const [values, setValues] = useState({ ...defaultValues }); return ( alert('Continue')} onBack={() => alert('Back')} - crematoriums={singleCrematorium} isPrePlanning navigation={nav} /> @@ -197,19 +128,46 @@ export const PrePlanning: Story = { }, }; -// ─── Validation error ─────────────────────────────────────────────────────── +// ─── No image ────────────────────────────────────────────────────────────── -/** No crematorium selected with error */ -export const WithError: Story = { +/** Crematorium without a photo */ +export const NoImage: Story = { render: () => { const [values, setValues] = useState({ ...defaultValues }); return ( {}} - errors={{ selectedCrematoriumId: 'Please confirm the crematorium.' }} - crematoriums={multipleCrematoriums} + onContinue={() => alert('Continue')} + onBack={() => alert('Back')} + onSaveAndExit={() => alert('Save')} + deceasedName="Robert" + navigation={nav} + /> + ); + }, +}; + +// ─── With error ──────────────────────────────────────────────────────────── + +/** Validation error on witness question */ +export const WithError: Story = { + render: () => { + const [values, setValues] = useState({ ...defaultValues }); + return ( + {}} + onBack={() => alert('Back')} + errors={{ attend: 'Please let us know if anyone will follow the hearse.' }} + deceasedName="Margaret" navigation={nav} /> ); diff --git a/src/components/pages/CrematoriumStep/CrematoriumStep.tsx b/src/components/pages/CrematoriumStep/CrematoriumStep.tsx index 1d74a4b..5ec9eef 100644 --- a/src/components/pages/CrematoriumStep/CrematoriumStep.tsx +++ b/src/components/pages/CrematoriumStep/CrematoriumStep.tsx @@ -1,58 +1,47 @@ import React from 'react'; import Box from '@mui/material/Box'; -import TextField from '@mui/material/TextField'; -import MenuItem from '@mui/material/MenuItem'; -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 LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; +import DirectionsCarOutlinedIcon from '@mui/icons-material/DirectionsCarOutlined'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import type { SxProps, Theme } from '@mui/material/styles'; import { WizardLayout } from '../../templates/WizardLayout'; import { Card } from '../../atoms/Card'; -import { Collapse } from '../../atoms/Collapse'; +import { Badge } from '../../atoms/Badge'; +import { ToggleButtonGroup } from '../../atoms/ToggleButtonGroup'; import { Typography } from '../../atoms/Typography'; import { Button } from '../../atoms/Button'; import { Divider } from '../../atoms/Divider'; // ─── Types ─────────────────────────────────────────────────────────────────── -/** A crematorium available for selection */ +/** The pre-selected crematorium */ export interface Crematorium { - id: string; name: string; - location: string; + imageUrl?: string; + address: string; distance?: string; - price?: number; } /** Form values for the crematorium step */ export interface CrematoriumStepValues { - /** Selected crematorium ID */ - selectedCrematoriumId: string | null; - /** Will anyone follow the hearse? */ - attend: 'yes' | 'no'; - /** Cremation timing preference */ - priority: string; - /** Has special instructions */ - hasInstructions: 'yes' | 'no'; - /** Special instructions text */ - specialInstructions: string; + /** Will anyone follow the hearse? (service & cremation only) */ + attend: 'yes' | 'no' | null; } /** Field-level error messages */ export interface CrematoriumStepErrors { - selectedCrematoriumId?: string; attend?: string; } /** Props for the CrematoriumStep page component */ export interface CrematoriumStepProps { + /** The pre-selected crematorium */ + crematorium: Crematorium; /** Current form values */ values: CrematoriumStepValues; /** Callback when any field value changes */ onChange: (values: CrematoriumStepValues) => void; - /** Callback when the Continue button is clicked */ + /** Callback when Continue is clicked */ onContinue: () => void; /** Callback for back navigation */ onBack?: () => void; @@ -60,17 +49,15 @@ export interface CrematoriumStepProps { onSaveAndExit?: () => void; /** Field-level validation errors */ errors?: CrematoriumStepErrors; - /** Whether the Continue button is in a loading state */ + /** Whether Continue is in a loading state */ loading?: boolean; - /** Available crematoriums */ - crematoriums: Crematorium[]; - /** Priority/timing options (provider-specific) */ - priorityOptions?: Array<{ value: string; label: string }>; - /** Deceased first name — used to personalise copy */ + /** Whether this is a cremation-only flow (no service) */ + isCremationOnly?: boolean; + /** Deceased first name — personalises witness question */ deceasedName?: string; /** Whether this is a pre-planning flow */ isPrePlanning?: boolean; - /** Navigation bar — passed through to WizardLayout */ + /** Navigation bar */ navigation?: React.ReactNode; /** Progress stepper */ progressStepper?: React.ReactNode; @@ -78,7 +65,7 @@ export interface CrematoriumStepProps { runningTotal?: React.ReactNode; /** Hide the help bar */ hideHelpBar?: boolean; - /** MUI sx prop for the root */ + /** MUI sx prop */ sx?: SxProps; } @@ -87,19 +74,21 @@ export interface CrematoriumStepProps { /** * Step 8 — Crematorium for the FA arrangement wizard. * - * Select a crematorium and cremation witness/attendance preferences. - * Only shown for cremation-type funerals. + * Confirmation page for the provider's pre-selected crematorium. + * Two variants based on funeral type: * - * Often a single pre-selected crematorium (provider's default). When - * multiple options exist, shows a card grid with radiogroup pattern. + * **Service & Cremation:** Crematorium card + witness question + * ("Will anyone follow the hearse?") with personalised helper text. * - * Personalises witness question with deceased name when available. + * **Cremation Only:** Crematorium card + "Cremation Only" badge + + * "Included in Package" notice. No witness question (no procession). * * Pure presentation component — props in, callbacks out. * * Spec: documentation/steps/steps/08_crematorium.yaml */ export const CrematoriumStep: React.FC = ({ + crematorium, values, onChange, onContinue, @@ -107,8 +96,7 @@ export const CrematoriumStep: React.FC = ({ onSaveAndExit, errors, loading = false, - crematoriums, - priorityOptions = [], + isCremationOnly = false, deceasedName, isPrePlanning = false, navigation, @@ -117,15 +105,6 @@ export const CrematoriumStep: React.FC = ({ hideHelpBar, sx, }) => { - const isSingle = crematoriums.length === 1; - - const handleFieldChange = ( - field: K, - value: CrematoriumStepValues[K], - ) => { - onChange({ ...values, [field]: value }); - }; - const witnessHelper = deceasedName ? `Please indicate if you wish to follow the hearse in your own vehicles to escort ${deceasedName} to the crematorium.` : isPrePlanning @@ -152,7 +131,9 @@ export const CrematoriumStep: React.FC = ({ {isPrePlanning ? 'Review the crematorium details. You can update this later.' - : 'Confirm the crematorium and let us know about any preferences.'} + : isCremationOnly + ? 'The crematorium included with your selected package.' + : 'Confirm the crematorium and let us know about any preferences.'} = ({ if (!loading) onContinue(); }} > - {/* ─── Crematorium selection ─── */} - - {isSingle ? ( - // Single crematorium — presented as confirmation - handleFieldChange('selectedCrematoriumId', crematoriums[0].id)} - role="radio" - aria-checked={values.selectedCrematoriumId === crematoriums[0].id} - tabIndex={0} - sx={{ p: 3 }} - > - {crematoriums[0].name} - - {crematoriums[0].location} - {crematoriums[0].distance && ` \u2022 ${crematoriums[0].distance}`} - - {crematoriums[0].price != null && ( - - ${crematoriums[0].price.toLocaleString('en-AU')} - - )} - - ) : ( - // Multiple crematoriums — radiogroup grid - - - {crematoriums.map((crem, index) => ( - handleFieldChange('selectedCrematoriumId', crem.id)} - role="radio" - aria-checked={crem.id === values.selectedCrematoriumId} - tabIndex={ - values.selectedCrematoriumId === null - ? index === 0 - ? 0 - : -1 - : crem.id === values.selectedCrematoriumId - ? 0 - : -1 - } - sx={{ p: 3 }} - > - {crem.name} - - {crem.location} - {crem.distance && ` \u2022 ${crem.distance}`} - - {crem.price != null && ( - - ${crem.price.toLocaleString('en-AU')} - - )} - - ))} - - + {/* ─── Compact crematorium card ─── */} + + {crematorium.imageUrl && ( + )} - - {errors?.selectedCrematoriumId && ( - - {errors.selectedCrematoriumId} - - )} - - - - - {/* ─── Witness / attendance question ─── */} - - - Will anyone be following the hearse to the crematorium? - - - {witnessHelper} - - - handleFieldChange('attend', e.target.value as CrematoriumStepValues['attend']) - } + - } label="Yes" /> - } label="No" /> - - {errors?.attend && ( - - {errors.attend} + + {crematorium.name} - )} - + + + + {crematorium.address} + + + {crematorium.distance && ( + + + + {crematorium.distance} + + + )} + + - {/* ─── Priority / timing preference (optional, provider-specific) ─── */} - {priorityOptions.length > 0 && ( + {isCremationOnly ? ( <> - - handleFieldChange('priority', e.target.value)} - placeholder="Select timing preference (optional)" - fullWidth - sx={{ mb: 3 }} + {/* ─── Cremation Only: badge + included notice ─── */} + + Cremation Only + + + - {priorityOptions.map((opt) => ( - - {opt.label} - - ))} - + + + + + Crematorium included in package + + + The selected crematorium is included in your funeral package. If you have any + questions or would prefer a different option, please speak with your funeral + director. + + + + + + ) : ( + <> + {/* ─── Service & Cremation: witness question ─── */} + + Will anyone be following the hearse to the crematorium? + + + {witnessHelper} + + + + onChange({ ...values, attend: v as CrematoriumStepValues['attend'] }) + } + error={!!errors?.attend} + helperText={errors?.attend} + fullWidth + /> )} - - - {/* ─── Special instructions ─── */} - - - Do you have any special requests for the cremation? - - - handleFieldChange( - 'hasInstructions', - e.target.value as CrematoriumStepValues['hasInstructions'], - ) - } - > - } label="No" /> - } label="Yes" /> - - - - - handleFieldChange('specialInstructions', e.target.value)} - placeholder="Enter any special requests or instructions..." - multiline - minRows={3} - maxRows={8} - fullWidth - sx={{ mb: 3 }} - /> - - {/* CTAs */}