Refactor site header, heroes, and design system

This commit is contained in:
MOH
2026-03-08 00:29:51 +01:00
parent a18370d003
commit 0c2b67e378
56 changed files with 2714 additions and 1318 deletions
+86 -33
View File
@@ -6,6 +6,7 @@ import { getTranslations } from "next-intl/server";
import { notFound } from "next/navigation";
import { Container } from "@/components/layout/container";
import { PageHero } from "@/components/layout/page-hero";
import { MotionFade } from "@/components/motion-fade";
import { buildLocalizedMetadata } from "@/lib/metadata";
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
@@ -51,6 +52,73 @@ function PortfolioImage({
);
}
function renderSectionContent(
section: NonNullable<Awaited<ReturnType<typeof getPublishedPortfolioProjectBySlug>>>["sections"][number],
localeKey: ReturnType<typeof resolveLocale>,
t: Awaited<ReturnType<typeof getTranslations>>,
) {
const title = getLocalizedValue(section.title, localeKey);
const body = getLocalizedValue(section.body, localeKey);
if (section.type === "GALLERY") {
return (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
{section.imagePath ? (
<PortfolioImage
src={section.imagePath}
alt={title}
width={1200}
height={720}
className="h-56 w-full rounded-md object-cover"
/>
) : (
<div className="rounded-md border border-dashed border-border px-4 py-10 text-center text-sm text-muted-foreground">
No image configured.
</div>
)}
</div>
);
}
if (section.type === "LINK") {
return (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
{body ? (
<p className="whitespace-pre-line text-sm text-muted-foreground">{body}</p>
) : null}
{section.linkUrl ? (
<Button asChild variant="outline">
<Link href={section.linkUrl} target="_blank" rel="noreferrer">
{t("openLink")}
<ArrowUpRight className="h-4 w-4" />
</Link>
</Button>
) : null}
</div>
);
}
if (section.type === "STATS" || section.type === "DELIVERABLES") {
return (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
<div className="rounded-md border border-input bg-background p-4">
<p className="whitespace-pre-line text-sm text-muted-foreground">{body}</p>
</div>
</div>
);
}
return (
<div className="space-y-4">
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
<p className="whitespace-pre-line text-sm text-muted-foreground">{body}</p>
</div>
);
}
export async function generateMetadata({
params: { locale, slug },
}: PortfolioItemPageProps): Promise<Metadata> {
@@ -87,8 +155,14 @@ export default async function PortfolioItemPage({
const t = await getTranslations({ locale: localeKey, namespace: "portfolioDetail" });
return (
<Container size="wide" className="flex flex-col gap-section py-10 lg:py-14">
<MotionFade>
<>
<PageHero
title={getLocalizedValue(item.title, localeKey)}
description={getLocalizedValue(item.summary, localeKey)}
/>
<Container size="wide" className="flex flex-col gap-section pb-12 lg:pb-16">
<MotionFade>
<AppCard level={3}>
<CardContent className="p-6 lg:p-10">
<Button asChild variant="ghost" className="h-auto px-0 py-0 text-sm">
@@ -161,44 +235,22 @@ export default async function PortfolioItemPage({
) : null}
</CardContent>
</AppCard>
</MotionFade>
</MotionFade>
<div className="grid gap-4 md:grid-cols-3">
<div className="grid gap-4 md:grid-cols-3">
{item.sections.map((section, index) => (
<MotionFade key={section.id} delay={0.05 * (index + 1)}>
<AppCard>
<CardContent className="p-5">
<h2 className="text-lg font-semibold text-foreground">
{getLocalizedValue(section.title, localeKey)}
</h2>
<p className="mt-2 whitespace-pre-line text-sm text-muted-foreground">
{getLocalizedValue(section.body, localeKey)}
</p>
{section.imagePath ? (
<PortfolioImage
src={section.imagePath}
alt={getLocalizedValue(section.title, localeKey)}
width={1200}
height={720}
className="mt-4 h-48 w-full rounded-md object-cover"
/>
) : null}
{section.linkUrl ? (
<Button asChild variant="outline" className="mt-4">
<Link href={section.linkUrl} target="_blank" rel="noreferrer">
{t("openLink")}
<ArrowUpRight className="h-4 w-4" />
</Link>
</Button>
) : null}
{renderSectionContent(section, localeKey, t)}
</CardContent>
</AppCard>
</MotionFade>
))}
</div>
</div>
{item.assets.length > 0 ? (
<MotionFade delay={0.1}>
{item.assets.length > 0 ? (
<MotionFade delay={0.1}>
<AppCard>
<CardContent className="p-6 lg:p-8">
<h2 className="text-xl font-semibold text-foreground">{t("gallery")}</h2>
@@ -230,8 +282,9 @@ export default async function PortfolioItemPage({
</div>
</CardContent>
</AppCard>
</MotionFade>
) : null}
</Container>
</MotionFade>
) : null}
</Container>
</>
);
}