Skip to content

Commit f10a2be

Browse files
committed
feat: show feedback ticket id after submit
1 parent 0cd8435 commit f10a2be

1 file changed

Lines changed: 89 additions & 10 deletions

File tree

src/components/FeedbackButton.tsx

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FormEvent, useMemo, useState } from "react";
22
import { invoke } from "@tauri-apps/api/core";
33
import { openUrl } from "@tauri-apps/plugin-opener";
4-
import { Loader2, Mail, MessageSquarePlus, Send } from "lucide-react";
4+
import { CheckCircle2, Copy, ExternalLink, Loader2, Mail, MessageSquarePlus, Send } from "lucide-react";
55
import { version as APP_VERSION } from "../../package.json";
66
import { Button } from "./ui/button";
77
import {
@@ -18,6 +18,8 @@ import { toast } from "./ui/toast";
1818
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
1919

2020
const FEEDBACK_EMAIL = "mark@lovstudio.ai";
21+
const MIN_MESSAGE_CHARS = 4;
22+
const TICKETS_URL = "https://lovstudio.ai/account/tickets";
2123

2224
const categoryOptions = [
2325
{ value: "bug", label: "问题" },
@@ -45,16 +47,23 @@ function getTimezone() {
4547
}
4648
}
4749

50+
function charCount(value: string) {
51+
return Array.from(value).length;
52+
}
53+
4854
export function FeedbackButton() {
4955
const [open, setOpen] = useState(false);
5056
const [category, setCategory] = useState<FeedbackCategory>("idea");
5157
const [message, setMessage] = useState("");
5258
const [contact, setContact] = useState("");
5359
const [submitting, setSubmitting] = useState(false);
5460
const [error, setError] = useState("");
61+
const [submittedTicket, setSubmittedTicket] = useState<FeedbackSubmitResult | null>(null);
5562

5663
const trimmedMessage = message.trim();
57-
const canSubmit = trimmedMessage.length >= 4 && !submitting;
64+
const messageLength = charCount(trimmedMessage);
65+
const isMessageTooShort = messageLength > 0 && messageLength < MIN_MESSAGE_CHARS;
66+
const canSubmit = messageLength >= MIN_MESSAGE_CHARS && !submitting;
5867

5968
const selectedCategoryLabel = useMemo(
6069
() => categoryOptions.find((item) => item.value === category)?.label ?? "建议",
@@ -66,12 +75,15 @@ export function FeedbackButton() {
6675
setMessage("");
6776
setContact("");
6877
setError("");
78+
setSubmittedTicket(null);
6979
};
7080

7181
const handleOpenChange = (nextOpen: boolean) => {
7282
setOpen(nextOpen);
7383
if (nextOpen) {
7484
setError("");
85+
} else {
86+
resetForm();
7587
}
7688
};
7789

@@ -99,6 +111,27 @@ export function FeedbackButton() {
99111
}
100112
};
101113

114+
const handleCopyTicketId = async () => {
115+
if (!submittedTicket?.feedbackId) return;
116+
117+
try {
118+
await navigator.clipboard.writeText(submittedTicket.feedbackId);
119+
toast.success("工单 ID 已复制");
120+
} catch (err) {
121+
const message = err instanceof Error ? err.message : String(err);
122+
toast.error(`复制失败: ${message}`);
123+
}
124+
};
125+
126+
const handleOpenTickets = async () => {
127+
try {
128+
await openUrl(TICKETS_URL);
129+
} catch (err) {
130+
const message = err instanceof Error ? err.message : String(err);
131+
toast.error(`打开用户中心失败: ${message}`);
132+
}
133+
};
134+
102135
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
103136
event.preventDefault();
104137
if (!canSubmit) return;
@@ -107,7 +140,7 @@ export function FeedbackButton() {
107140
setError("");
108141

109142
try {
110-
await invoke<FeedbackSubmitResult>("submit_feedback", {
143+
const result = await invoke<FeedbackSubmitResult>("submit_feedback", {
111144
payload: {
112145
category,
113146
message: trimmedMessage,
@@ -124,9 +157,9 @@ export function FeedbackButton() {
124157
},
125158
});
126159

127-
toast.success("反馈已提交");
128-
resetForm();
129-
setOpen(false);
160+
setSubmittedTicket(result);
161+
setMessage("");
162+
toast.success("反馈已提交,请复制工单 ID");
130163
} catch (err) {
131164
const message = err instanceof Error ? err.message : String(err);
132165
setError(message);
@@ -158,11 +191,47 @@ export function FeedbackButton() {
158191
<DialogHeader>
159192
<DialogTitle className="font-serif">提交反馈</DialogTitle>
160193
<DialogDescription>
161-
同步到 lovstudio.ai,并通知 {FEEDBACK_EMAIL}
194+
{submittedTicket
195+
? "反馈已进入工单系统。"
196+
: `同步到 lovstudio.ai,并通知 ${FEEDBACK_EMAIL}。`}
162197
</DialogDescription>
163198
</DialogHeader>
164199

165-
<form className="space-y-4" onSubmit={handleSubmit}>
200+
{submittedTicket ? (
201+
<div className="space-y-4">
202+
<div className="rounded-xl border border-primary/30 bg-primary/5 p-4">
203+
<div className="flex items-start gap-3">
204+
<CheckCircle2 className="mt-0.5 h-5 w-5 text-primary" />
205+
<div className="min-w-0 flex-1 space-y-2">
206+
<p className="font-medium text-foreground">反馈已提交</p>
207+
<p className="text-sm text-muted-foreground">
208+
复制工单 ID,后续可以在用户中心查看处理状态。
209+
</p>
210+
<div className="flex items-center gap-2 rounded-lg border border-border bg-background px-3 py-2">
211+
<code className="min-w-0 flex-1 truncate font-mono text-sm text-foreground">
212+
{submittedTicket.feedbackId}
213+
</code>
214+
<Button type="button" size="sm" variant="outline" onClick={handleCopyTicketId}>
215+
<Copy className="mr-2 h-4 w-4" />
216+
复制
217+
</Button>
218+
</div>
219+
</div>
220+
</div>
221+
</div>
222+
223+
<DialogFooter className="gap-2 sm:space-x-0">
224+
<Button type="button" variant="outline" onClick={handleOpenTickets}>
225+
<ExternalLink className="mr-2 h-4 w-4" />
226+
用户中心
227+
</Button>
228+
<Button type="button" onClick={() => handleOpenChange(false)}>
229+
完成
230+
</Button>
231+
</DialogFooter>
232+
</div>
233+
) : (
234+
<form className="space-y-4" onSubmit={handleSubmit}>
166235
<div className="grid grid-cols-3 gap-2">
167236
{categoryOptions.map((item) => (
168237
<button
@@ -190,6 +259,12 @@ export function FeedbackButton() {
190259
maxLength={5000}
191260
className="min-h-32 w-full resize-none rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground shadow-sm outline-none transition-colors placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring"
192261
/>
262+
<div className="flex items-center justify-between text-xs text-muted-foreground">
263+
<span className={isMessageTooShort ? "text-destructive" : ""}>
264+
{isMessageTooShort ? `至少输入 ${MIN_MESSAGE_CHARS} 个字符` : " "}
265+
</span>
266+
<span>{messageLength}/5000</span>
267+
</div>
193268
</div>
194269

195270
<div className="space-y-2">
@@ -198,9 +273,12 @@ export function FeedbackButton() {
198273
id="feedback-contact"
199274
value={contact}
200275
onChange={(event) => setContact(event.target.value)}
201-
placeholder="邮箱、微信或其他联系方式(可选)"
276+
placeholder="建议填写 lovstudio.ai 登录邮箱(可选)"
202277
maxLength={200}
203278
/>
279+
<p className="text-xs text-muted-foreground">
280+
使用登录邮箱提交后,可在用户中心查看已提交工单。
281+
</p>
204282
</div>
205283

206284
{error && (
@@ -219,7 +297,8 @@ export function FeedbackButton() {
219297
提交
220298
</Button>
221299
</DialogFooter>
222-
</form>
300+
</form>
301+
)}
223302
</DialogContent>
224303
</Dialog>
225304
</>

0 commit comments

Comments
 (0)