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 type { SxProps, Theme } from '@mui/material/styles'; import { WizardLayout } from '../../templates/WizardLayout'; import { AddOnOption } from '../../molecules/AddOnOption'; import { Collapse } from '../../atoms/Collapse'; import { Switch } from '../../atoms/Switch'; import { Typography } from '../../atoms/Typography'; import { Button } from '../../atoms/Button'; import { Divider } from '../../atoms/Divider'; // ─── Types ─────────────────────────────────────────────────────────────────── /** Form values for the extras step */ export interface ExtrasStepValues { catering: boolean; music: boolean; liveMusician: boolean; musicianType: 'vocalist' | 'cellist' | 'other' | null; bearing: boolean; bearerType: 'family' | 'funeralHouse' | 'both' | null; newspaperNotice: boolean; } /** Props for the ExtrasStep page component */ export interface ExtrasStepProps { /** Current form values */ values: ExtrasStepValues; /** Callback when any field value changes */ onChange: (values: ExtrasStepValues) => void; /** Callback when the Continue button is clicked */ onContinue: () => void; /** Callback for back navigation */ onBack?: () => void; /** Callback for save-and-exit */ onSaveAndExit?: () => void; /** Whether the Continue button is in a loading state */ loading?: boolean; /** Price for catering (omit for POA) */ cateringPrice?: number; /** Price for newspaper notice (omit for POA) */ newspaperPrice?: number; /** Price for live musician (omit for POA) */ musicianPrice?: number; /** Whether this is a pre-planning flow */ isPrePlanning?: boolean; /** Navigation bar */ navigation?: React.ReactNode; /** Progress stepper */ progressStepper?: React.ReactNode; /** Running total */ runningTotal?: React.ReactNode; /** Hide the help bar */ hideHelpBar?: boolean; /** MUI sx prop */ sx?: SxProps; } // ─── Component ─────────────────────────────────────────────────────────────── /** * Step 12b — Optional Extras for the FA arrangement wizard. * * Shows optional services that may have additional costs. Users select * anything they're interested in — their funeral director will follow * up with details and confirm pricing. * * This is a lead-generation step: selecting an extra signals interest, * not a firm commitment. Items show prices where available, otherwise * "Price on application". * * Sub-options (e.g. musician type, bearer type) render as flat form * fields inside the parent card — no nested cards. * * Pure presentation component — props in, callbacks out. * * Spec: documentation/steps/steps/12_additional_services.yaml (Section 2) */ export const ExtrasStep: React.FC = ({ values, onChange, onContinue, onBack, onSaveAndExit, loading = false, cateringPrice, newspaperPrice, musicianPrice, isPrePlanning = false, navigation, progressStepper, runningTotal, hideHelpBar, sx, }) => { const liveMusicianSwitchId = React.useId(); const handleToggle = (field: keyof ExtrasStepValues, checked: boolean) => { const next = { ...values, [field]: checked }; if (field === 'music' && !checked) { next.liveMusician = false; next.musicianType = null; } if (field === 'liveMusician' && !checked) { next.musicianType = null; } if (field === 'bearing' && !checked) { next.bearerType = null; } onChange(next); }; const handleFieldChange = ( field: K, value: ExtrasStepValues[K], ) => { onChange({ ...values, [field]: value }); }; // Compute tally of selected priced extras const tallyItems: { name: string; price: number }[] = []; if (values.catering && cateringPrice != null) tallyItems.push({ name: 'Catering', price: cateringPrice }); if (values.liveMusician && musicianPrice != null) tallyItems.push({ name: 'Live musician', price: musicianPrice }); if (values.newspaperNotice && newspaperPrice != null) tallyItems.push({ name: 'Newspaper notice', price: newspaperPrice }); const totalAdditional = tallyItems.reduce((sum, item) => sum + item.price, 0); return ( {/* Page heading */} Optional extras {isPrePlanning ? "These services are available if you'd like to personalise the arrangement. Select any you're interested in — details can be discussed when you're ready." : "You may wish to personalise the arrangement with any of these services. Where pricing isn't shown, your funeral director will be happy to discuss options and provide a quote."} { e.preventDefault(); if (!loading) onContinue(); }} > handleToggle('catering', c)} /> {/* Music — flat sub-options inside the card */} handleToggle('music', c)} > {/* Inline toggle row for live musician */} Live musician {musicianPrice != null ? ( ${musicianPrice.toLocaleString('en-AU')} ) : ( POA )} handleToggle('liveMusician', v)} onClick={(e) => e.stopPropagation()} inputProps={{ 'aria-labelledby': liveMusicianSwitchId }} sx={{ flexShrink: 0 }} /> {/* Musician type — revealed when live musician is on */} Musician type handleFieldChange( 'musicianType', e.target.value as ExtrasStepValues['musicianType'], ) } > } label="Vocalist" /> } label="Cellist" /> } label="Other" /> {/* Coffin bearing — toggle with radio sub-options */} handleToggle('bearing', c)} > Bearer preference handleFieldChange('bearerType', e.target.value as ExtrasStepValues['bearerType']) } > } label="Family and friends" /> } label="Professional bearers" /> } label="Both family and professional" /> handleToggle('newspaperNotice', c)} /> {/* ─── Tally ─── */} {tallyItems.length > 0 && ( <> Extras total ${totalAdditional.toLocaleString('en-AU')} )} {/* CTAs */} {onSaveAndExit ? ( ) : ( )} ); }; ExtrasStep.displayName = 'ExtrasStep'; export default ExtrasStep;