172 lines
7.1 KiB
TypeScript
172 lines
7.1 KiB
TypeScript
"use client";
|
|
|
|
import { Suspense, useEffect, useState } from "react";
|
|
import { useSearchParams } from "next/navigation";
|
|
import { useTranslation } from "@/i18n/navigation";
|
|
import Footer from "@/app/components/Footer";
|
|
import { apiFetch } from "@/app/lib/api-client";
|
|
|
|
type FeedbackType = "suggestion" | "bug" | "report";
|
|
|
|
function FeedbackForm() {
|
|
const { t } = useTranslation("feedback");
|
|
const searchParams = useSearchParams();
|
|
const initialType = searchParams.get("type") === "report" ? "report" : "suggestion";
|
|
const reportTarget = searchParams.get("target") || "";
|
|
|
|
const [submitted, setSubmitted] = useState(false);
|
|
const [form, setForm] = useState({
|
|
type: initialType as FeedbackType,
|
|
email: "",
|
|
title: "",
|
|
content: "",
|
|
targetId: reportTarget,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (reportTarget) {
|
|
setForm((prev) => ({
|
|
...prev,
|
|
type: "report",
|
|
targetId: reportTarget,
|
|
title: prev.title || t("reportTitle").replace("{target}", reportTarget),
|
|
content: prev.content || t("reportContent").replace("{target}", reportTarget),
|
|
}));
|
|
}
|
|
}, [reportTarget, t]);
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
await apiFetch("/api/feedback", {
|
|
method: "POST",
|
|
body: JSON.stringify(form),
|
|
});
|
|
setSubmitted(true);
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-[#fafafa] dark:bg-gray-950">
|
|
<main className="mx-auto max-w-[640px] px-4 py-8 sm:px-6 sm:py-12">
|
|
<header className="mb-8">
|
|
<h1 className="flex items-center gap-2 text-2xl font-bold text-gray-900 dark:text-gray-100 sm:text-3xl">
|
|
<span className="h-8 w-1 rounded-full bg-[#ff4d4f]" />
|
|
{form.type === "report" ? t("reportPageTitle") : t("title")}
|
|
</h1>
|
|
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400 sm:text-base">
|
|
{form.type === "report" ? t("reportPageSubtitle") : t("subtitle")}
|
|
</p>
|
|
</header>
|
|
|
|
{submitted ? (
|
|
<div className="rounded-2xl border border-gray-100 bg-white p-8 text-center shadow-sm dark:border-gray-800 dark:bg-gray-900">
|
|
<span className="mb-4 block text-5xl">🙏</span>
|
|
<h2 className="mb-2 text-xl font-bold text-gray-900 dark:text-gray-100">{t("successTitle")}</h2>
|
|
<p className="mb-6 text-gray-500 dark:text-gray-400">{t("successDesc")}</p>
|
|
<button
|
|
onClick={() => {
|
|
setSubmitted(false);
|
|
setForm({
|
|
type: "suggestion",
|
|
email: "",
|
|
title: "",
|
|
content: "",
|
|
targetId: "",
|
|
});
|
|
}}
|
|
className="rounded-xl border border-gray-200 px-6 py-2.5 font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
|
|
>
|
|
{t("submitAnother")}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<form
|
|
onSubmit={handleSubmit}
|
|
className="space-y-6 rounded-2xl border border-gray-100 bg-white p-6 shadow-sm dark:border-gray-800 dark:bg-gray-900 sm:p-8"
|
|
>
|
|
<div>
|
|
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">{t("type")} *</label>
|
|
<div className="flex flex-wrap gap-3">
|
|
{(["suggestion", "bug", "report"] as FeedbackType[]).map((type) => (
|
|
<label
|
|
key={type}
|
|
className="flex flex-1 min-w-[120px] cursor-pointer items-center justify-center gap-2 rounded-xl border p-3 transition-colors has-[:checked]:border-[#ff4d4f] has-[:checked]:bg-[#ff4d4f]/5"
|
|
>
|
|
<input
|
|
type="radio"
|
|
name="type"
|
|
value={type}
|
|
checked={form.type === type}
|
|
onChange={() => setForm((f) => ({ ...f, type }))}
|
|
className="sr-only"
|
|
/>
|
|
<span>{type === "suggestion" ? "💡" : type === "bug" ? "🐛" : "🚩"}</span>
|
|
<span>{t(type)}</span>
|
|
</label>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{form.type === "report" && form.targetId ? (
|
|
<div className="rounded-xl bg-rose-50 px-4 py-3 text-sm text-rose-800 dark:bg-rose-950/30 dark:text-rose-200">
|
|
{t("reportTargetLabel")}: <code className="font-mono">{form.targetId}</code>
|
|
</div>
|
|
) : null}
|
|
|
|
<div>
|
|
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">{t("email")} *</label>
|
|
<input
|
|
type="email"
|
|
required
|
|
value={form.email}
|
|
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
|
|
placeholder={t("emailPlaceholder")}
|
|
className="w-full rounded-xl border border-gray-200 bg-white px-4 py-3 text-gray-900 placeholder-gray-400 focus:border-[#ff4d4f] focus:outline-none focus:ring-2 focus:ring-[#ff4d4f]/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">{t("titleLabel")} *</label>
|
|
<input
|
|
type="text"
|
|
required
|
|
value={form.title}
|
|
onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}
|
|
placeholder={t("titlePlaceholder")}
|
|
className="w-full rounded-xl border border-gray-200 bg-white px-4 py-3 text-gray-900 placeholder-gray-400 focus:border-[#ff4d4f] focus:outline-none focus:ring-2 focus:ring-[#ff4d4f]/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">{t("content")} *</label>
|
|
<textarea
|
|
required
|
|
rows={5}
|
|
value={form.content}
|
|
onChange={(e) => setForm((f) => ({ ...f, content: e.target.value }))}
|
|
placeholder={t("contentPlaceholder")}
|
|
className="w-full resize-none rounded-xl border border-gray-200 bg-white px-4 py-3 text-gray-900 placeholder-gray-400 focus:border-[#ff4d4f] focus:outline-none focus:ring-2 focus:ring-[#ff4d4f]/20 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-100"
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
className="w-full rounded-xl bg-[#ff4d4f] py-3.5 font-semibold text-white transition-colors hover:bg-[#ff7a45]"
|
|
>
|
|
{t("submit")}
|
|
</button>
|
|
</form>
|
|
)}
|
|
</main>
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function FeedbackPage() {
|
|
return (
|
|
<Suspense fallback={<div className="min-h-screen bg-[#fafafa] dark:bg-gray-950" />}>
|
|
<FeedbackForm />
|
|
</Suspense>
|
|
);
|
|
}
|