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:
@@ -126,7 +126,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
|
||||
|
||||
{preflight.isLoading ? (
|
||||
<div className="py-8 text-center text-sm text-muted-foreground">
|
||||
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />
|
||||
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" aria-hidden />
|
||||
Checking each client…
|
||||
</div>
|
||||
) : preflight.error ? (
|
||||
@@ -156,7 +156,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
|
||||
{blocked.length > 0 && (
|
||||
<div className="rounded-md border border-red-300 bg-red-50 p-3 text-xs text-red-900 space-y-1">
|
||||
<div className="font-medium flex items-center gap-1.5">
|
||||
<AlertTriangle className="h-4 w-4" /> Blocked
|
||||
<AlertTriangle className="h-4 w-4" aria-hidden /> Blocked
|
||||
</div>
|
||||
{blocked.slice(0, 5).map((b) => (
|
||||
<div key={b.clientId}>
|
||||
@@ -236,7 +236,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
|
||||
{stage === 'confirm' && (
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="rounded-md border border-emerald-300 bg-emerald-50 p-3 text-emerald-900 flex items-start gap-2">
|
||||
<CheckCircle2 className="h-4 w-4 mt-0.5" />
|
||||
<CheckCircle2 className="h-4 w-4 mt-0.5" aria-hidden />
|
||||
<div>
|
||||
Ready to archive <strong>{archivable.length}</strong> client
|
||||
{archivable.length === 1 ? '' : 's'}
|
||||
@@ -268,7 +268,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
|
||||
}
|
||||
}}
|
||||
>
|
||||
Continue <ArrowRight className="h-4 w-4 ml-1" />
|
||||
Continue <ArrowRight className="h-4 w-4 ml-1" aria-hidden />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -279,18 +279,18 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
|
||||
disabled={carouselIndex === 0}
|
||||
onClick={() => setCarouselIndex((i) => Math.max(0, i - 1))}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4 mr-1" /> Back
|
||||
<ArrowLeft className="h-4 w-4 mr-1" aria-hidden /> Back
|
||||
</Button>
|
||||
{carouselIndex < highStakes.length - 1 ? (
|
||||
<Button
|
||||
disabled={(reasons[currentHighStakes?.clientId ?? '']?.trim().length ?? 0) < 5}
|
||||
onClick={() => setCarouselIndex((i) => i + 1)}
|
||||
>
|
||||
Next <ArrowRight className="h-4 w-4 ml-1" />
|
||||
Next <ArrowRight className="h-4 w-4 ml-1" aria-hidden />
|
||||
</Button>
|
||||
) : (
|
||||
<Button disabled={!allHighStakesReasoned} onClick={() => setStage('confirm')}>
|
||||
Review <ArrowRight className="h-4 w-4 ml-1" />
|
||||
Review <ArrowRight className="h-4 w-4 ml-1" aria-hidden />
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
@@ -304,7 +304,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
|
||||
>
|
||||
{archiveMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" /> Archiving…
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" aria-hidden /> Archiving…
|
||||
</>
|
||||
) : (
|
||||
`Archive ${archivable.length}`
|
||||
|
||||
@@ -111,7 +111,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-destructive">
|
||||
<AlertTriangle className="h-5 w-5" />
|
||||
<AlertTriangle className="h-5 w-5" aria-hidden />
|
||||
Permanently delete {clientIds.length} client{clientIds.length === 1 ? '' : 's'}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -136,7 +136,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
|
||||
{stage === 'confirm' && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-2 rounded-md border border-blue-300 bg-blue-50 p-3 text-xs text-blue-900">
|
||||
<Mail className="h-4 w-4 shrink-0 mt-0.5" />
|
||||
<Mail className="h-4 w-4 shrink-0 mt-0.5" aria-hidden />
|
||||
<div>
|
||||
Code sent to <span className="font-mono">{maskedEmail}</span>. Enter both fields
|
||||
below.
|
||||
@@ -214,7 +214,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
|
||||
>
|
||||
{requestCode.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" /> Sending…
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" aria-hidden /> Sending…
|
||||
</>
|
||||
) : (
|
||||
'Send confirmation code'
|
||||
@@ -229,7 +229,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
|
||||
>
|
||||
{bulkDelete.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" /> Deleting…
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" aria-hidden /> Deleting…
|
||||
</>
|
||||
) : (
|
||||
`Permanently delete ${clientIds.length}`
|
||||
|
||||
@@ -61,16 +61,16 @@ export function ClientCard({ client, portSlug, onEdit, onArchive }: ClientCardPr
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label={`Actions for ${client.fullName}`}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<MoreHorizontal className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => onEdit(client)}>
|
||||
<Pencil className="mr-2 h-3.5 w-3.5" />
|
||||
<Pencil className="mr-2 h-3.5 w-3.5" aria-hidden />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(client)}>
|
||||
<Archive className="mr-2 h-3.5 w-3.5" />
|
||||
<Archive className="mr-2 h-3.5 w-3.5" aria-hidden />
|
||||
Archive
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -121,7 +121,7 @@ export function getClientColumns({
|
||||
className="inline-flex items-center gap-1.5 text-sm text-foreground hover:text-primary hover:underline"
|
||||
title={`Email ${value}`}
|
||||
>
|
||||
<Mail className="h-3 w-3 shrink-0 text-muted-foreground" />
|
||||
<Mail className="h-3 w-3 shrink-0 text-muted-foreground" aria-hidden />
|
||||
<span className="truncate">{value}</span>
|
||||
</a>
|
||||
);
|
||||
@@ -147,7 +147,7 @@ export function getClientColumns({
|
||||
className="inline-flex items-center gap-1.5 text-foreground hover:text-primary hover:underline"
|
||||
title={`Call ${value}`}
|
||||
>
|
||||
<Phone className="h-3 w-3 shrink-0 text-muted-foreground" />
|
||||
<Phone className="h-3 w-3 shrink-0 text-muted-foreground" aria-hidden />
|
||||
<span>{value}</span>
|
||||
</a>
|
||||
{waDigits && (
|
||||
@@ -160,7 +160,7 @@ export function getClientColumns({
|
||||
title={`WhatsApp ${value}`}
|
||||
aria-label={`WhatsApp ${value}`}
|
||||
>
|
||||
<MessageCircle className="h-3.5 w-3.5" />
|
||||
<MessageCircle className="h-3.5 w-3.5" aria-hidden />
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
@@ -303,16 +303,16 @@ export function getClientColumns({
|
||||
className="h-7 w-7"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<MoreHorizontal className="h-4 w-4" aria-hidden />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => onEdit(row.original)}>
|
||||
<Pencil className="mr-2 h-3.5 w-3.5" />
|
||||
<Pencil className="mr-2 h-3.5 w-3.5" aria-hidden />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(row.original)}>
|
||||
<Archive className="mr-2 h-3.5 w-3.5" />
|
||||
<Archive className="mr-2 h-3.5 w-3.5" aria-hidden />
|
||||
Archive
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -167,7 +167,7 @@ export function ClientDetailHeader({ client }: ClientDetailHeaderProps) {
|
||||
title="Permanently delete client"
|
||||
className="shrink-0 rounded-md p-1.5 text-muted-foreground/70 transition-colors hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
<Trash2 className="size-4" aria-hidden />
|
||||
</button>
|
||||
</PermissionGate>
|
||||
)}
|
||||
@@ -182,7 +182,11 @@ export function ClientDetailHeader({ client }: ClientDetailHeaderProps) {
|
||||
isArchived ? 'hover:text-emerald-600' : 'hover:text-destructive',
|
||||
)}
|
||||
>
|
||||
{isArchived ? <RotateCcw className="size-4" /> : <Archive className="size-4" />}
|
||||
{isArchived ? (
|
||||
<RotateCcw className="size-4" aria-hidden />
|
||||
) : (
|
||||
<Archive className="size-4" aria-hidden />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -224,7 +224,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
|
||||
size="sm"
|
||||
onClick={() => append({ channel: 'email', value: '', isPrimary: false })}
|
||||
>
|
||||
<Plus className="mr-1 h-3.5 w-3.5" />
|
||||
<Plus className="mr-1 h-3.5 w-3.5" aria-hidden />
|
||||
Add Contact
|
||||
</Button>
|
||||
</div>
|
||||
@@ -333,7 +333,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
|
||||
className="h-8 text-destructive hover:text-destructive"
|
||||
onClick={() => remove(index)}
|
||||
>
|
||||
<Trash2 className="mr-1 h-3.5 w-3.5" />
|
||||
<Trash2 className="mr-1 h-3.5 w-3.5" aria-hidden />
|
||||
Remove
|
||||
</Button>
|
||||
)}
|
||||
@@ -419,7 +419,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
|
||||
</Button>
|
||||
<Button type="submit" disabled={isSubmitting || mutation.isPending}>
|
||||
{(isSubmitting || mutation.isPending) && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden />
|
||||
)}
|
||||
{isEdit ? 'Save changes' : 'Create Client'}
|
||||
</Button>
|
||||
|
||||
@@ -77,7 +77,10 @@ function InterestRowItem({
|
||||
<p className="mt-0.5 truncate text-xs text-muted-foreground">{yachtLabel}</p>
|
||||
) : null}
|
||||
</div>
|
||||
<ChevronRight className="size-4 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5" />
|
||||
<ChevronRight
|
||||
className="size-4 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mt-3">
|
||||
@@ -364,9 +367,9 @@ function InterestPreviewSheet({
|
||||
function InterestSkeleton() {
|
||||
return (
|
||||
<div className="rounded-xl border border-border bg-card p-4 shadow-sm">
|
||||
<Skeleton className="h-4 w-32" />
|
||||
<Skeleton className="mt-2 h-3 w-24" />
|
||||
<Skeleton className="mt-3 h-2 w-48" />
|
||||
<Skeleton className="h-4 w-32" aria-hidden />
|
||||
<Skeleton className="mt-2 h-3 w-24" aria-hidden />
|
||||
<Skeleton className="mt-3 h-2 w-48" aria-hidden />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -421,7 +424,7 @@ export function ClientInterestsTab({ clientId }: ClientInterestsTabProps) {
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-end">
|
||||
<Button size="sm" onClick={() => setCreateOpen(true)}>
|
||||
<Plus className="mr-1.5 size-3.5" />
|
||||
<Plus className="mr-1.5 size-3.5" aria-hidden />
|
||||
Add interest
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +167,7 @@ export function ClientList() {
|
||||
far-right edge, which is where reps look first. */}
|
||||
<PermissionGate resource="clients" action="create">
|
||||
<Button size="sm" className="ml-auto" onClick={() => setCreateOpen(true)}>
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
<Plus className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
New Client
|
||||
</Button>
|
||||
</PermissionGate>
|
||||
|
||||
@@ -124,8 +124,8 @@ function HeroVariant({ clientId, portSlug }: { clientId: string; portSlug: strin
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-40" />
|
||||
<Skeleton className="h-2 w-48" />
|
||||
<Skeleton className="h-4 w-40" aria-hidden />
|
||||
<Skeleton className="h-2 w-48" aria-hidden />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -143,7 +143,7 @@ function HeroVariant({ clientId, portSlug }: { clientId: string; portSlug: strin
|
||||
href={`/${portSlug}/interests/new` as Route}
|
||||
className="inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
|
||||
>
|
||||
Start interest <ArrowRight className="size-3" />
|
||||
Start interest <ArrowRight className="size-3" aria-hidden />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
@@ -182,7 +182,10 @@ function HeroVariant({ clientId, portSlug }: { clientId: string; portSlug: strin
|
||||
>
|
||||
{STAGE_LABELS[stage]}
|
||||
</span>
|
||||
<ChevronRight className="size-3.5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5" />
|
||||
<ChevronRight
|
||||
className="size-3.5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
||||
aria-hidden
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-1.5">
|
||||
<StageStepper current={stage} size="xs" />
|
||||
@@ -214,8 +217,8 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-40" />
|
||||
<Skeleton className="h-2 w-48" />
|
||||
<Skeleton className="h-4 w-40" aria-hidden />
|
||||
<Skeleton className="h-2 w-48" aria-hidden />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -233,7 +236,7 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
|
||||
href={`/${portSlug}/interests/new` as Route}
|
||||
className="inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline"
|
||||
>
|
||||
Start interest <ArrowRight className="size-3" />
|
||||
Start interest <ArrowRight className="size-3" aria-hidden />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
@@ -289,7 +292,10 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
|
||||
<StageStepper current={stage} size="xs" />
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="size-3.5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5" />
|
||||
<ChevronRight
|
||||
className="size-3.5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5"
|
||||
aria-hidden
|
||||
/>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export function ClientYachtsTab({ clientId, yachts }: ClientYachtsTabProps) {
|
||||
<h3 className="text-sm font-medium">Client-owned yachts</h3>
|
||||
<PermissionGate resource="yachts" action="create">
|
||||
<Button size="sm" onClick={() => setCreateOpen(true)}>
|
||||
<Plus className="mr-1.5 h-4 w-4" />
|
||||
<Plus className="mr-1.5 h-4 w-4" aria-hidden />
|
||||
Add yacht
|
||||
</Button>
|
||||
</PermissionGate>
|
||||
|
||||
@@ -141,7 +141,7 @@ export function ContactsEditor({ clientId, contacts }: { clientId: string; conta
|
||||
onClick={() => setAdding(true)}
|
||||
className="w-full justify-center"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5 mr-1.5" />
|
||||
<Plus className="h-3.5 w-3.5 mr-1.5" aria-hidden />
|
||||
Add contact
|
||||
</Button>
|
||||
)}
|
||||
@@ -201,7 +201,7 @@ function ContactRow({
|
||||
{/* Top / left: channel + value */}
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
<ChannelPicker value={contact.channel} onChange={changeChannel}>
|
||||
<Icon className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Icon className="h-3.5 w-3.5 text-muted-foreground" aria-hidden />
|
||||
</ChannelPicker>
|
||||
<div className="min-w-0 flex-1">
|
||||
{contact.channel === 'phone' || contact.channel === 'whatsapp' ? (
|
||||
@@ -282,7 +282,7 @@ function ContactRow({
|
||||
title="Remove"
|
||||
className="rounded p-1 text-muted-foreground/50 transition-all hover:bg-background/60 hover:text-destructive sm:opacity-0 sm:group-hover:opacity-100"
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" />
|
||||
<Trash2 className="h-3.5 w-3.5" aria-hidden />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -439,7 +439,7 @@ function NewContactForm({
|
||||
|
||||
<div className="ml-auto flex gap-2">
|
||||
<Button type="button" size="sm" onClick={submit} disabled={submitDisabled}>
|
||||
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : 'Save'}
|
||||
{saving ? <Loader2 className="h-3.5 w-3.5 animate-spin" aria-hidden /> : 'Save'}
|
||||
</Button>
|
||||
<Button type="button" size="sm" variant="ghost" onClick={onCancel} disabled={saving}>
|
||||
Cancel
|
||||
|
||||
@@ -111,7 +111,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="h-8">
|
||||
<FileDown className="mr-1.5 h-3.5 w-3.5" />
|
||||
<FileDown className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
GDPR export
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
@@ -154,9 +154,9 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
|
||||
|
||||
<Button onClick={() => request.mutate()} disabled={request.isPending}>
|
||||
{request.isPending ? (
|
||||
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
|
||||
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" aria-hidden />
|
||||
) : (
|
||||
<FileDown className="mr-1.5 h-3.5 w-3.5" />
|
||||
<FileDown className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
)}
|
||||
Queue export
|
||||
</Button>
|
||||
@@ -180,7 +180,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
|
||||
</div>
|
||||
{r.sentTo ? (
|
||||
<div className="text-xs text-muted-foreground inline-flex items-center gap-1">
|
||||
<Mail className="h-3 w-3" />
|
||||
<Mail className="h-3 w-3" aria-hidden />
|
||||
Sent to {r.sentTo}
|
||||
</div>
|
||||
) : null}
|
||||
@@ -195,7 +195,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
|
||||
size="sm"
|
||||
onClick={() => downloadById(r.id)}
|
||||
>
|
||||
<Download className="h-3.5 w-3.5" />
|
||||
<Download className="h-3.5 w-3.5" aria-hidden />
|
||||
</Button>
|
||||
) : null}
|
||||
</li>
|
||||
|
||||
@@ -94,7 +94,7 @@ function HardDeleteDialogBody({ onOpenChange, clientId, clientName, onDeleted }:
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-destructive">
|
||||
<AlertTriangle className="h-5 w-5" />
|
||||
<AlertTriangle className="h-5 w-5" aria-hidden />
|
||||
Permanently delete {clientName}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -126,7 +126,7 @@ function HardDeleteDialogBody({ onOpenChange, clientId, clientName, onDeleted }:
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-2 rounded-md border border-blue-300 bg-blue-50 p-3 text-xs text-blue-900">
|
||||
<Mail className="h-4 w-4 shrink-0 mt-0.5" />
|
||||
<Mail className="h-4 w-4 shrink-0 mt-0.5" aria-hidden />
|
||||
<div className="flex-1">
|
||||
<div>
|
||||
Code sent to <span className="font-mono">{maskedEmail}</span>. It expires in 10
|
||||
@@ -185,7 +185,7 @@ function HardDeleteDialogBody({ onOpenChange, clientId, clientName, onDeleted }:
|
||||
>
|
||||
{requestCode.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" /> Sending…
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" aria-hidden /> Sending…
|
||||
</>
|
||||
) : (
|
||||
'Send confirmation code'
|
||||
@@ -199,7 +199,7 @@ function HardDeleteDialogBody({ onOpenChange, clientId, clientName, onDeleted }:
|
||||
>
|
||||
{hardDelete.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" /> Deleting…
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" aria-hidden /> Deleting…
|
||||
</>
|
||||
) : (
|
||||
'Permanently delete'
|
||||
|
||||
@@ -75,7 +75,7 @@ export function PortalInviteButton({
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<UserPlus className="mr-1.5 h-3.5 w-3.5" />
|
||||
<UserPlus className="mr-1.5 h-3.5 w-3.5" aria-hidden />
|
||||
Invite to portal
|
||||
</Button>
|
||||
|
||||
@@ -96,7 +96,7 @@ export function PortalInviteButton({
|
||||
|
||||
{success ? (
|
||||
<div className="py-4 flex items-center gap-3 text-sm text-green-700">
|
||||
<Check className="h-5 w-5" />
|
||||
<Check className="h-5 w-5" aria-hidden />
|
||||
Activation email sent to <strong>{email}</strong>.
|
||||
</div>
|
||||
) : (
|
||||
@@ -138,7 +138,7 @@ export function PortalInviteButton({
|
||||
<Button onClick={submit} disabled={loading || !email}>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
||||
<Loader2 className="h-4 w-4 mr-2 animate-spin" aria-hidden />
|
||||
Sending…
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -82,11 +82,11 @@ export function SendDocumentsDialog({
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h3 className="mb-2 flex items-center gap-2 text-sm font-semibold">
|
||||
<Mail className="h-4 w-4" /> Brochures
|
||||
<Mail className="h-4 w-4" aria-hidden /> Brochures
|
||||
</h3>
|
||||
{brochuresQuery.isLoading && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Loading brochures…
|
||||
<Loader2 className="h-4 w-4 animate-spin" aria-hidden /> Loading brochures…
|
||||
</div>
|
||||
)}
|
||||
{!brochuresQuery.isLoading && usableBrochures.length === 0 && (
|
||||
@@ -103,7 +103,7 @@ export function SendDocumentsDialog({
|
||||
onClick={() => setActiveSend({ kind: 'brochure', brochureId: b.id })}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
<FileText className="h-4 w-4" aria-hidden />
|
||||
{b.label}
|
||||
{b.isDefault && (
|
||||
<span className="rounded bg-primary/10 px-2 py-0.5 text-xs text-primary">
|
||||
|
||||
@@ -283,7 +283,7 @@ function SmartArchiveDialogBody({
|
||||
|
||||
{isLoading ? (
|
||||
<div className="py-8 text-center text-sm text-muted-foreground">
|
||||
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />
|
||||
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" aria-hidden />
|
||||
Loading dossier…
|
||||
</div>
|
||||
) : error || !dossier ? (
|
||||
@@ -296,7 +296,7 @@ function SmartArchiveDialogBody({
|
||||
<Card className="border-red-300 bg-red-50">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-red-800 flex items-center gap-2">
|
||||
<AlertTriangle className="h-4 w-4" /> Cannot archive
|
||||
<AlertTriangle className="h-4 w-4" aria-hidden /> Cannot archive
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-red-700 space-y-1">
|
||||
@@ -311,7 +311,7 @@ function SmartArchiveDialogBody({
|
||||
<Card className="border-amber-300 bg-amber-50">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-amber-900 flex items-center gap-2">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertTriangle className="h-4 w-4" aria-hidden />
|
||||
Late-stage deal — confirmation required
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -328,7 +328,8 @@ function SmartArchiveDialogBody({
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" /> Pipeline interests ({dossier.interests.length})
|
||||
<FileText className="h-4 w-4" aria-hidden /> Pipeline interests (
|
||||
{dossier.interests.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-xs space-y-1">
|
||||
@@ -371,7 +372,7 @@ function SmartArchiveDialogBody({
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<Anchor className="h-4 w-4" /> Berths ({dossier.berths.length})
|
||||
<Anchor className="h-4 w-4" aria-hidden /> Berths ({dossier.berths.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
@@ -425,7 +426,7 @@ function SmartArchiveDialogBody({
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<Ship className="h-4 w-4" /> Yachts owned ({dossier.yachts.length})
|
||||
<Ship className="h-4 w-4" aria-hidden /> Yachts owned ({dossier.yachts.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
@@ -459,7 +460,7 @@ function SmartArchiveDialogBody({
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<Anchor className="h-4 w-4" /> Active reservations (
|
||||
<Anchor className="h-4 w-4" aria-hidden /> Active reservations (
|
||||
{dossier.reservations.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -493,7 +494,8 @@ function SmartArchiveDialogBody({
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<Receipt className="h-4 w-4" /> Outstanding invoices ({dossier.invoices.length})
|
||||
<Receipt className="h-4 w-4" aria-hidden /> Outstanding invoices (
|
||||
{dossier.invoices.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
@@ -527,7 +529,7 @@ function SmartArchiveDialogBody({
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<FileText className="h-4 w-4" /> In-flight signing requests
|
||||
<FileText className="h-4 w-4" aria-hidden /> In-flight signing requests
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
@@ -559,7 +561,7 @@ function SmartArchiveDialogBody({
|
||||
<Card className="border-muted">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
||||
<Users className="h-4 w-4" /> Automatically handled
|
||||
<Users className="h-4 w-4" aria-hidden /> Automatically handled
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-xs text-muted-foreground space-y-1">
|
||||
@@ -600,7 +602,7 @@ function SmartArchiveDialogBody({
|
||||
onClick={() => archiveMutation.mutate()}
|
||||
>
|
||||
{archiveMutation.isPending ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin mr-1.5" />
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin mr-1.5" aria-hidden />
|
||||
) : null}
|
||||
Archive
|
||||
</Button>
|
||||
|
||||
@@ -53,10 +53,10 @@ interface Props {
|
||||
}
|
||||
|
||||
function iconFor(kind: string) {
|
||||
if (kind.startsWith('berth_')) return <Anchor className="h-3 w-3" />;
|
||||
if (kind.startsWith('yacht_')) return <Ship className="h-3 w-3" />;
|
||||
if (kind.startsWith('documenso_')) return <FileText className="h-3 w-3" />;
|
||||
return <Wrench className="h-3 w-3" />;
|
||||
if (kind.startsWith('berth_')) return <Anchor className="h-3 w-3" aria-hidden />;
|
||||
if (kind.startsWith('yacht_')) return <Ship className="h-3 w-3" aria-hidden />;
|
||||
if (kind.startsWith('documenso_')) return <FileText className="h-3 w-3" aria-hidden />;
|
||||
return <Wrench className="h-3 w-3" aria-hidden />;
|
||||
}
|
||||
|
||||
export function SmartRestoreDialog(props: Props) {
|
||||
@@ -135,7 +135,7 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
|
||||
|
||||
{dossierQuery.isLoading ? (
|
||||
<div className="py-8 text-center text-sm text-muted-foreground">
|
||||
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" />
|
||||
<Loader2 className="h-5 w-5 animate-spin mx-auto mb-2" aria-hidden />
|
||||
Loading restore dossier…
|
||||
</div>
|
||||
) : dossierQuery.error || !dossier ? (
|
||||
@@ -153,7 +153,7 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
|
||||
<Card className="border-emerald-300 bg-emerald-50">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-emerald-900 flex items-center gap-2">
|
||||
<CheckCircle2 className="h-4 w-4" /> Auto-reversed (
|
||||
<CheckCircle2 className="h-4 w-4" aria-hidden /> Auto-reversed (
|
||||
{dossier.autoReversible.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -174,7 +174,7 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
|
||||
<Card className="border-amber-300 bg-amber-50">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-amber-900 flex items-center gap-2">
|
||||
<AlertTriangle className="h-4 w-4" /> Opt-in to undo (
|
||||
<AlertTriangle className="h-4 w-4" aria-hidden /> Opt-in to undo (
|
||||
{dossier.reversibleWithPrompt.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -207,7 +207,8 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
|
||||
<Card className="border-slate-300 bg-slate-50">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-slate-900 flex items-center gap-2">
|
||||
<Lock className="h-4 w-4" /> Cannot be undone ({dossier.locked.length})
|
||||
<Lock className="h-4 w-4" aria-hidden /> Cannot be undone (
|
||||
{dossier.locked.length})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-xs space-y-1.5 text-slate-700">
|
||||
@@ -236,7 +237,7 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
|
||||
>
|
||||
{restoreMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" /> Restoring…
|
||||
<Loader2 className="h-4 w-4 animate-spin mr-1.5" aria-hidden /> Restoring…
|
||||
</>
|
||||
) : (
|
||||
'Restore client'
|
||||
|
||||
Reference in New Issue
Block a user