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 ─────────────────────────────────────────────────────────────────── /** A funeral type option (dynamic, from API) */ export interface FuneralTypeOption { /** Unique identifier */ 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 */ export interface FuneralSearchParams { /** "arrange" (immediate need) or "preplan" */ intent: 'arrange' | 'preplan'; /** Only present when intent is "preplan" */ planningFor?: 'myself' | 'someone-else'; /** 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; } /** Props for the FA FuneralFinder organism */ export interface FuneralFinderProps { /** Available funeral types — dynamic list from API */ funeralTypes: FuneralTypeOption[]; /** 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; } // ─── Internal types ────────────────────────────────────────────────────────── 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 ( {children} ); } /** Large tappable option for binary choices (intent, planning-for) */ function ChoiceCard({ label, description, selected, onClick, }: { label: string; description?: string; selected: boolean; onClick: () => void; }) { return ( {selected && ( {description && ( {description} )} ); } /** Funeral type card — compact selectable card */ function TypeCard({ label, description, note, selected, onClick, }: { label: string; description?: string; note?: string; selected: boolean; onClick: () => void; }) { return ( {selected && ( {description && ( {description} )} {note && ( {note} )} ); } /** Completed answer row — question + answer + change link */ function CompletedRow({ question, answer, onChangeClick, }: { question: string; answer: string; onChangeClick: () => void; }) { return ( {question} {answer} Change ); } // ─── 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 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?" → 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( ( { 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(null); const [planningFor, setPlanningFor] = React.useState(null); const [typeSelection, setTypeSelection] = React.useState(null); const [servicePref, setServicePref] = React.useState('either'); const [serviceAnswered, setServiceAnswered] = React.useState(false); const [selectedThemes, setSelectedThemes] = React.useState([]); const [location, setLocation] = React.useState(''); const [locationError, setLocationError] = React.useState(''); const [showIntentPrompt, setShowIntentPrompt] = React.useState(false); const [editingStep, setEditingStep] = React.useState(null); const needsPlanningFor = intent === 'preplan'; 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? (0 = all complete) const activeStep = (() => { if (editingStep !== null) return editingStep; if (!intent) return 1; if (needsPlanningFor && !planningFor) return 2; if (!typeSelection) return 3; if (showServiceStep && !serviceAnswered) return 4; return 0; })(); // ─── Labels ───────────────────────────────────────────────────── const intentLabel = intent === 'arrange' ? 'Arrange a funeral now' : 'Pre-plan a funeral'; const planningForLabel = planningFor === 'myself' ? 'Myself' : 'Someone else'; 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); }; const selectPlanningFor = (value: PlanningFor) => { setPlanningFor(value); setEditingStep(null); }; 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 = () => { // 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: isExploreAll ? null : (typeSelection ?? null), servicePreference: showServiceStep && serviceAnswered ? servicePref : 'either', themes: selectedThemes, location: location.trim(), }); }; // ─── Render ───────────────────────────────────────────────────── return ( {/* Header */} {heading} {subheading} {/* ── Completed rows ─────────────────────────────────────── */} revertTo(1)} /> revertTo(2)} /> revertTo(3)} /> revertTo(4)} /> {/* ── Step 1: Intent ─────────────────────────────────────── */} {showIntentPrompt && ( Please let us know how we can help )} How can we help you today? selectIntent('arrange')} /> selectIntent('preplan')} /> {/* ── Step 2: Planning for (conditional) ─────────────────── */} Who are you planning for? selectPlanningFor('myself')} /> selectPlanningFor('someone-else')} /> {/* ── Step 3: Type + Preferences ─────────────────────────── */} What type of funeral are you considering? {funeralTypes.map((ft) => ( selectType(ft.id)} /> ))} {showExploreAll && ( selectType('all')} /> )} {/* Theme preferences — optional, inside type step */} {themeOptions.length > 0 && ( Any preferences? (optional) {themeOptions.map((theme) => { const isSelected = selectedThemes.includes(theme.id); return ( toggleTheme(theme.id)} clickable aria-pressed={isSelected} sx={{ height: 44 }} /> ); })} )} {/* ── Step 4: Service (conditional, auto-advance) ────────── */} Would you like a service? {SERVICE_OPTIONS.map((opt) => ( selectService(opt.value)} clickable aria-pressed={serviceAnswered && servicePref === opt.value} sx={{ justifyContent: 'flex-start', height: 44, borderRadius: 'var(--fa-border-radius-md)', }} /> ))} {/* ── Always visible: Location + CTA ─────────────────────── */} Where are you looking? { 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(); }} /> Free to use · No obligation ); }, ); FuneralFinder.displayName = 'FuneralFinder'; export default FuneralFinder;