From b177656efc4430d7edfee2840abc0276c87119e8 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Tue, 17 Mar 2026 11:53:10 +0100 Subject: [PATCH] feat: replace native confirm() dialogs with custom delete confirmation dialogs - Add Dialog component for delete confirmation in trainers page - Add Dialog component for delete confirmation in workouts page - Show entity name in confirmation dialog - Dark theme styling to match app design - Add loading state with spinner during deletion --- admin-web/app/trainers/page.tsx | 64 +++++++++++++++++++++++++++++---- admin-web/app/workouts/page.tsx | 64 +++++++++++++++++++++++++++++---- 2 files changed, 116 insertions(+), 12 deletions(-) diff --git a/admin-web/app/trainers/page.tsx b/admin-web/app/trainers/page.tsx index 1eec42e..11f6999 100644 --- a/admin-web/app/trainers/page.tsx +++ b/admin-web/app/trainers/page.tsx @@ -4,6 +4,14 @@ import { useEffect, useState } from "react"; import { supabase } from "@/lib/supabase"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { Plus, Trash2, Edit, Loader2, Users, AlertCircle } from "lucide-react"; import Link from "next/link"; import { toast } from "sonner"; @@ -16,6 +24,8 @@ export default function TrainersPage() { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [deletingId, setDeletingId] = useState(null); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [trainerToDelete, setTrainerToDelete] = useState(null); useEffect(() => { fetchTrainers(); @@ -49,14 +59,19 @@ export default function TrainersPage() { } }; - const handleDelete = async (id: string) => { - if (!confirm("Are you sure you want to delete this trainer?")) return; + const openDeleteDialog = (trainer: Trainer) => { + setTrainerToDelete(trainer); + setDeleteDialogOpen(true); + }; - setDeletingId(id); + const handleDelete = async () => { + if (!trainerToDelete) return; + + setDeletingId(trainerToDelete.id); try { - const { error } = await supabase.from("trainers").delete().eq("id", id); + const { error } = await supabase.from("trainers").delete().eq("id", trainerToDelete.id); if (error) throw error; - setTrainers(trainers.filter((t) => t.id !== id)); + setTrainers(trainers.filter((t) => t.id !== trainerToDelete.id)); toast.success("Trainer deleted", { description: "The trainer has been removed successfully." }); @@ -65,6 +80,8 @@ export default function TrainersPage() { toast.error("Failed to delete trainer"); } finally { setDeletingId(null); + setDeleteDialogOpen(false); + setTrainerToDelete(null); } }; @@ -151,7 +168,7 @@ export default function TrainersPage() { variant="ghost" size="icon" className="text-neutral-400 hover:text-red-500" - onClick={() => handleDelete(trainer.id)} + onClick={() => openDeleteDialog(trainer)} disabled={deletingId === trainer.id} > {deletingId === trainer.id ? ( @@ -167,6 +184,41 @@ export default function TrainersPage() { ))} )} + + {/* Delete Confirmation Dialog */} + + + + Delete Trainer + + Are you sure you want to delete {trainerToDelete?.name}? This action cannot be undone. + + + + + + + + ); } diff --git a/admin-web/app/workouts/page.tsx b/admin-web/app/workouts/page.tsx index 2a493b4..54bde1e 100644 --- a/admin-web/app/workouts/page.tsx +++ b/admin-web/app/workouts/page.tsx @@ -18,6 +18,14 @@ import { import { Plus, Trash2, Edit, Loader2, Eye } from "lucide-react"; import { toast } from "sonner"; import type { Database } from "@/lib/supabase"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; type Workout = Database["public"]["Tables"]["workouts"]["Row"]; @@ -26,6 +34,8 @@ export default function WorkoutsPage() { const [workouts, setWorkouts] = useState([]); const [loading, setLoading] = useState(true); const [deletingId, setDeletingId] = useState(null); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [workoutToDelete, setWorkoutToDelete] = useState(null); useEffect(() => { fetchWorkouts(); @@ -47,14 +57,19 @@ export default function WorkoutsPage() { } }; - const handleDelete = async (id: string) => { - if (!confirm("Are you sure you want to delete this workout?")) return; + const openDeleteDialog = (workout: Workout) => { + setWorkoutToDelete(workout); + setDeleteDialogOpen(true); + }; - setDeletingId(id); + const handleDelete = async () => { + if (!workoutToDelete) return; + + setDeletingId(workoutToDelete.id); try { - const { error } = await supabase.from("workouts").delete().eq("id", id); + const { error } = await supabase.from("workouts").delete().eq("id", workoutToDelete.id); if (error) throw error; - setWorkouts(workouts.filter((w) => w.id !== id)); + setWorkouts(workouts.filter((w) => w.id !== workoutToDelete.id)); toast.success("Workout deleted", { description: "The workout has been removed successfully." }); @@ -63,6 +78,8 @@ export default function WorkoutsPage() { toast.error("Failed to delete workout"); } finally { setDeletingId(null); + setDeleteDialogOpen(false); + setWorkoutToDelete(null); } }; @@ -159,7 +176,7 @@ export default function WorkoutsPage() { variant="ghost" size="icon" className="text-neutral-400 hover:text-red-500" - onClick={() => handleDelete(workout.id)} + onClick={() => openDeleteDialog(workout)} disabled={deletingId === workout.id} > {deletingId === workout.id ? ( @@ -177,6 +194,41 @@ export default function WorkoutsPage() { )} + + {/* Delete Confirmation Dialog */} + + + + Delete Workout + + Are you sure you want to delete "{workoutToDelete?.title}"? This action cannot be undone. + + + + + + + + ); }