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:
2026-05-13 12:37:22 +02:00
parent ecf49be18c
commit c8ea9ec0a0
172 changed files with 727 additions and 614 deletions

View File

@@ -377,7 +377,10 @@ export function AdminSectionsBrowser({ portSlug }: AdminSectionsBrowserProps) {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div className="relative max-w-md"> <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 <Input
type="search" type="search"
inputMode="search" inputMode="search"
@@ -466,7 +469,10 @@ function SectionCard({
)} )}
> >
<CardHeader className="flex flex-row items-start gap-3 space-y-0 pb-2"> <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"> <div className="flex-1">
<CardTitle className="text-base">{section.label}</CardTitle> <CardTitle className="text-base">{section.label}</CardTitle>
{groupTitle ? ( {groupTitle ? (

View File

@@ -88,7 +88,7 @@ function AiBudgetCardBody({
<CardTitle>AI cost guardrails</CardTitle> <CardTitle>AI cost guardrails</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="flex items-center gap-2 text-sm text-muted-foreground"> <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> </CardContent>
</Card> </Card>
); );
@@ -198,7 +198,9 @@ function AiBudgetCardBody({
<div className="flex gap-2"> <div className="flex gap-2">
<Button onClick={() => save.mutate()} disabled={save.isPending}> <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 Save guardrails
</Button> </Button>
</div> </div>

View File

@@ -44,11 +44,11 @@ const ACTION_BADGE_COLORS: Record<string, string> = {
}; };
function ActionIcon({ action }: { action: string }) { function ActionIcon({ action }: { action: string }) {
if (action === 'create') return <Plus 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" />; if (action === 'update') return <Pencil className="h-5 w-5" aria-hidden />;
if (action === 'delete') return <Trash2 className="h-5 w-5" />; if (action === 'delete') return <Trash2 className="h-5 w-5" aria-hidden />;
if (action === 'viewed') return <Eye className="h-5 w-5" />; if (action === 'viewed') return <Eye className="h-5 w-5" aria-hidden />;
return <Activity className="h-5 w-5" />; return <Activity className="h-5 w-5" aria-hidden />;
} }
function actionVerb(action: string): string { function actionVerb(action: string): string {
@@ -114,7 +114,7 @@ export function AuditLogCard({ entry }: AuditLogCardProps) {
{/* Timestamp + IP meta line */} {/* 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"> <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 })} {formatDistanceToNow(new Date(entry.createdAt), { addSuffix: true })}
</ListCardMeta> </ListCardMeta>
{entry.ipAddress ? ( {entry.ipAddress ? (

View File

@@ -351,7 +351,10 @@ export function AuditLogList() {
Search Search
</Label> </Label>
<div className="relative w-72"> <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 <Input
id="audit-search" id="audit-search"
className="pl-9 h-9" className="pl-9 h-9"

View File

@@ -102,7 +102,7 @@ export function BackupAdminPanel() {
<CardHeader className="flex flex-row items-start justify-between gap-3 space-y-0"> <CardHeader className="flex flex-row items-start justify-between gap-3 space-y-0">
<div> <div>
<CardTitle className="text-base flex items-center gap-2"> <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 Run a backup now
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
@@ -112,11 +112,11 @@ export function BackupAdminPanel() {
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button variant="ghost" size="sm" onClick={load} disabled={loading || running}> <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 Refresh
</Button> </Button>
<Button onClick={() => setConfirmOpen(true)} disabled={running}> <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'} {running ? 'Running…' : 'Run backup'}
</Button> </Button>
</div> </div>
@@ -136,7 +136,7 @@ export function BackupAdminPanel() {
<CardContent> <CardContent>
{loading ? ( {loading ? (
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <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> </div>
) : jobs.length === 0 ? ( ) : jobs.length === 0 ? (
<p className="text-sm text-muted-foreground">No backups yet.</p> <p className="text-sm text-muted-foreground">No backups yet.</p>
@@ -168,7 +168,7 @@ export function BackupAdminPanel() {
</span> </span>
{j.status === 'completed' && ( {j.status === 'completed' && (
<Button size="sm" variant="outline" onClick={() => download(j.id)}> <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 Download
</Button> </Button>
)} )}
@@ -184,7 +184,7 @@ export function BackupAdminPanel() {
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <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? Run a fresh backup now?
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -207,7 +207,7 @@ export function PdfLogoUploader() {
{loading ? ( {loading ? (
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <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> </div>
) : current ? ( ) : current ? (
<div className="flex items-start gap-4 rounded-md border p-4"> <div className="flex items-start gap-4 rounded-md border p-4">
@@ -232,7 +232,7 @@ export function PdfLogoUploader() {
Test with sample PDF Test with sample PDF
</Button> </Button>
<Button variant="outline" size="sm" onClick={clear} disabled={working}> <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> </Button>
</div> </div>
</div> </div>
@@ -292,9 +292,9 @@ export function PdfLogoUploader() {
<div className="flex gap-2"> <div className="flex gap-2">
<Button onClick={upload} disabled={working}> <Button onClick={upload} disabled={working}>
{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 Save logo
</Button> </Button>
@@ -316,7 +316,7 @@ export function PdfLogoUploader() {
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
disabled={working} 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> </Button>
</div> </div>
</div> </div>

View File

@@ -69,13 +69,13 @@ export function BrochuresAdminPanel() {
<div className="space-y-4"> <div className="space-y-4">
<div className="flex justify-end"> <div className="flex justify-end">
<Button onClick={() => setCreateOpen(true)}> <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> </Button>
</div> </div>
{brochuresQuery.isLoading && ( {brochuresQuery.isLoading && (
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <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> </div>
)} )}
@@ -179,10 +179,10 @@ function BrochureCard({ brochure, onChange }: { brochure: BrochureRow; onChange:
<CardHeader> <CardHeader>
<CardTitle className="flex items-center justify-between text-base"> <CardTitle className="flex items-center justify-between text-base">
<span className="flex items-center gap-2"> <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 && ( {brochure.isDefault && (
<span className="flex items-center gap-1 rounded bg-primary/10 px-2 py-0.5 text-xs text-primary"> <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> </span>
)} )}
{brochure.archivedAt && ( {brochure.archivedAt && (
@@ -222,9 +222,9 @@ function BrochureCard({ brochure, onChange }: { brochure: BrochureRow; onChange:
<Button asChild variant="outline" size="sm" disabled={uploading}> <Button asChild variant="outline" size="sm" disabled={uploading}>
<span> <span>
{uploading ? ( {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 Upload version
</span> </span>
@@ -237,7 +237,7 @@ function BrochureCard({ brochure, onChange }: { brochure: BrochureRow; onChange:
onClick={() => setDefaultMutation.mutate()} onClick={() => setDefaultMutation.mutate()}
disabled={setDefaultMutation.isPending} 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>
)} )}
<Button <Button
@@ -246,7 +246,7 @@ function BrochureCard({ brochure, onChange }: { brochure: BrochureRow; onChange:
onClick={() => archiveMutation.mutate()} onClick={() => archiveMutation.mutate()}
disabled={archiveMutation.isPending} disabled={archiveMutation.isPending}
> >
<Archive className="mr-2 h-3 w-3" /> Archive <Archive className="mr-2 h-3 w-3" aria-hidden /> Archive
</Button> </Button>
</> </>
)} )}
@@ -331,7 +331,9 @@ function CreateBrochureDialog({
disabled={!label.trim() || createMutation.isPending} disabled={!label.trim() || createMutation.isPending}
onClick={() => createMutation.mutate()} 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 Create
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -263,7 +263,7 @@ function CustomFieldFormBody({ open, onOpenChange, field, onSuccess }: CustomFie
maxLength={100} maxLength={100}
/> />
<Button type="button" variant="outline" size="sm" onClick={addOption}> <Button type="button" variant="outline" size="sm" onClick={addOption}>
<Plus className="h-4 w-4" /> <Plus className="h-4 w-4" aria-hidden />
</Button> </Button>
</div> </div>
{selectOptions.length > 0 && ( {selectOptions.length > 0 && (

View File

@@ -124,7 +124,7 @@ export function CustomFieldsManager() {
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center justify-end gap-1"> <div className="flex items-center justify-end gap-1">
<Button variant="ghost" size="sm" onClick={() => handleEdit(row.original)}> <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> <span className="sr-only">Edit</span>
</Button> </Button>
<ConfirmationDialog <ConfirmationDialog
@@ -135,7 +135,7 @@ export function CustomFieldsManager() {
className="text-destructive hover:text-destructive" className="text-destructive hover:text-destructive"
disabled={deletingId === row.original.id} 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> <span className="sr-only">Delete</span>
</Button> </Button>
} }
@@ -159,7 +159,7 @@ export function CustomFieldsManager() {
description="Define custom fields for clients and records" description="Define custom fields for clients and records"
actions={ actions={
<Button onClick={handleCreate}> <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 New Field
</Button> </Button>
} }
@@ -228,7 +228,7 @@ export function CustomFieldsManager() {
onClick={() => handleEdit(original)} onClick={() => handleEdit(original)}
aria-label="Edit field" aria-label="Edit field"
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" aria-hidden />
</Button> </Button>
<ConfirmationDialog <ConfirmationDialog
trigger={ trigger={
@@ -239,7 +239,7 @@ export function CustomFieldsManager() {
disabled={deletingId === original.id} disabled={deletingId === original.id}
aria-label="Delete field" aria-label="Delete field"
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" aria-hidden />
</Button> </Button>
} }
title="Delete Custom Field" title="Delete Custom Field"

View File

@@ -47,17 +47,17 @@ export function DocumensoTestButton() {
{result && {result &&
(result.ok ? ( (result.ok ? (
<span className="flex items-center text-xs text-green-600"> <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} HTTP {result.status}
</span> </span>
) : ( ) : (
<span className="flex items-center text-xs text-destructive"> <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}`} {result.error ?? `HTTP ${result.status}`}
</span> </span>
))} ))}
<Button variant="outline" onClick={runTest} disabled={pending}> <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 Test connection
</Button> </Button>
</div> </div>

View File

@@ -94,7 +94,7 @@ export function TemplateList() {
header: 'Name', header: 'Name',
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center gap-2"> <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> <span className="font-medium">{row.original.name}</span>
</div> </div>
), ),
@@ -147,7 +147,7 @@ export function TemplateList() {
title="Version history" title="Version history"
onClick={() => handleViewHistory(row.original)} onClick={() => handleViewHistory(row.original)}
> >
<History className="h-4 w-4" /> <History className="h-4 w-4" aria-hidden />
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
@@ -155,7 +155,7 @@ export function TemplateList() {
title="Edit template" title="Edit template"
onClick={() => handleEditTemplate(row.original)} onClick={() => handleEditTemplate(row.original)}
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" aria-hidden />
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
@@ -168,7 +168,7 @@ export function TemplateList() {
<ConfirmationDialog <ConfirmationDialog
trigger={ trigger={
<Button variant="ghost" size="icon" title="Delete template"> <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> </Button>
} }
title="Delete Template" title="Delete Template"
@@ -189,7 +189,7 @@ export function TemplateList() {
description="Manage reusable document templates with TipTap content and PDF generation" description="Manage reusable document templates with TipTap content and PDF generation"
actions={ actions={
<Button onClick={handleNewTemplate}> <Button onClick={handleNewTemplate}>
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" aria-hidden />
New Template New Template
</Button> </Button>
} }
@@ -237,7 +237,7 @@ export function TemplateList() {
title="Version history" title="Version history"
onClick={() => handleViewHistory(original)} onClick={() => handleViewHistory(original)}
> >
<History className="h-4 w-4" /> <History className="h-4 w-4" aria-hidden />
</Button> </Button>
<Button <Button
variant="ghost" variant="ghost"
@@ -245,12 +245,12 @@ export function TemplateList() {
title="Edit template" title="Edit template"
onClick={() => handleEditTemplate(original)} onClick={() => handleEditTemplate(original)}
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" aria-hidden />
</Button> </Button>
<ConfirmationDialog <ConfirmationDialog
trigger={ trigger={
<Button variant="ghost" size="icon" title="Delete template"> <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> </Button>
} }
title="Delete Template" title="Delete Template"

View File

@@ -59,7 +59,7 @@ export function TemplatePreview({ content, templateName }: TemplatePreviewProps)
return ( return (
<> <>
<Button variant="outline" size="sm" onClick={handlePreview}> <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 Preview PDF
</Button> </Button>
@@ -70,7 +70,7 @@ export function TemplatePreview({ content, templateName }: TemplatePreviewProps)
<DialogTitle>Preview - {templateName}</DialogTitle> <DialogTitle>Preview - {templateName}</DialogTitle>
{pdfBase64 && ( {pdfBase64 && (
<Button variant="ghost" size="sm" onClick={handleOpenInNewTab} className="mr-6"> <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 Open in new tab
</Button> </Button>
)} )}

View File

@@ -81,7 +81,7 @@ export function TemplateVersionHistory({
if (versions.length === 0) { if (versions.length === 0) {
return ( return (
<div className="flex flex-col items-center gap-2 rounded-md border border-dashed p-6 text-center"> <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"> <p className="text-sm text-muted-foreground">
No previous versions found. Versions are saved whenever you update the template content. No previous versions found. Versions are saved whenever you update the template content.
</p> </p>
@@ -129,7 +129,7 @@ export function TemplateVersionHistory({
onClick={() => handleRollback(v.version)} onClick={() => handleRollback(v.version)}
disabled={rollingBack === 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'} {rollingBack === v.version ? 'Restoring…' : 'Restore'}
</Button> </Button>
</div> </div>

View File

@@ -150,7 +150,7 @@ function EmailTemplatesAdminBody({
onClick={() => save(row, 'save')} onClick={() => save(row, 'save')}
disabled={savingKey === row.key || !dirty} 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> </Button>
{overridden ? ( {overridden ? (
<Button <Button
@@ -159,7 +159,7 @@ function EmailTemplatesAdminBody({
onClick={() => save(row, 'reset')} onClick={() => save(row, 'reset')}
disabled={savingKey === row.key} 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> </Button>
) : null} ) : null}
{message?.key === row.key ? ( {message?.key === row.key ? (

View File

@@ -139,7 +139,7 @@ function FormTemplateFormBody({ open, onOpenChange, template, onSaved }: Props)
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Label className="text-sm font-medium">Fields</Label> <Label className="text-sm font-medium">Fields</Label>
<Button variant="outline" size="sm" onClick={addField}> <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 Add field
</Button> </Button>
</div> </div>
@@ -154,7 +154,7 @@ function FormTemplateFormBody({ open, onOpenChange, template, onSaved }: Props)
className="text-destructive h-7 w-7" className="text-destructive h-7 w-7"
onClick={() => removeField(i)} onClick={() => removeField(i)}
> >
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" aria-hidden />
</Button> </Button>
)} )}
</div> </div>

View File

@@ -57,7 +57,7 @@ export function FormTemplateList() {
setFormOpen(true); setFormOpen(true);
}} }}
> >
<Plus className="h-4 w-4 mr-1.5" /> <Plus className="h-4 w-4 mr-1.5" aria-hidden />
New template New template
</Button> </Button>
} }
@@ -95,12 +95,12 @@ export function FormTemplateList() {
setFormOpen(true); setFormOpen(true);
}} }}
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" aria-hidden />
</Button> </Button>
<ConfirmationDialog <ConfirmationDialog
trigger={ trigger={
<Button variant="ghost" size="icon" className="text-destructive"> <Button variant="ghost" size="icon" className="text-destructive">
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" aria-hidden />
</Button> </Button>
} }
title="Delete form template" title="Delete form template"

View File

@@ -89,7 +89,7 @@ export function InvitationsManager() {
</p> </p>
</div> </div>
<Button onClick={() => setSheetOpen(true)}> <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 Send invite
</Button> </Button>
</div> </div>
@@ -98,7 +98,7 @@ export function InvitationsManager() {
<p className="text-sm text-muted-foreground">Loading</p> <p className="text-sm text-muted-foreground">Loading</p>
) : invites.length === 0 ? ( ) : invites.length === 0 ? (
<div className="rounded-lg border border-dashed p-8 text-center text-muted-foreground"> <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> <p className="text-sm">No invitations issued yet.</p>
</div> </div>
) : ( ) : (
@@ -142,7 +142,7 @@ export function InvitationsManager() {
disabled={resendMutation.isPending || !!i.usedAt} disabled={resendMutation.isPending || !!i.usedAt}
title="Resend invite" title="Resend invite"
> >
<RotateCw className="h-3.5 w-3.5" /> <RotateCw className="h-3.5 w-3.5" aria-hidden />
</Button> </Button>
{i.status === 'pending' && ( {i.status === 'pending' && (
<ConfirmationDialog <ConfirmationDialog
@@ -153,7 +153,7 @@ export function InvitationsManager() {
className="text-destructive" className="text-destructive"
title="Revoke" title="Revoke"
> >
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" aria-hidden />
</Button> </Button>
} }
title="Revoke invitation?" title="Revoke invitation?"
@@ -230,7 +230,9 @@ export function InvitationsManager() {
Cancel Cancel
</Button> </Button>
<Button type="submit" disabled={!email.trim() || createMutation.isPending}> <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 Send invite
</Button> </Button>
</SheetFooter> </SheetFooter>

View File

@@ -138,7 +138,7 @@ function SettingsBlockBody({
<CardTitle>{title}</CardTitle> <CardTitle>{title}</CardTitle>
</CardHeader> </CardHeader>
<CardContent className="flex items-center gap-2 text-sm text-muted-foreground"> <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> </CardContent>
</Card> </Card>
); );
@@ -250,7 +250,11 @@ function SettingsBlockBody({
onClick={() => setShowKey((v) => !v)} onClick={() => setShowKey((v) => !v)}
aria-label={showKey ? 'Hide key' : 'Show key'} 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> </Button>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
@@ -264,7 +268,9 @@ function SettingsBlockBody({
disabled={save.isPending} disabled={save.isPending}
data-testid={`save-${scope}`} 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 Save settings
</Button> </Button>
<Button <Button
@@ -273,7 +279,9 @@ function SettingsBlockBody({
onClick={() => test.mutate()} onClick={() => test.mutate()}
disabled={test.isPending || apiKey.length === 0} 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 Test connection
</Button> </Button>
{hasKey ? ( {hasKey ? (
@@ -290,13 +298,13 @@ function SettingsBlockBody({
{testStatus?.ok ? ( {testStatus?.ok ? (
<span className="inline-flex items-center gap-1 text-sm text-green-700"> <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 Connection OK
</span> </span>
) : null} ) : null}
{testStatus && !testStatus.ok ? ( {testStatus && !testStatus.ok ? (
<span className="inline-flex items-center gap-1 text-sm text-destructive"> <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} {testStatus.reason}
</span> </span>
) : null} ) : null}

View File

@@ -228,11 +228,11 @@ export function OnboardingChecklist() {
} }
> >
{done ? ( {done ? (
<Check className="h-4 w-4" /> <Check className="h-4 w-4" aria-hidden />
) : loading ? ( ) : 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> </span>
<div className="flex-1 min-w-0"> <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" className="text-sm font-medium hover:underline inline-flex items-center gap-1"
> >
{idx + 1}. {step.label} {idx + 1}. {step.label}
<ExternalLink className="h-3 w-3 opacity-50" /> <ExternalLink className="h-3 w-3 opacity-50" aria-hidden />
</Link> </Link>
<p className="mt-0.5 text-xs text-muted-foreground">{step.description}</p> <p className="mt-0.5 text-xs text-muted-foreground">{step.description}</p>
{auto && ( {auto && (
@@ -265,7 +265,7 @@ export function OnboardingChecklist() {
onClick={() => toggleManual(step.id)} onClick={() => toggleManual(step.id)}
> >
{saving === 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 ? ( ) : manual ? (
'Mark incomplete' 'Mark incomplete'
) : ( ) : (

View File

@@ -95,7 +95,7 @@ export function PortList() {
header: '', header: '',
cell: ({ row }) => ( cell: ({ row }) => (
<Button variant="ghost" size="sm" onClick={() => handleEditPort(row.original)}> <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> <span className="sr-only">Edit</span>
</Button> </Button>
), ),
@@ -111,7 +111,7 @@ export function PortList() {
description="Manage marina ports and their configuration" description="Manage marina ports and their configuration"
actions={ actions={
<Button onClick={handleNewPort}> <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 New Port
</Button> </Button>
} }
@@ -154,7 +154,7 @@ export function PortList() {
aria-label={`Edit ${original.name}`} aria-label={`Edit ${original.name}`}
className="shrink-0" className="shrink-0"
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" aria-hidden />
</Button> </Button>
</div> </div>
)} )}

View File

@@ -159,7 +159,7 @@ export function QueueDetailTable({ queueName }: QueueDetailTableProps) {
disabled={retryMutation.isPending} disabled={retryMutation.isPending}
onClick={() => retryMutation.mutate(job.id)} 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>
)} )}
<Button <Button
@@ -170,7 +170,7 @@ export function QueueDetailTable({ queueName }: QueueDetailTableProps) {
disabled={deleteMutation.isPending} disabled={deleteMutation.isPending}
onClick={() => deleteMutation.mutate(job.id)} onClick={() => deleteMutation.mutate(job.id)}
> >
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" aria-hidden />
</Button> </Button>
</div> </div>
</TableCell> </TableCell>

View File

@@ -128,7 +128,7 @@ export function ResidentialStagesAdmin() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <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> </div>
); );
} }
@@ -149,7 +149,7 @@ export function ResidentialStagesAdmin() {
key={`${stage.id}-${idx}`} key={`${stage.id}-${idx}`}
className="flex items-center gap-3 rounded-md border p-3" 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="flex-1 grid grid-cols-1 sm:grid-cols-3 gap-2">
<div className="space-y-1"> <div className="space-y-1">
<Label className="text-[11px] uppercase tracking-wide text-muted-foreground"> <Label className="text-[11px] uppercase tracking-wide text-muted-foreground">
@@ -223,13 +223,13 @@ export function ResidentialStagesAdmin() {
onClick={() => remove(idx)} onClick={() => remove(idx)}
className="text-destructive" className="text-destructive"
> >
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" aria-hidden />
</Button> </Button>
</div> </div>
))} ))}
<Button type="button" variant="outline" size="sm" onClick={add}> <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 Add stage
</Button> </Button>
</CardContent> </CardContent>
@@ -245,9 +245,9 @@ export function ResidentialStagesAdmin() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Button onClick={() => attemptSave(false)} disabled={saving}> <Button onClick={() => attemptSave(false)} disabled={saving}>
{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 Save stages
</Button> </Button>
@@ -318,7 +318,7 @@ export function ResidentialStagesAdmin() {
}} }}
disabled={saving} 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 Reassign + save
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -87,7 +87,7 @@ export function RoleList() {
<span className="font-medium">{formatRole(row.original.name)}</span> <span className="font-medium">{formatRole(row.original.name)}</span>
{row.original.isSystem && ( {row.original.isSystem && (
<Badge variant="outline" className="text-xs"> <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 System
</Badge> </Badge>
)} )}
@@ -126,7 +126,7 @@ export function RoleList() {
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center justify-end gap-1"> <div className="flex items-center justify-end gap-1">
<Button variant="ghost" size="sm" onClick={() => handleEditRole(row.original)}> <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> <span className="sr-only">Edit</span>
</Button> </Button>
{!row.original.isSystem && ( {!row.original.isSystem && (
@@ -137,7 +137,7 @@ export function RoleList() {
size="sm" size="sm"
className="text-destructive hover:text-destructive" 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> <span className="sr-only">Delete</span>
</Button> </Button>
} }
@@ -162,7 +162,7 @@ export function RoleList() {
description="Manage roles and their permissions" description="Manage roles and their permissions"
actions={ actions={
<Button onClick={handleNewRole}> <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 New Role
</Button> </Button>
} }
@@ -209,7 +209,7 @@ export function RoleList() {
onClick={() => handleEditRole(original)} onClick={() => handleEditRole(original)}
aria-label="Edit role" aria-label="Edit role"
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" aria-hidden />
</Button> </Button>
{!original.isSystem ? ( {!original.isSystem ? (
<ConfirmationDialog <ConfirmationDialog
@@ -220,7 +220,7 @@ export function RoleList() {
className="text-destructive hover:text-destructive" className="text-destructive hover:text-destructive"
aria-label="Delete role" aria-label="Delete role"
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" aria-hidden />
</Button> </Button>
} }
title="Delete Role" title="Delete Role"

View File

@@ -174,7 +174,7 @@ export function SalesEmailConfigCard() {
return ( return (
<Card> <Card>
<CardContent className="flex items-center gap-2 py-6 text-sm text-muted-foreground"> <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> </CardContent>
</Card> </Card>
); );
@@ -387,7 +387,7 @@ export function SalesEmailConfigCard() {
<div className="flex justify-end"> <div className="flex justify-end">
<Button onClick={handleSave} disabled={saving}> <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 Save sales email settings
</Button> </Button>
</div> </div>

View File

@@ -408,7 +408,7 @@ export function SettingsManager() {
saveSetting(setting.key, values[setting.key] ?? setting.defaultValue) 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> </Button>
</div> </div>
</div> </div>
@@ -454,7 +454,7 @@ export function SettingsManager() {
saveSetting(setting.key, values[setting.key] ?? setting.defaultValue) 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> </Button>
</div> </div>
</div> </div>
@@ -542,7 +542,7 @@ export function SettingsManager() {
size="sm" size="sm"
className="text-destructive hover:text-destructive" className="text-destructive hover:text-destructive"
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" aria-hidden />
</Button> </Button>
} }
title="Delete Setting" title="Delete Setting"
@@ -569,7 +569,7 @@ export function SettingsManager() {
className="flex-1" className="flex-1"
/> />
<Button variant="outline" onClick={handleAddCustom} disabled={!customKey.trim()}> <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 Add
</Button> </Button>
</div> </div>

View File

@@ -147,7 +147,7 @@ export function SettingsFormCard({ title, description, fields, extra }: Settings
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="flex items-center text-sm text-muted-foreground"> <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 Loading
</div> </div>
</CardContent> </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"> <div className="flex items-center justify-end gap-2 pt-2 border-t">
{extra} {extra}
<Button onClick={handleSave} disabled={saving || !dirty}> <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 Save changes
</Button> </Button>
</div> </div>

View File

@@ -174,7 +174,7 @@ export function StorageAdminPanel() {
if (status.isLoading) { if (status.isLoading) {
return ( return (
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <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> </div>
); );
} }
@@ -216,18 +216,22 @@ export function StorageAdminPanel() {
onClick={() => testMutation.mutate()} onClick={() => testMutation.mutate()}
disabled={testMutation.isPending} 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 Test S3 connection
</Button> </Button>
{testResult && ( {testResult && (
<div className="rounded-md border p-3 text-sm"> <div className="rounded-md border p-3 text-sm">
{testResult.ok ? ( {testResult.ok ? (
<div className="flex items-center gap-2 text-emerald-600"> <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>
) : ( ) : (
<div className="flex items-center gap-2 text-destructive"> <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>
)} )}
</div> </div>
@@ -248,9 +252,9 @@ export function StorageAdminPanel() {
<Card className="lg:col-span-2"> <Card className="lg:col-span-2">
<CardHeader className="flex flex-row items-start gap-3 space-y-0 pb-2"> <CardHeader className="flex flex-row items-start gap-3 space-y-0 pb-2">
{s.backend === 's3' ? ( {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> <div>
<CardTitle className="text-base">Active backend: {s.backend}</CardTitle> <CardTitle className="text-base">Active backend: {s.backend}</CardTitle>
@@ -283,7 +287,9 @@ export function StorageAdminPanel() {
disabled={dryRunMutation.isPending} disabled={dryRunMutation.isPending}
onClick={() => openConfirm('switch-and-migrate')} 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} Switch + migrate existing files to {otherBackend}
</Button> </Button>
<Button variant="outline" onClick={() => openConfirm('switch-only')}> <Button variant="outline" onClick={() => openConfirm('switch-only')}>
@@ -294,7 +300,7 @@ export function StorageAdminPanel() {
onClick={() => status.refetch()} onClick={() => status.refetch()}
disabled={status.isFetching} disabled={status.isFetching}
> >
<RefreshCw className="mr-2 h-4 w-4" /> Refresh <RefreshCw className="mr-2 h-4 w-4" aria-hidden /> Refresh
</Button> </Button>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
@@ -337,7 +343,7 @@ export function StorageAdminPanel() {
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <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' {confirmMode === 'switch-and-migrate'
? `Switch + migrate to ${otherBackend}?` ? `Switch + migrate to ${otherBackend}?`
: `Switch active backend 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'} {confirmMode === 'switch-and-migrate' ? 'Migrate now' : 'Switch now'}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -60,9 +60,9 @@ export function SystemMonitoringDashboard() {
<> <>
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
{health.overall === 'healthy' ? ( {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"> <span className="text-sm text-muted-foreground">
All services checked at {new Date(health.checkedAt).toLocaleTimeString()} All services checked at {new Date(health.checkedAt).toLocaleTimeString()}
@@ -88,7 +88,7 @@ export function SystemMonitoringDashboard() {
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-xs text-muted-foreground font-medium uppercase tracking-wide flex items-center gap-1.5"> <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 Active Connections
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@@ -100,7 +100,7 @@ export function SystemMonitoringDashboard() {
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-xs text-muted-foreground font-medium uppercase tracking-wide flex items-center gap-1.5"> <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 Total Failed Jobs
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@@ -114,7 +114,7 @@ export function SystemMonitoringDashboard() {
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-xs text-muted-foreground font-medium uppercase tracking-wide flex items-center gap-1.5"> <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 Active Jobs
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>

View File

@@ -87,13 +87,13 @@ export function TagList() {
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center justify-end gap-1"> <div className="flex items-center justify-end gap-1">
<Button variant="ghost" size="sm" onClick={() => handleEditTag(row.original)}> <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> <span className="sr-only">Edit</span>
</Button> </Button>
<ConfirmationDialog <ConfirmationDialog
trigger={ trigger={
<Button variant="ghost" size="sm" className="text-destructive hover:text-destructive"> <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> <span className="sr-only">Delete</span>
</Button> </Button>
} }
@@ -117,7 +117,7 @@ export function TagList() {
description="Manage tags used across clients and records" description="Manage tags used across clients and records"
actions={ actions={
<Button onClick={handleNewTag}> <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 New Tag
</Button> </Button>
} }
@@ -152,7 +152,7 @@ export function TagList() {
onClick={() => handleEditTag(original)} onClick={() => handleEditTag(original)}
aria-label={`Edit ${original.name}`} aria-label={`Edit ${original.name}`}
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" aria-hidden />
</Button> </Button>
<ConfirmationDialog <ConfirmationDialog
trigger={ trigger={
@@ -162,7 +162,7 @@ export function TagList() {
className="text-destructive hover:text-destructive" className="text-destructive hover:text-destructive"
aria-label={`Delete ${original.name}`} aria-label={`Delete ${original.name}`}
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" aria-hidden />
</Button> </Button>
} }
title="Delete Tag" title="Delete Tag"

View File

@@ -73,7 +73,7 @@ export function UserCard({
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
aria-label={`Actions for ${user.displayName}`} aria-label={`Actions for ${user.displayName}`}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
@@ -83,7 +83,7 @@ export function UserCard({
onEdit(user); 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 Edit
</DropdownMenuItem> </DropdownMenuItem>
<ConfirmationDialog <ConfirmationDialog
@@ -91,12 +91,12 @@ export function UserCard({
<DropdownMenuItem onSelect={(e) => e.preventDefault()} disabled={isToggling}> <DropdownMenuItem onSelect={(e) => e.preventDefault()} disabled={isToggling}>
{user.isActive ? ( {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 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 Enable sign-in
</> </>
)} )}
@@ -115,7 +115,7 @@ export function UserCard({
<ConfirmationDialog <ConfirmationDialog
trigger={ trigger={
<DropdownMenuItem className="text-destructive" onSelect={(e) => e.preventDefault()}> <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 Remove
</DropdownMenuItem> </DropdownMenuItem>
} }
@@ -155,16 +155,18 @@ export function UserCard({
{/* Role + last login meta */} {/* 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"> <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)} {formatRole(user.role.name)}
</ListCardMeta> </ListCardMeta>
{user.lastLoginAt ? ( {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 })} {formatDistanceToNow(new Date(user.lastLoginAt), { addSuffix: true })}
</ListCardMeta> </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> </div>

View File

@@ -130,7 +130,7 @@ export function UserList() {
onClick={() => handleEditUser(row.original)} onClick={() => handleEditUser(row.original)}
title="Edit user" title="Edit user"
> >
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" aria-hidden />
<span className="sr-only">Edit</span> <span className="sr-only">Edit</span>
</Button> </Button>
</PermissionGate> </PermissionGate>
@@ -149,9 +149,9 @@ export function UserList() {
} }
> >
{row.original.isActive ? ( {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> <span className="sr-only">{row.original.isActive ? 'Disable' : 'Enable'}</span>
</Button> </Button>
@@ -176,7 +176,7 @@ export function UserList() {
title="Remove from port" title="Remove from port"
className="text-destructive hover:text-destructive" 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> <span className="sr-only">Remove</span>
</Button> </Button>
} }
@@ -201,7 +201,7 @@ export function UserList() {
description="Manage users and their roles for this port" description="Manage users and their roles for this port"
actions={ actions={
<Button onClick={handleNewUser}> <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 New User
</Button> </Button>
} }

View File

@@ -166,7 +166,7 @@ export function VocabulariesManager() {
<div> <div>
<PageHeader title="Vocabularies" description="Per-port pick lists used across the CRM." /> <PageHeader title="Vocabularies" description="Per-port pick lists used across the CRM." />
<div className="flex items-center justify-center py-12 text-muted-foreground"> <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>
</div> </div>
); );
@@ -208,7 +208,7 @@ export function VocabulariesManager() {
disabled={saving === def.key} disabled={saving === def.key}
title="Restore the shipped defaults" 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 Reset
</Button> </Button>
<Button <Button
@@ -217,9 +217,9 @@ export function VocabulariesManager() {
disabled={!v.dirty || saving === def.key} disabled={!v.dirty || saving === def.key}
> >
{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 Save
</Button> </Button>
@@ -244,7 +244,7 @@ export function VocabulariesManager() {
onClick={() => moveEntry(def.key, index, -1)} onClick={() => moveEntry(def.key, index, -1)}
disabled={index === 0} 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> </button>
</div> </div>
<Input <Input
@@ -259,7 +259,7 @@ export function VocabulariesManager() {
onClick={() => removeEntry(def.key, index)} onClick={() => removeEntry(def.key, index)}
aria-label={`Remove ${entry}`} aria-label={`Remove ${entry}`}
> >
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" aria-hidden />
</Button> </Button>
</li> </li>
))} ))}
@@ -285,7 +285,7 @@ export function VocabulariesManager() {
onClick={() => addEntry(def.key)} onClick={() => addEntry(def.key)}
disabled={!v.newEntry.trim()} 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> </Button>
</div> </div>
</CardContent> </CardContent>

View File

@@ -50,17 +50,17 @@ export function UmamiTestButton() {
{result && {result &&
(result.ok ? ( (result.ok ? (
<span className="flex items-center text-xs text-green-600"> <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) Connected ({result.visitors ?? 0} active)
</span> </span>
) : ( ) : (
<span className="flex items-center text-xs text-destructive"> <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'} {result.error ?? 'Failed'}
</span> </span>
))} ))}
<Button type="button" size="sm" variant="outline" onClick={runTest} disabled={pending}> <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 Test connection
</Button> </Button>
</div> </div>

View File

@@ -107,7 +107,7 @@ export function BerthCard({ berth }: BerthCardProps) {
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
aria-label={`Actions for berth ${berth.mooringNumber}`} aria-label={`Actions for berth ${berth.mooringNumber}`}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
@@ -117,7 +117,7 @@ export function BerthCard({ berth }: BerthCardProps) {
router.push(`/${portSlug}/berths/${berth.id}`); router.push(`/${portSlug}/berths/${berth.id}`);
}} }}
> >
<Activity className="mr-2 h-3.5 w-3.5" /> <Activity className="mr-2 h-3.5 w-3.5" aria-hidden />
View details View details
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@@ -126,7 +126,7 @@ export function BerthCard({ berth }: BerthCardProps) {
router.push(`/${portSlug}/berths/${berth.id}?edit=true`); router.push(`/${portSlug}/berths/${berth.id}?edit=true`);
}} }}
> >
<Pencil className="mr-2 h-3.5 w-3.5" /> <Pencil className="mr-2 h-3.5 w-3.5" aria-hidden />
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -147,7 +147,7 @@ function ActionsCell({ row }: { row: { original: BerthRow } }) {
className="h-8 w-8" className="h-8 w-8"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
<span className="sr-only">Open menu</span> <span className="sr-only">Open menu</span>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
@@ -158,7 +158,7 @@ function ActionsCell({ row }: { row: { original: BerthRow } }) {
router.push(`/${params.portSlug}/berths/${berth.id}`); router.push(`/${params.portSlug}/berths/${berth.id}`);
}} }}
> >
<Activity className="mr-2 h-4 w-4" /> <Activity className="mr-2 h-4 w-4" aria-hidden />
View details View details
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
@@ -167,7 +167,7 @@ function ActionsCell({ row }: { row: { original: BerthRow } }) {
router.push(`/${params.portSlug}/berths/${berth.id}?edit=true`); router.push(`/${params.portSlug}/berths/${berth.id}?edit=true`);
}} }}
> >
<Pencil className="mr-2 h-4 w-4" /> <Pencil className="mr-2 h-4 w-4" aria-hidden />
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -65,7 +65,7 @@ export function BerthDealDocumentsTab({ berthId }: { berthId: string }) {
className="flex flex-wrap items-center justify-between gap-2 py-2.5 text-sm" className="flex flex-wrap items-center justify-between gap-2 py-2.5 text-sm"
> >
<div className="flex min-w-0 items-center gap-2"> <div className="flex min-w-0 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="truncate font-medium">{doc.title}</span> <span className="truncate font-medium">{doc.title}</span>
<span className="text-xs text-muted-foreground">{doc.documentType}</span> <span className="text-xs text-muted-foreground">{doc.documentType}</span>
</div> </div>
@@ -76,7 +76,7 @@ export function BerthDealDocumentsTab({ berthId }: { berthId: string }) {
href={`/${portSlug}/interests/${doc.interestId}` as any} href={`/${portSlug}/interests/${doc.interestId}` as any}
className="inline-flex items-center gap-1 text-xs text-primary hover:underline" className="inline-flex items-center gap-1 text-xs text-primary hover:underline"
> >
Open <ExternalLink className="h-3 w-3" /> Open <ExternalLink className="h-3 w-3" aria-hidden />
</Link> </Link>
</div> </div>
</li> </li>

View File

@@ -290,11 +290,11 @@ export function BerthDetailHeader({ berth }: BerthDetailHeaderProps) {
<div className="flex flex-wrap items-center gap-2 sm:shrink-0"> <div className="flex flex-wrap items-center gap-2 sm:shrink-0">
<PermissionGate resource="berths" action="edit"> <PermissionGate resource="berths" action="edit">
<Button variant="outline" size="sm" onClick={() => setStatusOpen(true)}> <Button variant="outline" size="sm" onClick={() => setStatusOpen(true)}>
<RefreshCw className="mr-1.5 h-4 w-4" /> <RefreshCw className="mr-1.5 h-4 w-4" aria-hidden />
Change Status Change Status
</Button> </Button>
<Button size="sm" onClick={() => setEditOpen(true)}> <Button size="sm" onClick={() => setEditOpen(true)}>
<Pencil className="mr-1.5 h-4 w-4" /> <Pencil className="mr-1.5 h-4 w-4" aria-hidden />
Edit Edit
</Button> </Button>
</PermissionGate> </PermissionGate>
@@ -369,7 +369,7 @@ function InterestLinkPicker({
) : ( ) : (
'— No interest —' '— No interest —'
)} )}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" aria-hidden />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[320px] p-0" align="start"> <PopoverContent className="w-(--radix-popper-anchor-width) min-w-[320px] p-0" align="start">
@@ -420,7 +420,7 @@ function InterestLinkPicker({
{stageLabel(opt.pipelineStage)} {stageLabel(opt.pipelineStage)}
</span> </span>
{value === opt.id ? ( {value === opt.id ? (
<Check className="h-3.5 w-3.5 text-muted-foreground" /> <Check className="h-3.5 w-3.5 text-muted-foreground" aria-hidden />
) : null} ) : null}
</CommandItem> </CommandItem>
))} ))}

View File

@@ -84,7 +84,7 @@ export function BerthInterestPulse({ berthId }: { berthId: string }) {
<Card> <Card>
<CardHeader className="pb-3"> <CardHeader className="pb-3">
<CardTitle className="flex items-center gap-1.5 text-sm font-medium"> <CardTitle className="flex items-center gap-1.5 text-sm font-medium">
<Users className="size-3.5" /> <Users className="size-3.5" aria-hidden />
Interested parties Interested parties
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@@ -99,7 +99,7 @@ export function BerthInterestPulse({ berthId }: { berthId: string }) {
<Card> <Card>
<CardHeader className="flex flex-row items-center justify-between pb-3 space-y-0"> <CardHeader className="flex flex-row items-center justify-between pb-3 space-y-0">
<CardTitle className="flex items-center gap-1.5 text-sm font-medium"> <CardTitle className="flex items-center gap-1.5 text-sm font-medium">
<Users className="size-3.5" /> <Users className="size-3.5" aria-hidden />
Interested parties Interested parties
<span className="ml-1 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground"> <span className="ml-1 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
{active.length} {active.length}
@@ -161,7 +161,10 @@ export function BerthInterestPulse({ berthId }: { berthId: string }) {
</p> </p>
) : null} ) : null}
</div> </div>
<ChevronRight className="size-4 shrink-0 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5" /> <ChevronRight
className="size-4 shrink-0 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5"
aria-hidden
/>
</Link> </Link>
</li> </li>
); );

View File

@@ -48,7 +48,7 @@ export function BerthReservationsTab({ berthId }: BerthReservationsTabProps) {
<h3 className="text-lg font-semibold">Reservations</h3> <h3 className="text-lg font-semibold">Reservations</h3>
<PermissionGate resource="reservations" action="create"> <PermissionGate resource="reservations" action="create">
<Button size="sm" onClick={() => setReserveOpen(true)}> <Button size="sm" onClick={() => setReserveOpen(true)}>
<Plus className="mr-1.5 h-4 w-4" /> <Plus className="mr-1.5 h-4 w-4" aria-hidden />
Reserve this berth Reserve this berth
</Button> </Button>
</PermissionGate> </PermissionGate>

View File

@@ -60,7 +60,7 @@ export function BerthStatusSuggestionDialog({
<Badge variant="outline" className="text-base px-4 py-1.5"> <Badge variant="outline" className="text-base px-4 py-1.5">
{currentStatus.replace(/_/g, ' ')} {currentStatus.replace(/_/g, ' ')}
</Badge> </Badge>
<ArrowRight className="h-5 w-5 text-muted-foreground" /> <ArrowRight className="h-5 w-5 text-muted-foreground" aria-hidden />
<Badge variant="default" className="text-base px-4 py-1.5"> <Badge variant="default" className="text-base px-4 py-1.5">
{suggestedStatus.replace(/_/g, ' ')} {suggestedStatus.replace(/_/g, ' ')}
</Badge> </Badge>
@@ -73,7 +73,9 @@ export function BerthStatusSuggestionDialog({
Dismiss Dismiss
</Button> </Button>
<Button onClick={() => applyMutation.mutate()} disabled={applyMutation.isPending}> <Button onClick={() => applyMutation.mutate()} disabled={applyMutation.isPending}>
{applyMutation.isPending && <Loader2 className="mr-1.5 h-4 w-4 animate-spin" />} {applyMutation.isPending && (
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" aria-hidden />
)}
Apply Change Apply Change
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -68,7 +68,7 @@ function SortableEntry({
{...listeners} {...listeners}
className="cursor-grab active:cursor-grabbing text-muted-foreground" className="cursor-grab active:cursor-grabbing text-muted-foreground"
> >
<GripVertical className="h-4 w-4" /> <GripVertical className="h-4 w-4" aria-hidden />
</button> </button>
<span className="text-sm font-mono w-6 text-center text-muted-foreground"> <span className="text-sm font-mono w-6 text-center text-muted-foreground">
@@ -88,7 +88,7 @@ function SortableEntry({
onClick={() => onRemove(entry.id)} onClick={() => onRemove(entry.id)}
className="text-muted-foreground hover:text-destructive transition-colors" className="text-muted-foreground hover:text-destructive transition-colors"
> >
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" aria-hidden />
</button> </button>
</div> </div>
); );
@@ -189,7 +189,7 @@ export function WaitingListManager({ berthId }: WaitingListManagerProps) {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm font-medium">Waiting List ({entries.length})</span> <span className="text-sm font-medium">Waiting List ({entries.length})</span>
<Button size="sm" variant="outline" onClick={() => setShowAddForm((v) => !v)}> <Button size="sm" variant="outline" onClick={() => setShowAddForm((v) => !v)}>
<Plus className="mr-1.5 h-4 w-4" /> <Plus className="mr-1.5 h-4 w-4" aria-hidden />
Add Add
</Button> </Button>
</div> </div>
@@ -217,7 +217,9 @@ export function WaitingListManager({ berthId }: WaitingListManagerProps) {
/> />
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" onClick={handleAdd} disabled={addMutation.isPending}> <Button size="sm" onClick={handleAdd} disabled={addMutation.isPending}>
{addMutation.isPending && <Loader2 className="mr-1.5 h-4 w-4 animate-spin" />} {addMutation.isPending && (
<Loader2 className="mr-1.5 h-4 w-4 animate-spin" aria-hidden />
)}
Add to List Add to List
</Button> </Button>
<Button size="sm" variant="ghost" onClick={() => setShowAddForm(false)}> <Button size="sm" variant="ghost" onClick={() => setShowAddForm(false)}>

View File

@@ -126,7 +126,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
{preflight.isLoading ? ( {preflight.isLoading ? (
<div className="py-8 text-center text-sm text-muted-foreground"> <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 Checking each client
</div> </div>
) : preflight.error ? ( ) : preflight.error ? (
@@ -156,7 +156,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
{blocked.length > 0 && ( {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="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"> <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> </div>
{blocked.slice(0, 5).map((b) => ( {blocked.slice(0, 5).map((b) => (
<div key={b.clientId}> <div key={b.clientId}>
@@ -236,7 +236,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
{stage === 'confirm' && ( {stage === 'confirm' && (
<div className="space-y-2 text-sm"> <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"> <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> <div>
Ready to archive <strong>{archivable.length}</strong> client Ready to archive <strong>{archivable.length}</strong> client
{archivable.length === 1 ? '' : 's'} {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> </Button>
)} )}
@@ -279,18 +279,18 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
disabled={carouselIndex === 0} disabled={carouselIndex === 0}
onClick={() => setCarouselIndex((i) => Math.max(0, i - 1))} 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> </Button>
{carouselIndex < highStakes.length - 1 ? ( {carouselIndex < highStakes.length - 1 ? (
<Button <Button
disabled={(reasons[currentHighStakes?.clientId ?? '']?.trim().length ?? 0) < 5} disabled={(reasons[currentHighStakes?.clientId ?? '']?.trim().length ?? 0) < 5}
onClick={() => setCarouselIndex((i) => i + 1)} 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>
) : ( ) : (
<Button disabled={!allHighStakesReasoned} onClick={() => setStage('confirm')}> <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> </Button>
)} )}
</> </>
@@ -304,7 +304,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
> >
{archiveMutation.isPending ? ( {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}` `Archive ${archivable.length}`

View File

@@ -111,7 +111,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
<> <>
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2 text-destructive"> <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'} Permanently delete {clientIds.length} client{clientIds.length === 1 ? '' : 's'}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
@@ -136,7 +136,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
{stage === 'confirm' && ( {stage === 'confirm' && (
<div className="space-y-3"> <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"> <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> <div>
Code sent to <span className="font-mono">{maskedEmail}</span>. Enter both fields Code sent to <span className="font-mono">{maskedEmail}</span>. Enter both fields
below. below.
@@ -214,7 +214,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
> >
{requestCode.isPending ? ( {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' 'Send confirmation code'
@@ -229,7 +229,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
> >
{bulkDelete.isPending ? ( {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}` `Permanently delete ${clientIds.length}`

View File

@@ -61,16 +61,16 @@ export function ClientCard({ client, portSlug, onEdit, onArchive }: ClientCardPr
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
aria-label={`Actions for ${client.fullName}`} aria-label={`Actions for ${client.fullName}`}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onEdit(client)}> <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 Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(client)}> <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 Archive
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -121,7 +121,7 @@ export function getClientColumns({
className="inline-flex items-center gap-1.5 text-sm text-foreground hover:text-primary hover:underline" className="inline-flex items-center gap-1.5 text-sm text-foreground hover:text-primary hover:underline"
title={`Email ${value}`} 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> <span className="truncate">{value}</span>
</a> </a>
); );
@@ -147,7 +147,7 @@ export function getClientColumns({
className="inline-flex items-center gap-1.5 text-foreground hover:text-primary hover:underline" className="inline-flex items-center gap-1.5 text-foreground hover:text-primary hover:underline"
title={`Call ${value}`} 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> <span>{value}</span>
</a> </a>
{waDigits && ( {waDigits && (
@@ -160,7 +160,7 @@ export function getClientColumns({
title={`WhatsApp ${value}`} title={`WhatsApp ${value}`}
aria-label={`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> </a>
)} )}
</span> </span>
@@ -303,16 +303,16 @@ export function getClientColumns({
className="h-7 w-7" className="h-7 w-7"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => onEdit(row.original)}> <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 Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(row.original)}> <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 Archive
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -167,7 +167,7 @@ export function ClientDetailHeader({ client }: ClientDetailHeaderProps) {
title="Permanently delete client" 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" 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> </button>
</PermissionGate> </PermissionGate>
)} )}
@@ -182,7 +182,11 @@ export function ClientDetailHeader({ client }: ClientDetailHeaderProps) {
isArchived ? 'hover:text-emerald-600' : 'hover:text-destructive', 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> </button>
</div> </div>
</div> </div>

View File

@@ -224,7 +224,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
size="sm" size="sm"
onClick={() => append({ channel: 'email', value: '', isPrimary: false })} 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 Add Contact
</Button> </Button>
</div> </div>
@@ -333,7 +333,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
className="h-8 text-destructive hover:text-destructive" className="h-8 text-destructive hover:text-destructive"
onClick={() => remove(index)} 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 Remove
</Button> </Button>
)} )}
@@ -419,7 +419,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
</Button> </Button>
<Button type="submit" disabled={isSubmitting || mutation.isPending}> <Button type="submit" disabled={isSubmitting || mutation.isPending}>
{(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'} {isEdit ? 'Save changes' : 'Create Client'}
</Button> </Button>

View File

@@ -77,7 +77,10 @@ function InterestRowItem({
<p className="mt-0.5 truncate text-xs text-muted-foreground">{yachtLabel}</p> <p className="mt-0.5 truncate text-xs text-muted-foreground">{yachtLabel}</p>
) : null} ) : null}
</div> </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>
<div className="mt-3"> <div className="mt-3">
@@ -364,9 +367,9 @@ function InterestPreviewSheet({
function InterestSkeleton() { function InterestSkeleton() {
return ( return (
<div className="rounded-xl border border-border bg-card p-4 shadow-sm"> <div className="rounded-xl border border-border bg-card p-4 shadow-sm">
<Skeleton className="h-4 w-32" /> <Skeleton className="h-4 w-32" aria-hidden />
<Skeleton className="mt-2 h-3 w-24" /> <Skeleton className="mt-2 h-3 w-24" aria-hidden />
<Skeleton className="mt-3 h-2 w-48" /> <Skeleton className="mt-3 h-2 w-48" aria-hidden />
</div> </div>
); );
} }
@@ -421,7 +424,7 @@ export function ClientInterestsTab({ clientId }: ClientInterestsTabProps) {
<div className="space-y-6"> <div className="space-y-6">
<div className="flex justify-end"> <div className="flex justify-end">
<Button size="sm" onClick={() => setCreateOpen(true)}> <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 Add interest
</Button> </Button>
</div> </div>

View File

@@ -167,7 +167,7 @@ export function ClientList() {
far-right edge, which is where reps look first. */} far-right edge, which is where reps look first. */}
<PermissionGate resource="clients" action="create"> <PermissionGate resource="clients" action="create">
<Button size="sm" className="ml-auto" onClick={() => setCreateOpen(true)}> <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 New Client
</Button> </Button>
</PermissionGate> </PermissionGate>

View File

@@ -124,8 +124,8 @@ function HeroVariant({ clientId, portSlug }: { clientId: string; portSlug: strin
if (isLoading) { if (isLoading) {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<Skeleton className="h-4 w-40" /> <Skeleton className="h-4 w-40" aria-hidden />
<Skeleton className="h-2 w-48" /> <Skeleton className="h-2 w-48" aria-hidden />
</div> </div>
); );
} }
@@ -143,7 +143,7 @@ function HeroVariant({ clientId, portSlug }: { clientId: string; portSlug: strin
href={`/${portSlug}/interests/new` as Route} href={`/${portSlug}/interests/new` as Route}
className="inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline" 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> </Link>
</div> </div>
); );
@@ -182,7 +182,10 @@ function HeroVariant({ clientId, portSlug }: { clientId: string; portSlug: strin
> >
{STAGE_LABELS[stage]} {STAGE_LABELS[stage]}
</span> </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>
<div className="mt-1.5"> <div className="mt-1.5">
<StageStepper current={stage} size="xs" /> <StageStepper current={stage} size="xs" />
@@ -214,8 +217,8 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
if (isLoading) { if (isLoading) {
return ( return (
<div className="space-y-2"> <div className="space-y-2">
<Skeleton className="h-4 w-40" /> <Skeleton className="h-4 w-40" aria-hidden />
<Skeleton className="h-2 w-48" /> <Skeleton className="h-2 w-48" aria-hidden />
</div> </div>
); );
} }
@@ -233,7 +236,7 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
href={`/${portSlug}/interests/new` as Route} href={`/${portSlug}/interests/new` as Route}
className="inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline" 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> </Link>
</div> </div>
); );
@@ -289,7 +292,10 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
<StageStepper current={stage} size="xs" /> <StageStepper current={stage} size="xs" />
</div> </div>
</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> </Link>
</li> </li>
); );

View File

@@ -42,7 +42,7 @@ export function ClientYachtsTab({ clientId, yachts }: ClientYachtsTabProps) {
<h3 className="text-sm font-medium">Client-owned yachts</h3> <h3 className="text-sm font-medium">Client-owned yachts</h3>
<PermissionGate resource="yachts" action="create"> <PermissionGate resource="yachts" action="create">
<Button size="sm" onClick={() => setCreateOpen(true)}> <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 Add yacht
</Button> </Button>
</PermissionGate> </PermissionGate>

View File

@@ -141,7 +141,7 @@ export function ContactsEditor({ clientId, contacts }: { clientId: string; conta
onClick={() => setAdding(true)} onClick={() => setAdding(true)}
className="w-full justify-center" 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 Add contact
</Button> </Button>
)} )}
@@ -201,7 +201,7 @@ function ContactRow({
{/* Top / left: channel + value */} {/* Top / left: channel + value */}
<div className="flex min-w-0 flex-1 items-center gap-2"> <div className="flex min-w-0 flex-1 items-center gap-2">
<ChannelPicker value={contact.channel} onChange={changeChannel}> <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> </ChannelPicker>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
{contact.channel === 'phone' || contact.channel === 'whatsapp' ? ( {contact.channel === 'phone' || contact.channel === 'whatsapp' ? (
@@ -282,7 +282,7 @@ function ContactRow({
title="Remove" 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" 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> </button>
</div> </div>
) : null} ) : null}
@@ -439,7 +439,7 @@ function NewContactForm({
<div className="ml-auto flex gap-2"> <div className="ml-auto flex gap-2">
<Button type="button" size="sm" onClick={submit} disabled={submitDisabled}> <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>
<Button type="button" size="sm" variant="ghost" onClick={onCancel} disabled={saving}> <Button type="button" size="sm" variant="ghost" onClick={onCancel} disabled={saving}>
Cancel Cancel

View File

@@ -111,7 +111,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" size="sm" className="h-8"> <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 GDPR export
</Button> </Button>
</DialogTrigger> </DialogTrigger>
@@ -154,9 +154,9 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
<Button onClick={() => request.mutate()} disabled={request.isPending}> <Button onClick={() => request.mutate()} disabled={request.isPending}>
{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 Queue export
</Button> </Button>
@@ -180,7 +180,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
</div> </div>
{r.sentTo ? ( {r.sentTo ? (
<div className="text-xs text-muted-foreground inline-flex items-center gap-1"> <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} Sent to {r.sentTo}
</div> </div>
) : null} ) : null}
@@ -195,7 +195,7 @@ export function GdprExportButton({ clientId }: { clientId: string }) {
size="sm" size="sm"
onClick={() => downloadById(r.id)} onClick={() => downloadById(r.id)}
> >
<Download className="h-3.5 w-3.5" /> <Download className="h-3.5 w-3.5" aria-hidden />
</Button> </Button>
) : null} ) : null}
</li> </li>

View File

@@ -94,7 +94,7 @@ function HardDeleteDialogBody({ onOpenChange, clientId, clientName, onDeleted }:
<> <>
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2 text-destructive"> <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} Permanently delete {clientName}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
@@ -126,7 +126,7 @@ function HardDeleteDialogBody({ onOpenChange, clientId, clientName, onDeleted }:
) : ( ) : (
<div className="space-y-3"> <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"> <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 className="flex-1">
<div> <div>
Code sent to <span className="font-mono">{maskedEmail}</span>. It expires in 10 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 ? ( {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' 'Send confirmation code'
@@ -199,7 +199,7 @@ function HardDeleteDialogBody({ onOpenChange, clientId, clientName, onDeleted }:
> >
{hardDelete.isPending ? ( {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' 'Permanently delete'

View File

@@ -75,7 +75,7 @@ export function PortalInviteButton({
setOpen(true); 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 Invite to portal
</Button> </Button>
@@ -96,7 +96,7 @@ export function PortalInviteButton({
{success ? ( {success ? (
<div className="py-4 flex items-center gap-3 text-sm text-green-700"> <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>. Activation email sent to <strong>{email}</strong>.
</div> </div>
) : ( ) : (
@@ -138,7 +138,7 @@ export function PortalInviteButton({
<Button onClick={submit} disabled={loading || !email}> <Button onClick={submit} disabled={loading || !email}>
{loading ? ( {loading ? (
<> <>
<Loader2 className="h-4 w-4 mr-2 animate-spin" /> <Loader2 className="h-4 w-4 mr-2 animate-spin" aria-hidden />
Sending Sending
</> </>
) : ( ) : (

View File

@@ -82,11 +82,11 @@ export function SendDocumentsDialog({
<div className="space-y-3"> <div className="space-y-3">
<div> <div>
<h3 className="mb-2 flex items-center gap-2 text-sm font-semibold"> <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> </h3>
{brochuresQuery.isLoading && ( {brochuresQuery.isLoading && (
<div className="flex items-center gap-2 text-sm text-muted-foreground"> <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> </div>
)} )}
{!brochuresQuery.isLoading && usableBrochures.length === 0 && ( {!brochuresQuery.isLoading && usableBrochures.length === 0 && (
@@ -103,7 +103,7 @@ export function SendDocumentsDialog({
onClick={() => setActiveSend({ kind: 'brochure', brochureId: b.id })} onClick={() => setActiveSend({ kind: 'brochure', brochureId: b.id })}
> >
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<FileText className="h-4 w-4" /> <FileText className="h-4 w-4" aria-hidden />
{b.label} {b.label}
{b.isDefault && ( {b.isDefault && (
<span className="rounded bg-primary/10 px-2 py-0.5 text-xs text-primary"> <span className="rounded bg-primary/10 px-2 py-0.5 text-xs text-primary">

View File

@@ -283,7 +283,7 @@ function SmartArchiveDialogBody({
{isLoading ? ( {isLoading ? (
<div className="py-8 text-center text-sm text-muted-foreground"> <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 Loading dossier
</div> </div>
) : error || !dossier ? ( ) : error || !dossier ? (
@@ -296,7 +296,7 @@ function SmartArchiveDialogBody({
<Card className="border-red-300 bg-red-50"> <Card className="border-red-300 bg-red-50">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-red-800 flex items-center gap-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> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="text-sm text-red-700 space-y-1"> <CardContent className="text-sm text-red-700 space-y-1">
@@ -311,7 +311,7 @@ function SmartArchiveDialogBody({
<Card className="border-amber-300 bg-amber-50"> <Card className="border-amber-300 bg-amber-50">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-amber-900 flex items-center gap-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 Late-stage deal confirmation required
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@@ -328,7 +328,8 @@ function SmartArchiveDialogBody({
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-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> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="text-xs space-y-1"> <CardContent className="text-xs space-y-1">
@@ -371,7 +372,7 @@ function SmartArchiveDialogBody({
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-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> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-3"> <CardContent className="space-y-3">
@@ -425,7 +426,7 @@ function SmartArchiveDialogBody({
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-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> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-2"> <CardContent className="space-y-2">
@@ -459,7 +460,7 @@ function SmartArchiveDialogBody({
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-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}) {dossier.reservations.length})
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@@ -493,7 +494,8 @@ function SmartArchiveDialogBody({
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-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> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-2"> <CardContent className="space-y-2">
@@ -527,7 +529,7 @@ function SmartArchiveDialogBody({
<Card> <Card>
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-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> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="space-y-2"> <CardContent className="space-y-2">
@@ -559,7 +561,7 @@ function SmartArchiveDialogBody({
<Card className="border-muted"> <Card className="border-muted">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium flex items-center gap-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> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="text-xs text-muted-foreground space-y-1"> <CardContent className="text-xs text-muted-foreground space-y-1">
@@ -600,7 +602,7 @@ function SmartArchiveDialogBody({
onClick={() => archiveMutation.mutate()} onClick={() => archiveMutation.mutate()}
> >
{archiveMutation.isPending ? ( {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} ) : null}
Archive Archive
</Button> </Button>

View File

@@ -53,10 +53,10 @@ interface Props {
} }
function iconFor(kind: string) { function iconFor(kind: string) {
if (kind.startsWith('berth_')) return <Anchor 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" />; if (kind.startsWith('yacht_')) return <Ship className="h-3 w-3" aria-hidden />;
if (kind.startsWith('documenso_')) return <FileText className="h-3 w-3" />; if (kind.startsWith('documenso_')) return <FileText className="h-3 w-3" aria-hidden />;
return <Wrench className="h-3 w-3" />; return <Wrench className="h-3 w-3" aria-hidden />;
} }
export function SmartRestoreDialog(props: Props) { export function SmartRestoreDialog(props: Props) {
@@ -135,7 +135,7 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
{dossierQuery.isLoading ? ( {dossierQuery.isLoading ? (
<div className="py-8 text-center text-sm text-muted-foreground"> <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 Loading restore dossier
</div> </div>
) : dossierQuery.error || !dossier ? ( ) : dossierQuery.error || !dossier ? (
@@ -153,7 +153,7 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
<Card className="border-emerald-300 bg-emerald-50"> <Card className="border-emerald-300 bg-emerald-50">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-emerald-900 flex items-center gap-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}) {dossier.autoReversible.length})
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@@ -174,7 +174,7 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
<Card className="border-amber-300 bg-amber-50"> <Card className="border-amber-300 bg-amber-50">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-amber-900 flex items-center gap-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}) {dossier.reversibleWithPrompt.length})
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@@ -207,7 +207,8 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
<Card className="border-slate-300 bg-slate-50"> <Card className="border-slate-300 bg-slate-50">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-slate-900 flex items-center gap-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> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="text-xs space-y-1.5 text-slate-700"> <CardContent className="text-xs space-y-1.5 text-slate-700">
@@ -236,7 +237,7 @@ function SmartRestoreDialogBody({ open, onOpenChange, clientId, clientName, onSu
> >
{restoreMutation.isPending ? ( {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' 'Restore client'

View File

@@ -208,7 +208,7 @@ export function AddMembershipDialog({ open, onOpenChange, companyId }: AddMember
</Button> </Button>
<Button type="submit" disabled={isSubmitting || mutation.isPending}> <Button type="submit" disabled={isSubmitting || mutation.isPending}>
{(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 />
)} )}
Add member Add member
</Button> </Button>

View File

@@ -66,22 +66,22 @@ export function CompanyCard({ company, portSlug, onEdit, onArchive }: CompanyCar
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
aria-label={`Actions for ${company.name}`} aria-label={`Actions for ${company.name}`}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link href={`/${portSlug}/companies/${company.id}`}> <Link href={`/${portSlug}/companies/${company.id}`}>
<Eye className="mr-2 h-3.5 w-3.5" /> <Eye className="mr-2 h-3.5 w-3.5" aria-hidden />
View View
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => onEdit(company)}> <DropdownMenuItem onClick={() => onEdit(company)}>
<Pencil className="mr-2 h-3.5 w-3.5" /> <Pencil className="mr-2 h-3.5 w-3.5" aria-hidden />
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(company)}> <DropdownMenuItem className="text-destructive" onClick={() => onArchive(company)}>
<Archive className="mr-2 h-3.5 w-3.5" /> <Archive className="mr-2 h-3.5 w-3.5" aria-hidden />
Archive Archive
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@@ -89,7 +89,7 @@ export function CompanyCard({ company, portSlug, onEdit, onArchive }: CompanyCar
} }
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<ListCardAvatar icon={<Building2 className="h-5 w-5" />} /> <ListCardAvatar icon={<Building2 className="h-5 w-5" aria-hidden />} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
{/* Title row + spacer for actions button */} {/* Title row + spacer for actions button */}
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
@@ -108,10 +108,14 @@ export function CompanyCard({ company, portSlug, onEdit, onArchive }: CompanyCar
{country || company.taxId ? ( {country || company.taxId ? (
<div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground"> <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
{country ? ( {country ? (
<ListCardMeta icon={<MapPin className="h-3 w-3" />}>{country}</ListCardMeta> <ListCardMeta icon={<MapPin className="h-3 w-3" aria-hidden />}>
{country}
</ListCardMeta>
) : null} ) : null}
{company.taxId ? ( {company.taxId ? (
<ListCardMeta icon={<Hash className="h-3 w-3" />}>{company.taxId}</ListCardMeta> <ListCardMeta icon={<Hash className="h-3 w-3" aria-hidden />}>
{company.taxId}
</ListCardMeta>
) : null} ) : null}
</div> </div>
) : null} ) : null}

View File

@@ -157,7 +157,7 @@ export function getCompanyColumns({
className="h-7 w-7" className="h-7 w-7"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
@@ -166,16 +166,16 @@ export function getCompanyColumns({
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
href={`/${portSlug}/companies/${row.original.id}` as any} href={`/${portSlug}/companies/${row.original.id}` as any}
> >
<Eye className="mr-2 h-3.5 w-3.5" /> <Eye className="mr-2 h-3.5 w-3.5" aria-hidden />
View View
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => onEdit(row.original)}> <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 Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(row.original)}> <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 Archive
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -108,7 +108,7 @@ export function CompanyDetailHeader({ company }: CompanyDetailHeaderProps) {
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
<PermissionGate resource="companies" action="edit"> <PermissionGate resource="companies" action="edit">
<Button variant="outline" size="sm" onClick={() => setEditOpen(true)}> <Button variant="outline" size="sm" onClick={() => setEditOpen(true)}>
<Pencil className="mr-1.5 h-3.5 w-3.5" /> <Pencil className="mr-1.5 h-3.5 w-3.5" aria-hidden />
Edit Edit
</Button> </Button>
</PermissionGate> </PermissionGate>
@@ -119,7 +119,7 @@ export function CompanyDetailHeader({ company }: CompanyDetailHeaderProps) {
onClick={() => setArchiveOpen(true)} onClick={() => setArchiveOpen(true)}
disabled={isArchived} disabled={isArchived}
> >
<Archive className="mr-1.5 h-3.5 w-3.5" /> <Archive className="mr-1.5 h-3.5 w-3.5" aria-hidden />
Archive Archive
</Button> </Button>
</PermissionGate> </PermissionGate>

View File

@@ -412,7 +412,7 @@ export function CompanyForm({ open, onOpenChange, company }: CompanyFormProps) {
size="sm" size="sm"
onClick={() => setClientFormOpen(true)} onClick={() => setClientFormOpen(true)}
> >
<Plus className="mr-1 h-3.5 w-3.5" /> New client <Plus className="mr-1 h-3.5 w-3.5" aria-hidden /> New client
</Button> </Button>
<Button <Button
type="button" type="button"
@@ -420,7 +420,7 @@ export function CompanyForm({ open, onOpenChange, company }: CompanyFormProps) {
size="sm" size="sm"
onClick={() => setYachtFormOpen(true)} onClick={() => setYachtFormOpen(true)}
> >
<Plus className="mr-1 h-3.5 w-3.5" /> New yacht <Plus className="mr-1 h-3.5 w-3.5" aria-hidden /> New yacht
</Button> </Button>
</div> </div>
</div> </div>
@@ -459,7 +459,7 @@ export function CompanyForm({ open, onOpenChange, company }: CompanyFormProps) {
</Button> </Button>
<Button type="submit" disabled={isSubmitting || mutation.isPending}> <Button type="submit" disabled={isSubmitting || mutation.isPending}>
{(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 Company'} {isEdit ? 'Save changes' : 'Create Company'}
</Button> </Button>
@@ -669,7 +669,7 @@ function EntityMultiPicker({
)} )}
> >
{placeholder} {placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" aria-hidden />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-(--radix-popper-anchor-width) min-w-[280px] p-0" align="start"> <PopoverContent className="w-(--radix-popper-anchor-width) min-w-[280px] p-0" align="start">

View File

@@ -126,7 +126,7 @@ export function CompanyList() {
actions={ actions={
<PermissionGate resource="companies" action="create"> <PermissionGate resource="companies" action="create">
<Button size="sm" onClick={() => setCreateOpen(true)}> <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 />
New Company New Company
</Button> </Button>
</PermissionGate> </PermissionGate>

View File

@@ -159,7 +159,7 @@ export function CompanyMembersTab({ companyId, portSlug }: CompanyMembersTabProp
<PermissionGate resource="memberships" action="manage"> <PermissionGate resource="memberships" action="manage">
<Button size="sm" onClick={() => setAddOpen(true)}> <Button size="sm" onClick={() => setAddOpen(true)}>
<Plus className="mr-1.5 h-4 w-4" /> <Plus className="mr-1.5 h-4 w-4" aria-hidden />
Add Member Add Member
</Button> </Button>
</PermissionGate> </PermissionGate>
@@ -167,7 +167,7 @@ export function CompanyMembersTab({ companyId, portSlug }: CompanyMembersTabProp
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /> <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" aria-hidden />
</div> </div>
) : members.length === 0 ? ( ) : members.length === 0 ? (
<EmptyState <EmptyState
@@ -226,7 +226,7 @@ export function CompanyMembersTab({ companyId, portSlug }: CompanyMembersTabProp
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-7 w-7"> <Button variant="ghost" size="icon" className="h-7 w-7">
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
@@ -235,7 +235,7 @@ export function CompanyMembersTab({ companyId, portSlug }: CompanyMembersTabProp
onClick={() => setPrimaryMutation.mutate(m.id)} onClick={() => setPrimaryMutation.mutate(m.id)}
disabled={setPrimaryMutation.isPending} disabled={setPrimaryMutation.isPending}
> >
<Star className="mr-2 h-3.5 w-3.5" /> <Star className="mr-2 h-3.5 w-3.5" aria-hidden />
Set Primary Set Primary
</DropdownMenuItem> </DropdownMenuItem>
)} )}
@@ -245,7 +245,7 @@ export function CompanyMembersTab({ companyId, portSlug }: CompanyMembersTabProp
onClick={() => endMutation.mutate(m.id)} onClick={() => endMutation.mutate(m.id)}
disabled={endMutation.isPending} disabled={endMutation.isPending}
> >
<XCircle className="mr-2 h-3.5 w-3.5" /> <XCircle className="mr-2 h-3.5 w-3.5" aria-hidden />
Remove from company Remove from company
</DropdownMenuItem> </DropdownMenuItem>
)} )}

View File

@@ -81,7 +81,7 @@ export function CompanyOwnedYachtsTab({ companyId, portSlug }: CompanyOwnedYacht
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex items-center justify-center py-12"> <div className="flex items-center justify-center py-12">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /> <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" aria-hidden />
</div> </div>
); );
} }

View File

@@ -65,7 +65,7 @@ export function CompanyPicker({
className={cn('w-full justify-between', !value && 'text-muted-foreground')} className={cn('w-full justify-between', !value && 'text-muted-foreground')}
> >
<span className="truncate">{selectedLabel}</span> <span className="truncate">{selectedLabel}</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" aria-hidden />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-[320px] p-0" align="start"> <PopoverContent className="w-[320px] p-0" align="start">

View File

@@ -46,7 +46,7 @@ export function ActiveDealsTile() {
Active deals Active deals
</p> </p>
{isLoading ? ( {isLoading ? (
<Skeleton className="mt-1 h-7 w-12" /> <Skeleton className="mt-1 h-7 w-12" aria-hidden />
) : ( ) : (
<p className="text-2xl font-bold leading-tight text-foreground"> <p className="text-2xl font-bold leading-tight text-foreground">
{data?.activeInterests ?? 0} {data?.activeInterests ?? 0}

View File

@@ -56,7 +56,7 @@ export function BerthStatusChart() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{isLoading ? ( {isLoading ? (
<Skeleton className="h-[240px] w-full" /> <Skeleton className="h-[240px] w-full" aria-hidden />
) : chartData.length === 0 ? ( ) : chartData.length === 0 ? (
<p className="py-8 text-center text-sm text-muted-foreground">No berths yet.</p> <p className="py-8 text-center text-sm text-muted-foreground">No berths yet.</p>
) : ( ) : (

View File

@@ -114,18 +114,18 @@ export function ChartCard({
aria-label="Chart options" aria-label="Chart options"
data-testid="chart-menu" data-testid="chart-menu"
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-44"> <DropdownMenuContent align="end" className="w-44">
{toCsv ? ( {toCsv ? (
<DropdownMenuItem onSelect={onDownloadCsv}> <DropdownMenuItem onSelect={onDownloadCsv}>
<Download className="mr-2 h-4 w-4" /> <Download className="mr-2 h-4 w-4" aria-hidden />
Download CSV Download CSV
</DropdownMenuItem> </DropdownMenuItem>
) : null} ) : null}
<DropdownMenuItem onSelect={onDownloadPng}> <DropdownMenuItem onSelect={onDownloadPng}>
<ImageIcon className="mr-2 h-4 w-4" /> <ImageIcon className="mr-2 h-4 w-4" aria-hidden />
Download PNG Download PNG
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -41,7 +41,7 @@ export function CustomizeWidgetsMenu() {
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button variant="outline" size="sm" className="gap-1.5"> <Button variant="outline" size="sm" className="gap-1.5">
<LayoutGrid className="h-4 w-4" /> <LayoutGrid className="h-4 w-4" aria-hidden />
Customize Customize
</Button> </Button>
</DialogTrigger> </DialogTrigger>

View File

@@ -69,9 +69,9 @@ export function HotDealsCard() {
<CardContent> <CardContent>
{isLoading ? ( {isLoading ? (
<div className="space-y-3"> <div className="space-y-3">
<Skeleton className="h-12 w-full" /> <Skeleton className="h-12 w-full" aria-hidden />
<Skeleton className="h-12 w-full" /> <Skeleton className="h-12 w-full" aria-hidden />
<Skeleton className="h-12 w-full" /> <Skeleton className="h-12 w-full" aria-hidden />
</div> </div>
) : deals.length === 0 ? ( ) : deals.length === 0 ? (
<p className="py-4 text-center text-sm text-muted-foreground"> <p className="py-4 text-center text-sm text-muted-foreground">

View File

@@ -26,8 +26,8 @@ function KpiTileSkeleton() {
return ( return (
<div className="relative overflow-hidden rounded-xl border border-border bg-card p-3 shadow-sm sm:p-5"> <div className="relative overflow-hidden rounded-xl border border-border bg-card p-3 shadow-sm sm:p-5">
<div className="absolute inset-x-0 top-0 h-1 bg-muted" aria-hidden /> <div className="absolute inset-x-0 top-0 h-1 bg-muted" aria-hidden />
<Skeleton className="h-3 w-20" /> <Skeleton className="h-3 w-20" aria-hidden />
<Skeleton className="mt-2 h-6 w-24 sm:mt-3 sm:h-7" /> <Skeleton className="mt-2 h-6 w-24 sm:mt-3 sm:h-7" aria-hidden />
</div> </div>
); );
} }

View File

@@ -82,7 +82,7 @@ export function MyRemindersRail() {
<CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0 pb-3"> <CardHeader className="flex flex-row items-start justify-between gap-2 space-y-0 pb-3">
<div className="space-y-0.5"> <div className="space-y-0.5">
<CardTitle className="flex items-center gap-1.5 text-base"> <CardTitle className="flex items-center gap-1.5 text-base">
<AlarmClock className="size-4" /> <AlarmClock className="size-4" aria-hidden />
Reminders Reminders
</CardTitle> </CardTitle>
{overdueCount > 0 ? ( {overdueCount > 0 ? (
@@ -150,7 +150,10 @@ export function MyRemindersRail() {
? 'in ' + formatDistanceToNowStrict(due) ? 'in ' + formatDistanceToNowStrict(due)
: 'now'} : 'now'}
</span> </span>
<ChevronRight className="size-3.5 shrink-0 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5" /> <ChevronRight
className="size-3.5 shrink-0 text-muted-foreground/60 transition-transform group-hover:translate-x-0.5"
aria-hidden
/>
</Link> </Link>
</li> </li>
); );

View File

@@ -38,7 +38,7 @@ export function PipelineValueTile() {
Pipeline value Pipeline value
</p> </p>
{isLoading ? ( {isLoading ? (
<Skeleton className="mt-1 h-7 w-24" /> <Skeleton className="mt-1 h-7 w-24" aria-hidden />
) : ( ) : (
<p <p
className="truncate text-2xl font-bold leading-tight text-foreground" className="truncate text-2xl font-bold leading-tight text-foreground"

View File

@@ -46,9 +46,9 @@ export function SourceConversionChart() {
<CardContent> <CardContent>
{isLoading ? ( {isLoading ? (
<div className="space-y-3"> <div className="space-y-3">
<Skeleton className="h-8 w-full" /> <Skeleton className="h-8 w-full" aria-hidden />
<Skeleton className="h-8 w-full" /> <Skeleton className="h-8 w-full" aria-hidden />
<Skeleton className="h-8 w-full" /> <Skeleton className="h-8 w-full" aria-hidden />
</div> </div>
) : rows.length === 0 ? ( ) : rows.length === 0 ? (
<p className="py-4 text-center text-sm text-muted-foreground"> <p className="py-4 text-center text-sm text-muted-foreground">

View File

@@ -103,7 +103,7 @@ export function TimezoneDriftBanner() {
return ( return (
<div className="flex flex-wrap items-center justify-between gap-3 rounded-md border border-amber-300 bg-amber-50 px-4 py-2.5 text-sm text-amber-900 sm:flex-nowrap"> <div className="flex flex-wrap items-center justify-between gap-3 rounded-md border border-amber-300 bg-amber-50 px-4 py-2.5 text-sm text-amber-900 sm:flex-nowrap">
<div className="flex items-start gap-2 min-w-0"> <div className="flex items-start gap-2 min-w-0">
<Clock className="mt-0.5 h-4 w-4 shrink-0" /> <Clock className="mt-0.5 h-4 w-4 shrink-0" aria-hidden />
<div className="min-w-0"> <div className="min-w-0">
<p className="font-medium leading-tight"> <p className="font-medium leading-tight">
You appear to be in{' '} You appear to be in{' '}

View File

@@ -52,7 +52,7 @@ export function WebsiteGlanceTile() {
Website today Website today
</div> </div>
{loading ? ( {loading ? (
<Skeleton className="mt-2 h-7 w-20" /> <Skeleton className="mt-2 h-7 w-20" aria-hidden />
) : ( ) : (
<div className="mt-1 flex items-baseline gap-2 text-lg font-semibold tabular-nums sm:mt-2 sm:text-2xl"> <div className="mt-1 flex items-baseline gap-2 text-lg font-semibold tabular-nums sm:mt-2 sm:text-2xl">
{today.toLocaleString()} {today.toLocaleString()}

View File

@@ -56,7 +56,7 @@ export function AggregatedSection<K extends AggregatedItemKind['kind']>({
<h3 className="flex items-center gap-2 text-sm font-semibold text-foreground"> <h3 className="flex items-center gap-2 text-sm font-semibold text-foreground">
{icon} {icon}
{title} {title}
<Loader2 className="ml-1 h-3.5 w-3.5 animate-spin text-muted-foreground" /> <Loader2 className="ml-1 h-3.5 w-3.5 animate-spin text-muted-foreground" aria-hidden />
</h3> </h3>
</section> </section>
); );
@@ -108,7 +108,7 @@ function GroupBlock<K extends AggregatedItemKind['kind']>({
// The server always sets exactly one of `files` / `workflows` per group; // The server always sets exactly one of `files` / `workflows` per group;
// unify them into a single list for rendering. The discriminated-union // unify them into a single list for rendering. The discriminated-union
// generic on `AggregatedSection` keeps the row type correct upstream. // generic on `AggregatedSection` keeps the row type correct upstream.
const items = ((group.files ?? group.workflows ?? []) as unknown) as ItemOfKind<K>[]; const items = (group.files ?? group.workflows ?? []) as unknown as ItemOfKind<K>[];
return ( return (
<div className="px-3 py-2"> <div className="px-3 py-2">
<header className="mb-1 text-[0.7rem] font-medium uppercase tracking-wide text-muted-foreground"> <header className="mb-1 text-[0.7rem] font-medium uppercase tracking-wide text-muted-foreground">

View File

@@ -192,7 +192,7 @@ export function CreateDocumentWizard({ portSlug }: CreateDocumentWizardProps) {
actions={ actions={
<Button asChild variant="outline"> <Button asChild variant="outline">
<Link href={`/${portSlug}/documents`}> <Link href={`/${portSlug}/documents`}>
<ArrowLeft className="mr-1.5 h-4 w-4" /> Back <ArrowLeft className="mr-1.5 h-4 w-4" aria-hidden /> Back
</Link> </Link>
</Button> </Button>
} }
@@ -360,7 +360,7 @@ export function CreateDocumentWizard({ portSlug }: CreateDocumentWizardProps) {
Signers Signers
</h2> </h2>
<Button size="sm" variant="outline" onClick={addSigner}> <Button size="sm" variant="outline" onClick={addSigner}>
<Plus className="mr-1.5 h-3.5 w-3.5" /> Add signer <Plus className="mr-1.5 h-3.5 w-3.5" aria-hidden /> Add signer
</Button> </Button>
</div> </div>
<ul className="space-y-2"> <ul className="space-y-2">
@@ -405,7 +405,7 @@ export function CreateDocumentWizard({ portSlug }: CreateDocumentWizardProps) {
onClick={() => removeSigner(idx)} onClick={() => removeSigner(idx)}
className="text-muted-foreground hover:text-destructive" className="text-muted-foreground hover:text-destructive"
> >
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" aria-hidden />
</button> </button>
</li> </li>
))} ))}

View File

@@ -133,7 +133,7 @@ export function DocumentDetail({ documentId, portSlug }: DocumentDetailProps) {
actions={ actions={
<Button asChild variant="outline"> <Button asChild variant="outline">
<Link href={`/${portSlug}/documents`}> <Link href={`/${portSlug}/documents`}>
<ArrowLeft className="mr-1.5 h-4 w-4" /> <ArrowLeft className="mr-1.5 h-4 w-4" aria-hidden />
Back to documents Back to documents
</Link> </Link>
</Button> </Button>
@@ -225,18 +225,18 @@ export function DocumentDetail({ documentId, portSlug }: DocumentDetailProps) {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button asChild variant="outline" size="sm"> <Button asChild variant="outline" size="sm">
<Link href={`/${portSlug}/documents`}> <Link href={`/${portSlug}/documents`}>
<ArrowLeft className="mr-1.5 h-4 w-4" /> Back <ArrowLeft className="mr-1.5 h-4 w-4" aria-hidden /> Back
</Link> </Link>
</Button> </Button>
{isComplete && doc.signedFileId ? ( {isComplete && doc.signedFileId ? (
<> <>
<Button asChild size="sm"> <Button asChild size="sm">
<Link href={`/api/v1/files/${doc.signedFileId}/download`}> <Link href={`/api/v1/files/${doc.signedFileId}/download`}>
<Download className="mr-1.5 h-4 w-4" /> Download signed PDF <Download className="mr-1.5 h-4 w-4" aria-hidden /> Download signed PDF
</Link> </Link>
</Button> </Button>
<Button size="sm" variant="outline" onClick={handleEmailSignedPdf}> <Button size="sm" variant="outline" onClick={handleEmailSignedPdf}>
<Mail className="mr-1.5 h-4 w-4" /> Email signatories <Mail className="mr-1.5 h-4 w-4" aria-hidden /> Email signatories
</Button> </Button>
</> </>
) : null} ) : null}
@@ -299,7 +299,7 @@ export function DocumentDetail({ documentId, portSlug }: DocumentDetailProps) {
variant="outline" variant="outline"
onClick={() => handleRemind(signer.id)} onClick={() => handleRemind(signer.id)}
> >
<Bell className="mr-1.5 h-3 w-3" /> Remind <Bell className="mr-1.5 h-3 w-3" aria-hidden /> Remind
</Button> </Button>
{signer.signingUrl ? ( {signer.signingUrl ? (
<button <button
@@ -370,7 +370,7 @@ export function DocumentDetail({ documentId, portSlug }: DocumentDetailProps) {
}} }}
className="text-muted-foreground hover:text-destructive" className="text-muted-foreground hover:text-destructive"
> >
<Trash2 className="h-3.5 w-3.5" /> <Trash2 className="h-3.5 w-3.5" aria-hidden />
</button> </button>
</li> </li>
))} ))}

View File

@@ -95,7 +95,7 @@ function DocRow({ doc, onDelete, onSend }: DocRowProps) {
)} )}
<PermissionGate resource="documents" action="manage_folders"> <PermissionGate resource="documents" action="manage_folders">
<DropdownMenuItem onSelect={() => setMoveOpen(true)}> <DropdownMenuItem onSelect={() => setMoveOpen(true)}>
<FolderInput className="mr-2 h-4 w-4" /> <FolderInput className="mr-2 h-4 w-4" aria-hidden />
Move to folder Move to folder
</DropdownMenuItem> </DropdownMenuItem>
</PermissionGate> </PermissionGate>

View File

@@ -79,7 +79,7 @@ export function DocumentTemplatePicker({
className={cn('w-full justify-between', !value && 'text-muted-foreground')} className={cn('w-full justify-between', !value && 'text-muted-foreground')}
> >
<span className="truncate">{selectedLabel}</span> <span className="truncate">{selectedLabel}</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" aria-hidden />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-[320px] p-0" align="start"> <PopoverContent className="w-[320px] p-0" align="start">

View File

@@ -311,7 +311,11 @@ function FlatFolderListing({ portSlug, folderId }: FlatFolderListingProps) {
onClick={() => setExpandedDocId(expanded ? null : doc.id)} onClick={() => setExpandedDocId(expanded ? null : doc.id)}
className="flex min-h-[44px] min-w-[44px] items-center justify-center text-muted-foreground transition-transform" className="flex min-h-[44px] min-w-[44px] items-center justify-center text-muted-foreground transition-transform"
> >
{expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />} {expanded ? (
<ChevronDown className="h-4 w-4" aria-hidden />
) : (
<ChevronRight className="h-4 w-4" aria-hidden />
)}
</button> </button>
<Link <Link
href={`/${portSlug}/documents/${doc.id}`} href={`/${portSlug}/documents/${doc.id}`}
@@ -409,18 +413,18 @@ function FlatFolderListing({ portSlug, folderId }: FlatFolderListingProps) {
</ul> </ul>
) : documents.length === 0 ? ( ) : documents.length === 0 ? (
<EmptyState <EmptyState
icon={<FileText className="h-7 w-7" />} icon={<FileText className="h-7 w-7" aria-hidden />}
title="No documents in this folder" title="No documents in this folder"
body="Upload a file, generate a signing flow, or move existing documents here." body="Upload a file, generate a signing flow, or move existing documents here."
actions={ actions={
<> <>
<Button onClick={() => setUploadOpen(true)}> <Button onClick={() => setUploadOpen(true)}>
<Upload className="mr-1.5 h-4 w-4" /> <Upload className="mr-1.5 h-4 w-4" aria-hidden />
Upload file Upload file
</Button> </Button>
<Button asChild variant="outline"> <Button asChild variant="outline">
<Link href={`/${portSlug}/documents/new`}> <Link href={`/${portSlug}/documents/new`}>
<Plus className="mr-1.5 h-4 w-4" /> <Plus className="mr-1.5 h-4 w-4" aria-hidden />
Generate for signing Generate for signing
</Link> </Link>
</Button> </Button>
@@ -560,7 +564,7 @@ function FolderDropZone({ folderId, entityType, entityId, children }: FolderDrop
{(dragActive || uploading) && ( {(dragActive || uploading) && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center rounded-md border-2 border-dashed border-primary bg-primary/5 backdrop-blur-[1px] z-10"> <div className="pointer-events-none absolute inset-0 flex items-center justify-center rounded-md border-2 border-dashed border-primary bg-primary/5 backdrop-blur-[1px] z-10">
<div className="flex flex-col items-center gap-2 text-sm font-medium text-primary"> <div className="flex flex-col items-center gap-2 text-sm font-medium text-primary">
<Upload className="h-8 w-8" /> <Upload className="h-8 w-8" aria-hidden />
{uploading ? 'Uploading…' : 'Drop to upload to this folder'} {uploading ? 'Uploading…' : 'Drop to upload to this folder'}
</div> </div>
</div> </div>

View File

@@ -50,7 +50,7 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
<div className="space-y-4"> <div className="space-y-4">
<AggregatedSection<'workflows'> <AggregatedSection<'workflows'>
title="Signing in progress" title="Signing in progress"
icon={<ClipboardSignature className="h-4 w-4 text-muted-foreground" />} icon={<ClipboardSignature className="h-4 w-4 text-muted-foreground" aria-hidden />}
groups={workflowGroups} groups={workflowGroups}
loading={workflowsLoading} loading={workflowsLoading}
emptyMessage="No workflows in flight for this entity." emptyMessage="No workflows in flight for this entity."
@@ -68,7 +68,7 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
<AggregatedSection<'files'> <AggregatedSection<'files'>
title="Files" title="Files"
icon={<FileText className="h-4 w-4 text-muted-foreground" />} icon={<FileText className="h-4 w-4 text-muted-foreground" aria-hidden />}
groups={fileGroups} groups={fileGroups}
loading={filesLoading} loading={filesLoading}
emptyMessage="No files for this entity yet." emptyMessage="No files for this entity yet."
@@ -86,7 +86,7 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
className="min-h-[44px] gap-1 px-2 text-xs text-brand" className="min-h-[44px] gap-1 px-2 text-xs text-brand"
onClick={() => setDetailsId(signedFromDocumentId)} onClick={() => setDetailsId(signedFromDocumentId)}
> >
<Eye className="h-3 w-3" /> <Eye className="h-3 w-3" aria-hidden />
View signing details View signing details
</Button> </Button>
) : null} ) : null}

View File

@@ -259,7 +259,7 @@ export function EoiGenerateDialog({
<DialogContent className="sm:max-w-lg"> <DialogContent className="sm:max-w-lg">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
<FileSignature className="size-4" /> <FileSignature className="size-4" aria-hidden />
Generate Expression of Interest Generate Expression of Interest
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
@@ -290,9 +290,9 @@ export function EoiGenerateDialog({
{ctxLoading ? ( {ctxLoading ? (
<div className="space-y-2"> <div className="space-y-2">
<Skeleton className="h-4 w-full" /> <Skeleton className="h-4 w-full" aria-hidden />
<Skeleton className="h-4 w-3/4" /> <Skeleton className="h-4 w-3/4" aria-hidden />
<Skeleton className="h-4 w-2/3" /> <Skeleton className="h-4 w-2/3" aria-hidden />
</div> </div>
) : ctx ? ( ) : ctx ? (
<div className="space-y-3 rounded-md border bg-muted/20 p-3"> <div className="space-y-3 rounded-md border bg-muted/20 p-3">
@@ -339,9 +339,9 @@ export function EoiGenerateDialog({
className="inline-flex items-center gap-1 text-xs text-primary hover:underline" className="inline-flex items-center gap-1 text-xs text-primary hover:underline"
onClick={() => onOpenChange(false)} onClick={() => onOpenChange(false)}
> >
<Pencil className="size-3" /> <Pencil className="size-3" aria-hidden />
Edit client details Edit client details
<ExternalLink className="size-3" /> <ExternalLink className="size-3" aria-hidden />
</Link> </Link>
<Link <Link
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -349,9 +349,9 @@ export function EoiGenerateDialog({
className="inline-flex items-center gap-1 text-xs text-primary hover:underline" className="inline-flex items-center gap-1 text-xs text-primary hover:underline"
onClick={() => onOpenChange(false)} onClick={() => onOpenChange(false)}
> >
<Pencil className="size-3" /> <Pencil className="size-3" aria-hidden />
Manage linked berths Manage linked berths
<ExternalLink className="size-3" /> <ExternalLink className="size-3" aria-hidden />
</Link> </Link>
</div> </div>
</div> </div>
@@ -365,7 +365,7 @@ export function EoiGenerateDialog({
{!ctxLoading && ctx && !requiredMet && ( {!ctxLoading && ctx && !requiredMet && (
<p className="flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900"> <p className="flex items-start gap-2 rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900">
<AlertTriangle className="size-3.5 shrink-0 mt-0.5" /> <AlertTriangle className="size-3.5 shrink-0 mt-0.5" aria-hidden />
Add the missing required details on the client&apos;s record before generating the Add the missing required details on the client&apos;s record before generating the
EOI. EOI.
</p> </p>
@@ -481,7 +481,7 @@ function PreviewRow({
className="ml-1 inline-flex items-center rounded p-0.5 text-muted-foreground hover:bg-muted/60 hover:text-foreground" className="ml-1 inline-flex items-center rounded p-0.5 text-muted-foreground hover:bg-muted/60 hover:text-foreground"
aria-label={`Edit ${label}`} aria-label={`Edit ${label}`}
> >
<Pencil className="h-3 w-3" /> <Pencil className="h-3 w-3" aria-hidden />
</button> </button>
) : null} ) : null}
</> </>

View File

@@ -87,14 +87,14 @@ export function FolderActionsMenu({ selectedFolderId, onAfterDelete }: FolderAct
setCreateOpen(true); setCreateOpen(true);
}} }}
> >
<FolderPlus className="mr-2 h-4 w-4" /> <FolderPlus className="mr-2 h-4 w-4" aria-hidden />
New folder {isFolderSelected ? 'inside this' : 'at root'} New folder {isFolderSelected ? 'inside this' : 'at root'}
</Button> </Button>
{isFolderSelected ? ( {isFolderSelected ? (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon"> <Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
<span className="sr-only">More folder actions</span> <span className="sr-only">More folder actions</span>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
@@ -111,7 +111,7 @@ export function FolderActionsMenu({ selectedFolderId, onAfterDelete }: FolderAct
setRenameOpen(true); setRenameOpen(true);
}} }}
> >
<Pencil className="mr-2 h-4 w-4" /> <Pencil className="mr-2 h-4 w-4" aria-hidden />
Rename Rename
</DropdownMenuItem> </DropdownMenuItem>
</span> </span>
@@ -130,7 +130,7 @@ export function FolderActionsMenu({ selectedFolderId, onAfterDelete }: FolderAct
onSelect={(e) => e.preventDefault()} onSelect={(e) => e.preventDefault()}
className="text-destructive" className="text-destructive"
> >
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" aria-hidden />
Delete Delete
</DropdownMenuItem> </DropdownMenuItem>
} }

View File

@@ -42,7 +42,7 @@ export function FolderBreadcrumb({ selectedFolderId, onSelect }: FolderBreadcrum
onClick={() => onSelect(undefined)} onClick={() => onSelect(undefined)}
className="flex min-h-[44px] items-center gap-1 py-2 hover:text-foreground" className="flex min-h-[44px] items-center gap-1 py-2 hover:text-foreground"
> >
<Home className="h-3.5 w-3.5" /> <Home className="h-3.5 w-3.5" aria-hidden />
<span>All</span> <span>All</span>
</button> </button>
{path.length === 0 && selectedFolderId === null ? ( {path.length === 0 && selectedFolderId === null ? (

View File

@@ -37,7 +37,7 @@ export function FolderTreeSidebar({ selectedFolderId, onSelect, footer }: Folder
<Sheet open={mobileOpen} onOpenChange={setMobileOpen}> <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
<SheetTrigger asChild> <SheetTrigger asChild>
<Button variant="outline" size="sm" className="min-h-[44px]"> <Button variant="outline" size="sm" className="min-h-[44px]">
<FolderTree className="mr-2 h-4 w-4" /> <FolderTree className="mr-2 h-4 w-4" aria-hidden />
Show folders Show folders
</Button> </Button>
</SheetTrigger> </SheetTrigger>
@@ -130,7 +130,7 @@ function PseudoRow({
)} )}
onClick={onClick} onClick={onClick}
> >
<Icon className="mr-2 h-4 w-4" /> <Icon className="mr-2 h-4 w-4" aria-hidden />
{label} {label}
</Button> </Button>
); );
@@ -183,9 +183,9 @@ function FolderRow({
)} )}
> >
{open && hasChildren ? ( {open && hasChildren ? (
<FolderOpen className="h-4 w-4 shrink-0" /> <FolderOpen className="h-4 w-4 shrink-0" aria-hidden />
) : ( ) : (
<Folder className="h-4 w-4 shrink-0" /> <Folder className="h-4 w-4 shrink-0" aria-hidden />
)} )}
<span className="truncate"> <span className="truncate">
{node.name} {node.name}

View File

@@ -51,7 +51,7 @@ export function HubRootView({ portSlug }: Props) {
<div className="space-y-4"> <div className="space-y-4">
<section className="rounded-md border bg-white"> <section className="rounded-md border bg-white">
<h3 className="flex items-center gap-2 border-b px-3 py-2 text-sm font-semibold"> <h3 className="flex items-center gap-2 border-b px-3 py-2 text-sm font-semibold">
<ClipboardSignature className="h-4 w-4 text-muted-foreground" /> <ClipboardSignature className="h-4 w-4 text-muted-foreground" aria-hidden />
Signing in progress Signing in progress
</h3> </h3>
{workflowsLoading ? ( {workflowsLoading ? (
@@ -76,7 +76,7 @@ export function HubRootView({ portSlug }: Props) {
<section className="rounded-md border bg-white"> <section className="rounded-md border bg-white">
<h3 className="flex items-center gap-2 border-b px-3 py-2 text-sm font-semibold"> <h3 className="flex items-center gap-2 border-b px-3 py-2 text-sm font-semibold">
<FileText className="h-4 w-4 text-muted-foreground" /> <FileText className="h-4 w-4 text-muted-foreground" aria-hidden />
Recent files Recent files
</h3> </h3>
{filesLoading ? ( {filesLoading ? (

View File

@@ -119,7 +119,7 @@ function DialogBody({
} }
}} }}
> >
<FolderInput className="mr-1.5 h-4 w-4" /> <FolderInput className="mr-1.5 h-4 w-4" aria-hidden />
Move Move
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -62,14 +62,14 @@ export function NewDocumentMenu({
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button size={size}> <Button size={size}>
<Plus className="mr-1.5 h-4 w-4" /> <Plus className="mr-1.5 h-4 w-4" aria-hidden />
New document New document
<ChevronDown className="ml-1.5 h-4 w-4 opacity-80" /> <ChevronDown className="ml-1.5 h-4 w-4 opacity-80" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-64"> <DropdownMenuContent align="end" className="w-64">
<DropdownMenuItem onSelect={() => setUploadOpen(true)} className="gap-2 py-2.5"> <DropdownMenuItem onSelect={() => setUploadOpen(true)} className="gap-2 py-2.5">
<Upload className="h-4 w-4" /> <Upload className="h-4 w-4" aria-hidden />
<div className="flex flex-col"> <div className="flex flex-col">
<span>Upload file</span> <span>Upload file</span>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
@@ -79,7 +79,7 @@ export function NewDocumentMenu({
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem asChild className="gap-2 py-2.5"> <DropdownMenuItem asChild className="gap-2 py-2.5">
<Link href={`/${portSlug}/documents/new`}> <Link href={`/${portSlug}/documents/new`}>
<FileSignature className="h-4 w-4" /> <FileSignature className="h-4 w-4" aria-hidden />
<div className="flex flex-col"> <div className="flex flex-col">
<span>Generate document for signing</span> <span>Generate document for signing</span>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">

View File

@@ -64,7 +64,7 @@ export function SigningDetailsDialog({ documentId, open, onOpenChange }: Props)
</DialogHeader> </DialogHeader>
{isLoading || !data ? ( {isLoading || !data ? (
<div className="flex items-center justify-center py-12 text-muted-foreground"> <div className="flex items-center justify-center py-12 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... Loading...
</div> </div>
) : ( ) : (

View File

@@ -117,7 +117,7 @@ export function EmailAccountsList() {
</p> </p>
</div> </div>
<Button onClick={() => setSheetOpen(true)}> <Button onClick={() => setSheetOpen(true)}>
<Plus className="h-4 w-4 mr-1.5" /> <Plus className="h-4 w-4 mr-1.5" aria-hidden />
Add account Add account
</Button> </Button>
</div> </div>
@@ -126,14 +126,14 @@ export function EmailAccountsList() {
<p className="text-sm text-muted-foreground">Loading</p> <p className="text-sm text-muted-foreground">Loading</p>
) : accounts.length === 0 ? ( ) : accounts.length === 0 ? (
<div className="rounded-lg border border-dashed p-8 text-center text-muted-foreground"> <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 email accounts connected.</p> <p className="text-sm">No email accounts connected.</p>
</div> </div>
) : ( ) : (
<div className="rounded-lg border divide-y"> <div className="rounded-lg border divide-y">
{accounts.map((a) => ( {accounts.map((a) => (
<div key={a.id} className="flex items-center gap-3 p-3"> <div key={a.id} className="flex items-center gap-3 p-3">
<Mail className="h-5 w-5 text-muted-foreground" /> <Mail className="h-5 w-5 text-muted-foreground" aria-hidden />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate">{a.emailAddress}</div> <div className="text-sm font-medium truncate">{a.emailAddress}</div>
<div className="text-xs text-muted-foreground"> <div className="text-xs text-muted-foreground">
@@ -152,12 +152,12 @@ export function EmailAccountsList() {
disabled={syncMutation.isPending} disabled={syncMutation.isPending}
title="Sync now" title="Sync now"
> >
<RefreshCw className="h-4 w-4" /> <RefreshCw className="h-4 w-4" aria-hidden />
</Button> </Button>
<ConfirmationDialog <ConfirmationDialog
trigger={ trigger={
<Button variant="ghost" size="icon" className="text-destructive"> <Button variant="ghost" size="icon" className="text-destructive">
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" aria-hidden />
</Button> </Button>
} }
title="Remove account" title="Remove account"

View File

@@ -101,12 +101,12 @@ export function EmailDraftButton({
<Button variant="outline" size="sm" onClick={handleGenerateDraft} disabled={isLoading}> <Button variant="outline" size="sm" onClick={handleGenerateDraft} disabled={isLoading}>
{isLoading ? ( {isLoading ? (
<> <>
<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 />
Generating... Generating...
</> </>
) : ( ) : (
<> <>
<Mail className="mr-1.5 h-3.5 w-3.5" /> <Mail className="mr-1.5 h-3.5 w-3.5" aria-hidden />
Draft Email Draft Email
</> </>
)} )}
@@ -156,12 +156,12 @@ export function EmailDraftButton({
<Button variant="outline" size="sm" onClick={handleCopy}> <Button variant="outline" size="sm" onClick={handleCopy}>
{copied ? ( {copied ? (
<> <>
<Check className="mr-1.5 h-3.5 w-3.5 text-green-600" /> <Check className="mr-1.5 h-3.5 w-3.5 text-green-600" aria-hidden />
Copied Copied
</> </>
) : ( ) : (
<> <>
<Copy className="mr-1.5 h-3.5 w-3.5" /> <Copy className="mr-1.5 h-3.5 w-3.5" aria-hidden />
Copy to clipboard Copy to clipboard
</> </>
)} )}
@@ -174,7 +174,7 @@ export function EmailDraftButton({
> >
{isLoading ? ( {isLoading ? (
<> <>
<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 />
Regenerating... Regenerating...
</> </>
) : ( ) : (

View File

@@ -35,11 +35,11 @@ export function EmailThreadsList() {
{Array.from({ length: 4 }).map((_, i) => ( {Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="p-3 space-y-2"> <div key={i} className="p-3 space-y-2">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<Skeleton className="h-4 w-1/3" /> <Skeleton className="h-4 w-1/3" aria-hidden />
<Skeleton className="h-3 w-16" /> <Skeleton className="h-3 w-16" aria-hidden />
</div> </div>
<Skeleton className="h-3 w-1/2" /> <Skeleton className="h-3 w-1/2" aria-hidden />
<Skeleton className="h-3 w-2/3" /> <Skeleton className="h-3 w-2/3" aria-hidden />
</div> </div>
))} ))}
</div> </div>

View File

@@ -108,22 +108,22 @@ export function ExpenseCard({ expense, portSlug, onEdit, onArchive }: ExpenseCar
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
aria-label={`Actions for expense: ${title}`} aria-label={`Actions for expense: ${title}`}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link href={`/${portSlug}/expenses/${expense.id}`}> <Link href={`/${portSlug}/expenses/${expense.id}`}>
<Eye className="mr-2 h-3.5 w-3.5" /> <Eye className="mr-2 h-3.5 w-3.5" aria-hidden />
View View
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => onEdit(expense)}> <DropdownMenuItem onClick={() => onEdit(expense)}>
<Pencil className="mr-2 h-3.5 w-3.5" /> <Pencil className="mr-2 h-3.5 w-3.5" aria-hidden />
Edit Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(expense)}> <DropdownMenuItem className="text-destructive" onClick={() => onArchive(expense)}>
<Archive className="mr-2 h-3.5 w-3.5" /> <Archive className="mr-2 h-3.5 w-3.5" aria-hidden />
Archive Archive
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@@ -131,7 +131,7 @@ export function ExpenseCard({ expense, portSlug, onEdit, onArchive }: ExpenseCar
} }
> >
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<ListCardAvatar icon={<Receipt className="h-5 w-5" />} /> <ListCardAvatar icon={<Receipt className="h-5 w-5" aria-hidden />} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
{/* Title row + spacer for actions button */} {/* Title row + spacer for actions button */}
<div className="flex items-start justify-between gap-2"> <div className="flex items-start justify-between gap-2">
@@ -159,7 +159,9 @@ export function ExpenseCard({ expense, portSlug, onEdit, onArchive }: ExpenseCar
{/* Date meta */} {/* Date meta */}
{dateFormatted ? ( {dateFormatted ? (
<div className="mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-muted-foreground"> <div className="mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-muted-foreground">
<ListCardMeta icon={<Calendar className="h-3 w-3" />}>{dateFormatted}</ListCardMeta> <ListCardMeta icon={<Calendar className="h-3 w-3" aria-hidden />}>
{dateFormatted}
</ListCardMeta>
</div> </div>
) : null} ) : null}

View File

@@ -166,22 +166,22 @@ export function getExpenseColumns({
className="h-7 w-7" className="h-7 w-7"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" aria-hidden />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link href={`/${portSlug}/expenses/${row.original.id}`}> <Link href={`/${portSlug}/expenses/${row.original.id}`}>
<Eye className="mr-2 h-3.5 w-3.5" /> <Eye className="mr-2 h-3.5 w-3.5" aria-hidden />
View View
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => onEdit(row.original)}> <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 Edit
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem className="text-destructive" onClick={() => onArchive(row.original)}> <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 Archive
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View File

@@ -48,7 +48,7 @@ function ReceiptThumbnail({ fileId }: { fileId: string }) {
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex h-32 items-center justify-center rounded border bg-muted/40 text-xs text-muted-foreground"> <div className="flex h-32 items-center justify-center rounded border bg-muted/40 text-xs text-muted-foreground">
<Loader2 className="mr-2 h-3 w-3 animate-spin" /> Loading preview <Loader2 className="mr-2 h-3 w-3 animate-spin" aria-hidden /> Loading preview
</div> </div>
); );
} }
@@ -69,7 +69,7 @@ function ReceiptThumbnail({ fileId }: { fileId: string }) {
</a> </a>
) : ( ) : (
<div className="flex h-32 items-center justify-center text-muted-foreground"> <div className="flex h-32 items-center justify-center text-muted-foreground">
<FileText className="h-8 w-8" /> <FileText className="h-8 w-8" aria-hidden />
</div> </div>
)} )}
<div className="mt-2 flex items-center justify-between text-xs text-muted-foreground"> <div className="mt-2 flex items-center justify-between text-xs text-muted-foreground">
@@ -78,7 +78,7 @@ function ReceiptThumbnail({ fileId }: { fileId: string }) {
href={`/api/v1/files/${fileId}/download`} href={`/api/v1/files/${fileId}/download`}
className="inline-flex items-center gap-1 text-primary hover:underline" className="inline-flex items-center gap-1 text-primary hover:underline"
> >
<Download className="h-3 w-3" /> Download <Download className="h-3 w-3" aria-hidden /> Download
</a> </a>
</div> </div>
</div> </div>
@@ -131,7 +131,7 @@ export function ExpenseDetail({ expenseId, onEdit, onArchived }: ExpenseDetailPr
if (isLoading) { if (isLoading) {
return ( return (
<div className="flex items-center justify-center p-12"> <div className="flex items-center justify-center p-12">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" /> <Loader2 className="h-6 w-6 animate-spin text-muted-foreground" aria-hidden />
</div> </div>
); );
} }
@@ -162,7 +162,7 @@ export function ExpenseDetail({ expenseId, onEdit, onArchived }: ExpenseDetailPr
{onEdit && ( {onEdit && (
<PermissionGate resource="expenses" action="edit"> <PermissionGate resource="expenses" action="edit">
<Button variant="outline" size="sm" onClick={onEdit}> <Button variant="outline" size="sm" onClick={onEdit}>
<Edit className="mr-1.5 h-4 w-4" /> <Edit className="mr-1.5 h-4 w-4" aria-hidden />
Edit Edit
</Button> </Button>
</PermissionGate> </PermissionGate>
@@ -174,7 +174,7 @@ export function ExpenseDetail({ expenseId, onEdit, onArchived }: ExpenseDetailPr
className="text-destructive" className="text-destructive"
onClick={() => setArchiveOpen(true)} onClick={() => setArchiveOpen(true)}
> >
<Archive className="mr-1.5 h-4 w-4" /> <Archive className="mr-1.5 h-4 w-4" aria-hidden />
Archive Archive
</Button> </Button>
</PermissionGate> </PermissionGate>
@@ -259,7 +259,7 @@ export function ExpenseDetail({ expenseId, onEdit, onArchived }: ExpenseDetailPr
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-sm font-medium flex items-center gap-2"> <CardTitle className="text-sm font-medium flex items-center gap-2">
<Receipt className="h-4 w-4" /> <Receipt className="h-4 w-4" aria-hidden />
Receipts ({expense.receiptFileIds.length}) Receipts ({expense.receiptFileIds.length})
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>

View File

@@ -75,7 +75,7 @@ export function ExpenseDuplicateBanner({ expense }: Props) {
)} )}
> >
<div className="flex min-w-0 items-start gap-2"> <div className="flex min-w-0 items-start gap-2">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" /> <AlertTriangle className="mt-0.5 h-4 w-4 shrink-0" aria-hidden />
<div className="min-w-0"> <div className="min-w-0">
<p className="font-medium">Looks like a duplicate</p> <p className="font-medium">Looks like a duplicate</p>
<p className="mt-0.5 text-xs text-amber-800"> <p className="mt-0.5 text-xs text-amber-800">

Some files were not shown because too many files have changed in this diff Show More