From fdae966461657794940d60ea81c6ed3d5e4c5311 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 6 Jul 2026 08:51:24 +0000 Subject: [PATCH] feat(scenarios): type-to-confirm delete modal, replacing browser confirm() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap the native confirm() popup for the app's shadcn Dialog pattern (as in Settings → Danger Zone): the destructive button stays disabled until the user types "delete" (case-insensitive), with Enter-to-submit and a proper explanation of what's removed. On a delete error the dialog closes so the inline error is visible. Co-Authored-By: Claude Fable 5 --- .../scenarios/[scenarioId]/DetailActions.tsx | 71 ++++++++++++++++--- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/scenarios/[scenarioId]/DetailActions.tsx b/src/app/portfolio/[slug]/(portfolio)/scenarios/[scenarioId]/DetailActions.tsx index 63cbcf54..9fb942b2 100644 --- a/src/app/portfolio/[slug]/(portfolio)/scenarios/[scenarioId]/DetailActions.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/scenarios/[scenarioId]/DetailActions.tsx @@ -4,6 +4,13 @@ import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useMutation } from "@tanstack/react-query"; +import { + Dialog, + DialogContent, + DialogTitle, + DialogFooter, +} from "@/app/shadcn_components/ui/dialog"; +import { Button } from "@/app/shadcn_components/ui/button"; const btn = "rounded-xl border border-gray-200 bg-white px-4 py-2 text-sm font-semibold text-brandblue transition hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-45"; @@ -36,6 +43,9 @@ export function DetailActions({ const [editing, setEditing] = useState(false); const [draftName, setDraftName] = useState(initialName); const [error, setError] = useState(null); + const [deleteOpen, setDeleteOpen] = useState(false); + const [deleteConfirmation, setDeleteConfirmation] = useState(""); + const deleteConfirmed = deleteConfirmation.trim().toLowerCase() === "delete"; const rename = useMutation({ mutationFn: async (newName: string) => { @@ -68,7 +78,10 @@ export function DetailActions({ if (!res.ok) throw new Error((await res.json()).error ?? "Delete failed"); }, onSuccess: () => router.push(`/portfolio/${portfolioId}/scenarios`), - onError: (e: Error) => setError(e.message), + onError: (e: Error) => { + setError(e.message); + setDeleteOpen(false); + }, }); return ( @@ -162,17 +175,12 @@ export function DetailActions({ <> Nothing has been modelled against this scenario — deleting removes @@ -182,6 +190,51 @@ export function DetailActions({ )} {error &&
{error}
} + + + + + Delete “{name}”? + +

+ Nothing has been modelled against this scenario — deleting removes + only the configuration. This can't be undone. +

+
+ + setDeleteConfirmation(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter" && deleteConfirmed && !remove.isLoading) + remove.mutate(); + }} + className="w-full rounded-xl border border-gray-200 px-3 py-2 text-sm outline-none transition focus:border-red-300 focus:shadow-[0_0_0_3px_rgba(185,28,28,.10)]" + /> +
+ + + + +
+
); }