150 lines
5.2 KiB
TypeScript
150 lines
5.2 KiB
TypeScript
import type { Metadata } from "next";
|
|
import { getLocale, getTranslations } from "next-intl/server";
|
|
|
|
import { Container } from "@/components/layout/container";
|
|
import { PageHero } from "@/components/layout/page-hero";
|
|
import { MotionFade } from "@/components/motion-fade";
|
|
import { AppCard } from "@/components/ui/app-card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { CapabilitiesSection } from "@/components/home/capabilities-section";
|
|
import { ProcessSection } from "@/components/home/process-section";
|
|
import { ContactCtaSection } from "@/components/home/contact-cta-section";
|
|
import type {
|
|
CapabilitiesSectionCopy,
|
|
ContactCtaSectionCopy,
|
|
ProcessSectionCopy,
|
|
} from "@/components/home/types";
|
|
import { getSiteSettings } from "@/lib/app-config";
|
|
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
|
import { buildLocalizedMetadata } from "@/lib/metadata";
|
|
|
|
type AboutPageProps = {
|
|
params: Promise<{
|
|
locale: string;
|
|
}>;
|
|
};
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export async function generateMetadata({ params }: AboutPageProps): Promise<Metadata> {
|
|
await params;
|
|
const siteSettings = await getSiteSettings();
|
|
const localeKey = resolveLocale(await getLocale().catch(() => siteSettings.defaultLocale), siteSettings.defaultLocale);
|
|
const t = await getTranslations({ locale: localeKey, namespace: "aboutPage" });
|
|
|
|
return await buildLocalizedMetadata({
|
|
locale: localeKey,
|
|
pathname: "/about",
|
|
title: t("title"),
|
|
description: t("description"),
|
|
});
|
|
}
|
|
|
|
export default async function AboutPage({ params }: AboutPageProps) {
|
|
await params;
|
|
const siteSettings = await getSiteSettings();
|
|
const localeKey = resolveLocale(await getLocale().catch(() => siteSettings.defaultLocale), siteSettings.defaultLocale);
|
|
const t = await getTranslations({ locale: localeKey, namespace: "aboutPage" });
|
|
|
|
const contactHref = getLocalizedPath(localeKey, "/contact", siteSettings.defaultLocale);
|
|
|
|
const capabilitiesCopy: CapabilitiesSectionCopy = {
|
|
eyebrow: t("capabilities.eyebrow"),
|
|
title: t("capabilities.title"),
|
|
description: t("capabilities.description"),
|
|
items: t.raw("capabilities.items") as CapabilitiesSectionCopy["items"],
|
|
};
|
|
|
|
const processCopy: ProcessSectionCopy = {
|
|
eyebrow: t("process.eyebrow"),
|
|
title: t("process.title"),
|
|
description: t("process.description"),
|
|
steps: t.raw("process.steps") as ProcessSectionCopy["steps"],
|
|
};
|
|
|
|
const contactCtaCopy: ContactCtaSectionCopy = {
|
|
eyebrow: t("contactCta.eyebrow"),
|
|
title: t("contactCta.title"),
|
|
description: t("contactCta.description"),
|
|
contactCta: t("contactCta.contactCta"),
|
|
githubCta: t("contactCta.githubCta"),
|
|
emailLabel: t("contactCta.emailLabel"),
|
|
emailValue: t("contactCta.emailValue"),
|
|
availabilityLabel: t("contactCta.availabilityLabel"),
|
|
availabilityValue: t("contactCta.availabilityValue"),
|
|
githubHref: t("contactCta.githubHref"),
|
|
};
|
|
|
|
const storyParagraphs = t.raw("story.paragraphs") as string[];
|
|
const toolItems = t.raw("tools.items") as string[];
|
|
|
|
return (
|
|
<>
|
|
<PageHero
|
|
locale={localeKey}
|
|
badge={t("heroEyebrow")}
|
|
title={t("title")}
|
|
description={t("description")}
|
|
/>
|
|
|
|
<Container className="flex flex-col gap-16 pb-16 sm:gap-20 lg:gap-24 lg:pb-20">
|
|
<MotionFade>
|
|
<section className="mx-auto max-w-3xl space-y-6">
|
|
<p className="eyebrow text-caption font-medium text-brand-primary">
|
|
{t("story.eyebrow")}
|
|
</p>
|
|
<h2 className="text-balance text-h2 text-foreground">{t("story.title")}</h2>
|
|
<div className="space-y-4">
|
|
{storyParagraphs.map((paragraph) => (
|
|
<p key={paragraph} className="text-body-lg text-muted-foreground">
|
|
{paragraph}
|
|
</p>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</MotionFade>
|
|
|
|
<MotionFade delay={0.06}>
|
|
<CapabilitiesSection copy={capabilitiesCopy} />
|
|
</MotionFade>
|
|
|
|
<MotionFade delay={0.08}>
|
|
<AppCard padding="lg">
|
|
<div className="space-y-5">
|
|
<div className="space-y-2">
|
|
<p className="eyebrow text-caption font-medium text-brand-primary">
|
|
{t("tools.eyebrow")}
|
|
</p>
|
|
<h3 className="text-title font-semibold text-foreground">{t("tools.title")}</h3>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{toolItems.map((tool) => (
|
|
<Badge key={tool} variant="secondary">
|
|
{tool}
|
|
</Badge>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</AppCard>
|
|
</MotionFade>
|
|
|
|
<MotionFade delay={0.1}>
|
|
<ProcessSection copy={processCopy} />
|
|
</MotionFade>
|
|
|
|
<MotionFade delay={0.12}>
|
|
<p className="mx-auto max-w-2xl text-center text-small text-muted-foreground">
|
|
<span className="text-brand-primary">{t("personalNote.eyebrow")}</span>
|
|
{" — "}
|
|
{t("personalNote.text")}
|
|
</p>
|
|
</MotionFade>
|
|
|
|
<MotionFade delay={0.14}>
|
|
<ContactCtaSection copy={contactCtaCopy} contactHref={contactHref} />
|
|
</MotionFade>
|
|
</Container>
|
|
</>
|
|
);
|
|
}
|