-
+
{loading ? 'Creating account\u2026' : 'Create Admin Account'}
-
+
)
diff --git a/frontend/src/components/colors/ColorsView.tsx b/frontend/src/components/colors/ColorsView.tsx
index f20c831..014493c 100644
--- a/frontend/src/components/colors/ColorsView.tsx
+++ b/frontend/src/components/colors/ColorsView.tsx
@@ -1,6 +1,7 @@
import { useState, useMemo, useCallback } from 'react'
import { Palette, ArrowLeft, Loader2 } from 'lucide-react'
import clsx from 'clsx'
+import { Button } from '@/components/ui/button'
import { photos as photosApi } from '../../services/api'
import { useFilterStore } from '../../store/filterStore'
import { usePhotosQuery } from '../../hooks/usePhotosQuery'
@@ -90,13 +91,15 @@ export function ColorsView() {
return (
-
-
+
{selectedGroup.label}
diff --git a/frontend/src/components/dialogs/ConfirmDialog.tsx b/frontend/src/components/dialogs/ConfirmDialog.tsx
index c400652..e0a6648 100644
--- a/frontend/src/components/dialogs/ConfirmDialog.tsx
+++ b/frontend/src/components/dialogs/ConfirmDialog.tsx
@@ -1,5 +1,12 @@
-import { useEffect } from 'react'
-import clsx from 'clsx'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Button } from '@/components/ui/button'
interface ConfirmDialogProps {
isOpen: boolean
@@ -14,8 +21,9 @@ interface ConfirmDialogProps {
}
/**
- * Tiny modal-confirmation dialog. Mirrors the AddSourceFolderDialog overlay
- * pattern (custom fixed inset-0 backdrop, no shadcn Dialog dep). Esc closes.
+ * Modal confirmation dialog. Built on the shadcn Dialog primitive:
+ * Radix handles focus trap, portal, overlay-click dismissal, Esc, and
+ * animations. We only supply title, body, and the two action buttons.
*/
export function ConfirmDialog({
isOpen,
@@ -27,49 +35,27 @@ export function ConfirmDialog({
onConfirm,
onClose,
}: ConfirmDialogProps) {
- // Esc to close.
- useEffect(() => {
- if (!isOpen) return
- const handler = (e: KeyboardEvent) => {
- if (e.key === 'Escape') onClose()
- }
- window.addEventListener('keydown', handler)
- return () => window.removeEventListener('keydown', handler)
- }, [isOpen, onClose])
-
- if (!isOpen) return null
-
return (
-
-
-
-
-
{title}
-
{message}
-
-
- {cancelLabel}
-
-
- {confirmLabel}
-
-
-
-
-
+
)
}
diff --git a/frontend/src/components/dialogs/DeleteFolderDialog.tsx b/frontend/src/components/dialogs/DeleteFolderDialog.tsx
index 7573a03..62e953e 100644
--- a/frontend/src/components/dialogs/DeleteFolderDialog.tsx
+++ b/frontend/src/components/dialogs/DeleteFolderDialog.tsx
@@ -2,6 +2,18 @@ import { useEffect, useState } from 'react'
import clsx from 'clsx'
import { Trash2, Archive } from 'lucide-react'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Button } from '@/components/ui/button'
+import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
+import { Label } from '@/components/ui/label'
+
interface DeleteFolderDialogProps {
isOpen: boolean
folderName: string
@@ -13,14 +25,12 @@ interface DeleteFolderDialogProps {
onConfirm: (mode: 'discard' | 'permanent') => void
}
+type Mode = 'discard' | 'permanent'
+
/**
- * Two-mode folder delete dialog:
- *
- * - Move to discard pile (default, soft, recoverable)
- * - Permanently delete (destructive, irreversible)
- *
- * The user picks a mode via the radio cards then clicks Delete. Esc /
- * backdrop click cancels.
+ * Two-mode folder delete dialog built on the shadcn Dialog + RadioGroup
+ * primitives (instead of the old custom fixed-inset backdrop). Esc
+ * closes, overlay click closes, focus is trapped by Radix.
*/
export function DeleteFolderDialog({
isOpen,
@@ -29,110 +39,93 @@ export function DeleteFolderDialog({
onClose,
onConfirm,
}: DeleteFolderDialogProps) {
- const [mode, setMode] = useState<'discard' | 'permanent'>('discard')
+ const [mode, setMode] = useState('discard')
// Reset mode when re-opening so the safe option is always the default.
useEffect(() => {
if (isOpen) setMode('discard')
}, [isOpen])
- // Esc to close.
- useEffect(() => {
- if (!isOpen) return
- const handler = (e: KeyboardEvent) => {
- if (e.key === 'Escape') onClose()
- }
- window.addEventListener('keydown', handler)
- return () => window.removeEventListener('keydown', handler)
- }, [isOpen, onClose])
-
- if (!isOpen) return null
-
const photoBlurb =
photoCount === undefined
? 'photos in this folder'
: photoCount === 0
- ? 'this empty folder'
- : `${photoCount} photo${photoCount === 1 ? '' : 's'} in this folder`
+ ? 'this empty folder'
+ : `${photoCount} photo${photoCount === 1 ? '' : 's'} in this folder`
return (
-
-
-
-
-
- Delete folder "{folderName}"?
-
-
+
+
+
-
- }
- title="Move photos to discard pile"
- description="Photos can be restored later from Discarded. The folder and files stay on disk."
- selected={mode === 'discard'}
- onClick={() => setMode('discard')}
- />
- }
- title="Permanently delete folder and photos"
- description="Removes the folder, every photo inside it, and the directory from disk. This cannot be undone."
- selected={mode === 'permanent'}
- destructive
- onClick={() => setMode('permanent')}
- />
-
+ setMode(v as Mode)}
+ className="gap-2"
+ >
+ }
+ title="Move photos to discard pile"
+ description="Photos can be restored later from Discarded. The folder and files stay on disk."
+ selected={mode === 'discard'}
+ />
+ }
+ title="Permanently delete folder and photos"
+ description="Removes the folder, every photo inside it, and the directory from disk. This cannot be undone."
+ selected={mode === 'permanent'}
+ destructive
+ />
+
-