fix(ux): replace confirm() with ConfirmModal, fix dark-mode colors, add currency format

- Add reusable ConfirmModal component (themed, Escape key, focus trap)
- Replace all native confirm() calls in Orders, ProductLibrary, Materials, Admin, Billing
- Fix ValidationDialog (Upload.tsx) hardcoded bg-white/text-gray-* → semantic tokens
- Fix NewInvoiceModal (Billing.tsx) hardcoded colors → semantic tokens
- Add formatCurrency (Intl.NumberFormat de-DE/EUR) to NewProductOrder wizard
- Resolves audit issues C1, C3, M3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 19:59:13 +01:00
parent 9f54bc3ab1
commit 915abe9d74
8 changed files with 307 additions and 45 deletions
+32 -2
View File
@@ -4,6 +4,7 @@ import { toast } from 'sonner'
import { UserPlus, Trash2, Pencil, ChevronDown, ChevronUp, ChevronRight, Settings, RefreshCw, CheckCircle2, XCircle, Clock, DollarSign, Layers, AlertTriangle, Upload, FileBox, Plus, X, LayoutDashboard } from 'lucide-react'
import { Link } from 'react-router-dom'
import api from '../api/client'
import ConfirmModal from '../components/ConfirmModal'
import TemplateEditor from '../components/admin/TemplateEditor'
import PricingTierTable from '../components/admin/PricingTierTable'
import OutputTypeTable from '../components/admin/OutputTypeTable'
@@ -191,6 +192,7 @@ export default function AdminPage() {
const [smtpDraft, setSmtpDraft] = useState<Partial<Settings>>({})
const smtp = { ...settings, ...smtpDraft } as Settings
const [confirmState, setConfirmState] = useState<{ open: boolean; title: string; message: string; onConfirm: () => void }>({ open: false, title: '', message: '', onConfirm: () => {} })
const [showTenantDashboardModal, setShowTenantDashboardModal] = useState(false)
const { data: tenantDefaultWidgets } = useQuery<WidgetConfig[]>({
queryKey: ['tenant-default-dashboard'],
@@ -276,7 +278,17 @@ export default function AdminPage() {
{user.is_active ? 'active' : 'inactive'}
</span>
<button
onClick={() => { if (confirm('Delete user?')) deleteUserMut.mutate(user.id) }}
onClick={() => {
setConfirmState({
open: true,
title: 'Delete User',
message: `Delete user "${user.email}"? This cannot be undone.`,
onConfirm: () => {
deleteUserMut.mutate(user.id)
setConfirmState((s) => ({ ...s, open: false }))
},
})
}}
className="text-content-muted hover:text-red-500 transition-colors"
title="Delete user"
>
@@ -1440,7 +1452,17 @@ function AssetLibraryPanel() {
</button>
<button
className="btn-danger text-xs"
onClick={() => { if (confirm(`Delete "${lib.name}"?`)) deleteMut.mutate(lib.id) }}
onClick={() => {
setConfirmState({
open: true,
title: 'Delete Asset Library',
message: `Delete "${lib.name}"?`,
onConfirm: () => {
deleteMut.mutate(lib.id)
setConfirmState((s) => ({ ...s, open: false }))
},
})
}}
>
<Trash2 size={12} />
</button>
@@ -1484,6 +1506,14 @@ function AssetLibraryPanel() {
})}
</div>
)}
<ConfirmModal
open={confirmState.open}
title={confirmState.title}
message={confirmState.message}
onConfirm={confirmState.onConfirm}
onCancel={() => setConfirmState((s) => ({ ...s, open: false }))}
/>
</div>
)
}