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:
2026-04-01 14:02:52 +11:00
parent aaa4f80a05
commit eb6cf6a185
12 changed files with 1521 additions and 138 deletions

View File

@@ -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}

View File

@@ -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"

View File

@@ -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>
),
],
};

View 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&hellip;</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&rsquo;m looking to&hellip;
</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&rsquo;m planning for&hellip;
</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 &middot; No obligation
</Typography>
</Box>
</Box>
);
},
);
FuneralFinderV4.displayName = 'FuneralFinderV4';
export default FuneralFinderV4;

View File

@@ -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'),
},
};

View File

@@ -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>

View 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'),
},
};

View 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'),
},
};

View 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'),
},
};