diff --git a/src/renderer/components/modals/ConfirmDialog.tsx b/src/renderer/components/modals/ConfirmDialog.tsx
new file mode 100644
index 0000000..f082d5f
--- /dev/null
+++ b/src/renderer/components/modals/ConfirmDialog.tsx
@@ -0,0 +1,36 @@
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
+import { Button } from '@/components/ui/button';
+import { useAppStore, type ConfirmProps } from '@/stores/app-store';
+
+export function ConfirmDialog(props: ConfirmProps) {
+ const closeModal = useAppStore((s) => s.closeModal);
+ const { title, body, confirmLabel = 'Confirm', cancelLabel = 'Cancel', destructive, onConfirm, onCancel } = props;
+
+ const handleConfirm = async () => {
+ await onConfirm();
+ closeModal();
+ };
+ const handleCancel = () => {
+ onCancel?.();
+ closeModal();
+ };
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/tests/component/modals/ConfirmDialog.test.tsx b/tests/component/modals/ConfirmDialog.test.tsx
new file mode 100644
index 0000000..89790ac
--- /dev/null
+++ b/tests/component/modals/ConfirmDialog.test.tsx
@@ -0,0 +1,43 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { ConfirmDialog } from '@/components/modals/ConfirmDialog';
+import { useAppStore } from '@/stores/app-store';
+
+describe('ConfirmDialog', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ useAppStore.setState({ modal: { kind: null } } as any);
+ });
+
+ it('renders title, body, and confirm/cancel labels', () => {
+ render(
+ {}}
+ />
+ );
+ expect(screen.getByText(/delete file/i)).toBeInTheDocument();
+ expect(screen.getByText(/cannot be undone/i)).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /^delete$/i })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
+ });
+
+ it('calls onConfirm and closes on confirm click', async () => {
+ const onConfirm = vi.fn();
+ render();
+ await userEvent.click(screen.getByRole('button', { name: /confirm/i }));
+ expect(onConfirm).toHaveBeenCalledTimes(1);
+ expect(useAppStore.getState().modal).toEqual({ kind: null });
+ });
+
+ it('destructive variant uses destructive button class', () => {
+ render(
+ {}} />
+ );
+ const btn = screen.getByRole('button', { name: /confirm/i });
+ expect(btn.className).toContain('bg-destructive');
+ });
+});
\ No newline at end of file