Add SummaryStep, PaymentStep, ConfirmationStep (wizard steps 13-15)
SummaryStep (step 13): - Accordion sections with edit IconButtons linking back to each step - dl/dt/dd definition list for label-value pairs - Total bar with prominent price display (aria-live) - Share plan icon button, deposit display - Pre-planning: "Save your plan" CTA; at-need: "Confirm" CTA PaymentStep (step 14): - Payment plan (full/deposit) shown before method (amount before how) - ToggleButtonGroup for plan + method selection - Card: PayWay iframe slot with placeholder; Bank: account details display - Terms checkbox with service agreement + privacy links - Security reassurance (lock icon, no-surprise copy) ConfirmationStep (step 15): - Terminal page — no back button, no progress indicator - At-need: "submitted" + callback timeframe + arranger contact - Pre-planning: "saved" + return-anytime + family-ready copy - Muted success icon (not celebratory), next-steps link buttons - VenueCard selected prop also staged (from step 7 work) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
150
src/components/pages/SummaryStep/SummaryStep.stories.tsx
Normal file
150
src/components/pages/SummaryStep/SummaryStep.stories.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { SummaryStep } from './SummaryStep';
|
||||
import type { SummarySection } from './SummaryStep';
|
||||
import { Navigation } from '../../organisms/Navigation';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const FALogo = () => (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Box
|
||||
component="img"
|
||||
src="/brandlogo/logo-full.svg"
|
||||
alt="Funeral Arranger"
|
||||
sx={{ height: 28, display: { xs: 'none', md: 'block' } }}
|
||||
/>
|
||||
<Box
|
||||
component="img"
|
||||
src="/brandlogo/logo-short.svg"
|
||||
alt="Funeral Arranger"
|
||||
sx={{ height: 28, display: { xs: 'block', md: 'none' } }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const nav = (
|
||||
<Navigation
|
||||
logo={<FALogo />}
|
||||
items={[
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const sampleSections: SummarySection[] = [
|
||||
{
|
||||
id: 'provider',
|
||||
title: 'Funeral Provider',
|
||||
editStepId: 'providers',
|
||||
items: [
|
||||
{ label: 'Provider', value: 'H. Parsons Funeral Directors' },
|
||||
{ label: 'Package', value: 'Essential Service Package', price: 4950 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'venue',
|
||||
title: 'Service Venue',
|
||||
editStepId: 'venue',
|
||||
items: [
|
||||
{ label: 'Venue', value: 'West Chapel, Strathfield', price: 900 },
|
||||
{ label: 'Photo presentation', value: 'Included', price: 150 },
|
||||
{ label: 'Livestream', value: 'Included', price: 200 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'crematorium',
|
||||
title: 'Crematorium',
|
||||
editStepId: 'crematorium',
|
||||
items: [
|
||||
{ label: 'Crematorium', value: 'Warrill Park Crematorium', price: 850 },
|
||||
{ label: 'Following hearse', value: 'Yes' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'coffin',
|
||||
title: 'Coffin',
|
||||
editStepId: 'coffins',
|
||||
items: [
|
||||
{ label: 'Coffin', value: 'Cedar Classic', price: 2800 },
|
||||
{ label: 'Handles', value: 'Brass Bar Handle', price: 0 },
|
||||
{ label: 'Lining', value: 'White Satin', price: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'services',
|
||||
title: 'Additional Services',
|
||||
editStepId: 'additional_services',
|
||||
items: [
|
||||
{ label: 'Funeral announcement', value: 'Included' },
|
||||
{ label: 'Bearing', value: 'Family and friends' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// ─── Meta ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const meta: Meta<typeof SummaryStep> = {
|
||||
title: 'Pages/SummaryStep',
|
||||
component: SummaryStep,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof SummaryStep>;
|
||||
|
||||
// ─── At-need (default) ──────────────────────────────────────────────────────
|
||||
|
||||
/** Full summary for at-need flow */
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<SummaryStep
|
||||
sections={sampleSections}
|
||||
totalPrice={9850}
|
||||
depositAmount={2000}
|
||||
onConfirm={() => alert('Confirmed — proceed to payment')}
|
||||
onBack={() => alert('Back')}
|
||||
onSaveAndExit={() => alert('Save')}
|
||||
onEdit={(stepId) => alert(`Edit: ${stepId}`)}
|
||||
onShare={() => alert('Share plan')}
|
||||
navigation={nav}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Pre-planning ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Pre-planning variant — "Save your plan" CTA, no payment */
|
||||
export const PrePlanning: Story = {
|
||||
render: () => (
|
||||
<SummaryStep
|
||||
sections={sampleSections}
|
||||
totalPrice={9850}
|
||||
onConfirm={() => alert('Plan saved')}
|
||||
onBack={() => alert('Back')}
|
||||
onEdit={(stepId) => alert(`Edit: ${stepId}`)}
|
||||
onShare={() => alert('Share plan')}
|
||||
isPrePlanning
|
||||
navigation={nav}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
// ─── Loading ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** Confirm button loading */
|
||||
export const Loading: Story = {
|
||||
render: () => (
|
||||
<SummaryStep
|
||||
sections={sampleSections}
|
||||
totalPrice={9850}
|
||||
onConfirm={() => {}}
|
||||
loading
|
||||
navigation={nav}
|
||||
/>
|
||||
),
|
||||
};
|
||||
264
src/components/pages/SummaryStep/SummaryStep.tsx
Normal file
264
src/components/pages/SummaryStep/SummaryStep.tsx
Normal file
@@ -0,0 +1,264 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Accordion from '@mui/material/Accordion';
|
||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||
import AccordionDetails from '@mui/material/AccordionDetails';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||
import ShareOutlinedIcon from '@mui/icons-material/ShareOutlined';
|
||||
import type { SxProps, Theme } from '@mui/material/styles';
|
||||
import { WizardLayout } from '../../templates/WizardLayout';
|
||||
import { Typography } from '../../atoms/Typography';
|
||||
import { Button } from '../../atoms/Button';
|
||||
import { IconButton } from '../../atoms/IconButton';
|
||||
import { Divider } from '../../atoms/Divider';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/** A single line item in the summary */
|
||||
export interface SummaryLineItem {
|
||||
label: string;
|
||||
value: string;
|
||||
price?: number;
|
||||
}
|
||||
|
||||
/** A section in the summary (e.g. Provider, Venue, Coffin) */
|
||||
export interface SummarySection {
|
||||
id: string;
|
||||
title: string;
|
||||
items: SummaryLineItem[];
|
||||
/** Step ID to navigate back to for editing */
|
||||
editStepId?: string;
|
||||
}
|
||||
|
||||
/** Props for the SummaryStep page component */
|
||||
export interface SummaryStepProps {
|
||||
/** Summary sections */
|
||||
sections: SummarySection[];
|
||||
/** Total cost */
|
||||
totalPrice: number;
|
||||
/** Deposit amount (if applicable) */
|
||||
depositAmount?: number;
|
||||
/** Callback when Confirm is clicked */
|
||||
onConfirm: () => void;
|
||||
/** Callback for back navigation */
|
||||
onBack?: () => void;
|
||||
/** Callback for save-and-exit */
|
||||
onSaveAndExit?: () => void;
|
||||
/** Callback when edit is clicked on a section */
|
||||
onEdit?: (stepId: string) => void;
|
||||
/** Callback for sharing the plan */
|
||||
onShare?: () => void;
|
||||
/** Whether the Confirm button is in a loading state */
|
||||
loading?: boolean;
|
||||
/** Whether this is a pre-planning flow */
|
||||
isPrePlanning?: boolean;
|
||||
/** Navigation bar */
|
||||
navigation?: React.ReactNode;
|
||||
/** Progress stepper */
|
||||
progressStepper?: React.ReactNode;
|
||||
/** Hide the help bar */
|
||||
hideHelpBar?: boolean;
|
||||
/** MUI sx prop */
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
// ─── Component ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Step 13 — Summary / Review for the FA arrangement wizard.
|
||||
*
|
||||
* Complete summary of the funeral plan with all selections and pricing.
|
||||
* Accordion sections with edit links back to each step. Total bar at bottom.
|
||||
*
|
||||
* For pre-planning: CTA is "Save your plan" instead of "Confirm".
|
||||
* For at-need: CTA is "Confirm and continue to payment".
|
||||
*
|
||||
* Pure presentation component — props in, callbacks out.
|
||||
*
|
||||
* Spec: documentation/steps/steps/13_summary.yaml
|
||||
*/
|
||||
export const SummaryStep: React.FC<SummaryStepProps> = ({
|
||||
sections,
|
||||
totalPrice,
|
||||
depositAmount,
|
||||
onConfirm,
|
||||
onBack,
|
||||
onSaveAndExit,
|
||||
onEdit,
|
||||
onShare,
|
||||
loading = false,
|
||||
isPrePlanning = false,
|
||||
navigation,
|
||||
progressStepper,
|
||||
hideHelpBar,
|
||||
sx,
|
||||
}) => {
|
||||
return (
|
||||
<WizardLayout
|
||||
variant="centered-form"
|
||||
navigation={navigation}
|
||||
progressStepper={progressStepper}
|
||||
showBackLink={!!onBack}
|
||||
backLabel="Back"
|
||||
onBack={onBack}
|
||||
hideHelpBar={hideHelpBar}
|
||||
sx={sx}
|
||||
>
|
||||
{/* Header with share */}
|
||||
<Box
|
||||
sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 1 }}
|
||||
>
|
||||
<Typography variant="display3" component="h1" tabIndex={-1}>
|
||||
Review your plan
|
||||
</Typography>
|
||||
{onShare && (
|
||||
<IconButton aria-label="Share this plan via email" onClick={onShare} size="medium">
|
||||
<ShareOutlinedIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
|
||||
Check everything looks right before confirming.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 4 }}>
|
||||
You can edit any section by tapping the edit icon.
|
||||
</Typography>
|
||||
|
||||
{/* ─── Summary sections ─── */}
|
||||
{sections.map((section) => (
|
||||
<Accordion
|
||||
key={section.id}
|
||||
defaultExpanded
|
||||
disableGutters
|
||||
elevation={0}
|
||||
sx={{
|
||||
border: 1,
|
||||
borderColor: 'divider',
|
||||
borderRadius: '8px !important',
|
||||
mb: 2,
|
||||
'&:before': { display: 'none' },
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={{ px: 3, py: 1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', flex: 1, gap: 1 }}>
|
||||
<Typography variant="h5" sx={{ flex: 1 }}>
|
||||
{section.title}
|
||||
</Typography>
|
||||
{section.editStepId && onEdit && (
|
||||
<IconButton
|
||||
aria-label={`Edit ${section.title}`}
|
||||
size="small"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit(section.editStepId!);
|
||||
}}
|
||||
>
|
||||
<EditOutlinedIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ px: 3, pb: 3 }}>
|
||||
<Box component="dl" sx={{ m: 0 }}>
|
||||
{section.items.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
py: 1,
|
||||
borderBottom: i < section.items.length - 1 ? 1 : 0,
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography component="dt" variant="body2" color="text.secondary">
|
||||
{item.label}
|
||||
</Typography>
|
||||
<Typography component="dd" variant="body1" sx={{ m: 0 }}>
|
||||
{item.value}
|
||||
</Typography>
|
||||
</Box>
|
||||
{item.price != null && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="primary"
|
||||
sx={{ ml: 2, whiteSpace: 'nowrap' }}
|
||||
>
|
||||
${item.price.toLocaleString('en-AU')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
|
||||
{/* ─── Total bar ─── */}
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
p: 3,
|
||||
mt: 3,
|
||||
mb: 4,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderRadius: 2,
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography variant="h5">Total cost</Typography>
|
||||
{depositAmount != null && !isPrePlanning && (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Deposit: ${depositAmount.toLocaleString('en-AU')}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Typography variant="display3" color="primary" aria-live="polite" aria-atomic="true">
|
||||
${totalPrice.toLocaleString('en-AU')}
|
||||
</Typography>
|
||||
</Paper>
|
||||
|
||||
{/* Payment reassurance */}
|
||||
{!isPrePlanning && (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2, textAlign: 'center' }}>
|
||||
You won't be charged until you complete the next step.
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Divider sx={{ my: 3 }} />
|
||||
|
||||
{/* CTAs */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexDirection: { xs: 'column-reverse', sm: 'row' },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{onSaveAndExit ? (
|
||||
<Button variant="text" color="secondary" onClick={onSaveAndExit} type="button">
|
||||
Save and continue later
|
||||
</Button>
|
||||
) : (
|
||||
<Box />
|
||||
)}
|
||||
<Button variant="contained" size="large" loading={loading} onClick={onConfirm}>
|
||||
{isPrePlanning ? 'Save your plan' : 'Confirm'}
|
||||
</Button>
|
||||
</Box>
|
||||
</WizardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
SummaryStep.displayName = 'SummaryStep';
|
||||
export default SummaryStep;
|
||||
2
src/components/pages/SummaryStep/index.ts
Normal file
2
src/components/pages/SummaryStep/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { SummaryStep, default } from './SummaryStep';
|
||||
export type { SummaryStepProps, SummarySection, SummaryLineItem } from './SummaryStep';
|
||||
Reference in New Issue
Block a user