- Share this plan: right-aligned below divider, clear spacing before content - Save and continue later: outlined button, full width, under confirm CTA Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
739 lines
27 KiB
TypeScript
739 lines
27 KiB
TypeScript
import React from 'react';
|
||
import Box from '@mui/material/Box';
|
||
import Paper from '@mui/material/Paper';
|
||
import TextField from '@mui/material/TextField';
|
||
import ForwardToInboxOutlinedIcon from '@mui/icons-material/ForwardToInboxOutlined';
|
||
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
||
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 { WizardLayout } from '../../templates/WizardLayout';
|
||
import { DialogShell } from '../../atoms/DialogShell';
|
||
import { Card } from '../../atoms/Card';
|
||
import { Typography } from '../../atoms/Typography';
|
||
import { Button } from '../../atoms/Button';
|
||
import { IconButton } from '../../atoms/IconButton';
|
||
import { Divider } from '../../atoms/Divider';
|
||
|
||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||
|
||
/** A line item within a summary section */
|
||
export interface SummaryLineItem {
|
||
label: string;
|
||
value?: string;
|
||
price?: number;
|
||
/** Custom price label like "POA" */
|
||
priceLabel?: string;
|
||
}
|
||
|
||
/** A section of the plan summary */
|
||
export interface SummarySection {
|
||
id: string;
|
||
/** Section heading (e.g. "Coffin", "Service Venue") */
|
||
title: string;
|
||
/** Step ID for edit navigation */
|
||
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;
|
||
}
|
||
|
||
/** Props for the SummaryStep page component */
|
||
export interface SummaryStepProps {
|
||
/** Arrangement details shown at the top */
|
||
arrangementDetails?: ArrangementDetails;
|
||
/** Summary sections */
|
||
sections: SummarySection[];
|
||
/** Total cost */
|
||
totalPrice: number;
|
||
/** Total allowances applied */
|
||
totalAllowances?: 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 when plan is shared via email */
|
||
onShareByEmail?: (emails: string[]) => void;
|
||
/** Whether the share is in progress */
|
||
shareLoading?: boolean;
|
||
/** 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>;
|
||
}
|
||
|
||
// ─── 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 ───────────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Step 13 — Summary / Review for the FA arrangement wizard.
|
||
*
|
||
* Visual cart-style summary of the funeral plan. Each selection is shown
|
||
* as a compact card with image, details, and pricing. Allowances are
|
||
* displayed inline per section. Email sharing via dialog.
|
||
*
|
||
* Pure presentation component — props in, callbacks out.
|
||
*
|
||
* Spec: documentation/steps/steps/13_summary.yaml
|
||
*/
|
||
export const SummaryStep: React.FC<SummaryStepProps> = ({
|
||
arrangementDetails,
|
||
sections,
|
||
totalPrice,
|
||
totalAllowances,
|
||
onConfirm,
|
||
onBack,
|
||
onSaveAndExit,
|
||
onEdit,
|
||
onShareByEmail,
|
||
shareLoading = false,
|
||
loading = false,
|
||
isPrePlanning = false,
|
||
navigation,
|
||
progressStepper,
|
||
hideHelpBar,
|
||
sx,
|
||
}) => {
|
||
const [shareOpen, setShareOpen] = React.useState(false);
|
||
|
||
return (
|
||
<WizardLayout
|
||
variant="centered-form"
|
||
navigation={navigation}
|
||
progressStepper={progressStepper}
|
||
showBackLink={!!onBack}
|
||
backLabel="Back"
|
||
onBack={onBack}
|
||
hideHelpBar={hideHelpBar}
|
||
sx={sx}
|
||
>
|
||
{/* Header */}
|
||
<Typography variant="display3" component="h1" tabIndex={-1} sx={{ mb: 1 }}>
|
||
Review your plan
|
||
</Typography>
|
||
|
||
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}>
|
||
{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>
|
||
|
||
<Divider sx={{ mb: 2 }} />
|
||
|
||
{onShareByEmail && (
|
||
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mb: 4 }}>
|
||
<Button
|
||
variant="outlined"
|
||
color="secondary"
|
||
size="small"
|
||
startIcon={<ForwardToInboxOutlinedIcon />}
|
||
onClick={() => setShareOpen(true)}
|
||
>
|
||
Share this plan
|
||
</Button>
|
||
</Box>
|
||
)}
|
||
|
||
{/* ─── Arrangement details ─── */}
|
||
{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 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 ─── */}
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||
{sections.map((section) => {
|
||
// Determine if this section has any priced items (affects tick display)
|
||
const hasPricedItems =
|
||
section.items?.some((item) => item.price != null || item.priceLabel) ?? false;
|
||
|
||
// 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={{
|
||
display: 'flex',
|
||
flexDirection: { xs: 'column', sm: 'row' },
|
||
}}
|
||
>
|
||
{/* Thumbnail */}
|
||
{section.imageUrl && (
|
||
<Box
|
||
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>
|
||
|
||
{section.subtitle && (
|
||
<Typography variant="body2" color="text.secondary">
|
||
{section.subtitle}
|
||
</Typography>
|
||
)}
|
||
|
||
{/* 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,
|
||
}}
|
||
>
|
||
<Typography variant="label" color="primary">
|
||
${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>
|
||
|
||
{/* 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) => (
|
||
<Box
|
||
key={i}
|
||
sx={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'baseline',
|
||
py: 0.5,
|
||
}}
|
||
>
|
||
<Typography variant="body2" color="text.secondary">
|
||
{item.label}
|
||
{item.value && ` — ${item.value}`}
|
||
</Typography>
|
||
{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>
|
||
))}
|
||
</Box>
|
||
)}
|
||
</Card>
|
||
)}
|
||
|
||
{/* List-style section (no image/name — included services, extras) */}
|
||
{!section.name && section.items && section.items.length > 0 && (
|
||
<Card variant="outlined" padding="compact">
|
||
{section.items.map((item, i) => (
|
||
<Box
|
||
key={i}
|
||
sx={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
py: 0.75,
|
||
borderBottom: i < section.items!.length - 1 ? 1 : 0,
|
||
borderColor: 'divider',
|
||
}}
|
||
>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
{/* Ticks only in sections with no priced items (included services) */}
|
||
{!hasPricedItems && (
|
||
<CheckIcon sx={{ fontSize: 18, color: 'success.main' }} />
|
||
)}
|
||
<Typography variant="body2">
|
||
{item.label}
|
||
{item.value && (
|
||
<Typography component="span" variant="body2" color="text.secondary">
|
||
{' '}
|
||
— {item.value}
|
||
</Typography>
|
||
)}
|
||
</Typography>
|
||
</Box>
|
||
{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')}
|
||
</Typography>
|
||
</Box>
|
||
</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 */}
|
||
<Button variant="contained" size="large" fullWidth loading={loading} onClick={onConfirm}>
|
||
{isPrePlanning ? 'Save your plan' : 'Confirm and continue to payment'}
|
||
</Button>
|
||
|
||
{onSaveAndExit && (
|
||
<Button
|
||
variant="outlined"
|
||
color="secondary"
|
||
size="large"
|
||
fullWidth
|
||
onClick={onSaveAndExit}
|
||
sx={{ mt: 1.5 }}
|
||
>
|
||
Save and continue later
|
||
</Button>
|
||
)}
|
||
|
||
{/* Share dialog */}
|
||
{onShareByEmail && (
|
||
<ShareDialog
|
||
open={shareOpen}
|
||
onClose={() => setShareOpen(false)}
|
||
onSend={onShareByEmail}
|
||
loading={shareLoading}
|
||
/>
|
||
)}
|
||
</WizardLayout>
|
||
);
|
||
};
|
||
|
||
SummaryStep.displayName = 'SummaryStep';
|
||
export default SummaryStep;
|