diff --git a/frontend/app/dataset/[id]/page.tsx b/frontend/app/dataset/[id]/page.tsx index 697addd..a4bfa29 100644 --- a/frontend/app/dataset/[id]/page.tsx +++ b/frontend/app/dataset/[id]/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useParams } from "next/navigation"; +import { useParams, useRouter } from "next/navigation"; import Link from "next/link"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useMutation, useQuery } from "convex/react"; @@ -25,6 +25,7 @@ import { useAppAuth, useAppClerk, useAppConvexAuth, useAppUser } from "@/lib/app export default function DatasetPage() { const params = useParams(); + const router = useRouter(); const { isLoading: authLoading, isAuthenticated } = useAppConvexAuth(); const { userId, getToken } = useAppAuth(); const { user } = useAppUser(); @@ -36,6 +37,8 @@ export default function DatasetPage() { const [exportOpen, setExportOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false); const [confirmPopulate, setConfirmPopulate] = useState(false); + const [confirmDelete, setConfirmDelete] = useState(false); + const [deleting, setDeleting] = useState(false); const [savingRefreshCadence, setSavingRefreshCadence] = useState(false); const [savingMaxRowCount, setSavingMaxRowCount] = useState(false); const [maxRowCountSaveError, setMaxRowCountSaveError] = useState(null); @@ -55,6 +58,7 @@ export default function DatasetPage() { authLoading ? "skip" : { datasetId }, ); const updateRefreshSettings = useMutation(api.datasets.updateRefreshSettings); + const removeDataset = useMutation(api.datasets.remove); const updateMaxRowCount = useMutation(api.datasets.updateMaxRowCount); const usage = useQuery( api.quota.getMy, @@ -288,6 +292,21 @@ export default function DatasetPage() { } } + async function handleDelete() { + if (!dataset || deleting || dataset.status === "building" || dataset.status === "updating") return; + setDeleting(true); + try { + await removeDataset({ id: dataset._id }); + setConfirmDelete(false); + router.replace("/dashboard"); + } catch (err) { + console.error("[delete] failed", err); + captureException(err, { operation: "dataset_delete", datasetId: dataset._id }); + } finally { + setDeleting(false); + } + } + // Computed before the loading guard so the useEffect below can depend on it // without hitting the TDZ. Optional chaining handles the pre-load undefined state. const isDatasetBusy = dataset?.status === "building" || dataset?.status === "updating"; @@ -302,7 +321,6 @@ export default function DatasetPage() { return () => clearTimeout(id); } }, [isDatasetBusy]); - if (authLoading || dataset === undefined || rows === undefined) { return (
@@ -414,6 +432,7 @@ export default function DatasetPage() { handlePopulate(); } }} + onDelete={isOwner && !isDatasetBusy ? () => { setSettingsOpen(false); setConfirmDelete(true); } : undefined} />
@@ -476,6 +495,15 @@ export default function DatasetPage() { onCancel={() => setConfirmPopulate(false)} /> )} + + {confirmDelete && ( + { if (!deleting) setConfirmDelete(false); }} + /> + )}
); } @@ -581,6 +609,7 @@ function SettingsDropdown({ onMaxRowCountChange, onUpdate, onPopulate, + onDelete, }: { open: boolean; onToggle: () => void; @@ -599,6 +628,7 @@ function SettingsDropdown({ onMaxRowCountChange: (maxRowCount: number) => void; onUpdate: () => void; onPopulate: () => void; + onDelete?: () => void; }) { const ref = useRef(null); const [maxRowCountInput, setMaxRowCountInput] = useState(String(maxRowCount)); @@ -727,6 +757,18 @@ function SettingsDropdown({ ); })}
+ {onDelete && ( +
+ +
+ )} )} @@ -859,3 +901,58 @@ function ConfirmPopulateModal({ ); } + +/* ------------------------------------------------------------------ */ +/* Confirm delete modal */ +/* ------------------------------------------------------------------ */ + +function ConfirmDeleteModal({ + datasetName, + deleting, + onConfirm, + onCancel, +}: { + datasetName: string; + deleting: boolean; + onConfirm: () => void; + onCancel: () => void; +}) { + useEffect(() => { + function handleKey(e: KeyboardEvent) { + if (e.key === "Escape") onCancel(); + } + document.addEventListener("keydown", handleKey); + return () => document.removeEventListener("keydown", handleKey); + }, [onCancel]); + + return ( +
{ if (e.target === e.currentTarget) onCancel(); }} + role="presentation" + > +
+

+ Delete “{datasetName}”? +

+

All rows will be permanently deleted. This can't be undone.

+
+ + +
+
+
+ ); +}