Redesign FuneralFinder — conversational flow, not bureaucratic form
Complete rewrite of the interaction model: Before: numbered steps + uppercase overline labels + cramped option cards → felt like a government form, not gentle guidance After: conversational questions with generous choice cards → "How can we help you today?" not "1. I'M HERE TO" Key changes: - Questions as warm sentences, not uppercase labels - ChoiceCards: generous padding (20px × 16px), descriptions explaining each option, check icon on selection - TypePills: pill-shaped buttons with proper touch targets for funeral types - CompletedRow: "I'm here to: **Arrange a funeral now** · Change" — explicit, accessible, no hidden pencil icon - Only shows one question at a time — true progressive disclosure - CTA only appears once location step is reached - No step numbers, no badges — the vertical flow IS the progression Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
import { Typography } from '../../atoms/Typography';
|
import { Typography } from '../../atoms/Typography';
|
||||||
import { Button } from '../../atoms/Button';
|
import { Button } from '../../atoms/Button';
|
||||||
import { Chip } from '../../atoms/Chip';
|
|
||||||
import { Input } from '../../atoms/Input';
|
import { Input } from '../../atoms/Input';
|
||||||
|
import { Link } from '../../atoms/Link';
|
||||||
|
|
||||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -44,61 +43,24 @@ export interface FuneralFinderProps {
|
|||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Step state types ────────────────────────────────────────────────────────
|
// ─── Internal types ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
type Intent = 'arrange' | 'preplan' | null;
|
type Intent = 'arrange' | 'preplan' | null;
|
||||||
type PlanningFor = 'myself' | 'someone-else' | null;
|
type PlanningFor = 'myself' | 'someone-else' | null;
|
||||||
|
|
||||||
type StepStatus = 'active' | 'completed' | 'locked';
|
|
||||||
|
|
||||||
// ─── Sub-components ──────────────────────────────────────────────────────────
|
// ─── Sub-components ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Step number badge or completed checkmark */
|
/** Large tappable option for binary choices (intent, planning-for) */
|
||||||
function StepBadge({ step, status }: { step: number; status: StepStatus }) {
|
function ChoiceCard({
|
||||||
if (status === 'completed') {
|
|
||||||
return (
|
|
||||||
<CheckCircleIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
sx={{
|
|
||||||
fontSize: 24,
|
|
||||||
color: 'var(--fa-color-brand-500)',
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
borderRadius: '50%',
|
|
||||||
bgcolor: status === 'active' ? 'var(--fa-color-brand-500)' : 'var(--fa-color-neutral-300)',
|
|
||||||
color: status === 'active' ? 'var(--fa-color-white)' : 'var(--fa-color-neutral-500)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
fontWeight: 600,
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{step}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A selectable option card within a step — uses radio semantics */
|
|
||||||
function OptionCard({
|
|
||||||
label,
|
label,
|
||||||
|
description,
|
||||||
selected,
|
selected,
|
||||||
onClick,
|
onClick,
|
||||||
disabled,
|
|
||||||
}: {
|
}: {
|
||||||
label: string;
|
label: string;
|
||||||
|
description?: string;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
disabled?: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -106,27 +68,95 @@ function OptionCard({
|
|||||||
role="radio"
|
role="radio"
|
||||||
aria-checked={selected}
|
aria-checked={selected}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
|
||||||
sx={{
|
sx={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
px: 2,
|
px: 2.5,
|
||||||
py: 1.5,
|
py: 2,
|
||||||
border: '2px solid',
|
border: '2px solid',
|
||||||
borderColor: selected ? 'var(--fa-color-brand-500)' : 'divider',
|
borderColor: selected ? 'var(--fa-color-brand-500)' : 'var(--fa-color-neutral-200)',
|
||||||
borderRadius: 'var(--fa-border-radius-md)',
|
borderRadius: 'var(--fa-border-radius-md)',
|
||||||
bgcolor: selected ? 'var(--fa-color-surface-warm)' : 'transparent',
|
bgcolor: selected ? 'var(--fa-color-surface-warm)' : 'var(--fa-color-surface-default)',
|
||||||
cursor: disabled ? 'default' : 'pointer',
|
cursor: 'pointer',
|
||||||
opacity: disabled ? 0.4 : 1,
|
fontFamily: 'inherit',
|
||||||
|
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)',
|
||||||
|
},
|
||||||
|
'&:focus-visible': {
|
||||||
|
outline: '2px solid var(--fa-color-brand-500)',
|
||||||
|
outlineOffset: 2,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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="body1"
|
||||||
|
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.5,
|
||||||
|
color: 'text.secondary',
|
||||||
|
ml: selected ? 3.5 : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Funeral type option — generous pill button */
|
||||||
|
function TypePill({
|
||||||
|
label,
|
||||||
|
selected,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
selected: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
component="button"
|
||||||
|
role="radio"
|
||||||
|
aria-checked={selected}
|
||||||
|
onClick={onClick}
|
||||||
|
sx={{
|
||||||
|
px: 2.5,
|
||||||
|
py: 1.25,
|
||||||
|
border: '2px solid',
|
||||||
|
borderColor: selected ? 'var(--fa-color-brand-500)' : 'var(--fa-color-neutral-200)',
|
||||||
|
borderRadius: 'var(--fa-border-radius-full)',
|
||||||
|
bgcolor: selected ? 'var(--fa-color-surface-warm)' : 'var(--fa-color-surface-default)',
|
||||||
|
cursor: 'pointer',
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit',
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
fontWeight: 500,
|
fontWeight: selected ? 600 : 500,
|
||||||
color: selected ? 'var(--fa-color-brand-700)' : 'text.primary',
|
color: selected ? 'var(--fa-color-brand-700)' : 'text.primary',
|
||||||
textAlign: 'left',
|
whiteSpace: 'nowrap',
|
||||||
transition: 'all 150ms ease-in-out',
|
transition: 'border-color 150ms ease-in-out, background-color 150ms ease-in-out',
|
||||||
'&:hover:not(:disabled)': {
|
'&:hover': {
|
||||||
borderColor: 'var(--fa-color-brand-400)',
|
borderColor: 'var(--fa-color-brand-400)',
|
||||||
bgcolor: 'var(--fa-color-brand-50)',
|
bgcolor: selected ? 'var(--fa-color-surface-warm)' : 'var(--fa-color-brand-50)',
|
||||||
},
|
},
|
||||||
'&:focus-visible': {
|
'&:focus-visible': {
|
||||||
outline: '2px solid var(--fa-color-brand-500)',
|
outline: '2px solid var(--fa-color-brand-500)',
|
||||||
@@ -139,24 +169,63 @@ function OptionCard({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Completed answer row — question + answer + change link */
|
||||||
|
function CompletedRow({
|
||||||
|
question,
|
||||||
|
answer,
|
||||||
|
onChangeClick,
|
||||||
|
}: {
|
||||||
|
question: string;
|
||||||
|
answer: string;
|
||||||
|
onChangeClick: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'baseline',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 0.75,
|
||||||
|
py: 1,
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderColor: 'var(--fa-color-neutral-100)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body2" color="text.secondary" component="span">
|
||||||
|
{question}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" component="span" sx={{ fontWeight: 600 }}>
|
||||||
|
{answer}
|
||||||
|
</Typography>
|
||||||
|
<Link
|
||||||
|
component="button"
|
||||||
|
variant="caption"
|
||||||
|
onClick={onChangeClick}
|
||||||
|
sx={{ color: 'text.secondary', ml: 'auto' }}
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Component ───────────────────────────────────────────────────────────────
|
// ─── Component ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hero search widget for the FA homepage.
|
* Hero search widget for the FA homepage.
|
||||||
*
|
*
|
||||||
* Guides users through a procedural stepped flow to find funeral directors:
|
* Guides users through a conversational stepped flow to find funeral directors.
|
||||||
* 1. "I'm here to" — Arrange a funeral now / Pre-plan a funeral
|
* Each question appears naturally after the previous is answered. Completed
|
||||||
* 2. "I'm planning for" (conditional, only for pre-plan) — Myself / Someone else
|
* answers collapse to a compact row with a "Change" link to revert.
|
||||||
* 3. "Type of funeral" — dynamic list of funeral types (Cremation, Burial, etc.)
|
*
|
||||||
* 4. Location — suburb or postcode input
|
* 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"
|
* 5. CTA: "Find funeral directors"
|
||||||
*
|
*
|
||||||
* Each step reveals progressively. Completed steps collapse to show the
|
* Composes Typography + Button + Input + Link + custom ChoiceCard/TypePill.
|
||||||
* selected value with a brand checkmark. Click a completed step to re-edit.
|
|
||||||
* Reverting a step only resets dependent steps (e.g., changing intent from
|
|
||||||
* "Pre-plan" to "Arrange now" removes the "I'm planning for" step).
|
|
||||||
*
|
|
||||||
* Composes Typography + Button + Chip + Input.
|
|
||||||
*/
|
*/
|
||||||
export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps>(
|
export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps>(
|
||||||
(
|
(
|
||||||
@@ -169,77 +238,48 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
|
|||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
// ─── State ───────────────────────────────────────────────────────
|
|
||||||
const [intent, setIntent] = React.useState<Intent>(null);
|
const [intent, setIntent] = React.useState<Intent>(null);
|
||||||
const [planningFor, setPlanningFor] = React.useState<PlanningFor>(null);
|
const [planningFor, setPlanningFor] = React.useState<PlanningFor>(null);
|
||||||
const [funeralTypeId, setFuneralTypeId] = React.useState<string | null>(null);
|
const [funeralTypeId, setFuneralTypeId] = React.useState<string | null>(null);
|
||||||
const [location, setLocation] = React.useState('');
|
const [location, setLocation] = React.useState('');
|
||||||
|
|
||||||
// Track which step the user is currently editing (null = auto-advance)
|
|
||||||
const [editingStep, setEditingStep] = React.useState<number | null>(null);
|
const [editingStep, setEditingStep] = React.useState<number | null>(null);
|
||||||
|
|
||||||
// ─── Derived state ──────────────────────────────────────────────
|
|
||||||
const needsPlanningFor = intent === 'preplan';
|
const needsPlanningFor = intent === 'preplan';
|
||||||
const funeralTypeLabel = funeralTypes.find((ft) => ft.id === funeralTypeId)?.label;
|
const funeralTypeLabel = funeralTypes.find((ft) => ft.id === funeralTypeId)?.label;
|
||||||
|
|
||||||
// Determine step statuses
|
// Which step is currently active?
|
||||||
const getStepStatus = (step: number): StepStatus => {
|
const activeStep = (() => {
|
||||||
if (editingStep === step) return 'active';
|
if (editingStep) return editingStep;
|
||||||
|
if (!intent) return 1;
|
||||||
|
if (needsPlanningFor && !planningFor) return 2;
|
||||||
|
if (!funeralTypeId) return 3;
|
||||||
|
return 4;
|
||||||
|
})();
|
||||||
|
|
||||||
switch (step) {
|
|
||||||
case 1:
|
|
||||||
return intent ? 'completed' : 'active';
|
|
||||||
case 2: // planning for (conditional)
|
|
||||||
if (!needsPlanningFor) return 'locked';
|
|
||||||
if (planningFor) return 'completed';
|
|
||||||
return intent ? 'active' : 'locked';
|
|
||||||
case 3: // funeral type
|
|
||||||
if (funeralTypeId) return 'completed';
|
|
||||||
if (!intent) return 'locked';
|
|
||||||
if (needsPlanningFor && !planningFor) return 'locked';
|
|
||||||
return 'active';
|
|
||||||
case 4: // location
|
|
||||||
if (!funeralTypeId) return 'locked';
|
|
||||||
return 'active';
|
|
||||||
default:
|
|
||||||
return 'locked';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Step numbering adjusts when planning-for step is hidden
|
|
||||||
const getVisibleStepNumber = (step: number): number => {
|
|
||||||
if (!needsPlanningFor && step > 2) return step - 1;
|
|
||||||
return step;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Can submit?
|
|
||||||
const canSubmit =
|
const canSubmit =
|
||||||
intent !== null &&
|
intent !== null &&
|
||||||
(!needsPlanningFor || planningFor !== null) &&
|
(!needsPlanningFor || planningFor !== null) &&
|
||||||
funeralTypeId !== null &&
|
funeralTypeId !== null &&
|
||||||
location.trim().length > 0;
|
location.trim().length > 0;
|
||||||
|
|
||||||
// ─── Handlers ───────────────────────────────────────────────────
|
// ─── Handlers ────────────────────────────────────────────────────
|
||||||
const handleIntentSelect = (value: Intent) => {
|
const selectIntent = (value: Intent) => {
|
||||||
setIntent(value);
|
setIntent(value);
|
||||||
// If switching from preplan to arrange, clear planningFor
|
if (value === 'arrange') setPlanningFor(null);
|
||||||
if (value === 'arrange') {
|
|
||||||
setPlanningFor(null);
|
|
||||||
}
|
|
||||||
setEditingStep(null);
|
setEditingStep(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePlanningForSelect = (value: PlanningFor) => {
|
const selectPlanningFor = (value: PlanningFor) => {
|
||||||
setPlanningFor(value);
|
setPlanningFor(value);
|
||||||
setEditingStep(null);
|
setEditingStep(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFuneralTypeSelect = (id: string) => {
|
const selectFuneralType = (id: string) => {
|
||||||
setFuneralTypeId(id);
|
setFuneralTypeId(id);
|
||||||
setEditingStep(null);
|
setEditingStep(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRevert = (step: number) => {
|
const revertTo = (step: number) => {
|
||||||
setEditingStep(step);
|
setEditingStep(step);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,90 +293,7 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// ─── Render helpers ─────────────────────────────────────────────
|
// ─── Render ──────────────────────────────────────────────────────
|
||||||
const renderStep = (
|
|
||||||
stepNum: number,
|
|
||||||
label: string,
|
|
||||||
status: StepStatus,
|
|
||||||
completedValue: string | undefined,
|
|
||||||
content: React.ReactNode,
|
|
||||||
) => {
|
|
||||||
const visibleNum = getVisibleStepNumber(stepNum);
|
|
||||||
const isCompleted = status === 'completed' && editingStep !== stepNum;
|
|
||||||
const isActive = status === 'active' || editingStep === stepNum;
|
|
||||||
const isLocked = status === 'locked';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={stepNum}
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: 1.5,
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
opacity: isLocked ? 0.5 : 1,
|
|
||||||
transition: 'opacity 150ms',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ pt: 0.25 }}>
|
|
||||||
<StepBadge step={visibleNum} status={isCompleted ? 'completed' : isActive ? 'active' : 'locked'} />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
||||||
<Typography
|
|
||||||
variant="overlineSm"
|
|
||||||
sx={{
|
|
||||||
display: 'block',
|
|
||||||
color: isLocked ? 'text.disabled' : 'text.secondary',
|
|
||||||
mb: isCompleted ? 0 : 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{isCompleted && completedValue && (
|
|
||||||
<Box
|
|
||||||
component="button"
|
|
||||||
onClick={() => handleRevert(stepNum)}
|
|
||||||
sx={{
|
|
||||||
background: 'none',
|
|
||||||
border: 'none',
|
|
||||||
p: 0,
|
|
||||||
cursor: 'pointer',
|
|
||||||
fontFamily: 'inherit',
|
|
||||||
fontSize: '0.9375rem',
|
|
||||||
fontWeight: 600,
|
|
||||||
color: 'text.primary',
|
|
||||||
textAlign: 'left',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 0.75,
|
|
||||||
'&:hover': { color: 'primary.main' },
|
|
||||||
'&:focus-visible': {
|
|
||||||
outline: '2px solid var(--fa-color-brand-500)',
|
|
||||||
outlineOffset: 2,
|
|
||||||
borderRadius: '2px',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
aria-label={`${label}: ${completedValue}. Click to change.`}
|
|
||||||
>
|
|
||||||
{completedValue}
|
|
||||||
<EditOutlinedIcon sx={{ fontSize: 14, opacity: 0.5 }} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isActive && content}
|
|
||||||
|
|
||||||
{isLocked && (
|
|
||||||
<Typography variant="caption" color="text.disabled">
|
|
||||||
Complete the step above
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─── Render ─────────────────────────────────────────────────────
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -361,79 +318,107 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
|
|||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
color="text.secondary"
|
color="text.secondary"
|
||||||
sx={{ textAlign: 'center', mb: 3, maxWidth: 400, mx: 'auto' }}
|
sx={{ textAlign: 'center', mb: 3.5, maxWidth: 400, mx: 'auto' }}
|
||||||
>
|
>
|
||||||
{subheading}
|
{subheading}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Steps */}
|
{/* Completed answers */}
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2.5 }}>
|
{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)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Active question */}
|
||||||
|
<Box sx={{ mt: intent && activeStep > 1 && editingStep !== 1 ? 3 : 0 }}>
|
||||||
{/* Step 1: Intent */}
|
{/* Step 1: Intent */}
|
||||||
{renderStep(
|
{activeStep === 1 && (
|
||||||
1,
|
<Box>
|
||||||
"I'M HERE TO",
|
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}>
|
||||||
getStepStatus(1),
|
How can we help you today?
|
||||||
intent === 'arrange' ? 'Arrange a funeral now' : intent === 'preplan' ? 'Pre-plan a funeral' : undefined,
|
</Typography>
|
||||||
<Box role="radiogroup" aria-label="I'm here to" sx={{ display: 'flex', gap: 1.5, flexDirection: { xs: 'column', sm: 'row' } }}>
|
<Box role="radiogroup" aria-label="How can we help" sx={{ display: 'flex', gap: 1.5, flexDirection: { xs: 'column', sm: 'row' } }}>
|
||||||
<OptionCard
|
<ChoiceCard
|
||||||
label="Arrange a funeral now"
|
label="Arrange a funeral now"
|
||||||
|
description="Someone has passed and I need to make arrangements"
|
||||||
selected={intent === 'arrange'}
|
selected={intent === 'arrange'}
|
||||||
onClick={() => handleIntentSelect('arrange')}
|
onClick={() => selectIntent('arrange')}
|
||||||
/>
|
/>
|
||||||
<OptionCard
|
<ChoiceCard
|
||||||
label="Pre-plan a funeral"
|
label="Pre-plan a funeral"
|
||||||
|
description="I'd like to plan ahead for the future"
|
||||||
selected={intent === 'preplan'}
|
selected={intent === 'preplan'}
|
||||||
onClick={() => handleIntentSelect('preplan')}
|
onClick={() => selectIntent('preplan')}
|
||||||
/>
|
/>
|
||||||
</Box>,
|
</Box>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Step 2: Planning for (conditional) */}
|
{/* Step 2: Planning for (conditional) */}
|
||||||
{needsPlanningFor &&
|
{activeStep === 2 && needsPlanningFor && (
|
||||||
renderStep(
|
<Box>
|
||||||
2,
|
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}>
|
||||||
"I'M PLANNING FOR",
|
Who are you planning for?
|
||||||
getStepStatus(2),
|
</Typography>
|
||||||
planningFor === 'myself' ? 'Myself' : planningFor === 'someone-else' ? 'Someone else' : undefined,
|
<Box role="radiogroup" aria-label="Who are you planning for" sx={{ display: 'flex', gap: 1.5, flexDirection: { xs: 'column', sm: 'row' } }}>
|
||||||
<Box role="radiogroup" aria-label="I'm planning for" sx={{ display: 'flex', gap: 1.5, flexDirection: { xs: 'column', sm: 'row' } }}>
|
<ChoiceCard
|
||||||
<OptionCard
|
|
||||||
label="Myself"
|
label="Myself"
|
||||||
|
description="I want to plan my own funeral in advance"
|
||||||
selected={planningFor === 'myself'}
|
selected={planningFor === 'myself'}
|
||||||
onClick={() => handlePlanningForSelect('myself')}
|
onClick={() => selectPlanningFor('myself')}
|
||||||
/>
|
/>
|
||||||
<OptionCard
|
<ChoiceCard
|
||||||
label="Someone else"
|
label="Someone else"
|
||||||
|
description="I'm helping a family member or friend plan ahead"
|
||||||
selected={planningFor === 'someone-else'}
|
selected={planningFor === 'someone-else'}
|
||||||
onClick={() => handlePlanningForSelect('someone-else')}
|
onClick={() => selectPlanningFor('someone-else')}
|
||||||
/>
|
/>
|
||||||
</Box>,
|
</Box>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Step 3: Funeral type */}
|
{/* Step 3: Funeral type */}
|
||||||
{renderStep(
|
{activeStep === 3 && (
|
||||||
3,
|
<Box>
|
||||||
'TYPE OF FUNERAL',
|
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}>
|
||||||
getStepStatus(3),
|
What type of funeral are you considering?
|
||||||
funeralTypeLabel,
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
<Box role="radiogroup" aria-label="Type of funeral" sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||||
{funeralTypes.map((ft) => (
|
{funeralTypes.map((ft) => (
|
||||||
<Chip
|
<TypePill
|
||||||
key={ft.id}
|
key={ft.id}
|
||||||
label={ft.label}
|
label={ft.label}
|
||||||
variant={funeralTypeId === ft.id ? 'filled' : 'outlined'}
|
|
||||||
selected={funeralTypeId === ft.id}
|
selected={funeralTypeId === ft.id}
|
||||||
onClick={() => handleFuneralTypeSelect(ft.id)}
|
onClick={() => selectFuneralType(ft.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Box>,
|
</Box>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Step 4: Location */}
|
{/* Step 4: Location */}
|
||||||
{renderStep(
|
{activeStep === 4 && (
|
||||||
4,
|
<Box>
|
||||||
'LOCATION',
|
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}>
|
||||||
getStepStatus(4),
|
Where are you located?
|
||||||
undefined, // Location doesn't collapse — always editable when unlocked
|
</Typography>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Suburb or postcode"
|
placeholder="Suburb or postcode"
|
||||||
value={location}
|
value={location}
|
||||||
@@ -444,11 +429,13 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' && canSubmit) handleSubmit();
|
if (e.key === 'Enter' && canSubmit) handleSubmit();
|
||||||
}}
|
}}
|
||||||
/>,
|
/>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* CTA */}
|
{/* CTA — only visible once we're on the location step or beyond */}
|
||||||
|
{activeStep >= 4 && (
|
||||||
<Box sx={{ mt: 3 }}>
|
<Box sx={{ mt: 3 }}>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -459,9 +446,6 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
|
|||||||
>
|
>
|
||||||
Find funeral directors
|
Find funeral directors
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Trust signal */}
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="captionSm"
|
variant="captionSm"
|
||||||
color="text.secondary"
|
color="text.secondary"
|
||||||
@@ -470,6 +454,8 @@ export const FuneralFinder = React.forwardRef<HTMLDivElement, FuneralFinderProps
|
|||||||
Free to use · No obligation
|
Free to use · No obligation
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user