From 31ab34de6060cf0725c11e819c564f2b75fa6831 Mon Sep 17 00:00:00 2001 From: Erza Bytyci Date: Tue, 19 May 2026 10:02:53 +0000 Subject: [PATCH] feat: add feedback form, remove: talkback form --- gradesa/src/app/api/contact/route.js | 59 +++++++ gradesa/src/app/api/talkback/route.js | 61 ------- .../talkback.css => contact/contact.css} | 2 +- gradesa/src/app/contact/page.js | 96 +++++++++++ gradesa/src/app/talkback/layout.js | 9 - gradesa/src/app/talkback/page.js | 156 ------------------ gradesa/src/components/ui/sidebar/sidebar.js | 4 +- gradesa/src/middleware.js | 12 +- 8 files changed, 167 insertions(+), 232 deletions(-) create mode 100644 gradesa/src/app/api/contact/route.js delete mode 100644 gradesa/src/app/api/talkback/route.js rename gradesa/src/app/{talkback/talkback.css => contact/contact.css} (98%) create mode 100644 gradesa/src/app/contact/page.js delete mode 100644 gradesa/src/app/talkback/layout.js delete mode 100644 gradesa/src/app/talkback/page.js diff --git a/gradesa/src/app/api/contact/route.js b/gradesa/src/app/api/contact/route.js new file mode 100644 index 0000000..01201b3 --- /dev/null +++ b/gradesa/src/app/api/contact/route.js @@ -0,0 +1,59 @@ +import { z } from "zod"; +import { withAuth } from "@/backend/middleware/withAuth"; +import sendEmail from "@/backend/mailService"; + +const contactSchema = z.object({ + message: z + .string() + .trim() + .min(1, "Nachricht ist erforderlich.") + .max(2000, "Nachricht ist zu lang."), +}); + +export const POST = withAuth( + async (req) => { + try { + const body = await req.json(); + const result = contactSchema.safeParse(body); + + if (!result.success) { + return Response.json( + { message: result.error.issues[0]?.message || "Ungültige Eingabe." }, + { status: 400 } + ); + } + + const { message } = result.data; + const contactEmail = process.env.CONTACT_EMAIL; + + if (!contactEmail) { + return Response.json( + { message: "Kontakt-E-Mail ist nicht konfiguriert." }, + { status: 500 } + ); + } + + await sendEmail({ + to: contactEmail, + subject: "Kontaktieren Sie uns", + text: message, + }); + + return Response.json({ message: "Nachricht erfolgreich gesendet." }); + } catch (error) { + return Response.json( + { + message: + error instanceof Error + ? error.message + : "Nachricht konnte nicht gesendet werden.", + }, + { status: 500 } + ); + } + }, + { + requireAuth: true, + requireAdmin: false, + } +); diff --git a/gradesa/src/app/api/talkback/route.js b/gradesa/src/app/api/talkback/route.js deleted file mode 100644 index bc1775d..0000000 --- a/gradesa/src/app/api/talkback/route.js +++ /dev/null @@ -1,61 +0,0 @@ -import { z } from "zod"; -import { withAuth } from "@/backend/middleware/withAuth"; -import sendEmail from "@/backend/mailService"; - -const talkbackSchema = z.object({ - email: z - .string() - .trim() - .email("Bitte geben Sie eine gültige E-Mail-Adresse des Empfängers ein."), - subject: z - .string() - .trim() - .min(1, "Betreff ist erforderlich.") - .max(200, "Betreff ist zu lang."), - message: z - .string() - .trim() - .min(1, "Nachricht ist erforderlich.") - .max(2000, "Nachricht ist zu lang."), -}); - -export const POST = withAuth( - async (req) => { - try { - const body = await req.json(); - const result = talkbackSchema.safeParse(body); - - if (!result.success) { - return Response.json( - { - message: result.error.issues[0]?.message || "Ungültige Eingabe.", - }, - { status: 400 } - ); - } - - const { email, subject, message } = result.data; - - await sendEmail({ - to: email, - subject, - text: message, - }); - - return Response.json({ - message: "Nachricht erfolgreich gesendet.", - }); - } catch { - return Response.json( - { - message: "Nachricht konnte nicht gesendet werden.", - }, - { status: 500 } - ); - } - }, - { - requireAuth: true, - requireAdmin: true, - } -); diff --git a/gradesa/src/app/talkback/talkback.css b/gradesa/src/app/contact/contact.css similarity index 98% rename from gradesa/src/app/talkback/talkback.css rename to gradesa/src/app/contact/contact.css index cb0f967..1a76c70 100644 --- a/gradesa/src/app/talkback/talkback.css +++ b/gradesa/src/app/contact/contact.css @@ -12,7 +12,7 @@ border-radius: var(--radius-2xl); padding: var(--u-xl); box-shadow: 0 10px 24px rgb(0 0 0 / 10%); - width: 570px; + width: fit-content; } .auth-box { diff --git a/gradesa/src/app/contact/page.js b/gradesa/src/app/contact/page.js new file mode 100644 index 0000000..b86ba1d --- /dev/null +++ b/gradesa/src/app/contact/page.js @@ -0,0 +1,96 @@ +"use client"; + +import "./contact.css"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { withBasePath } from "@/shared/utils/basePath"; +import { useUser } from "@/context/user.context"; + +export default function ContactPage() { + const { + auth: { isLoggedIn, isAuthResolved }, + } = useUser(); + const [message, setMessage] = useState(""); + const [error, setError] = useState(""); + const [isSending, setIsSending] = useState(false); + + // Show nothing while session is being resolved, or if not logged in. + // Middleware already redirects unauthenticated requests to /auth/login. + if (!isAuthResolved || !isLoggedIn) return null; + + const handleSubmit = async (event) => { + event.preventDefault(); + + setError(""); + + const trimmedMessage = message.trim(); + + if (!trimmedMessage) { + setError("Nachricht ist erforderlich."); + return; + } + + try { + setIsSending(true); + + const response = await fetch(withBasePath("/api/contact"), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ message: trimmedMessage }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error( + data.message || "Nachricht konnte nicht gesendet werden." + ); + } + + setMessage(""); + alert("Nachricht erfolgreich gesendet."); + } catch (requestError) { + setError(requestError.message || "Etwas ist schief gelaufen."); + } finally { + setIsSending(false); + } + }; + + return ( +
+
+

Kontaktieren Sie unser Team

+ + {error &&

{error}

} + +
+
+ +