refactor: migrate global toast notifications to Sonner and update admin UI navigation, layouts, and submission workflows

This commit is contained in:
2026-05-28 20:18:45 +07:00 Unverified
parent 9693bef355
commit bc78873ff8
7 changed files with 113 additions and 70 deletions
+32 -17
View File
@@ -83,10 +83,9 @@ export default function FormEditorClient({ form, initialQuestions }: FormEditorC
displayOrder,
options: [],
});
toast.success("Question added!");
router.refresh();
} catch (err: any) {
toast.error(err.message || "Failed to add question.");
console.error("Failed to add question:", err);
}
};
@@ -111,10 +110,9 @@ export default function FormEditorClient({ form, initialQuestions }: FormEditorC
const handleDeleteQuestion = async (id: string) => {
try {
await deleteQuestion(id, form.id);
toast.success("Question deleted!");
router.refresh();
} catch (err: any) {
toast.error(err.message || "Failed to delete question.");
console.error("Failed to delete question:", err);
}
};
@@ -146,22 +144,39 @@ export default function FormEditorClient({ form, initialQuestions }: FormEditorC
formData.append("file", file);
setUploadingId(questionId);
try {
const imageUrl = await uploadImage(formData);
await handleUpdateQuestion(questionId, { imageUrl });
toast.success("Image uploaded!");
router.refresh();
} catch (err: any) {
toast.error(err.message || "Failed to upload image.");
} finally {
setUploadingId(null);
}
toast.promise(
uploadImage(formData).then(async (imageUrl) => {
await updateQuestion(questionId, form.id, { imageUrl });
setQuestions((prev) =>
prev.map((q) => (q.id === questionId ? { ...q, imageUrl } : q))
);
router.refresh();
}).finally(() => {
setUploadingId(null);
}),
{
loading: "Uploading image...",
success: "Image uploaded!",
error: (err: any) => err.message || "Failed to upload image.",
}
);
};
const handleRemoveImage = async (questionId: string) => {
await handleUpdateQuestion(questionId, { imageUrl: null });
toast.success("Image removed.");
router.refresh();
toast.promise(
updateQuestion(questionId, form.id, { imageUrl: null }).then(() => {
setQuestions((prev) =>
prev.map((q) => (q.id === questionId ? { ...q, imageUrl: null } : q))
);
router.refresh();
}),
{
loading: "Removing image...",
success: "Image removed.",
error: (err: any) => err.message || "Failed to remove image.",
}
);
};
const handleAddOption = async (question: Question) => {
-1
View File
@@ -1,6 +1,5 @@
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AdminSidebar } from "@/components/admin-sidebar";
import { Toaster } from "@/components/ui/sonner";
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import { redirect } from "next/navigation";
+21 -2
View File
@@ -2,7 +2,7 @@ import { db } from "@/db";
import { notFound } from "next/navigation";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { ArrowLeft } from "lucide-react";
import { ArrowLeft, ArrowRight } from "lucide-react";
import SubmissionDetailClient from "./client";
import { DeleteSubmissionButton } from "@/components/delete-buttons";
@@ -45,7 +45,7 @@ export default async function SubmissionDetailPage({ params }: SubmissionDetailP
const nextId = currentIndex >= 0 && currentIndex < submissionsList.length - 1 ? submissionsList[currentIndex + 1].id : null;
return (
<div className="space-y-6 max-w-2xl mx-auto">
<div className="space-y-6 w-full pb-10">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Link href="/form/admin/result">
@@ -75,6 +75,25 @@ export default async function SubmissionDetailPage({ params }: SubmissionDetailP
questions={questions}
answers={answersMap}
/>
<div className="flex justify-between items-center mt-8 pt-6 border-t">
{prevId ? (
<Link href={`/form/admin/result/${prevId}`}>
<Button variant="outline">
<ArrowLeft className="w-4 h-4 mr-2" /> Previous
</Button>
</Link>
) : (
<div />
)}
{nextId && (
<Link href={`/form/admin/result/${nextId}`}>
<Button variant="outline">
Next <ArrowRight className="w-4 h-4 ml-2" />
</Button>
</Link>
)}
</div>
</div>
);
}
+12 -15
View File
@@ -3,7 +3,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { Eye, Users } from "lucide-react";
import { Users } from "lucide-react";
import { DeleteSubmissionButton, DeleteAllSubmissionsButton } from "@/components/delete-buttons";
export const dynamic = "force-dynamic";
@@ -14,7 +14,7 @@ export default async function ResultsListPage() {
});
return (
<div className="space-y-6 max-w-4xl mx-auto">
<div className="space-y-6 w-full">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold tracking-tight">Form Submissions</h1>
@@ -39,7 +39,7 @@ export default async function ResultsListPage() {
<CardHeader>
<CardTitle>Submissions</CardTitle>
<CardDescription>
Total {submissionsList.length} responses collected. Click view to inspect individual answers.
Total {submissionsList.length} responses collected. Click on a submission to inspect individual answers.
</CardDescription>
</CardHeader>
<CardContent className="p-0">
@@ -58,23 +58,20 @@ export default async function ResultsListPage() {
: sub.userDiscordId || "Anonymous";
return (
<TableRow key={sub.id}>
<TableCell className="font-medium">
<div className="flex items-center gap-2">
<TableRow key={sub.id} className="cursor-pointer group">
<TableCell className="p-0">
<Link href={`/form/admin/result/${sub.id}`} className="flex items-center gap-2 p-4 w-full h-full font-medium">
<Users className="w-4 h-4 text-muted-foreground" />
<span>{userLabel}</span>
</div>
</Link>
</TableCell>
<TableCell className="text-muted-foreground">
{new Date(sub.submittedAt).toLocaleString()}
<TableCell className="p-0 text-muted-foreground">
<Link href={`/form/admin/result/${sub.id}`} className="flex items-center p-4 w-full h-full">
{new Date(sub.submittedAt).toLocaleString()}
</Link>
</TableCell>
<TableCell className="text-right">
<TableCell className="text-right p-4">
<div className="flex justify-end gap-2">
<Link href={`/form/admin/result/${sub.id}`}>
<Button size="sm" variant="outline" className="gap-1">
<Eye className="w-3.5 h-3.5" />
</Button>
</Link>
<DeleteSubmissionButton id={sub.id} />
</div>
</TableCell>