HomePage V2: full-bleed hero, stats bar, discover section, editorial testimonials
New layout inspired by reference designs: - Full-bleed hero with parsonshero.png + gradient overlay (heroImageUrl prop) - Trust stats bar: 1,500+ families, 4.9 rating, 300+ directors - "See what you'll discover" section: map placeholder + featured ProviderCards - Editorial testimonials: alternating left/right with quote marks (no cards) - Minimal FAQ: borderless accordion with divider lines - V1 split hero preserved when heroImage prop used instead New types: TrustStat, FeaturedProvider. New props: heroImageUrl, stats, featuredProviders, discoverMapSlot, onSelectFeaturedProvider. V1 stories unchanged, new V2 story added. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import AccessTimeIcon from '@mui/icons-material/AccessTime';
|
|||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import SupportAgentOutlinedIcon from '@mui/icons-material/SupportAgentOutlined';
|
import SupportAgentOutlinedIcon from '@mui/icons-material/SupportAgentOutlined';
|
||||||
import { HomePage } from './HomePage';
|
import { HomePage } from './HomePage';
|
||||||
|
import type { FeaturedProvider, TrustStat } from './HomePage';
|
||||||
import { Navigation } from '../../organisms/Navigation';
|
import { Navigation } from '../../organisms/Navigation';
|
||||||
import { Footer } from '../../organisms/Footer';
|
import { Footer } from '../../organisms/Footer';
|
||||||
|
|
||||||
@@ -264,3 +265,68 @@ export const Mobile: Story = {
|
|||||||
viewport: { defaultViewport: 'mobile1' },
|
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'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Accordion from '@mui/material/Accordion';
|
|||||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||||
import AccordionDetails from '@mui/material/AccordionDetails';
|
import AccordionDetails from '@mui/material/AccordionDetails';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
import FormatQuoteIcon from '@mui/icons-material/FormatQuote';
|
||||||
import StarIcon from '@mui/icons-material/Star';
|
import StarIcon from '@mui/icons-material/Star';
|
||||||
import StarHalfIcon from '@mui/icons-material/StarHalf';
|
import StarHalfIcon from '@mui/icons-material/StarHalf';
|
||||||
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
||||||
@@ -12,6 +13,7 @@ import type { SxProps, Theme } from '@mui/material/styles';
|
|||||||
import { Typography } from '../../atoms/Typography';
|
import { Typography } from '../../atoms/Typography';
|
||||||
import { Button } from '../../atoms/Button';
|
import { Button } from '../../atoms/Button';
|
||||||
import { Card } from '../../atoms/Card';
|
import { Card } from '../../atoms/Card';
|
||||||
|
import { ProviderCard } from '../../molecules/ProviderCard';
|
||||||
import { FuneralFinderV3, type FuneralFinderV3SearchParams } from '../../organisms/FuneralFinder';
|
import { FuneralFinderV3, type FuneralFinderV3SearchParams } from '../../organisms/FuneralFinder';
|
||||||
|
|
||||||
// ─── Types ───────────────────────────────────────────────────────────────────
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
||||||
@@ -43,6 +45,25 @@ export interface FAQItem {
|
|||||||
answer: React.ReactNode;
|
answer: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A trust stat for the stats bar */
|
||||||
|
export interface TrustStat {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A featured provider for the discover section */
|
||||||
|
export interface FeaturedProvider {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
location: string;
|
||||||
|
verified?: boolean;
|
||||||
|
imageUrl?: string;
|
||||||
|
logoUrl?: string;
|
||||||
|
rating?: number;
|
||||||
|
reviewCount?: number;
|
||||||
|
startingPrice?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/** Props for the HomePage component */
|
/** Props for the HomePage component */
|
||||||
export interface HomePageProps {
|
export interface HomePageProps {
|
||||||
/** Navigation bar */
|
/** Navigation bar */
|
||||||
@@ -54,19 +75,31 @@ export interface HomePageProps {
|
|||||||
heroHeading?: string;
|
heroHeading?: string;
|
||||||
/** Hero subheading paragraph */
|
/** Hero subheading paragraph */
|
||||||
heroSubheading?: string;
|
heroSubheading?: string;
|
||||||
/** Hero image slot */
|
/** Hero image slot (split layout — V1) */
|
||||||
heroImage?: React.ReactNode;
|
heroImage?: React.ReactNode;
|
||||||
|
/** Hero background image URL (full-bleed layout — V2). Takes priority over heroImage. */
|
||||||
|
heroImageUrl?: string;
|
||||||
|
|
||||||
/** FuneralFinder search callback */
|
/** FuneralFinder search callback */
|
||||||
onSearch?: (params: FuneralFinderV3SearchParams) => void;
|
onSearch?: (params: FuneralFinderV3SearchParams) => void;
|
||||||
/** FuneralFinder loading state */
|
/** FuneralFinder loading state */
|
||||||
searchLoading?: boolean;
|
searchLoading?: boolean;
|
||||||
|
|
||||||
|
/** Trust stats bar (e.g. "1,500+ families helped") */
|
||||||
|
stats?: TrustStat[];
|
||||||
|
|
||||||
/** Partner logos for the trust carousel */
|
/** Partner logos for the trust carousel */
|
||||||
partnerLogos?: PartnerLogo[];
|
partnerLogos?: PartnerLogo[];
|
||||||
/** Trust line text above the carousel */
|
/** Trust line text above the carousel */
|
||||||
partnerTrustLine?: string;
|
partnerTrustLine?: string;
|
||||||
|
|
||||||
|
/** Featured providers for the "Discover" section */
|
||||||
|
featuredProviders?: FeaturedProvider[];
|
||||||
|
/** Map slot for the discover section */
|
||||||
|
discoverMapSlot?: React.ReactNode;
|
||||||
|
/** Callback when a featured provider card is clicked */
|
||||||
|
onSelectFeaturedProvider?: (id: string) => void;
|
||||||
|
|
||||||
/** Feature cards for "Why Use FA" */
|
/** Feature cards for "Why Use FA" */
|
||||||
features?: FeatureCard[];
|
features?: FeatureCard[];
|
||||||
|
|
||||||
@@ -136,10 +169,15 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
|||||||
heroHeading = 'Compare Funeral Directors Near You and Arrange with Confidence',
|
heroHeading = 'Compare Funeral Directors Near You and Arrange with Confidence',
|
||||||
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.",
|
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,
|
heroImage,
|
||||||
|
heroImageUrl,
|
||||||
onSearch,
|
onSearch,
|
||||||
searchLoading,
|
searchLoading,
|
||||||
|
stats = [],
|
||||||
partnerLogos = [],
|
partnerLogos = [],
|
||||||
partnerTrustLine = 'Providing services from hundreds of trusted funeral homes across Australia',
|
partnerTrustLine = 'Providing services from hundreds of trusted funeral homes across Australia',
|
||||||
|
featuredProviders = [],
|
||||||
|
discoverMapSlot,
|
||||||
|
onSelectFeaturedProvider,
|
||||||
features = [],
|
features = [],
|
||||||
googleRating,
|
googleRating,
|
||||||
googleReviewCount,
|
googleReviewCount,
|
||||||
@@ -152,6 +190,8 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
|||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
|
const isFullBleedHero = !!heroImageUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -167,73 +207,114 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
|||||||
{/* ═══════════════════════════════════════════════════════════════════
|
{/* ═══════════════════════════════════════════════════════════════════
|
||||||
Section 1: Hero
|
Section 1: Hero
|
||||||
═══════════════════════════════════════════════════════════════════ */}
|
═══════════════════════════════════════════════════════════════════ */}
|
||||||
<Box
|
{isFullBleedHero ? (
|
||||||
component="section"
|
/* ── V2: Full-bleed hero with text overlay ── */
|
||||||
aria-labelledby="hero-heading"
|
<Box
|
||||||
sx={{
|
component="section"
|
||||||
bgcolor: 'var(--fa-color-surface-warm)',
|
aria-labelledby="hero-heading"
|
||||||
overflow: 'hidden',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Container
|
|
||||||
maxWidth="lg"
|
|
||||||
sx={{
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
minHeight: { xs: 420, md: 520 },
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: { xs: 'column', md: 'row' },
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: { xs: 3, md: 0 },
|
justifyContent: 'center',
|
||||||
py: { xs: 4, md: 0 },
|
backgroundImage: `url(${heroImageUrl})`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
'&::after': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
background:
|
||||||
|
'linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0.15) 50%, rgba(0,0,0,0.4) 100%)',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Hero text */}
|
<Container
|
||||||
<Box
|
maxWidth="md"
|
||||||
sx={{
|
sx={{ position: 'relative', zIndex: 1, textAlign: 'center', py: 6 }}
|
||||||
flex: { md: '0 0 50%' },
|
|
||||||
py: { md: 6 },
|
|
||||||
pr: { md: 5 },
|
|
||||||
textAlign: { xs: 'center', md: 'left' },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="display3"
|
variant="display3"
|
||||||
component="h1"
|
component="h1"
|
||||||
id="hero-heading"
|
id="hero-heading"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
sx={{ mb: 2, color: 'text.primary' }}
|
sx={{ mb: 2, color: 'var(--fa-color-white)' }}
|
||||||
>
|
>
|
||||||
{heroHeading}
|
{heroHeading}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ color: 'rgba(255,255,255,0.85)', maxWidth: 560, mx: 'auto' }}
|
||||||
|
>
|
||||||
{heroSubheading}
|
{heroSubheading}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Container>
|
||||||
|
</Box>
|
||||||
{/* Hero image */}
|
) : (
|
||||||
<Box
|
/* ── V1: Split hero ── */
|
||||||
role="img"
|
<Box
|
||||||
aria-label="Family planning together"
|
component="section"
|
||||||
|
aria-labelledby="hero-heading"
|
||||||
|
sx={{ bgcolor: 'var(--fa-color-surface-warm)', overflow: 'hidden' }}
|
||||||
|
>
|
||||||
|
<Container
|
||||||
|
maxWidth="lg"
|
||||||
sx={{
|
sx={{
|
||||||
flex: { md: '0 0 50%' },
|
display: 'flex',
|
||||||
minHeight: { xs: 200, md: 320 },
|
flexDirection: { xs: 'column', md: 'row' },
|
||||||
position: 'relative',
|
alignItems: 'center',
|
||||||
overflow: 'hidden',
|
gap: { xs: 3, md: 0 },
|
||||||
borderRadius: { xs: 'var(--fa-border-radius-lg)', md: 0 },
|
py: { xs: 4, md: 0 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{heroImage || (
|
<Box
|
||||||
<Box
|
sx={{
|
||||||
sx={{
|
flex: { md: '0 0 50%' },
|
||||||
width: '100%',
|
py: { md: 6 },
|
||||||
height: '100%',
|
pr: { md: 5 },
|
||||||
minHeight: 'inherit',
|
textAlign: { xs: 'center', md: 'left' },
|
||||||
background:
|
}}
|
||||||
'linear-gradient(160deg, var(--fa-color-brand-100) 0%, var(--fa-color-brand-200) 50%, var(--fa-color-brand-300) 100%)',
|
>
|
||||||
}}
|
<Typography
|
||||||
/>
|
variant="display3"
|
||||||
)}
|
component="h1"
|
||||||
</Box>
|
id="hero-heading"
|
||||||
</Container>
|
tabIndex={-1}
|
||||||
</Box>
|
sx={{ mb: 2, color: 'text.primary' }}
|
||||||
|
>
|
||||||
|
{heroHeading}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
{heroSubheading}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
role="img"
|
||||||
|
aria-label="Family planning together"
|
||||||
|
sx={{
|
||||||
|
flex: { md: '0 0 50%' },
|
||||||
|
minHeight: { xs: 200, md: 320 },
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
borderRadius: { xs: 'var(--fa-border-radius-lg)', md: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{heroImage || (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
minHeight: 'inherit',
|
||||||
|
background:
|
||||||
|
'linear-gradient(160deg, var(--fa-color-brand-100) 0%, var(--fa-color-brand-200) 50%, var(--fa-color-brand-300) 100%)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ═══════════════════════════════════════════════════════════════════
|
{/* ═══════════════════════════════════════════════════════════════════
|
||||||
Section 2: FuneralFinder Widget (overlapping card)
|
Section 2: FuneralFinder Widget (overlapping card)
|
||||||
@@ -256,6 +337,131 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* ═══════════════════════════════════════════════════════════════════
|
||||||
|
Section 2b: Stats Bar (V2 only)
|
||||||
|
═══════════════════════════════════════════════════════════════════ */}
|
||||||
|
{stats.length > 0 && (
|
||||||
|
<Box
|
||||||
|
component="section"
|
||||||
|
aria-label="Trust statistics"
|
||||||
|
sx={{ py: { xs: 4, md: 5 }, mt: { xs: 4, md: 5 } }}
|
||||||
|
>
|
||||||
|
<Container maxWidth="md">
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: { xs: 3, md: 6 },
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stats.map((stat) => (
|
||||||
|
<Box key={stat.label}>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
component="p"
|
||||||
|
sx={{ color: 'primary.main', mb: 0.5 }}
|
||||||
|
>
|
||||||
|
{stat.value}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{stat.label}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ═══════════════════════════════════════════════════════════════════
|
||||||
|
Section 2c: Discover — Map + Featured Providers (V2)
|
||||||
|
═══════════════════════════════════════════════════════════════════ */}
|
||||||
|
{featuredProviders.length > 0 && (
|
||||||
|
<Box
|
||||||
|
component="section"
|
||||||
|
aria-labelledby="discover-heading"
|
||||||
|
sx={{
|
||||||
|
bgcolor: 'var(--fa-color-surface-subtle)',
|
||||||
|
py: { xs: 6, md: 10 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Container maxWidth="lg">
|
||||||
|
<Box sx={{ textAlign: 'center', mb: { xs: 4, md: 6 } }}>
|
||||||
|
<Typography
|
||||||
|
variant="display3"
|
||||||
|
component="h2"
|
||||||
|
id="discover-heading"
|
||||||
|
sx={{ mb: 1.5, color: 'text.primary' }}
|
||||||
|
>
|
||||||
|
See what you’ll discover
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ maxWidth: 520, mx: 'auto' }}
|
||||||
|
>
|
||||||
|
From trusted local providers to personalised options, find the right care near
|
||||||
|
you.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' },
|
||||||
|
gap: 3,
|
||||||
|
alignItems: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Map placeholder */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
borderRadius: 2,
|
||||||
|
overflow: 'hidden',
|
||||||
|
minHeight: { xs: 240, md: 400 },
|
||||||
|
bgcolor: 'var(--fa-color-surface-cool)',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{discoverMapSlot || (
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
Map coming soon
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Featured provider cards */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
|
{featuredProviders.map((provider) => (
|
||||||
|
<ProviderCard
|
||||||
|
key={provider.id}
|
||||||
|
name={provider.name}
|
||||||
|
location={provider.location}
|
||||||
|
verified={provider.verified}
|
||||||
|
imageUrl={provider.imageUrl}
|
||||||
|
logoUrl={provider.logoUrl}
|
||||||
|
rating={provider.rating}
|
||||||
|
reviewCount={provider.reviewCount}
|
||||||
|
startingPrice={provider.startingPrice}
|
||||||
|
onClick={
|
||||||
|
onSelectFeaturedProvider
|
||||||
|
? () => onSelectFeaturedProvider(provider.id)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ═══════════════════════════════════════════════════════════════════
|
{/* ═══════════════════════════════════════════════════════════════════
|
||||||
Section 3: Partner Logos Carousel
|
Section 3: Partner Logos Carousel
|
||||||
═══════════════════════════════════════════════════════════════════ */}
|
═══════════════════════════════════════════════════════════════════ */}
|
||||||
@@ -319,12 +525,9 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
|||||||
'100%': { transform: 'translateX(-50%)' },
|
'100%': { transform: 'translateX(-50%)' },
|
||||||
},
|
},
|
||||||
'&:hover': { animationPlayState: 'paused' },
|
'&:hover': { animationPlayState: 'paused' },
|
||||||
'@media (prefers-reduced-motion: reduce)': {
|
'@media (prefers-reduced-motion: reduce)': { animation: 'none' },
|
||||||
animation: 'none',
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Duplicate logos for seamless infinite scroll */}
|
|
||||||
{[...partnerLogos, ...partnerLogos].map((logo, i) => (
|
{[...partnerLogos, ...partnerLogos].map((logo, i) => (
|
||||||
<Box
|
<Box
|
||||||
key={`${logo.alt}-${i}`}
|
key={`${logo.alt}-${i}`}
|
||||||
@@ -339,10 +542,7 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
|||||||
filter: 'grayscale(100%)',
|
filter: 'grayscale(100%)',
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
transition: 'opacity 0.2s, filter 0.2s',
|
transition: 'opacity 0.2s, filter 0.2s',
|
||||||
'&:hover': {
|
'&:hover': { filter: 'grayscale(0%)', opacity: 1 },
|
||||||
filter: 'grayscale(0%)',
|
|
||||||
opacity: 1,
|
|
||||||
},
|
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -442,112 +642,100 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ═══════════════════════════════════════════════════════════════════
|
{/* ═══════════════════════════════════════════════════════════════════
|
||||||
Section 5: Reviews / Testimonials
|
Section 5: Testimonials
|
||||||
═══════════════════════════════════════════════════════════════════ */}
|
═══════════════════════════════════════════════════════════════════ */}
|
||||||
{(testimonials.length > 0 || googleRating != null) && (
|
{testimonials.length > 0 && (
|
||||||
<Box
|
<Box
|
||||||
component="section"
|
component="section"
|
||||||
aria-labelledby="reviews-heading"
|
aria-labelledby="reviews-heading"
|
||||||
sx={{
|
sx={{
|
||||||
bgcolor: 'var(--fa-color-brand-950)',
|
|
||||||
py: { xs: 6, md: 10 },
|
py: { xs: 6, md: 10 },
|
||||||
|
bgcolor: 'var(--fa-color-surface-default)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="md">
|
||||||
<Box sx={{ textAlign: 'center', mb: { xs: 3, md: 5 } }}>
|
<Typography
|
||||||
<Typography
|
variant="h2"
|
||||||
variant="overline"
|
component="h2"
|
||||||
sx={{ color: 'var(--fa-color-brand-400)', mb: 1.5, display: 'block' }}
|
id="reviews-heading"
|
||||||
>
|
sx={{ textAlign: 'center', mb: 1, color: 'text.primary' }}
|
||||||
What our customers say
|
>
|
||||||
</Typography>
|
Testimonials
|
||||||
<Typography
|
</Typography>
|
||||||
variant="h2"
|
|
||||||
component="h2"
|
|
||||||
id="reviews-heading"
|
|
||||||
sx={{ color: 'var(--fa-color-white)', mb: 3 }}
|
|
||||||
>
|
|
||||||
How we have helped families like yours
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{/* Google aggregate */}
|
{googleRating != null && (
|
||||||
{googleRating != null && (
|
<Box sx={{ textAlign: 'center', mb: { xs: 4, md: 6 } }}>
|
||||||
<Box sx={{ mb: { xs: 3, md: 5 } }}>
|
<Box
|
||||||
<Typography
|
sx={{
|
||||||
variant="overline"
|
display: 'inline-flex',
|
||||||
sx={{
|
alignItems: 'center',
|
||||||
color: 'var(--fa-color-neutral-400)',
|
gap: 1,
|
||||||
mb: 0.5,
|
mt: 1,
|
||||||
display: 'block',
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<StarRating rating={googleRating} size={16} />
|
||||||
Google Reviews
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{googleRating} Google Rating
|
||||||
|
{googleReviewCount
|
||||||
|
? ` · ${googleReviewCount.toLocaleString('en-AU')} reviews`
|
||||||
|
: ''}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
|
||||||
variant="h1"
|
|
||||||
component="p"
|
|
||||||
sx={{ color: 'var(--fa-color-white)', mb: 0.5 }}
|
|
||||||
>
|
|
||||||
{googleRating}
|
|
||||||
</Typography>
|
|
||||||
<StarRating
|
|
||||||
rating={googleRating}
|
|
||||||
size={20}
|
|
||||||
color="var(--fa-color-brand-400)"
|
|
||||||
/>
|
|
||||||
{googleReviewCount != null && (
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
sx={{ color: 'var(--fa-color-neutral-400)', mt: 0.5, display: 'block' }}
|
|
||||||
>
|
|
||||||
{googleReviewCount.toLocaleString('en-AU')} reviews
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Testimonial cards */}
|
|
||||||
{testimonials.length > 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: {
|
|
||||||
xs: '1fr',
|
|
||||||
md: 'repeat(3, 1fr)',
|
|
||||||
},
|
|
||||||
gap: 3,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{testimonials.map((t, i) => (
|
|
||||||
<Card key={`${t.name}-${i}`} variant="elevated" padding="default">
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
mb: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="h6" component="p">
|
|
||||||
{t.name}
|
|
||||||
</Typography>
|
|
||||||
<StarRating rating={t.rating} size={14} />
|
|
||||||
</Box>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ mb: 2, fontStyle: 'italic' }}
|
|
||||||
>
|
|
||||||
“{t.quote}”
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="caption" color="text.tertiary">
|
|
||||||
{t.timeAgo}
|
|
||||||
</Typography>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Editorial testimonials — alternating alignment */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
|
{testimonials.map((t, i) => {
|
||||||
|
const isRight = i % 2 === 1;
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={`${t.name}-${i}`}
|
||||||
|
sx={{
|
||||||
|
textAlign: isRight ? 'right' : 'left',
|
||||||
|
maxWidth: '85%',
|
||||||
|
ml: isRight ? 'auto' : 0,
|
||||||
|
mr: isRight ? 0 : 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormatQuoteIcon
|
||||||
|
sx={{
|
||||||
|
fontSize: 32,
|
||||||
|
color: 'var(--fa-color-brand-300)',
|
||||||
|
transform: isRight ? 'scaleX(-1)' : 'none',
|
||||||
|
mb: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
component="blockquote"
|
||||||
|
sx={{ mb: 2, fontWeight: 400 }}
|
||||||
|
>
|
||||||
|
{t.quote}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="label"
|
||||||
|
component="cite"
|
||||||
|
sx={{ fontStyle: 'normal', display: 'block', mb: 0.5 }}
|
||||||
|
>
|
||||||
|
{t.name}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StarRating rating={t.rating} size={12} />
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
· {t.timeAgo}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -597,32 +785,34 @@ export const HomePage = React.forwardRef<HTMLDivElement, HomePageProps>(
|
|||||||
id="faq-heading"
|
id="faq-heading"
|
||||||
sx={{ textAlign: 'center', mb: { xs: 4, md: 6 }, color: 'text.primary' }}
|
sx={{ textAlign: 'center', mb: { xs: 4, md: 6 }, color: 'text.primary' }}
|
||||||
>
|
>
|
||||||
Frequently Asked Questions
|
FAQ
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ maxWidth: 800, mx: 'auto' }}>
|
<Box sx={{ maxWidth: 700, mx: 'auto' }}>
|
||||||
{faqItems.map((item, i) => (
|
{faqItems.map((item, i) => (
|
||||||
<Accordion
|
<Accordion
|
||||||
key={i}
|
key={i}
|
||||||
disableGutters
|
disableGutters
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={{
|
sx={{
|
||||||
border: 1,
|
bgcolor: 'transparent',
|
||||||
|
borderBottom: '1px solid',
|
||||||
borderColor: 'divider',
|
borderColor: 'divider',
|
||||||
borderRadius: '8px !important',
|
|
||||||
mb: 2,
|
|
||||||
'&:before': { display: 'none' },
|
'&:before': { display: 'none' },
|
||||||
overflow: 'hidden',
|
'&:first-of-type': {
|
||||||
|
borderTop: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={{ px: 3, py: 1 }}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />} sx={{ px: 0, py: 1.5 }}>
|
||||||
<Typography variant="h6" component="h3">
|
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||||
{item.question}
|
{item.question}
|
||||||
</Typography>
|
</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails sx={{ px: 3, pb: 3 }}>
|
<AccordionDetails sx={{ px: 0, pb: 3 }}>
|
||||||
{typeof item.answer === 'string' ? (
|
{typeof item.answer === 'string' ? (
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
{item.answer}
|
{item.answer}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user