Files
petfeed/src/app/page.tsx
T
admin f899ed5f7c feat(landing): translate landing page copy (Landing i18n namespace)
The landing page was hardcoded English, so switching languages had no
visible effect on it — added a "Landing" namespace to both message
catalogs and wired src/app/page.tsx up via getTranslations, matching
the pattern already used by other server-rendered pages.
2026-08-11 20:09:13 +02:00

139 lines
6.0 KiB
TypeScript

import { auth } from "@clerk/nextjs/server";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { PawPrint, HeartPulse, MessageCircle, Images } from "lucide-react";
import { LanguageSwitcher } from "@/components/i18n/LanguageSwitcher";
/**
* Root route (/) — public landing page for logged-out visitors, redirects
* to /feed for authenticated ones (D-04).
*
* Previously an unconditional redirect("/feed"), gated entirely by
* proxy.ts's auth.protect() — so logged-out visitors AND social-media
* crawlers scraping og:image for link previews got bounced to Clerk's
* hosted sign-in before Next.js ever rendered this route's metadata.
* proxy.ts now leaves "/" out of the protected matcher so this page can
* render for everyone; the redirect-when-authenticated check happens here
* instead via a plain auth() read (not .protect()).
*/
export default async function RootPage() {
const { userId } = await auth();
if (userId) redirect("/feed");
const t = await getTranslations("Landing");
const features = [
{ icon: PawPrint, title: t("feature1Title"), body: t("feature1Body") },
{ icon: Images, title: t("feature2Title"), body: t("feature2Body") },
{ icon: HeartPulse, title: t("feature3Title"), body: t("feature3Body") },
{ icon: MessageCircle, title: t("feature4Title"), body: t("feature4Body") },
] as const;
return (
<div className="min-h-screen bg-background">
{/* Header */}
<header className="flex items-center justify-between px-6 py-5 sm:px-10">
<div className="flex items-center gap-2">
<PawPrint className="h-6 w-6 text-orange-500" fill="currentColor" strokeWidth={0} aria-hidden="true" />
<span className="text-lg font-bold text-foreground">PawFeed</span>
</div>
<LanguageSwitcher />
</header>
{/* Hero */}
<section className="relative overflow-hidden bg-gradient-to-br from-orange-400 via-orange-500 to-orange-700">
<PawPrint
className="pointer-events-none absolute -right-24 -top-20 h-[480px] w-[480px] text-white opacity-[0.14] sm:-right-16"
fill="white"
strokeWidth={0}
aria-hidden="true"
/>
<div className="relative z-10 mx-auto max-w-5xl px-6 py-20 sm:px-10 sm:py-28">
<div className="max-w-2xl text-center sm:text-left">
<h1 className="text-6xl font-extrabold tracking-tight text-white sm:text-7xl">
PawFeed
</h1>
<p className="mx-auto mt-5 max-w-lg text-xl font-medium text-orange-50 sm:mx-0 sm:text-2xl">
{t("tagline")}
</p>
<div className="mt-10 flex flex-col justify-center gap-3 sm:flex-row sm:justify-start">
<Link
href="/sign-up"
className="inline-flex items-center justify-center rounded-xl bg-white px-7 py-3.5 text-base font-semibold text-orange-600 shadow-lg transition-colors hover:bg-orange-50"
>
{t("getStarted")}
</Link>
<Link
href="/log-in"
className="inline-flex items-center justify-center rounded-xl border-2 border-white/70 px-7 py-3.5 text-base font-semibold text-white transition-colors hover:bg-white/10"
>
{t("logIn")}
</Link>
</div>
</div>
</div>
</section>
{/* App preview — browser-chrome mockup around a real feed screenshot */}
<section className="px-6 py-20 sm:px-10">
<div className="mx-auto max-w-4xl">
<div className="overflow-hidden rounded-2xl border shadow-2xl">
<div className="flex items-center gap-1.5 border-b bg-muted/60 px-4 py-2.5">
<span className="h-2.5 w-2.5 rounded-full bg-red-400" />
<span className="h-2.5 w-2.5 rounded-full bg-amber-400" />
<span className="h-2.5 w-2.5 rounded-full bg-green-400" />
</div>
<div className="relative aspect-[16/10] w-full bg-muted">
<Image
src="/app-preview-feed.jpg"
alt={t("previewAlt")}
fill
sizes="(min-width: 768px) 896px, 100vw"
className="object-cover object-left-top"
priority
/>
</div>
</div>
</div>
</section>
{/* Feature overview */}
<section className="bg-muted/30 px-6 py-20 sm:px-10">
<div className="mx-auto max-w-5xl">
<h2 className="text-center text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
{t("featuresHeading")}
</h2>
<div className="mt-14 grid gap-8 sm:grid-cols-2 lg:grid-cols-4">
{features.map(({ icon: Icon, title, body }) => (
<div key={title} className="flex flex-col items-start">
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-orange-100 text-orange-600">
<Icon className="h-5.5 w-5.5" aria-hidden="true" />
</div>
<h3 className="mt-4 text-base font-semibold text-foreground">{title}</h3>
<p className="mt-1.5 text-sm leading-relaxed text-muted-foreground">{body}</p>
</div>
))}
</div>
</div>
</section>
{/* Footer */}
<footer className="border-t px-6 py-10 sm:px-10">
<div className="mx-auto flex max-w-5xl flex-col items-center justify-between gap-4 text-sm text-muted-foreground sm:flex-row">
<span>© {new Date().getFullYear()} PawFeed</span>
<nav className="flex gap-5">
<Link href="/impressum" className="hover:text-foreground">{t("footerImpressum")}</Link>
<Link href="/datenschutz" className="hover:text-foreground">{t("footerDatenschutz")}</Link>
<Link href="/nutzungsbedingungen" className="hover:text-foreground">{t("footerAgb")}</Link>
</nav>
</div>
</footer>
</div>
);
}