Skip to content

Commit f5587a5

Browse files
Add delete course action, UI column, and confirmation dialog (#41)
Co-authored-by: Joshua Silva <72359611+joshuasilva414@users.noreply.github.com>
1 parent 50d62d7 commit f5587a5

File tree

5 files changed

+325
-36
lines changed

5 files changed

+325
-36
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
],
2020
"dependencies": {
2121
"@libsql/client": "^0.15.15",
22+
"@radix-ui/react-alert-dialog": "^1.1.15",
2223
"@radix-ui/react-avatar": "^1.1.10",
2324
"@radix-ui/react-dropdown-menu": "^2.1.16",
2425
"@radix-ui/react-checkbox": "^1.3.3",

pnpm-lock.yaml

Lines changed: 66 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions/delete-course.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use server";
2+
3+
import { createSafeActionClient } from "next-safe-action";
4+
import { z } from "zod";
5+
import { db } from "@/db";
6+
import { courses } from "@/db/schema";
7+
import { eq } from "drizzle-orm";
8+
9+
const actionClient = createSafeActionClient();
10+
11+
export const deleteCourseAction = actionClient
12+
.schema(
13+
z.object({
14+
id: z.number(),
15+
})
16+
)
17+
.action(async ({ parsedInput }) => {
18+
const { id } = parsedInput;
19+
20+
await db.delete(courses).where(eq(courses.id, id));
21+
22+
return { success: true };
23+
});

src/app/admin/courses/page.tsx

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import {
24
Table,
35
TableBody,
@@ -7,66 +9,107 @@ import {
79
TableRow,
810
} from "@/components/ui/table";
911

10-
// dummy course array
12+
import { Button } from "@/components/ui/button";
13+
14+
import {
15+
AlertDialog,
16+
AlertDialogAction,
17+
AlertDialogCancel,
18+
AlertDialogContent,
19+
AlertDialogDescription,
20+
AlertDialogFooter,
21+
AlertDialogHeader,
22+
AlertDialogTitle,
23+
AlertDialogTrigger,
24+
} from "@/components/ui/alert-dialog";
25+
26+
import { deleteCourseAction } from "@/actions/delete-course";
27+
import { useTransition } from "react";
28+
29+
// Temporary placeholder data until backend fetch is added
1130
const dummyCourses = [
12-
{
13-
id: "1",
14-
title: "Intro to Python",
15-
description: "Learn Python for beginners",
16-
status: "published",
17-
},
18-
{
19-
id: "2",
20-
title: "Web Development Basics",
21-
description: "Web dev fundamentals",
22-
status: "published",
23-
},
24-
{
25-
id: "3",
26-
title: "Data Structures",
27-
description: "Learn about core data structures",
28-
status: "unpublished",
29-
},
30-
{
31-
id: "4",
32-
title: "Advanced JavaScript",
33-
description: "Advanced topics in JavaScript",
34-
status: "published",
35-
},
36-
{
37-
id: "5",
38-
title: "React Fundamentals",
39-
description: "Build modern web apps with React",
40-
status: "unpublished",
41-
},
31+
{ id: 1, title: "Intro to Python", description: "Learn Python", status: "published" },
32+
{ id: 2, title: "Web Development Basics", description: "Web dev fundamentals", status: "published" },
33+
{ id: 3, title: "Data Structures", description: "Core data structures", status: "unpublished" },
34+
{ id: 4, title: "Advanced JavaScript", description: "Advanced JS topics", status: "published" },
35+
{ id: 5, title: "React Fundamentals", description: "React basics", status: "unpublished" },
4236
];
4337

44-
// admin course page setup
4538
export default function AdminCoursesPage() {
39+
const [isPending, startTransition] = useTransition();
40+
41+
// Calls the Safe Action to delete a course by ID
42+
function handleDelete(id: number) {
43+
startTransition(() => {
44+
deleteCourseAction({ id });
45+
});
46+
}
47+
4648
return (
4749
<div className="container mx-auto py-10">
48-
<h1 className="text-3xl font-bold mb-8">Admin Data Table</h1>
49-
50+
<h1 className="text-3xl font-bold mb-8">Admin Courses</h1>
51+
52+
{/* Table of course data rendered from placeholder array */}
5053
<div className="rounded-md border">
5154
<Table>
5255
<TableHeader>
5356
<TableRow>
5457
<TableHead>Title</TableHead>
5558
<TableHead>Description</TableHead>
5659
<TableHead>Status</TableHead>
60+
<TableHead className="text-right">Actions</TableHead>
5761
</TableRow>
5862
</TableHeader>
63+
5964
<TableBody>
6065
{dummyCourses.map((course) => (
6166
<TableRow key={course.id}>
6267
<TableCell>{course.title}</TableCell>
6368
<TableCell>{course.description}</TableCell>
6469
<TableCell>{course.status}</TableCell>
70+
71+
{/* Delete button wrapped in confirmation dialog */}
72+
<TableCell className="text-right">
73+
<AlertDialog>
74+
<AlertDialogTrigger asChild>
75+
<Button variant="destructive" size="sm">
76+
Delete
77+
</Button>
78+
</AlertDialogTrigger>
79+
80+
{/* Confirmation modal to prevent accidental deletion */}
81+
<AlertDialogContent>
82+
<AlertDialogHeader>
83+
<AlertDialogTitle>
84+
Delete “{course.title}”?
85+
</AlertDialogTitle>
86+
87+
<AlertDialogDescription>
88+
This action cannot be undone.
89+
It will permanently delete this course and all related data.
90+
</AlertDialogDescription>
91+
</AlertDialogHeader>
92+
93+
<AlertDialogFooter>
94+
<AlertDialogCancel>Cancel</AlertDialogCancel>
95+
96+
{/* Executes the delete action when confirmed */}
97+
<AlertDialogAction
98+
disabled={isPending}
99+
onClick={() => handleDelete(course.id)}
100+
>
101+
{isPending ? "Deleting..." : "Delete"}
102+
</AlertDialogAction>
103+
</AlertDialogFooter>
104+
</AlertDialogContent>
105+
</AlertDialog>
106+
</TableCell>
65107
</TableRow>
66108
))}
67109
</TableBody>
110+
68111
</Table>
69112
</div>
70113
</div>
71114
);
72-
}
115+
}

0 commit comments

Comments
 (0)