159 lines
6.5 KiB
TypeScript
159 lines
6.5 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import { useTranslation } from "@/i18n/navigation";
|
||
import Footer from "@/app/components/Footer";
|
||
import { apiFetch } from "@/app/lib/api-client";
|
||
|
||
export default function FeedbackPage() {
|
||
const { t } = useTranslation("feedback");
|
||
const [submitted, setSubmitted] = useState(false);
|
||
const [form, setForm] = useState({
|
||
type: "suggestion" as "suggestion" | "bug",
|
||
email: "",
|
||
title: "",
|
||
content: "",
|
||
});
|
||
|
||
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="max-w-[640px] mx-auto px-4 sm:px-6 py-8 sm:py-12">
|
||
<header className="mb-8">
|
||
<h1 className="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-gray-100 flex items-center gap-2">
|
||
<span className="w-1 h-8 bg-[#ff4d4f] rounded-full" />
|
||
{t("title")}
|
||
</h1>
|
||
<p className="text-gray-500 dark:text-gray-400 mt-1 text-sm sm:text-base">
|
||
{t("subtitle")}
|
||
</p>
|
||
</header>
|
||
|
||
{submitted ? (
|
||
<div className="bg-white dark:bg-gray-900 rounded-2xl shadow-sm border border-gray-100 dark:border-gray-800 p-8 text-center">
|
||
<span className="text-5xl mb-4 block">🙏</span>
|
||
<h2 className="text-xl font-bold text-gray-900 dark:text-gray-100 mb-2">
|
||
{t("successTitle")}
|
||
</h2>
|
||
<p className="text-gray-500 dark:text-gray-400 mb-6">
|
||
{t("successDesc")}
|
||
</p>
|
||
<button
|
||
onClick={() => {
|
||
setSubmitted(false);
|
||
setForm({ type: "suggestion", email: "", title: "", content: "" });
|
||
}}
|
||
className="px-6 py-2.5 rounded-xl border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300 font-medium hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||
>
|
||
{t("submitAnother")}
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<form
|
||
onSubmit={handleSubmit}
|
||
className="bg-white dark:bg-gray-900 rounded-2xl shadow-sm border border-gray-100 dark:border-gray-800 p-6 sm:p-8 space-y-6"
|
||
>
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
{t("type")} *
|
||
</label>
|
||
<div className="flex gap-3">
|
||
<label className="flex-1 flex items-center justify-center gap-2 p-3 rounded-xl border cursor-pointer transition-colors has-[:checked]:border-[#ff4d4f] has-[:checked]:bg-[#ff4d4f]/5">
|
||
<input
|
||
type="radio"
|
||
name="type"
|
||
value="suggestion"
|
||
checked={form.type === "suggestion"}
|
||
onChange={(e) =>
|
||
setForm((f) => ({ ...f, type: e.target.value as "suggestion" }))
|
||
}
|
||
className="sr-only"
|
||
/>
|
||
<span>💡</span>
|
||
<span>{t("suggestion")}</span>
|
||
</label>
|
||
<label className="flex-1 flex items-center justify-center gap-2 p-3 rounded-xl border cursor-pointer transition-colors has-[:checked]:border-[#ff4d4f] has-[:checked]:bg-[#ff4d4f]/5">
|
||
<input
|
||
type="radio"
|
||
name="type"
|
||
value="bug"
|
||
checked={form.type === "bug"}
|
||
onChange={(e) =>
|
||
setForm((f) => ({ ...f, type: e.target.value as "bug" }))
|
||
}
|
||
className="sr-only"
|
||
/>
|
||
<span>🐛</span>
|
||
<span>{t("bug")}</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
{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 px-4 py-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#ff4d4f]/20 focus:border-[#ff4d4f]"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
{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 px-4 py-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#ff4d4f]/20 focus:border-[#ff4d4f]"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
{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 px-4 py-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-[#ff4d4f]/20 focus:border-[#ff4d4f] resize-none"
|
||
/>
|
||
</div>
|
||
|
||
<p className="text-xs text-gray-400 dark:text-gray-500">
|
||
已接入本地 FastAPI + PocketBase,提交后会写入 feedback 集合。
|
||
</p>
|
||
|
||
<button
|
||
type="submit"
|
||
className="w-full py-3.5 rounded-xl bg-[#ff4d4f] text-white font-semibold hover:bg-[#ff7a45] transition-colors"
|
||
>
|
||
{t("submit")}
|
||
</button>
|
||
</form>
|
||
)}
|
||
</main>
|
||
<Footer />
|
||
</div>
|
||
);
|
||
}
|