diff --git a/app/feedback/page.tsx b/app/feedback/page.tsx new file mode 100644 index 00000000..d497bdfc --- /dev/null +++ b/app/feedback/page.tsx @@ -0,0 +1,180 @@ +"use client"; + +/** + * /feedback — single-page form for bug reports, feature requests, and + * general comments. Submissions persist to the BFF and trigger an + * admin notification email. No follow-up UI beyond a success toast + * and a redirect back to /. + * + * Authed visitors don't see the contact-email field — the BFF reads + * their account email from the session cookie. Anonymous visitors get + * the field and must fill it in so we have a reply path. + */ + +import { useState } from "react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { ArrowLeft, MessageSquare, Send } from "lucide-react"; +import { toast } from "sonner"; +import { feedback as feedbackAPI, SocialError } from "@/lib/social"; +import { useAuth } from "@/components/auth/auth-provider"; + +type Category = "bug" | "feature" | "general" | "other"; + +const CATEGORIES: { value: Category; label: string; description: string }[] = [ + { value: "general", label: "General feedback", description: "Thoughts, impressions, suggestions — anything else." }, + { value: "bug", label: "Bug report", description: "Something is broken or behaving unexpectedly." }, + { value: "feature", label: "Feature request", description: "An idea for something new on the site." }, + { value: "other", label: "Other", description: "Doesn't fit the above? Use this." }, +]; + +export default function FeedbackPage() { + const router = useRouter(); + const { user, loading } = useAuth(); + const authed = !loading && !!user; + + const [category, setCategory] = useState("general"); + const [message, setMessage] = useState(""); + const [contactEmail, setContactEmail] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setError(null); + const trimmed = message.trim(); + if (!trimmed) { + setError("Message is required."); + return; + } + if (!authed && !contactEmail.trim()) { + setError("Email is required so we can reply."); + return; + } + setSubmitting(true); + try { + await feedbackAPI.submit({ + category, + message: trimmed, + contact_email: authed ? undefined : contactEmail.trim(), + }); + toast.success("Thanks for the feedback!", { + description: "We've forwarded it to the team.", + duration: 5000, + }); + setMessage(""); + setContactEmail(""); + router.push("/"); + } catch (err) { + setError(err instanceof SocialError ? err.message : "Could not send feedback."); + } finally { + setSubmitting(false); + } + } + + const selected = CATEGORIES.find((c) => c.value === category); + + return ( +
+ + Back to home + + +
+ + + +
+

+ Send feedback +

+

+ Tell us what's working, what's broken, or what you'd like to see next. We read every message. +

+
+
+ +
+
+ + +

+ {selected?.description} +

+
+ +
+ +