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.
139 lines
6.0 KiB
TypeScript
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>
|
|
);
|
|
}
|