90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
"use client";
|
|
|
|
import { useRef, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import type { FeedbackCategory } from "@/db/repository";
|
|
|
|
type SubmitState = { loading: boolean; message: string; error: boolean };
|
|
|
|
export function FeedbackForm() {
|
|
const router = useRouter();
|
|
const pendingKey = useRef<string | null>(null);
|
|
const [category, setCategory] = useState<FeedbackCategory>("connection_issue");
|
|
const [message, setMessage] = useState("");
|
|
const [state, setState] = useState<SubmitState>({
|
|
loading: false,
|
|
message: "",
|
|
error: false,
|
|
});
|
|
|
|
async function submit(event: React.FormEvent) {
|
|
event.preventDefault();
|
|
setState({ loading: true, message: "", error: false });
|
|
pendingKey.current ??= crypto.randomUUID();
|
|
try {
|
|
const response = await fetch("/api/feedback", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({
|
|
category,
|
|
message,
|
|
idempotencyKey: pendingKey.current,
|
|
}),
|
|
});
|
|
const body = (await response.json()) as { message?: string };
|
|
if (!response.ok) throw new Error(body.message || "提交失败");
|
|
pendingKey.current = null;
|
|
setMessage("");
|
|
setState({
|
|
loading: false,
|
|
message: "已收到。处理结果会显示在本页,不需要重复提交。",
|
|
error: false,
|
|
});
|
|
router.refresh();
|
|
} catch (error) {
|
|
setState({
|
|
loading: false,
|
|
message: error instanceof Error ? error.message : "提交失败",
|
|
error: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form className="cloud-form feedback-form" onSubmit={submit}>
|
|
<label>
|
|
<span>问题类型</span>
|
|
<select
|
|
value={category}
|
|
onChange={(event) => setCategory(event.target.value as FeedbackCategory)}
|
|
>
|
|
<option value="connection_issue">连接或接入问题</option>
|
|
<option value="bug">功能异常</option>
|
|
<option value="suggestion">使用建议</option>
|
|
<option value="other">其他</option>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
<span>详细描述</span>
|
|
<textarea
|
|
minLength={10}
|
|
maxLength={2000}
|
|
value={message}
|
|
onChange={(event) => setMessage(event.target.value)}
|
|
placeholder="请写清出现了什么、你原本想完成什么;不要粘贴令牌、密码或私密会话内容。"
|
|
required
|
|
/>
|
|
<small>{message.length}/2000 · 请勿提交密钥、令牌或会话正文</small>
|
|
</label>
|
|
<button className="button button-primary" type="submit" disabled={state.loading}>
|
|
{state.loading ? "正在提交…" : "提交反馈"}
|
|
</button>
|
|
{state.message && (
|
|
<p className={state.error ? "form-error" : "form-success"} role="status">
|
|
{state.message}
|
|
</p>
|
|
)}
|
|
</form>
|
|
);
|
|
}
|