Groom wizard steps 1-15: critique/harden/polish pass

- [P0] CrematoriumStep: Fix <option> → <MenuItem> in priority select
- [P1] All form steps: Add aria-busy={loading} + loading guard on submit
- [P1] Error messages: Replace color="error" (red) with copper
  (var(--fa-color-text-brand)) across ProvidersStep, PackagesStep,
  VenueStep, CrematoriumStep, CemeteryStep, CoffinsStep, PaymentStep
- [P2] IntroStep: "Has the person died?" → "Has this person passed away?"
- [P2] DateTimeStep: "About the person who died" → "who has passed"
- [P2] ProvidersStep: "Showing results from X" → "X providers found"
- [P2] Empty states: Add guidance text for ProvidersStep, PackagesStep,
  VenueStep, CoffinsStep empty results

Steps 4, 13, 15 passed with no issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 15:33:01 +11:00
parent 826496e645
commit 87249b6d9b
12 changed files with 112 additions and 29 deletions

View File

@@ -151,9 +151,10 @@ export const AdditionalServicesStep: React.FC<AdditionalServicesStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
{/* ─── Section 1: Complimentary inclusions ─── */} {/* ─── Section 1: Complimentary inclusions ─── */}

View File

@@ -156,9 +156,10 @@ export const AuthGateStep: React.FC<AuthGateStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
{/* ─── Sub-step 1: SSO + Email ─── */} {/* ─── Sub-step 1: SSO + Email ─── */}

View File

