Feedback form: load categories from the BFF
The form offers the admin-managed categories, falling back to the original four if they can't be loaded. "My feedback" shows the server's labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0a23ab59f7
commit
52cd48b12e
+43
-14
@@ -9,23 +9,31 @@
|
||||
* 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.
|
||||
*
|
||||
* Categories are managed in the admin panel and loaded from the BFF. If
|
||||
* that request fails the form falls back to the original four, so it can
|
||||
* always be submitted.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { useEffect, 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 {
|
||||
feedback as feedbackAPI,
|
||||
SocialError,
|
||||
type FeedbackCategory,
|
||||
} 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." },
|
||||
// Used only if the categories can't be loaded — the original four, which
|
||||
// the BFF seeds and keeps accepting unless an admin archives them.
|
||||
const FALLBACK_CATEGORIES: FeedbackCategory[] = [
|
||||
{ key: "general", label: "General feedback", description: "Thoughts, impressions, suggestions — anything else." },
|
||||
{ key: "bug", label: "Bug report", description: "Something is broken or behaving unexpectedly." },
|
||||
{ key: "feature", label: "Feature request", description: "An idea for something new on the site." },
|
||||
{ key: "other", label: "Other", description: "Doesn't fit the above? Use this." },
|
||||
];
|
||||
|
||||
export default function FeedbackPage() {
|
||||
@@ -33,7 +41,28 @@ export default function FeedbackPage() {
|
||||
const { user, loading } = useAuth();
|
||||
const authed = !loading && !!user;
|
||||
|
||||
const [category, setCategory] = useState<Category>("general");
|
||||
const [categories, setCategories] =
|
||||
useState<FeedbackCategory[]>(FALLBACK_CATEGORIES);
|
||||
// Empty until chosen: the first category on the form.
|
||||
const [picked, setPicked] = useState("");
|
||||
const category = categories.some((c) => c.key === picked)
|
||||
? picked
|
||||
: (categories[0]?.key ?? "");
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
feedbackAPI
|
||||
.categories()
|
||||
.then((list) => {
|
||||
if (!cancelled && list?.length) setCategories(list);
|
||||
})
|
||||
.catch(() => {
|
||||
/* keep the fallback list */
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
const [message, setMessage] = useState("");
|
||||
const [contactEmail, setContactEmail] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@@ -72,7 +101,7 @@ export default function FeedbackPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const selected = CATEGORIES.find((c) => c.value === category);
|
||||
const selected = categories.find((c) => c.key === category);
|
||||
|
||||
return (
|
||||
<main className="flex-1 max-w-xl w-full mx-auto px-4 sm:px-6 py-10 sm:py-14">
|
||||
@@ -111,12 +140,12 @@ export default function FeedbackPage() {
|
||||
<select
|
||||
id="category"
|
||||
value={category}
|
||||
onChange={(e) => setCategory(e.target.value as Category)}
|
||||
onChange={(e) => setPicked(e.target.value)}
|
||||
className="mt-1.5 h-10 w-full rounded-lg border border-border bg-card px-3 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
disabled={submitting}
|
||||
>
|
||||
{CATEGORIES.map((c) => (
|
||||
<option key={c.value} value={c.value}>{c.label}</option>
|
||||
{categories.map((c) => (
|
||||
<option key={c.key} value={c.key}>{c.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
|
||||
@@ -152,7 +152,9 @@ export default function FeedbacksPage() {
|
||||
function FeedbackCard({ feedback: f }: { feedback: Feedback }) {
|
||||
const status = statusOf(f);
|
||||
const meta = STATUS_META[status];
|
||||
const category = CATEGORY_LABEL[f.category] ?? f.category;
|
||||
// The server's label tracks renames in the admin; the map covers an older
|
||||
// BFF that doesn't send one yet.
|
||||
const category = f.category_label || CATEGORY_LABEL[f.category] || f.category;
|
||||
|
||||
return (
|
||||
<li className="rounded-xl border border-border bg-card p-4 sm:p-5">
|
||||
|
||||
+15
-1
@@ -212,6 +212,8 @@ export type Feedback = {
|
||||
id: string;
|
||||
user_id?: string;
|
||||
category: string;
|
||||
/** The category's current display name (renames apply to past feedback). */
|
||||
category_label?: string;
|
||||
message: string;
|
||||
contact_email?: string;
|
||||
created_at: string;
|
||||
@@ -560,13 +562,25 @@ export const collections = {
|
||||
|
||||
// ─── Feedback ────────────────────────────────────────────────────────────
|
||||
|
||||
/** One option on the /feedback form, managed from the admin panel. */
|
||||
export type FeedbackCategory = {
|
||||
key: string;
|
||||
label: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export const feedback = {
|
||||
/** The form's categories, in order. Public. */
|
||||
categories() {
|
||||
return call<FeedbackCategory[]>("/feedback/categories");
|
||||
},
|
||||
/** Submit a /feedback page entry. When the viewer is signed in the
|
||||
* BFF captures their user_id automatically via the auth cookie —
|
||||
* the form doesn't ask for it. `contact_email` is required only
|
||||
* for anonymous submissions; the BFF re-enforces that on its end. */
|
||||
submit(input: {
|
||||
category: "bug" | "feature" | "general" | "other";
|
||||
/** A FeedbackCategory key. */
|
||||
category: string;
|
||||
message: string;
|
||||
contact_email?: string;
|
||||
}) {
|
||||
|
||||
Reference in New Issue
Block a user