From 0c28e22a1cde2e1ebbc776850a81ed9f69efff3a Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sun, 22 Mar 2026 14:19:48 +0100 Subject: [PATCH] refactor: remove remaining browser confirm dialogs from job actions --- job-tracker-ui/src/components/Attachments.tsx | 5 ++-- job-tracker-ui/src/components/JobTable.tsx | 16 +++++------ job-tracker-ui/src/prompt.test.tsx | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 job-tracker-ui/src/prompt.test.tsx diff --git a/job-tracker-ui/src/components/Attachments.tsx b/job-tracker-ui/src/components/Attachments.tsx index 118c4a7..e8c3637 100644 --- a/job-tracker-ui/src/components/Attachments.tsx +++ b/job-tracker-ui/src/components/Attachments.tsx @@ -24,7 +24,7 @@ import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import { api } from "../api"; import { useToast } from "../toast"; -import { confirmAction, promptForValue } from "../dialogs"; +import { useDialogActions } from "../dialogs"; interface AttachmentItem { id: number; @@ -59,6 +59,7 @@ function guessKind(fileName: string): string { export default function Attachments({ jobId }: { jobId: number }) { const { toast } = useToast(); + const { confirmAction, promptForValue } = useDialogActions(); const [items, setItems] = useState([]); const [previewOpen, setPreviewOpen] = useState(false); const [preview, setPreview] = useState<{ url: string; type: string; name: string } | null>(null); @@ -83,7 +84,7 @@ export default function Attachments({ jobId }: { jobId: number }) { }, [preview?.url]); const rename = async (a: AttachmentItem) => { - const next = promptForValue("Rename attachment to:", a.fileName); + const next = await promptForValue("Rename attachment to:", a.fileName, { title: "Rename attachment", confirmLabel: "Rename" }); if (!next || next.trim() === a.fileName) return; try { await api.patch(`/attachments/${a.id}`, { fileName: next.trim() }); diff --git a/job-tracker-ui/src/components/JobTable.tsx b/job-tracker-ui/src/components/JobTable.tsx index 9e58b1b..d76c11f 100644 --- a/job-tracker-ui/src/components/JobTable.tsx +++ b/job-tracker-ui/src/components/JobTable.tsx @@ -45,6 +45,7 @@ import JobDetailsDialog from "./JobDetailsDialog"; import EditJobDialog from "./EditJobDialog"; import { useToast } from "../toast"; import SavedViewsMenu, { SavedViewParams } from "./SavedViewsMenu"; +import { useDialogActions } from "../dialogs"; interface JobApplication { id: number; @@ -205,17 +206,17 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col setSelectedIds((prev) => checked ? [...prev, id] : prev.filter((x) => x !== id)); }; - const confirmDelete = (jobsToDelete: JobApplication[]) => { + const confirmDelete = async (jobsToDelete: JobApplication[]) => { if (jobsToDelete.length === 0) return false; if (jobsToDelete.length === 1) { const job = jobsToDelete[0]; - return window.confirm(`Move "${job.jobTitle}" at ${job.company?.name ?? "this company"} to trash?`); + return confirmAction(`Move "${job.jobTitle}" at ${job.company?.name ?? "this company"} to trash?`, { title: "Move job to trash", confirmLabel: "Move", destructive: true }); } - return window.confirm(`Move ${jobsToDelete.length} selected jobs to trash?`); + return confirmAction(`Move ${jobsToDelete.length} selected jobs to trash?`, { title: "Move jobs to trash", confirmLabel: "Move", destructive: true }); }; const softDelete = async (job: JobApplication) => { - if (!confirmDelete([job])) return; + if (!(await confirmDelete([job]))) return; try { await api.delete(`/jobapplications/${job.id}`); toast("Job moved to trash.", "success", { label: "Undo", onClick: () => { void restore(job.id); } }); @@ -248,7 +249,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col const runBulkAction = async (action: "delete" | "restore" | "status", value?: string) => { if (selectedIds.length === 0) return; const selectedJobs = jobs.filter((job) => selectedIds.includes(job.id)); - if (action === "delete" && !confirmDelete(selectedJobs)) return; + if (action === "delete" && !(await confirmDelete(selectedJobs))) return; try { await Promise.all(selectedIds.map((id) => { if (action === "delete") return api.delete(`/jobapplications/${id}`); @@ -401,8 +402,3 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col ); } -s); setStatusAnchor(null); setStatusJobId(null); }}>Set {s})} - - - ); -} diff --git a/job-tracker-ui/src/prompt.test.tsx b/job-tracker-ui/src/prompt.test.tsx new file mode 100644 index 0000000..cc3dfd8 --- /dev/null +++ b/job-tracker-ui/src/prompt.test.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import '@testing-library/jest-dom'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { PromptProvider, usePrompt } from './prompt'; + +function Demo() { + const { prompt } = usePrompt(); + return ( + + ); +} + +test('renders app-owned prompt dialog', async () => { + render( + + + , + ); + + fireEvent.click(screen.getByRole('button', { name: /open prompt/i })); + + expect(await screen.findByText(/choose a new name/i)).toBeInTheDocument(); + expect(screen.getByDisplayValue('resume.pdf')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /rename/i })).toBeInTheDocument(); +});