SummaryStep: visual cart layout with arrangement details and share dialog
- Visual cart-style cards with image thumbnails for provider, venue, crematorium, coffin (replaces accordion text lists) - Arrangement details section at top: arranger name, deceased name, service tradition, preferred dates/times - Location pin icons on all location-based cards - Allowance display: fully covered shows "Included in your package", partially covered shows price/allowance/remaining breakdown - Share dialog: "Share this plan" button opens DialogShell with multi-email input, add/remove recipients, send confirmation state - Included services as checkmark list, extras as priced list (consistent tick logic — only in sections with no priced items) - Full-width CTA, deposit deferred to payment step - Edit buttons with pencil icon in secondary colour Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { SummaryStep } from './SummaryStep';
|
import { SummaryStep } from './SummaryStep';
|
||||||
import type { SummarySection } from './SummaryStep';
|
import type { SummarySection, ArrangementDetails } from './SummaryStep';
|
||||||
import { Navigation } from '../../organisms/Navigation';
|
import { Navigation } from '../../organisms/Navigation';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
@@ -33,52 +33,78 @@ const nav = (
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sampleDetails: ArrangementDetails = {
|
||||||
|
arrangerName: 'Sarah Mitchell',
|
||||||
|
deceasedName: 'Robert Mitchell',
|
||||||
|
serviceTradition: 'Non-denominational',
|
||||||
|
preferredDates: ['Tuesday 15 April', 'Wednesday 16 April'],
|
||||||
|
preferredTime: 'Morning',
|
||||||
|
editStepId: 'date_time',
|
||||||
|
};
|
||||||
|
|
||||||
const sampleSections: SummarySection[] = [
|
const sampleSections: SummarySection[] = [
|
||||||
{
|
{
|
||||||
id: 'provider',
|
id: 'provider',
|
||||||
title: 'Funeral Provider',
|
title: 'Funeral Provider',
|
||||||
editStepId: 'providers',
|
editStepId: 'providers',
|
||||||
items: [
|
imageUrl: 'https://placehold.co/320x200/F5F5F0/8B8B7E?text=H.+Parsons',
|
||||||
{ label: 'Provider', value: 'H. Parsons Funeral Directors' },
|
name: 'H. Parsons Funeral Directors',
|
||||||
{ label: 'Package', value: 'Essential Service Package', price: 4950 },
|
subtitle: 'Essential Service Package',
|
||||||
],
|
location: 'Mackay, QLD',
|
||||||
|
price: 4950,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'venue',
|
id: 'venue',
|
||||||
title: 'Service Venue',
|
title: 'Service Venue',
|
||||||
editStepId: 'venue',
|
editStepId: 'venue',
|
||||||
|
imageUrl: 'https://placehold.co/320x200/F5F5F0/8B8B7E?text=West+Chapel',
|
||||||
|
name: 'West Chapel',
|
||||||
|
location: 'Strathfield, NSW',
|
||||||
|
price: 900,
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Venue', value: 'West Chapel, Strathfield', price: 900 },
|
{ label: 'Photo presentation', price: 150 },
|
||||||
{ label: 'Photo presentation', value: 'Included', price: 150 },
|
{ label: 'Livestream', price: 200 },
|
||||||
{ label: 'Livestream', value: 'Included', price: 200 },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'crematorium',
|
id: 'crematorium',
|
||||||
title: 'Crematorium',
|
title: 'Crematorium',
|
||||||
editStepId: 'crematorium',
|
editStepId: 'crematorium',
|
||||||
items: [
|
imageUrl: 'https://placehold.co/320x200/F5F5F0/8B8B7E?text=Warrill+Park',
|
||||||
{ label: 'Crematorium', value: 'Warrill Park Crematorium', price: 850 },
|
name: 'Warrill Park Crematorium',
|
||||||
{ label: 'Following hearse', value: 'Yes' },
|
location: 'Ipswich, QLD',
|
||||||
],
|
price: 850,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'coffin',
|
id: 'coffin',
|
||||||
title: 'Coffin',
|
title: 'Coffin',
|
||||||
editStepId: 'coffins',
|
editStepId: 'coffins',
|
||||||
|
imageUrl: 'https://images.unsplash.com/photo-1618220179428-22790b461013?w=320&h=200&fit=crop',
|
||||||
|
name: 'Richmond Rosewood Coffin',
|
||||||
|
colourName: 'Natural Oak',
|
||||||
|
colourHex: '#D4A76A',
|
||||||
|
price: 1750,
|
||||||
|
allowanceAmount: 500,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'included',
|
||||||
|
title: 'Included Services',
|
||||||
|
editStepId: 'included_services',
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Coffin', value: 'Cedar Classic', price: 2800 },
|
{ label: 'Dressing and preparation' },
|
||||||
{ label: 'Handles', value: 'Brass Bar Handle', price: 0 },
|
{ label: 'Viewing', value: 'Same venue' },
|
||||||
{ label: 'Lining', value: 'White Satin', price: 0 },
|
{ label: 'Funeral announcement' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'services',
|
id: 'extras',
|
||||||
title: 'Additional Services',
|
title: 'Optional Extras',
|
||||||
editStepId: 'additional_services',
|
editStepId: 'extras',
|
||||||
items: [
|
items: [
|
||||||
{ label: 'Funeral announcement', value: 'Included' },
|
{ label: 'Catering', priceLabel: 'Price on application' },
|
||||||
{ label: 'Bearing', value: 'Family and friends' },
|
{ label: 'Live musician', value: 'Vocalist', price: 450 },
|
||||||
|
{ label: 'Coffin bearing', value: 'Family and friends' },
|
||||||
|
{ label: 'Newspaper notice', price: 250 },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -99,35 +125,61 @@ type Story = StoryObj<typeof SummaryStep>;
|
|||||||
|
|
||||||
// ─── At-need (default) ──────────────────────────────────────────────────────
|
// ─── At-need (default) ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Full summary for at-need flow */
|
/** Full summary for at-need flow with allowances */
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
<SummaryStep
|
<SummaryStep
|
||||||
|
arrangementDetails={sampleDetails}
|
||||||
sections={sampleSections}
|
sections={sampleSections}
|
||||||
totalPrice={9850}
|
totalPrice={9050}
|
||||||
depositAmount={2000}
|
totalAllowances={500}
|
||||||
onConfirm={() => alert('Confirmed — proceed to payment')}
|
onConfirm={() => alert('Confirmed — proceed to payment')}
|
||||||
onBack={() => alert('Back')}
|
onBack={() => alert('Back')}
|
||||||
onSaveAndExit={() => alert('Save')}
|
onSaveAndExit={() => alert('Save')}
|
||||||
onEdit={(stepId) => alert(`Edit: ${stepId}`)}
|
onEdit={(stepId) => alert(`Edit: ${stepId}`)}
|
||||||
onShare={() => alert('Share plan')}
|
onShareByEmail={(emails) => alert(`Share to: ${emails.join(', ')}`)}
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ─── Coffin fully covered ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Coffin fully covered by allowance */
|
||||||
|
export const FullyCovered: Story = {
|
||||||
|
render: () => {
|
||||||
|
const sections = sampleSections.map((s) =>
|
||||||
|
s.id === 'coffin' ? { ...s, price: 900, allowanceAmount: 1000 } : s,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SummaryStep
|
||||||
|
arrangementDetails={sampleDetails}
|
||||||
|
sections={sections}
|
||||||
|
totalPrice={7500}
|
||||||
|
onConfirm={() => alert('Confirmed')}
|
||||||
|
onBack={() => alert('Back')}
|
||||||
|
onEdit={(stepId) => alert(`Edit: ${stepId}`)}
|
||||||
|
onShareByEmail={(emails) => alert(`Share to: ${emails.join(', ')}`)}
|
||||||
|
navigation={nav}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// ─── Pre-planning ───────────────────────────────────────────────────────────
|
// ─── Pre-planning ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** Pre-planning variant — "Save your plan" CTA, no payment */
|
/** Pre-planning variant — "Save your plan" CTA */
|
||||||
export const PrePlanning: Story = {
|
export const PrePlanning: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
<SummaryStep
|
<SummaryStep
|
||||||
|
arrangementDetails={sampleDetails}
|
||||||
sections={sampleSections}
|
sections={sampleSections}
|
||||||
totalPrice={9850}
|
totalPrice={9050}
|
||||||
|
totalAllowances={500}
|
||||||
onConfirm={() => alert('Plan saved')}
|
onConfirm={() => alert('Plan saved')}
|
||||||
onBack={() => alert('Back')}
|
onBack={() => alert('Back')}
|
||||||
onEdit={(stepId) => alert(`Edit: ${stepId}`)}
|
onEdit={(stepId) => alert(`Edit: ${stepId}`)}
|
||||||
onShare={() => alert('Share plan')}
|
onShareByEmail={(emails) => alert(`Share to: ${emails.join(', ')}`)}
|
||||||
isPrePlanning
|
isPrePlanning
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
/>
|
/>
|
||||||
@@ -140,8 +192,9 @@ export const PrePlanning: Story = {
|
|||||||
export const Loading: Story = {
|
export const Loading: Story = {
|
||||||
render: () => (
|
render: () => (
|
||||||
<SummaryStep
|
<SummaryStep
|
||||||
|
arrangementDetails={sampleDetails}
|
||||||
sections={sampleSections}
|
sections={sampleSections}
|
||||||
totalPrice={9850}
|
totalPrice={9050}
|
||||||
onConfirm={() => {}}
|
onConfirm={() => {}}
|
||||||
loading
|
loading
|
||||||
navigation={nav}
|
navigation={nav}
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from '@mui/material/Paper';
|
||||||
import Accordion from '@mui/material/Accordion';
|
import TextField from '@mui/material/TextField';
|
||||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
import ForwardToInboxOutlinedIcon from '@mui/icons-material/ForwardToInboxOutlined';
|
||||||
import AccordionDetails from '@mui/material/AccordionDetails';
|
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
||||||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||||||
import ShareOutlinedIcon from '@mui/icons-material/ShareOutlined';
|
import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined';
|
||||||
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
|
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||||
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
import { WizardLayout } from '../../templates/WizardLayout';
|
import { WizardLayout } from '../../templates/WizardLayout';
|
||||||
|
import { DialogShell } from '../../atoms/DialogShell';
|
||||||
|
import { Card } from '../../atoms/Card';
|
||||||
import { Typography } from '../../atoms/Typography';
|
import { Typography } from '../../atoms/Typography';
|
||||||
import { Button } from '../../atoms/Button';
|
import { Button } from '../../atoms/Button';
|
||||||
import { IconButton } from '../../atoms/IconButton';
|
import { IconButton } from '../../atoms/IconButton';
|
||||||
@@ -16,30 +20,67 @@ import { Divider } from '../../atoms/Divider';
|
|||||||
|
|
||||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/** A single line item in the summary */
|
/** A line item within a summary section */
|
||||||
export interface SummaryLineItem {
|
export interface SummaryLineItem {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value?: string;
|
||||||
price?: number;
|
price?: number;
|
||||||
|
/** Custom price label like "POA" */
|
||||||
|
priceLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A section in the summary (e.g. Provider, Venue, Coffin) */
|
/** A section of the plan summary */
|
||||||
export interface SummarySection {
|
export interface SummarySection {
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Section heading (e.g. "Coffin", "Service Venue") */
|
||||||
title: string;
|
title: string;
|
||||||
items: SummaryLineItem[];
|
/** Step ID for edit navigation */
|
||||||
/** Step ID to navigate back to for editing */
|
editStepId?: string;
|
||||||
|
/** Image URL for the visual card thumbnail */
|
||||||
|
imageUrl?: string;
|
||||||
|
/** Primary selection name (e.g. provider name, coffin name) */
|
||||||
|
name?: string;
|
||||||
|
/** Supporting text (e.g. package name) */
|
||||||
|
subtitle?: string;
|
||||||
|
/** Location with pin icon (e.g. "Strathfield, NSW") */
|
||||||
|
location?: string;
|
||||||
|
/** Colour swatch for coffin */
|
||||||
|
colourName?: string;
|
||||||
|
colourHex?: string;
|
||||||
|
/** Price of the primary selection */
|
||||||
|
price?: number;
|
||||||
|
/** Package allowance for this section's primary price */
|
||||||
|
allowanceAmount?: number;
|
||||||
|
/** Additional line items (services, extras, details) */
|
||||||
|
items?: SummaryLineItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Arrangement details shown at the top of the summary */
|
||||||
|
export interface ArrangementDetails {
|
||||||
|
/** Name of the person arranging */
|
||||||
|
arrangerName?: string;
|
||||||
|
/** Name of the deceased or person the arrangement is for */
|
||||||
|
deceasedName?: string;
|
||||||
|
/** Service tradition / religious considerations */
|
||||||
|
serviceTradition?: string;
|
||||||
|
/** Preferred date(s) */
|
||||||
|
preferredDates?: string[];
|
||||||
|
/** Preferred time of day */
|
||||||
|
preferredTime?: string;
|
||||||
|
/** Step ID for editing these details */
|
||||||
editStepId?: string;
|
editStepId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Props for the SummaryStep page component */
|
/** Props for the SummaryStep page component */
|
||||||
export interface SummaryStepProps {
|
export interface SummaryStepProps {
|
||||||
|
/** Arrangement details shown at the top */
|
||||||
|
arrangementDetails?: ArrangementDetails;
|
||||||
/** Summary sections */
|
/** Summary sections */
|
||||||
sections: SummarySection[];
|
sections: SummarySection[];
|
||||||
/** Total cost */
|
/** Total cost */
|
||||||
totalPrice: number;
|
totalPrice: number;
|
||||||
/** Deposit amount (if applicable) */
|
/** Total allowances applied */
|
||||||
depositAmount?: number;
|
totalAllowances?: number;
|
||||||
/** Callback when Confirm is clicked */
|
/** Callback when Confirm is clicked */
|
||||||
onConfirm: () => void;
|
onConfirm: () => void;
|
||||||
/** Callback for back navigation */
|
/** Callback for back navigation */
|
||||||
@@ -48,8 +89,10 @@ export interface SummaryStepProps {
|
|||||||
onSaveAndExit?: () => void;
|
onSaveAndExit?: () => void;
|
||||||
/** Callback when edit is clicked on a section */
|
/** Callback when edit is clicked on a section */
|
||||||
onEdit?: (stepId: string) => void;
|
onEdit?: (stepId: string) => void;
|
||||||
/** Callback for sharing the plan */
|
/** Callback when plan is shared via email */
|
||||||
onShare?: () => void;
|
onShareByEmail?: (emails: string[]) => void;
|
||||||
|
/** Whether the share is in progress */
|
||||||
|
shareLoading?: boolean;
|
||||||
/** Whether the Confirm button is in a loading state */
|
/** Whether the Confirm button is in a loading state */
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
/** Whether this is a pre-planning flow */
|
/** Whether this is a pre-planning flow */
|
||||||
@@ -64,30 +107,182 @@ export interface SummaryStepProps {
|
|||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Internal: Share Dialog ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const ShareDialog: React.FC<{
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSend: (emails: string[]) => void;
|
||||||
|
loading?: boolean;
|
||||||
|
}> = ({ open, onClose, onSend, loading }) => {
|
||||||
|
const [emails, setEmails] = React.useState(['']);
|
||||||
|
const [sent, setSent] = React.useState(false);
|
||||||
|
|
||||||
|
const handleAdd = () => setEmails((prev) => [...prev, '']);
|
||||||
|
const handleRemove = (index: number) => setEmails((prev) => prev.filter((_, i) => i !== index));
|
||||||
|
const handleChange = (index: number, value: string) =>
|
||||||
|
setEmails((prev) => prev.map((e, i) => (i === index ? value : e)));
|
||||||
|
|
||||||
|
const validEmails = emails.filter((e) => e.includes('@') && e.includes('.'));
|
||||||
|
|
||||||
|
const handleSend = () => {
|
||||||
|
if (validEmails.length > 0) {
|
||||||
|
onSend(validEmails);
|
||||||
|
setSent(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
onClose();
|
||||||
|
setTimeout(() => {
|
||||||
|
setEmails(['']);
|
||||||
|
setSent(false);
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogShell
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
title="Send your plan"
|
||||||
|
maxWidth="sm"
|
||||||
|
fullWidth
|
||||||
|
footer={
|
||||||
|
!sent ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button variant="text" color="secondary" onClick={handleClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleSend}
|
||||||
|
loading={loading}
|
||||||
|
disabled={validEmails.length === 0}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<Button variant="contained" onClick={handleClose}>
|
||||||
|
Done
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!sent ? (
|
||||||
|
<>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
|
Enter the email address of anyone you'd like to share this plan with.
|
||||||
|
</Typography>
|
||||||
|
{emails.map((email, index) => (
|
||||||
|
<Box key={index} sx={{ display: 'flex', gap: 1, mb: 1.5, alignItems: 'center' }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
size="small"
|
||||||
|
type="email"
|
||||||
|
placeholder="Email address"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => handleChange(index, e.target.value)}
|
||||||
|
inputRef={(el) => {
|
||||||
|
if (el && index === emails.length - 1 && emails.length > 1) el.focus();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{emails.length > 1 && (
|
||||||
|
<IconButton
|
||||||
|
aria-label="Remove email"
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleRemove(index)}
|
||||||
|
>
|
||||||
|
<DeleteOutlineIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
startIcon={<AddIcon />}
|
||||||
|
onClick={handleAdd}
|
||||||
|
sx={{ mt: 0.5 }}
|
||||||
|
>
|
||||||
|
Add another recipient
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ textAlign: 'center', py: 3 }}>
|
||||||
|
<CheckCircleOutlineIcon sx={{ fontSize: 48, color: 'success.main', mb: 2 }} />
|
||||||
|
<Typography variant="h5" sx={{ mb: 1 }}>
|
||||||
|
Plan sent
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Your plan has been sent to{' '}
|
||||||
|
{validEmails.length === 1 ? validEmails[0] : `${validEmails.length} recipients`}.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</DialogShell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── Internal: Section Header ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
const SectionHeader: React.FC<{
|
||||||
|
title: string;
|
||||||
|
editStepId?: string;
|
||||||
|
onEdit?: (stepId: string) => void;
|
||||||
|
}> = ({ title, editStepId, onEdit }) => (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 1.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" component="h2">
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
{editStepId && onEdit && (
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
startIcon={<EditOutlinedIcon sx={{ fontSize: '16px !important' }} />}
|
||||||
|
onClick={() => onEdit(editStepId)}
|
||||||
|
sx={{ fontWeight: 500 }}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
// ─── Component ───────────────────────────────────────────────────────────────
|
// ─── Component ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Step 13 — Summary / Review for the FA arrangement wizard.
|
* Step 13 — Summary / Review for the FA arrangement wizard.
|
||||||
*
|
*
|
||||||
* Complete summary of the funeral plan with all selections and pricing.
|
* Visual cart-style summary of the funeral plan. Each selection is shown
|
||||||
* Accordion sections with edit links back to each step. Total bar at bottom.
|
* as a compact card with image, details, and pricing. Allowances are
|
||||||
*
|
* displayed inline per section. Email sharing via dialog.
|
||||||
* 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.
|
* Pure presentation component — props in, callbacks out.
|
||||||
*
|
*
|
||||||
* Spec: documentation/steps/steps/13_summary.yaml
|
* Spec: documentation/steps/steps/13_summary.yaml
|
||||||
*/
|
*/
|
||||||
export const SummaryStep: React.FC<SummaryStepProps> = ({
|
export const SummaryStep: React.FC<SummaryStepProps> = ({
|
||||||
|
arrangementDetails,
|
||||||
sections,
|
sections,
|
||||||
totalPrice,
|
totalPrice,
|
||||||
depositAmount,
|
totalAllowances,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
onBack,
|
onBack,
|
||||||
onSaveAndExit,
|
onSaveAndExit,
|
||||||
onEdit,
|
onEdit,
|
||||||
onShare,
|
onShareByEmail,
|
||||||
|
shareLoading = false,
|
||||||
loading = false,
|
loading = false,
|
||||||
isPrePlanning = false,
|
isPrePlanning = false,
|
||||||
navigation,
|
navigation,
|
||||||
@@ -95,6 +290,8 @@ export const SummaryStep: React.FC<SummaryStepProps> = ({
|
|||||||
hideHelpBar,
|
hideHelpBar,
|
||||||
sx,
|
sx,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [shareOpen, setShareOpen] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WizardLayout
|
<WizardLayout
|
||||||
variant="centered-form"
|
variant="centered-form"
|
||||||
@@ -106,124 +303,402 @@ export const SummaryStep: React.FC<SummaryStepProps> = ({
|
|||||||
hideHelpBar={hideHelpBar}
|
hideHelpBar={hideHelpBar}
|
||||||
sx={sx}
|
sx={sx}
|
||||||
>
|
>
|
||||||
{/* Header with share */}
|
{/* Header */}
|
||||||
<Box
|
<Typography variant="display3" component="h1" tabIndex={-1} sx={{ mb: 1 }}>
|
||||||
sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 1 }}
|
|
||||||
>
|
|
||||||
<Typography variant="display3" component="h1" tabIndex={-1}>
|
|
||||||
Review your plan
|
Review your plan
|
||||||
</Typography>
|
</Typography>
|
||||||
{onShare && (
|
|
||||||
<IconButton aria-label="Share this plan via email" onClick={onShare} size="medium">
|
<Box
|
||||||
<ShareOutlinedIcon />
|
sx={{
|
||||||
</IconButton>
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
mb: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
{isPrePlanning
|
||||||
|
? "Here's an overview of your plan. You can make changes at any time."
|
||||||
|
: 'Please review your selections below. You can edit any section before confirming.'}
|
||||||
|
</Typography>
|
||||||
|
{onShareByEmail && (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
startIcon={<ForwardToInboxOutlinedIcon />}
|
||||||
|
onClick={() => setShareOpen(true)}
|
||||||
|
sx={{ ml: 2, whiteSpace: 'nowrap', flexShrink: 0 }}
|
||||||
|
>
|
||||||
|
Share this plan
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
|
<Divider sx={{ mb: 4 }} />
|
||||||
Check everything looks right before confirming.
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 5 }}>
|
{/* ─── Arrangement details ─── */}
|
||||||
You can edit any section by tapping the edit icon.
|
{arrangementDetails && (
|
||||||
|
<>
|
||||||
|
<SectionHeader
|
||||||
|
title="Arrangement Details"
|
||||||
|
editStepId={arrangementDetails.editStepId}
|
||||||
|
onEdit={onEdit}
|
||||||
|
/>
|
||||||
|
<Card variant="outlined" padding="compact" sx={{ mb: 4 }}>
|
||||||
|
<Box
|
||||||
|
component="dl"
|
||||||
|
sx={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },
|
||||||
|
gap: 1.5,
|
||||||
|
m: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{arrangementDetails.arrangerName && (
|
||||||
|
<Box>
|
||||||
|
<Typography component="dt" variant="caption" color="text.secondary">
|
||||||
|
Arranged by
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Typography component="dd" variant="body2" sx={{ m: 0 }}>
|
||||||
|
{arrangementDetails.arrangerName}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{arrangementDetails.deceasedName && (
|
||||||
|
<Box>
|
||||||
|
<Typography component="dt" variant="caption" color="text.secondary">
|
||||||
|
In memory of
|
||||||
|
</Typography>
|
||||||
|
<Typography component="dd" variant="body2" sx={{ m: 0 }}>
|
||||||
|
{arrangementDetails.deceasedName}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{arrangementDetails.serviceTradition && (
|
||||||
|
<Box>
|
||||||
|
<Typography component="dt" variant="caption" color="text.secondary">
|
||||||
|
Service tradition
|
||||||
|
</Typography>
|
||||||
|
<Typography component="dd" variant="body2" sx={{ m: 0 }}>
|
||||||
|
{arrangementDetails.serviceTradition}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{arrangementDetails.preferredTime && (
|
||||||
|
<Box>
|
||||||
|
<Typography component="dt" variant="caption" color="text.secondary">
|
||||||
|
Preferred time
|
||||||
|
</Typography>
|
||||||
|
<Typography component="dd" variant="body2" sx={{ m: 0 }}>
|
||||||
|
{arrangementDetails.preferredTime}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{arrangementDetails.preferredDates &&
|
||||||
|
arrangementDetails.preferredDates.length > 0 && (
|
||||||
|
<Box sx={{ gridColumn: { sm: '1 / -1' } }}>
|
||||||
|
<Typography component="dt" variant="caption" color="text.secondary">
|
||||||
|
Preferred date{arrangementDetails.preferredDates.length > 1 ? 's' : ''}
|
||||||
|
</Typography>
|
||||||
|
<Typography component="dd" variant="body2" sx={{ m: 0 }}>
|
||||||
|
{arrangementDetails.preferredDates.join(' · ')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ─── Summary sections ─── */}
|
{/* ─── Summary sections ─── */}
|
||||||
{sections.map((section) => (
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
<Accordion
|
{sections.map((section) => {
|
||||||
key={section.id}
|
// Determine if this section has any priced items (affects tick display)
|
||||||
defaultExpanded
|
const hasPricedItems =
|
||||||
disableGutters
|
section.items?.some((item) => item.price != null || item.priceLabel) ?? false;
|
||||||
elevation={0}
|
|
||||||
|
// Allowance logic for primary price
|
||||||
|
const hasAllowance = section.allowanceAmount != null && section.price != null;
|
||||||
|
const isFullyCovered = hasAllowance && section.allowanceAmount! >= section.price!;
|
||||||
|
const remainingCost =
|
||||||
|
hasAllowance && !isFullyCovered ? section.price! - section.allowanceAmount! : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box key={section.id}>
|
||||||
|
<SectionHeader
|
||||||
|
title={section.title}
|
||||||
|
editStepId={section.editStepId}
|
||||||
|
onEdit={onEdit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Visual card for sections with image/name */}
|
||||||
|
{section.name && (
|
||||||
|
<Card variant="outlined" padding="none" sx={{ overflow: 'hidden' }}>
|
||||||
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
border: 1,
|
display: 'flex',
|
||||||
borderColor: 'divider',
|
flexDirection: { xs: 'column', sm: 'row' },
|
||||||
borderRadius: '8px !important',
|
|
||||||
mb: 2,
|
|
||||||
'&:before': { display: 'none' },
|
|
||||||
overflow: 'hidden',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={{ px: 3, py: 1 }}>
|
{/* Thumbnail */}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', flex: 1, gap: 1 }}>
|
{section.imageUrl && (
|
||||||
<Typography variant="h5" sx={{ flex: 1 }}>
|
<Box
|
||||||
{section.title}
|
sx={{
|
||||||
|
width: { xs: '100%', sm: 160 },
|
||||||
|
height: { xs: 160, sm: 'auto' },
|
||||||
|
minHeight: { sm: 120 },
|
||||||
|
flexShrink: 0,
|
||||||
|
backgroundImage: `url(${section.imageUrl})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
bgcolor: 'var(--fa-color-surface-subtle)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Details */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="label" sx={{ display: 'block', mb: 0.25 }}>
|
||||||
|
{section.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
{section.editStepId && onEdit && (
|
|
||||||
<IconButton
|
{section.subtitle && (
|
||||||
aria-label={`Edit ${section.title}`}
|
<Typography variant="body2" color="text.secondary">
|
||||||
size="small"
|
{section.subtitle}
|
||||||
onClick={(e) => {
|
</Typography>
|
||||||
e.stopPropagation();
|
)}
|
||||||
onEdit(section.editStepId!);
|
|
||||||
|
{/* Location with pin */}
|
||||||
|
{section.location && (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 0.25 }}>
|
||||||
|
<LocationOnOutlinedIcon
|
||||||
|
sx={{ fontSize: 16, color: 'text.secondary' }}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{section.location}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Colour swatch */}
|
||||||
|
{section.colourName && (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 0.75 }}>
|
||||||
|
{section.colourHex && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
borderRadius: '50%',
|
||||||
|
bgcolor: section.colourHex,
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
{section.colourName}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Spacer pushes price to bottom */}
|
||||||
|
<Box sx={{ flex: 1 }} />
|
||||||
|
|
||||||
|
{/* Price — hugs bottom of card detail area */}
|
||||||
|
{section.price != null && !hasAllowance && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
mt: 1.5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditOutlinedIcon fontSize="small" />
|
<Typography variant="label" color="primary">
|
||||||
</IconButton>
|
${section.price.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Fully covered — no price breakdown */}
|
||||||
|
{isFullyCovered && (
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 1.5 }}>
|
||||||
|
Included in your package
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</AccordionSummary>
|
</Box>
|
||||||
<AccordionDetails sx={{ px: 3, pb: 3 }}>
|
|
||||||
<Box component="dl" sx={{ m: 0 }}>
|
{/* Allowance breakdown — full-width divider section */}
|
||||||
|
{hasAllowance && !isFullyCovered && (
|
||||||
|
<Box sx={{ px: 2, pb: 2 }}>
|
||||||
|
<Divider sx={{ mb: 1.5 }} />
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Price
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
${section.price!.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Allowance applied
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
−${section.allowanceAmount!.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 0.5 }}>
|
||||||
|
<Typography variant="label">Remaining</Typography>
|
||||||
|
<Typography variant="label" color="primary">
|
||||||
|
${remainingCost.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Sub-items — full-width divider section */}
|
||||||
|
{section.items && section.items.length > 0 && (
|
||||||
|
<Box sx={{ px: 2, pb: 2 }}>
|
||||||
|
<Divider sx={{ mb: 1.5 }} />
|
||||||
{section.items.map((item, i) => (
|
{section.items.map((item, i) => (
|
||||||
<Box
|
<Box
|
||||||
key={i}
|
key={i}
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
py: 1,
|
alignItems: 'baseline',
|
||||||
borderBottom: i < section.items.length - 1 ? 1 : 0,
|
py: 0.5,
|
||||||
borderColor: 'divider',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>
|
<Typography variant="body2" color="text.secondary">
|
||||||
<Typography component="dt" variant="body2" color="text.secondary">
|
|
||||||
{item.label}
|
{item.label}
|
||||||
|
{item.value && ` — ${item.value}`}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="dd" variant="body1" sx={{ m: 0 }}>
|
{item.priceLabel ? (
|
||||||
{item.value}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
{item.price != null && (
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body2"
|
||||||
|
color="primary"
|
||||||
|
sx={{ ml: 2, whiteSpace: 'nowrap', fontStyle: 'italic' }}
|
||||||
|
>
|
||||||
|
{item.priceLabel}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
item.price != null && (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
color="primary"
|
color="primary"
|
||||||
sx={{ ml: 2, whiteSpace: 'nowrap' }}
|
sx={{ ml: 2, whiteSpace: 'nowrap' }}
|
||||||
>
|
>
|
||||||
${item.price.toLocaleString('en-AU')}
|
${item.price.toLocaleString('en-AU')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</AccordionDetails>
|
)}
|
||||||
</Accordion>
|
</Card>
|
||||||
))}
|
)}
|
||||||
|
|
||||||
{/* ─── Total bar ─── */}
|
{/* List-style section (no image/name — included services, extras) */}
|
||||||
<Paper
|
{!section.name && section.items && section.items.length > 0 && (
|
||||||
elevation={2}
|
<Card variant="outlined" padding="compact">
|
||||||
|
{section.items.map((item, i) => (
|
||||||
|
<Box
|
||||||
|
key={i}
|
||||||
sx={{
|
sx={{
|
||||||
p: 3,
|
|
||||||
mt: 3,
|
|
||||||
mb: 4,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRadius: 2,
|
py: 0.75,
|
||||||
|
borderBottom: i < section.items!.length - 1 ? 1 : 0,
|
||||||
|
borderColor: 'divider',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="h5">Total cost</Typography>
|
{/* Ticks only in sections with no priced items (included services) */}
|
||||||
{depositAmount != null && !isPrePlanning && (
|
{!hasPricedItems && (
|
||||||
<Typography variant="body2" color="text.secondary">
|
<CheckIcon sx={{ fontSize: 18, color: 'success.main' }} />
|
||||||
Deposit: ${depositAmount.toLocaleString('en-AU')}
|
)}
|
||||||
|
<Typography variant="body2">
|
||||||
|
{item.label}
|
||||||
|
{item.value && (
|
||||||
|
<Typography component="span" variant="body2" color="text.secondary">
|
||||||
|
{' '}
|
||||||
|
— {item.value}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="display3" color="primary" aria-live="polite" aria-atomic="true">
|
{item.priceLabel ? (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="primary"
|
||||||
|
sx={{ ml: 2, whiteSpace: 'nowrap', fontStyle: 'italic' }}
|
||||||
|
>
|
||||||
|
{item.priceLabel}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
item.price != null && (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="primary"
|
||||||
|
sx={{ ml: 2, whiteSpace: 'nowrap' }}
|
||||||
|
>
|
||||||
|
${item.price.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* ─── Total bar ─── */}
|
||||||
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
p: 3,
|
||||||
|
mt: 4,
|
||||||
|
mb: 4,
|
||||||
|
borderRadius: 2,
|
||||||
|
bgcolor: 'var(--fa-color-surface-warm)',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'var(--fa-color-brand-200)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{totalAllowances != null && totalAllowances > 0 && (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1 }}>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Package allowances applied
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
−${totalAllowances.toLocaleString('en-AU')}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||||
|
<Typography variant="h5">Total</Typography>
|
||||||
|
<Typography variant="h4" color="primary" aria-live="polite" aria-atomic="true">
|
||||||
${totalPrice.toLocaleString('en-AU')}
|
${totalPrice.toLocaleString('en-AU')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Payment reassurance */}
|
{/* Payment reassurance */}
|
||||||
@@ -236,26 +711,25 @@ export const SummaryStep: React.FC<SummaryStepProps> = ({
|
|||||||
<Divider sx={{ my: 3 }} />
|
<Divider sx={{ my: 3 }} />
|
||||||
|
|
||||||
{/* CTAs */}
|
{/* CTAs */}
|
||||||
<Box
|
<Button variant="contained" size="large" fullWidth loading={loading} onClick={onConfirm}>
|
||||||
sx={{
|
{isPrePlanning ? 'Save your plan' : 'Confirm and continue to payment'}
|
||||||
display: 'flex',
|
</Button>
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
{onSaveAndExit && (
|
||||||
flexDirection: { xs: 'column-reverse', sm: 'row' },
|
<Button variant="text" color="secondary" fullWidth onClick={onSaveAndExit} sx={{ mt: 1 }}>
|
||||||
gap: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{onSaveAndExit ? (
|
|
||||||
<Button variant="text" color="secondary" onClick={onSaveAndExit} type="button">
|
|
||||||
Save and continue later
|
Save and continue later
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
|
||||||
<Box />
|
|
||||||
)}
|
)}
|
||||||
<Button variant="contained" size="large" loading={loading} onClick={onConfirm}>
|
|
||||||
{isPrePlanning ? 'Save your plan' : 'Confirm'}
|
{/* Share dialog */}
|
||||||
</Button>
|
{onShareByEmail && (
|
||||||
</Box>
|
<ShareDialog
|
||||||
|
open={shareOpen}
|
||||||
|
onClose={() => setShareOpen(false)}
|
||||||
|
onSend={onShareByEmail}
|
||||||
|
loading={shareLoading}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</WizardLayout>
|
</WizardLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
export { SummaryStep, default } from './SummaryStep';
|
export { SummaryStep, default } from './SummaryStep';
|
||||||
export type { SummaryStepProps, SummarySection, SummaryLineItem } from './SummaryStep';
|
export type {
|
||||||
|
SummaryStepProps,
|
||||||
|
SummarySection,
|
||||||
|
SummaryLineItem,
|
||||||
|
ArrangementDetails,
|
||||||
|
} from './SummaryStep';
|
||||||
|
|||||||
Reference in New Issue
Block a user