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:
@@ -151,9 +151,10 @@ export const AdditionalServicesStep: React.FC<AdditionalServicesStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
{/* ─── Section 1: Complimentary inclusions ─── */}
|
||||
|
||||
@@ -156,9 +156,10 @@ export const AuthGateStep: React.FC<AuthGateStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
{/* ─── Sub-step 1: SSO + Email ─── */}
|
||||
|
||||
@@ -159,9 +159,10 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
{/* ─── 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" />
|
||||
</RadioGroup>
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -201,7 +206,11 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
|
||||
/>
|
||||
</RadioGroup>
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -257,7 +266,11 @@ export const CemeteryStep: React.FC<CemeteryStepProps> = ({
|
||||
</Box>
|
||||
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@@ -272,9 +272,10 @@ export const CoffinDetailsStep: React.FC<CoffinDetailsStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
<OptionSection
|
||||
|
||||
@@ -191,9 +191,10 @@ export const CoffinsStep: React.FC<CoffinsStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
{/* Page heading */}
|
||||
@@ -293,11 +294,26 @@ export const CoffinsStep: React.FC<CoffinsStepProps> = ({
|
||||
</Box>
|
||||
</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>
|
||||
|
||||
{/* Validation error */}
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -151,9 +152,10 @@ export const CrematoriumStep: React.FC<CrematoriumStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
{/* ─── Crematorium selection ─── */}
|
||||
@@ -226,7 +228,11 @@ export const CrematoriumStep: React.FC<CrematoriumStepProps> = ({
|
||||
)}
|
||||
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -250,7 +256,11 @@ export const CrematoriumStep: React.FC<CrematoriumStepProps> = ({
|
||||
<FormControlLabel value="no" control={<Radio />} label="No" />
|
||||
</RadioGroup>
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -268,9 +278,9 @@ export const CrematoriumStep: React.FC<CrematoriumStepProps> = ({
|
||||
sx={{ mb: 3 }}
|
||||
>
|
||||
{priorityOptions.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
<MenuItem key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
)}
|
||||
|
||||
@@ -148,7 +148,7 @@ export const DateTimeStep: React.FC<DateTimeStepProps> = ({
|
||||
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
|
||||
? 'When are you hoping to have the service?'
|
||||
@@ -178,9 +178,10 @@ export const DateTimeStep: React.FC<DateTimeStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
{/* ─── Section 1: About the person ─── */}
|
||||
|
||||
@@ -65,7 +65,7 @@ function getSubheading(values: IntroStepValues): string {
|
||||
*
|
||||
* Entry point with urgency-sensitive segmentation. User selects who
|
||||
* 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:
|
||||
* selecting "Someone else" reveals the hasPassedAway question.
|
||||
@@ -117,9 +117,10 @@ export const IntroStep: React.FC<IntroStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
{/* forWhom field */}
|
||||
@@ -151,7 +152,7 @@ export const IntroStep: React.FC<IntroStepProps> = ({
|
||||
<Collapse in={showHasPassedAway}>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<ToggleButtonGroup
|
||||
label="Has the person died?"
|
||||
label="Has this person passed away?"
|
||||
options={[
|
||||
{
|
||||
value: 'yes',
|
||||
|
||||
@@ -221,7 +221,11 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
|
||||
|
||||
{/* Error message */}
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -262,9 +266,12 @@ export const PackagesStep: React.FC<PackagesStepProps> = ({
|
||||
|
||||
{packages.length === 0 && (
|
||||
<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.
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Try selecting "All packages" to see the full range.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -161,9 +161,10 @@ export const PaymentStep: React.FC<PaymentStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onConfirmPayment();
|
||||
if (!loading) onConfirmPayment();
|
||||
}}
|
||||
>
|
||||
{/* ─── Payment plan ─── */}
|
||||
@@ -229,7 +230,11 @@ export const PaymentStep: React.FC<PaymentStepProps> = ({
|
||||
</Paper>
|
||||
)}
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -310,7 +315,11 @@ export const PaymentStep: React.FC<PaymentStepProps> = ({
|
||||
sx={{ mb: 1, alignItems: 'flex-start', '& .MuiCheckbox-root': { pt: 0.5 } }}
|
||||
/>
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@@ -188,12 +188,16 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
|
||||
sx={{ mb: 2, display: 'block' }}
|
||||
aria-live="polite"
|
||||
>
|
||||
Showing results from {providers.length} provider{providers.length !== 1 ? 's' : ''}
|
||||
{providers.length} provider{providers.length !== 1 ? 's' : ''} found
|
||||
</Typography>
|
||||
|
||||
{/* Error message */}
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -230,9 +234,12 @@ export const ProvidersStep: React.FC<ProvidersStepProps> = ({
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
|
||||
No providers found matching your search.
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Try adjusting your search or clearing filters.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -207,9 +207,10 @@ export const VenueStep: React.FC<VenueStepProps> = ({
|
||||
<Box
|
||||
component="form"
|
||||
noValidate
|
||||
aria-busy={loading}
|
||||
onSubmit={(e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onContinue();
|
||||
if (!loading) onContinue();
|
||||
}}
|
||||
>
|
||||
{/* ─── 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>
|
||||
|
||||
{/* Validation error */}
|
||||
{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}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user