- ConfirmationStep: animated SVG tick, "What happens next" warm card, bullet-point layout, contactPhone prop, link-based secondary actions - VenueStep + ProvidersStep: sticky search bar padding fix, off-white bg behind card lists - IntroStep, CemeteryStep, CrematoriumStep, DateTimeStep: add divider under subheading for visual separation - CoffinsStep: h4 heading (matches VenueStep/ProvidersStep list layout), sidebar headings h5 → h6 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
620 lines
20 KiB
TypeScript
620 lines
20 KiB
TypeScript
import React from 'react';
|
||
import Box from '@mui/material/Box';
|
||
import TextField from '@mui/material/TextField';
|
||
import MenuItem from '@mui/material/MenuItem';
|
||
import Paper from '@mui/material/Paper';
|
||
import Slider from '@mui/material/Slider';
|
||
import Pagination from '@mui/material/Pagination';
|
||
import InputAdornment from '@mui/material/InputAdornment';
|
||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||
import type { SxProps, Theme } from '@mui/material/styles';
|
||
import { WizardLayout } from '../../templates/WizardLayout';
|
||
import { Card } from '../../atoms/Card';
|
||
import { Badge } from '../../atoms/Badge';
|
||
import { Collapse } from '../../atoms/Collapse';
|
||
import { Typography } from '../../atoms/Typography';
|
||
import { Divider } from '../../atoms/Divider';
|
||
import { Link } from '../../atoms/Link';
|
||
|
||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||
|
||
/** A coffin available for selection */
|
||
export interface Coffin {
|
||
id: string;
|
||
name: string;
|
||
imageUrl: string;
|
||
/** Additional images (handles, fixtures, interior) shown as thumbnails */
|
||
thumbnails?: string[];
|
||
price: number;
|
||
category: string;
|
||
isPopular?: boolean;
|
||
/** Number of colour/finish variants available */
|
||
colourCount?: number;
|
||
}
|
||
|
||
/** Filter category option (supports one level of subcategories) */
|
||
export interface CoffinCategory {
|
||
value: string;
|
||
label: string;
|
||
/** Subcategories — shown when parent is selected */
|
||
children?: CoffinCategory[];
|
||
}
|
||
|
||
/** Sort direction */
|
||
export type CoffinSortBy = 'popularity' | 'price_asc' | 'price_desc';
|
||
|
||
/** Form values for the coffins step */
|
||
export interface CoffinsStepValues {
|
||
/** Active category filter */
|
||
categoryFilter: string;
|
||
/** Price range [min, max] for slider filter */
|
||
priceRange: [number, number];
|
||
/** Sort order */
|
||
sortBy: CoffinSortBy;
|
||
/** Current page (1-indexed) */
|
||
page: number;
|
||
}
|
||
|
||
/** Props for the CoffinsStep page component */
|
||
export interface CoffinsStepProps {
|
||
/** Current filter/sort values */
|
||
values: CoffinsStepValues;
|
||
/** Callback when any filter/sort value changes */
|
||
onChange: (values: CoffinsStepValues) => void;
|
||
/** Called when a coffin card is clicked — navigates to CoffinDetailsStep */
|
||
onSelectCoffin: (coffinId: string) => void;
|
||
/** Callback for back navigation */
|
||
onBack?: () => void;
|
||
/** Callback for save-and-exit */
|
||
onSaveAndExit?: () => void;
|
||
/** Available coffins (already filtered/sorted/paginated by parent) */
|
||
coffins: Coffin[];
|
||
/** Total count before pagination (for display) */
|
||
totalCount?: number;
|
||
/** Total pages */
|
||
totalPages?: number;
|
||
/** Category filter options */
|
||
categories?: CoffinCategory[];
|
||
/** Min price for the range slider */
|
||
minPrice?: number;
|
||
/** Max price for the range slider */
|
||
maxPrice?: number;
|
||
/** Package coffin allowance amount (shown in info bubble, omit if no allowance) */
|
||
allowanceAmount?: number;
|
||
/** Max coffins per page before pagination (default 20) */
|
||
pageSize?: number;
|
||
/** Whether this is a pre-planning flow */
|
||
isPrePlanning?: boolean;
|
||
/** Navigation bar */
|
||
navigation?: React.ReactNode;
|
||
/** Progress stepper */
|
||
progressStepper?: React.ReactNode;
|
||
/** Running total */
|
||
runningTotal?: React.ReactNode;
|
||
/** Hide the help bar */
|
||
hideHelpBar?: boolean;
|
||
/** MUI sx prop */
|
||
sx?: SxProps<Theme>;
|
||
}
|
||
|
||
// ─── Category menu item style ───────────────────────────────────────────────
|
||
|
||
const categoryItemSx = (isActive: boolean): SxProps<Theme> => ({
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
width: '100%',
|
||
textAlign: 'left',
|
||
border: 'none',
|
||
background: 'none',
|
||
cursor: 'pointer',
|
||
py: 0.75,
|
||
px: 1.5,
|
||
borderRadius: 1,
|
||
fontSize: '0.875rem',
|
||
fontWeight: isActive ? 600 : 400,
|
||
color: isActive ? 'primary.main' : 'text.primary',
|
||
bgcolor: isActive ? 'var(--fa-color-brand-50)' : 'transparent',
|
||
'&:hover': {
|
||
bgcolor: isActive ? 'var(--fa-color-brand-50)' : 'var(--fa-color-surface-subtle)',
|
||
},
|
||
fontFamily: 'inherit',
|
||
});
|
||
|
||
// ─── Coffin card with thumbnail hover ───────────────────────────────────────
|
||
|
||
const CoffinCard: React.FC<{
|
||
coffin: Coffin;
|
||
onClick: () => void;
|
||
}> = ({ coffin, onClick }) => {
|
||
const [previewImage, setPreviewImage] = React.useState(coffin.imageUrl);
|
||
|
||
const allImages = [coffin.imageUrl, ...(coffin.thumbnails || [])];
|
||
const hasThumbnails = allImages.length > 1;
|
||
|
||
return (
|
||
<Card
|
||
interactive
|
||
padding="none"
|
||
onClick={onClick}
|
||
sx={{
|
||
overflow: 'hidden',
|
||
cursor: 'pointer',
|
||
height: '100%',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
}}
|
||
>
|
||
{/* Main image on subtle background — handles white-bg product photos */}
|
||
<Box
|
||
sx={{
|
||
position: 'relative',
|
||
height: 180,
|
||
bgcolor: 'var(--fa-color-surface-subtle)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
p: 2,
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
<Box
|
||
component="img"
|
||
src={previewImage}
|
||
alt={coffin.name}
|
||
sx={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
|
||
/>
|
||
{coffin.isPopular && (
|
||
<Box sx={{ position: 'absolute', top: 8, left: 8 }}>
|
||
<Badge variant="soft" color="brand" aria-label="Most popular choice">
|
||
Most Popular
|
||
</Badge>
|
||
</Box>
|
||
)}
|
||
</Box>
|
||
|
||
{/* Thumbnail strip — hover swaps main preview */}
|
||
{hasThumbnails && (
|
||
<Box
|
||
sx={{ display: 'flex', gap: 0.5, px: 2, pt: 1, flexShrink: 0 }}
|
||
onMouseLeave={() => setPreviewImage(coffin.imageUrl)}
|
||
>
|
||
{allImages.map((img, i) => (
|
||
<Box
|
||
key={i}
|
||
component="img"
|
||
src={img}
|
||
alt=""
|
||
onMouseEnter={() => setPreviewImage(img)}
|
||
sx={{
|
||
width: 48,
|
||
height: 36,
|
||
objectFit: 'cover',
|
||
borderRadius: 0.5,
|
||
border: 2,
|
||
borderColor: previewImage === img ? 'primary.main' : 'transparent',
|
||
opacity: previewImage === img ? 1 : 0.7,
|
||
transition: 'border-color 0.15s, opacity 0.15s',
|
||
'&:hover': { opacity: 1 },
|
||
}}
|
||
/>
|
||
))}
|
||
</Box>
|
||
)}
|
||
|
||
{/* Content — flex: 1 ensures all cards are the same height */}
|
||
<Box
|
||
sx={{ p: 2, pt: hasThumbnails ? 1 : 2, flex: 1, display: 'flex', flexDirection: 'column' }}
|
||
>
|
||
<Typography variant="h6" maxLines={2} sx={{ mb: 0.5 }}>
|
||
{coffin.name}
|
||
</Typography>
|
||
<Typography variant="h6" color="primary">
|
||
${coffin.price.toLocaleString('en-AU')}
|
||
</Typography>
|
||
{coffin.colourCount != null && coffin.colourCount > 0 && (
|
||
<Typography
|
||
variant="caption"
|
||
color="text.secondary"
|
||
sx={{ mt: 'auto', pt: 0.5, display: 'block' }}
|
||
>
|
||
{coffin.colourCount} Colour option{coffin.colourCount !== 1 ? 's' : ''} available
|
||
</Typography>
|
||
)}
|
||
</Box>
|
||
</Card>
|
||
);
|
||
};
|
||
|
||
// ─── Component ───────────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Step 10 — Coffin browsing for the FA arrangement wizard.
|
||
*
|
||
* Grid-sidebar layout: filter sidebar (left, stacked on mobile) + coffin
|
||
* card grid (right). Both panels scroll independently on desktop; sidebar
|
||
* scrollbar appears on hover.
|
||
*
|
||
* Clicking a coffin card navigates to CoffinDetailsStep — no Continue
|
||
* button. Sidebar contains heading, allowance info, category menu with
|
||
* expandable subcategories, price range slider with editable inputs,
|
||
* sort control, and save-and-exit link.
|
||
*
|
||
* Cards show product image on subtle background (for white-bg photos),
|
||
* thumbnail strip with hover preview, name, price, and colour count.
|
||
* All cards are equal height regardless of thumbnail/colour presence.
|
||
*
|
||
* Uses Australian terminology: "coffin" not "casket".
|
||
*
|
||
* Pure presentation component — props in, callbacks out.
|
||
* Filtering, sorting, and pagination logic handled by parent.
|
||
*
|
||
* Spec: documentation/steps/steps/10_coffins.yaml
|
||
*/
|
||
export const CoffinsStep: React.FC<CoffinsStepProps> = ({
|
||
values,
|
||
onChange,
|
||
onSelectCoffin,
|
||
onBack,
|
||
onSaveAndExit,
|
||
coffins,
|
||
totalCount,
|
||
totalPages = 1,
|
||
categories = [
|
||
{ value: 'all', label: 'All' },
|
||
{
|
||
value: 'solid_timber',
|
||
label: 'Solid Timber',
|
||
children: [
|
||
{ value: 'cedar', label: 'Cedar' },
|
||
{ value: 'oak', label: 'Oak' },
|
||
{ value: 'mahogany', label: 'Mahogany' },
|
||
{ value: 'teak', label: 'Teak' },
|
||
],
|
||
},
|
||
{
|
||
value: 'custom_board',
|
||
label: 'Custom Board',
|
||
children: [
|
||
{ value: 'printed', label: 'Printed' },
|
||
{ value: 'painted', label: 'Painted' },
|
||
],
|
||
},
|
||
{ value: 'environmental', label: 'Environmental' },
|
||
{ value: 'designer', label: 'Designer' },
|
||
{ value: 'protective_metal', label: 'Protective Metal' },
|
||
],
|
||
minPrice = 0,
|
||
maxPrice = 15000,
|
||
allowanceAmount,
|
||
pageSize = 20,
|
||
isPrePlanning = false,
|
||
navigation,
|
||
progressStepper,
|
||
runningTotal,
|
||
hideHelpBar,
|
||
sx,
|
||
}) => {
|
||
// Cap displayed coffins to pageSize (default 20 per page)
|
||
const visibleCoffins = coffins.slice(0, pageSize);
|
||
const displayCount = totalCount ?? coffins.length;
|
||
const derivedTotalPages = totalPages > 1 ? totalPages : Math.ceil(coffins.length / pageSize);
|
||
|
||
// ─── Price input local state (commits on blur / Enter) ───
|
||
const [priceMinInput, setPriceMinInput] = React.useState(String(values.priceRange[0]));
|
||
const [priceMaxInput, setPriceMaxInput] = React.useState(String(values.priceRange[1]));
|
||
|
||
// Sync local state when the slider (or clear) changes the value
|
||
const rangeMin = values.priceRange[0];
|
||
const rangeMax = values.priceRange[1];
|
||
React.useEffect(() => {
|
||
setPriceMinInput(String(rangeMin));
|
||
setPriceMaxInput(String(rangeMax));
|
||
}, [rangeMin, rangeMax]);
|
||
|
||
const commitPriceRange = () => {
|
||
let lo = parseInt(priceMinInput, 10);
|
||
let hi = parseInt(priceMaxInput, 10);
|
||
if (isNaN(lo)) lo = minPrice;
|
||
if (isNaN(hi)) hi = maxPrice;
|
||
lo = Math.max(minPrice, Math.min(lo, maxPrice));
|
||
hi = Math.max(minPrice, Math.min(hi, maxPrice));
|
||
if (lo > hi) [lo, hi] = [hi, lo];
|
||
const newRange: [number, number] = [lo, hi];
|
||
if (newRange[0] !== values.priceRange[0] || newRange[1] !== values.priceRange[1]) {
|
||
onChange({ ...values, priceRange: newRange, page: 1 });
|
||
}
|
||
};
|
||
|
||
// ─── Derived state ───
|
||
const hasActiveFilters =
|
||
values.categoryFilter !== 'all' ||
|
||
values.priceRange[0] !== minPrice ||
|
||
values.priceRange[1] !== maxPrice;
|
||
|
||
// Determine which parent category is expanded (if filter matches parent or child)
|
||
const expandedParent =
|
||
categories.find(
|
||
(cat) =>
|
||
cat.value === values.categoryFilter ||
|
||
cat.children?.some((child) => child.value === values.categoryFilter),
|
||
)?.value ?? null;
|
||
|
||
const handleClearFilters = () => {
|
||
onChange({
|
||
...values,
|
||
categoryFilter: 'all',
|
||
priceRange: [minPrice, maxPrice],
|
||
page: 1,
|
||
});
|
||
};
|
||
|
||
return (
|
||
<WizardLayout
|
||
variant="grid-sidebar"
|
||
navigation={navigation}
|
||
progressStepper={progressStepper}
|
||
runningTotal={runningTotal}
|
||
showBackLink={!!onBack}
|
||
backLabel="Back"
|
||
onBack={onBack}
|
||
hideHelpBar={hideHelpBar}
|
||
sx={sx}
|
||
secondaryPanel={
|
||
/* ─── Grid area (right panel) ─── */
|
||
<Box>
|
||
{/* Results count */}
|
||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }} aria-live="polite">
|
||
Showing {displayCount} coffin{displayCount !== 1 ? 's' : ''}
|
||
</Typography>
|
||
|
||
{/* Coffin card grid */}
|
||
<Box
|
||
role="list"
|
||
aria-label="Available coffins"
|
||
sx={{
|
||
display: 'grid',
|
||
gridTemplateColumns: {
|
||
xs: '1fr',
|
||
sm: 'repeat(2, 1fr)',
|
||
md: 'repeat(3, 1fr)',
|
||
},
|
||
gap: 2,
|
||
mb: 3,
|
||
}}
|
||
>
|
||
{visibleCoffins.map((coffin) => (
|
||
<Box key={coffin.id} role="listitem">
|
||
<CoffinCard coffin={coffin} onClick={() => onSelectCoffin(coffin.id)} />
|
||
</Box>
|
||
))}
|
||
|
||
{visibleCoffins.length === 0 && (
|
||
<Box sx={{ py: 6, textAlign: 'center', gridColumn: '1 / -1' }}>
|
||
<Typography variant="body1" color="text.secondary" sx={{ mb: 1 }}>
|
||
No coffins match your selected filters.
|
||
</Typography>
|
||
<Typography variant="body2" color="text.secondary">
|
||
Try adjusting the category or price range.
|
||
</Typography>
|
||
</Box>
|
||
)}
|
||
</Box>
|
||
|
||
{/* Pagination */}
|
||
{derivedTotalPages > 1 && (
|
||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||
<Pagination
|
||
count={derivedTotalPages}
|
||
page={values.page}
|
||
onChange={(_, page) => onChange({ ...values, page })}
|
||
color="primary"
|
||
/>
|
||
</Box>
|
||
)}
|
||
</Box>
|
||
}
|
||
>
|
||
{/* ─── Sidebar (left panel) ─── */}
|
||
|
||
{/* Heading — matches VenueStep / ProvidersStep list layout */}
|
||
<Typography variant="h4" component="h1" sx={{ mb: 0.5, pt: 2 }} tabIndex={-1}>
|
||
Choose a coffin
|
||
</Typography>
|
||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||
{isPrePlanning
|
||
? 'Browse the range to get an idea of styles and pricing.'
|
||
: 'Browse our selection of bespoke designer coffins.'}
|
||
</Typography>
|
||
|
||
{/* Allowance info bubble */}
|
||
{allowanceAmount != null && (
|
||
<Paper
|
||
variant="outlined"
|
||
sx={{
|
||
p: 2,
|
||
mb: 3,
|
||
display: 'flex',
|
||
gap: 1.5,
|
||
alignItems: 'flex-start',
|
||
bgcolor: 'var(--fa-color-surface-warm)',
|
||
borderColor: 'var(--fa-color-brand-200)',
|
||
}}
|
||
>
|
||
<InfoOutlinedIcon
|
||
sx={{ fontSize: 20, color: 'var(--fa-color-brand-500)', flexShrink: 0, mt: 0.25 }}
|
||
/>
|
||
<Typography variant="body2">
|
||
Your package includes a <strong>${allowanceAmount.toLocaleString('en-AU')}</strong>{' '}
|
||
allowance for coffins which will be applied to your order after you have made a
|
||
selection.
|
||
</Typography>
|
||
</Paper>
|
||
)}
|
||
|
||
<Divider sx={{ mb: 3 }} />
|
||
|
||
{/* ─── Categories menu — single-select with expandable subcategories ─── */}
|
||
<Typography variant="h6" sx={{ mb: 1.5 }}>
|
||
Categories
|
||
</Typography>
|
||
<Box component="nav" aria-label="Filter by category" sx={{ mb: 3 }}>
|
||
{categories.map((cat) => {
|
||
const isParentActive =
|
||
cat.value === values.categoryFilter ||
|
||
cat.children?.some((child) => child.value === values.categoryFilter);
|
||
const isExactActive = cat.value === values.categoryFilter;
|
||
const isExpanded = expandedParent === cat.value;
|
||
|
||
return (
|
||
<React.Fragment key={cat.value}>
|
||
{/* Parent category */}
|
||
<Box
|
||
component="button"
|
||
type="button"
|
||
onClick={() => onChange({ ...values, categoryFilter: cat.value, page: 1 })}
|
||
sx={categoryItemSx(!!isExactActive)}
|
||
>
|
||
<span>{cat.label}</span>
|
||
{cat.children && cat.children.length > 0 && (
|
||
<ExpandMoreIcon
|
||
sx={{
|
||
fontSize: 18,
|
||
color: isParentActive ? 'primary.main' : 'text.secondary',
|
||
transform: isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)',
|
||
transition: 'transform 0.2s',
|
||
}}
|
||
/>
|
||
)}
|
||
</Box>
|
||
|
||
{/* Subcategories — expand when parent is active */}
|
||
{cat.children && cat.children.length > 0 && (
|
||
<Collapse in={isExpanded}>
|
||
<Box sx={{ pl: 2 }}>
|
||
{cat.children.map((child) => (
|
||
<Box
|
||
key={child.value}
|
||
component="button"
|
||
type="button"
|
||
onClick={() =>
|
||
onChange({ ...values, categoryFilter: child.value, page: 1 })
|
||
}
|
||
sx={categoryItemSx(child.value === values.categoryFilter)}
|
||
>
|
||
<span>{child.label}</span>
|
||
</Box>
|
||
))}
|
||
</Box>
|
||
</Collapse>
|
||
)}
|
||
</React.Fragment>
|
||
);
|
||
})}
|
||
</Box>
|
||
|
||
<Divider sx={{ mb: 3 }} />
|
||
|
||
{/* ─── Price range slider with editable inputs ─── */}
|
||
<Typography variant="h6" sx={{ mb: 1.5 }}>
|
||
Price
|
||
</Typography>
|
||
<Box sx={{ px: 1, mb: 1.5 }}>
|
||
<Slider
|
||
value={values.priceRange}
|
||
onChange={(_, newValue) =>
|
||
onChange({ ...values, priceRange: newValue as [number, number], page: 1 })
|
||
}
|
||
min={minPrice}
|
||
max={maxPrice}
|
||
step={100}
|
||
valueLabelDisplay="auto"
|
||
valueLabelFormat={(v) => `$${v.toLocaleString('en-AU')}`}
|
||
color="primary"
|
||
/>
|
||
</Box>
|
||
<Box sx={{ display: 'flex', gap: 1, mb: 3 }}>
|
||
<TextField
|
||
size="small"
|
||
value={priceMinInput}
|
||
onChange={(e) => setPriceMinInput(e.target.value.replace(/[^0-9]/g, ''))}
|
||
onBlur={commitPriceRange}
|
||
onKeyDown={(e) => e.key === 'Enter' && commitPriceRange()}
|
||
InputProps={{
|
||
startAdornment: <InputAdornment position="start">$</InputAdornment>,
|
||
}}
|
||
inputProps={{ inputMode: 'numeric', 'aria-label': 'Minimum price' }}
|
||
sx={{ flex: 1 }}
|
||
/>
|
||
<Typography variant="body2" color="text.secondary" sx={{ alignSelf: 'center' }}>
|
||
–
|
||
</Typography>
|
||
<TextField
|
||
size="small"
|
||
value={priceMaxInput}
|
||
onChange={(e) => setPriceMaxInput(e.target.value.replace(/[^0-9]/g, ''))}
|
||
onBlur={commitPriceRange}
|
||
onKeyDown={(e) => e.key === 'Enter' && commitPriceRange()}
|
||
InputProps={{
|
||
startAdornment: <InputAdornment position="start">$</InputAdornment>,
|
||
}}
|
||
inputProps={{ inputMode: 'numeric', 'aria-label': 'Maximum price' }}
|
||
sx={{ flex: 1 }}
|
||
/>
|
||
</Box>
|
||
|
||
<Divider sx={{ mb: 3 }} />
|
||
|
||
{/* ─── Sort by ─── */}
|
||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||
Sort by
|
||
</Typography>
|
||
<TextField
|
||
select
|
||
value={values.sortBy}
|
||
onChange={(e) => onChange({ ...values, sortBy: e.target.value as CoffinSortBy })}
|
||
fullWidth
|
||
size="small"
|
||
>
|
||
<MenuItem value="popularity">Popularity</MenuItem>
|
||
<MenuItem value="price_asc">Price: Low to High</MenuItem>
|
||
<MenuItem value="price_desc">Price: High to Low</MenuItem>
|
||
</TextField>
|
||
|
||
{/* Clear filters — at bottom of sidebar, under sort */}
|
||
{hasActiveFilters && (
|
||
<>
|
||
<Divider sx={{ my: 3 }} />
|
||
<Link
|
||
component="button"
|
||
onClick={handleClearFilters}
|
||
underline="hover"
|
||
sx={{ fontSize: '0.875rem' }}
|
||
>
|
||
Clear all filters
|
||
</Link>
|
||
</>
|
||
)}
|
||
|
||
{/* Save and continue later — bottom of sidebar */}
|
||
{onSaveAndExit && (
|
||
<>
|
||
<Divider sx={{ my: 3 }} />
|
||
<Link
|
||
component="button"
|
||
onClick={onSaveAndExit}
|
||
underline="hover"
|
||
sx={{ fontSize: '0.875rem', color: 'text.secondary' }}
|
||
>
|
||
Save and continue later
|
||
</Link>
|
||
</>
|
||
)}
|
||
</WizardLayout>
|
||
);
|
||
};
|
||
|
||
CoffinsStep.displayName = 'CoffinsStep';
|
||
export default CoffinsStep;
|