Files
petfeed/src/app/join/page.tsx
T
admin 4b17537a9a feat(i18n): add EN/DE language support with next-intl
- Install next-intl@4 with cookie-based locale switching (NEXT_LOCALE)
- Add LanguageSwitcher component in Sidebar footer
- Translate Nav, PostTypeSheet, Feed, Explore, WelcomeCard, PetForm,
  AvatarUpload, and Onboarding pages (EN + DE)
- Add SpeciesTranslation and BreedTranslation DB models for locale-aware
  taxonomy; listSpecies and listBreeds accept optional locale param
- Seed German translations for all 3 species and 45 breeds
- Add SQL migration script for Supabase (add_i18n_translations.sql)
- fix(invite): secure cookie flag and set-invite route for HTTPS
2026-06-22 14:01:16 +02:00

93 lines
3.2 KiB
TypeScript

import { redirect } from "next/navigation";
import { cookies } from "next/headers";
import { prisma } from "@/lib/prisma";
interface JoinPageProps {
searchParams: Promise<{ code?: string; error?: string }>;
}
async function submitCode(formData: FormData) {
"use server";
const code = (formData.get("code") as string | null)?.trim().toUpperCase();
if (!code) redirect("/join?error=missing");
const invite = await prisma.inviteCode.findUnique({ where: { code } });
if (!invite || invite.usedById || invite.revoked) {
redirect(`/join?error=invalid`);
}
const cookieStore = await cookies();
cookieStore.set("pf_invite", code, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 60,
path: "/",
});
redirect("/sign-up");
}
export default async function JoinPage({ searchParams }: JoinPageProps) {
const { code: codeParam, error } = await searchParams;
// Code in URL → hand off to route handler which can set cookies
if (codeParam) {
redirect(`/api/auth/set-invite?code=${encodeURIComponent(codeParam)}`);
}
const errorMessage =
error === "invalid"
? "Ungültiger oder bereits verwendeter Einladungscode."
: error === "missing"
? "Bitte gib einen Einladungscode ein."
: null;
return (
<main className="min-h-screen flex items-center justify-center bg-zinc-950 px-4">
<div className="w-full max-w-sm">
{/* Logo / brand */}
<div className="mb-8 text-center">
<p className="text-2xl font-bold text-zinc-100 tracking-tight">PawFeed</p>
<p className="text-sm text-zinc-500 mt-1">Alpha · Nur auf Einladung</p>
</div>
<div className="rounded-2xl border border-zinc-800 bg-zinc-900 p-6 shadow-xl">
<h1 className="text-lg font-semibold text-zinc-100 mb-1">
Einladungscode eingeben
</h1>
<p className="text-sm text-zinc-500 mb-5">
PawFeed ist aktuell nur per Einladung zugänglich. Du brauchst einen Code von einem Mitglied.
</p>
<form action={submitCode} className="space-y-4">
<div>
<input
name="code"
type="text"
placeholder="z.B. PAWS-X7K2"
autoComplete="off"
autoCapitalize="characters"
spellCheck={false}
className="w-full rounded-lg border border-zinc-700 bg-zinc-800 px-4 py-2.5 text-sm font-mono text-zinc-100 placeholder:text-zinc-600 focus:outline-none focus:ring-2 focus:ring-orange-500 transition-shadow uppercase"
/>
{errorMessage && (
<p className="mt-2 text-xs text-red-400">{errorMessage}</p>
)}
</div>
<button
type="submit"
className="w-full rounded-lg bg-orange-500 hover:bg-orange-600 active:bg-orange-700 px-4 py-2.5 text-sm font-semibold text-white transition-colors"
>
Weiter
</button>
</form>
</div>
<p className="mt-6 text-center text-xs text-zinc-600">
Noch kein Code? Bitte einen Freund, der PawFeed-Mitglied ist.
</p>
</div>
</main>
);
}