- 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
93 lines
3.2 KiB
TypeScript
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>
|
|
);
|
|
}
|