Refine FuneralFinder v1 — full stepped flow, always-visible CTA

- Extend one-question-at-a-time pattern through all steps (type, service, location)
- CTA + location always visible at bottom; smart defaults for missing optional fields
- Minimum search requirements: intent + location; type/service/themes default to "all"
- Funeral types: Cremation, Burial, Water Burial (QLD only) + Explore All as TypeCard
- Service preference step (conditional): With a service / No service / I'm flexible
- Theme preferences (eco-friendly, budget-friendly, religious specialisation) as optional
  sub-option within type step
- StepHeading sub-component: bodyLg centered, distinct from card labels
- CompletedRows: generous py:1.5 spacing, caption-size "Change" with aria-label
- Loading prop on CTA button, location validation (3+ chars)
- Divider under subheading for visual structure
- Main heading upgraded to h2 with display font
- Audit: 14/20 (Good), Critique: 29/40 (Good)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 11:39:57 +11:00
parent e6f7817c18
commit a655245842
2 changed files with 402 additions and 218 deletions

View File

@@ -1,10 +1,13 @@
import React from 'react';
import Box from '@mui/material/Box';
import Collapse from '@mui/material/Collapse';
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import type { SxProps, Theme } from '@mui/material/styles';
import { Typography } from '../../atoms/Typography';
import { Button } from '../../atoms/Button';
import { Input } from '../../atoms/Input';
import { Chip } from '../../atoms/Chip';
import { Divider } from '../../atoms/Divider';
import { Link } from '../../atoms/Link';
// ─── Types ───────────────────────────────────────────────────────────────────
@@ -15,6 +18,20 @@ export interface FuneralTypeOption {
id: string;
/** Display label */
label: string;
/** Brief description shown below the label */
description?: string;
/** Availability note, e.g., "Available in QLD only" */
note?: string;
/** Whether this type supports with/without service toggle */
hasServiceOption?: boolean;
}
/** A thematic filter option */
export interface ThemeOption {
/** Unique identifier */
id: string;
/** Display label */
label: string;
}
/** Search parameters returned when the user submits */
@@ -23,8 +40,12 @@ export interface FuneralSearchParams {
intent: 'arrange' | 'preplan';
/** Only present when intent is "preplan" */
planningFor?: 'myself' | 'someone-else';
/** Selected funeral type ID */
funeralTypeId: string;
/** Selected funeral type ID, or null if "Explore all" / not specified */
funeralTypeId: string | null;
/** "with-service", "without-service", or "either" */
servicePreference: 'with-service' | 'without-service' | 'either';
/** Selected theme filter IDs (may be empty) */
themes: string[];
/** Suburb or postcode entered */
location: string;
}
@@ -33,12 +54,18 @@ export interface FuneralSearchParams {
export interface FuneralFinderProps {
/** Available funeral types — dynamic list from API */
funeralTypes: FuneralTypeOption[];
/** Called when the user clicks "Find funeral directors" */
/** Optional thematic filter options (e.g., eco-friendly, budget-friendly) */
themeOptions?: ThemeOption[];
/** Called when the user clicks "Find funeral providers" */
onSearch?: (params: FuneralSearchParams) => void;
/** Whether a search is in progress — shows loading state on the CTA */
loading?: boolean;
/** Optional heading override */
heading?: string;
/** Optional subheading override */
subheading?: string;
/** Show "Explore all options" choice. Default true. */
showExploreAll?: boolean;
/** MUI sx prop for the root card */
sx?: SxProps<Theme>;
}
@@ -47,9 +74,21 @@ export interface FuneralFinderProps {
type Intent = 'arrange' | 'preplan' | null;
type PlanningFor = 'myself' | 'someone-else' | null;
type ServicePref = 'with-service' | 'without-service' | 'either';
// ─── Sub-components ──────────────────────────────────────────────────────────
/** Step question heading — centered, larger than card labels */
function StepHeading({ children }: { children: React.ReactNode }) {
return (
<Box sx={{ mb: 2.5 }}>
<Typography variant="bodyLg" sx={{ fontWeight: 600, textAlign: 'center' }}>
{children}
</Typography>
</Box>
);
}
/** Large tappable option for binary choices (intent, planning-for) */
function ChoiceCard({
label,
@@ -69,8 +108,7 @@ function ChoiceCard({
aria-checked={selected}
onClick={onClick}
sx={{
flex: 1,
minWidth: 0,
width: '100%',
px: 2.5,
py: 2,
border: '2px solid',
@@ -85,6 +123,7 @@ function ChoiceCard({
borderColor: 'var(--fa-color-brand-400)',
bgcolor: selected ? 'var(--fa-color-surface-warm)' : 'var(--fa-color-brand-50)',
},
'&:active': { filter: 'brightness(0.95)' },
'&:focus-visible': {
outline: '2px solid var(--fa-color-brand-500)',
outlineOffset: 2,
@@ -107,16 +146,7 @@ function ChoiceCard({
</Typography>
</Box>
{description && (
<Typography
variant="caption"
component="span"
sx={{
display: 'block',
mt: 0.5,
color: 'text.secondary',
ml: selected ? 3.5 : 0,
}}
>
<Typography variant="caption" component="span" sx={{ display: 'block', mt: 0.5, color: 'text.secondary' }}>
{description}
</Typography>
)}
@@ -124,13 +154,17 @@ function ChoiceCard({
);
}
/** Funeral type option — generous pill button */
function TypePill({
/** Funeral type card — compact selectable card */
function TypeCard({
label,
description,
note,
selected,
onClick,
}: {
label: string;
description?: string;
note?: string;
selected: boolean;
onClick: () => void;
}) {
@@ -141,30 +175,54 @@ function TypePill({
aria-checked={selected}
onClick={onClick}
sx={{
px: 2.5,
py: 1.25,
width: '100%',
minHeight: 44,
px: 2,
py: 2,
border: '2px solid',
borderColor: selected ? 'var(--fa-color-brand-500)' : 'var(--fa-color-neutral-200)',
borderRadius: 'var(--fa-border-radius-full)',
borderRadius: 'var(--fa-border-radius-md)',
bgcolor: selected ? 'var(--fa-color-surface-warm)' : 'var(--fa-color-surface-default)',
cursor: 'pointer',
fontFamily: 'inherit',
fontSize: '0.875rem',
fontWeight: selected ? 600 : 500,
color: selected ? 'var(--fa-color-brand-700)' : 'text.primary',
whiteSpace: 'nowrap',
textAlign: 'left',
transition: 'border-color 150ms ease-in-out, background-color 150ms ease-in-out',
'&:hover': {
borderColor: 'var(--fa-color-brand-400)',
bgcolor: selected ? 'var(--fa-color-surface-warm)' : 'var(--fa-color-brand-50)',
},
'&:active': { filter: 'brightness(0.95)' },
'&:focus-visible': {
outline: '2px solid var(--fa-color-brand-500)',
outlineOffset: 2,
},
}}
>
{label}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{selected && (
<CheckCircleOutlineIcon
aria-hidden="true"
sx={{ fontSize: 20, color: 'var(--fa-color-brand-500)', flexShrink: 0 }}
/>
)}
<Typography
variant="body2"
component="span"
sx={{ fontWeight: 600, color: selected ? 'var(--fa-color-brand-700)' : 'text.primary' }}
>
{label}
</Typography>
</Box>
{description && (
<Typography variant="caption" component="span" sx={{ display: 'block', mt: 0.25, color: 'text.secondary' }}>
{description}
</Typography>
)}
{note && (
<Typography variant="captionSm" component="span" sx={{ display: 'block', mt: 0.5, color: 'text.secondary', fontWeight: 500 }}>
{note}
</Typography>
)}
</Box>
);
}
@@ -186,7 +244,7 @@ function CompletedRow({
alignItems: 'baseline',
flexWrap: 'wrap',
gap: 0.75,
py: 1,
py: 1.5,
borderBottom: '1px solid',
borderColor: 'var(--fa-color-neutral-100)',
}}
@@ -201,7 +259,9 @@ function CompletedRow({
component="button"
variant="caption"
onClick={onChangeClick}
sx={{ color: 'text.secondary', ml: 'auto' }}
underline="hover"
aria-label={`Change ${question.toLowerCase()}`}
sx={{ color: 'text.secondary', ml: 'auto', minHeight: 44, display: 'inline-flex', alignItems: 'center' }}
>
Change
</Link>
@@ -209,63 +269,94 @@ function CompletedRow({
);
}
// ─── Service preference options ──────────────────────────────────────────────
const SERVICE_OPTIONS: { value: ServicePref; label: string }[] = [
{ value: 'with-service', label: 'With a service' },
{ value: 'without-service', label: 'No service' },
{ value: 'either', label: "I'm flexible" },
];
// ─── Component ───────────────────────────────────────────────────────────────
/**
* Hero search widget for the FA homepage.
*
* Guides users through a conversational stepped flow to find funeral directors.
* Each question appears naturally after the previous is answered. Completed
* answers collapse to a compact row with a "Change" link to revert.
* Guides users through a conversational stepped flow to find funeral providers.
* Every question is its own step that collapses to a compact summary row once
* answered. The location input and CTA are always visible at the bottom —
* minimum search requirements are intent + location; all other fields default
* to "show all" if not explicitly answered.
*
* Flow:
* 1. "How can we help?" → Arrange now / Pre-plan
* 2. "Who is this for?" (pre-plan only) → Myself / Someone else
* 3. "What type of funeral?" → dynamic pill buttons
* 4. "Where are you located?" → suburb/postcode input
* 5. CTA: "Find funeral directors"
*
* Composes Typography + Button + Input + Link + custom ChoiceCard/TypePill.
* 3. "What type of funeral?" → type cards + "Explore all" + optional theme chips
* 4. "Would you like a service?" (conditional) → chips (auto-advance)
* 5. Location + CTA (always visible)
*/
export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps>(
(
{
funeralTypes,
themeOptions = [],
onSearch,
loading = false,
heading = 'Find funeral directors near you',
subheading = "Tell us a little about what you're looking for and we'll show you options in your area.",
showExploreAll = true,
sx,
},
ref,
) => {
const [intent, setIntent] = React.useState<Intent>(null);
const [planningFor, setPlanningFor] = React.useState<PlanningFor>(null);
const [funeralTypeId, setFuneralTypeId] = React.useState<string | null>(null);
const [typeSelection, setTypeSelection] = React.useState<string | null>(null);
const [servicePref, setServicePref] = React.useState<ServicePref>('either');
const [serviceAnswered, setServiceAnswered] = React.useState(false);
const [selectedThemes, setSelectedThemes] = React.useState<string[]>([]);
const [location, setLocation] = React.useState('');
const [locationError, setLocationError] = React.useState('');
const [showIntentPrompt, setShowIntentPrompt] = React.useState(false);
const [editingStep, setEditingStep] = React.useState<number | null>(null);
const needsPlanningFor = intent === 'preplan';
const funeralTypeLabel = funeralTypes.find((ft) => ft.id === funeralTypeId)?.label;
const isExploreAll = typeSelection === 'all';
const selectedType = funeralTypes.find((ft) => ft.id === typeSelection);
const showServiceStep = !isExploreAll && selectedType?.hasServiceOption === true;
const typeSelected = typeSelection !== null;
// Which step is currently active?
// Which step is currently active? (0 = all complete)
const activeStep = (() => {
if (editingStep) return editingStep;
if (editingStep !== null) return editingStep;
if (!intent) return 1;
if (needsPlanningFor && !planningFor) return 2;
if (!funeralTypeId) return 3;
return 4;
if (!typeSelection) return 3;
if (showServiceStep && !serviceAnswered) return 4;
return 0;
})();
const canSubmit =
intent !== null &&
(!needsPlanningFor || planningFor !== null) &&
funeralTypeId !== null &&
location.trim().length > 0;
// ─── Labels ─────────────────────────────────────────────────────
const intentLabel = intent === 'arrange' ? 'Arrange a funeral now' : 'Pre-plan a funeral';
const planningForLabel = planningFor === 'myself' ? 'Myself' : 'Someone else';
// ─── Handlers ────────────────────────────────────────────────────
const typeLabel = isExploreAll ? 'All options' : (selectedType?.label ?? '');
const themeSuffix = selectedThemes
.map((id) => themeOptions.find((t) => t.id === id)?.label?.toLowerCase())
.filter(Boolean)
.join(', ');
const typeSummary = [typeLabel, themeSuffix].filter(Boolean).join(', ');
const serviceLabel =
servicePref === 'with-service' ? 'With a service'
: servicePref === 'without-service' ? 'No service'
: 'Flexible';
// ─── Handlers ───────────────────────────────────────────────────
const selectIntent = (value: Intent) => {
setIntent(value);
if (value === 'arrange') setPlanningFor(null);
setShowIntentPrompt(false);
setEditingStep(null);
};
@@ -274,26 +365,57 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
setEditingStep(null);
};
const selectFuneralType = (id: string) => {
setFuneralTypeId(id);
const selectType = (id: string) => {
setTypeSelection(id);
if (id === 'all' || !funeralTypes.find((ft) => ft.id === id)?.hasServiceOption) {
setServicePref('either');
setServiceAnswered(false);
}
setEditingStep(null);
};
const selectService = (value: ServicePref) => {
setServicePref(value);
setServiceAnswered(true);
setEditingStep(null);
};
const toggleTheme = (id: string) => {
setSelectedThemes((prev) =>
prev.includes(id) ? prev.filter((t) => t !== id) : [...prev, id],
);
};
const revertTo = (step: number) => {
setEditingStep(step);
};
const handleSubmit = () => {
if (!canSubmit || !intent || !funeralTypeId) return;
// Minimum: intent + location
if (!intent) {
setEditingStep(null);
setShowIntentPrompt(true);
return;
}
if (location.trim().length < 3) {
setLocationError('Please enter a suburb or postcode');
return;
}
setLocationError('');
setShowIntentPrompt(false);
// Smart defaults — missing optional fields default to "all"
onSearch?.({
intent,
planningFor: needsPlanningFor ? (planningFor ?? undefined) : undefined,
funeralTypeId,
funeralTypeId: isExploreAll ? null : (typeSelection ?? null),
servicePreference: (showServiceStep && serviceAnswered) ? servicePref : 'either',
themes: selectedThemes,
location: location.trim(),
});
};
// ─── Render ─────────────────────────────────────────────────────
// ─── Render ─────────────────────────────────────────────────────
return (
<Box
ref={ref}
@@ -309,142 +431,197 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
>
{/* Header */}
<Typography
variant="h4"
variant="h2"
component="h2"
sx={{ fontFamily: 'var(--fa-font-family-display)', textAlign: 'center', mb: 1 }}
sx={{ fontFamily: 'var(--fa-font-family-display)', textAlign: 'center', mb: 1.5 }}
>
{heading}
</Typography>
<Typography
variant="body2"
color="text.secondary"
sx={{ textAlign: 'center', mb: 3.5, maxWidth: 400, mx: 'auto' }}
sx={{ textAlign: 'center', maxWidth: 400, mx: 'auto' }}
>
{subheading}
</Typography>
<Divider sx={{ mt: 3, mb: 0 }} />
{/* Completed answers */}
{intent && activeStep > 1 && editingStep !== 1 && (
<CompletedRow
question="I'm here to"
answer={intent === 'arrange' ? 'Arrange a funeral now' : 'Pre-plan a funeral'}
onChangeClick={() => revertTo(1)}
/>
)}
{needsPlanningFor && planningFor && activeStep > 2 && editingStep !== 2 && (
<CompletedRow
question="Planning for"
answer={planningFor === 'myself' ? 'Myself' : 'Someone else'}
onChangeClick={() => revertTo(2)}
/>
)}
{funeralTypeId && funeralTypeLabel && activeStep > 3 && editingStep !== 3 && (
<CompletedRow
question="Type of funeral"
answer={funeralTypeLabel}
onChangeClick={() => revertTo(3)}
/>
)}
{/* ── Completed rows ─────────────────────────────────────── */}
<Collapse in={intent !== null && activeStep !== 1} timeout={250}>
<CompletedRow question="I'm here to" answer={intentLabel} onChangeClick={() => revertTo(1)} />
</Collapse>
<Collapse in={needsPlanningFor && planningFor !== null && activeStep !== 2} timeout={250}>
<CompletedRow question="Planning for" answer={planningForLabel} onChangeClick={() => revertTo(2)} />
</Collapse>
<Collapse in={typeSelected && activeStep !== 3} timeout={250}>
<CompletedRow question="Looking for" answer={typeSummary} onChangeClick={() => revertTo(3)} />
</Collapse>
<Collapse in={showServiceStep && serviceAnswered && activeStep !== 4} timeout={250}>
<CompletedRow question="Service" answer={serviceLabel} onChangeClick={() => revertTo(4)} />
</Collapse>
{/* Active question */}
<Box sx={{ mt: intent && activeStep > 1 && editingStep !== 1 ? 3 : 0 }}>
{/* Step 1: Intent */}
{activeStep === 1 && (
<Box>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}>
How can we help you today?
{/* ── Step 1: Intent ─────────────────────────────────────── */}
<Collapse in={activeStep === 1} timeout={250}>
<Box sx={{ mt: 3 }}>
{showIntentPrompt && (
<Typography
variant="caption"
role="alert"
sx={{ color: 'var(--fa-color-brand-600)', textAlign: 'center', display: 'block', mb: 1.5 }}
>
Please let us know how we can help
</Typography>
<Box role="radiogroup" aria-label="How can we help" sx={{ display: 'flex', gap: 1.5, flexDirection: { xs: 'column', sm: 'row' } }}>
<ChoiceCard
label="Arrange a funeral now"
description="Someone has passed and I need to make arrangements"
selected={intent === 'arrange'}
onClick={() => selectIntent('arrange')}
/>
<ChoiceCard
label="Pre-plan a funeral"
description="I'd like to plan ahead for the future"
selected={intent === 'preplan'}
onClick={() => selectIntent('preplan')}
/>
</Box>
</Box>
)}
{/* Step 2: Planning for (conditional) */}
{activeStep === 2 && needsPlanningFor && (
<Box>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}>
Who are you planning for?
</Typography>
<Box role="radiogroup" aria-label="Who are you planning for" sx={{ display: 'flex', gap: 1.5, flexDirection: { xs: 'column', sm: 'row' } }}>
<ChoiceCard
label="Myself"
description="I want to plan my own funeral in advance"
selected={planningFor === 'myself'}
onClick={() => selectPlanningFor('myself')}
/>
<ChoiceCard
label="Someone else"
description="I'm helping a family member or friend plan ahead"
selected={planningFor === 'someone-else'}
onClick={() => selectPlanningFor('someone-else')}
/>
</Box>
</Box>
)}
{/* Step 3: Funeral type */}
{activeStep === 3 && (
<Box>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}>
What type of funeral are you considering?
</Typography>
<Box role="radiogroup" aria-label="Type of funeral" sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{funeralTypes.map((ft) => (
<TypePill
key={ft.id}
label={ft.label}
selected={funeralTypeId === ft.id}
onClick={() => selectFuneralType(ft.id)}
/>
))}
</Box>
</Box>
)}
{/* Step 4: Location */}
{activeStep === 4 && (
<Box>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}>
Where are you located?
</Typography>
<Input
placeholder="Suburb or postcode"
value={location}
onChange={(e) => setLocation(e.target.value)}
size="small"
fullWidth
inputProps={{ 'aria-label': 'Location — suburb or postcode' }}
onKeyDown={(e) => {
if (e.key === 'Enter' && canSubmit) handleSubmit();
}}
)}
<StepHeading>How can we help you today?</StepHeading>
<Box role="radiogroup" aria-label="How can we help" sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<ChoiceCard
label="Arrange a funeral now"
description="Someone has passed and I need to make arrangements"
selected={intent === 'arrange'}
onClick={() => selectIntent('arrange')}
/>
<ChoiceCard
label="Pre-plan a funeral"
description="I'd like to plan ahead for the future"
selected={intent === 'preplan'}
onClick={() => selectIntent('preplan')}
/>
</Box>
)}
</Box>
</Box>
</Collapse>
{/* ── Step 2: Planning for (conditional) ─────────────────── */}
<Collapse in={activeStep === 2 && needsPlanningFor} timeout={250}>
<Box sx={{ mt: 3 }}>
<StepHeading>Who are you planning for?</StepHeading>
<Box role="radiogroup" aria-label="Who are you planning for" sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<ChoiceCard
label="Myself"
description="I want to plan my own funeral in advance"
selected={planningFor === 'myself'}
onClick={() => selectPlanningFor('myself')}
/>
<ChoiceCard
label="Someone else"
description="I'm helping a family member or friend plan ahead"
selected={planningFor === 'someone-else'}
onClick={() => selectPlanningFor('someone-else')}
/>
</Box>
</Box>
</Collapse>
{/* ── Step 3: Type + Preferences ─────────────────────────── */}
<Collapse in={activeStep === 3} timeout={250}>
<Box sx={{ mt: 3 }}>
<StepHeading>What type of funeral are you considering?</StepHeading>
<Box role="radiogroup" aria-label="Type of funeral" sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{funeralTypes.map((ft) => (
<TypeCard
key={ft.id}
label={ft.label}
description={ft.description}
note={ft.note}
selected={typeSelection === ft.id}
onClick={() => selectType(ft.id)}
/>
))}
{showExploreAll && (
<TypeCard
label="Explore all options"
description="Browse everything available in your area"
selected={isExploreAll}
onClick={() => selectType('all')}
/>
)}
</Box>
{/* Theme preferences — optional, inside type step */}
{themeOptions.length > 0 && (
<Box sx={{ mt: 3 }}>
<Box sx={{ mb: 1.5 }}>
<Typography variant="body2" component="span" sx={{ fontWeight: 600 }}>
Any preferences?
</Typography>
<Typography variant="caption" component="span" color="text.secondary" sx={{ ml: 0.75 }}>
(optional)
</Typography>
</Box>
<Box role="group" aria-label="Preferences" sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{themeOptions.map((theme) => {
const isSelected = selectedThemes.includes(theme.id);
return (
<Chip
key={theme.id}
label={theme.label}
variant="outlined"
selected={isSelected}
onClick={() => toggleTheme(theme.id)}
clickable
aria-pressed={isSelected}
sx={{ height: 44 }}
/>
);
})}
</Box>
</Box>
)}
</Box>
</Collapse>
{/* ── Step 4: Service (conditional, auto-advance) ────────── */}
<Collapse in={activeStep === 4 && showServiceStep} timeout={250}>
<Box sx={{ mt: 3 }}>
<StepHeading>Would you like a service?</StepHeading>
<Box role="group" aria-label="Service preference" sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{SERVICE_OPTIONS.map((opt) => (
<Chip
key={opt.value}
label={opt.label}
variant="outlined"
selected={serviceAnswered && servicePref === opt.value}
onClick={() => selectService(opt.value)}
clickable
aria-pressed={serviceAnswered && servicePref === opt.value}
sx={{ justifyContent: 'flex-start', height: 44, borderRadius: 'var(--fa-border-radius-md)' }}
/>
))}
</Box>
</Box>
</Collapse>
{/* ── Always visible: Location + CTA ─────────────────────── */}
<Box sx={{ mt: 3 }}>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1.5 }}>
Where are you looking?
</Typography>
<Input
placeholder="Suburb or postcode"
value={location}
onChange={(e) => {
setLocation(e.target.value);
if (locationError) setLocationError('');
}}
size="small"
fullWidth
error={!!locationError}
helperText={locationError || 'Enter a suburb name or 4-digit postcode'}
inputProps={{ 'aria-label': 'Where are you looking — suburb or postcode' }}
onKeyDown={(e) => {
if (e.key === 'Enter') handleSubmit();
}}
/>
{/* CTA — only visible once we're on the location step or beyond */}
{activeStep >= 4 && (
<Box sx={{ mt: 3 }}>
<Button
variant="contained"
size="large"
fullWidth
disabled={!canSubmit}
loading={loading}
disabled={loading}
onClick={handleSubmit}
>
Find funeral directors
Find funeral providers
</Button>
<Typography
variant="captionSm"
@@ -454,7 +631,7 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
Free to use · No obligation
</Typography>
</Box>
)}
</Box>
</Box>
);
},