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:
@@ -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">
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user