Add contact email confirmation and abuse protection
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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,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
@@ -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,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 (
|
||||
|
||||
Reference in New Issue
Block a user