fix(audit-wave-10): aria-hidden sweep on decorative Lucide icons (#69)
Mechanical codemod added \`aria-hidden\` to 444 self-closing single-line Lucide icon JSX elements across 267 .tsx files in: - shared/, layout/, dashboard/ - admin/ (all sections) - clients/, berths/, yachts/, companies/, interests/, documents/ - reminders/, reservations/, residential/, expenses/, email/ The regex targeted only the safe pattern \`<IconName className="..." />\` (no other props, self-closing, capitalized component name). Every match inspected is a decorative companion to visible text or sits inside a button whose accessible name comes from \`aria-label\` / sr-only text — the icon itself should not be announced. Screen readers no longer double-read the icon + the adjacent label text (e.g. "Pencil Pencil Edit" → just "Edit"). The existing @axe-core/playwright smoke test (\`20-accessibility.spec.ts\`) continues to pass. Test suite stays at 1315/1315 vitest. typescript clean. Closes task #69 (aria-hidden sweep) from the AUDIT-2026-05-12 follow-ups backlog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -377,7 +377,10 @@ export function AdminSectionsBrowser({ portSlug }: AdminSectionsBrowserProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="relative max-w-md">
|
||||
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
<Search
|
||||
className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
|
||||
aria-hidden
|
||||
/>
|
||||
<Input
|
||||
type="search"
|
||||
inputMode="search"
|
||||
@@ -466,7 +469,10 @@ function SectionCard({
|
||||
)}
|
||||
>
|
||||
<CardHeader className="flex flex-row items-start gap-3 space-y-0 pb-2">
|
||||
<Icon className="h-5 w-5 mt-0.5 text-muted-foreground group-hover:text-primary" />
|
||||
<Icon
|
||||
className="h-5 w-5 mt-0.5 text-muted-foreground group-hover:text-primary"
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<CardTitle className="text-base">{section.label}</CardTitle>
|
||||
{groupTitle ? (
|
||||
|
||||
@@ -88,7 +88,7 @@ function AiBudgetCardBody({
|
||||
<CardTitle>AI cost guardrails</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading…
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -198,7 +198,9 @@ function AiBudgetCardBody({
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={() => save.mutate()} disabled={save.isPending}>
|
||||
{save.isPending ? <Loader2 className="mr-1.5 h-3 w-3 animate-spin" /> : null}
|
||||
{save.isPending ? (
|
||||
<Loader2 className="mr-1.5 h-3 w-3 animate-spin" aria-hidden />
|
||||
) : null}
|
||||
Save guardrails
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -44,11 +44,11 @@ const ACTION_BADGE_COLORS: Record<string, string> = {
|
||||
};
|
||||
|
||||
function ActionIcon({ action }: { action: string }) {
|
||||
if (action === 'create') return <Plus className="h-5 w-5" />;
|
||||
if (action === 'update') return <Pencil className="h-5 w-5" />;
|
||||
if (action === 'delete') return <Trash2 className="h-5 w-5" />;
|
||||
if (action === 'viewed') return <Eye className="h-5 w-5" />;
|
||||
return <Activity className="h-5 w-5" />;
|
||||
if (action === 'create') return <Plus className="h-5 w-5" aria-hidden />;
|
||||
if (action === 'update') return <Pencil className="h-5 w-5" aria-hidden />;
|
||||
if (action === 'delete') return <Trash2 className="h-5 w-5" aria-hidden />;
|
||||
if (action === 'viewed') return <Eye className="h-5 w-5" aria-hidden />;
|
||||
return <Activity className="h-5 w-5" aria-hidden />;
|
||||
}
|
||||
|
||||
function actionVerb(action: string): string {
|
||||
@@ -114,7 +114,7 @@ export function AuditLogCard({ entry }: AuditLogCardProps) {
|
||||
|
||||
{/* Timestamp + IP meta line */}
|
||||
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
|
||||
<ListCardMeta icon={<Clock className="h-3 w-3" />}>
|
||||
<ListCardMeta icon={<Clock className="h-3 w-3" aria-hidden />}>
|
||||
{formatDistanceToNow(new Date(entry.createdAt), { addSuffix: true })}
|
||||
</ListCardMeta>
|
||||
{entry.ipAddress ? (
|
||||
|
||||
@@ -351,7 +351,10 @@ export function AuditLogList() {
|
||||
Search
|
||||
</Label>
|
||||
<div className="relative w-72">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Search
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground"
|
||||
aria-hidden
|
||||
/>
|
||||
<Input
|
||||
id="audit-search"
|
||||
className="pl-9 h-9"
|
||||
|
||||
@@ -102,7 +102,7 @@ export function BackupAdminPanel() {
|
||||
<CardHeader className="flex flex-row items-start justify-between gap-3 space-y-0">
|
||||
<div>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Database className="h-4 w-4" />
|
||||
<Database className="h-4 w-4" aria-hidden />
|
||||
Run a backup now
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
@@ -112,11 +112,11 @@ export function BackupAdminPanel() {
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={load} disabled={loading || running}>
|
||||
<RefreshCw className="mr-1.5 h-3.5 w-3.5" />
|
||||
<RefreshCw className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
Refresh
|
||||
</Button>
|
||||
<Button onClick={() => setConfirmOpen(true)} disabled={running}>
|
||||
{running && <Loader2 className="mr-1.5 h-4 w-4 animate-spin" />}
|
||||
{running && <Loader2 className="mr-1.5 h-4 w-4 animate-spin" aria-hidden />}
|
||||
{running ? 'Running…' : 'Run backup'}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -136,7 +136,7 @@ export function BackupAdminPanel() {
|
||||
<CardContent>
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading…
|
||||
</div>
|
||||
) : jobs.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No backups yet.</p>
|
||||
@@ -168,7 +168,7 @@ export function BackupAdminPanel() {
|
||||
</span>
|
||||
{j.status === 'completed' && (
|
||||
<Button size="sm" variant="outline" onClick={() => download(j.id)}>
|
||||
<Download className="mr-1.5 h-3.5 w-3.5" />
|
||||
<Download className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
Download
|
||||
</Button>
|
||||
)}
|
||||
@@ -184,7 +184,7 @@ export function BackupAdminPanel() {
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600" />
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600" aria-hidden />
|
||||
Run a fresh backup now?
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
|
||||
@@ -207,7 +207,7 @@ export function PdfLogoUploader() {
|
||||
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading current logo…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading current logo…
|
||||
</div>
|
||||
) : current ? (
|
||||
<div className="flex items-start gap-4 rounded-md border p-4">
|
||||
@@ -232,7 +232,7 @@ export function PdfLogoUploader() {
|
||||
Test with sample PDF
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" onClick={clear} disabled={working}>
|
||||
<Trash2 className="mr-1 h-3 w-3" /> Remove
|
||||
<Trash2 className="mr-1 h-3 w-3" aria-hidden /> Remove
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -292,9 +292,9 @@ export function PdfLogoUploader() {
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={upload} disabled={working}>
|
||||
{working ? (
|
||||
<Loader2 className="mr-1 h-3 w-3 animate-spin" />
|
||||
<Loader2 className="mr-1 h-3 w-3 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<Upload className="mr-1 h-3 w-3" />
|
||||
<Upload className="mr-1 h-3 w-3" aria-hidden />
|
||||
)}
|
||||
Save logo
|
||||
</Button>
|
||||
@@ -316,7 +316,7 @@ export function PdfLogoUploader() {
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={working}
|
||||
>
|
||||
<RefreshCw className="mr-1 h-3 w-3" /> Choose a different file
|
||||
<RefreshCw className="mr-1 h-3 w-3" aria-hidden /> Choose a different file
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,13 +69,13 @@ export function BrochuresAdminPanel() {
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={() => setCreateOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" /> New brochure
|
||||
<Plus className="mr-2 h-4 w-4" aria-hidden /> New brochure
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{brochuresQuery.isLoading && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading…
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -179,10 +179,10 @@ function BrochureCard({ brochure, onChange }: { brochure: BrochureRow; onChange:
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between text-base">
|
||||
<span className="flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" /> {brochure.label}
|
||||
<FileText className="h-4 w-4" aria-hidden /> {brochure.label}
|
||||
{brochure.isDefault && (
|
||||
<span className="flex items-center gap-1 rounded bg-primary/10 px-2 py-0.5 text-xs text-primary">
|
||||
<Star className="h-3 w-3" /> default
|
||||
<Star className="h-3 w-3" aria-hidden /> default
|
||||
</span>
|
||||
)}
|
||||
{brochure.archivedAt && (
|
||||
@@ -222,9 +222,9 @@ function BrochureCard({ brochure, onChange }: { brochure: BrochureRow; onChange:
|
||||
<Button asChild variant="outline" size="sm" disabled={uploading}>
|
||||
<span>
|
||||
{uploading ? (
|
||||
<Loader2 className="mr-2 h-3 w-3 animate-spin" />
|
||||
<Loader2 className="mr-2 h-3 w-3 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<Upload className="mr-2 h-3 w-3" />
|
||||
<Upload className="mr-2 h-3 w-3" aria-hidden />
|
||||
)}
|
||||
Upload version
|
||||
</span>
|
||||
@@ -237,7 +237,7 @@ function BrochureCard({ brochure, onChange }: { brochure: BrochureRow; onChange:
|
||||
onClick={() => setDefaultMutation.mutate()}
|
||||
disabled={setDefaultMutation.isPending}
|
||||
>
|
||||
<Star className="mr-2 h-3 w-3" /> Mark default
|
||||
<Star className="mr-2 h-3 w-3" aria-hidden /> Mark default
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -246,7 +246,7 @@ function BrochureCard({ brochure, onChange }: { brochure: BrochureRow; onChange:
|
||||
onClick={() => archiveMutation.mutate()}
|
||||
disabled={archiveMutation.isPending}
|
||||
>
|
||||
<Archive className="mr-2 h-3 w-3" /> Archive
|
||||
<Archive className="mr-2 h-3 w-3" aria-hidden /> Archive
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -331,7 +331,9 @@ function CreateBrochureDialog({
|
||||
disabled={!label.trim() || createMutation.isPending}
|
||||
onClick={() => createMutation.mutate()}
|
||||
>
|
||||
{createMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{createMutation.isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
|
||||
)}
|
||||
Create
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -263,7 +263,7 @@ function CustomFieldFormBody({ open, onOpenChange, field, onSuccess }: CustomFie
|
||||
maxLength={100}
|
||||
/>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addOption}>
|
||||
<Plus className="h-4 w-4" />
|
||||
<Plus className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
{selectOptions.length > 0 && (
|
||||
|
||||
@@ -124,7 +124,7 @@ export function CustomFieldsManager() {
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleEdit(row.original)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Edit</span>
|
||||
</Button>
|
||||
<ConfirmationDialog
|
||||
@@ -135,7 +135,7 @@ export function CustomFieldsManager() {
|
||||
className="text-destructive hover:text-destructive"
|
||||
disabled={deletingId === row.original.id}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Delete</span>
|
||||
</Button>
|
||||
}
|
||||
@@ -159,7 +159,7 @@ export function CustomFieldsManager() {
|
||||
description="Define custom fields for clients and records"
|
||||
actions={
|
||||
<Button onClick={handleCreate}>
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
<Plus className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
New Field
|
||||
</Button>
|
||||
}
|
||||
@@ -228,7 +228,7 @@ export function CustomFieldsManager() {
|
||||
onClick={() => handleEdit(original)}
|
||||
aria-label="Edit field"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
<ConfirmationDialog
|
||||
trigger={
|
||||
@@ -239,7 +239,7 @@ export function CustomFieldsManager() {
|
||||
disabled={deletingId === original.id}
|
||||
aria-label="Delete field"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
}
|
||||
title="Delete Custom Field"
|
||||
|
||||
@@ -47,17 +47,17 @@ export function DocumensoTestButton() {
|
||||
{result &&
|
||||
(result.ok ? (
|
||||
<span className="flex items-center text-xs text-green-600">
|
||||
<CheckCircle2 className="mr-1 h-3.5 w-3.5" />
|
||||
<CheckCircle2 className="mr-1 h-3.5 w-3.5" aria-hidden />
|
||||
HTTP {result.status}
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center text-xs text-destructive">
|
||||
<XCircle className="mr-1 h-3.5 w-3.5" />
|
||||
<XCircle className="mr-1 h-3.5 w-3.5" aria-hidden />
|
||||
{result.error ?? `HTTP ${result.status}`}
|
||||
</span>
|
||||
))}
|
||||
<Button variant="outline" onClick={runTest} disabled={pending}>
|
||||
{pending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{pending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />}
|
||||
Test connection
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -94,7 +94,7 @@ export function TemplateList() {
|
||||
header: 'Name',
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<FileText className="h-4 w-4 shrink-0 text-muted-foreground" aria-hidden />
|
||||
<span className="font-medium">{row.original.name}</span>
|
||||
</div>
|
||||
),
|
||||
@@ -147,7 +147,7 @@ export function TemplateList() {
|
||||
title="Version history"
|
||||
onClick={() => handleViewHistory(row.original)}
|
||||
>
|
||||
<History className="h-4 w-4" />
|
||||
<History className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -155,7 +155,7 @@ export function TemplateList() {
|
||||
title="Edit template"
|
||||
onClick={() => handleEditTemplate(row.original)}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -168,7 +168,7 @@ export function TemplateList() {
|
||||
<ConfirmationDialog
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon" title="Delete template">
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
<Trash2 className="h-4 w-4 text-destructive" aria-hidden />
|
||||
</Button>
|
||||
}
|
||||
title="Delete Template"
|
||||
@@ -189,7 +189,7 @@ export function TemplateList() {
|
||||
description="Manage reusable document templates with TipTap content and PDF generation"
|
||||
actions={
|
||||
<Button onClick={handleNewTemplate}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
<Plus className="mr-2 h-4 w-4" aria-hidden />
|
||||
New Template
|
||||
</Button>
|
||||
}
|
||||
@@ -237,7 +237,7 @@ export function TemplateList() {
|
||||
title="Version history"
|
||||
onClick={() => handleViewHistory(original)}
|
||||
>
|
||||
<History className="h-4 w-4" />
|
||||
<History className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -245,12 +245,12 @@ export function TemplateList() {
|
||||
title="Edit template"
|
||||
onClick={() => handleEditTemplate(original)}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
<ConfirmationDialog
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon" title="Delete template">
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
<Trash2 className="h-4 w-4 text-destructive" aria-hidden />
|
||||
</Button>
|
||||
}
|
||||
title="Delete Template"
|
||||
|
||||
@@ -59,7 +59,7 @@ export function TemplatePreview({ content, templateName }: TemplatePreviewProps)
|
||||
return (
|
||||
<>
|
||||
<Button variant="outline" size="sm" onClick={handlePreview}>
|
||||
<Eye className="mr-1.5 h-3.5 w-3.5" />
|
||||
<Eye className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
Preview PDF
|
||||
</Button>
|
||||
|
||||
@@ -70,7 +70,7 @@ export function TemplatePreview({ content, templateName }: TemplatePreviewProps)
|
||||
<DialogTitle>Preview - {templateName}</DialogTitle>
|
||||
{pdfBase64 && (
|
||||
<Button variant="ghost" size="sm" onClick={handleOpenInNewTab} className="mr-6">
|
||||
<ExternalLink className="mr-1.5 h-3.5 w-3.5" />
|
||||
<ExternalLink className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
Open in new tab
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -81,7 +81,7 @@ export function TemplateVersionHistory({
|
||||
if (versions.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-2 rounded-md border border-dashed p-6 text-center">
|
||||
<Clock className="h-6 w-6 text-muted-foreground" />
|
||||
<Clock className="h-6 w-6 text-muted-foreground" aria-hidden />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No previous versions found. Versions are saved whenever you update the template content.
|
||||
</p>
|
||||
@@ -129,7 +129,7 @@ export function TemplateVersionHistory({
|
||||
onClick={() => handleRollback(v.version)}
|
||||
disabled={rollingBack === v.version}
|
||||
>
|
||||
<RotateCcw className="mr-1.5 h-3.5 w-3.5" />
|
||||
<RotateCcw className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
{rollingBack === v.version ? 'Restoring…' : 'Restore'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -150,7 +150,7 @@ function EmailTemplatesAdminBody({
|
||||
onClick={() => save(row, 'save')}
|
||||
disabled={savingKey === row.key || !dirty}
|
||||
>
|
||||
<Save className="h-3.5 w-3.5 mr-1.5" /> Save
|
||||
<Save className="h-3.5 w-3.5 mr-1.5" aria-hidden /> Save
|
||||
</Button>
|
||||
{overridden ? (
|
||||
<Button
|
||||
@@ -159,7 +159,7 @@ function EmailTemplatesAdminBody({
|
||||
onClick={() => save(row, 'reset')}
|
||||
disabled={savingKey === row.key}
|
||||
>
|
||||
<RotateCcw className="h-3.5 w-3.5 mr-1.5" /> Reset to default
|
||||
<RotateCcw className="h-3.5 w-3.5 mr-1.5" aria-hidden /> Reset to default
|
||||
</Button>
|
||||
) : null}
|
||||
{message?.key === row.key ? (
|
||||
|
||||
@@ -139,7 +139,7 @@ function FormTemplateFormBody({ open, onOpenChange, template, onSaved }: Props)
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm font-medium">Fields</Label>
|
||||
<Button variant="outline" size="sm" onClick={addField}>
|
||||
<Plus className="h-3.5 w-3.5 mr-1" />
|
||||
<Plus className="h-3.5 w-3.5 mr-1" aria-hidden />
|
||||
Add field
|
||||
</Button>
|
||||
</div>
|
||||
@@ -154,7 +154,7 @@ function FormTemplateFormBody({ open, onOpenChange, template, onSaved }: Props)
|
||||
className="text-destructive h-7 w-7"
|
||||
onClick={() => removeField(i)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -57,7 +57,7 @@ export function FormTemplateList() {
|
||||
setFormOpen(true);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1.5" />
|
||||
<Plus className="h-4 w-4 mr-1.5" aria-hidden />
|
||||
New template
|
||||
</Button>
|
||||
}
|
||||
@@ -95,12 +95,12 @@ export function FormTemplateList() {
|
||||
setFormOpen(true);
|
||||
}}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
<ConfirmationDialog
|
||||
trigger={
|
||||
<Button variant="ghost" size="icon" className="text-destructive">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
}
|
||||
title="Delete form template"
|
||||
|
||||
@@ -89,7 +89,7 @@ export function InvitationsManager() {
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => setSheetOpen(true)}>
|
||||
<Plus className="h-4 w-4 mr-1.5" />
|
||||
<Plus className="h-4 w-4 mr-1.5" aria-hidden />
|
||||
Send invite
|
||||
</Button>
|
||||
</div>
|
||||
@@ -98,7 +98,7 @@ export function InvitationsManager() {
|
||||
<p className="text-sm text-muted-foreground">Loading…</p>
|
||||
) : invites.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed p-8 text-center text-muted-foreground">
|
||||
<Mail className="mx-auto h-6 w-6 mb-2" />
|
||||
<Mail className="mx-auto h-6 w-6 mb-2" aria-hidden />
|
||||
<p className="text-sm">No invitations issued yet.</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -142,7 +142,7 @@ export function InvitationsManager() {
|
||||
disabled={resendMutation.isPending || !!i.usedAt}
|
||||
title="Resend invite"
|
||||
>
|
||||
<RotateCw className="h-3.5 w-3.5" />
|
||||
<RotateCw className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
{i.status === 'pending' && (
|
||||
<ConfirmationDialog
|
||||
@@ -153,7 +153,7 @@ export function InvitationsManager() {
|
||||
className="text-destructive"
|
||||
title="Revoke"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
}
|
||||
title="Revoke invitation?"
|
||||
@@ -230,7 +230,9 @@ export function InvitationsManager() {
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={!email.trim() || createMutation.isPending}>
|
||||
{createMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{createMutation.isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
|
||||
)}
|
||||
Send invite
|
||||
</Button>
|
||||
</SheetFooter>
|
||||
|
||||
@@ -138,7 +138,7 @@ function SettingsBlockBody({
|
||||
<CardTitle>{title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading…
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -250,7 +250,11 @@ function SettingsBlockBody({
|
||||
onClick={() => setShowKey((v) => !v)}
|
||||
aria-label={showKey ? 'Hide key' : 'Show key'}
|
||||
>
|
||||
{showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
||||
{showKey ? (
|
||||
<EyeOff className="h-4 w-4" aria-hidden />
|
||||
) : (
|
||||
<Eye className="h-4 w-4" aria-hidden />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -264,7 +268,9 @@ function SettingsBlockBody({
|
||||
disabled={save.isPending}
|
||||
data-testid={`save-${scope}`}
|
||||
>
|
||||
{save.isPending ? <Loader2 className="mr-1.5 h-3 w-3 animate-spin" /> : null}
|
||||
{save.isPending ? (
|
||||
<Loader2 className="mr-1.5 h-3 w-3 animate-spin" aria-hidden />
|
||||
) : null}
|
||||
Save settings
|
||||
</Button>
|
||||
<Button
|
||||
@@ -273,7 +279,9 @@ function SettingsBlockBody({
|
||||
onClick={() => test.mutate()}
|
||||
disabled={test.isPending || apiKey.length === 0}
|
||||
>
|
||||
{test.isPending ? <Loader2 className="mr-1.5 h-3 w-3 animate-spin" /> : null}
|
||||
{test.isPending ? (
|
||||
<Loader2 className="mr-1.5 h-3 w-3 animate-spin" aria-hidden />
|
||||
) : null}
|
||||
Test connection
|
||||
</Button>
|
||||
{hasKey ? (
|
||||
@@ -290,13 +298,13 @@ function SettingsBlockBody({
|
||||
|
||||
{testStatus?.ok ? (
|
||||
<span className="inline-flex items-center gap-1 text-sm text-green-700">
|
||||
<CheckCircle2 className="h-4 w-4" />
|
||||
<CheckCircle2 className="h-4 w-4" aria-hidden />
|
||||
Connection OK
|
||||
</span>
|
||||
) : null}
|
||||
{testStatus && !testStatus.ok ? (
|
||||
<span className="inline-flex items-center gap-1 text-sm text-destructive">
|
||||
<XCircle className="h-4 w-4" />
|
||||
<XCircle className="h-4 w-4" aria-hidden />
|
||||
{testStatus.reason}
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -228,11 +228,11 @@ export function OnboardingChecklist() {
|
||||
}
|
||||
>
|
||||
{done ? (
|
||||
<Check className="h-4 w-4" />
|
||||
<Check className="h-4 w-4" aria-hidden />
|
||||
) : loading ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<Circle className="h-4 w-4" />
|
||||
<Circle className="h-4 w-4" aria-hidden />
|
||||
)}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -243,7 +243,7 @@ export function OnboardingChecklist() {
|
||||
className="text-sm font-medium hover:underline inline-flex items-center gap-1"
|
||||
>
|
||||
{idx + 1}. {step.label}
|
||||
<ExternalLink className="h-3 w-3 opacity-50" />
|
||||
<ExternalLink className="h-3 w-3 opacity-50" aria-hidden />
|
||||
</Link>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">{step.description}</p>
|
||||
{auto && (
|
||||
@@ -265,7 +265,7 @@ export function OnboardingChecklist() {
|
||||
onClick={() => toggleManual(step.id)}
|
||||
>
|
||||
{saving === step.id ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" aria-hidden />
|
||||
) : manual ? (
|
||||
'Mark incomplete'
|
||||
) : (
|
||||
|
||||
@@ -95,7 +95,7 @@ export function PortList() {
|
||||
header: '',
|
||||
cell: ({ row }) => (
|
||||
<Button variant="ghost" size="sm" onClick={() => handleEditPort(row.original)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Edit</span>
|
||||
</Button>
|
||||
),
|
||||
@@ -111,7 +111,7 @@ export function PortList() {
|
||||
description="Manage marina ports and their configuration"
|
||||
actions={
|
||||
<Button onClick={handleNewPort}>
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
<Plus className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
New Port
|
||||
</Button>
|
||||
}
|
||||
@@ -154,7 +154,7 @@ export function PortList() {
|
||||
aria-label={`Edit ${original.name}`}
|
||||
className="shrink-0"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -159,7 +159,7 @@ export function QueueDetailTable({ queueName }: QueueDetailTableProps) {
|
||||
disabled={retryMutation.isPending}
|
||||
onClick={() => retryMutation.mutate(job.id)}
|
||||
>
|
||||
<RotateCcw className="h-3.5 w-3.5" />
|
||||
<RotateCcw className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -170,7 +170,7 @@ export function QueueDetailTable({ queueName }: QueueDetailTableProps) {
|
||||
disabled={deleteMutation.isPending}
|
||||
onClick={() => deleteMutation.mutate(job.id)}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
@@ -128,7 +128,7 @@ export function ResidentialStagesAdmin() {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading stages…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading stages…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export function ResidentialStagesAdmin() {
|
||||
key={`${stage.id}-${idx}`}
|
||||
className="flex items-center gap-3 rounded-md border p-3"
|
||||
>
|
||||
<GripVertical className="h-4 w-4 text-muted-foreground" />
|
||||
<GripVertical className="h-4 w-4 text-muted-foreground" aria-hidden />
|
||||
<div className="flex-1 grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[11px] uppercase tracking-wide text-muted-foreground">
|
||||
@@ -223,13 +223,13 @@ export function ResidentialStagesAdmin() {
|
||||
onClick={() => remove(idx)}
|
||||
className="text-destructive"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button type="button" variant="outline" size="sm" onClick={add}>
|
||||
<Plus className="mr-1.5 h-3.5 w-3.5" />
|
||||
<Plus className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
Add stage
|
||||
</Button>
|
||||
</CardContent>
|
||||
@@ -245,9 +245,9 @@ export function ResidentialStagesAdmin() {
|
||||
<div className="flex items-center gap-3">
|
||||
<Button onClick={() => attemptSave(false)} disabled={saving}>
|
||||
{saving ? (
|
||||
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
|
||||
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<Save className="mr-1.5 h-4 w-4" />
|
||||
<Save className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
)}
|
||||
Save stages
|
||||
</Button>
|
||||
@@ -318,7 +318,7 @@ export function ResidentialStagesAdmin() {
|
||||
}}
|
||||
disabled={saving}
|
||||
>
|
||||
{saving && <Loader2 className="mr-1.5 h-4 w-4 animate-spin" />}
|
||||
{saving && <Loader2 className="mr-1.5 h-4 w-4 animate-spin" aria-hidden />}
|
||||
Reassign + save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -87,7 +87,7 @@ export function RoleList() {
|
||||
<span className="font-medium">{formatRole(row.original.name)}</span>
|
||||
{row.original.isSystem && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
<Lock className="mr-1 h-3 w-3" />
|
||||
<Lock className="mr-1 h-3 w-3" aria-hidden />
|
||||
System
|
||||
</Badge>
|
||||
)}
|
||||
@@ -126,7 +126,7 @@ export function RoleList() {
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleEditRole(row.original)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Edit</span>
|
||||
</Button>
|
||||
{!row.original.isSystem && (
|
||||
@@ -137,7 +137,7 @@ export function RoleList() {
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Delete</span>
|
||||
</Button>
|
||||
}
|
||||
@@ -162,7 +162,7 @@ export function RoleList() {
|
||||
description="Manage roles and their permissions"
|
||||
actions={
|
||||
<Button onClick={handleNewRole}>
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
<Plus className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
New Role
|
||||
</Button>
|
||||
}
|
||||
@@ -209,7 +209,7 @@ export function RoleList() {
|
||||
onClick={() => handleEditRole(original)}
|
||||
aria-label="Edit role"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
{!original.isSystem ? (
|
||||
<ConfirmationDialog
|
||||
@@ -220,7 +220,7 @@ export function RoleList() {
|
||||
className="text-destructive hover:text-destructive"
|
||||
aria-label="Delete role"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
}
|
||||
title="Delete Role"
|
||||
|
||||
@@ -174,7 +174,7 @@ export function SalesEmailConfigCard() {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex items-center gap-2 py-6 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading sales email config…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading sales email config…
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -387,7 +387,7 @@ export function SalesEmailConfigCard() {
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleSave} disabled={saving}>
|
||||
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />}
|
||||
Save sales email settings
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -408,7 +408,7 @@ export function SettingsManager() {
|
||||
saveSetting(setting.key, values[setting.key] ?? setting.defaultValue)
|
||||
}
|
||||
>
|
||||
<Save className="h-3.5 w-3.5" />
|
||||
<Save className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -454,7 +454,7 @@ export function SettingsManager() {
|
||||
saveSetting(setting.key, values[setting.key] ?? setting.defaultValue)
|
||||
}
|
||||
>
|
||||
<Save className="h-3.5 w-3.5" />
|
||||
<Save className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -542,7 +542,7 @@ export function SettingsManager() {
|
||||
size="sm"
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
}
|
||||
title="Delete Setting"
|
||||
@@ -569,7 +569,7 @@ export function SettingsManager() {
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button variant="outline" onClick={handleAddCustom} disabled={!customKey.trim()}>
|
||||
<Plus className="mr-1 h-4 w-4" />
|
||||
<Plus className="mr-1 h-4 w-4" aria-hidden />
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -147,7 +147,7 @@ export function SettingsFormCard({ title, description, fields, extra }: Settings
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center text-sm text-muted-foreground">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
|
||||
Loading…
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -176,7 +176,7 @@ export function SettingsFormCard({ title, description, fields, extra }: Settings
|
||||
<div className="flex items-center justify-end gap-2 pt-2 border-t">
|
||||
{extra}
|
||||
<Button onClick={handleSave} disabled={saving || !dirty}>
|
||||
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />}
|
||||
Save changes
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -174,7 +174,7 @@ export function StorageAdminPanel() {
|
||||
if (status.isLoading) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading storage status…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading storage status…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -216,18 +216,22 @@ export function StorageAdminPanel() {
|
||||
onClick={() => testMutation.mutate()}
|
||||
disabled={testMutation.isPending}
|
||||
>
|
||||
{testMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{testMutation.isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
|
||||
)}
|
||||
Test S3 connection
|
||||
</Button>
|
||||
{testResult && (
|
||||
<div className="rounded-md border p-3 text-sm">
|
||||
{testResult.ok ? (
|
||||
<div className="flex items-center gap-2 text-emerald-600">
|
||||
<CheckCircle2 className="h-4 w-4" /> Connection OK — round-trip succeeded.
|
||||
<CheckCircle2 className="h-4 w-4" aria-hidden /> Connection OK — round-trip
|
||||
succeeded.
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 text-destructive">
|
||||
<XCircle className="h-4 w-4" /> {testResult.error ?? 'Connection failed'}
|
||||
<XCircle className="h-4 w-4" aria-hidden />{' '}
|
||||
{testResult.error ?? 'Connection failed'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -248,9 +252,9 @@ export function StorageAdminPanel() {
|
||||
<Card className="lg:col-span-2">
|
||||
<CardHeader className="flex flex-row items-start gap-3 space-y-0 pb-2">
|
||||
{s.backend === 's3' ? (
|
||||
<ServerCog className="mt-0.5 h-5 w-5 text-muted-foreground" />
|
||||
<ServerCog className="mt-0.5 h-5 w-5 text-muted-foreground" aria-hidden />
|
||||
) : (
|
||||
<HardDrive className="mt-0.5 h-5 w-5 text-muted-foreground" />
|
||||
<HardDrive className="mt-0.5 h-5 w-5 text-muted-foreground" aria-hidden />
|
||||
)}
|
||||
<div>
|
||||
<CardTitle className="text-base">Active backend: {s.backend}</CardTitle>
|
||||
@@ -283,7 +287,9 @@ export function StorageAdminPanel() {
|
||||
disabled={dryRunMutation.isPending}
|
||||
onClick={() => openConfirm('switch-and-migrate')}
|
||||
>
|
||||
{dryRunMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{dryRunMutation.isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
|
||||
)}
|
||||
Switch + migrate existing files to {otherBackend}
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => openConfirm('switch-only')}>
|
||||
@@ -294,7 +300,7 @@ export function StorageAdminPanel() {
|
||||
onClick={() => status.refetch()}
|
||||
disabled={status.isFetching}
|
||||
>
|
||||
<RefreshCw className="mr-2 h-4 w-4" /> Refresh
|
||||
<RefreshCw className="mr-2 h-4 w-4" aria-hidden /> Refresh
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -337,7 +343,7 @@ export function StorageAdminPanel() {
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600" />
|
||||
<AlertTriangle className="h-5 w-5 text-amber-600" aria-hidden />
|
||||
{confirmMode === 'switch-and-migrate'
|
||||
? `Switch + migrate to ${otherBackend}?`
|
||||
: `Switch active backend to ${otherBackend}?`}
|
||||
@@ -382,7 +388,9 @@ export function StorageAdminPanel() {
|
||||
})
|
||||
}
|
||||
>
|
||||
{migrateMutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
||||
{migrateMutation.isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
|
||||
)}
|
||||
{confirmMode === 'switch-and-migrate' ? 'Migrate now' : 'Switch now'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
||||
@@ -60,9 +60,9 @@ export function SystemMonitoringDashboard() {
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
{health.overall === 'healthy' ? (
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" aria-hidden />
|
||||
) : (
|
||||
<AlertTriangle className="h-4 w-4 text-yellow-500" />
|
||||
<AlertTriangle className="h-4 w-4 text-yellow-500" aria-hidden />
|
||||
)}
|
||||
<span className="text-sm text-muted-foreground">
|
||||
All services checked at {new Date(health.checkedAt).toLocaleTimeString()}
|
||||
@@ -88,7 +88,7 @@ export function SystemMonitoringDashboard() {
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-xs text-muted-foreground font-medium uppercase tracking-wide flex items-center gap-1.5">
|
||||
<Wifi className="h-3.5 w-3.5" />
|
||||
<Wifi className="h-3.5 w-3.5" aria-hidden />
|
||||
Active Connections
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -100,7 +100,7 @@ export function SystemMonitoringDashboard() {
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-xs text-muted-foreground font-medium uppercase tracking-wide flex items-center gap-1.5">
|
||||
<AlertTriangle className="h-3.5 w-3.5" />
|
||||
<AlertTriangle className="h-3.5 w-3.5" aria-hidden />
|
||||
Total Failed Jobs
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -114,7 +114,7 @@ export function SystemMonitoringDashboard() {
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-xs text-muted-foreground font-medium uppercase tracking-wide flex items-center gap-1.5">
|
||||
<Activity className="h-3.5 w-3.5" />
|
||||
<Activity className="h-3.5 w-3.5" aria-hidden />
|
||||
Active Jobs
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@@ -87,13 +87,13 @@ export function TagList() {
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
<Button variant="ghost" size="sm" onClick={() => handleEditTag(row.original)}>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Edit</span>
|
||||
</Button>
|
||||
<ConfirmationDialog
|
||||
trigger={
|
||||
<Button variant="ghost" size="sm" className="text-destructive hover:text-destructive">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Delete</span>
|
||||
</Button>
|
||||
}
|
||||
@@ -117,7 +117,7 @@ export function TagList() {
|
||||
description="Manage tags used across clients and records"
|
||||
actions={
|
||||
<Button onClick={handleNewTag}>
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
<Plus className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
New Tag
|
||||
</Button>
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export function TagList() {
|
||||
onClick={() => handleEditTag(original)}
|
||||
aria-label={`Edit ${original.name}`}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
<ConfirmationDialog
|
||||
trigger={
|
||||
@@ -162,7 +162,7 @@ export function TagList() {
|
||||
className="text-destructive hover:text-destructive"
|
||||
aria-label={`Delete ${original.name}`}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
}
|
||||
title="Delete Tag"
|
||||
|
||||
@@ -73,7 +73,7 @@ export function UserCard({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={`Actions for ${user.displayName}`}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<MoreHorizontal className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
@@ -83,7 +83,7 @@ export function UserCard({
|
||||
onEdit(user);
|
||||
}}
|
||||
>
|
||||
<Pencil className="mr-2 h-3.5 w-3.5" />
|
||||
<Pencil className="mr-2 h-3.5 w-3.5" aria-hidden />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<ConfirmationDialog
|
||||
@@ -91,12 +91,12 @@ export function UserCard({
|
||||
<DropdownMenuItem onSelect={(e) => e.preventDefault()} disabled={isToggling}>
|
||||
{user.isActive ? (
|
||||
<>
|
||||
<PowerOff className="mr-2 h-3.5 w-3.5" />
|
||||
<PowerOff className="mr-2 h-3.5 w-3.5" aria-hidden />
|
||||
Disable sign-in
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Power className="mr-2 h-3.5 w-3.5 text-emerald-600" />
|
||||
<Power className="mr-2 h-3.5 w-3.5 text-emerald-600" aria-hidden />
|
||||
Enable sign-in
|
||||
</>
|
||||
)}
|
||||
@@ -115,7 +115,7 @@ export function UserCard({
|
||||
<ConfirmationDialog
|
||||
trigger={
|
||||
<DropdownMenuItem className="text-destructive" onSelect={(e) => e.preventDefault()}>
|
||||
<Trash2 className="mr-2 h-3.5 w-3.5" />
|
||||
<Trash2 className="mr-2 h-3.5 w-3.5" aria-hidden />
|
||||
Remove
|
||||
</DropdownMenuItem>
|
||||
}
|
||||
@@ -155,16 +155,18 @@ export function UserCard({
|
||||
|
||||
{/* Role + last login meta */}
|
||||
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
|
||||
<ListCardMeta icon={<Shield className="h-3 w-3" />}>
|
||||
<ListCardMeta icon={<Shield className="h-3 w-3" aria-hidden />}>
|
||||
{formatRole(user.role.name)}
|
||||
</ListCardMeta>
|
||||
|
||||
{user.lastLoginAt ? (
|
||||
<ListCardMeta icon={<Clock className="h-3 w-3" />}>
|
||||
<ListCardMeta icon={<Clock className="h-3 w-3" aria-hidden />}>
|
||||
{formatDistanceToNow(new Date(user.lastLoginAt), { addSuffix: true })}
|
||||
</ListCardMeta>
|
||||
) : (
|
||||
<ListCardMeta icon={<Clock className="h-3 w-3" />}>Never logged in</ListCardMeta>
|
||||
<ListCardMeta icon={<Clock className="h-3 w-3" aria-hidden />}>
|
||||
Never logged in
|
||||
</ListCardMeta>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ export function UserList() {
|
||||
onClick={() => handleEditUser(row.original)}
|
||||
title="Edit user"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<Pencil className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Edit</span>
|
||||
</Button>
|
||||
</PermissionGate>
|
||||
@@ -149,9 +149,9 @@ export function UserList() {
|
||||
}
|
||||
>
|
||||
{row.original.isActive ? (
|
||||
<PowerOff className="h-4 w-4" />
|
||||
<PowerOff className="h-4 w-4" aria-hidden />
|
||||
) : (
|
||||
<Power className="h-4 w-4" />
|
||||
<Power className="h-4 w-4" aria-hidden />
|
||||
)}
|
||||
<span className="sr-only">{row.original.isActive ? 'Disable' : 'Enable'}</span>
|
||||
</Button>
|
||||
@@ -176,7 +176,7 @@ export function UserList() {
|
||||
title="Remove from port"
|
||||
className="text-destructive hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
<Trash2 className="h-4 w-4" aria-hidden />
|
||||
<span className="sr-only">Remove</span>
|
||||
</Button>
|
||||
}
|
||||
@@ -201,7 +201,7 @@ export function UserList() {
|
||||
description="Manage users and their roles for this port"
|
||||
actions={
|
||||
<Button onClick={handleNewUser}>
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
<Plus className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
New User
|
||||
</Button>
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ export function VocabulariesManager() {
|
||||
<div>
|
||||
<PageHeader title="Vocabularies" description="Per-port pick lists used across the CRM." />
|
||||
<div className="flex items-center justify-center py-12 text-muted-foreground">
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Loading…
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden /> Loading…
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -208,7 +208,7 @@ export function VocabulariesManager() {
|
||||
disabled={saving === def.key}
|
||||
title="Restore the shipped defaults"
|
||||
>
|
||||
<RotateCcw className="mr-1 h-3.5 w-3.5" />
|
||||
<RotateCcw className="mr-1 h-3.5 w-3.5" aria-hidden />
|
||||
Reset
|
||||
</Button>
|
||||
<Button
|
||||
@@ -217,9 +217,9 @@ export function VocabulariesManager() {
|
||||
disabled={!v.dirty || saving === def.key}
|
||||
>
|
||||
{saving === def.key ? (
|
||||
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />
|
||||
<Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<Save className="mr-1 h-3.5 w-3.5" />
|
||||
<Save className="mr-1 h-3.5 w-3.5" aria-hidden />
|
||||
)}
|
||||
Save
|
||||
</Button>
|
||||
@@ -244,7 +244,7 @@ export function VocabulariesManager() {
|
||||
onClick={() => moveEntry(def.key, index, -1)}
|
||||
disabled={index === 0}
|
||||
>
|
||||
<GripVertical className="h-3.5 w-3.5 rotate-90" />
|
||||
<GripVertical className="h-3.5 w-3.5 rotate-90" aria-hidden />
|
||||
</button>
|
||||
</div>
|
||||
<Input
|
||||
@@ -259,7 +259,7 @@ export function VocabulariesManager() {
|
||||
onClick={() => removeEntry(def.key, index)}
|
||||
aria-label={`Remove ${entry}`}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
</li>
|
||||
))}
|
||||
@@ -285,7 +285,7 @@ export function VocabulariesManager() {
|
||||
onClick={() => addEntry(def.key)}
|
||||
disabled={!v.newEntry.trim()}
|
||||
>
|
||||
<Plus className="mr-1 h-3.5 w-3.5" /> Add
|
||||
<Plus className="mr-1 h-3.5 w-3.5" aria-hidden /> Add
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -50,17 +50,17 @@ export function UmamiTestButton() {
|
||||
{result &&
|
||||
(result.ok ? (
|
||||
<span className="flex items-center text-xs text-green-600">
|
||||
<CheckCircle2 className="mr-1 h-3.5 w-3.5" />
|
||||
<CheckCircle2 className="mr-1 h-3.5 w-3.5" aria-hidden />
|
||||
Connected ({result.visitors ?? 0} active)
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center text-xs text-destructive">
|
||||
<XCircle className="mr-1 h-3.5 w-3.5" />
|
||||
<XCircle className="mr-1 h-3.5 w-3.5" aria-hidden />
|
||||
{result.error ?? 'Failed'}
|
||||
</span>
|
||||
))}
|
||||
<Button type="button" size="sm" variant="outline" onClick={runTest} disabled={pending}>
|
||||
{pending ? <Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" /> : null}
|
||||
{pending ? <Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" aria-hidden /> : null}
|
||||
Test connection
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user