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
This commit is contained in:
Millian Lamiaux
2026-03-17 11:53:10 +01:00
parent fc43f73b82
commit b177656efc
2 changed files with 116 additions and 12 deletions

View File

@@ -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<string | null>(null);
const [deletingId, setDeletingId] = useState<string | null>(null);
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [trainerToDelete, setTrainerToDelete] = useState<Trainer | null>(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() {
))}
</div>
)}
{/* Delete Confirmation Dialog */}
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<DialogContent className="bg-neutral-900 border-neutral-800 text-white">
<DialogHeader>
<DialogTitle>Delete Trainer</DialogTitle>
<DialogDescription className="text-neutral-400">
Are you sure you want to delete {trainerToDelete?.name}? This action cannot be undone.
</DialogDescription>
</DialogHeader>
<DialogFooter className="gap-2">
<Button
variant="outline"
onClick={() => setDeleteDialogOpen(false)}
className="border-neutral-700 text-neutral-300 hover:bg-neutral-800"
>
Cancel
</Button>
<Button
onClick={handleDelete}
disabled={!!deletingId}
className="bg-red-500 hover:bg-red-600"
>
{deletingId ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Deleting...
</>
) : (
"Delete"
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}