Add FuneralFinder flow logic reference doc
- Step flow, state management, conditional logic map - Smart defaults and CTA submit behaviour - Props reference and sub-component index - Guide for adding new steps Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
203
docs/reference/funeral-finder-logic.md
Normal file
203
docs/reference/funeral-finder-logic.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# FuneralFinder — Flow Logic Reference
|
||||
|
||||
Technical reference for the FuneralFinder stepped search widget.
|
||||
Use this when modifying the flow, adding steps, or integrating with a backend.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The widget is a **single React component** with internal state. No external state
|
||||
management required. The parent only needs to provide `funeralTypes`, optional
|
||||
`themeOptions`, and an `onSearch` callback.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Header (h2 display + subheading) │
|
||||
│ ───────────────────────────────── │
|
||||
│ │
|
||||
│ CompletedRows (stack of answered steps)│
|
||||
│ │
|
||||
│ Active Step (one at a time, Collapse) │
|
||||
│ Step 1 │ Step 2 │ Step 3 │ Step 4 │
|
||||
│ │
|
||||
│ ─── always visible ─────────────────── │
|
||||
│ Location input │
|
||||
│ [Find funeral providers] CTA │
|
||||
│ Free to use · No obligation │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## State
|
||||
|
||||
| State variable | Type | Default | Purpose |
|
||||
|---|---|---|---|
|
||||
| `intent` | `'arrange' \| 'preplan' \| null` | `null` | Step 1 answer |
|
||||
| `planningFor` | `'myself' \| 'someone-else' \| null` | `null` | Step 2 answer (preplan only) |
|
||||
| `typeSelection` | `string \| null` | `null` | Step 3 answer — funeral type ID or `'all'` |
|
||||
| `servicePref` | `'with-service' \| 'without-service' \| 'either'` | `'either'` | Step 4 answer |
|
||||
| `serviceAnswered` | `boolean` | `false` | Whether step 4 was explicitly answered |
|
||||
| `selectedThemes` | `string[]` | `[]` | Optional theme filter IDs (multi-select) |
|
||||
| `location` | `string` | `''` | Location input value |
|
||||
| `locationError` | `string` | `''` | Validation error for location |
|
||||
| `showIntentPrompt` | `boolean` | `false` | Show nudge when CTA clicked without intent |
|
||||
| `editingStep` | `number \| null` | `null` | Which step is being re-edited (via "Change") |
|
||||
|
||||
## Step Flow
|
||||
|
||||
### Active Step Calculation
|
||||
|
||||
```typescript
|
||||
const activeStep = (() => {
|
||||
if (editingStep !== null) return editingStep; // User clicked "Change"
|
||||
if (!intent) return 1; // Need intent
|
||||
if (needsPlanningFor && !planningFor) return 2; // Need planning-for (preplan only)
|
||||
if (!typeSelection) return 3; // Need funeral type
|
||||
if (showServiceStep && !serviceAnswered) return 4; // Need service pref
|
||||
return 0; // All complete
|
||||
})();
|
||||
```
|
||||
|
||||
`activeStep === 0` means all optional steps are answered. Only CompletedRows +
|
||||
location + CTA are visible.
|
||||
|
||||
### Step Details
|
||||
|
||||
| Step | Question | Options | Auto-advances? | Conditional? |
|
||||
|---|---|---|---|---|
|
||||
| 1 | How can we help you today? | Arrange now / Pre-plan | Yes, on click | Always shown |
|
||||
| 2 | Who are you planning for? | Myself / Someone else | Yes, on click | Only when `intent === 'preplan'` |
|
||||
| 3 | What type of funeral? | TypeCards + Explore All + theme chips | Yes, on type card click | Always shown |
|
||||
| 4 | Would you like a service? | With / No / Flexible (chips) | Yes, on chip click | Only when selected type has `hasServiceOption: true` |
|
||||
|
||||
### Auto-advance Mechanic
|
||||
|
||||
Steps 1, 2, and 4 auto-advance because selecting an option sets the state and
|
||||
clears `editingStep`. The `activeStep` recalculation on the next render
|
||||
determines the new step.
|
||||
|
||||
Step 3 also auto-advances when a type card is clicked. Theme preferences within
|
||||
step 3 are optional — they're captured at whatever state they're in when the
|
||||
type card click triggers collapse.
|
||||
|
||||
### Editing (reverting to a previous step)
|
||||
|
||||
Clicking "Change" on a CompletedRow calls `revertTo(stepNumber)`, which sets
|
||||
`editingStep`. This overrides the `activeStep` calculation, reopening that step.
|
||||
When the user makes a new selection, the handler clears `editingStep` and the
|
||||
flow recalculates.
|
||||
|
||||
**Key behaviour:** Editing a step does NOT reset downstream answers. If you
|
||||
change from Cremation to Burial (both have `hasServiceOption`), the service
|
||||
preference carries forward. If you change to a type without `hasServiceOption`
|
||||
(or to "Explore all"), `servicePref` resets to `'either'` and `serviceAnswered`
|
||||
resets to `false`.
|
||||
|
||||
## CTA and Search Logic
|
||||
|
||||
### Minimum Requirements
|
||||
|
||||
The CTA button is **always visible and always enabled** (except during loading).
|
||||
Minimum search requirements: **intent + location (3+ chars)**.
|
||||
|
||||
### Submit Behaviour
|
||||
|
||||
```
|
||||
User clicks "Find funeral providers"
|
||||
│
|
||||
├─ intent is null?
|
||||
│ → Show intent prompt (role="alert"), keep step 1 visible
|
||||
│ → Return (don't search)
|
||||
│
|
||||
├─ location < 3 chars?
|
||||
│ → Show error on location input
|
||||
│ → Return (don't search)
|
||||
│
|
||||
└─ Both present?
|
||||
→ Call onSearch() with smart defaults for missing optional fields
|
||||
```
|
||||
|
||||
### Smart Defaults
|
||||
|
||||
| Field | If not explicitly answered | Default value |
|
||||
|---|---|---|
|
||||
| `funeralTypeId` | User didn't select a type | `null` (= show all types) |
|
||||
| `servicePreference` | User didn't answer service step | `'either'` (= show all) |
|
||||
| `themes` | User didn't select any themes | `[]` (= no filter) |
|
||||
| `planningFor` | User on preplan path but didn't answer step 2 | `undefined` |
|
||||
|
||||
This means a user can: select intent → type location → click CTA. Everything
|
||||
else defaults to "show all."
|
||||
|
||||
### Search Params Shape
|
||||
|
||||
```typescript
|
||||
interface FuneralSearchParams {
|
||||
intent: 'arrange' | 'preplan';
|
||||
planningFor?: 'myself' | 'someone-else'; // Only on preplan path
|
||||
funeralTypeId: string | null; // null = all types
|
||||
servicePreference: 'with-service' | 'without-service' | 'either';
|
||||
themes: string[]; // May be empty
|
||||
location: string; // Trimmed, 3+ chars
|
||||
}
|
||||
```
|
||||
|
||||
## Conditional Logic Map
|
||||
|
||||
```
|
||||
intent === 'preplan'
|
||||
└─ Shows step 2 (planning-for)
|
||||
|
||||
typeSelection !== 'all' && selectedType.hasServiceOption === true
|
||||
└─ Shows step 4 (service preference)
|
||||
|
||||
typeSelection !== null
|
||||
└─ CompletedRow for type shows (with theme summary if any selected)
|
||||
|
||||
serviceAnswered && showServiceStep
|
||||
└─ CompletedRow for service shows
|
||||
|
||||
themeOptions.length > 0
|
||||
└─ Theme chips appear within step 3 (always, not gated by type selection)
|
||||
|
||||
loading === true
|
||||
└─ CTA button shows spinner, button disabled
|
||||
```
|
||||
|
||||
## Props Reference
|
||||
|
||||
| Prop | Type | Default | Notes |
|
||||
|---|---|---|---|
|
||||
| `funeralTypes` | `FuneralTypeOption[]` | required | Each has `id`, `label`, optional `description`, `note`, `hasServiceOption` |
|
||||
| `themeOptions` | `ThemeOption[]` | `[]` | Each has `id`, `label`. Shown as optional chips in step 3 |
|
||||
| `onSearch` | `(params: FuneralSearchParams) => void` | — | Called on valid submit |
|
||||
| `loading` | `boolean` | `false` | Shows spinner on CTA, disables button |
|
||||
| `heading` | `string` | `'Find funeral directors near you'` | Main h2 heading |
|
||||
| `subheading` | `string` | `'Tell us a little about...'` | Below heading |
|
||||
| `showExploreAll` | `boolean` | `true` | Show "Explore all options" TypeCard |
|
||||
| `sx` | `SxProps<Theme>` | — | MUI sx override on root card |
|
||||
|
||||
## Sub-components (internal)
|
||||
|
||||
| Component | Purpose | Used in |
|
||||
|---|---|---|
|
||||
| `StepHeading` | Centered bodyLg heading with bottom margin | Steps 1-4 |
|
||||
| `ChoiceCard` | Full-width radio card with label + description | Steps 1, 2 |
|
||||
| `TypeCard` | Compact radio card with label + optional description/note | Step 3 |
|
||||
| `CompletedRow` | Summary row: question + bold answer + "Change" link | All completed steps |
|
||||
|
||||
## Adding a New Step
|
||||
|
||||
1. Add state variable(s) for the new step's answer
|
||||
2. Add a condition in `activeStep` calculation (between existing steps)
|
||||
3. Add a `<Collapse in={activeStep === N}>` block in the render
|
||||
4. Add a `<Collapse>` for the CompletedRow (with appropriate visibility condition)
|
||||
5. Include the new data in `handleSubmit` → `onSearch()` params
|
||||
6. Update `FuneralSearchParams` type
|
||||
|
||||
## Known Limitations (deferred)
|
||||
|
||||
- **No progress indicator** — users can't see how many steps remain
|
||||
- **No roving tabindex** — radiogroups use button elements with `role="radio"` but
|
||||
arrow-key navigation between options is not implemented
|
||||
- **No location autocomplete** — free text input only, validated on length
|
||||
- **CSS vars used directly** — some styling uses `var(--fa-*)` tokens instead of
|
||||
MUI theme paths; works but doesn't support dynamic theme switching
|
||||
Reference in New Issue
Block a user