Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 99 additions & 2 deletions frontend/app/dataset/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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();
Expand All @@ -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<string | null>(null);
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// 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";
Expand All @@ -302,7 +321,6 @@ export default function DatasetPage() {
return () => clearTimeout(id);
}
}, [isDatasetBusy]);

if (authLoading || dataset === undefined || rows === undefined) {
return (
<div className="flex flex-1 items-center justify-center">
Expand Down Expand Up @@ -414,6 +432,7 @@ export default function DatasetPage() {
handlePopulate();
}
}}
onDelete={isOwner && !isDatasetBusy ? () => { setSettingsOpen(false); setConfirmDelete(true); } : undefined}
/>

<div className="w-px h-4 bg-border mx-0.5" />
Expand Down Expand Up @@ -476,6 +495,15 @@ export default function DatasetPage() {
onCancel={() => setConfirmPopulate(false)}
/>
)}

{confirmDelete && (
<ConfirmDeleteModal
datasetName={dataset.name}
deleting={deleting}
onConfirm={handleDelete}
onCancel={() => { if (!deleting) setConfirmDelete(false); }}
/>
)}
</div>
);
}
Expand Down Expand Up @@ -581,6 +609,7 @@ function SettingsDropdown({
onMaxRowCountChange,
onUpdate,
onPopulate,
onDelete,
}: {
open: boolean;
onToggle: () => void;
Expand All @@ -599,6 +628,7 @@ function SettingsDropdown({
onMaxRowCountChange: (maxRowCount: number) => void;
onUpdate: () => void;
onPopulate: () => void;
onDelete?: () => void;
}) {
const ref = useRef<HTMLDivElement>(null);
const [maxRowCountInput, setMaxRowCountInput] = useState(String(maxRowCount));
Expand Down Expand Up @@ -727,6 +757,18 @@ function SettingsDropdown({
);
})}
</div>
{onDelete && (
<div className="border-t border-border p-1">
<button
type="button"
onClick={onDelete}
className="w-full flex items-center gap-2 px-2 py-1.5 rounded-lg text-xs text-red-500 hover:bg-red-500/[0.08] transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>
Delete dataset
</button>
</div>
)}
</div>
)}
</div>
Expand Down Expand Up @@ -859,3 +901,58 @@ function ConfirmPopulateModal({
</div>
);
}

/* ------------------------------------------------------------------ */
/* 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 (
<div
className="fixed inset-0 z-[100] flex items-center justify-center bg-black/40"
onClick={(e) => { if (e.target === e.currentTarget) onCancel(); }}
role="presentation"
>
<div role="dialog" aria-modal="true" aria-labelledby="confirm-delete-title" className="w-full max-w-xs rounded-xl border border-border bg-surface shadow-2xl p-4 text-center">
<p id="confirm-delete-title" className="text-sm font-semibold text-foreground">
Delete &ldquo;{datasetName}&rdquo;?
</p>
<p className="mt-1 text-xs text-muted">All rows will be permanently deleted. This can&apos;t be undone.</p>
<div className="mt-4 flex gap-2">
<button
onClick={onCancel}
disabled={deleting}
className="flex-1 rounded-lg bg-foreground/[0.06] py-1.5 text-xs font-medium text-foreground hover:bg-foreground/[0.1] transition-colors disabled:opacity-40"
>
Cancel
</button>
<button
onClick={onConfirm}
disabled={deleting}
className="flex-1 rounded-lg bg-red-600 py-1.5 text-xs font-medium text-white hover:bg-red-700 transition-colors disabled:opacity-60"
>
{deleting ? "Deleting..." : "Delete dataset"}
</button>
</div>
</div>
</div>
);
}