Files
Parsons/src/components/pages/IntroStep/IntroStep.tsx
Richie 1faa320f4b Feedback iteration: DialogShell, page consistency, popup standardisation
- Add DialogShell atom — shared dialog container (header, scrollable body, footer)
- Refactor FilterPanel to use DialogShell (Popover → centered Dialog)
- Refactor ArrangementDialog to use DialogShell
- Remove PreviewStep + AuthGateStep pages (consolidated into ArrangementDialog, D-E)
- IntroStep: static subheading, top-left aligned toggle button content
- ProvidersStep: h4 heading "Find a funeral director", location search with pin icon,
  filter moved below search right-aligned, map fill fix, hover scrollbar
- VenueStep: same consistency fixes (h4 heading, filter layout, location icon, map fix)
- PackagesStep: grouped packages ("Matching your preferences" / "Other packages from
  [Provider]"), removed budget filter + Most Popular badge, clickable provider card,
  onArrange replaces onContinue, h4 heading
- WizardLayout: list-map left panel gets thin scrollbar visible on hover

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 12:20:26 +11:00

186 lines
6.3 KiB
TypeScript

import React from 'react';
import Box from '@mui/material/Box';
import type { SxProps, Theme } from '@mui/material/styles';
import { WizardLayout } from '../../templates/WizardLayout';
import { ToggleButtonGroup } from '../../atoms/ToggleButtonGroup';
import { Collapse } from '../../atoms/Collapse';
import { Typography } from '../../atoms/Typography';
import { Button } from '../../atoms/Button';
import { Divider } from '../../atoms/Divider';
// ─── Types ───────────────────────────────────────────────────────────────────
/** Form values for the intro step */
export interface IntroStepValues {
/** Who the funeral is being arranged for */
forWhom: 'myself' | 'someone' | null;
/** Whether the person has passed away (only relevant when forWhom="someone") */
hasPassedAway: 'yes' | 'no' | null;
}
/** Field-level error messages */
export interface IntroStepErrors {
/** Error for the forWhom field */
forWhom?: string;
/** Error for the hasPassedAway field */
hasPassedAway?: string;
}
/** Props for the IntroStep page component */
export interface IntroStepProps {
/** Current form values */
values: IntroStepValues;
/** Callback when any field value changes */
onChange: (values: IntroStepValues) => void;
/** Callback when the Continue button is clicked */
onContinue: () => void;
/** Field-level validation errors */
errors?: IntroStepErrors;
/** Whether the Continue button is in a loading state */
loading?: boolean;
/** Navigation bar — passed through to WizardLayout */
navigation?: React.ReactNode;
/** Hide the help bar */
hideHelpBar?: boolean;
/** MUI sx prop for the root */
sx?: SxProps<Theme>;
}
// ─── Constants ──────────────────────────────────────────────────────────────
const SUBHEADING =
"We'll guide you through arranging a funeral, step by step. You can save your progress and come back anytime.";
// ─── Component ───────────────────────────────────────────────────────────────
/**
* Step 1 — Intro page for the FA arrangement wizard.
*
* Entry point with urgency-sensitive segmentation. User selects who
* the funeral is for, and (if arranging for someone else) whether
* that person has passed away.
*
* Uses the Centered Form layout variant. Progressive disclosure:
* selecting "Someone else" reveals the hasPassedAway question.
* Selecting "Myself" auto-sets hasPassedAway to "no" (pre-planning).
*
* Pure presentation component — props in, callbacks out.
* No routing, state management, or API calls.
*
* Spec: documentation/steps/steps/01_intro.yaml
*/
export const IntroStep: React.FC<IntroStepProps> = ({
values,
onChange,
onContinue,
errors,
loading = false,
navigation,
hideHelpBar,
sx,
}) => {
const handleForWhomChange = (newValue: string | null) => {
const forWhom = newValue as IntroStepValues['forWhom'];
if (forWhom === 'myself') {
// Auto-set hasPassedAway to 'no' — user is alive, pre-planning
onChange({ forWhom, hasPassedAway: 'no' });
} else {
// Reset hasPassedAway when switching to "someone"
onChange({ forWhom, hasPassedAway: null });
}
};
const handleHasPassedAwayChange = (newValue: string | null) => {
onChange({ ...values, hasPassedAway: newValue as IntroStepValues['hasPassedAway'] });
};
const showHasPassedAway = values.forWhom === 'someone';
return (
<WizardLayout variant="centered-form" navigation={navigation} hideHelpBar={hideHelpBar} sx={sx}>
{/* Page heading — receives focus on entry for screen readers */}
<Typography variant="display3" component="h1" sx={{ mb: 1 }} tabIndex={-1}>
Let&apos;s get started
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 5 }}>
{SUBHEADING}
</Typography>
<Box
component="form"
noValidate
aria-busy={loading}
onSubmit={(e: React.FormEvent) => {
e.preventDefault();
if (!loading) onContinue();
}}
>
{/* forWhom field */}
<Box sx={{ mb: 3 }}>
<ToggleButtonGroup
label="Who is this funeral being arranged for?"
options={[
{
value: 'myself',
label: 'Myself',
description: 'I want to plan my own funeral',
},
{
value: 'someone',
label: 'Someone else',
description: 'I am arranging for a family member or friend',
},
]}
value={values.forWhom}
onChange={handleForWhomChange}
error={!!errors?.forWhom}
helperText={errors?.forWhom}
required
fullWidth
/>
</Box>
{/* hasPassedAway field — revealed when forWhom="someone" */}
<Collapse in={showHasPassedAway}>
<Box sx={{ mb: 3 }}>
<ToggleButtonGroup
label="Has this person passed away?"
options={[
{
value: 'yes',
label: 'Yes',
description: 'I need to arrange a funeral now',
},
{
value: 'no',
label: 'No',
description: 'I am planning ahead',
},
]}
value={values.hasPassedAway}
onChange={handleHasPassedAwayChange}
error={!!errors?.hasPassedAway}
helperText={errors?.hasPassedAway}
required
fullWidth
/>
</Box>
</Collapse>
<Divider sx={{ my: 4 }} />
{/* CTA */}
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button type="submit" variant="contained" size="large" loading={loading}>
Continue
</Button>
</Box>
</Box>
</WizardLayout>
);
};
IntroStep.displayName = 'IntroStep';
export default IntroStep;