Files
Rental-operations-platform/src/app/[locale]/page.tsx
T
2026-06-25 19:06:59 -04:00

335 lines
12 KiB
TypeScript

import { ActionLink } from '@/components/actions/ActionLink';
import { Accordion } from '@/components/controls/Accordion';
import { Icon } from '@/components/foundation/Icon';
import { Container, Section, Stack } from '@/components/layout/LayoutPrimitives';
import { CTA } from '@/components/marketing/CTA';
import { Comparison } from '@/components/marketing/Comparison';
import { FeatureCard } from '@/components/marketing/FeatureCard';
import { ProductPreview } from '@/components/marketing/ProductPreview';
import { SectionHeader } from '@/components/marketing/SectionHeader';
import { FeatureGrid } from '@/components/marketing/SectionPatterns';
import { Workflow } from '@/components/marketing/Workflow';
import { Callout } from '@/components/surfaces/Callout';
import { Card } from '@/components/surfaces/Card';
import { Eyebrow, Heading, Text } from '@/components/typography/Typography';
import { buildHomepageContent } from '@/content/homepage-model';
import { isLocale, localizedPath, type Locale } from '@/lib/localization/config';
import { getMessages } from '@/lib/localization/messages';
import { buildLocalizedMetadata } from '@/lib/metadata/build-metadata';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import styles from './page.module.css';
interface LocalePageProps {
params: Promise<{ locale: string }>;
}
export async function generateMetadata({ params }: LocalePageProps): Promise<Metadata> {
const { locale: localeValue } = await params;
if (!isLocale(localeValue)) notFound();
const messages = getMessages(localeValue);
return buildLocalizedMetadata({
locale: localeValue,
routeId: 'home',
title: messages.homepage.meta.title,
description: messages.homepage.meta.description,
indexable: true,
});
}
export default async function LocaleHomePage({ params }: LocalePageProps) {
const { locale: localeValue } = await params;
if (!isLocale(localeValue)) notFound();
const locale: Locale = localeValue;
const messages = getMessages(locale);
const content = buildHomepageContent(messages.homepage, messages.shell);
const homePath = localizedPath('home', locale);
return (
<main id="main-content" className={styles.main} tabIndex={-1}>
<Section id="product" className={styles.hero} aria-label={content.hero.title}>
<Container>
<div className={styles.heroGrid}>
<div className={styles.heroCopy}>
<Eyebrow>{content.hero.eyebrow}</Eyebrow>
<Heading level={1} appearance="display">
{content.hero.title}
</Heading>
<Text variant="lead">{content.hero.body}</Text>
<div className={styles.heroActions} role="group" aria-label={content.hero.eyebrow}>
<ActionLink
href={homePath}
variant="button-conversion"
disabledReason={content.hero.primary.disabledReason}
>
{content.hero.primary.label}
</ActionLink>
<ActionLink
href={homePath}
variant="button-primary"
disabledReason={content.hero.secondary.disabledReason}
>
{content.hero.secondary.label}
</ActionLink>
</div>
</div>
<div className={styles.previewStage}>
<ProductPreview {...content.hero.preview} headingLevel={2} />
</div>
</div>
</Container>
</Section>
<Section className={styles.sectionBorder} aria-label={content.trust.title}>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={content.trust.eyebrow}
title={content.trust.title}
body={content.trust.body}
alignment="center"
/>
<FeatureGrid>
{content.trust.items.map((item) => (
<FeatureCard
key={item.id}
title={item.title}
description={item.description}
{...(item.icon ? { icon: item.icon } : {})}
emphasis="muted"
/>
))}
</FeatureGrid>
</Stack>
</Container>
</Section>
<Section tone="muted" aria-label={content.comparison.title}>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={content.comparison.eyebrow}
title={content.comparison.title}
body={content.comparison.body}
/>
<Comparison
columns={[
{
title: content.comparison.beforeTitle,
items: content.comparison.beforeItems,
tone: 'problem',
},
{
title: content.comparison.afterTitle,
items: content.comparison.afterItems,
tone: 'solution',
},
]}
/>
</Stack>
</Container>
</Section>
<Section id="workflow" aria-label={content.workflow.title}>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={content.workflow.eyebrow}
title={content.workflow.title}
body={content.workflow.body}
/>
<Workflow items={content.workflow.steps} label={content.workflow.title} />
</Stack>
</Container>
</Section>
<Section tone="strong" aria-label={content.roles.title}>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={content.roles.eyebrow}
title={content.roles.title}
alignment="center"
/>
<FeatureGrid>
{content.roles.items.map((item) => (
<FeatureCard
key={item.id}
title={item.title}
description={item.description}
{...(item.icon ? { icon: item.icon } : {})}
emphasis="primary"
/>
))}
</FeatureGrid>
</Stack>
</Container>
</Section>
<Section id="modules" aria-label={content.modules.title}>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={content.modules.eyebrow}
title={content.modules.title}
body={content.modules.body}
/>
<FeatureGrid>
{content.modules.items.map((item) => (
<FeatureCard
key={item.id}
title={item.title}
description={item.description}
{...(item.icon ? { icon: item.icon } : {})}
/>
))}
</FeatureGrid>
</Stack>
</Container>
</Section>
<Section tone="muted" aria-label={content.results.title}>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={content.results.eyebrow}
title={content.results.title}
body={content.results.body}
/>
<FeatureGrid>
{content.results.metrics.map((metric) => (
<FeatureCard
key={metric.id}
title={metric.title}
description={metric.description}
{...(metric.icon ? { icon: metric.icon } : {})}
emphasis="muted"
/>
))}
</FeatureGrid>
<Callout icon="info" title={content.results.methodTitle}>
<ol className={styles.methodList}>
{content.results.method.map((item) => (
<li key={item}>{item}</li>
))}
</ol>
</Callout>
</Stack>
</Container>
</Section>
<Section aria-label={content.integrations.title}>
<Container>
<div className={styles.splitSection}>
<SectionHeader
eyebrow={content.integrations.eyebrow}
title={content.integrations.title}
body={content.integrations.body}
/>
<Card as="div" tone="elevated" padding="spacious">
<ul className={styles.capabilityList}>
{content.integrations.items.map((item) => (
<li key={item}>
<Icon name="plus" size="sm" />
<span>{item}</span>
</li>
))}
</ul>
</Card>
</div>
</Container>
</Section>
<Section tone="strong" aria-label={content.security.title}>
<Container>
<Stack gap="large">
<SectionHeader
eyebrow={content.security.eyebrow}
title={content.security.title}
body={content.security.body}
/>
<FeatureGrid>
{content.security.checks.map((check) => (
<FeatureCard
key={check.id}
title={check.title}
description={check.description}
{...(check.icon ? { icon: check.icon } : {})}
emphasis="primary"
/>
))}
</FeatureGrid>
</Stack>
</Container>
</Section>
<Section id="pricing" aria-label={content.pricing.title}>
<Container>
<div className={styles.pricingGrid}>
<div className={styles.pricingCopy}>
<SectionHeader
eyebrow={content.pricing.eyebrow}
title={content.pricing.title}
body={content.pricing.body}
/>
<ul className={styles.factorList}>
{content.pricing.factors.map((factor) => (
<li key={factor}>
<Icon name="check" size="sm" />
<span>{factor}</span>
</li>
))}
</ul>
</div>
<aside>
<Card as="div" tone="elevated" padding="spacious" className={styles.pricingCard}>
<Icon name="info" size="lg" />
<Text variant="supporting">{content.pricing.note}</Text>
<ActionLink
href={homePath}
variant="button-conversion"
disabledReason={content.pricing.action.disabledReason}
>
{content.pricing.action.label}
</ActionLink>
</Card>
</aside>
</div>
</Container>
</Section>
<Section id="faq" tone="muted" aria-label={content.faq.title}>
<Container width="content">
<Stack gap="large">
<SectionHeader
eyebrow={content.faq.eyebrow}
title={content.faq.title}
alignment="center"
/>
<Accordion
items={content.faq.items.map((item, index) => ({
id: item.id,
title: item.title,
content: <Text variant="supporting">{item.body}</Text>,
open: index === 0,
}))}
/>
</Stack>
</Container>
</Section>
<Section className={styles.finalSection} aria-label={content.final.title}>
<Container>
<CTA
eyebrow={content.final.eyebrow}
title={content.final.title}
body={content.final.body}
primary={content.final.primary}
secondary={content.final.secondary}
/>
</Container>
</Section>
</main>
);
}