feat: add submission deletion support, transition webhook templates to index-based placeholders, enforce image-only uploads, and localize UI to Thai.

This commit is contained in:
2026-05-28 20:48:38 +07:00 Unverified
parent dedf92965b
commit 1ae0cf2992
7 changed files with 108 additions and 27 deletions
+2 -2
View File
@@ -73,7 +73,7 @@ export default function FormExtraClient({ form }: FormExtraClientProps) {
className="min-h-24"
/>
<p className="text-sm text-muted-foreground">
Use <code>{`{QuestionLabel}`}</code> to insert answers. Example: <code>{`{Name}`}</code> or <code>{`{skill}`}</code>.
Use <code>{`{1}`}</code>, <code>{`{2}`}</code> to insert answers according to question order. Example: <code>{`{1}`}</code> for the first question.
</p>
</div>
<div className="space-y-2">
@@ -86,7 +86,7 @@ export default function FormExtraClient({ form }: FormExtraClientProps) {
className="min-h-24"
/>
<p className="text-sm text-muted-foreground">
Used when a user updates their existing submission. Use <code>{`{QuestionLabel}`}</code> to insert answers.
Used when a user updates their existing submission. Use <code>{`{1}`}</code>, <code>{`{2}`}</code> to insert answers.
</p>
</div>
<div className="flex justify-end pt-2">
-1
View File
@@ -52,7 +52,6 @@ export default function SubmissionDetailClient({
if (!text) return;
navigator.clipboard.writeText(text);
setCopiedId(id);
toast.success("Answer copied to clipboard!");
setTimeout(() => setCopiedId(null), 2000);
};
+63 -12
View File
@@ -10,8 +10,9 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Dialog, DialogContent, DialogTrigger, DialogTitle } from "@/components/ui/dialog";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { toast } from "sonner";
import { submitForm } from "@/app/actions/submissions";
import { submitForm, deleteOwnSubmission } from "@/app/actions/submissions";
import { Loader2, LogOut } from "lucide-react";
import { signOut } from "next-auth/react";
import Image from "next/image";
@@ -141,13 +142,13 @@ export function FormFillerClient({ form, questions, initialAnswers = {} }: FormF
if (q.type === "checkbox") {
const selected = checkboxAnswers[q.id] || [];
if (selected.length === 0) {
toast.error(`"${q.label}" is required.`);
toast.error(`จำเป็นต้องตอบ "${q.label}"`);
return;
}
} else {
const ans = answers[q.id] || "";
if (!ans.trim()) {
toast.error(`"${q.label}" is required.`);
toast.error(`จำเป็นต้องตอบ "${q.label}"`);
return;
}
}
@@ -171,12 +172,31 @@ export function FormFillerClient({ form, questions, initialAnswers = {} }: FormF
const res = await submitForm(form.id, formattedAnswers);
if (res.ok) {
toast.success(isUpdating ? "Form updated successfully!" : "Form submitted successfully!");
toast.success(isUpdating ? "อัปเดตฟอร์มสำเร็จ!" : "ส่งฟอร์มสำเร็จ!");
// Fields are kept as-is; localStorage already reflects current state
}
} catch (err: any) {
console.error(err);
toast.error(err.message || "Failed to submit form.");
toast.error(err.message || "ไม่สามารถส่งฟอร์มได้");
} finally {
setSubmitting(false);
}
};
const handleDelete = async () => {
setSubmitting(true);
try {
const res = await deleteOwnSubmission(form.id);
if (res.ok) {
toast.success("ลบข้อมูลสำเร็จ!");
localStorage.removeItem(LS_KEY(form.id));
setAnswers({});
setCheckboxAnswers({});
router.refresh();
}
} catch (err: any) {
console.error(err);
toast.error(err.message || "ไม่สามารถลบข้อมูลได้");
} finally {
setSubmitting(false);
}
@@ -189,7 +209,7 @@ export function FormFillerClient({ form, questions, initialAnswers = {} }: FormF
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle className="text-center text-xl">
This form is disabled for now
ฟอร์มนี้ถูกปิดใช้งานชั่วคราว
</CardTitle>
</CardHeader>
</Card>
@@ -245,7 +265,7 @@ export function FormFillerClient({ form, questions, initialAnswers = {} }: FormF
{q.type === "text" && (
<Input
type="text"
placeholder="Your answer"
placeholder="คำตอบของคุณ"
required={q.required}
value={answers[q.id] || ""}
onChange={(e) => handleTextChange(q.id, e.target.value)}
@@ -256,7 +276,7 @@ export function FormFillerClient({ form, questions, initialAnswers = {} }: FormF
{q.type === "textarea" && (
<Textarea
placeholder="Your answer"
placeholder="คำตอบของคุณ"
required={q.required}
value={answers[q.id] || ""}
onChange={(e) => handleTextChange(q.id, e.target.value)}
@@ -311,17 +331,48 @@ export function FormFillerClient({ form, questions, initialAnswers = {} }: FormF
))}
</div>
<div className="flex justify-end items-center pt-4">
<div className="flex justify-end items-center gap-3 pt-4">
{isUpdating && (
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
type="button"
variant="destructive"
disabled={submitting || !form.isOpen}
>
ลบ
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>ยืนยันการลบข้อมูล?</AlertDialogTitle>
<AlertDialogDescription>
คุณแน่ใจหรือไม่ว่าต้องการลบข้อมูลที่ส่งไปแล้ว? การกระทำนี้ไม่สามารถย้อนกลับได้
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={submitting}>ยกเลิก</AlertDialogCancel>
<AlertDialogAction
variant="destructive"
onClick={handleDelete}
disabled={submitting}
>
ยืนยันการลบ
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
<Button type="submit" disabled={submitting || !form.isOpen} className="min-w-28">
{submitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Submitting
กำลังส่ง
</>
) : isUpdating ? (
"Update Submission"
"อัปเดตข้อมูล"
) : (
"Submit"
"ส่ง"
)}
</Button>
</div>
+2 -2
View File
@@ -13,8 +13,8 @@ export default async function FormLayout({
return (
<div className="min-h-screen flex flex-col">
<header className="border-b border-border px-6 py-3 flex items-center justify-between">
<span className="font-semibold text-sm text-muted-foreground tracking-wide uppercase">
Forms
<span className="font-semibold text-sm">
ฟอร์ม
</span>
{session?.user && (
<FormLayoutClient