Add FuneralFinder V4, HomePage V3/V4, restyle Footer to light grey
- FuneralFinder V4: 3 numbered steps (48px circles), ungated location, no heading, inline copper errors, "Search" CTA. Archived. - FuneralFinderV3: heading weight 600, "Find your local providers", "Search Local Providers" CTA, optional subheading - HomePage V1/V2: split into separate archived stories - HomePage V3: hero-3.png, updated copy, venue photos, map placeholder, scrolling partner logo bar, warm gradient CTA, increased spacing - HomePage V4: same as V3 with FuneralFinderV4 via new finderSlot prop - Footer: surface.subtle bg (matches header), dark-on-light text - All versions archived in Storybook Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,7 +69,7 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
const copyrightText = copyright || `\u00A9 ${year} Funeral Arranger. All rights reserved.`;
|
||||
|
||||
const overlineSx = {
|
||||
color: 'var(--fa-color-brand-400)',
|
||||
color: 'text.secondary',
|
||||
textTransform: 'uppercase' as const,
|
||||
letterSpacing: '0.08em',
|
||||
display: 'block',
|
||||
@@ -77,8 +77,8 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
};
|
||||
|
||||
const contactLinkSx = {
|
||||
color: 'var(--fa-color-white)',
|
||||
'&:hover': { color: 'var(--fa-color-brand-300)' },
|
||||
color: 'text.primary',
|
||||
'&:hover': { color: 'var(--fa-color-brand-600)' },
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -87,8 +87,8 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
component="footer"
|
||||
sx={[
|
||||
{
|
||||
bgcolor: 'var(--fa-color-brand-950)',
|
||||
color: 'var(--fa-color-white)',
|
||||
bgcolor: 'var(--fa-color-surface-subtle)',
|
||||
color: 'text.primary',
|
||||
pt: { xs: 5, md: 8 },
|
||||
pb: 0,
|
||||
},
|
||||
@@ -104,7 +104,7 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
{tagline && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: 'var(--fa-color-brand-300)', maxWidth: { xs: '100%', md: 280 } }}
|
||||
sx={{ color: 'text.secondary', maxWidth: { xs: '100%', md: 280 } }}
|
||||
>
|
||||
{tagline}
|
||||
</Typography>
|
||||
@@ -168,7 +168,7 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
<Typography
|
||||
variant="label"
|
||||
sx={{
|
||||
color: 'var(--fa-color-brand-300)',
|
||||
color: 'text.secondary',
|
||||
mb: 2,
|
||||
display: 'block',
|
||||
}}
|
||||
@@ -192,13 +192,13 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
href={link.href}
|
||||
onClick={link.onClick}
|
||||
sx={{
|
||||
color: 'var(--fa-color-brand-200)',
|
||||
color: 'text.primary',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
minHeight: 44,
|
||||
'&:hover': { color: 'var(--fa-color-white)' },
|
||||
'&:hover': { color: 'var(--fa-color-brand-600)' },
|
||||
}}
|
||||
>
|
||||
{link.label}
|
||||
@@ -211,7 +211,7 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
</Grid>
|
||||
|
||||
{/* Bottom bar */}
|
||||
<Divider sx={{ borderColor: 'var(--fa-color-brand-900)', mt: { xs: 5, md: 8 } }} />
|
||||
<Divider sx={{ mt: { xs: 5, md: 8 } }} />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
@@ -223,7 +223,7 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
py: 3,
|
||||
}}
|
||||
>
|
||||
<Typography variant="captionSm" sx={{ color: 'var(--fa-color-brand-400)' }}>
|
||||
<Typography variant="captionSm" sx={{ color: 'text.secondary' }}>
|
||||
{copyrightText}
|
||||
</Typography>
|
||||
|
||||
@@ -242,13 +242,13 @@ export const Footer = React.forwardRef<HTMLDivElement, FooterProps>(
|
||||
href={link.href}
|
||||
onClick={link.onClick}
|
||||
sx={{
|
||||
color: 'var(--fa-color-brand-400)',
|
||||
color: 'text.secondary',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 500,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
minHeight: 44,
|
||||
'&:hover': { color: 'var(--fa-color-white)' },
|
||||
'&:hover': { color: 'var(--fa-color-brand-600)' },
|
||||
}}
|
||||
>
|
||||
{link.label}
|
||||
|
||||
@@ -244,7 +244,7 @@ export const FuneralFinderV3 = React.forwardRef<HTMLDivElement, FuneralFinderV3P
|
||||
onSearch,
|
||||
loading = false,
|
||||
heading = 'Find funeral directors near you',
|
||||
subheading = 'Tell us what you need and we\u2019ll show options in your area.',
|
||||
subheading,
|
||||
sx,
|
||||
} = props;
|
||||
|
||||
@@ -367,15 +367,17 @@ export const FuneralFinderV3 = React.forwardRef<HTMLDivElement, FuneralFinderV3P
|
||||
component="h2"
|
||||
sx={{
|
||||
fontFamily: 'var(--fa-font-family-display)',
|
||||
fontWeight: 400,
|
||||
mb: 1,
|
||||
fontWeight: 600,
|
||||
mb: subheading ? 1 : 0,
|
||||
}}
|
||||
>
|
||||
{heading}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{subheading}
|
||||
</Typography>
|
||||
{subheading && (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{subheading}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
@@ -527,7 +529,7 @@ export const FuneralFinderV3 = React.forwardRef<HTMLDivElement, FuneralFinderV3P
|
||||
onClick={handleSubmit}
|
||||
sx={{ minHeight: 52 }}
|
||||
>
|
||||
Find Funeral Directors
|
||||
Search Local Providers
|
||||
</Button>
|
||||
<Typography
|
||||
variant="captionSm"
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { FuneralFinderV4 } from './FuneralFinderV4';
|
||||
|
||||
const meta: Meta<typeof FuneralFinderV4> = {
|
||||
title: 'Archive/FuneralFinder V4',
|
||||
component: FuneralFinderV4,
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
},
|
||||
args: {
|
||||
onSearch: (params) => {
|
||||
console.log('Search params:', params);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof FuneralFinderV4>;
|
||||
|
||||
/** Default empty state — 3 steps + location ready for input */
|
||||
export const Default: Story = {};
|
||||
|
||||
/** Loading state — CTA shows spinner */
|
||||
export const Loading: Story = {
|
||||
args: { loading: true },
|
||||
};
|
||||
|
||||
/** Placed inside a dark hero section to preview in context */
|
||||
export const InsideHero: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-sage-800, #4c5459)',
|
||||
color: '#fff',
|
||||
py: 8,
|
||||
px: 4,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ fontSize: '2.5rem', fontWeight: 700, mb: 1 }}>Funeral Arranger</Box>
|
||||
<Box sx={{ opacity: 0.8, mb: 4 }}>Find trusted funeral directors near you</Box>
|
||||
<Box sx={{ maxWidth: 480, mx: 'auto' }}>
|
||||
<Story />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
/** Constrained width — typical sidebar or narrow column */
|
||||
export const Narrow: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<Box sx={{ maxWidth: 380, mx: 'auto' }}>
|
||||
<Story />
|
||||
</Box>
|
||||
),
|
||||
],
|
||||
};
|
||||
493
src/components/organisms/FuneralFinder/FuneralFinderV4.tsx
Normal file
493
src/components/organisms/FuneralFinder/FuneralFinderV4.tsx
Normal file
@@ -0,0 +1,493 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Select, { type SelectChangeEvent } from '@mui/material/Select';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import type { SxProps, Theme } from '@mui/material/styles';
|
||||
import { Typography } from '../../atoms/Typography';
|
||||
import { Button } from '../../atoms/Button';
|
||||
import { Input } from '../../atoms/Input';
|
||||
import { Divider } from '../../atoms/Divider';
|
||||
|
||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||
|
||||
type LookingTo = 'arrange-now' | 'expected' | 'future';
|
||||
type PlanningFor = 'myself' | 'someone-else';
|
||||
type FuneralType =
|
||||
| 'cremation-funeral'
|
||||
| 'cremation-only'
|
||||
| 'burial-funeral'
|
||||
| 'graveside-only'
|
||||
| 'water-cremation';
|
||||
|
||||
/** Search parameters returned on form submission */
|
||||
export interface FuneralFinderV4SearchParams {
|
||||
/** User's situation — immediate, expected, or future need */
|
||||
lookingTo: LookingTo;
|
||||
/** Who the funeral is for */
|
||||
planningFor: PlanningFor;
|
||||
/** Type of funeral selected */
|
||||
funeralType: FuneralType;
|
||||
/** Suburb or postcode */
|
||||
location: string;
|
||||
}
|
||||
|
||||
/** Props for the FuneralFinder v4 organism */
|
||||
export interface FuneralFinderV4Props {
|
||||
/** Called when the user submits with valid data */
|
||||
onSearch?: (params: FuneralFinderV4SearchParams) => void;
|
||||
/** Shows loading state on the CTA */
|
||||
loading?: boolean;
|
||||
/** MUI sx override for the root container */
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
// ─── Options ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const LOOKING_TO_OPTIONS: { value: LookingTo; label: string }[] = [
|
||||
{ value: 'arrange-now', label: 'Arrange a funeral for someone who has passed' },
|
||||
{ value: 'expected', label: 'Plan ahead for someone who is unwell' },
|
||||
{ value: 'future', label: "Plan for a future need that isn't expected soon" },
|
||||
];
|
||||
|
||||
const PLANNING_FOR_OPTIONS: { value: PlanningFor; label: string }[] = [
|
||||
{ value: 'someone-else', label: 'Someone else' },
|
||||
{ value: 'myself', label: 'Myself' },
|
||||
];
|
||||
|
||||
const FUNERAL_TYPE_OPTIONS: { value: FuneralType; label: string }[] = [
|
||||
{ value: 'cremation-funeral', label: 'Cremation with funeral' },
|
||||
{ value: 'cremation-only', label: 'Cremation only (no funeral, no attendance)' },
|
||||
{ value: 'burial-funeral', label: 'Burial with funeral' },
|
||||
{ value: 'graveside-only', label: 'Graveside burial only' },
|
||||
{ value: 'water-cremation', label: 'Water cremation (QLD only)' },
|
||||
];
|
||||
|
||||
// ─── Step indicator ─────────────────────────────────────────────────────────
|
||||
|
||||
const INDICATOR_SIZE = 48;
|
||||
const ICON_SIZE = 20;
|
||||
|
||||
type StepState = 'inactive' | 'active' | 'completed';
|
||||
|
||||
function StepIndicator({ step, state }: { step: number; state: StepState }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: INDICATOR_SIZE,
|
||||
height: INDICATOR_SIZE,
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
transition: 'all 250ms cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
...(state === 'completed' && {
|
||||
bgcolor: 'var(--fa-color-brand-500)',
|
||||
color: 'common.white',
|
||||
boxShadow: '0 0 0 3px var(--fa-color-brand-100, #F5EDE4)',
|
||||
}),
|
||||
...(state === 'active' && {
|
||||
bgcolor: 'var(--fa-color-brand-500)',
|
||||
color: 'common.white',
|
||||
boxShadow: '0 0 0 3px var(--fa-color-brand-100, #F5EDE4)',
|
||||
}),
|
||||
...(state === 'inactive' && {
|
||||
bgcolor: 'transparent',
|
||||
border: '2px solid var(--fa-color-neutral-300, #C4C4C4)',
|
||||
color: 'var(--fa-color-neutral-400, #9E9E9E)',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{state === 'completed' ? (
|
||||
<CheckIcon
|
||||
sx={{
|
||||
fontSize: ICON_SIZE,
|
||||
animation: 'fadeScaleIn 250ms cubic-bezier(0.34, 1.56, 0.64, 1)',
|
||||
'@keyframes fadeScaleIn': {
|
||||
'0%': { opacity: 0, transform: 'scale(0.5)' },
|
||||
'100%': { opacity: 1, transform: 'scale(1)' },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography
|
||||
variant="captionSm"
|
||||
component="span"
|
||||
sx={{
|
||||
fontWeight: 700,
|
||||
fontSize: '1.125rem',
|
||||
lineHeight: 1,
|
||||
color: 'inherit',
|
||||
}}
|
||||
>
|
||||
{step}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
/** Inline error message shown below a field */
|
||||
function FieldError({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Typography
|
||||
variant="captionSm"
|
||||
role="alert"
|
||||
sx={{
|
||||
mt: 0.5,
|
||||
color: 'var(--fa-color-brand-600, #B0610F)',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Shared select styles ───────────────────────────────────────────────────
|
||||
|
||||
const selectSx: SxProps<Theme> = {
|
||||
width: '100%',
|
||||
bgcolor: 'var(--fa-color-surface-default, #fff)',
|
||||
'.MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--fa-color-neutral-200)',
|
||||
borderRadius: 'var(--fa-border-radius-md, 8px)',
|
||||
},
|
||||
'&:hover:not(.Mui-disabled) .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--fa-color-brand-400)',
|
||||
},
|
||||
'&.Mui-focused': { boxShadow: 'none' },
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--fa-color-brand-400)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.6,
|
||||
'.MuiOutlinedInput-notchedOutline': { borderStyle: 'dashed' },
|
||||
},
|
||||
'&.Mui-error .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--fa-color-brand-600, #B0610F)',
|
||||
},
|
||||
'.MuiSelect-select': {
|
||||
py: '14px',
|
||||
px: 2,
|
||||
fontSize: '0.875rem',
|
||||
minHeight: 'unset !important',
|
||||
},
|
||||
'.MuiSelect-icon': { color: 'var(--fa-color-neutral-400)' },
|
||||
};
|
||||
|
||||
const selectMenuProps = {
|
||||
PaperProps: {
|
||||
sx: {
|
||||
mt: 0.5,
|
||||
borderRadius: 'var(--fa-border-radius-md, 8px)',
|
||||
boxShadow: 'var(--fa-shadow-md)',
|
||||
minWidth: 280,
|
||||
'& .MuiMenuItem-root': {
|
||||
py: 1.5,
|
||||
px: 2,
|
||||
fontSize: '0.875rem',
|
||||
whiteSpace: 'normal',
|
||||
'&:hover': { bgcolor: 'var(--fa-color-brand-50)' },
|
||||
'&.Mui-selected': {
|
||||
bgcolor: 'var(--fa-color-surface-warm)',
|
||||
fontWeight: 600,
|
||||
'&:hover': { bgcolor: 'var(--fa-color-surface-warm)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Component ──────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* FuneralFinder V4 — compact search widget.
|
||||
*
|
||||
* Based on V2's field set with a streamlined layout:
|
||||
* - No heading/subheading — designed to sit inside a hero or card
|
||||
* - 3 numbered steps (intent, planning-for, funeral type) with refined indicators
|
||||
* - Location field is always enabled (not a numbered step)
|
||||
* - "Search" CTA
|
||||
*
|
||||
* Conditional logic:
|
||||
* - "Arrange a funeral for someone who has passed" auto-sets step 2
|
||||
* to "Someone else" and disables it.
|
||||
* - "Myself" is only available for pre-planning paths (expected / future).
|
||||
* - Steps 2 and 3 unlock sequentially; location is always available.
|
||||
*/
|
||||
export const FuneralFinderV4 = React.forwardRef<HTMLDivElement, FuneralFinderV4Props>(
|
||||
({ onSearch, loading = false, sx }, ref) => {
|
||||
// ─── State ───────────────────────────────────────────────────
|
||||
const [lookingTo, setLookingTo] = React.useState<LookingTo | ''>('');
|
||||
const [planningFor, setPlanningFor] = React.useState<PlanningFor | ''>('');
|
||||
const [funeralType, setFuneralType] = React.useState<FuneralType | ''>('');
|
||||
const [location, setLocation] = React.useState('');
|
||||
const [submitted, setSubmitted] = React.useState(false);
|
||||
|
||||
// ─── Derived ─────────────────────────────────────────────────
|
||||
const isArrangeNow = lookingTo === 'arrange-now';
|
||||
const step2Disabled = !lookingTo || isArrangeNow;
|
||||
const step3Disabled = !planningFor;
|
||||
|
||||
// Step states for indicators
|
||||
const step1State: StepState = lookingTo ? 'completed' : 'active';
|
||||
const step2State: StepState = planningFor ? 'completed' : lookingTo ? 'active' : 'inactive';
|
||||
const step3State: StepState = funeralType ? 'completed' : planningFor ? 'active' : 'inactive';
|
||||
|
||||
// Errors only show after first submit attempt
|
||||
const errs = submitted
|
||||
? {
|
||||
lookingTo: !lookingTo,
|
||||
planningFor: !planningFor,
|
||||
funeralType: !funeralType,
|
||||
location: location.trim().length < 3,
|
||||
}
|
||||
: { lookingTo: false, planningFor: false, funeralType: false, location: false };
|
||||
|
||||
// ─── Handlers ────────────────────────────────────────────────
|
||||
const handleLookingTo = (e: SelectChangeEvent<string>) => {
|
||||
const val = e.target.value as LookingTo;
|
||||
setLookingTo(val);
|
||||
if (val === 'arrange-now') {
|
||||
setPlanningFor('someone-else');
|
||||
} else {
|
||||
setPlanningFor('');
|
||||
}
|
||||
};
|
||||
|
||||
const handlePlanningFor = (e: SelectChangeEvent<string>) => {
|
||||
setPlanningFor(e.target.value as PlanningFor);
|
||||
};
|
||||
|
||||
const handleFuneralType = (e: SelectChangeEvent<string>) => {
|
||||
setFuneralType(e.target.value as FuneralType);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
setSubmitted(true);
|
||||
if (!lookingTo || !planningFor || !funeralType || location.trim().length < 3) return;
|
||||
onSearch?.({
|
||||
lookingTo,
|
||||
planningFor,
|
||||
funeralType,
|
||||
location: location.trim(),
|
||||
});
|
||||
};
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────
|
||||
const placeholder = (
|
||||
<span style={{ color: 'var(--fa-color-neutral-400)' }}>Select…</span>
|
||||
);
|
||||
|
||||
const findLabel = (opts: { value: string; label: string }[], val: string) =>
|
||||
opts.find((o) => o.value === val)?.label ?? '';
|
||||
|
||||
// ─── Render ──────────────────────────────────────────────────
|
||||
return (
|
||||
<Box
|
||||
ref={ref}
|
||||
role="search"
|
||||
aria-label="Find funeral providers"
|
||||
sx={[
|
||||
{
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 'var(--fa-card-border-radius-default, 12px)',
|
||||
boxShadow: 'var(--fa-shadow-md)',
|
||||
textAlign: 'left',
|
||||
px: { xs: 3, sm: 5 },
|
||||
py: { xs: 3, sm: 4 },
|
||||
},
|
||||
...(Array.isArray(sx) ? sx : [sx]),
|
||||
]}
|
||||
>
|
||||
{/* ── Steps ───────────────────────────────────────────── */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3.5 }}>
|
||||
{/* Step 1: I'm looking to */}
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{ fontWeight: 600, mb: 0.75, color: 'var(--fa-color-brand-700)' }}
|
||||
>
|
||||
I’m looking to…
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
||||
<StepIndicator step={1} state={step1State} />
|
||||
<Select
|
||||
value={lookingTo}
|
||||
onChange={handleLookingTo}
|
||||
displayEmpty
|
||||
error={errs.lookingTo}
|
||||
renderValue={(v) => (v ? findLabel(LOOKING_TO_OPTIONS, v) : placeholder)}
|
||||
MenuProps={selectMenuProps}
|
||||
sx={{ ...selectSx, flex: 1 } as SxProps<Theme>}
|
||||
inputProps={{ 'aria-label': "I'm looking to", 'aria-required': true }}
|
||||
>
|
||||
{LOOKING_TO_OPTIONS.map((o) => (
|
||||
<MenuItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
{errs.lookingTo && <FieldError>Please tell us what you need help with</FieldError>}
|
||||
</Box>
|
||||
|
||||
{/* Step 2: I'm planning for */}
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
mb: 0.75,
|
||||
color: lookingTo ? 'var(--fa-color-brand-700)' : 'text.disabled',
|
||||
}}
|
||||
>
|
||||
I’m planning for…
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
||||
<StepIndicator step={2} state={step2State} />
|
||||
<Select
|
||||
value={planningFor}
|
||||
onChange={handlePlanningFor}
|
||||
displayEmpty
|
||||
disabled={step2Disabled}
|
||||
error={errs.planningFor}
|
||||
renderValue={(v) => (v ? findLabel(PLANNING_FOR_OPTIONS, v) : placeholder)}
|
||||
MenuProps={selectMenuProps}
|
||||
sx={{ ...selectSx, flex: 1 } as SxProps<Theme>}
|
||||
inputProps={{ 'aria-label': "I'm planning for", 'aria-required': true }}
|
||||
>
|
||||
{PLANNING_FOR_OPTIONS.map((o) => (
|
||||
<MenuItem
|
||||
key={o.value}
|
||||
value={o.value}
|
||||
disabled={isArrangeNow && o.value === 'myself'}
|
||||
>
|
||||
{o.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
{errs.planningFor && <FieldError>Please select who you are planning for</FieldError>}
|
||||
</Box>
|
||||
|
||||
{/* Step 3: Type of funeral */}
|
||||
<Box>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
mb: 0.75,
|
||||
color: step3Disabled ? 'text.disabled' : 'var(--fa-color-brand-700)',
|
||||
}}
|
||||
>
|
||||
Type of funeral
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
||||
<StepIndicator step={3} state={step3State} />
|
||||
<Select
|
||||
value={funeralType}
|
||||
onChange={handleFuneralType}
|
||||
displayEmpty
|
||||
disabled={step3Disabled}
|
||||
error={errs.funeralType}
|
||||
renderValue={(v) => (v ? findLabel(FUNERAL_TYPE_OPTIONS, v) : placeholder)}
|
||||
MenuProps={selectMenuProps}
|
||||
sx={{ ...selectSx, flex: 1 } as SxProps<Theme>}
|
||||
inputProps={{ 'aria-label': 'Type of funeral', 'aria-required': true }}
|
||||
>
|
||||
{FUNERAL_TYPE_OPTIONS.map((o) => (
|
||||
<MenuItem key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
{errs.funeralType && <FieldError>Please select a funeral type</FieldError>}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* ── Location (not a numbered step) ─────────────────── */}
|
||||
<Box sx={{ mt: 3.5 }}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
mb: 0.75,
|
||||
color: funeralType ? 'var(--fa-color-brand-700)' : 'text.disabled',
|
||||
}}
|
||||
>
|
||||
Looking for providers in
|
||||
</Typography>
|
||||
<Input
|
||||
placeholder="Suburb or postcode"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
fullWidth
|
||||
disabled={!funeralType}
|
||||
error={errs.location}
|
||||
inputProps={{ 'aria-label': 'Suburb or postcode', 'aria-required': true }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') handleSubmit();
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-default, #fff)',
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--fa-color-neutral-200)',
|
||||
borderRadius: 'var(--fa-border-radius-md, 8px)',
|
||||
},
|
||||
'&:hover:not(.Mui-disabled) .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--fa-color-brand-400)',
|
||||
},
|
||||
'&.Mui-focused': { boxShadow: 'none' },
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--fa-color-brand-400)',
|
||||
borderWidth: '1px',
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0.6,
|
||||
'& .MuiOutlinedInput-notchedOutline': { borderStyle: 'dashed' },
|
||||
},
|
||||
'&.Mui-error .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: 'var(--fa-color-brand-600, #B0610F)',
|
||||
},
|
||||
'& .MuiOutlinedInput-input': {
|
||||
py: '14px',
|
||||
px: 2,
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{errs.location && <FieldError>Please enter a suburb or postcode</FieldError>}
|
||||
</Box>
|
||||
|
||||
{/* ── CTA ─────────────────────────────────────────────── */}
|
||||
<Divider sx={{ my: 3 }} />
|
||||
<Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
fullWidth
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Typography
|
||||
variant="captionSm"
|
||||
color="text.secondary"
|
||||
sx={{ textAlign: 'center', display: 'block', mt: 1.5 }}
|
||||
>
|
||||
Free to use · No obligation
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
FuneralFinderV4.displayName = 'FuneralFinderV4';
|
||||
export default FuneralFinderV4;
|
||||
@@ -5,7 +5,6 @@ import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import SupportAgentOutlinedIcon from '@mui/icons-material/SupportAgentOutlined';
|
||||
import { HomePage } from './HomePage';
|
||||
import type { FeaturedProvider, TrustStat } from './HomePage';
|
||||
import { Navigation } from '../../organisms/Navigation';
|
||||
import { Footer } from '../../organisms/Footer';
|
||||
|
||||
@@ -210,9 +209,8 @@ const faqItems = [
|
||||
// ─── Meta ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const meta: Meta<typeof HomePage> = {
|
||||
title: 'Pages/HomePage',
|
||||
title: 'Archive/HomePage V1',
|
||||
component: HomePage,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
@@ -265,68 +263,3 @@ export const Mobile: Story = {
|
||||
viewport: { defaultViewport: 'mobile1' },
|
||||
},
|
||||
};
|
||||
|
||||
// ─── V2 data ────────────────────────────────────────────────────────────────
|
||||
|
||||
const trustStats: TrustStat[] = [
|
||||
{ value: '1,500+', label: 'Families helped' },
|
||||
{ value: '4.9', label: 'Google Rating' },
|
||||
{ value: '300+', label: 'Funeral directors' },
|
||||
];
|
||||
|
||||
const featuredProviders: FeaturedProvider[] = [
|
||||
{
|
||||
id: 'parsons',
|
||||
name: 'H.Parsons Funeral Directors',
|
||||
location: 'Wentworth, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/E8E0D6/8B6F47?text=H.Parsons',
|
||||
logoUrl: 'https://placehold.co/64x64/FEF9F5/BA834E?text=HP',
|
||||
rating: 4.6,
|
||||
reviewCount: 7,
|
||||
startingPrice: 900,
|
||||
},
|
||||
{
|
||||
id: 'rankins',
|
||||
name: 'Rankins Funeral Services',
|
||||
location: 'Wollongong, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/D7E1E2/4C5B6B?text=Rankins',
|
||||
logoUrl: 'https://placehold.co/64x64/F2F5F6/4C5B6B?text=R',
|
||||
rating: 4.8,
|
||||
reviewCount: 23,
|
||||
startingPrice: 1200,
|
||||
},
|
||||
{
|
||||
id: 'easy-funerals',
|
||||
name: 'Easy Funerals',
|
||||
location: 'Sydney, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/F0F7F0/3B7A3B?text=Easy+Funerals',
|
||||
logoUrl: 'https://placehold.co/64x64/F0F7F0/3B7A3B?text=EF',
|
||||
rating: 4.5,
|
||||
reviewCount: 42,
|
||||
startingPrice: 850,
|
||||
},
|
||||
];
|
||||
|
||||
// ─── V2 Story ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** V2 layout — full-bleed hero, stats bar, map + provider cards, editorial testimonials */
|
||||
export const V2: Story = {
|
||||
args: {
|
||||
navigation: nav,
|
||||
footer,
|
||||
heroImageUrl: '/brandassets/images/heroes/parsonshero.png',
|
||||
stats: trustStats,
|
||||
featuredProviders,
|
||||
onSelectFeaturedProvider: (id) => console.log('Featured provider:', id),
|
||||
features,
|
||||
googleRating: 4.9,
|
||||
googleReviewCount: 2340,
|
||||
testimonials,
|
||||
faqItems,
|
||||
onSearch: (params) => console.log('Search:', params),
|
||||
onCtaClick: () => console.log('CTA clicked'),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -80,9 +80,11 @@ export interface HomePageProps {
|
||||
/** Hero background image URL (full-bleed layout — V2). Takes priority over heroImage. */
|
||||
heroImageUrl?: string;
|
||||
|
||||
/** FuneralFinder search callback */
|
||||
/** Override the default FuneralFinder widget with a custom element */
|
||||
finderSlot?: React.ReactNode;
|
||||
/** FuneralFinder search callback (used when finderSlot is not provided) */
|
||||
onSearch?: (params: FuneralFinderV3SearchParams) => void;
|
||||
/** FuneralFinder loading state */
|
||||
/** FuneralFinder loading state (used when finderSlot is not provided) */
|
||||
searchLoading?: boolean;
|
||||
|
||||
/** Trust stats bar (e.g. "1,500+ families helped") */
|
||||
@@ -174,6 +176,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
heroSubheading = "Funeral planning doesn't have to be overwhelming. Whether you're thinking ahead or arranging for a loved one, find trusted local providers with transparent pricing, all at your own pace.",
|
||||
heroImage,
|
||||
heroImageUrl,
|
||||
finderSlot,
|
||||
onSearch,
|
||||
searchLoading,
|
||||
partnerLogos = [],
|
||||
@@ -242,7 +245,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
textAlign: 'center',
|
||||
pt: { xs: 6, md: 8 },
|
||||
pt: { xs: 8, md: 11 },
|
||||
pb: 4,
|
||||
}}
|
||||
>
|
||||
@@ -251,7 +254,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
component="h1"
|
||||
id="hero-heading"
|
||||
tabIndex={-1}
|
||||
sx={{ mb: 2, color: 'var(--fa-color-white)' }}
|
||||
sx={{ mb: 3, color: 'var(--fa-color-white)' }}
|
||||
>
|
||||
{heroHeading}
|
||||
</Typography>
|
||||
@@ -269,17 +272,19 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
position: 'relative',
|
||||
zIndex: 2,
|
||||
px: 2,
|
||||
pt: 2,
|
||||
pb: 0,
|
||||
mb: { xs: -14, md: -18 },
|
||||
}}
|
||||
>
|
||||
<Box sx={{ maxWidth: 520, mx: 'auto' }}>
|
||||
<FuneralFinderV3
|
||||
heading="Find funeral providers near you"
|
||||
subheading="Tell us a little about what you're looking for and we'll show you options in your area."
|
||||
onSearch={onSearch}
|
||||
loading={searchLoading}
|
||||
/>
|
||||
<Box sx={{ maxWidth: finderSlot ? 800 : 520, mx: 'auto' }}>
|
||||
{finderSlot || (
|
||||
<FuneralFinderV3
|
||||
heading="Find your local providers"
|
||||
onSearch={onSearch}
|
||||
loading={searchLoading}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -362,12 +367,13 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
}}
|
||||
>
|
||||
<Box sx={{ maxWidth: 620, mx: 'auto' }}>
|
||||
<FuneralFinderV3
|
||||
heading="Find funeral providers near you"
|
||||
subheading="Tell us a little about what you're looking for and we'll show you options in your area."
|
||||
onSearch={onSearch}
|
||||
loading={searchLoading}
|
||||
/>
|
||||
{finderSlot || (
|
||||
<FuneralFinderV3
|
||||
heading="Find your local providers"
|
||||
onSearch={onSearch}
|
||||
loading={searchLoading}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
@@ -381,17 +387,17 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
aria-labelledby="discover-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-subtle)',
|
||||
pt: { xs: 20, md: 24 },
|
||||
pb: { xs: 6, md: 10 },
|
||||
pt: { xs: 22, md: 28 },
|
||||
pb: { xs: 8, md: 12 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ textAlign: 'center', mb: { xs: 4, md: 6 } }}>
|
||||
<Box sx={{ textAlign: 'center', mb: { xs: 5, md: 8 } }}>
|
||||
<Typography
|
||||
variant="display3"
|
||||
component="h2"
|
||||
id="discover-heading"
|
||||
sx={{ mb: 1.5, color: 'text.primary' }}
|
||||
sx={{ mb: 2, color: 'text.primary' }}
|
||||
>
|
||||
See what you'll discover
|
||||
</Typography>
|
||||
@@ -413,24 +419,38 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
alignItems: 'stretch',
|
||||
}}
|
||||
>
|
||||
{/* Map placeholder — stretches to match card stack */}
|
||||
{/* Map — fills the grid cell to match card stack height */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
minHeight: { xs: 240, md: 0 },
|
||||
bgcolor: 'var(--fa-color-surface-cool)',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
'& > img, & > div': {
|
||||
position: { md: 'absolute' },
|
||||
inset: { md: 0 },
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{discoverMapSlot || (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Map coming soon
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Map coming soon
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -473,15 +493,15 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
aria-label="Trusted partners"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-cool)',
|
||||
pt: { xs: 8, md: 10 },
|
||||
pb: { xs: 6, md: 8 },
|
||||
pt: { xs: 10, md: 13 },
|
||||
pb: { xs: 8, md: 10 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Typography
|
||||
variant="body1"
|
||||
color="text.secondary"
|
||||
sx={{ textAlign: 'center', mb: { xs: 3, md: 4 } }}
|
||||
sx={{ textAlign: 'center', mb: { xs: 4, md: 6 } }}
|
||||
>
|
||||
{partnerTrustLine}
|
||||
</Typography>
|
||||
@@ -518,7 +538,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
aria-label="Partner funeral directors"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: { xs: 6, md: 8 },
|
||||
gap: { xs: 8, md: 12 },
|
||||
alignItems: 'center',
|
||||
width: 'max-content',
|
||||
animation: 'logoScroll 35s linear infinite',
|
||||
@@ -538,13 +558,12 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
alt={i < partnerLogos.length ? logo.alt : ''}
|
||||
aria-hidden={i >= partnerLogos.length ? true : undefined}
|
||||
sx={{
|
||||
height: { xs: 48, md: 64 },
|
||||
height: { xs: 46, md: 55 },
|
||||
maxWidth: { xs: 140, md: 184 },
|
||||
width: 'auto',
|
||||
objectFit: 'contain',
|
||||
filter: 'grayscale(100%)',
|
||||
opacity: 0.5,
|
||||
transition: 'opacity 0.2s, filter 0.2s',
|
||||
'&:hover': { filter: 'grayscale(0%)', opacity: 1 },
|
||||
filter: 'grayscale(100%) brightness(1.2)',
|
||||
opacity: 0.4,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
@@ -563,16 +582,16 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
aria-labelledby="features-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-default)',
|
||||
py: { xs: 6, md: 10 },
|
||||
py: { xs: 8, md: 12 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ textAlign: 'center', mb: { xs: 4, md: 6 } }}>
|
||||
<Box sx={{ textAlign: 'center', mb: { xs: 5, md: 8 } }}>
|
||||
<Typography
|
||||
variant="display3"
|
||||
component="h2"
|
||||
id="features-heading"
|
||||
sx={{ mb: 2, color: 'text.primary' }}
|
||||
sx={{ mb: 2.5, color: 'text.primary' }}
|
||||
>
|
||||
{featuresHeading}
|
||||
</Typography>
|
||||
@@ -628,7 +647,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
component="section"
|
||||
aria-labelledby="reviews-heading"
|
||||
sx={{
|
||||
py: { xs: 6, md: 10 },
|
||||
py: { xs: 8, md: 12 },
|
||||
bgcolor: 'var(--fa-color-surface-subtle)',
|
||||
}}
|
||||
>
|
||||
@@ -728,20 +747,21 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
component="section"
|
||||
aria-labelledby="cta-heading"
|
||||
sx={{
|
||||
py: { xs: 6, md: 8 },
|
||||
background:
|
||||
'linear-gradient(180deg, var(--fa-color-brand-100, #F5EDE4) 0%, var(--fa-color-surface-warm, #FEF9F5) 100%)',
|
||||
py: { xs: 8, md: 10 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="sm" sx={{ textAlign: 'center' }}>
|
||||
<Divider sx={{ mb: { xs: 5, md: 6 } }} />
|
||||
<Container maxWidth="md" sx={{ textAlign: 'center' }}>
|
||||
<Typography
|
||||
variant="display3"
|
||||
component="h2"
|
||||
id="cta-heading"
|
||||
sx={{ mb: 3, color: 'text.primary' }}
|
||||
sx={{ mb: 3, color: 'text.primary', whiteSpace: { md: 'nowrap' } }}
|
||||
>
|
||||
{ctaHeading}
|
||||
</Typography>
|
||||
<Button variant="contained" size="large" onClick={onCtaClick}>
|
||||
<Button variant="text" size="large" onClick={onCtaClick}>
|
||||
{ctaButtonLabel}
|
||||
</Button>
|
||||
</Container>
|
||||
@@ -756,7 +776,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
aria-labelledby="faq-heading"
|
||||
sx={{
|
||||
bgcolor: 'var(--fa-color-surface-default)',
|
||||
py: { xs: 6, md: 10 },
|
||||
py: { xs: 8, md: 12 },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="lg">
|
||||
@@ -764,7 +784,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
||||
variant="h2"
|
||||
component="h2"
|
||||
id="faq-heading"
|
||||
sx={{ textAlign: 'center', mb: { xs: 4, md: 6 }, color: 'text.primary' }}
|
||||
sx={{ textAlign: 'center', mb: { xs: 5, md: 8 }, color: 'text.primary' }}
|
||||
>
|
||||
FAQ
|
||||
</Typography>
|
||||
|
||||
246
src/components/pages/HomePage/HomePageV2.stories.tsx
Normal file
246
src/components/pages/HomePage/HomePageV2.stories.tsx
Normal file
@@ -0,0 +1,246 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Box from '@mui/material/Box';
|
||||
import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import SupportAgentOutlinedIcon from '@mui/icons-material/SupportAgentOutlined';
|
||||
import { HomePage } from './HomePage';
|
||||
import type { FeaturedProvider, TrustStat } from './HomePage';
|
||||
import { Navigation } from '../../organisms/Navigation';
|
||||
import { Footer } from '../../organisms/Footer';
|
||||
|
||||
// ─── Shared 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 FALogoInverse = () => (
|
||||
<Box
|
||||
component="img"
|
||||
src="/brandlogo/logo-full.svg"
|
||||
alt="Funeral Arranger"
|
||||
sx={{ height: 24, filter: 'brightness(0) invert(1)', opacity: 0.9 }}
|
||||
/>
|
||||
);
|
||||
|
||||
const nav = (
|
||||
<Navigation
|
||||
logo={<FALogo />}
|
||||
items={[
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
{ label: 'Log in', href: '/login' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<Footer
|
||||
logo={<FALogoInverse />}
|
||||
tagline="Helping Australian families plan with confidence"
|
||||
linkGroups={[
|
||||
{
|
||||
heading: 'Services',
|
||||
links: [
|
||||
{ label: 'Find a Director', href: '/directors' },
|
||||
{ label: 'Compare Venues', href: '/venues' },
|
||||
{ label: 'Pricing Guide', href: '/pricing' },
|
||||
{ label: 'Start Planning', href: '/arrange' },
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Support',
|
||||
links: [
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
{ label: 'Grief Resources', href: '/resources' },
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Company',
|
||||
links: [
|
||||
{ label: 'About Us', href: '/about' },
|
||||
{ label: 'Provider Portal', href: '/provider-portal' },
|
||||
{ label: 'Partner With Us', href: '/partners' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
phone="1800 987 888"
|
||||
email="support@funeralarranger.com.au"
|
||||
legalLinks={[
|
||||
{ label: 'Privacy Policy', href: '/privacy' },
|
||||
{ label: 'Terms of Service', href: '/terms' },
|
||||
{ label: 'Accessibility', href: '/accessibility' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
// ─── Data ───────────────────────────────────────────────────────────────────
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <VerifiedOutlinedIcon />,
|
||||
heading: 'Transparent, verified pricing',
|
||||
description:
|
||||
'All costs are itemised for verified partners. No surprise fees. See pricing and options before you commit.',
|
||||
},
|
||||
{
|
||||
icon: <AccessTimeIcon />,
|
||||
heading: 'Available 24 hours a day',
|
||||
description:
|
||||
'Compare, plan and arrange at your own pace, day or night. No pressure to commit online.',
|
||||
},
|
||||
{
|
||||
icon: <SearchIcon />,
|
||||
heading: 'Find local & compare',
|
||||
description: 'Search and compare local funeral directors to find the right choice for you.',
|
||||
},
|
||||
{
|
||||
icon: <SupportAgentOutlinedIcon />,
|
||||
heading: 'Support when you need it',
|
||||
description:
|
||||
'Arrange everything online or be guided through the steps by your preferred funeral director.',
|
||||
},
|
||||
];
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
name: 'Sarah H.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'At the most difficult time in our lives, this site made comparing costs so straightforward. We saved over $800.',
|
||||
timeAgo: '3 weeks ago',
|
||||
},
|
||||
{
|
||||
name: 'James M.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'The itemised quote builder meant we could personalise the service within our budget. Highly recommended.',
|
||||
timeAgo: '1 month ago',
|
||||
},
|
||||
{
|
||||
name: 'Tracy W.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'I had no idea there was such a price difference between local directors. This saved us from overpaying.',
|
||||
timeAgo: '2 months ago',
|
||||
},
|
||||
];
|
||||
|
||||
const faqItems = [
|
||||
{
|
||||
question: 'What is Funeral Arranger?',
|
||||
answer:
|
||||
'Funeral Arranger is an online platform that helps Australian families find, compare and arrange funeral services. We connect you with trusted local funeral directors and provide transparent pricing so you can make informed decisions during a difficult time.',
|
||||
},
|
||||
{
|
||||
question: 'What makes Funeral Arranger different from other funeral service providers?',
|
||||
answer:
|
||||
'Unlike traditional funeral homes, we are an independent comparison platform. We show you transparent, itemised pricing from multiple verified providers in your area so you can compare options and choose what is right for your family and budget.',
|
||||
},
|
||||
{
|
||||
question: 'Do I need to complete all steps at once?',
|
||||
answer:
|
||||
'No. You can save your progress at any time and return when you are ready. Whether you are pre-planning or arranging at short notice, the process works at your pace with no time pressure.',
|
||||
},
|
||||
{
|
||||
question: 'How much does a funeral cost in Australia?',
|
||||
answer:
|
||||
'Funeral costs in Australia typically range from $4,000 for a simple cremation to $15,000 or more for a full traditional service. Costs vary by location, provider, and the options you choose. Our platform helps you compare real prices from local providers.',
|
||||
},
|
||||
{
|
||||
question: 'What is the cheapest funeral option?',
|
||||
answer:
|
||||
'A direct cremation (no service) is generally the most affordable option, starting from around $2,000\u2013$4,000 depending on your location. Our platform shows you all available options so you can find the right balance of service and cost.',
|
||||
},
|
||||
];
|
||||
|
||||
const trustStats: TrustStat[] = [
|
||||
{ value: '1,500+', label: 'Families helped' },
|
||||
{ value: '4.9', label: 'Google Rating' },
|
||||
{ value: '300+', label: 'Funeral directors' },
|
||||
];
|
||||
|
||||
const featuredProviders: FeaturedProvider[] = [
|
||||
{
|
||||
id: 'parsons',
|
||||
name: 'H.Parsons Funeral Directors',
|
||||
location: 'Wentworth, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/E8E0D6/8B6F47?text=H.Parsons',
|
||||
logoUrl: 'https://placehold.co/64x64/FEF9F5/BA834E?text=HP',
|
||||
rating: 4.6,
|
||||
reviewCount: 7,
|
||||
startingPrice: 900,
|
||||
},
|
||||
{
|
||||
id: 'rankins',
|
||||
name: 'Rankins Funeral Services',
|
||||
location: 'Wollongong, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/D7E1E2/4C5B6B?text=Rankins',
|
||||
logoUrl: 'https://placehold.co/64x64/F2F5F6/4C5B6B?text=R',
|
||||
rating: 4.8,
|
||||
reviewCount: 23,
|
||||
startingPrice: 1200,
|
||||
},
|
||||
{
|
||||
id: 'easy-funerals',
|
||||
name: 'Easy Funerals',
|
||||
location: 'Sydney, NSW',
|
||||
verified: true,
|
||||
imageUrl: 'https://placehold.co/600x200/F0F7F0/3B7A3B?text=Easy+Funerals',
|
||||
logoUrl: 'https://placehold.co/64x64/F0F7F0/3B7A3B?text=EF',
|
||||
rating: 4.5,
|
||||
reviewCount: 42,
|
||||
startingPrice: 850,
|
||||
},
|
||||
];
|
||||
|
||||
// ─── Meta ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const meta: Meta<typeof HomePage> = {
|
||||
title: 'Archive/HomePage V2',
|
||||
component: HomePage,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof HomePage>;
|
||||
|
||||
// ─── Stories ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** V2 layout — full-bleed hero, stats bar, map + provider cards, editorial testimonials */
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
navigation: nav,
|
||||
footer,
|
||||
heroImageUrl: '/brandassets/images/heroes/parsonshero.png',
|
||||
stats: trustStats,
|
||||
featuredProviders,
|
||||
onSelectFeaturedProvider: (id) => console.log('Featured provider:', id),
|
||||
features,
|
||||
googleRating: 4.9,
|
||||
googleReviewCount: 2340,
|
||||
testimonials,
|
||||
faqItems,
|
||||
onSearch: (params) => console.log('Search:', params),
|
||||
onCtaClick: () => console.log('CTA clicked'),
|
||||
},
|
||||
};
|
||||
281
src/components/pages/HomePage/HomePageV3.stories.tsx
Normal file
281
src/components/pages/HomePage/HomePageV3.stories.tsx
Normal file
@@ -0,0 +1,281 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Box from '@mui/material/Box';
|
||||
import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import SupportAgentOutlinedIcon from '@mui/icons-material/SupportAgentOutlined';
|
||||
import { HomePage } from './HomePage';
|
||||
import type { FeaturedProvider, TrustStat, PartnerLogo } from './HomePage';
|
||||
import React from 'react';
|
||||
import { Navigation } from '../../organisms/Navigation';
|
||||
import { Footer } from '../../organisms/Footer';
|
||||
|
||||
// ─── Shared 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 FALogoFooter = () => (
|
||||
<Box component="img" src="/brandlogo/logo-full.svg" alt="Funeral Arranger" sx={{ height: 24 }} />
|
||||
);
|
||||
|
||||
const nav = (
|
||||
<Navigation
|
||||
logo={<FALogo />}
|
||||
items={[
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
{ label: 'Log in', href: '/login' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<Footer
|
||||
logo={<FALogoFooter />}
|
||||
tagline="Helping Australian families plan with confidence"
|
||||
linkGroups={[
|
||||
{
|
||||
heading: 'Services',
|
||||
links: [
|
||||
{ label: 'Find a Director', href: '/directors' },
|
||||
{ label: 'Compare Venues', href: '/venues' },
|
||||
{ label: 'Pricing Guide', href: '/pricing' },
|
||||
{ label: 'Start Planning', href: '/arrange' },
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Support',
|
||||
links: [
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
{ label: 'Grief Resources', href: '/resources' },
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Company',
|
||||
links: [
|
||||
{ label: 'About Us', href: '/about' },
|
||||
{ label: 'Provider Portal', href: '/provider-portal' },
|
||||
{ label: 'Partner With Us', href: '/partners' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
phone="1800 987 888"
|
||||
email="support@funeralarranger.com.au"
|
||||
legalLinks={[
|
||||
{ label: 'Privacy Policy', href: '/privacy' },
|
||||
{ label: 'Terms of Service', href: '/terms' },
|
||||
{ label: 'Accessibility', href: '/accessibility' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
// ─── Data ───────────────────────────────────────────────────────────────────
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <VerifiedOutlinedIcon />,
|
||||
heading: 'Transparent, verified pricing',
|
||||
description:
|
||||
'All costs are itemised for verified partners. No surprise fees. See pricing and options before you commit.',
|
||||
},
|
||||
{
|
||||
icon: <AccessTimeIcon />,
|
||||
heading: 'Available 24 hours a day',
|
||||
description:
|
||||
'Compare, plan and arrange at your own pace, day or night. No pressure to commit online.',
|
||||
},
|
||||
{
|
||||
icon: <SearchIcon />,
|
||||
heading: 'Find local & compare',
|
||||
description: 'Search and compare local funeral directors to find the right choice for you.',
|
||||
},
|
||||
{
|
||||
icon: <SupportAgentOutlinedIcon />,
|
||||
heading: 'Support when you need it',
|
||||
description:
|
||||
'Arrange everything online or be guided through the steps by your preferred funeral director.',
|
||||
},
|
||||
];
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
name: 'Sarah H.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'At the most difficult time in our lives, this site made comparing costs so straightforward. We saved over $800.',
|
||||
timeAgo: '3 weeks ago',
|
||||
},
|
||||
{
|
||||
name: 'James M.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'The itemised quote builder meant we could personalise the service within our budget. Highly recommended.',
|
||||
timeAgo: '1 month ago',
|
||||
},
|
||||
{
|
||||
name: 'Tracy W.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'I had no idea there was such a price difference between local directors. This saved us from overpaying.',
|
||||
timeAgo: '2 months ago',
|
||||
},
|
||||
];
|
||||
|
||||
const faqItems = [
|
||||
{
|
||||
question: 'What is Funeral Arranger?',
|
||||
answer:
|
||||
'Funeral Arranger is an online platform that helps Australian families find, compare and arrange funeral services. We connect you with trusted local funeral directors and provide transparent pricing so you can make informed decisions during a difficult time.',
|
||||
},
|
||||
{
|
||||
question: 'What makes Funeral Arranger different from other funeral service providers?',
|
||||
answer:
|
||||
'Unlike traditional funeral homes, we are an independent comparison platform. We show you transparent, itemised pricing from multiple verified providers in your area so you can compare options and choose what is right for your family and budget.',
|
||||
},
|
||||
{
|
||||
question: 'Do I need to complete all steps at once?',
|
||||
answer:
|
||||
'No. You can save your progress at any time and return when you are ready. Whether you are pre-planning or arranging at short notice, the process works at your pace with no time pressure.',
|
||||
},
|
||||
{
|
||||
question: 'How much does a funeral cost in Australia?',
|
||||
answer:
|
||||
'Funeral costs in Australia typically range from $4,000 for a simple cremation to $15,000 or more for a full traditional service. Costs vary by location, provider, and the options you choose. Our platform helps you compare real prices from local providers.',
|
||||
},
|
||||
{
|
||||
question: 'What is the cheapest funeral option?',
|
||||
answer:
|
||||
'A direct cremation (no service) is generally the most affordable option, starting from around $2,000\u2013$4,000 depending on your location. Our platform shows you all available options so you can find the right balance of service and cost.',
|
||||
},
|
||||
];
|
||||
|
||||
const trustStats: TrustStat[] = [
|
||||
{ value: '1,500+', label: 'Families helped' },
|
||||
{ value: '4.9', label: 'Google Rating' },
|
||||
{ value: '300+', label: 'Funeral directors' },
|
||||
];
|
||||
|
||||
const featuredProviders: FeaturedProvider[] = [
|
||||
{
|
||||
id: 'parsons',
|
||||
name: 'H.Parsons Funeral Directors',
|
||||
location: 'Wollongong, NSW',
|
||||
verified: true,
|
||||
imageUrl: '/brandassets/images/venues/hparsons-funeral-home-kiama/01.jpg',
|
||||
logoUrl: '/brandassets/images/providers/hparsons-funeral-directors/logo.png',
|
||||
rating: 4.6,
|
||||
reviewCount: 7,
|
||||
startingPrice: 900,
|
||||
},
|
||||
{
|
||||
id: 'rankins',
|
||||
name: 'Rankins Funerals',
|
||||
location: 'Wollongong, NSW',
|
||||
verified: true,
|
||||
imageUrl: '/brandassets/images/venues/rankins-funeral-home-warrawong/01.jpg',
|
||||
logoUrl: '/brandassets/images/providers/rankins-funerals/logo.png',
|
||||
rating: 4.8,
|
||||
reviewCount: 23,
|
||||
startingPrice: 1200,
|
||||
},
|
||||
{
|
||||
id: 'easy-funerals',
|
||||
name: 'Easy Funerals',
|
||||
location: 'Sydney, NSW',
|
||||
verified: true,
|
||||
imageUrl: '/brandassets/images/venues/lakeside-memorial-park-chapel/01.jpg',
|
||||
logoUrl: '/brandassets/images/providers/easy-funerals/logo.png',
|
||||
rating: 4.5,
|
||||
reviewCount: 42,
|
||||
startingPrice: 850,
|
||||
},
|
||||
];
|
||||
|
||||
const partnerLogos: PartnerLogo[] = [
|
||||
{
|
||||
src: '/brandassets/images/providers/hparsons-funeral-directors/logo.png',
|
||||
alt: 'H.Parsons Funeral Directors',
|
||||
},
|
||||
{ src: '/brandassets/images/providers/rankins-funerals/logo.png', alt: 'Rankins Funerals' },
|
||||
{ src: '/brandassets/images/providers/easy-funerals/logo.png', alt: 'Easy Funerals' },
|
||||
{ src: '/brandassets/images/providers/lady-anne-funerals/logo.png', alt: 'Lady Anne Funerals' },
|
||||
{
|
||||
src: '/brandassets/images/providers/killick-family-funerals/logo.png',
|
||||
alt: 'Killick Family Funerals',
|
||||
},
|
||||
{
|
||||
src: '/brandassets/images/providers/kenneallys-funerals/logo.png',
|
||||
alt: "Kenneally's Funerals",
|
||||
},
|
||||
{
|
||||
src: '/brandassets/images/providers/wollongong-city-funerals/logo.png',
|
||||
alt: 'Wollongong City Funerals',
|
||||
},
|
||||
{
|
||||
src: '/brandassets/images/providers/hparsons-funeral-directors-shoalhaven/logo.png',
|
||||
alt: 'H.Parsons Shoalhaven',
|
||||
},
|
||||
{
|
||||
src: '/brandassets/images/providers/mackay-family-funerals/logo.webp',
|
||||
alt: 'Mackay Family Funerals',
|
||||
},
|
||||
];
|
||||
|
||||
// ─── Meta ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const meta: Meta<typeof HomePage> = {
|
||||
title: 'Archive/HomePage V3',
|
||||
component: HomePage,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof HomePage>;
|
||||
|
||||
// ─── Stories ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** V3 layout — hero-3 background, updated copy, bullet-point subheading */
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
navigation: nav,
|
||||
footer,
|
||||
heroImageUrl: '/brandassets/images/heroes/hero-3.png',
|
||||
heroHeading: 'Compare funeral directors pricing near you and arrange with confidence',
|
||||
heroSubheading: 'Transparent pricing \u00B7 No hidden fees \u00B7 Arrange 24/7',
|
||||
stats: trustStats,
|
||||
featuredProviders,
|
||||
discoverMapSlot: React.createElement('img', {
|
||||
src: '/brandassets/images/placeholder/map.png',
|
||||
alt: 'Map showing provider locations',
|
||||
style: { width: '100%', height: '100%', objectFit: 'cover' },
|
||||
}),
|
||||
onSelectFeaturedProvider: (id) => console.log('Featured provider:', id),
|
||||
partnerLogos,
|
||||
partnerTrustLine: 'Trusted by hundreds of verified funeral directors across Australia',
|
||||
features,
|
||||
googleRating: 4.9,
|
||||
googleReviewCount: 2340,
|
||||
testimonials,
|
||||
faqItems,
|
||||
onSearch: (params) => console.log('Search:', params),
|
||||
onCtaClick: () => console.log('CTA clicked'),
|
||||
},
|
||||
};
|
||||
285
src/components/pages/HomePage/HomePageV4.stories.tsx
Normal file
285
src/components/pages/HomePage/HomePageV4.stories.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import Box from '@mui/material/Box';
|
||||
import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
|
||||
import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import SupportAgentOutlinedIcon from '@mui/icons-material/SupportAgentOutlined';
|
||||
import { HomePage } from './HomePage';
|
||||
import type { FeaturedProvider, TrustStat, PartnerLogo } from './HomePage';
|
||||
import { FuneralFinderV4 } from '../../organisms/FuneralFinder/FuneralFinderV4';
|
||||
import React from 'react';
|
||||
import { Navigation } from '../../organisms/Navigation';
|
||||
import { Footer } from '../../organisms/Footer';
|
||||
|
||||
// ─── Shared 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 FALogoFooter = () => (
|
||||
<Box component="img" src="/brandlogo/logo-full.svg" alt="Funeral Arranger" sx={{ height: 24 }} />
|
||||
);
|
||||
|
||||
const nav = (
|
||||
<Navigation
|
||||
logo={<FALogo />}
|
||||
items={[
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
{ label: 'Log in', href: '/login' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<Footer
|
||||
logo={<FALogoFooter />}
|
||||
tagline="Helping Australian families plan with confidence"
|
||||
linkGroups={[
|
||||
{
|
||||
heading: 'Services',
|
||||
links: [
|
||||
{ label: 'Find a Director', href: '/directors' },
|
||||
{ label: 'Compare Venues', href: '/venues' },
|
||||
{ label: 'Pricing Guide', href: '/pricing' },
|
||||
{ label: 'Start Planning', href: '/arrange' },
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Support',
|
||||
links: [
|
||||
{ label: 'FAQ', href: '/faq' },
|
||||
{ label: 'Contact Us', href: '/contact' },
|
||||
{ label: 'Grief Resources', href: '/resources' },
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: 'Company',
|
||||
links: [
|
||||
{ label: 'About Us', href: '/about' },
|
||||
{ label: 'Provider Portal', href: '/provider-portal' },
|
||||
{ label: 'Partner With Us', href: '/partners' },
|
||||
],
|
||||
},
|
||||
]}
|
||||
phone="1800 987 888"
|
||||
email="support@funeralarranger.com.au"
|
||||
legalLinks={[
|
||||
{ label: 'Privacy Policy', href: '/privacy' },
|
||||
{ label: 'Terms of Service', href: '/terms' },
|
||||
{ label: 'Accessibility', href: '/accessibility' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
// ─── Data ───────────────────────────────────────────────────────────────────
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: <VerifiedOutlinedIcon />,
|
||||
heading: 'Transparent, verified pricing',
|
||||
description:
|
||||
'All costs are itemised for verified partners. No surprise fees. See pricing and options before you commit.',
|
||||
},
|
||||
{
|
||||
icon: <AccessTimeIcon />,
|
||||
heading: 'Available 24 hours a day',
|
||||
description:
|
||||
'Compare, plan and arrange at your own pace, day or night. No pressure to commit online.',
|
||||
},
|
||||
{
|
||||
icon: <SearchIcon />,
|
||||
heading: 'Find local & compare',
|
||||
description: 'Search and compare local funeral directors to find the right choice for you.',
|
||||
},
|
||||
{
|
||||
icon: <SupportAgentOutlinedIcon />,
|
||||
heading: 'Support when you need it',
|
||||
description:
|
||||
'Arrange everything online or be guided through the steps by your preferred funeral director.',
|
||||
},
|
||||
];
|
||||
|
||||
const testimonials = [
|
||||
{
|
||||
name: 'Sarah H.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'At the most difficult time in our lives, this site made comparing costs so straightforward. We saved over $800.',
|
||||
timeAgo: '3 weeks ago',
|
||||
},
|
||||
{
|
||||
name: 'James M.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'The itemised quote builder meant we could personalise the service within our budget. Highly recommended.',
|
||||
timeAgo: '1 month ago',
|
||||
},
|
||||
{
|
||||
name: 'Tracy W.',
|
||||
rating: 5,
|
||||
quote:
|
||||
'I had no idea there was such a price difference between local directors. This saved us from overpaying.',
|
||||
timeAgo: '2 months ago',
|
||||
},
|
||||
];
|
||||
|
||||
const faqItems = [
|
||||
{
|
||||
question: 'What is Funeral Arranger?',
|
||||
answer:
|
||||
'Funeral Arranger is an online platform that helps Australian families find, compare and arrange funeral services. We connect you with trusted local funeral directors and provide transparent pricing so you can make informed decisions during a difficult time.',
|
||||
},
|
||||
{
|
||||
question: 'What makes Funeral Arranger different from other funeral service providers?',
|
||||
answer:
|
||||
'Unlike traditional funeral homes, we are an independent comparison platform. We show you transparent, itemised pricing from multiple verified providers in your area so you can compare options and choose what is right for your family and budget.',
|
||||
},
|
||||
{
|
||||
question: 'Do I need to complete all steps at once?',
|
||||
answer:
|
||||
'No. You can save your progress at any time and return when you are ready. Whether you are pre-planning or arranging at short notice, the process works at your pace with no time pressure.',
|
||||
},
|
||||
{
|
||||
question: 'How much does a funeral cost in Australia?',
|
||||
answer:
|
||||
'Funeral costs in Australia typically range from $4,000 for a simple cremation to $15,000 or more for a full traditional service. Costs vary by location, provider, and the options you choose. Our platform helps you compare real prices from local providers.',
|
||||
},
|
||||
{
|
||||
question: 'What is the cheapest funeral option?',
|
||||
answer:
|
||||
'A direct cremation (no service) is generally the most affordable option, starting from around $2,000\u2013$4,000 depending on your location. Our platform shows you all available options so you can find the right balance of service and cost.',
|
||||
},
|
||||
];
|
||||
|
||||
const trustStats: TrustStat[] = [
|
||||
{ value: '1,500+', label: 'Families helped' },
|
||||
{ value: '4.9', label: 'Google Rating' },
|
||||
{ value: '300+', label: 'Funeral directors' },
|
||||
];
|
||||
|
||||
const featuredProviders: FeaturedProvider[] = [
|
||||
{
|
||||
id: 'parsons',
|
||||
name: 'H.Parsons Funeral Directors',
|
||||
location: 'Wollongong, NSW',
|
||||
verified: true,
|
||||
imageUrl: '/brandassets/images/venues/hparsons-funeral-home-kiama/01.jpg',
|
||||
logoUrl: '/brandassets/images/providers/hparsons-funeral-directors/logo.png',
|
||||
rating: 4.6,
|
||||
reviewCount: 7,
|
||||
startingPrice: 900,
|
||||
},
|
||||
{
|
||||
id: 'rankins',
|
||||
name: 'Rankins Funerals',
|
||||
location: 'Wollongong, NSW',
|
||||
verified: true,
|
||||
imageUrl: '/brandassets/images/venues/rankins-funeral-home-warrawong/01.jpg',
|
||||
logoUrl: '/brandassets/images/providers/rankins-funerals/logo.png',
|
||||
rating: 4.8,
|
||||
reviewCount: 23,
|
||||
startingPrice: 1200,
|
||||
},
|
||||
{
|
||||
id: 'easy-funerals',
|
||||
name: 'Easy Funerals',
|
||||
location: 'Sydney, NSW',
|
||||
verified: true,
|
||||
imageUrl: '/brandassets/images/venues/lakeside-memorial-park-chapel/01.jpg',
|
||||
logoUrl: '/brandassets/images/providers/easy-funerals/logo.png',
|
||||
rating: 4.5,
|
||||
reviewCount: 42,
|
||||
startingPrice: 850,
|
||||
},
|
||||
];
|
||||
|
||||
const partnerLogos: PartnerLogo[] = [
|
||||
{
|
||||
src: '/brandassets/images/providers/hparsons-funeral-directors/logo.png',
|
||||
alt: 'H.Parsons Funeral Directors',
|
||||
},
|
||||
{ src: '/brandassets/images/providers/rankins-funerals/logo.png', alt: 'Rankins Funerals' },
|
||||
{ src: '/brandassets/images/providers/easy-funerals/logo.png', alt: 'Easy Funerals' },
|
||||
{ src: '/brandassets/images/providers/lady-anne-funerals/logo.png', alt: 'Lady Anne Funerals' },
|
||||
{
|
||||
src: '/brandassets/images/providers/killick-family-funerals/logo.png',
|
||||
alt: 'Killick Family Funerals',
|
||||
},
|
||||
{
|
||||
src: '/brandassets/images/providers/kenneallys-funerals/logo.png',
|
||||
alt: "Kenneally's Funerals",
|
||||
},
|
||||
{
|
||||
src: '/brandassets/images/providers/wollongong-city-funerals/logo.png',
|
||||
alt: 'Wollongong City Funerals',
|
||||
},
|
||||
{
|
||||
src: '/brandassets/images/providers/hparsons-funeral-directors-shoalhaven/logo.png',
|
||||
alt: 'H.Parsons Shoalhaven',
|
||||
},
|
||||
{
|
||||
src: '/brandassets/images/providers/mackay-family-funerals/logo.webp',
|
||||
alt: 'Mackay Family Funerals',
|
||||
},
|
||||
];
|
||||
|
||||
// ─── Meta ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const meta: Meta<typeof HomePage> = {
|
||||
title: 'Archive/HomePage V4',
|
||||
component: HomePage,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof HomePage>;
|
||||
|
||||
// ─── Stories ────────────────────────────────────────────────────────────────
|
||||
|
||||
/** V4 layout — uses FuneralFinder V4 (stepped form with numbered indicators) */
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
navigation: nav,
|
||||
footer,
|
||||
heroImageUrl: '/brandassets/images/heroes/hero-3.png',
|
||||
heroHeading: 'Compare funeral directors pricing near you and arrange with confidence',
|
||||
heroSubheading: 'Transparent pricing \u00B7 No hidden fees \u00B7 Arrange 24/7',
|
||||
finderSlot: React.createElement(FuneralFinderV4, {
|
||||
onSearch: (params) => console.log('Search:', params),
|
||||
}),
|
||||
stats: trustStats,
|
||||
featuredProviders,
|
||||
discoverMapSlot: React.createElement('img', {
|
||||
src: '/brandassets/images/placeholder/map.png',
|
||||
alt: 'Map showing provider locations',
|
||||
style: { width: '100%', height: '100%', objectFit: 'cover' },
|
||||
}),
|
||||
onSelectFeaturedProvider: (id) => console.log('Featured provider:', id),
|
||||
partnerLogos,
|
||||
partnerTrustLine: 'Trusted by hundreds of verified funeral directors across Australia',
|
||||
features,
|
||||
googleRating: 4.9,
|
||||
googleReviewCount: 2340,
|
||||
testimonials,
|
||||
faqItems,
|
||||
onSearch: (params) => console.log('Search:', params),
|
||||
onCtaClick: () => console.log('CTA clicked'),
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user