Add contact email confirmation and abuse protection

This commit is contained in:
t.indorf
2026-05-16 11:38:30 +02:00
parent 9542def530
commit 0b80c62beb
12 changed files with 376 additions and 17 deletions
+65
View File
@@ -0,0 +1,65 @@
import type { Metadata } from "next";
import { LogOut, ShieldCheck } from "lucide-react";
import { UserRole } from "@prisma/client";
import { signOut } from "@/auth";
import { requireRole } from "@/lib/auth-utils";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
export const metadata: Metadata = {
title: "Systemadmin · Kita-Planer",
robots: {
index: false,
follow: false,
},
};
export default async function AdminPage() {
const session = await requireRole([UserRole.SUPERADMIN]);
return (
<div className="flex min-h-screen items-center justify-center bg-muted/30 px-4 py-12">
<Card className="w-full max-w-xl">
<CardHeader className="space-y-3">
<div className="flex h-11 w-11 items-center justify-center rounded-md bg-primary text-primary-foreground">
<ShieldCheck className="h-5 w-5" />
</div>
<div>
<CardTitle>Systemadmin-Bereich</CardTitle>
<CardDescription className="mt-2">
Du bist als Superadmin angemeldet. Eine zentrale Systemverwaltung
ist noch nicht umgesetzt, deshalb endet der Login hier statt auf
einer nicht vorhandenen Seite.
</CardDescription>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="rounded-md border bg-muted/40 p-4 text-sm text-muted-foreground">
Angemeldet als{" "}
<span className="font-medium text-foreground">
{session.user.email ?? session.user.name ?? "Superadmin"}
</span>
</div>
<form
action={async () => {
"use server";
await signOut({ redirectTo: "/" });
}}
>
<Button type="submit" variant="outline">
<LogOut className="h-4 w-4" />
Abmelden
</Button>
</form>
</CardContent>
</Card>
</div>
);
}
+41 -3
View File
@@ -11,6 +11,7 @@ import {
contactRequestSchema,
contactRequestTypeLabels,
type ContactRequestInput,
type ContactSubmissionPayload,
} from "@/lib/contact-schema";
import { Button } from "@/components/ui/button";
import {
@@ -44,16 +45,27 @@ export function ContactForm() {
},
});
function onSubmit(values: ContactRequestInput) {
function onSubmit(
values: ContactRequestInput,
event?: React.BaseSyntheticEvent,
) {
const formEl = event?.target as HTMLFormElement | undefined;
const website =
(formEl?.elements.namedItem("website") as HTMLInputElement | null)
?.value ?? "";
startTransition(async () => {
const result = await submitContactRequest(values);
const payload: ContactSubmissionPayload = { ...values, website };
const result = await submitContactRequest(payload);
if (!result.success) {
toast.error(result.error);
return;
}
toast.success("Danke! Wir melden uns in Kürze bei euch.");
toast.success(
"Danke! Wir haben dir eine Bestätigung an deine E-Mail-Adresse geschickt.",
);
form.reset({
name: "",
kitaName: "",
@@ -61,6 +73,10 @@ export function ContactForm() {
requestType: "DEMO",
message: "",
});
const honeypot = formEl?.elements.namedItem(
"website",
) as HTMLInputElement | null;
if (honeypot) honeypot.value = "";
});
}
@@ -171,6 +187,28 @@ export function ContactForm() {
)}
/>
<div
aria-hidden="true"
style={{
position: "absolute",
left: "-10000px",
top: "auto",
width: "1px",
height: "1px",
overflow: "hidden",
}}
>
<label htmlFor="contact-website">Website (bitte leer lassen)</label>
<input
id="contact-website"
type="text"
name="website"
tabIndex={-1}
autoComplete="off"
defaultValue=""
/>
</div>
<Button type="submit" size="lg" disabled={isPending} className="h-12">
{isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
+2 -1
View File
@@ -2,6 +2,7 @@ import Link from "next/link";
import { redirect } from "next/navigation";
import { auth } from "@/auth";
import { getPostLoginRedirect } from "@/lib/post-login-redirect";
import {
Card,
CardContent,
@@ -17,7 +18,7 @@ export const metadata = { title: "Anmelden · Kita-Planer" };
export default async function LoginPage() {
const session = await auth();
if (session?.user?.id) {
redirect(session.user.kitaId ? "/dashboard" : "/onboarding");
redirect(getPostLoginRedirect(session.user));
}
return (
+2 -1
View File
@@ -15,6 +15,7 @@ import {
} from "lucide-react";
import { auth } from "@/auth";
import { getPostLoginRedirect } from "@/lib/post-login-redirect";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
@@ -91,7 +92,7 @@ const proofPoints = [
export default async function LandingPage() {
const session = await auth();
if (session?.user?.id) {
redirect(session.user.kitaId ? "/dashboard" : "/onboarding");
redirect(getPostLoginRedirect(session.user));
}
return (
+2 -1
View File
@@ -2,6 +2,7 @@ import Link from "next/link";
import { redirect } from "next/navigation";
import { auth } from "@/auth";
import { getPostLoginRedirect } from "@/lib/post-login-redirect";
import {
Card,
CardContent,
@@ -17,7 +18,7 @@ export const metadata = { title: "Registrieren · Kita-Planer" };
export default async function RegisterPage() {
const session = await auth();
if (session?.user) {
redirect(session.user.kitaId ? "/dashboard" : "/onboarding");
redirect(getPostLoginRedirect(session.user));
}
return (