refactor: remove remaining browser confirm dialogs from job actions
This commit is contained in:
@@ -24,7 +24,7 @@ import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
|
|||||||
|
|
||||||
import { api } from "../api";
|
import { api } from "../api";
|
||||||
import { useToast } from "../toast";
|
import { useToast } from "../toast";
|
||||||
import { confirmAction, promptForValue } from "../dialogs";
|
import { useDialogActions } from "../dialogs";
|
||||||
|
|
||||||
interface AttachmentItem {
|
interface AttachmentItem {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -59,6 +59,7 @@ function guessKind(fileName: string): string {
|
|||||||
|
|
||||||
export default function Attachments({ jobId }: { jobId: number }) {
|
export default function Attachments({ jobId }: { jobId: number }) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const { confirmAction, promptForValue } = useDialogActions();
|
||||||
const [items, setItems] = useState<AttachmentItem[]>([]);
|
const [items, setItems] = useState<AttachmentItem[]>([]);
|
||||||
const [previewOpen, setPreviewOpen] = useState(false);
|
const [previewOpen, setPreviewOpen] = useState(false);
|
||||||
const [preview, setPreview] = useState<{ url: string; type: string; name: string } | null>(null);
|
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]);
|
}, [preview?.url]);
|
||||||
|
|
||||||
const rename = async (a: AttachmentItem) => {
|
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;
|
if (!next || next.trim() === a.fileName) return;
|
||||||
try {
|
try {
|
||||||
await api.patch(`/attachments/${a.id}`, { fileName: next.trim() });
|
await api.patch(`/attachments/${a.id}`, { fileName: next.trim() });
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import JobDetailsDialog from "./JobDetailsDialog";
|
|||||||
import EditJobDialog from "./EditJobDialog";
|
import EditJobDialog from "./EditJobDialog";
|
||||||
import { useToast } from "../toast";
|
import { useToast } from "../toast";
|
||||||
import SavedViewsMenu, { SavedViewParams } from "./SavedViewsMenu";
|
import SavedViewsMenu, { SavedViewParams } from "./SavedViewsMenu";
|
||||||
|
import { useDialogActions } from "../dialogs";
|
||||||
|
|
||||||
interface JobApplication {
|
interface JobApplication {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -205,17 +206,17 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
|||||||
setSelectedIds((prev) => checked ? [...prev, id] : prev.filter((x) => x !== id));
|
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 === 0) return false;
|
||||||
if (jobsToDelete.length === 1) {
|
if (jobsToDelete.length === 1) {
|
||||||
const job = jobsToDelete[0];
|
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) => {
|
const softDelete = async (job: JobApplication) => {
|
||||||
if (!confirmDelete([job])) return;
|
if (!(await confirmDelete([job]))) return;
|
||||||
try {
|
try {
|
||||||
await api.delete(`/jobapplications/${job.id}`);
|
await api.delete(`/jobapplications/${job.id}`);
|
||||||
toast("Job moved to trash.", "success", { label: "Undo", onClick: () => { void restore(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) => {
|
const runBulkAction = async (action: "delete" | "restore" | "status", value?: string) => {
|
||||||
if (selectedIds.length === 0) return;
|
if (selectedIds.length === 0) return;
|
||||||
const selectedJobs = jobs.filter((job) => selectedIds.includes(job.id));
|
const selectedJobs = jobs.filter((job) => selectedIds.includes(job.id));
|
||||||
if (action === "delete" && !confirmDelete(selectedJobs)) return;
|
if (action === "delete" && !(await confirmDelete(selectedJobs))) return;
|
||||||
try {
|
try {
|
||||||
await Promise.all(selectedIds.map((id) => {
|
await Promise.all(selectedIds.map((id) => {
|
||||||
if (action === "delete") return api.delete(`/jobapplications/${id}`);
|
if (action === "delete") return api.delete(`/jobapplications/${id}`);
|
||||||
@@ -401,8 +402,3 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
s); setStatusAnchor(null); setStatusJobId(null); }}>Set {s}</MenuItem>)}
|
|
||||||
</Menu>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<button onClick={() => void prompt({ title: 'Rename attachment', message: 'Choose a new name', defaultValue: 'resume.pdf', confirmLabel: 'Rename' })}>
|
||||||
|
Open prompt
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('renders app-owned prompt dialog', async () => {
|
||||||
|
render(
|
||||||
|
<PromptProvider>
|
||||||
|
<Demo />
|
||||||
|
</PromptProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user