Refactor site header, heroes, and design system
This commit is contained in:
@@ -3,6 +3,7 @@ import { Compass, Layers3, Users } from "lucide-react";
|
||||
import { 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 { buildLocalizedMetadata } from "@/lib/metadata";
|
||||
import { resolveLocale } from "@/lib/locale";
|
||||
@@ -34,21 +35,11 @@ export default async function AboutPage({ params: { locale } }: AboutPageProps)
|
||||
const t = await getTranslations({ locale: localeKey, namespace: "aboutPage" });
|
||||
|
||||
return (
|
||||
<Container className="flex flex-col gap-section py-10 lg:py-14">
|
||||
<MotionFade>
|
||||
<AppCard level={3}>
|
||||
<CardContent className="p-6 lg:p-10">
|
||||
<h1 className="text-3xl font-semibold text-foreground sm:text-4xl">
|
||||
{t("title")}
|
||||
</h1>
|
||||
<p className="mt-4 max-w-3xl text-base text-muted-foreground sm:text-lg">
|
||||
{t("intro")}
|
||||
</p>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
<>
|
||||
<PageHero title={t("title")} description={t("intro")} />
|
||||
|
||||
<MotionFade delay={0.05}>
|
||||
<Container className="flex flex-col gap-section pb-12 lg:pb-16">
|
||||
<MotionFade delay={0.05}>
|
||||
<AppCard>
|
||||
<CardContent className="p-6 lg:p-8">
|
||||
<h2 className="text-2xl font-semibold text-foreground">
|
||||
@@ -89,9 +80,9 @@ export default async function AboutPage({ params: { locale } }: AboutPageProps)
|
||||
</div>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</MotionFade>
|
||||
|
||||
<MotionFade delay={0.1}>
|
||||
<MotionFade delay={0.1}>
|
||||
<AppCard>
|
||||
<CardContent className="p-6 lg:p-8">
|
||||
<h2 className="text-2xl font-semibold text-foreground">
|
||||
@@ -111,7 +102,8 @@ export default async function AboutPage({ params: { locale } }: AboutPageProps)
|
||||
</ol>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { 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 { buildLocalizedMetadata } from "@/lib/metadata";
|
||||
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
||||
@@ -39,8 +40,11 @@ export default async function ContactPage({ params: { locale } }: ContactPagePro
|
||||
const t = await getTranslations({ locale: localeKey, namespace: "contactPage" });
|
||||
|
||||
return (
|
||||
<Container className="grid gap-6 py-10 lg:grid-cols-2 lg:py-14">
|
||||
<MotionFade>
|
||||
<>
|
||||
<PageHero title={t("title")} description={t("intro")} />
|
||||
|
||||
<Container className="grid gap-6 pb-12 lg:grid-cols-2 lg:pb-16">
|
||||
<MotionFade>
|
||||
<AppCard level={3}>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-3xl sm:text-4xl">{t("title")}</CardTitle>
|
||||
@@ -63,9 +67,9 @@ export default async function ContactPage({ params: { locale } }: ContactPagePro
|
||||
</ul>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</MotionFade>
|
||||
|
||||
<MotionFade delay={0.05}>
|
||||
<MotionFade delay={0.05}>
|
||||
<AppCard>
|
||||
<CardContent className="pt-6">
|
||||
<form className="grid gap-4">
|
||||
@@ -93,7 +97,8 @@ export default async function ContactPage({ params: { locale } }: ContactPagePro
|
||||
</form>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ import type { ReactNode } from "react";
|
||||
import { unstable_noStore as noStore } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { SiteAmbientBackdrop } from "@/components/layout/site-ambient-backdrop";
|
||||
import { SiteFooter } from "@/components/layout/site-footer";
|
||||
import { SiteHeader } from "@/components/layout/site-header";
|
||||
import { isAdminAuthenticated } from "@/lib/admin-auth";
|
||||
import { getMaintenanceMode } from "@/lib/app-config";
|
||||
import { getMaintenanceMode, getSiteSettingsMediaBindings } from "@/lib/app-config";
|
||||
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
||||
|
||||
type SiteLayoutProps = {
|
||||
@@ -22,7 +23,10 @@ export default async function SiteLayout({ children, params: { locale } }: SiteL
|
||||
noStore();
|
||||
|
||||
const localeKey = resolveLocale(locale);
|
||||
const maintenanceEnabled = await getMaintenanceMode();
|
||||
const [maintenanceEnabled, mediaBindings] = await Promise.all([
|
||||
getMaintenanceMode(),
|
||||
getSiteSettingsMediaBindings(),
|
||||
]);
|
||||
const authenticated = isAdminAuthenticated();
|
||||
|
||||
if (maintenanceEnabled && !authenticated) {
|
||||
@@ -30,8 +34,13 @@ export default async function SiteLayout({ children, params: { locale } }: SiteL
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<SiteHeader isAdmin={authenticated} />
|
||||
<div className="relative flex min-h-screen flex-col overflow-hidden">
|
||||
<SiteAmbientBackdrop />
|
||||
<SiteHeader
|
||||
isAdmin={authenticated}
|
||||
lightLogoUrl={mediaBindings.siteLogoLight?.url}
|
||||
darkLogoUrl={mediaBindings.siteLogoDark?.url}
|
||||
/>
|
||||
<main className="flex-1">{children}</main>
|
||||
<SiteFooter isAdmin={authenticated} />
|
||||
</div>
|
||||
|
||||
@@ -4,12 +4,12 @@ import {
|
||||
BriefcaseBusiness,
|
||||
Boxes,
|
||||
Mail,
|
||||
Sparkles,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
import { Container } from "@/components/layout/container";
|
||||
import { HomeHero } from "@/components/layout/home-hero";
|
||||
import { MotionFade } from "@/components/motion-fade";
|
||||
import { getSiteSettings } from "@/lib/app-config";
|
||||
import { buildLocalizedMetadata } from "@/lib/metadata";
|
||||
@@ -56,48 +56,18 @@ export default async function HomePage({ params: { locale } }: HomePageProps) {
|
||||
const t = await getTranslations({ locale: localeKey, namespace: "homepage" });
|
||||
|
||||
return (
|
||||
<Container className="flex flex-col gap-section py-10 lg:py-14">
|
||||
<MotionFade>
|
||||
<AppCard level={3}>
|
||||
<CardContent className="grid gap-6 p-6 lg:grid-cols-[1.35fr_0.65fr] lg:p-10">
|
||||
<div className="space-y-5">
|
||||
<p className="inline-flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
||||
<Sparkles className="h-4 w-4 text-brand-secondary" />
|
||||
{t("heroKicker")}
|
||||
</p>
|
||||
<h1 className="text-balance text-3xl font-semibold tracking-tight text-foreground sm:text-5xl">
|
||||
{t("heroTitle")}
|
||||
</h1>
|
||||
<p className="max-w-2xl text-base text-muted-foreground sm:text-lg">
|
||||
{t("heroText")}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button asChild>
|
||||
<Link href={getLocalizedPath(localeKey, "/portfolio")}>
|
||||
{t("toPortfolio")}
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline">
|
||||
<Link href={getLocalizedPath(localeKey, "/products")}>{t("toProducts")}</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<HomeHero
|
||||
locale={localeKey}
|
||||
kicker={t("heroKicker")}
|
||||
title={t("heroTitle")}
|
||||
description={t("heroText")}
|
||||
portfolioLabel={t("toPortfolio")}
|
||||
contactLabel={t("toContact")}
|
||||
/>
|
||||
|
||||
<AppCard level={2} padding="md" className="self-start">
|
||||
<p className="inline-flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
||||
<BriefcaseBusiness className="h-4 w-4 text-brand-primary" />
|
||||
{t("heroCardTitle")}
|
||||
</p>
|
||||
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
|
||||
{t("heroCardText")}
|
||||
</p>
|
||||
</AppCard>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
|
||||
<MotionFade delay={0.05}>
|
||||
<Container className="flex flex-col gap-section pb-12 lg:pb-16">
|
||||
<MotionFade delay={0.05}>
|
||||
<AppCard>
|
||||
<CardContent className="p-6 lg:p-8">
|
||||
<div className="mb-6 flex items-center justify-between gap-4">
|
||||
@@ -139,9 +109,9 @@ export default async function HomePage({ params: { locale } }: HomePageProps) {
|
||||
</div>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</MotionFade>
|
||||
|
||||
<MotionFade delay={0.1}>
|
||||
<MotionFade delay={0.1}>
|
||||
<AppCard>
|
||||
<CardContent className="p-6 lg:p-8">
|
||||
<div className="mb-6 flex items-center justify-between gap-4">
|
||||
@@ -182,9 +152,9 @@ export default async function HomePage({ params: { locale } }: HomePageProps) {
|
||||
</div>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</MotionFade>
|
||||
|
||||
<MotionFade delay={0.15}>
|
||||
<MotionFade delay={0.15}>
|
||||
<AppCard level="inverse">
|
||||
<CardContent className="p-6 lg:p-8">
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
@@ -206,7 +176,8 @@ export default async function HomePage({ params: { locale } }: HomePageProps) {
|
||||
</div>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import { ArrowUpRight, CalendarDays, FolderKanban } from "lucide-react";
|
||||
import { ArrowUpRight, CalendarDays } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { 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 { buildLocalizedMetadata } from "@/lib/metadata";
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
@@ -55,24 +56,11 @@ export default async function PortfolioPage({
|
||||
]);
|
||||
|
||||
return (
|
||||
<Container className="flex flex-col gap-section py-10 lg:py-14">
|
||||
<MotionFade>
|
||||
<AppCard level={3}>
|
||||
<CardContent className="p-6 lg:p-10">
|
||||
<div className="flex items-center gap-3">
|
||||
<FolderKanban className="h-5 w-5 text-brand-primary" />
|
||||
<h1 className="text-3xl font-semibold text-foreground sm:text-4xl">
|
||||
{t("title")}
|
||||
</h1>
|
||||
</div>
|
||||
<p className="mt-4 max-w-3xl text-base text-muted-foreground sm:text-lg">
|
||||
{t("intro")}
|
||||
</p>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
<>
|
||||
<PageHero title={t("title")} description={t("intro")} />
|
||||
|
||||
<section className="flex flex-wrap gap-3">
|
||||
<Container className="flex flex-col gap-section pb-12 lg:pb-16">
|
||||
<section className="flex flex-wrap gap-3">
|
||||
<Link
|
||||
href={getLocalizedPath(localeKey, "/portfolio")}
|
||||
className={`rounded-md border px-4 py-2 text-sm transition-colors ${
|
||||
@@ -96,9 +84,9 @@ export default async function PortfolioPage({
|
||||
{getLocalizedValue(category.name, localeKey)}
|
||||
</Link>
|
||||
))}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
<section className="grid gap-4 md:grid-cols-2">
|
||||
{projects.map((item, index) => (
|
||||
<MotionFade key={item.slug} delay={index * 0.05}>
|
||||
<AppCard interactive>
|
||||
@@ -139,7 +127,8 @@ export default async function PortfolioPage({
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
) : null}
|
||||
</section>
|
||||
</Container>
|
||||
</section>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,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 { routing } from "@/i18n/routing";
|
||||
import { buildLocalizedMetadata } from "@/lib/metadata";
|
||||
@@ -64,8 +65,14 @@ export default async function ProductPage({ params: { locale, slug } }: ProductP
|
||||
const t = await getTranslations({ locale: localeKey, namespace: "productDetail" });
|
||||
|
||||
return (
|
||||
<Container size="wide" className="flex flex-col gap-section py-10 lg:py-14">
|
||||
<MotionFade>
|
||||
<>
|
||||
<PageHero
|
||||
title={pickText(item.name, localeKey)}
|
||||
description={pickText(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">
|
||||
@@ -111,9 +118,9 @@ export default async function ProductPage({ params: { locale, slug } }: ProductP
|
||||
</div>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</MotionFade>
|
||||
|
||||
<MotionFade delay={0.05}>
|
||||
<MotionFade delay={0.05}>
|
||||
<AppCard>
|
||||
<CardContent className="p-6 lg:p-8">
|
||||
<h2 className="text-xl font-semibold text-foreground">{t("included")}</h2>
|
||||
@@ -134,7 +141,8 @@ export default async function ProductPage({ params: { locale, slug } }: ProductP
|
||||
</Button>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import { ArrowUpRight, Boxes, CircleDollarSign } from "lucide-react";
|
||||
import { ArrowUpRight, CircleDollarSign } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { 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 { buildLocalizedMetadata } from "@/lib/metadata";
|
||||
import { AppCard } from "@/components/ui/app-card";
|
||||
@@ -36,24 +37,11 @@ export default async function ProductsPage({ params: { locale } }: ProductsPageP
|
||||
const t = await getTranslations({ locale: localeKey, namespace: "productsPage" });
|
||||
|
||||
return (
|
||||
<Container className="flex flex-col gap-section py-10 lg:py-14">
|
||||
<MotionFade>
|
||||
<AppCard level={3}>
|
||||
<CardContent className="p-6 lg:p-10">
|
||||
<div className="flex items-center gap-3">
|
||||
<Boxes className="h-5 w-5 text-brand-primary" />
|
||||
<h1 className="text-3xl font-semibold text-foreground sm:text-4xl">
|
||||
{t("title")}
|
||||
</h1>
|
||||
</div>
|
||||
<p className="mt-4 max-w-3xl text-base text-muted-foreground sm:text-lg">
|
||||
{t("intro")}
|
||||
</p>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
<>
|
||||
<PageHero title={t("title")} description={t("intro")} />
|
||||
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
<Container className="flex flex-col gap-section pb-12 lg:pb-16">
|
||||
<section className="grid gap-4 md:grid-cols-3">
|
||||
{productItems.map((item, index) => (
|
||||
<MotionFade key={item.slug} delay={index * 0.05}>
|
||||
<AppCard interactive>
|
||||
@@ -84,7 +72,8 @@ export default async function ProductsPage({ params: { locale } }: ProductsPageP
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
))}
|
||||
</section>
|
||||
</Container>
|
||||
</section>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { 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 { buildLocalizedMetadata } from "@/lib/metadata";
|
||||
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
||||
@@ -36,8 +37,11 @@ export default async function SuccessPage({ params: { locale } }: SuccessPagePro
|
||||
const t = await getTranslations({ locale: localeKey, namespace: "successPage" });
|
||||
|
||||
return (
|
||||
<Container size="narrow" className="py-12 lg:py-16">
|
||||
<MotionFade className="w-full">
|
||||
<>
|
||||
<PageHero title={t("title")} description={t("text")} />
|
||||
|
||||
<Container size="narrow" className="pb-12 lg:pb-16">
|
||||
<MotionFade className="w-full">
|
||||
<AppCard level={3}>
|
||||
<CardContent className="p-8 text-center">
|
||||
<CheckCircle2 className="mx-auto h-12 w-12 text-status-success" />
|
||||
@@ -57,7 +61,8 @@ export default async function SuccessPage({ params: { locale } }: SuccessPagePro
|
||||
</div>
|
||||
</CardContent>
|
||||
</AppCard>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</MotionFade>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user