@@ -159,9 +159,10 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
{/* ─── Burial plot question ─── */} {/* ─── Burial plot question ─── */}
@@ -177,7 +178,11 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
<FormControlLabel value="no" control={<Radio />} label="No, we need to find one" /> <FormControlLabel value="no" control={<Radio />} label="No, we need to find one" />
</RadioGroup> </RadioGroup>
{errors?.burialOwn && ( {errors?.burialOwn && (
<Typography variant="body2" color="error" sx={{ mt: 0.5 }} role="alert"> <Typography
variant="body2"
sx={{ mt: 0.5, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.burialOwn} {errors.burialOwn}
</Typography> </Typography>
)} )}
@@ -201,7 +206,11 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
/> />
</RadioGroup> </RadioGroup>
{errors?.burialCustom && ( {errors?.burialCustom && (
<Typography variant="body2" color="error" sx={{ mt: 0.5 }} role="alert"> <Typography
variant="body2"
sx={{ mt: 0.5, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.burialCustom} {errors.burialCustom}
</Typography> </Typography>
)} )}
@@ -257,7 +266,11 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
</Box> </Box>
{errors?.selectedCemeteryId && ( {errors?.selectedCemeteryId && (
<Typography variant="body2" color="error" sx={{ mt: 1 }} role="alert"> <Typography
variant="body2"
sx={{ mt: 1, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.selectedCemeteryId} {errors.selectedCemeteryId}
</Typography> </Typography>
)} )}

View File

@@ -272,9 +272,10 @@ export const CoffinDetailsStep: React.FC<CoffinDetailsStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
<OptionSection <OptionSection

View File

@@ -191,9 +191,10 @@ export const CoffinsStep: React.FC<CoffinsStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
{/* Page heading */} {/* Page heading */}
@@ -293,11 +294,26 @@ export const CoffinsStep: React.FC<CoffinsStepProps> = ({
</Box> </Box>
</Card> </Card>
))} ))}
{coffins.length === 0 && (
<Box sx={{ py: 6, textAlign: 'center', gridColumn: '1 / -1' }}>
<Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
No coffins match your selected filters.
</Typography>
<Typography variant="body2" color="text.secondary">
Try adjusting the category or price range.
</Typography>
</Box>
)}
</Box> </Box>
{/* Validation error */} {/* Validation error */}
{errors?.selectedCoffinId && ( {errors?.selectedCoffinId && (
<Typography variant="body2" color="error" sx={{ mb: 2 }} role="alert"> <Typography
variant="body2"
sx={{ mb: 2, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.selectedCoffinId} {errors.selectedCoffinId}
</Typography> </Typography>
)} )}

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import TextField from '@mui/material/TextField'; import TextField from '@mui/material/TextField';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl'; import FormControl from '@mui/material/FormControl';
import FormLabel from '@mui/material/FormLabel'; import FormLabel from '@mui/material/FormLabel';
import FormControlLabel from '@mui/material/FormControlLabel'; import FormControlLabel from '@mui/material/FormControlLabel';
@@ -151,9 +152,10 @@ export const CrematoriumStep: React.FC<CrematoriumStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
{/* ─── Crematorium selection ─── */} {/* ─── Crematorium selection ─── */}
@@ -226,7 +228,11 @@ export const CrematoriumStep: React.FC<CrematoriumStepProps> = ({
)} )}
{errors?.selectedCrematoriumId && ( {errors?.selectedCrematoriumId && (
<Typography variant="body2" color="error" sx={{ mt: 1 }} role="alert"> <Typography
variant="body2"
sx={{ mt: 1, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.selectedCrematoriumId} {errors.selectedCrematoriumId}
</Typography> </Typography>
)} )}
@@ -250,7 +256,11 @@ export const CrematoriumStep: React.FC<CrematoriumStepProps> = ({
<FormControlLabel value="no" control={<Radio />} label="No" /> <FormControlLabel value="no" control={<Radio />} label="No" />
</RadioGroup> </RadioGroup>
{errors?.attend && ( {errors?.attend && (
<Typography variant="body2" color="error" sx={{ mt: 0.5 }} role="alert"> <Typography
variant="body2"
sx={{ mt: 0.5, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.attend} {errors.attend}
</Typography> </Typography>
)} )}
@@ -268,9 +278,9 @@ export const CrematoriumStep: React.FC<CrematoriumStepProps> = ({
sx={{ mb: 3 }} sx={{ mb: 3 }}
> >
{priorityOptions.map((opt) => ( {priorityOptions.map((opt) => (
<option key={opt.value} value={opt.value}> <MenuItem key={opt.value} value={opt.value}>
{opt.label} {opt.label}
</option> </MenuItem>
))} ))}
</TextField> </TextField>
)} )}

View File

@@ -148,7 +148,7 @@ export const DateTimeStep: React.FC<DateTimeStepProps> = ({
onChange({ ...values, [field]: value }); onChange({ ...values, [field]: value });
}; };
const personSectionHeading = isAtNeed ? 'About the person who died' : 'About the person'; const personSectionHeading = isAtNeed ? 'About the person who has passed' : 'About the person';
const schedulingHeading = isAtNeed const schedulingHeading = isAtNeed
? 'When are you hoping to have the service?' ? 'When are you hoping to have the service?'
@@ -178,9 +178,10 @@ export const DateTimeStep: React.FC<DateTimeStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
{/* ─── Section 1: About the person ─── */} {/* ─── Section 1: About the person ─── */}

View File

@@ -65,7 +65,7 @@ function getSubheading(values: IntroStepValues): string {
* *
* Entry point with urgency-sensitive segmentation. User selects who * Entry point with urgency-sensitive segmentation. User selects who
* the funeral is for, and (if arranging for someone else) whether * the funeral is for, and (if arranging for someone else) whether
* that person has died. * that person has passed away.
* *
* Uses the Centered Form layout variant. Progressive disclosure: * Uses the Centered Form layout variant. Progressive disclosure:
* selecting "Someone else" reveals the hasPassedAway question. * selecting "Someone else" reveals the hasPassedAway question.
@@ -117,9 +117,10 @@ export const IntroStep: React.FC<IntroStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
{/* forWhom field */} {/* forWhom field */}
@@ -151,7 +152,7 @@ export const IntroStep: React.FC<IntroStepProps> = ({
<Collapse in={showHasPassedAway}> <Collapse in={showHasPassedAway}>
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 3 }}>
<ToggleButtonGroup <ToggleButtonGroup
label="Has the person died?" label="Has this person passed away?"
options={[ options={[
{ {
value: 'yes', value: 'yes',

View File

@@ -221,7 +221,11 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
{/* Error message */} {/* Error message */}
{error && ( {error && (
<Typography variant="body2" color="error" sx={{ mb: 2 }} role="alert"> <Typography
variant="body2"
sx={{ mb: 2, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{error} {error}
</Typography> </Typography>
)} )}
@@ -262,9 +266,12 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
{packages.length === 0 && ( {packages.length === 0 && (
<Box sx={{ py: 6, textAlign: 'center' }}> <Box sx={{ py: 6, textAlign: 'center' }}>
<Typography variant="body1" color="text.secondary"> <Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
No packages match the selected budget range. No packages match the selected budget range.
</Typography> </Typography>
<Typography variant="body2" color="text.secondary">
Try selecting &quot;All packages&quot; to see the full range.
</Typography>
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -161,9 +161,10 @@ export const PaymentStep: React.FC<PaymentStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onConfirmPayment(); if (!loading) onConfirmPayment();
}} }}
> >
{/* ─── Payment plan ─── */} {/* ─── Payment plan ─── */}
@@ -229,7 +230,11 @@ export const PaymentStep: React.FC<PaymentStepProps> = ({
</Paper> </Paper>
)} )}
{errors?.card && ( {errors?.card && (
<Typography variant="body2" color="error" sx={{ mt: 1 }} role="alert"> <Typography
variant="body2"
sx={{ mt: 1, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.card} {errors.card}
</Typography> </Typography>
)} )}
@@ -310,7 +315,11 @@ export const PaymentStep: React.FC<PaymentStepProps> = ({
sx={{ mb: 1, alignItems: 'flex-start', '& .MuiCheckbox-root': { pt: 0.5 } }} sx={{ mb: 1, alignItems: 'flex-start', '& .MuiCheckbox-root': { pt: 0.5 } }}
/> />
{errors?.termsAccepted && ( {errors?.termsAccepted && (
<Typography variant="body2" color="error" sx={{ mb: 2, ml: 4 }} role="alert"> <Typography
variant="body2"
sx={{ mb: 2, ml: 4, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.termsAccepted} {errors.termsAccepted}
</Typography> </Typography>
)} )}

View File

@@ -188,12 +188,16 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
sx={{ mb: 2, display: 'block' }} sx={{ mb: 2, display: 'block' }}
aria-live="polite" aria-live="polite"
> >
Showing results from {providers.length} provider{providers.length !== 1 ? 's' : ''} {providers.length} provider{providers.length !== 1 ? 's' : ''} found
</Typography> </Typography>
{/* Error message */} {/* Error message */}
{error && ( {error && (
<Typography variant="body2" color="error" sx={{ mb: 2 }} role="alert"> <Typography
variant="body2"
sx={{ mb: 2, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{error} {error}
</Typography> </Typography>
)} )}
@@ -230,9 +234,12 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
textAlign: 'center', textAlign: 'center',
}} }}
> >
<Typography variant="body1" color="text.secondary"> <Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
No providers found matching your search. No providers found matching your search.
</Typography> </Typography>
<Typography variant="body2" color="text.secondary">
Try adjusting your search or clearing filters.
</Typography>
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -207,9 +207,10 @@ export const VenueStep: React.FC<VenueStepProps> = ({
<Box <Box
component="form" component="form"
noValidate noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => { onSubmit={(e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
onContinue(); if (!loading) onContinue();
}} }}
> >
{/* ─── Search + Filters ─── */} {/* ─── Search + Filters ─── */}
@@ -276,11 +277,26 @@ export const VenueStep: React.FC<VenueStepProps> = ({
} }
/> />
))} ))}
{venues.length === 0 && (
<Box sx={{ py: 6, textAlign: 'center' }}>
<Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
No venues found in this area.
</Typography>
<Typography variant="body2" color="text.secondary">
Try adjusting your search or clearing filters.
</Typography>
</Box>
)}
</Box> </Box>
{/* Validation error */} {/* Validation error */}
{errors?.selectedVenueId && ( {errors?.selectedVenueId && (
<Typography variant="body2" color="error" sx={{ mb: 2 }} role="alert"> <Typography
variant="body2"
sx={{ mb: 2, color: 'var(--fa-color-text-brand)' }}
role="alert"
>
{errors.selectedVenueId} {errors.selectedVenueId}
</Typography> </Typography>
)} )}