chore(audit-drain): rip out next-intl, RTL lint, sweeps, polish

Drain the long-tail audit queue captured in alpha-uat-master.md.

- next-intl ripped out (zero useTranslations callers ever existed):
  package.json, next.config.ts plugin wrap, src/i18n/, messages/, and
  the layout NextIntlClientProvider all gone; <html lang="en"> hardcoded.
- RTL lint nudge added: warn-only no-restricted-syntax on physical
  Tailwind utilities (ml-/mr-/pl-/pr-/text-left/text-right/border-l/
  border-r/rounded-l-/rounded-r-) inside JSX className literals.
  Existing ~1,000 sites grandfathered; new code trends toward logical.
- Icon-only button accessibility lint: jsx-a11y/control-has-associated-
  label enabled at warn; 4 empty <th>/<td> action placeholders gain
  sr-only labels.
- Currency: SUPPORTED_CURRENCIES drops the hardcoded English labels;
  new currencyLabel(code, locale?) helper resolves via Intl.DisplayNames.
  CurrencySelect + settings-manager migrated.
- Date locale sweep: 7 surfaces flip from toLocaleString('en-GB'|'en-US')
  to toLocaleString(undefined, ...) so dates honour runtime locale.
- Dialog/Sheet width: 10 document/EOI/entity-form dialogs gain a
  lg:max-w-4xl or lg:max-w-5xl step so wide desktops get breathing room.
- PaymentsSection collapsed-bar: slim one-line bar showing
  "Payments - Not received yet" or "Payments - \$X received - N payments
  - Expand"; per-interest collapse state persists in localStorage; the
  RecordPayment flow auto-expands.
- muted-foreground opacity sweep: 10 text-bearing
  text-muted-foreground/{60,70,80} hits dropped to plain
  text-muted-foreground for AA contrast on muted bg. Icon-only
  (aria-hidden) opacity hits left as-is.
- Micro-type bump: text-[10px] and text-[11px] -> text-xs (12px)
  across 87 files in src/components + src/app. Pure mechanical sweep.
- Audit-doc cleanup: alpha-uat-master.md stale 2026-05-25 summary
  rewritten with cumulative state through today. Items genuinely still
  open are now a short long-tail list.
- New docs/marketing-site-followups.md: Umami Phase 4a/3/5, email
  pixel E2E verification, and website-cutover work parked here so
  they don't get lost in the CRM audit doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 18:48:46 +02:00
parent 353a31323e
commit e9509dc45c
115 changed files with 528 additions and 776 deletions

View File

@@ -0,0 +1,37 @@
# Marketing-site followups
Items that require edits to the **separate marketing-site repo** (port-nimara.com / portnimara.com), not the CRM. These can't ship from this codebase; they're parked here so they don't get lost when we drain the CRM audit doc.
Last updated: 2026-05-26.
---
## Umami analytics — Phases 4a, 3, 5
**Source:** `docs/superpowers/audits/alpha-uat-master.md` — Umami follow-ups parked at end of the 2026-05-19 build session.
- **Phase 4a — Marketing-site instrumentation.** The CRM's Umami integration (Phase 4b — pixel + tracked-link events on outbound sales emails) is shipped. Phase 4a is the parallel work on the marketing site: add the Umami tracking script to every page, instrument the public berth inquiry form submission, instrument the "request more info" buttons, and confirm session-level attribution flows back to the same Umami workspace the CRM reads.
- **Phase 3 — Events tab.** Once 4a lands, the CRM's `/admin/website-analytics` page gets an Events tab that lists every named Umami event (inquiry-submitted, brochure-downloaded, berth-details-viewed, contact-clicked, …) with counts, top-source breakdown, and a 30-day trendline. Backend already proxies `/api/umami/events`; UI surface is the missing piece. Blocked on 4a sending real event data.
- **Phase 5 — Funnels.** Multi-step funnel widget on the dashboard ("landed on /berths → opened a berth → submitted inquiry → was created as a CRM interest → reached EOI stage"). Joins Umami sessionId with the CRM's `interests.umamiSessionId` snapshot we already write. Blocked on 4a so the first three steps have real data to consume.
---
## Email-tracking end-to-end verification
**Source:** alpha-uat-master.md — Bucket 2 Umami follow-ups.
- **Verify the pixel + tracked-link with a real send** — flip `email_open_tracking_enabled = true` for port-nimara, send a real sales email to a personal inbox, open it in Mail.app + Gmail web, confirm: (a) a `document_send_opens` row appears, (b) `open_count` + `first_opened_at` increment on the parent row, (c) Umami records an `email-opened` event. Same drill for `/q/<slug>` short-links once the composer ships them. Cannot be automated — needs a real human inbox. This is a CRM-side manual UAT step but it depends on the marketing-site short-link redirector being live.
---
## Public berth endpoint email recipient UI (parking note)
**Source:** memory — "Email ownership at cutover" (`project_email_ownership_at_cutover.md`).
When the marketing site cuts over and inquiry emails route through the CRM rather than the website's own SMTP, the public berth endpoint + the admin recipient UI need to be in place. Templates + settings keys exist on the CRM side; the marketing-site side needs the form submission target updated to hit `/api/public/website-inquiries` (or whichever the final endpoint is) instead of the legacy mailto. Coordinate as one rollout.
---
## How to triage when picking these up
Each item here has a CRM-side prerequisite or downstream consumer that's already in place. The work itself lives in the marketing-site repo. When you tackle one, link the marketing-site PR back into this file and tick the item off — keep this doc shrinking, not growing.

View File

@@ -13,36 +13,46 @@
> - `medium` — UX regression, partial functionality, recoverable error > - `medium` — UX regression, partial functionality, recoverable error
> - `low` — cosmetic, copy, polish > - `low` — cosmetic, copy, polish
> **2026-05-25 status check** — full audit triage + execution pass landed (~25 commits over two passes). Shipped in this session: > **2026-05-26 status check** — drain pass landed across the long-tail polish queue. Cumulative state below; per-item commit hashes live in `git log`.
> >
> **Bucket 1 (Wave AE):** Interest form yacht auto-select + EOI dialog "View EOI" toast + berth-picker compact label + Documents-tab Generate-EOI removal + role-scoped interest auto-assign + LinkedBerthRowItem dimension cleanup + ExternalEoiUploadDialog prefill (title + signatories from active EOI) + Overview milestone EOI signature progress widget. a11y aria-live on supplemental info + 13-component date/currency locale sweep (en-US/en-GB → runtime). Primary-berth-always-in-bundle service guard strengthened + backfill migration 0083. Onboarding super_admin discoverability (topbar banner + dashboard tile + celebration toast) wired via `/api/v1/admin/onboarding/status` + shared service. Branded post-completion email idempotency regression test. > **Big-ticket modules shipped (May 2526):**
> >
> **Bucket 2 (Wave AE):** Documents Hub folder rail min-width fix at tablet 768. Website analytics KPI 6→3 cols at lg + 6 at xl. Pipeline Value tile compact `$3.5M` format at sm-. Form-error UX rollout to 5 highest-impact forms (client/interest/yacht/company/berth). TopList empty-state nudge primitive. Sheet width default `sm:max-w-sm → sm:max-w-md + lg:max-w-xl`. > - **Reports P3P7** — `e9ef583` BullMQ render+email worker; `2072f6c` landing + per-kind builder + Templates/Runs/Schedules sub-pages; `3f9c458` CSV output renderer; `866b910` subtitle override + `8998f68` cover-page brand picker. Page is end-to-end functional with scheduled runs, output formats, and dashboard cover branding.
> - **Tenancies P2P7** — `ccc775d` rename migration `berth_reservations → berth_tenancies`; `20549fb` webhook auto-create + first-insert flip; `bfb29ab` public-map status flip via active permanent tenancy; `3a48150` sidebar entry + 404 + API gate; `e4daa48` entity-tab module gate; `911b51a` generic create + edit dialog + self-FKs; `db14056` 4 module-gated dashboard widgets; `d32e557` tenure-aware renewal + transfer actions; `dd25ccf` 7-agent system-wide rename audit fixes. **Chicken-and-egg fix landed 2026-05-26** — webhook auto-create no longer gates itself on `isTenanciesModuleEnabled`; the row-exists fallback in that helper lazily surfaces the module on first signing (`docs/tenancies-design.md` §"When disabled" updated).
> - **UploadForSigningDialog field metadata** — `c4450dd` PlacedField.defaultValue + per-type panel inputs + Documenso v2 `field/create-many` payload extension.
> - **Bulk operations on berths** — `c549622` bulk-price editing UI (inline cell + bulk-edit sheet); `991e222` bulk-edit affordance covering status/tenure_type/tag/archive + 500-id cap + per-row failure reporting.
> - **Documents UX** — `c886933` clickable rows open in-page file preview (Tier 1+2 universal preview); `400ff99` inline edits on berth detail Overview tab now persist visually; `da391b1` interest dimensions dual-source (yacht dims for the recommender).
> >
> **Bucket 2 (Wave F):** Radio field type for admin registry + adopted on `eoi_send_mode` and `documenso_signing_order`. Include-yacht toggle on EOI generate dialog (blanks Section 3 even when a yacht is linked; choice recorded in audit log). External-EOI auto-cancel: replace-or-keep radio shows when a generated EOI is active; replace path voids the upstream Documenso envelope + flips the prior doc to cancelled before the new doc lands. > **Form-error UX sweep — complete.** All `useForm` callers in `src/components` adopt `useFormScrollToError` + `<FormErrorSummary>` (the one remaining holdout is the dev-only `shared/form-devtool.tsx`).
> >
> **Bucket 2 form-error sweep:** All 16 remaining form callsites adopted `useFormScrollToError` + `<FormErrorSummary>` — login / reset-password / set-password / setup auth forms + invoices/new + berth-detail-header + invoice-detail (record-payment) + reservations/berth-reserve-dialog + yachts/yacht-transfer-dialog + companies/add-membership-dialog. Form-error UX sweep now complete platform-wide. > **Bucket 2/4 polish drained 2026-05-26:**
> >
> **Bucket 3 design docs:** `docs/reports-page-design.md` (~400 lines, 7-PR plan) + `docs/tenancies-design.md` (~350 lines, 7-PR plan). > - **next-intl ripped out** — zero `useTranslations()` callers ever existed; the dependency, plugin wrap, request config, and `messages/en.json` are gone. `<html lang="en">` hardcoded.
> - **RTL lint rule** — warn-only `no-restricted-syntax` on physical Tailwind utilities (`ml-/mr-/pl-/pr-/text-left/text-right/border-l/border-r/rounded-l-/rounded-r-`) inside `JSXAttribute[name='className']` literals across `src/components` + `src/app`. Existing 1,000+ sites grandfathered; new code trends toward logical (`ms-/me-/ps-/pe-/text-start/text-end/border-s/border-e/rounded-s-/rounded-e-`).
> - **Currency labels via Intl.DisplayNames** — `SUPPORTED_CURRENCIES` no longer carries hardcoded English labels; new `currencyLabel(code, locale?)` helper resolves via `Intl.DisplayNames`. Two consumer sites migrated (`CurrencySelect`, `settings-manager`).
> - **Date locale sweep** — 7 surfaces (`template-version-history`, `website-analytics/session-detail-sheet` + `sessions-list`, `signing-details-dialog` ×2, `reports-list` ×2) flipped from `toLocaleString('en-GB'|'en-US')` to `toLocaleString(undefined, …)` so dates honor the user's runtime locale.
> - **Dialog/Sheet width bump** — document + EOI + entity-form dialogs all gain a `lg:max-w-4xl`/`lg:max-w-5xl` step so wide desktops get breathing room. Hit: `external-eoi-edit-dialog`, `signing-details-dialog`, `eoi-generate-dialog`, `external-eoi-upload-dialog`, `interest-form`, `client-form`, `yacht-form`, `company-form`, `form-template-form`, `template-form`.
> - **PaymentsSection collapsed-bar** — slim one-line bar shows "Payments · Not received yet" or "Payments · $X received · N payments · Expand"; per-interest collapse state persisted in localStorage; auto-expands on a fresh deposit record.
> - **muted-foreground opacity sweep** — text-bearing `text-muted-foreground/{60,70,80}` hits dropped to plain `text-muted-foreground` on `template-token-picker`, `admin-sections-browser`, `upload-receipts-guide`, `client-card`, `residential-interest-card`, `mobile-search-overlay`, `command-search` ×3, `aggregated-section`, `activity-feed`. Icon-only opacity hits (aria-hidden Lucide icons) left as-is.
> - **Micro-type bump** — `text-[10px]` and `text-[11px]` swept to `text-xs` (12px) across 87 files in `src/components` + `src/app`. Pure mechanical replace; no behavioural change.
> - **Icon-only button lint rule** — `jsx-a11y/control-has-associated-label` enabled (warn) with a sensible `controlComponents: ['Button']` config. Caught + fixed 4 empty `<th>`/`<td>` placeholders in bulk-add-berths-wizard, invitations-manager, berth-interests-tab via `sr-only` action labels.
> - **EOI tab upload-draft parity** — confirmed already shipped: `documentTypeSchema` accepts `'eoi'` and `interest-eoi-tab.tsx:243` mounts the dialog.
> - **EntityFolderView per-row interest badge** — confirmed already shipped (lines 116118 render the badge + interest link).
> - **Onboarding autoCheckResolver** — confirmed already working: the checklist hits `/api/v1/admin/settings/resolved?keys=...` which runs the port→global→env→default chain, and `smtp_host_override` / `documenso_api_url_override` / etc. have `envFallback` set on their registry entries. Steps auto-tick when a port relies on env config.
> - **Global-search translucent bug** — defensive fix in `command-search.tsx:327` (`bg-white dark:bg-popover shadow-lg`) is solidly opaque; no parent has opacity/blend that would leak through. Closed.
> >
> **Bucket 3 P1 foundations:** > **Marketing-site followups** parked separately in `docs/marketing-site-followups.md`:
> >
> - Tenancies module-enabled gate (`tenancies_module_enabled` setting, `tenancies-module.service.ts`, 3 admin endpoints under `/api/v1/admin/tenancies-module/`). > - Umami Phase 4a (marketing-site instrumentation), Phase 3/5 (events tab + funnels) — blocked on the other repo.
> - Reports schema migration 0084 (extends `report_templates` + adds `report_runs` + `report_schedules` tables) + matching Drizzle schema. > - Email pixel/tracked-link end-to-end verification with a real human inbox.
> - Public berth endpoint + admin recipient UI for the website-to-CRM email cutover.
> >
> **Bucket 3 Reports P2 (this session):** CRUD layer on `report_runs` + `report_schedules`. Validators (kind / output / cadence / status enums), services (`report-runs.service.ts`, `report-schedules.service.ts` with deterministic `nextRunFor` math), routes (`/api/v1/reports/runs` list+create + `/[id]` get, `/api/v1/reports/schedules` list+create + `/[id]` get+patch+delete). 9-case integration test covering cross-port FK guard, config.kind discriminator, listing filters, cadence math, no-op-doesn't-slip rule, and `ON DELETE SET NULL` contract on schedule deletion. > **Genuinely still-open (long-tail, low ROI):**
> >
> **Bucket 4:** Sheet width sweep (Sheet primitive update covers every site). External-EOI dialog cache collision: dialog was caching `{data:…}` on the same key the parent unwraps, blanking the page on open — fix unwraps to match. External-EOI advance-gate regression test (7 cases). Search popover defensive opaque background. EntityFolderView visual overhaul: shared FileIcon mapping for type-specific colours + inline "Signed" pill from `signedFromDocumentId`. > - **Sheet width on wide viewports** — bumped the document/EOI dialogs by one tier; if any specific dialog still feels narrow, name it.
> > - **Custom-field-form FieldLabel sweep** — primitive exists, registry-driven-form already surfaces descriptions inline (better discoverability than tooltip), custom-fields uses FieldLabel. Bespoke admin pages (~1015 surfaces) still use plain `<Label>`; sweep is multi-hour and low impact since most admin settings have inline copy already.
> **Audit-cleanup callouts:** "dock-letters entity" (B2 Wave G item) was already shipped in `431375d` (D25). "Email-test endpoints" was already shipped (registry + endpoint + admin card all exist). "Cancel doc delete-vs-keep" was already shipped (cancelMode plumbed through `cancelDocument` + dialog adoption complete). These three were misclassified as queued. > - **Naive ternary pluralization** (`count === 1 ? 'X' : 'Xs'`) across 15+ surfaces — won't matter until a Polish/Arabic/Russian customer signs. Cost: ~1h after i18n strategy lands.
> > - **CSS logical properties full sweep** — lint rule added; existing 1,000+ sites grandfathered. Skip unless RTL customer is imminent.
> **Deferred / queued for follow-up sessions (~110 h):**
>
> - Reports P3-P7 (~31 h): BullMQ render+email queues + landing + builder + sub-pages + CSV/PNG outputs + metadata overrides.
> - Tenancies P2-P7 (~36 h): rename migration + perms seed + webhook auto-create + public-map flip rules + sidebar entry + top-level page + entity tab CTAs + 4 reporting widgets.
> - UploadForSigningDialog field metadata (full bundle, ~6-9 h): PlacedField.defaultValue + fieldMeta with per-type panel inputs + Documenso v2 `field/create-many` payload extension.
> - B3 Wave remaining: bulk-price editing UI (~2-3 h — backend already shipped); Umami Phase 4a (marketing-site instrumentation, separate repo) + Phase 3/5 (events tab + funnels, blocked on 4a). _Shipped this session: B3-1 (interest dimensions dual-source). Shipped earlier in week: universal file preview Tier 1+2, all 16 PDF resolvers (incl. the 5 previously-tracked "remaining"), universal upload-with-fields backend + most UI sites._ Recharts→ECharts migration removed (rejected).\_
> - B4 bugs: Global-search dropdown translucent still wants live-browser repro to confirm the defensive fix actually addressed the cause.
--- ---

View File

@@ -33,6 +33,33 @@ const eslintConfig = [
'react-hooks/refs': 'error', 'react-hooks/refs': 'error',
'react-hooks/set-state-in-effect': 'error', 'react-hooks/set-state-in-effect': 'error',
'react-hooks/incompatible-library': 'off', 'react-hooks/incompatible-library': 'off',
// Icon-only buttons must carry a label that screen readers can
// surface — either an explicit `aria-label`, an `aria-labelledby`,
// a `title`, or a visible-but-sr-only text child. Catches the
// pattern where a `<button><Trash2 /></button>` ships with no
// accessible name. Default Next config enables this at `error`;
// we keep it loud so new code doesn't regress.
'jsx-a11y/control-has-associated-label': [
'warn',
{
labelAttributes: ['label'],
controlComponents: ['Button'],
ignoreElements: ['audio', 'canvas', 'embed', 'input', 'textarea', 'tr', 'video'],
ignoreRoles: [
'grid',
'listbox',
'menu',
'menubar',
'radiogroup',
'row',
'tablist',
'toolbar',
'tree',
'treegrid',
],
depth: 5,
},
],
}, },
}, },
{ {
@@ -41,18 +68,36 @@ const eslintConfig = [
// tell-tale "AI-generated" marker; we prefer periods, commas, or // tell-tale "AI-generated" marker; we prefer periods, commas, or
// simple hyphens. Code comments / audit-log strings / templates // simple hyphens. Code comments / audit-log strings / templates
// outside these directories are exempt. // outside these directories are exempt.
//
// Same rule block also nudges new code toward CSS logical properties
// (ms-/me-/ps-/pe-/text-start/text-end/border-s/border-e) instead of
// physical Tailwind utilities. RTL isn't a roadmap requirement today,
// but every new ml-/mr-/pl-/pr-/text-left/text-right we accept now
// is a class we'd have to migrate later. Existing 1,000+ sites stay
// untouched (warn-only). Inline `// eslint-disable-next-line` when
// the directional intent is truly physical (e.g. a chevron icon).
files: ['src/components/**/*.tsx', 'src/app/**/*.tsx'], files: ['src/components/**/*.tsx', 'src/app/**/*.tsx'],
rules: { rules: {
// Both selectors share `warn` severity because the RTL nudge is
// grandfathered (1,000+ existing sites use ml-/mr-/etc). The
// em-dash sweep cleared every existing instance (2026-05-21), so
// `warn` still effectively gates new code — it just doesn't break
// CI on grandfathered RTL utilities. Inline
// `// eslint-disable-next-line no-restricted-syntax` when the
// directional intent is truly physical.
'no-restricted-syntax': [ 'no-restricted-syntax': [
// Bumped from warn → error after the 2026-05-21 sweep cleared 'warn',
// the existing 108 instances. New code reintroducing em-dashes
// now fails the lint gate.
'error',
{ {
selector: "JSXText[value=/\\u2014/]", selector: "JSXText[value=/\\u2014/]",
message: message:
'No em-dash in user-facing JSX text. Use period, comma, or hyphen instead.', 'No em-dash in user-facing JSX text. Use period, comma, or hyphen instead.',
}, },
{
selector:
"JSXAttribute[name.name='className'] > Literal[value=/(?:^|[\\s:])(?:ml-|mr-|pl-|pr-|text-left|text-right|border-l\\b|border-r\\b|rounded-l-|rounded-r-)/]",
message:
'Prefer CSS logical properties (ms-/me-/ps-/pe-/text-start/text-end/border-s/border-e/rounded-s-/rounded-e-) over physical directional Tailwind utilities. Existing code is grandfathered; new code should default to logical so a future RTL pass is bounded.',
},
], ],
}, },
}, },

View File

@@ -1,9 +0,0 @@
{
"common": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
"back": "Back"
}
}

View File

@@ -1,13 +1,7 @@
import type { NextConfig } from 'next'; import type { NextConfig } from 'next';
import bundleAnalyzer from '@next/bundle-analyzer'; import bundleAnalyzer from '@next/bundle-analyzer';
import createNextIntlPlugin from 'next-intl/plugin';
import { withSentryConfig } from '@sentry/nextjs'; import { withSentryConfig } from '@sentry/nextjs';
// next-intl plugin — points at our request-config entrypoint. Even
// though we ship only English today, the plugin is wired so future
// locale additions are a config-only change, not a code rewrite.
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
const isProd = process.env.NODE_ENV === 'production'; const isProd = process.env.NODE_ENV === 'production';
// Wrap the config with the bundle analyzer. Run `ANALYZE=true pnpm build` // Wrap the config with the bundle analyzer. Run `ANALYZE=true pnpm build`
@@ -171,4 +165,4 @@ const withSentry = process.env.NEXT_PUBLIC_SENTRY_DSN
}) })
: (cfg: NextConfig) => cfg; : (cfg: NextConfig) => cfg;
export default withSentry(withBundleAnalyzer(withNextIntl(nextConfig))); export default withSentry(withBundleAnalyzer(nextConfig));

View File

@@ -94,7 +94,6 @@
"minio": "^8.0.7", "minio": "^8.0.7",
"motion": "^12.38.0", "motion": "^12.38.0",
"next": "16.2.6", "next": "16.2.6",
"next-intl": "^4.11.2",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"nodemailer": "^8.0.7", "nodemailer": "^8.0.7",
"openai": "^6.37.0", "openai": "^6.37.0",

410
pnpm-lock.yaml generated
View File

@@ -199,9 +199,6 @@ importers:
next: next:
specifier: 16.2.6 specifier: 16.2.6
version: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) version: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
next-intl:
specifier: ^4.11.2
version: 4.11.2(@swc/helpers@0.5.21)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(typescript@6.0.3)
next-themes: next-themes:
specifier: ^0.4.6 specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6) version: 0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -995,18 +992,6 @@ packages:
'@floating-ui/utils@0.2.11': '@floating-ui/utils@0.2.11':
resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
'@formatjs/fast-memoize@3.1.5':
resolution: {integrity: sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==}
'@formatjs/icu-messageformat-parser@3.5.8':
resolution: {integrity: sha512-uZLvzLFN7iV2l8cbDdROwgKGtdELeLI4bpnsuz1DnyscHDxn8TdDE0anHzcfjtWK66XYCllGLV3Mi3CYcEPg/g==}
'@formatjs/icu-skeleton-parser@2.1.8':
resolution: {integrity: sha512-iX5i0O15gPf69l1WqmLFYwn7wq53lauvytvWFnHamIfX/5Ta56gpFj6fdeHRcKTV58IhrKv8QOvWfTYZYm7f+g==}
'@formatjs/intl-localematcher@0.8.7':
resolution: {integrity: sha512-1R/ljfRKG1fUhKG4F0lUmrEKPkr/PlHqbgQ8xeYQYYunXu5/0+vbQeeVgGAgydp13Tq+S1X5Qjn6L90hijXjHg==}
'@formkit/auto-animate@0.9.0': '@formkit/auto-animate@0.9.0':
resolution: {integrity: sha512-VhP4zEAacXS3dfTpJpJ88QdLqMTcabMg0jwpOSxZ/VzfQVfl3GkZSCZThhGC5uhq/TxPHPzW0dzr4H9Bb1OgKA==} resolution: {integrity: sha512-VhP4zEAacXS3dfTpJpJ88QdLqMTcabMg0jwpOSxZ/VzfQVfl3GkZSCZThhGC5uhq/TxPHPzW0dzr4H9Bb1OgKA==}
@@ -1612,94 +1597,6 @@ packages:
'@oxc-project/types@0.122.0': '@oxc-project/types@0.122.0':
resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==}
'@parcel/watcher-android-arm64@2.5.6':
resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [android]
'@parcel/watcher-darwin-arm64@2.5.6':
resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [darwin]
'@parcel/watcher-darwin-x64@2.5.6':
resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [darwin]
'@parcel/watcher-freebsd-x64@2.5.6':
resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [freebsd]
'@parcel/watcher-linux-arm-glibc@2.5.6':
resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.6':
resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.6':
resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.6':
resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.6':
resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.6':
resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.6':
resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [win32]
'@parcel/watcher-win32-ia32@2.5.6':
resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==}
engines: {node: '>= 10.0.0'}
cpu: [ia32]
os: [win32]
'@parcel/watcher-win32-x64@2.5.6':
resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [win32]
'@parcel/watcher@2.5.6':
resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==}
engines: {node: '>= 10.0.0'}
'@pdf-lib/standard-fonts@1.0.0': '@pdf-lib/standard-fonts@1.0.0':
resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==} resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==}
@@ -2803,9 +2700,6 @@ packages:
'@rtsao/scc@1.1.0': '@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@schummar/icu-type-parser@1.21.5':
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
'@selderee/plugin-htmlparser2@0.11.0': '@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
@@ -2967,105 +2861,12 @@ packages:
'@standard-schema/utils@0.3.0': '@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
'@swc/core-darwin-arm64@1.15.33':
resolution: {integrity: sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.15.33':
resolution: {integrity: sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.15.33':
resolution: {integrity: sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.15.33':
resolution: {integrity: sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.15.33':
resolution: {integrity: sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/core-linux-ppc64-gnu@1.15.33':
resolution: {integrity: sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==}
engines: {node: '>=10'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@swc/core-linux-s390x-gnu@1.15.33':
resolution: {integrity: sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==}
engines: {node: '>=10'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-gnu@1.15.33':
resolution: {integrity: sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.15.33':
resolution: {integrity: sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.15.33':
resolution: {integrity: sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.15.33':
resolution: {integrity: sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.15.33':
resolution: {integrity: sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.15.33':
resolution: {integrity: sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
peerDependenciesMeta:
'@swc/helpers':
optional: true
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/helpers@0.5.15': '@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@swc/helpers@0.5.21': '@swc/helpers@0.5.21':
resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==}
'@swc/types@0.1.26':
resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==}
'@tailwindcss/node@4.3.0': '@tailwindcss/node@4.3.0':
resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==}
@@ -5077,9 +4878,6 @@ packages:
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
icu-minify@4.11.2:
resolution: {integrity: sha512-vZRLaDpZiFNBXZ1tUtekAf4WXl/Tow/BoWx8MWQqQpGckXf11opUXYzG+mXUj+OsDuv9Zz+etLfUWwxaMnYnzw==}
idb-keyval@6.2.2: idb-keyval@6.2.2:
resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==}
@@ -5129,9 +4927,6 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'} engines: {node: '>=12'}
intl-messageformat@11.2.5:
resolution: {integrity: sha512-zaROHiUsnlSFXVypU54AsQuAm3DLmmSH8KfDhiUuG1XZ9NTQ4o3xlxIJYVNmeWAklyp3CWg0lhexNUnee8PsYQ==}
ioredis@5.10.1: ioredis@5.10.1:
resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==} resolution: {integrity: sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==}
engines: {node: '>=12.22.0'} engines: {node: '>=12.22.0'}
@@ -5795,26 +5590,9 @@ packages:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
negotiator@1.0.0:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
neo-async@2.6.2: neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
next-intl-swc-plugin-extractor@4.11.2:
resolution: {integrity: sha512-1TQGAjkrV6wl4gqwabCFLAAvkAvaBs87ByitYlu01bzWpD/pT/am1JYmpQCIdAMzzpF0hLtj3/xSgVWHjj9fmw==}
next-intl@4.11.2:
resolution: {integrity: sha512-96GZVgiGF5zzEbKaml3lLVRVR9jIZiLsZCeezjq3RMcW4wrWr4v85SZm++/uutsS9IqsJ7rMM5KGYXqvVS8c/Q==}
peerDependencies:
next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
next-themes@0.4.6: next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies: peerDependencies:
@@ -5845,9 +5623,6 @@ packages:
node-abort-controller@3.1.1: node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-exports-info@1.6.0: node-exports-info@1.6.0:
resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -6150,9 +5925,6 @@ packages:
png-js@2.0.0: png-js@2.0.0:
resolution: {integrity: sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA==} resolution: {integrity: sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA==}
po-parser@2.1.1:
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
possible-typed-array-names@1.1.0: possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -7120,11 +6892,6 @@ packages:
peerDependencies: peerDependencies:
react: '>=16.13' react: '>=16.13'
use-intl@4.11.2:
resolution: {integrity: sha512-JsheePHtkp39cDLbIbFr5Ta8jcPJM8g0qc5AVlTwsCtg/G98hqcCdFtEuDbZadK+8qSor6VLFTSNsUyZ0Zietw==}
peerDependencies:
react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
use-sidecar@1.1.3: use-sidecar@1.1.3:
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -8033,18 +7800,6 @@ snapshots:
'@floating-ui/utils@0.2.11': {} '@floating-ui/utils@0.2.11': {}
'@formatjs/fast-memoize@3.1.5': {}
'@formatjs/icu-messageformat-parser@3.5.8':
dependencies:
'@formatjs/icu-skeleton-parser': 2.1.8
'@formatjs/icu-skeleton-parser@2.1.8': {}
'@formatjs/intl-localematcher@0.8.7':
dependencies:
'@formatjs/fast-memoize': 3.1.5
'@formkit/auto-animate@0.9.0': {} '@formkit/auto-animate@0.9.0': {}
'@hookform/devtools@4.4.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': '@hookform/devtools@4.4.0(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
@@ -8589,66 +8344,6 @@ snapshots:
'@oxc-project/types@0.122.0': {} '@oxc-project/types@0.122.0': {}
'@parcel/watcher-android-arm64@2.5.6':
optional: true
'@parcel/watcher-darwin-arm64@2.5.6':
optional: true
'@parcel/watcher-darwin-x64@2.5.6':
optional: true
'@parcel/watcher-freebsd-x64@2.5.6':
optional: true
'@parcel/watcher-linux-arm-glibc@2.5.6':
optional: true
'@parcel/watcher-linux-arm-musl@2.5.6':
optional: true
'@parcel/watcher-linux-arm64-glibc@2.5.6':
optional: true
'@parcel/watcher-linux-arm64-musl@2.5.6':
optional: true
'@parcel/watcher-linux-x64-glibc@2.5.6':
optional: true
'@parcel/watcher-linux-x64-musl@2.5.6':
optional: true
'@parcel/watcher-win32-arm64@2.5.6':
optional: true
'@parcel/watcher-win32-ia32@2.5.6':
optional: true
'@parcel/watcher-win32-x64@2.5.6':
optional: true
'@parcel/watcher@2.5.6':
dependencies:
detect-libc: 2.1.2
is-glob: 4.0.3
node-addon-api: 7.1.1
picomatch: 4.0.4
optionalDependencies:
'@parcel/watcher-android-arm64': 2.5.6
'@parcel/watcher-darwin-arm64': 2.5.6
'@parcel/watcher-darwin-x64': 2.5.6
'@parcel/watcher-freebsd-x64': 2.5.6
'@parcel/watcher-linux-arm-glibc': 2.5.6
'@parcel/watcher-linux-arm-musl': 2.5.6
'@parcel/watcher-linux-arm64-glibc': 2.5.6
'@parcel/watcher-linux-arm64-musl': 2.5.6
'@parcel/watcher-linux-x64-glibc': 2.5.6
'@parcel/watcher-linux-x64-musl': 2.5.6
'@parcel/watcher-win32-arm64': 2.5.6
'@parcel/watcher-win32-ia32': 2.5.6
'@parcel/watcher-win32-x64': 2.5.6
'@pdf-lib/standard-fonts@1.0.0': '@pdf-lib/standard-fonts@1.0.0':
dependencies: dependencies:
pako: 1.0.11 pako: 1.0.11
@@ -9683,8 +9378,6 @@ snapshots:
'@rtsao/scc@1.1.0': {} '@rtsao/scc@1.1.0': {}
'@schummar/icu-type-parser@1.21.5': {}
'@selderee/plugin-htmlparser2@0.11.0': '@selderee/plugin-htmlparser2@0.11.0':
dependencies: dependencies:
domhandler: 5.0.3 domhandler: 5.0.3
@@ -9892,63 +9585,6 @@ snapshots:
'@standard-schema/utils@0.3.0': {} '@standard-schema/utils@0.3.0': {}
'@swc/core-darwin-arm64@1.15.33':
optional: true
'@swc/core-darwin-x64@1.15.33':
optional: true
'@swc/core-linux-arm-gnueabihf@1.15.33':
optional: true
'@swc/core-linux-arm64-gnu@1.15.33':
optional: true
'@swc/core-linux-arm64-musl@1.15.33':
optional: true
'@swc/core-linux-ppc64-gnu@1.15.33':
optional: true
'@swc/core-linux-s390x-gnu@1.15.33':
optional: true
'@swc/core-linux-x64-gnu@1.15.33':
optional: true
'@swc/core-linux-x64-musl@1.15.33':
optional: true
'@swc/core-win32-arm64-msvc@1.15.33':
optional: true
'@swc/core-win32-ia32-msvc@1.15.33':
optional: true
'@swc/core-win32-x64-msvc@1.15.33':
optional: true
'@swc/core@1.15.33(@swc/helpers@0.5.21)':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.26
optionalDependencies:
'@swc/core-darwin-arm64': 1.15.33
'@swc/core-darwin-x64': 1.15.33
'@swc/core-linux-arm-gnueabihf': 1.15.33
'@swc/core-linux-arm64-gnu': 1.15.33
'@swc/core-linux-arm64-musl': 1.15.33
'@swc/core-linux-ppc64-gnu': 1.15.33
'@swc/core-linux-s390x-gnu': 1.15.33
'@swc/core-linux-x64-gnu': 1.15.33
'@swc/core-linux-x64-musl': 1.15.33
'@swc/core-win32-arm64-msvc': 1.15.33
'@swc/core-win32-ia32-msvc': 1.15.33
'@swc/core-win32-x64-msvc': 1.15.33
'@swc/helpers': 0.5.21
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.15': '@swc/helpers@0.5.15':
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
@@ -9957,10 +9593,6 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
'@swc/types@0.1.26':
dependencies:
'@swc/counter': 0.1.3
'@tailwindcss/node@4.3.0': '@tailwindcss/node@4.3.0':
dependencies: dependencies:
'@jridgewell/remapping': 2.3.5 '@jridgewell/remapping': 2.3.5
@@ -12051,10 +11683,6 @@ snapshots:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
icu-minify@4.11.2:
dependencies:
'@formatjs/icu-messageformat-parser': 3.5.8
idb-keyval@6.2.2: {} idb-keyval@6.2.2: {}
ieee754@1.2.1: {} ieee754@1.2.1: {}
@@ -12110,11 +11738,6 @@ snapshots:
internmap@2.0.3: {} internmap@2.0.3: {}
intl-messageformat@11.2.5:
dependencies:
'@formatjs/fast-memoize': 3.1.5
'@formatjs/icu-messageformat-parser': 3.5.8
ioredis@5.10.1: ioredis@5.10.1:
dependencies: dependencies:
'@ioredis/commands': 1.5.1 '@ioredis/commands': 1.5.1
@@ -12741,29 +12364,8 @@ snapshots:
negotiator@0.6.3: {} negotiator@0.6.3: {}
negotiator@1.0.0: {}
neo-async@2.6.2: {} neo-async@2.6.2: {}
next-intl-swc-plugin-extractor@4.11.2: {}
next-intl@4.11.2(@swc/helpers@0.5.21)(next@16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(react@19.2.6)(typescript@6.0.3):
dependencies:
'@formatjs/intl-localematcher': 0.8.7
'@parcel/watcher': 2.5.6
'@swc/core': 1.15.33(@swc/helpers@0.5.21)
icu-minify: 4.11.2
negotiator: 1.0.0
next: 16.2.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
next-intl-swc-plugin-extractor: 4.11.2
po-parser: 2.1.1
react: 19.2.6
use-intl: 4.11.2(react@19.2.6)
optionalDependencies:
typescript: 6.0.3
transitivePeerDependencies:
- '@swc/helpers'
next-themes@0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6): next-themes@0.4.6(react-dom@19.2.6(react@19.2.6))(react@19.2.6):
dependencies: dependencies:
react: 19.2.6 react: 19.2.6
@@ -12797,8 +12399,6 @@ snapshots:
node-abort-controller@3.1.1: {} node-abort-controller@3.1.1: {}
node-addon-api@7.1.1: {}
node-exports-info@1.6.0: node-exports-info@1.6.0:
dependencies: dependencies:
array.prototype.flatmap: 1.3.3 array.prototype.flatmap: 1.3.3
@@ -13118,8 +12718,6 @@ snapshots:
dependencies: dependencies:
fflate: 0.8.2 fflate: 0.8.2
po-parser@2.1.1: {}
possible-typed-array-names@1.1.0: {} possible-typed-array-names@1.1.0: {}
postcss-value-parser@4.2.0: {} postcss-value-parser@4.2.0: {}
@@ -14235,14 +13833,6 @@ snapshots:
dequal: 2.0.3 dequal: 2.0.3
react: 19.2.6 react: 19.2.6
use-intl@4.11.2(react@19.2.6):
dependencies:
'@formatjs/fast-memoize': 3.1.5
'@schummar/icu-type-parser': 1.21.5
icu-minify: 4.11.2
intl-messageformat: 11.2.5
react: 19.2.6
use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.6): use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.6):
dependencies: dependencies:
detect-node-es: 1.1.0 detect-node-es: 1.1.0

View File

@@ -192,7 +192,7 @@ export default function SetupPage() {
</Button> </Button>
</form> </form>
<p className="text-center text-[11px] text-muted-foreground"> <p className="text-center text-xs text-muted-foreground">
This screen is only available until the first administrator is created. After that, This screen is only available until the first administrator is created. After that,
subsequent users are added through Admin &rarr; Users. subsequent users are added through Admin &rarr; Users.
</p> </p>

View File

@@ -256,7 +256,7 @@ function PresetButton({
> >
<p className="text-sm font-semibold">{label}</p> <p className="text-sm font-semibold">{label}</p>
<p className="text-xs text-muted-foreground">{description}</p> <p className="text-xs text-muted-foreground">{description}</p>
<p className="mt-1 text-[10px] uppercase tracking-wide text-muted-foreground"> <p className="mt-1 text-xs uppercase tracking-wide text-muted-foreground">
{name === 'aggressive' ? 'auto for all triggers' : 'suggest for all triggers'} {name === 'aggressive' ? 'auto for all triggers' : 'suggest for all triggers'}
</p> </p>
</button> </button>

View File

@@ -2,8 +2,6 @@ import type { Metadata, Viewport } from 'next';
import Script from 'next/script'; import Script from 'next/script';
import { headers } from 'next/headers'; import { headers } from 'next/headers';
import { Inter, JetBrains_Mono } from 'next/font/google'; import { Inter, JetBrains_Mono } from 'next/font/google';
import { NextIntlClientProvider } from 'next-intl';
import { getLocale, getMessages } from 'next-intl/server';
import { Toaster } from 'sonner'; import { Toaster } from 'sonner';
import { classifyFormFactor } from '@/lib/form-factor'; import { classifyFormFactor } from '@/lib/form-factor';
import { ReactGrabViewportSync } from '@/components/dev/react-grab-viewport-sync'; import { ReactGrabViewportSync } from '@/components/dev/react-grab-viewport-sync';
@@ -64,11 +62,9 @@ export async function generateMetadata(): Promise<Metadata> {
export default async function RootLayout({ children }: { children: React.ReactNode }) { export default async function RootLayout({ children }: { children: React.ReactNode }) {
const headerList = await headers(); const headerList = await headers();
const formFactor = classifyFormFactor(headerList.get('user-agent')); const formFactor = classifyFormFactor(headerList.get('user-agent'));
const locale = await getLocale();
const messages = await getMessages();
return ( return (
<html lang={locale} suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<head> <head>
{process.env.NODE_ENV === 'development' && ( {process.env.NODE_ENV === 'development' && (
<Script <Script
@@ -82,9 +78,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
data-form-factor={formFactor} data-form-factor={formFactor}
className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased`} className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased`}
> >
<NextIntlClientProvider locale={locale} messages={messages}> {children}
{children}
</NextIntlClientProvider>
<Toaster richColors position="top-right" /> <Toaster richColors position="top-right" />
{process.env.NODE_ENV === 'development' && <ReactGrabViewportSync />} {process.env.NODE_ENV === 'development' && <ReactGrabViewportSync />}
</body> </body>

View File

@@ -220,7 +220,7 @@ export default function SupplementalInfoPage({ params }: PageProps) {
<div className="space-y-1.5"> <div className="space-y-1.5">
<Label htmlFor="phone">Phone</Label> <Label htmlFor="phone">Phone</Label>
<PhoneInput id="phone" value={phone} onChange={setPhone} placeholder="Phone number" /> <PhoneInput id="phone" value={phone} onChange={setPhone} placeholder="Phone number" />
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Use a different number than the one you signed up with if you&apos;d prefer to be Use a different number than the one you signed up with if you&apos;d prefer to be
reached there instead. reached there instead.
</p> </p>
@@ -298,7 +298,7 @@ export default function SupplementalInfoPage({ params }: PageProps) {
{submitting ? 'Submitting…' : 'Submit'} {submitting ? 'Submitting…' : 'Submit'}
</Button> </Button>
<p className="text-center text-[11px] text-muted-foreground"> <p className="text-center text-xs text-muted-foreground">
This link is private to you and expires after one use. This link is private to you and expires after one use.
</p> </p>
</form> </form>

View File

@@ -550,9 +550,7 @@ function SectionCard({
<div className="flex-1"> <div className="flex-1">
<CardTitle className="text-base">{section.label}</CardTitle> <CardTitle className="text-base">{section.label}</CardTitle>
{groupTitle ? ( {groupTitle ? (
<p className="text-[10px] uppercase tracking-wider text-muted-foreground"> <p className="text-xs uppercase tracking-wider text-muted-foreground">{groupTitle}</p>
{groupTitle}
</p>
) : null} ) : null}
</div> </div>
</CardHeader> </CardHeader>

View File

@@ -127,11 +127,9 @@ export function AuditLogCard({ entry }: AuditLogCardProps) {
<ListCardMeta icon={<Clock className="h-3 w-3" aria-hidden />}> <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 ? <span className="font-mono text-xs">{entry.ipAddress}</span> : null}
<span className="font-mono text-[11px]">{entry.ipAddress}</span>
) : null}
{entry.severity && entry.severity !== 'info' ? ( {entry.severity && entry.severity !== 'info' ? (
<span className="uppercase font-semibold tracking-wide text-[10px]"> <span className="uppercase font-semibold tracking-wide text-xs">
{entry.severity} {entry.severity}
</span> </span>
) : null} ) : null}
@@ -190,7 +188,7 @@ export function AuditLogCard({ entry }: AuditLogCardProps) {
<summary className="cursor-pointer font-semibold text-muted-foreground"> <summary className="cursor-pointer font-semibold text-muted-foreground">
Old value Old value
</summary> </summary>
<pre className="mt-1 max-h-64 overflow-auto rounded bg-background p-2 font-mono text-[11px]"> <pre className="mt-1 max-h-64 overflow-auto rounded bg-background p-2 font-mono text-xs">
{JSON.stringify(entry.oldValue, null, 2)} {JSON.stringify(entry.oldValue, null, 2)}
</pre> </pre>
</details> </details>
@@ -200,7 +198,7 @@ export function AuditLogCard({ entry }: AuditLogCardProps) {
<summary className="cursor-pointer font-semibold text-muted-foreground"> <summary className="cursor-pointer font-semibold text-muted-foreground">
New value New value
</summary> </summary>
<pre className="mt-1 max-h-64 overflow-auto rounded bg-background p-2 font-mono text-[11px]"> <pre className="mt-1 max-h-64 overflow-auto rounded bg-background p-2 font-mono text-xs">
{JSON.stringify(entry.newValue, null, 2)} {JSON.stringify(entry.newValue, null, 2)}
</pre> </pre>
</details> </details>
@@ -210,7 +208,7 @@ export function AuditLogCard({ entry }: AuditLogCardProps) {
<summary className="cursor-pointer font-semibold text-muted-foreground"> <summary className="cursor-pointer font-semibold text-muted-foreground">
Metadata Metadata
</summary> </summary>
<pre className="mt-1 max-h-64 overflow-auto rounded bg-background p-2 font-mono text-[11px]"> <pre className="mt-1 max-h-64 overflow-auto rounded bg-background p-2 font-mono text-xs">
{JSON.stringify(entry.metadata, null, 2)} {JSON.stringify(entry.metadata, null, 2)}
</pre> </pre>
</details> </details>

View File

@@ -267,7 +267,7 @@ export function AuditLogList() {
</Badge> </Badge>
{row.original.severity !== 'info' && ( {row.original.severity !== 'info' && (
<Badge <Badge
className={`${SEVERITY_BADGE[row.original.severity] ?? ''} text-[10px] px-1.5 py-0 uppercase`} className={`${SEVERITY_BADGE[row.original.severity] ?? ''} text-xs px-1.5 py-0 uppercase`}
variant="outline" variant="outline"
> >
{row.original.severity} {row.original.severity}
@@ -385,36 +385,36 @@ export function AuditLogList() {
</div> </div>
{e.oldValue ? ( {e.oldValue ? (
<details> <details>
<summary className="cursor-pointer text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Old value Old value
</summary> </summary>
<pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-[11px]"> <pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
{JSON.stringify(e.oldValue, null, 2)} {JSON.stringify(e.oldValue, null, 2)}
</pre> </pre>
</details> </details>
) : null} ) : null}
{e.newValue ? ( {e.newValue ? (
<details open> <details open>
<summary className="cursor-pointer text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
New value New value
</summary> </summary>
<pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-[11px]"> <pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
{JSON.stringify(e.newValue, null, 2)} {JSON.stringify(e.newValue, null, 2)}
</pre> </pre>
</details> </details>
) : null} ) : null}
{e.metadata ? ( {e.metadata ? (
<details> <details>
<summary className="cursor-pointer text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <summary className="cursor-pointer text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Metadata Metadata
</summary> </summary>
<pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-[11px]"> <pre className="mt-1 max-h-60 overflow-auto rounded bg-muted p-2 font-mono text-xs">
{JSON.stringify(e.metadata, null, 2)} {JSON.stringify(e.metadata, null, 2)}
</pre> </pre>
</details> </details>
) : null} ) : null}
{e.ipAddress || e.userAgent ? ( {e.ipAddress || e.userAgent ? (
<dl className="grid grid-cols-[88px_1fr] gap-x-2 gap-y-1 text-[11px]"> <dl className="grid grid-cols-[88px_1fr] gap-x-2 gap-y-1 text-xs">
{e.ipAddress ? ( {e.ipAddress ? (
<> <>
<dt className="font-semibold text-muted-foreground">IP address</dt> <dt className="font-semibold text-muted-foreground">IP address</dt>

View File

@@ -145,7 +145,7 @@ export function BackupAdminPanel() {
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span <span
className={`text-[10px] uppercase font-medium px-1.5 py-0.5 rounded-full ${STATUS_TONE[j.status]}`} className={`text-xs uppercase font-medium px-1.5 py-0.5 rounded-full ${STATUS_TONE[j.status]}`}
> >
{j.status} {j.status}
</span> </span>
@@ -153,7 +153,7 @@ export function BackupAdminPanel() {
{new Date(j.startedAt).toLocaleString()} {new Date(j.startedAt).toLocaleString()}
</span> </span>
{j.trigger === 'cron' && ( {j.trigger === 'cron' && (
<span className="text-[10px] text-muted-foreground">cron</span> <span className="text-xs text-muted-foreground">cron</span>
)} )}
</div> </div>
{j.errorMessage && ( {j.errorMessage && (

View File

@@ -255,7 +255,7 @@ export function BulkAddBerthsWizard() {
className="h-9 w-20 font-mono" className="h-9 w-20 font-mono"
/> />
</div> </div>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Any uppercase letter sequence. Common ports use A-E; mark a custom letter when Any uppercase letter sequence. Common ports use A-E; mark a custom letter when
expanding to F+ or letter-pairs like AA. expanding to F+ or letter-pairs like AA.
</p> </p>
@@ -388,10 +388,12 @@ export function BulkAddBerthsWizard() {
<th scope="col" className="py-2 pr-2"> <th scope="col" className="py-2 pr-2">
Currency Currency
</th> </th>
<th scope="col" className="py-2 pr-2" /> <th scope="col" className="py-2 pr-2">
<span className="sr-only">Actions</span>
</th>
</tr> </tr>
<tr className="border-b bg-muted/30"> <tr className="border-b bg-muted/30">
<td className="py-1 pr-2 text-[10px] text-muted-foreground">apply to all </td> <td className="py-1 pr-2 text-xs text-muted-foreground">apply to all </td>
{( {(
[ [
['lengthFt', 'number'], ['lengthFt', 'number'],
@@ -444,7 +446,9 @@ export function BulkAddBerthsWizard() {
className="h-7 text-xs" className="h-7 text-xs"
/> />
</td> </td>
<td /> <td>
<span className="sr-only">Actions placeholder</span>
</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -460,7 +464,7 @@ export function BulkAddBerthsWizard() {
<td className="py-1 pr-2 font-medium"> <td className="py-1 pr-2 font-medium">
{row.mooringNumber} {row.mooringNumber}
{isDup ? ( {isDup ? (
<span className="ml-1 inline-flex items-center rounded-sm bg-amber-200/70 px-1 text-[10px] font-semibold uppercase tracking-wide text-amber-900"> <span className="ml-1 inline-flex items-center rounded-sm bg-amber-200/70 px-1 text-xs font-semibold uppercase tracking-wide text-amber-900">
Dup Dup
</span> </span>
) : null} ) : null}

View File

@@ -167,7 +167,7 @@ export function EmbeddedSigningCard() {
))} ))}
</ul> </ul>
) : null} ) : null}
<p className="mt-1 text-[11px] opacity-70">{result.at.toLocaleTimeString()}</p> <p className="mt-1 text-xs opacity-70">{result.at.toLocaleTimeString()}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -204,7 +204,7 @@ export function EmbeddedSigningCard() {
tracking which slot the signer is in. tracking which slot the signer is in.
</p> </p>
<p className="mt-1 text-muted-foreground">Minimum Next.js example:</p> <p className="mt-1 text-muted-foreground">Minimum Next.js example:</p>
<pre className="mt-1 overflow-x-auto rounded bg-muted p-2 font-mono text-[11px]"> <pre className="mt-1 overflow-x-auto rounded bg-muted p-2 font-mono text-xs">
{`// app/sign/[role]/[token]/page.tsx {`// app/sign/[role]/[token]/page.tsx
export default function SignPage({ params }) { export default function SignPage({ params }) {
const documenseUrl = \`\${env.DOCUMENSO_URL}/sign/\${params.token}\`; const documenseUrl = \`\${env.DOCUMENSO_URL}/sign/\${params.token}\`;

View File

@@ -179,7 +179,7 @@ export function TemplateSyncButton() {
<CheckCircle2 className="size-4" />{' '} <CheckCircle2 className="size-4" />{' '}
{lastResult.title || `Template #${lastResult.templateId}`} {lastResult.title || `Template #${lastResult.templateId}`}
</div> </div>
<span className="text-[11px] font-normal text-muted-foreground"> <span className="text-xs font-normal text-muted-foreground">
Last synced {formatRelative(lastResult.syncedAt)} Last synced {formatRelative(lastResult.syncedAt)}
</span> </span>
</div> </div>
@@ -202,11 +202,11 @@ export function TemplateSyncButton() {
</> </>
)} )}
{r.mappedSettingKey ? ( {r.mappedSettingKey ? (
<span className="ml-auto rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] font-medium text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300"> <span className="ml-auto rounded bg-emerald-100 px-1.5 py-0.5 text-xs font-medium text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300">
{r.mappedSettingKey} {r.mappedSettingKey}
</span> </span>
) : ( ) : (
<span className="ml-auto rounded bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-800 dark:bg-amber-950 dark:text-amber-300"> <span className="ml-auto rounded bg-amber-100 px-1.5 py-0.5 text-xs font-medium text-amber-800 dark:bg-amber-950 dark:text-amber-300">
no slot match no slot match
</span> </span>
)} )}
@@ -216,13 +216,13 @@ export function TemplateSyncButton() {
{lastResult.templateMeta && ( {lastResult.templateMeta && (
<div className="pt-1.5 rounded-md bg-muted/60 px-2 py-1.5"> <div className="pt-1.5 rounded-md bg-muted/60 px-2 py-1.5">
<div className="font-medium text-muted-foreground">Template-level settings</div> <div className="font-medium text-muted-foreground">Template-level settings</div>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Read from the template itself on Documenso. These values are bound to the Read from the template itself on Documenso. These values are bound to the
template, so every envelope generated from it inherits them -{' '} template, so every envelope generated from it inherits them -{' '}
<code>/template/use</code> does <strong>not</strong> accept overrides for these. <code>/template/use</code> does <strong>not</strong> accept overrides for these.
Change them in Documenso&apos;s template editor. Change them in Documenso&apos;s template editor.
</p> </p>
<ul className="mt-1 space-y-0.5 text-[11px]"> <ul className="mt-1 space-y-0.5 text-xs">
<li> <li>
<span className="text-muted-foreground">Signing order:</span>{' '} <span className="text-muted-foreground">Signing order:</span>{' '}
<span className="font-mono"> <span className="font-mono">
@@ -235,7 +235,7 @@ export function TemplateSyncButton() {
{lastResult.templateMeta.distributionMethod ?? 'unset'} {lastResult.templateMeta.distributionMethod ?? 'unset'}
</span> </span>
{lastResult.templateMeta.distributionMethod === 'EMAIL' && ( {lastResult.templateMeta.distributionMethod === 'EMAIL' && (
<span className="ml-1 rounded bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-900 dark:bg-amber-950 dark:text-amber-200"> <span className="ml-1 rounded bg-amber-100 px-1.5 py-0.5 text-xs font-medium text-amber-900 dark:bg-amber-950 dark:text-amber-200">
Documenso will email recipients directly - the CRM&apos;s branded email Documenso will email recipients directly - the CRM&apos;s branded email
is in addition. Set to NONE on the template to let the CRM be the sole is in addition. Set to NONE on the template to let the CRM be the sole
sender. sender.
@@ -273,7 +273,7 @@ export function TemplateSyncButton() {
{lastResult.matchedFields.map((f) => ( {lastResult.matchedFields.map((f) => (
<span <span
key={f.fieldId} key={f.fieldId}
className="rounded bg-emerald-100 px-1.5 py-0.5 font-mono text-[10px] text-emerald-900 dark:bg-emerald-950 dark:text-emerald-200" className="rounded bg-emerald-100 px-1.5 py-0.5 font-mono text-xs text-emerald-900 dark:bg-emerald-950 dark:text-emerald-200"
> >
{f.label} #{f.fieldId} {f.label} #{f.fieldId}
</span> </span>
@@ -288,7 +288,7 @@ export function TemplateSyncButton() {
Template fields the CRM doesn&apos;t recognize ( Template fields the CRM doesn&apos;t recognize (
{lastResult.unmatchedTemplateFields.length}) {lastResult.unmatchedTemplateFields.length})
</div> </div>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
These won&apos;t be filled. Rename them in the Documenso template editor to match These won&apos;t be filled. Rename them in the Documenso template editor to match
a CRM-expected label (Name, Email, Address, Yacht Name, Length, Width, Draft, a CRM-expected label (Name, Email, Address, Yacht Name, Length, Width, Draft,
Berth Number, Lease_10, Purchase), or ignore if they&apos;re signature/date fields Berth Number, Lease_10, Purchase), or ignore if they&apos;re signature/date fields
@@ -298,7 +298,7 @@ export function TemplateSyncButton() {
{lastResult.unmatchedTemplateFields.map((f) => ( {lastResult.unmatchedTemplateFields.map((f) => (
<span <span
key={f.fieldId} key={f.fieldId}
className="rounded bg-amber-100 px-1.5 py-0.5 font-mono text-[10px] text-amber-900 dark:bg-amber-950 dark:text-amber-200" className="rounded bg-amber-100 px-1.5 py-0.5 font-mono text-xs text-amber-900 dark:bg-amber-950 dark:text-amber-200"
> >
{f.label} #{f.fieldId} {f.label} #{f.fieldId}
</span> </span>
@@ -312,7 +312,7 @@ export function TemplateSyncButton() {
<div className="font-medium text-foreground"> <div className="font-medium text-foreground">
PDF AcroForm fields (the <code>formValues</code> path) PDF AcroForm fields (the <code>formValues</code> path)
</div> </div>
<p className="pt-0.5 text-[11px] text-muted-foreground"> <p className="pt-0.5 text-xs text-muted-foreground">
These are the fillable fields actually in the PDF binary on Documenso. The CRM These are the fillable fields actually in the PDF binary on Documenso. The CRM
fills them by name at send time - this is the same mechanism the prod v1 server fills them by name at send time - this is the same mechanism the prod v1 server
uses. uses.
@@ -320,11 +320,11 @@ export function TemplateSyncButton() {
{lastResult.acroForm.map((report) => ( {lastResult.acroForm.map((report) => (
<div key={report.envelopeItemId} className="mt-1.5 space-y-1"> <div key={report.envelopeItemId} className="mt-1.5 space-y-1">
{report.error ? ( {report.error ? (
<div className="rounded bg-destructive/10 px-2 py-1 text-[11px] text-destructive"> <div className="rounded bg-destructive/10 px-2 py-1 text-xs text-destructive">
Couldn&apos;t inspect this PDF: {report.error} Couldn&apos;t inspect this PDF: {report.error}
</div> </div>
) : report.fields.length === 0 ? ( ) : report.fields.length === 0 ? (
<div className="rounded bg-amber-100 px-2 py-1 text-[11px] text-amber-900 dark:bg-amber-950 dark:text-amber-200"> <div className="rounded bg-amber-100 px-2 py-1 text-xs text-amber-900 dark:bg-amber-950 dark:text-amber-200">
This PDF has no AcroForm fields. The CRM&apos;s <code>formValues</code>{' '} This PDF has no AcroForm fields. The CRM&apos;s <code>formValues</code>{' '}
path will fill nothing. Re-export your PDF with form fields enabled, or path will fill nothing. Re-export your PDF with form fields enabled, or
place overlays inside Documenso&apos;s editor and use{' '} place overlays inside Documenso&apos;s editor and use{' '}
@@ -341,7 +341,7 @@ export function TemplateSyncButton() {
{report.matchedFieldNames.map((n) => ( {report.matchedFieldNames.map((n) => (
<span <span
key={n} key={n}
className="rounded bg-emerald-100 px-1.5 py-0.5 font-mono text-[10px] text-emerald-900 dark:bg-emerald-950 dark:text-emerald-200" className="rounded bg-emerald-100 px-1.5 py-0.5 font-mono text-xs text-emerald-900 dark:bg-emerald-950 dark:text-emerald-200"
> >
{n} {n}
</span> </span>
@@ -354,7 +354,7 @@ export function TemplateSyncButton() {
<div className="font-medium text-amber-700 dark:text-amber-400"> <div className="font-medium text-amber-700 dark:text-amber-400">
CRM tokens missing from the PDF ({report.missingFieldNames.length}) CRM tokens missing from the PDF ({report.missingFieldNames.length})
</div> </div>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
These exact names need AcroForm text/checkbox fields in the PDF, or These exact names need AcroForm text/checkbox fields in the PDF, or
they&apos;ll be dropped at send time. they&apos;ll be dropped at send time.
</p> </p>
@@ -362,7 +362,7 @@ export function TemplateSyncButton() {
{report.missingFieldNames.map((n) => ( {report.missingFieldNames.map((n) => (
<span <span
key={n} key={n}
className="rounded bg-amber-100 px-1.5 py-0.5 font-mono text-[10px] text-amber-900 dark:bg-amber-950 dark:text-amber-200" className="rounded bg-amber-100 px-1.5 py-0.5 font-mono text-xs text-amber-900 dark:bg-amber-950 dark:text-amber-200"
> >
{n} {n}
</span> </span>
@@ -375,7 +375,7 @@ export function TemplateSyncButton() {
<div className="font-medium text-muted-foreground"> <div className="font-medium text-muted-foreground">
PDF fields the CRM has no token for ({report.extraFieldNames.length}) PDF fields the CRM has no token for ({report.extraFieldNames.length})
</div> </div>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Usually signature blocks or other fields the recipient fills in Usually signature blocks or other fields the recipient fills in
directly. Safe to ignore. directly. Safe to ignore.
</p> </p>
@@ -383,7 +383,7 @@ export function TemplateSyncButton() {
{report.extraFieldNames.map((n) => ( {report.extraFieldNames.map((n) => (
<span <span
key={n} key={n}
className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground" className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-muted-foreground"
> >
{n} {n}
</span> </span>
@@ -404,7 +404,7 @@ export function TemplateSyncButton() {
CRM data points not in <code>prefillFields</code> ( CRM data points not in <code>prefillFields</code> (
{lastResult.missingFromTemplate.length}) {lastResult.missingFromTemplate.length})
</div> </div>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
These would also be available as <code>prefillFields</code> if you added matching These would also be available as <code>prefillFields</code> if you added matching
overlays inside Documenso&apos;s template editor. overlays inside Documenso&apos;s template editor.
</p> </p>
@@ -412,7 +412,7 @@ export function TemplateSyncButton() {
{lastResult.missingFromTemplate.map((label) => ( {lastResult.missingFromTemplate.map((label) => (
<span <span
key={label} key={label}
className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground" className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs text-muted-foreground"
> >
{label} {label}
</span> </span>

View File

@@ -112,7 +112,7 @@ export function TemplateForm({ open, onOpenChange, template, onSuccess }: Templa
return ( return (
<Sheet open={open} onOpenChange={onOpenChange}> <Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="w-full max-w-2xl overflow-y-auto sm:max-w-2xl"> <SheetContent className="w-full max-w-2xl overflow-y-auto sm:max-w-2xl lg:max-w-4xl">
<SheetHeader> <SheetHeader>
<SheetTitle>{isEdit ? 'Edit Template' : 'New Document Template'}</SheetTitle> <SheetTitle>{isEdit ? 'Edit Template' : 'New Document Template'}</SheetTitle>
</SheetHeader> </SheetHeader>

View File

@@ -112,7 +112,7 @@ export function TemplateVersionHistory({
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Saved{' '} Saved{' '}
{new Date(v.changedAt).toLocaleString('en-GB', { {new Date(v.changedAt).toLocaleString(undefined, {
day: '2-digit', day: '2-digit',
month: 'short', month: 'short',
year: 'numeric', year: 'numeric',

View File

@@ -123,7 +123,7 @@ function CandidateRow({
<div className="rounded-lg border bg-card p-4"> <div className="rounded-lg border bg-card p-4">
<div className="mb-3 flex items-baseline justify-between gap-3"> <div className="mb-3 flex items-baseline justify-between gap-3">
<div> <div>
<span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground"> <span className="rounded-full bg-muted px-2 py-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
score {pair.score} score {pair.score}
</span>{' '} </span>{' '}
<span className="text-xs text-muted-foreground">{pair.reasons.join(' · ')}</span> <span className="text-xs text-muted-foreground">{pair.reasons.join(' · ')}</span>
@@ -203,11 +203,11 @@ function ClientCard({
)} )}
> >
<p className="text-sm font-medium">{client.fullName}</p> <p className="text-sm font-medium">{client.fullName}</p>
<p className="mt-0.5 text-[11px] text-muted-foreground"> <p className="mt-0.5 text-xs text-muted-foreground">
Created {new Date(client.createdAt).toLocaleDateString()} Created {new Date(client.createdAt).toLocaleDateString()}
</p> </p>
{isSelected ? ( {isSelected ? (
<span className="mt-1 inline-block rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-semibold text-primary"> <span className="mt-1 inline-block rounded-full bg-primary/10 px-1.5 py-0.5 text-xs font-semibold text-primary">
KEEP KEEP
</span> </span>
) : null} ) : null}

View File

@@ -158,7 +158,7 @@ function FormTemplateFormBody({ open, onOpenChange, template, onSaved }: Props)
return ( return (
<Sheet open={open} onOpenChange={onOpenChange}> <Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="overflow-y-auto sm:max-w-2xl"> <SheetContent className="overflow-y-auto sm:max-w-2xl lg:max-w-4xl">
<SheetHeader> <SheetHeader>
<SheetTitle>{template ? 'Edit form template' : 'New form template'}</SheetTitle> <SheetTitle>{template ? 'Edit form template' : 'New form template'}</SheetTitle>
</SheetHeader> </SheetHeader>
@@ -230,7 +230,7 @@ function FormTemplateFormBody({ open, onOpenChange, template, onSaved }: Props)
</SelectContent> </SelectContent>
</Select> </Select>
{f.bindTo ? ( {f.bindTo ? (
<Badge variant="secondary" className="text-[10px] font-normal"> <Badge variant="secondary" className="text-xs font-normal">
Autofills from + writes back to {getBindableField(f.bindTo)?.label} ·{' '} Autofills from + writes back to {getBindableField(f.bindTo)?.label} ·{' '}
{f.bindTo} {f.bindTo}
</Badge> </Badge>

View File

@@ -111,7 +111,9 @@ export function InvitationsManager() {
<th className="text-left font-medium px-3 py-2">Role</th> <th className="text-left font-medium px-3 py-2">Role</th>
<th className="text-left font-medium px-3 py-2">Status</th> <th className="text-left font-medium px-3 py-2">Status</th>
<th className="text-left font-medium px-3 py-2">Expires</th> <th className="text-left font-medium px-3 py-2">Expires</th>
<th className="px-3 py-2"></th> <th className="px-3 py-2">
<span className="sr-only">Actions</span>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -326,15 +326,15 @@ export function OnboardingChecklist() {
</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 && (
<p className="mt-1 text-[11px] text-emerald-700"> <p className="mt-1 text-xs text-emerald-700">
Auto-detected complete via{' '} Auto-detected complete via{' '}
<code className="text-[10px]"> <code className="text-xs">
{step.autoCheckSettingKey ?? {step.autoCheckSettingKey ??
step.autoCheckSettingKeysAll?.join(' + ') ?? step.autoCheckSettingKeysAll?.join(' + ') ??
step.autoCheckListEndpoint} step.autoCheckListEndpoint}
</code> </code>
{autoSources[step.id] && autoSources[step.id] !== 'port' ? ( {autoSources[step.id] && autoSources[step.id] !== 'port' ? (
<span className="ml-1 text-[10px] text-amber-700"> <span className="ml-1 text-xs text-amber-700">
· resolving from{' '} · resolving from{' '}
<strong className="font-medium"> <strong className="font-medium">
{autoSources[step.id] === 'env' {autoSources[step.id] === 'env'

View File

@@ -265,7 +265,7 @@ function CriterionEditableRow({
onChange={(e) => setLabel(e.target.value)} onChange={(e) => setLabel(e.target.value)}
className="h-7 max-w-md text-sm font-medium" className="h-7 max-w-md text-sm font-medium"
/> />
<code className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground"> <code className="rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
{criterion.key} {criterion.key}
</code> </code>
<div className="ml-auto flex items-center gap-2"> <div className="ml-auto flex items-center gap-2">
@@ -363,7 +363,7 @@ function CreateCriterionDialog({
placeholder="e.g. budget_confirmed" placeholder="e.g. budget_confirmed"
/> />
{key && !/^[a-z][a-z0-9_]*$/.test(key) ? ( {key && !/^[a-z][a-z0-9_]*$/.test(key) ? (
<p className="text-[11px] text-rose-700"> <p className="text-xs text-rose-700">
Must start with a letter; lowercase alphanumeric and underscores only. Must start with a letter; lowercase alphanumeric and underscores only.
</p> </p>
) : null} ) : null}

View File

@@ -26,9 +26,7 @@ function StatPill({ label, value, highlight }: StatPillProps) {
> >
{value} {value}
</span> </span>
<span className="text-[10px] text-muted-foreground mt-0.5 uppercase tracking-wide"> <span className="text-xs text-muted-foreground mt-0.5 uppercase tracking-wide">{label}</span>
{label}
</span>
</div> </div>
); );
} }
@@ -65,7 +63,7 @@ export function QueueOverview({ queues }: QueueOverviewProps) {
<StatPill label="failed" value={queue.failed} highlight /> <StatPill label="failed" value={queue.failed} highlight />
</div> </div>
{queue.delayed > 0 && ( {queue.delayed > 0 && (
<p className="text-[10px] text-muted-foreground text-center mt-1"> <p className="text-xs text-muted-foreground text-center mt-1">
{queue.delayed} delayed {queue.delayed} delayed
</p> </p>
)} )}

View File

@@ -152,7 +152,7 @@ export function ResidentialStagesAdmin() {
<GripVertical className="h-4 w-4 text-muted-foreground" aria-hidden /> <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-xs uppercase tracking-wide text-muted-foreground">
Id Id
</Label> </Label>
<Input <Input
@@ -162,7 +162,7 @@ export function ResidentialStagesAdmin() {
/> />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<Label className="text-[11px] uppercase tracking-wide text-muted-foreground"> <Label className="text-xs uppercase tracking-wide text-muted-foreground">
Label Label
</Label> </Label>
<Input <Input
@@ -172,7 +172,7 @@ export function ResidentialStagesAdmin() {
/> />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<Label className="text-[11px] uppercase tracking-wide text-muted-foreground"> <Label className="text-xs uppercase tracking-wide text-muted-foreground">
Terminal Terminal
</Label> </Label>
<Select <Select

View File

@@ -282,8 +282,8 @@ export function RoleList() {
key={action} key={action}
className={ className={
allowed allowed
? 'inline-flex items-center rounded-full bg-emerald-50 text-emerald-900 px-2 py-0.5 text-[11px] font-medium' ? 'inline-flex items-center rounded-full bg-emerald-50 text-emerald-900 px-2 py-0.5 text-xs font-medium'
: 'inline-flex items-center rounded-full bg-muted text-muted-foreground px-2 py-0.5 text-[11px] font-medium line-through opacity-60' : 'inline-flex items-center rounded-full bg-muted text-muted-foreground px-2 py-0.5 text-xs font-medium line-through opacity-60'
} }
> >
{action.replace(/_/g, ' ')} {action.replace(/_/g, ' ')}

View File

@@ -435,7 +435,7 @@ export function SalesEmailConfigCard() {
<div className="flex-1"> <div className="flex-1">
<p className="font-medium">{lastTest.ok ? 'SMTP test sent' : 'SMTP test failed'}</p> <p className="font-medium">{lastTest.ok ? 'SMTP test sent' : 'SMTP test failed'}</p>
<p className="text-xs">{lastTest.message}</p> <p className="text-xs">{lastTest.message}</p>
<p className="mt-0.5 text-[11px] opacity-70">{lastTest.at.toLocaleTimeString()}</p> <p className="mt-0.5 text-xs opacity-70">{lastTest.at.toLocaleTimeString()}</p>
</div> </div>
</div> </div>
) : null} ) : null}

View File

@@ -20,7 +20,7 @@ import {
} from '@/components/ui/select'; } from '@/components/ui/select';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { apiFetch } from '@/lib/api/client'; import { apiFetch } from '@/lib/api/client';
import { SUPPORTED_CURRENCIES } from '@/lib/utils/currency'; import { SUPPORTED_CURRENCIES, currencyLabel } from '@/lib/utils/currency';
interface Setting { interface Setting {
key: string; key: string;
@@ -234,7 +234,7 @@ const KNOWN_SETTINGS: Array<{
defaultValue: 'USD', defaultValue: 'USD',
options: SUPPORTED_CURRENCIES.map((c) => ({ options: SUPPORTED_CURRENCIES.map((c) => ({
value: c.code, value: c.code,
label: `${c.code} - ${c.label}`, label: `${c.code} - ${currencyLabel(c.code)}`,
})), })),
}, },
]; ];

View File

@@ -394,7 +394,11 @@ function SettingField({
)} )}
</div> </div>
</div> </div>
{entry.description && <p className="text-xs text-muted-foreground">{entry.description}</p>} {entry.description && (
<p id={`${entry.key}-description`} className="text-xs text-muted-foreground">
{entry.description}
</p>
)}
<FieldInput <FieldInput
entry={entry} entry={entry}

View File

@@ -450,7 +450,7 @@ function ImageUploadField({
className="h-full w-full object-contain" className="h-full w-full object-contain"
/> />
) : ( ) : (
<span className="text-[10px] text-muted-foreground">No image</span> <span className="text-xs text-muted-foreground">No image</span>
)} )}
</div> </div>
<div className="flex-1 space-y-2"> <div className="flex-1 space-y-2">
@@ -476,7 +476,7 @@ function ImageUploadField({
placeholder="https://example.com/logo.png" placeholder="https://example.com/logo.png"
className="text-xs" className="text-xs"
/> />
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Upload to use the platform&apos;s storage backend, or paste an external URL. Upload to use the platform&apos;s storage backend, or paste an external URL.
</p> </p>
</div> </div>

View File

@@ -58,13 +58,13 @@ export function TemplateTokenPicker() {
<div className="space-y-4"> <div className="space-y-4">
{Object.entries(MERGE_FIELDS).map(([scope, fields]) => ( {Object.entries(MERGE_FIELDS).map(([scope, fields]) => (
<div key={scope}> <div key={scope}>
<p className="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{SCOPE_LABELS[scope] ?? scope} {SCOPE_LABELS[scope] ?? scope}
</p> </p>
<div className="grid grid-cols-1 gap-x-3 gap-y-0.5 text-[11px] sm:grid-cols-2"> <div className="grid grid-cols-1 gap-x-3 gap-y-0.5 text-xs sm:grid-cols-2">
{fields.map((f) => ( {fields.map((f) => (
<div key={f.token} className="flex items-baseline gap-1.5"> <div key={f.token} className="flex items-baseline gap-1.5">
<code className="rounded bg-muted px-1 font-mono text-[11px]">{f.token}</code> <code className="rounded bg-muted px-1 font-mono text-xs">{f.token}</code>
<span className="truncate text-muted-foreground">{f.label}</span> <span className="truncate text-muted-foreground">{f.label}</span>
</div> </div>
))} ))}
@@ -74,10 +74,10 @@ export function TemplateTokenPicker() {
{customFields.length > 0 ? ( {customFields.length > 0 ? (
<div> <div>
<p className="mb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="mb-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Custom (port-specific) Custom (port-specific)
</p> </p>
<div className="grid grid-cols-1 gap-x-3 gap-y-0.5 text-[11px] sm:grid-cols-2"> <div className="grid grid-cols-1 gap-x-3 gap-y-0.5 text-xs sm:grid-cols-2">
{customFields {customFields
.slice() .slice()
.sort((a, b) => .sort((a, b) =>
@@ -87,7 +87,7 @@ export function TemplateTokenPicker() {
) )
.map((f) => ( .map((f) => (
<div key={f.id} className="flex items-baseline gap-1.5"> <div key={f.id} className="flex items-baseline gap-1.5">
<code className="rounded bg-muted px-1 font-mono text-[11px]"> <code className="rounded bg-muted px-1 font-mono text-xs">
{`{{custom.${f.fieldName}}}`} {`{{custom.${f.fieldName}}}`}
</code> </code>
<span className="truncate text-muted-foreground"> <span className="truncate text-muted-foreground">

View File

@@ -801,7 +801,7 @@ function MarkerOverlay({
width: `${(marker.w ?? DEFAULT_MARKER_W) * 100}%`, width: `${(marker.w ?? DEFAULT_MARKER_W) * 100}%`,
height: `${(marker.h ?? DEFAULT_MARKER_H) * 100}%`, height: `${(marker.h ?? DEFAULT_MARKER_H) * 100}%`,
}} }}
className="cursor-move rounded border-2 border-primary/70 bg-primary/15 px-1 py-0.5 text-[10px] font-medium text-primary" className="cursor-move rounded border-2 border-primary/70 bg-primary/15 px-1 py-0.5 text-xs font-medium text-primary"
onMouseDown={onMouseDown} onMouseDown={onMouseDown}
onContextMenu={onContextMenu} onContextMenu={onContextMenu}
> >

View File

@@ -258,7 +258,7 @@ export function UserPermissionMatrix({ userId }: UserPermissionMatrixProps) {
> >
<div className="text-sm"> <div className="text-sm">
<Label className="text-sm">{formatAction(action)}</Label> <Label className="text-sm">{formatAction(action)}</Label>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Inherits: {inherited ? 'granted' : 'denied'} Inherits: {inherited ? 'granted' : 'denied'}
</p> </p>
</div> </div>

View File

@@ -43,7 +43,7 @@ export function AlertBell() {
key={total} key={total}
data-testid="alert-bell-badge" data-testid="alert-bell-badge"
className={cn( className={cn(
'absolute -top-0.5 -right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-bold text-white shadow-sm ring-2 ring-background animate-badge-pop', 'absolute -top-0.5 -right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-xs font-bold text-white shadow-sm ring-2 ring-background animate-badge-pop',
critical > 0 ? 'bg-destructive' : 'bg-amber-500', critical > 0 ? 'bg-destructive' : 'bg-amber-500',
)} )}
> >

View File

@@ -51,16 +51,16 @@ export function AlertCard({ alert, readOnly = false }: AlertCardProps) {
<div className="flex items-baseline gap-2"> <div className="flex items-baseline gap-2">
<p className="truncate text-sm font-medium text-foreground">{alert.title}</p> <p className="truncate text-sm font-medium text-foreground">{alert.title}</p>
{acknowledged ? ( {acknowledged ? (
<span className="text-[10px] uppercase tracking-wide text-muted-foreground">ack</span> <span className="text-xs uppercase tracking-wide text-muted-foreground">ack</span>
) : null} ) : null}
</div> </div>
{alert.body ? ( {alert.body ? (
<p className="mt-0.5 line-clamp-2 text-xs text-muted-foreground">{alert.body}</p> <p className="mt-0.5 line-clamp-2 text-xs text-muted-foreground">{alert.body}</p>
) : null} ) : null}
<div className="mt-1 flex items-center gap-2 text-[11px] text-muted-foreground"> <div className="mt-1 flex items-center gap-2 text-xs text-muted-foreground">
<span>{fired}</span> <span>{fired}</span>
<span aria-hidden>·</span> <span aria-hidden>·</span>
<span className="font-mono text-[10px]">{alert.ruleId}</span> <span className="font-mono text-xs">{alert.ruleId}</span>
</div> </div>
</div> </div>
{!readOnly ? ( {!readOnly ? (

View File

@@ -84,7 +84,7 @@ export function ActiveInterestsPopover({ berthId, portSlug, count }: Props) {
<span className="truncate">{row.clientName}</span> <span className="truncate">{row.clientName}</span>
</span> </span>
<span <span
className={`shrink-0 inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${stageBadgeClass( className={`shrink-0 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${stageBadgeClass(
row.pipelineStage, row.pipelineStage,
)}`} )}`}
> >

View File

@@ -158,7 +158,7 @@ function StatusBadge({ status }: { status: string }) {
function ManualBadge() { function ManualBadge() {
return ( return (
<span <span
className="inline-flex items-center rounded-full border border-amber-300 bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-amber-800" className="inline-flex items-center rounded-full border border-amber-300 bg-amber-50 px-1.5 py-0.5 text-xs font-medium uppercase tracking-wide text-amber-800"
title="Status set manually with no backing interest - needs catch-up" title="Status set manually with no backing interest - needs catch-up"
> >
Manual Manual

View File

@@ -420,7 +420,7 @@ function InterestLinkPicker({
<span className="flex-1 truncate">{opt.clientName || '(unnamed)'}</span> <span className="flex-1 truncate">{opt.clientName || '(unnamed)'}</span>
<span <span
className={cn( className={cn(
'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-[10px] font-medium', 'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-xs font-medium',
stageBadgeClass(opt.pipelineStage), stageBadgeClass(opt.pipelineStage),
)} )}
> >

View File

@@ -101,7 +101,7 @@ export function BerthInterestPulse({ berthId }: { berthId: string }) {
<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" aria-hidden /> <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-xs font-medium text-muted-foreground">
{active.length} {active.length}
</span> </span>
</CardTitle> </CardTitle>
@@ -136,7 +136,7 @@ export function BerthInterestPulse({ berthId }: { berthId: string }) {
</span> </span>
<span <span
className={cn( className={cn(
'inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium', 'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium',
stageBadgeClass(i.pipelineStage), stageBadgeClass(i.pipelineStage),
)} )}
> >
@@ -147,7 +147,7 @@ export function BerthInterestPulse({ berthId }: { berthId: string }) {
key={b.id} key={b.id}
title={b.detail} title={b.detail}
className={cn( className={cn(
'inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium', 'inline-flex items-center rounded-full px-1.5 py-0.5 text-xs font-medium',
b.className, b.className,
)} )}
> >
@@ -156,7 +156,7 @@ export function BerthInterestPulse({ berthId }: { berthId: string }) {
))} ))}
</div> </div>
{lastActivity ? ( {lastActivity ? (
<p className="text-[11px] tabular-nums text-muted-foreground"> <p className="text-xs tabular-nums text-muted-foreground">
Last activity {lastActivity} Last activity {lastActivity}
</p> </p>
) : null} ) : null}

View File

@@ -161,7 +161,9 @@ export function BerthInterestsTab({ berthId }: BerthInterestsTabProps) {
<th scope="col" className="px-3 py-2"> <th scope="col" className="px-3 py-2">
Last activity Last activity
</th> </th>
<th scope="col" className="px-3 py-2 text-right" /> <th scope="col" className="px-3 py-2 text-right">
<span className="sr-only">Actions</span>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -193,9 +193,7 @@ function BulkHardDeleteDialogBody({ onOpenChange, clientIds, onDeleted }: Props)
<tbody> <tbody>
{skipped.map((row) => ( {skipped.map((row) => (
<tr key={row.clientId} className="border-t"> <tr key={row.clientId} className="border-t">
<td className="px-2 py-1.5 font-mono text-[11px]"> <td className="px-2 py-1.5 font-mono text-xs">{row.clientId.slice(0, 8)}</td>
{row.clientId.slice(0, 8)}
</td>
<td className="px-2 py-1.5">{row.reason}</td> <td className="px-2 py-1.5">{row.reason}</td>
</tr> </tr>
))} ))}

View File

@@ -123,7 +123,7 @@ export function ClientCard({ client, portSlug, onEdit, onArchive }: ClientCardPr
<span className="truncate">{interestBerthLabel}</span> <span className="truncate">{interestBerthLabel}</span>
<span aria-hidden>·</span> <span aria-hidden>·</span>
<span <span
className={`shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${interestStageBadge}`} className={`shrink-0 rounded-full px-1.5 py-0.5 text-xs font-medium ${interestStageBadge}`}
> >
{interestStageLabel} {interestStageLabel}
</span> </span>

View File

@@ -251,7 +251,7 @@ function ContactRowItem({
)} )}
<span className="truncate text-sm">{displayValue}</span> <span className="truncate text-sm">{displayValue}</span>
{row.label ? ( {row.label ? (
<span className="shrink-0 rounded-sm bg-muted px-1 text-[10px] text-muted-foreground"> <span className="shrink-0 rounded-sm bg-muted px-1 text-xs text-muted-foreground">
{row.label} {row.label}
</span> </span>
) : null} ) : null}
@@ -265,7 +265,7 @@ function ContactRowItem({
onClick={() => promote.mutate()} onClick={() => promote.mutate()}
disabled={promote.isPending} disabled={promote.isPending}
title="Set as primary" title="Set as primary"
className={cn('h-6 px-1.5 text-[11px]')} className={cn('h-6 px-1.5 text-xs')}
> >
Make primary Make primary
</Button> </Button>
@@ -341,7 +341,7 @@ function AddContactRow({
return ( return (
<div className="mt-1 space-y-2 rounded-md border bg-muted/30 p-2"> <div className="mt-1 space-y-2 rounded-md border bg-muted/30 p-2">
<div className="space-y-1"> <div className="space-y-1">
<Label htmlFor={`new-${channel}`} className="text-[11px] uppercase tracking-wide"> <Label htmlFor={`new-${channel}`} className="text-xs uppercase tracking-wide">
New {channel} New {channel}
</Label> </Label>
<Input <Input

View File

@@ -234,7 +234,7 @@ export function getClientColumns({
className="w-64 p-1" className="w-64 p-1"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<div className="px-2 py-1.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <div className="px-2 py-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
All linked berths All linked berths
</div> </div>
<div className="space-y-0.5"> <div className="space-y-0.5">

View File

@@ -300,7 +300,7 @@ export function ClientForm({
return ( return (
<Sheet open={open} onOpenChange={onOpenChange}> <Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="w-full sm:max-w-2xl overflow-y-auto"> <SheetContent className="w-full sm:max-w-2xl lg:max-w-4xl overflow-y-auto">
<SheetHeader> <SheetHeader>
<SheetTitle>{isEdit ? 'Edit Client' : 'New Client'}</SheetTitle> <SheetTitle>{isEdit ? 'Edit Client' : 'New Client'}</SheetTitle>
</SheetHeader> </SheetHeader>

View File

@@ -67,7 +67,7 @@ function InterestRowItem({
</h3> </h3>
<span <span
className={cn( className={cn(
'shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium', 'shrink-0 rounded-full px-2 py-0.5 text-xs font-medium',
STAGE_BADGE[stage], STAGE_BADGE[stage],
)} )}
> >

View File

@@ -209,7 +209,7 @@ function HeroVariant({ clientId, portSlug }: { clientId: string; portSlug: strin
<span className="truncate text-sm font-semibold text-foreground">{berthLabel}</span> <span className="truncate text-sm font-semibold text-foreground">{berthLabel}</span>
<span <span
className={cn( className={cn(
'shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium', 'shrink-0 rounded-full px-2 py-0.5 text-xs font-medium',
STAGE_BADGE[stage], STAGE_BADGE[stage],
)} )}
> >
@@ -282,7 +282,7 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<span className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <span className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Sales pipeline · {interests.length} active Sales pipeline · {interests.length} active
</span> </span>
<Link <Link
@@ -313,7 +313,7 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
</span> </span>
<span <span
className={cn( className={cn(
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium', 'shrink-0 rounded-full px-2 py-0.5 text-xs font-medium',
STAGE_BADGE[stage], STAGE_BADGE[stage],
)} )}
> >
@@ -331,7 +331,7 @@ function PanelVariant({ clientId, portSlug }: { clientId: string; portSlug: stri
if (dims.length > 0) summary.push(`Wants ${dims.join(' × ')}`); if (dims.length > 0) summary.push(`Wants ${dims.join(' × ')}`);
if (i.source) summary.push(i.source); if (i.source) summary.push(i.source);
return summary.length > 0 ? ( return summary.length > 0 ? (
<div className="mt-0.5 truncate text-[11px] text-muted-foreground"> <div className="mt-0.5 truncate text-xs text-muted-foreground">
{summary.join(' · ')} {summary.join(' · ')}
</div> </div>
) : null; ) : null;

View File

@@ -273,7 +273,7 @@ function ContactRow({
{contact.source === 'eoi-custom-input' && !contact.isPrimary ? ( {contact.source === 'eoi-custom-input' && !contact.isPrimary ? (
<span <span
className="inline-flex items-center rounded bg-amber-100 px-1 text-[10px] font-medium text-amber-800" className="inline-flex items-center rounded bg-amber-100 px-1 text-xs font-medium text-amber-800"
title={ title={
contact.sourceDocumentId contact.sourceDocumentId
? 'Spawned from an EOI - open the source document for details.' ? 'Spawned from an EOI - open the source document for details.'

View File

@@ -149,13 +149,13 @@ export function DedupSuggestionPanel({
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<p className="truncate text-sm font-medium">{top.fullName}</p> <p className="truncate text-sm font-medium">{top.fullName}</p>
{isArchived && ( {isArchived && (
<span className="shrink-0 rounded-full bg-slate-200 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-slate-800 dark:bg-slate-700 dark:text-slate-200"> <span className="shrink-0 rounded-full bg-slate-200 px-1.5 py-0.5 text-xs font-medium uppercase tracking-wide text-slate-800 dark:bg-slate-700 dark:text-slate-200">
archived archived
</span> </span>
)} )}
<span <span
className={cn( className={cn(
'shrink-0 rounded-full px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide', 'shrink-0 rounded-full px-1.5 py-0.5 text-xs font-medium uppercase tracking-wide',
isHigh isHigh
? 'bg-amber-200 text-amber-900 dark:bg-amber-800 dark:text-amber-100' ? 'bg-amber-200 text-amber-900 dark:bg-amber-800 dark:text-amber-100'
: 'bg-muted text-muted-foreground', : 'bg-muted text-muted-foreground',
@@ -177,7 +177,7 @@ export function DedupSuggestionPanel({
</span> </span>
)} )}
</div> </div>
<p className="mt-1.5 text-[11px] text-muted-foreground">{top.reasons.join(' · ')}</p> <p className="mt-1.5 text-xs text-muted-foreground">{top.reasons.join(' · ')}</p>
</div> </div>
<div className="mt-3 flex flex-wrap items-center gap-2"> <div className="mt-3 flex flex-wrap items-center gap-2">
{isArchived ? ( {isArchived ? (

View File

@@ -262,7 +262,7 @@ export function CompanyForm({ open, onOpenChange, company, prefill }: CompanyFor
return ( return (
<Sheet open={open} onOpenChange={onOpenChange}> <Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="w-full sm:max-w-2xl overflow-y-auto"> <SheetContent className="w-full sm:max-w-2xl lg:max-w-4xl overflow-y-auto">
<SheetHeader> <SheetHeader>
<SheetTitle>{isEdit ? 'Edit Company' : 'New Company'}</SheetTitle> <SheetTitle>{isEdit ? 'Edit Company' : 'New Company'}</SheetTitle>
</SheetHeader> </SheetHeader>
@@ -420,7 +420,7 @@ export function CompanyForm({ open, onOpenChange, company, prefill }: CompanyFor
selectedIds={attachedClientIds} selectedIds={attachedClientIds}
onChange={setAttachedClientIds} onChange={setAttachedClientIds}
/> />
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Each pick becomes a company member with role=member. You can refine roles Each pick becomes a company member with role=member. You can refine roles
afterwards on the Members tab. afterwards on the Members tab.
</p> </p>
@@ -434,7 +434,7 @@ export function CompanyForm({ open, onOpenChange, company, prefill }: CompanyFor
selectedIds={attachedYachtIds} selectedIds={attachedYachtIds}
onChange={setAttachedYachtIds} onChange={setAttachedYachtIds}
/> />
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Adding a yacht transfers its ownership to this company (logged in the Adding a yacht transfers its ownership to this company (logged in the
yacht&apos;s audit trail). Skip if you only want to associate without changing yacht&apos;s audit trail). Skip if you only want to associate without changing
ownership. ownership.

View File

@@ -318,7 +318,7 @@ function ActivityFeedInner() {
{diffLine} {diffLine}
</p> </p>
) : null} ) : null}
<p className="text-[11px] text-muted-foreground/80 mt-0.5"> <p className="text-xs text-muted-foreground mt-0.5">
{formatDistanceToNow(new Date(item.createdAt), { addSuffix: true })} {formatDistanceToNow(new Date(item.createdAt), { addSuffix: true })}
</p> </p>
</div> </div>

View File

@@ -293,7 +293,7 @@ export function CustomizeWidgetsMenu() {
function Section({ title, children }: { title: string; children: React.ReactNode }) { function Section({ title, children }: { title: string; children: React.ReactNode }) {
return ( return (
<div className="py-2 first:pt-1"> <div className="py-2 first:pt-1">
<div className="px-1 pb-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <div className="px-1 pb-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{title} {title}
</div> </div>
{children} {children}

View File

@@ -96,7 +96,7 @@ export function HotDealsCard() {
) : null} ) : null}
</p> </p>
</div> </div>
<Badge variant="secondary" className="shrink-0 text-[10px]"> <Badge variant="secondary" className="shrink-0 text-xs">
{STAGE_LABELS[d.stage] ?? d.stage} {STAGE_LABELS[d.stage] ?? d.stage}
</Badge> </Badge>
</Link> </Link>

View File

@@ -136,14 +136,14 @@ export function MyRemindersRail() {
<Badge <Badge
variant="outline" variant="outline"
className={cn( className={cn(
'border-transparent text-[10px]', 'border-transparent text-xs',
PRIORITY_BADGE[r.priority] ?? 'bg-muted text-muted-foreground', PRIORITY_BADGE[r.priority] ?? 'bg-muted text-muted-foreground',
)} )}
> >
{r.priority} {r.priority}
</Badge> </Badge>
) : null} ) : null}
<span className="shrink-0 text-[11px] tabular-nums text-muted-foreground"> <span className="shrink-0 text-xs tabular-nums text-muted-foreground">
{isOverdue {isOverdue
? formatDistanceToNowStrict(due) + ' overdue' ? formatDistanceToNowStrict(due) + ' overdue'
: isUpcoming : isUpcoming

View File

@@ -57,7 +57,7 @@ export function OnboardingTile() {
</div> </div>
{data.nextStep ? ( {data.nextStep ? (
<div className="rounded-md border border-dashed border-brand-200 bg-white/60 p-2"> <div className="rounded-md border border-dashed border-brand-200 bg-white/60 p-2">
<p className="text-[10px] font-semibold uppercase tracking-wide text-brand-700"> <p className="text-xs font-semibold uppercase tracking-wide text-brand-700">
Next step Next step
</p> </p>
<p className="mt-0.5 text-xs text-foreground">{data.nextStep.label}</p> <p className="mt-0.5 text-xs text-foreground">{data.nextStep.label}</p>

View File

@@ -126,7 +126,7 @@ export function PipelineValueTile({ range }: { range?: DateRange } = {}) {
yields the weighted forecast - a defensible &ldquo;what will likely land&rdquo; yields the weighted forecast - a defensible &ldquo;what will likely land&rdquo;
number, vs the gross which assumes every deal closes at full value. number, vs the gross which assumes every deal closes at full value.
</p> </p>
<div className="mt-3 grid grid-cols-[1fr_auto] gap-x-3 gap-y-1 rounded-md bg-muted/50 p-2.5 text-[11px]"> <div className="mt-3 grid grid-cols-[1fr_auto] gap-x-3 gap-y-1 rounded-md bg-muted/50 p-2.5 text-xs">
{PIPELINE_STAGES.map((s) => { {PIPELINE_STAGES.map((s) => {
const dbWeight = forecast.data?.stageBreakdown.find((r) => r.stage === s)?.weight; const dbWeight = forecast.data?.stageBreakdown.find((r) => r.stage === s)?.weight;
const weight = dbWeight ?? STAGE_WEIGHTS[s]; const weight = dbWeight ?? STAGE_WEIGHTS[s];
@@ -140,7 +140,7 @@ export function PipelineValueTile({ range }: { range?: DateRange } = {}) {
); );
})} })}
</div> </div>
<p className="mt-3 text-[11px] text-muted-foreground"> <p className="mt-3 text-xs text-muted-foreground">
{forecast.data?.weightsSource === 'db' {forecast.data?.weightsSource === 'db'
? 'Using per-port weights (admins tune these in Settings → Pipeline).' ? 'Using per-port weights (admins tune these in Settings → Pipeline).'
: 'Using system defaults. Admins can override per port in Settings → Pipeline.'} : 'Using system defaults. Admins can override per port in Settings → Pipeline.'}
@@ -153,7 +153,7 @@ export function PipelineValueTile({ range }: { range?: DateRange } = {}) {
{/* ── Headline numbers ─────────────────────────────────────── */} {/* ── Headline numbers ─────────────────────────────────────── */}
<div className="flex items-end justify-between gap-4"> <div className="flex items-end justify-between gap-4">
<div className="min-w-0"> <div className="min-w-0">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Gross Gross
</p> </p>
{isLoading ? ( {isLoading ? (
@@ -168,7 +168,7 @@ export function PipelineValueTile({ range }: { range?: DateRange } = {}) {
)} )}
</div> </div>
<div className="text-right"> <div className="text-right">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Weighted forecast Weighted forecast
</p> </p>
{isLoading ? ( {isLoading ? (
@@ -222,12 +222,12 @@ export function PipelineValueTile({ range }: { range?: DateRange } = {}) {
<span className="sm:hidden">{fmtCompact(s.grossValue)}</span> <span className="sm:hidden">{fmtCompact(s.grossValue)}</span>
<span className="hidden sm:inline">{fmt(s.grossValue)}</span> <span className="hidden sm:inline">{fmt(s.grossValue)}</span>
</p> </p>
<p className="text-[11px] text-muted-foreground tabular-nums"> <p className="text-xs text-muted-foreground tabular-nums">
{s.count} {s.count === 1 ? 'deal' : 'deals'} · {Math.round(s.weight * 100)}% {s.count} {s.count === 1 ? 'deal' : 'deals'} · {Math.round(s.weight * 100)}%
</p> </p>
{s.dealsMissingPrice > 0 ? ( {s.dealsMissingPrice > 0 ? (
<p <p
className="mt-0.5 inline-flex items-center gap-1 text-[10px] font-medium text-warning" className="mt-0.5 inline-flex items-center gap-1 text-xs font-medium text-warning"
title={`${s.dealsMissingPrice} of ${s.count} ${s.count === 1 ? 'deal has' : 'deals have'} a berth with no price set - gross is undercounted here.`} title={`${s.dealsMissingPrice} of ${s.count} ${s.count === 1 ? 'deal has' : 'deals have'} a berth with no price set - gross is undercounted here.`}
> >
<AlertTriangle className="size-3" aria-hidden /> <AlertTriangle className="size-3" aria-hidden />
@@ -244,7 +244,7 @@ export function PipelineValueTile({ range }: { range?: DateRange } = {}) {
)} )}
{forecast.data?.weightsSource === 'default' ? ( {forecast.data?.weightsSource === 'default' ? (
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Using default stage weights. Tune them in Settings Pipeline. Using default stage weights. Tune them in Settings Pipeline.
</p> </p>
) : null} ) : null}

View File

@@ -68,7 +68,7 @@ export function WebsiteGlanceTile({ range = '30d' }: Props) {
<div className="absolute inset-x-0 top-0 h-1 bg-mint" aria-hidden /> <div className="absolute inset-x-0 top-0 h-1 bg-mint" aria-hidden />
<div className="flex items-start justify-between gap-3"> <div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground sm:text-xs"> <div className="flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-muted-foreground sm:text-xs">
<Globe className="h-3 w-3" aria-hidden /> <Globe className="h-3 w-3" aria-hidden />
Website · {shortRangeLabel(range)} Website · {shortRangeLabel(range)}
</div> </div>

View File

@@ -113,7 +113,7 @@ function GroupBlock<K extends AggregatedItemKind['kind']>({
<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">
{group.label} {group.label}
<span className="ml-1.5 text-muted-foreground/70 tabular-nums">· {group.total}</span> <span className="ml-1.5 text-muted-foreground tabular-nums">· {group.total}</span>
</header> </header>
<ul className="space-y-1"> <ul className="space-y-1">
{items.map((item) => ( {items.map((item) => (

View File

@@ -460,7 +460,7 @@ function FlatFolderListing({
{childFolders.length > 0 ? ( {childFolders.length > 0 ? (
<div> <div>
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground mb-1.5"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-1.5">
Subfolders Subfolders
</p> </p>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5"> <div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">

View File

@@ -107,7 +107,7 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
<span className="truncate group-hover:underline">{f.filename}</span> <span className="truncate group-hover:underline">{f.filename}</span>
{signedFromDocumentId ? ( {signedFromDocumentId ? (
<span <span
className="ml-1 inline-flex shrink-0 items-center rounded-full bg-emerald-50 px-1.5 py-px text-[10px] font-medium text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300" className="ml-1 inline-flex shrink-0 items-center rounded-full bg-emerald-50 px-1.5 py-px text-xs font-medium text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300"
title="This file is a signed copy of a Documenso/uploaded workflow" title="This file is a signed copy of a Documenso/uploaded workflow"
> >
Signed Signed
@@ -117,7 +117,7 @@ export function EntityFolderView({ portSlug, entityType, entityId }: Props) {
<Link <Link
href={`/${portSlug}/interests/${f.interestId}`} href={`/${portSlug}/interests/${f.interestId}`}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="ml-1 inline-flex shrink-0 items-center rounded-full border bg-muted/40 px-1.5 py-px text-[10px] font-medium text-muted-foreground hover:border-primary/40 hover:bg-accent/40 hover:text-foreground" className="ml-1 inline-flex shrink-0 items-center rounded-full border bg-muted/40 px-1.5 py-px text-xs font-medium text-muted-foreground hover:border-primary/40 hover:bg-accent/40 hover:text-foreground"
title={`Linked to interest ${f.interestBerthLabel}`} title={`Linked to interest ${f.interestBerthLabel}`}
> >
{f.interestBerthLabel} {f.interestBerthLabel}

View File

@@ -610,7 +610,7 @@ export function EoiGenerateDialog({
return ( return (
<Sheet open={open} onOpenChange={onOpenChange}> <Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="w-full sm:max-w-2xl overflow-y-auto"> <SheetContent className="w-full sm:max-w-2xl lg:max-w-4xl overflow-y-auto">
<SheetHeader> <SheetHeader>
<SheetTitle className="flex items-center gap-2"> <SheetTitle className="flex items-center gap-2">
<FileSignature className="size-4" aria-hidden /> <FileSignature className="size-4" aria-hidden />
@@ -654,7 +654,7 @@ export function EoiGenerateDialog({
) : 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">
<div className="space-y-1"> <div className="space-y-1">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Required (Section 2 of the EOI) Required (Section 2 of the EOI)
</p> </p>
<dl className="space-y-1.5"> <dl className="space-y-1.5">
@@ -690,12 +690,12 @@ export function EoiGenerateDialog({
</div> </div>
<div className="space-y-1 border-t pt-2"> <div className="space-y-1 border-t pt-2">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Section 3 - yacht details Section 3 - yacht details
</p> </p>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{ctx.yacht ? ( {ctx.yacht ? (
<label className="inline-flex cursor-pointer items-center gap-1.5 text-[11px] text-muted-foreground"> <label className="inline-flex cursor-pointer items-center gap-1.5 text-xs text-muted-foreground">
<input <input
type="checkbox" type="checkbox"
checked={includeYachtDetails} checked={includeYachtDetails}
@@ -707,7 +707,7 @@ export function EoiGenerateDialog({
</label> </label>
) : null} ) : null}
{ctx.yacht && includeYachtDetails ? ( {ctx.yacht && includeYachtDetails ? (
<div className="inline-flex rounded-md border bg-muted/30 p-0.5 text-[11px]"> <div className="inline-flex rounded-md border bg-muted/30 p-0.5 text-xs">
<button <button
type="button" type="button"
onClick={() => setDimensionUnit('ft')} onClick={() => setDimensionUnit('ft')}
@@ -739,7 +739,7 @@ export function EoiGenerateDialog({
</div> </div>
</div> </div>
{ctx.yacht && !includeYachtDetails ? ( {ctx.yacht && !includeYachtDetails ? (
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Yacht details will be omitted from the EOI even though a yacht is linked. Yacht details will be omitted from the EOI even though a yacht is linked.
</p> </p>
) : null} ) : null}
@@ -759,7 +759,7 @@ export function EoiGenerateDialog({
<button <button
type="button" type="button"
onClick={() => setYachtSpawnOpen(true)} onClick={() => setYachtSpawnOpen(true)}
className="mt-0.5 shrink-0 text-[11px] text-primary hover:underline" className="mt-0.5 shrink-0 text-xs text-primary hover:underline"
title="Create a new yacht linked to this client" title="Create a new yacht linked to this client"
> >
+ New yacht + New yacht
@@ -782,11 +782,11 @@ export function EoiGenerateDialog({
{linkedBerthsRes && linkedBerthsRes.data.length > 0 ? ( {linkedBerthsRes && linkedBerthsRes.data.length > 0 ? (
<div className="border-t pt-2 space-y-2"> <div className="border-t pt-2 space-y-2">
<div> <div>
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
EOI scope ({linkedBerthsRes.data.length} linked berth EOI scope ({linkedBerthsRes.data.length} linked berth
{linkedBerthsRes.data.length === 1 ? '' : 's'}) {linkedBerthsRes.data.length === 1 ? '' : 's'})
</p> </p>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Confirm signature scope and public-map visibility for each berth before Confirm signature scope and public-map visibility for each berth before
generating. Defaults reflect what&apos;s saved on the interest. generating. Defaults reflect what&apos;s saved on the interest.
</p> </p>
@@ -805,7 +805,7 @@ export function EoiGenerateDialog({
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<span className="font-mono text-sm">{link.mooringNumber ?? '-'}</span> <span className="font-mono text-sm">{link.mooringNumber ?? '-'}</span>
{link.isPrimary ? ( {link.isPrimary ? (
<span className="text-[10px] uppercase tracking-wide text-primary"> <span className="text-xs uppercase tracking-wide text-primary">
Primary Primary
</span> </span>
) : null} ) : null}
@@ -845,7 +845,7 @@ export function EoiGenerateDialog({
); );
})} })}
</div> </div>
<p className="text-[10px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
<strong>In EOI</strong>: covered by this signed envelope.{' '} <strong>In EOI</strong>: covered by this signed envelope.{' '}
<strong>Public map</strong>: shown as &quot;Under Offer&quot; on the marketing <strong>Public map</strong>: shown as &quot;Under Offer&quot; on the marketing
site. site.
@@ -854,7 +854,7 @@ export function EoiGenerateDialog({
) : null} ) : null}
{portSlug && clientId && ( {portSlug && clientId && (
<div className="border-t pt-2 space-y-1"> <div className="border-t pt-2 space-y-1">
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Editing name / yacht name above patches the underlying records directly. For Editing name / yacht name above patches the underlying records directly. For
phone, address, or to manage linked berths, jump to the canonical page: phone, address, or to manage linked berths, jump to the canonical page:
</p> </p>
@@ -889,7 +889,7 @@ export function EoiGenerateDialog({
<p className="text-xs font-medium text-amber-900"> <p className="text-xs font-medium text-amber-900">
Missing required client details Missing required client details
</p> </p>
<p className="text-[11px] text-amber-800/80"> <p className="text-xs text-amber-800/80">
Fill the fields below - they&apos;ll be saved to the client&apos;s record before Fill the fields below - they&apos;ll be saved to the client&apos;s record before
the EOI renders. the EOI renders.
</p> </p>
@@ -1236,7 +1236,7 @@ function OverridableContactField({
<span className="flex-1"> <span className="flex-1">
{effective ?? (missing ? 'Missing - required' : 'Not set')} {effective ?? (missing ? 'Missing - required' : 'Not set')}
{override?.value != null ? ( {override?.value != null ? (
<span className="ml-1 inline-flex items-center rounded bg-amber-100 px-1 text-[10px] font-medium text-amber-800"> <span className="ml-1 inline-flex items-center rounded bg-amber-100 px-1 text-xs font-medium text-amber-800">
[EOI] [EOI]
</span> </span>
) : null} ) : null}
@@ -1245,7 +1245,7 @@ function OverridableContactField({
<button <button
type="button" type="button"
onClick={() => setExpanded(true)} onClick={() => setExpanded(true)}
className="text-[11px] text-primary hover:underline" className="text-xs text-primary hover:underline"
> >
{override?.value != null ? 'Edit override' : 'Override'} {override?.value != null ? 'Edit override' : 'Override'}
</button> </button>
@@ -1253,7 +1253,7 @@ function OverridableContactField({
<button <button
type="button" type="button"
onClick={collapseAndClear} onClick={collapseAndClear}
className="text-[11px] text-muted-foreground hover:underline" className="text-xs text-muted-foreground hover:underline"
> >
Clear & close Clear & close
</button> </button>
@@ -1329,7 +1329,7 @@ function OverridableContactField({
/> />
) : null} ) : null}
<div className="space-y-1"> <div className="space-y-1">
<label className="flex items-start gap-2 text-[11px] text-muted-foreground cursor-pointer"> <label className="flex items-start gap-2 text-xs text-muted-foreground cursor-pointer">
<input <input
type="checkbox" type="checkbox"
className="mt-0.5" className="mt-0.5"
@@ -1348,12 +1348,12 @@ function OverridableContactField({
/> />
<span> <span>
Use only for this EOI Use only for this EOI
<span className="block text-[10px]"> <span className="block text-xs">
Records the deviation on this document; canonical record untouched. Records the deviation on this document; canonical record untouched.
</span> </span>
</span> </span>
</label> </label>
<label className="flex items-start gap-2 text-[11px] text-muted-foreground cursor-pointer"> <label className="flex items-start gap-2 text-xs text-muted-foreground cursor-pointer">
<input <input
type="checkbox" type="checkbox"
className="mt-0.5" className="mt-0.5"
@@ -1370,7 +1370,7 @@ function OverridableContactField({
/> />
<span> <span>
Set as default for future docs Set as default for future docs
<span className="block text-[10px]"> <span className="block text-xs">
Promotes this value to the canonical primary on save. Promotes this value to the canonical primary on save.
</span> </span>
</span> </span>
@@ -1497,7 +1497,7 @@ function OverridableAddressField({
<span className="flex-1"> <span className="flex-1">
{effectiveSummary ?? (missing ? 'Missing - required' : 'Not set')} {effectiveSummary ?? (missing ? 'Missing - required' : 'Not set')}
{override ? ( {override ? (
<span className="ml-1 inline-flex items-center rounded bg-amber-100 px-1 text-[10px] font-medium text-amber-800"> <span className="ml-1 inline-flex items-center rounded bg-amber-100 px-1 text-xs font-medium text-amber-800">
[EOI] [EOI]
</span> </span>
) : null} ) : null}
@@ -1506,7 +1506,7 @@ function OverridableAddressField({
<button <button
type="button" type="button"
onClick={() => setExpanded(true)} onClick={() => setExpanded(true)}
className="text-[11px] text-primary hover:underline" className="text-xs text-primary hover:underline"
> >
{override ? 'Edit override' : 'Override'} {override ? 'Edit override' : 'Override'}
</button> </button>
@@ -1517,7 +1517,7 @@ function OverridableAddressField({
setExpanded(false); setExpanded(false);
onChange(null); onChange(null);
}} }}
className="text-[11px] text-muted-foreground hover:underline" className="text-xs text-muted-foreground hover:underline"
> >
Clear & close Clear & close
</button> </button>
@@ -1606,7 +1606,7 @@ function OverridableAddressField({
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="flex items-start gap-2 text-[11px] text-muted-foreground cursor-pointer"> <label className="flex items-start gap-2 text-xs text-muted-foreground cursor-pointer">
<input <input
type="checkbox" type="checkbox"
className="mt-0.5" className="mt-0.5"
@@ -1621,12 +1621,12 @@ function OverridableAddressField({
/> />
<span> <span>
Use only for this EOI Use only for this EOI
<span className="block text-[10px]"> <span className="block text-xs">
Records the deviation on this document; canonical address untouched. Records the deviation on this document; canonical address untouched.
</span> </span>
</span> </span>
</label> </label>
<label className="flex items-start gap-2 text-[11px] text-muted-foreground cursor-pointer"> <label className="flex items-start gap-2 text-xs text-muted-foreground cursor-pointer">
<input <input
type="checkbox" type="checkbox"
className="mt-0.5" className="mt-0.5"
@@ -1643,7 +1643,7 @@ function OverridableAddressField({
/> />
<span> <span>
Set as default for future docs Set as default for future docs
<span className="block text-[10px]"> <span className="block text-xs">
Promotes this address to the canonical primary on save. Promotes this address to the canonical primary on save.
</span> </span>
</span> </span>

View File

@@ -119,7 +119,7 @@ export function ExternalEoiEditDialog({ open, onOpenChange, documentId, initial
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-3xl"> <DialogContent className="sm:max-w-3xl lg:max-w-5xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Edit document metadata</DialogTitle> <DialogTitle>Edit document metadata</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -55,7 +55,7 @@ export function SigningDetailsDialog({ documentId, open, onOpenChange }: Props)
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-3xl"> <DialogContent className="sm:max-w-3xl lg:max-w-5xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Signing details</DialogTitle> <DialogTitle>Signing details</DialogTitle>
<DialogDescription> <DialogDescription>
@@ -78,7 +78,7 @@ export function SigningDetailsDialog({ documentId, open, onOpenChange }: Props)
</StatusPill> </StatusPill>
<span>·</span> <span>·</span>
<span> <span>
Created {new Date(data.data.workflow.createdAt).toLocaleString('en-GB')} Created {new Date(data.data.workflow.createdAt).toLocaleString(undefined)}
</span> </span>
</p> </p>
</section> </section>
@@ -118,7 +118,7 @@ export function SigningDetailsDialog({ documentId, open, onOpenChange }: Props)
{data.data.events.map((e) => ( {data.data.events.map((e) => (
<li key={e.id} className="flex items-center gap-2 text-muted-foreground"> <li key={e.id} className="flex items-center gap-2 text-muted-foreground">
<span className="tabular-nums"> <span className="tabular-nums">
{new Date(e.createdAt).toLocaleString('en-GB')} {new Date(e.createdAt).toLocaleString(undefined)}
</span> </span>
<span>{e.eventType.replace(/_/g, ' ')}</span> <span>{e.eventType.replace(/_/g, ' ')}</span>
</li> </li>

View File

@@ -311,7 +311,7 @@ export function SigningProgress({ documentId, signers }: SigningProgressProps) {
</span> </span>
<span <span
className={cn( className={cn(
'inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide', 'inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-xs font-semibold uppercase tracking-wide',
styles.statusChipBg, styles.statusChipBg,
styles.statusChipText, styles.statusChipText,
)} )}
@@ -319,7 +319,7 @@ export function SigningProgress({ documentId, signers }: SigningProgressProps) {
<StatusIcon className="size-2.5" aria-hidden /> <StatusIcon className="size-2.5" aria-hidden />
{statusLabel} {statusLabel}
</span> </span>
<span className="text-[11px] text-muted-foreground"> <span className="text-xs text-muted-foreground">
· {ROLE_LABELS[signer.signerRole] ?? signer.signerRole} · {ROLE_LABELS[signer.signerRole] ?? signer.signerRole}
{' · '} {' · '}
<span className="font-medium">#{signer.signingOrder}</span> <span className="font-medium">#{signer.signingOrder}</span>
@@ -331,7 +331,7 @@ export function SigningProgress({ documentId, signers }: SigningProgressProps) {
reps in manual-send mode know an action is required. reps in manual-send mode know an action is required.
Once invited, each event surfaces with a precise Once invited, each event surfaces with a precise
timestamp tooltip (the relative-time is the headline). */} timestamp tooltip (the relative-time is the headline). */}
<div className="flex flex-wrap items-center gap-x-3 gap-y-0.5 pt-0.5 text-[11px] text-muted-foreground"> <div className="flex flex-wrap items-center gap-x-3 gap-y-0.5 pt-0.5 text-xs text-muted-foreground">
{!signer.invitedAt && signer.status === 'pending' ? ( {!signer.invitedAt && signer.status === 'pending' ? (
<span className="inline-flex items-center gap-1 italic text-amber-700"> <span className="inline-flex items-center gap-1 italic text-amber-700">
<Mail className="size-3" aria-hidden /> <Mail className="size-3" aria-hidden />

View File

@@ -613,7 +613,7 @@ function DialogBody({
rep needs to re-pick the PDF - the rest of the state rep needs to re-pick the PDF - the rest of the state
(title, signers, placements, custom note) survives. */} (title, signers, placements, custom note) survives. */}
{draftSavedAt ? ( {draftSavedAt ? (
<div className="flex shrink-0 items-center gap-2 text-[11px] text-muted-foreground"> <div className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
<span title={`Draft auto-saved ${new Date(draftSavedAt).toLocaleString()}`}> <span title={`Draft auto-saved ${new Date(draftSavedAt).toLocaleString()}`}>
Draft saved Draft saved
</span> </span>
@@ -621,7 +621,7 @@ function DialogBody({
type="button" type="button"
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-6 px-2 text-[11px] text-muted-foreground hover:text-destructive" className="h-6 px-2 text-xs text-muted-foreground hover:text-destructive"
onClick={discardDraft} onClick={discardDraft}
> >
Discard Discard
@@ -1551,7 +1551,7 @@ function FieldMetaSubPanel({
if (field.type === 'TEXT') { if (field.type === 'TEXT') {
return ( return (
<div className="space-y-2 rounded-md border bg-muted/30 p-2"> <div className="space-y-2 rounded-md border bg-muted/30 p-2">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Text settings Text settings
</p> </p>
<div className="space-y-1"> <div className="space-y-1">
@@ -1588,7 +1588,7 @@ function FieldMetaSubPanel({
if (field.type === 'NUMBER') { if (field.type === 'NUMBER') {
return ( return (
<div className="space-y-2 rounded-md border bg-muted/30 p-2"> <div className="space-y-2 rounded-md border bg-muted/30 p-2">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Number settings Number settings
</p> </p>
<div className="space-y-1"> <div className="space-y-1">
@@ -1641,7 +1641,7 @@ function FieldMetaSubPanel({
const rawValues = (meta.values as Array<{ value: string; checked?: boolean }>) ?? []; const rawValues = (meta.values as Array<{ value: string; checked?: boolean }>) ?? [];
return ( return (
<div className="space-y-2 rounded-md border bg-muted/30 p-2"> <div className="space-y-2 rounded-md border bg-muted/30 p-2">
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{field.type === 'CHECKBOX' {field.type === 'CHECKBOX'
? 'Checkbox options' ? 'Checkbox options'
: field.type === 'RADIO' : field.type === 'RADIO'

View File

@@ -130,7 +130,7 @@ export function TrackedLinkComposerButton({ sendId, onInsert, variant = 'inline'
Copy Copy
</Button> </Button>
</div> </div>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Redirects to{' '} Redirects to{' '}
<a <a
href={result.targetUrl} href={result.targetUrl}

View File

@@ -97,7 +97,7 @@ export function FileGrid({
<p className="w-full truncate text-center text-xs font-medium" title={file.filename}> <p className="w-full truncate text-center text-xs font-medium" title={file.filename}>
{file.filename} {file.filename}
</p> </p>
<div className="flex flex-col items-center gap-0.5 text-[10px] text-muted-foreground"> <div className="flex flex-col items-center gap-0.5 text-xs text-muted-foreground">
<span>{formatBytes(file.sizeBytes)}</span> <span>{formatBytes(file.sizeBytes)}</span>
<span>{format(new Date(file.createdAt), 'MMM d, yyyy')}</span> <span>{format(new Date(file.createdAt), 'MMM d, yyyy')}</span>
</div> </div>

View File

@@ -77,7 +77,7 @@ export function AssignedToChip({
type="button" type="button"
aria-label={`Change deal owner (currently ${label})`} aria-label={`Change deal owner (currently ${label})`}
className={cn( className={cn(
'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium transition-colors', 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors',
currentAssignedTo currentAssignedTo
? 'border-sky-200 bg-sky-50 text-sky-800 hover:bg-sky-100' ? 'border-sky-200 bg-sky-50 text-sky-800 hover:bg-sky-100'
: 'border-border bg-muted/50 text-muted-foreground hover:bg-muted', : 'border-border bg-muted/50 text-muted-foreground hover:bg-muted',

View File

@@ -45,7 +45,7 @@ export function DealPulseChip({ interest }: { interest: DealHealthInput }) {
<button <button
type="button" type="button"
className={cn( className={cn(
'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium transition-colors cursor-pointer', 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors cursor-pointer',
tint, tint,
)} )}
aria-label={`Deal pulse: ${label}, score ${health.score}/100. Click for breakdown.`} aria-label={`Deal pulse: ${label}, score ${health.score}/100. Click for breakdown.`}
@@ -65,7 +65,7 @@ export function DealPulseChip({ interest }: { interest: DealHealthInput }) {
</div> </div>
<div> <div>
<p className="text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <p className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
What pushed the score What pushed the score
</p> </p>
{health.signals.length === 0 ? ( {health.signals.length === 0 ? (
@@ -79,7 +79,7 @@ export function DealPulseChip({ interest }: { interest: DealHealthInput }) {
<li key={s.id} className="flex items-start gap-2"> <li key={s.id} className="flex items-start gap-2">
<span <span
className={cn( className={cn(
'shrink-0 rounded px-1.5 py-0.5 text-[10px] font-semibold tabular-nums', 'shrink-0 rounded px-1.5 py-0.5 text-xs font-semibold tabular-nums',
s.delta > 0 ? 'bg-emerald-100 text-emerald-800' : 'bg-rose-100 text-rose-800', s.delta > 0 ? 'bg-emerald-100 text-emerald-800' : 'bg-rose-100 text-rose-800',
)} )}
> >
@@ -92,7 +92,7 @@ export function DealPulseChip({ interest }: { interest: DealHealthInput }) {
)} )}
</div> </div>
<div className="rounded-md bg-muted/40 p-2.5 text-[11px] text-muted-foreground"> <div className="rounded-md bg-muted/40 p-2.5 text-xs text-muted-foreground">
<p className="font-medium text-foreground/80">How this is calculated</p> <p className="font-medium text-foreground/80">How this is calculated</p>
<p className="mt-0.5"> <p className="mt-0.5">
Every signal above traces to a specific date or pipeline stage on this deal. Recent Every signal above traces to a specific date or pipeline stage on this deal. Recent

View File

@@ -237,7 +237,7 @@ export function ExternalEoiUploadDialog({
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-2xl"> <DialogContent className="sm:max-w-2xl lg:max-w-4xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Upload externally-signed EOI</DialogTitle> <DialogTitle>Upload externally-signed EOI</DialogTitle>
<DialogDescription> <DialogDescription>

View File

@@ -161,7 +161,7 @@ export function InterestCard({ interest, portSlug, onEdit, onArchive }: Interest
key={b.id} key={b.id}
title={b.detail} title={b.detail}
className={cn( className={cn(
'inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium', 'inline-flex items-center rounded-full px-1.5 py-0.5 text-xs font-medium',
b.className, b.className,
)} )}
> >
@@ -185,7 +185,7 @@ export function InterestCard({ interest, portSlug, onEdit, onArchive }: Interest
) : null} ) : null}
{lastActivity ? ( {lastActivity ? (
<p className="mt-1.5 text-[11px] text-muted-foreground tabular-nums"> <p className="mt-1.5 text-xs text-muted-foreground tabular-nums">
Last activity {lastActivity} Last activity {lastActivity}
</p> </p>
) : null} ) : null}

View File

@@ -227,7 +227,7 @@ export function getInterestColumns({
key={b.id} key={b.id}
title={b.detail} title={b.detail}
aria-label={b.detail} aria-label={b.detail}
className={`inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-medium ${b.className}`} className={`inline-flex items-center rounded-full px-1.5 py-0.5 text-xs font-medium ${b.className}`}
> >
{b.label} {b.label}
</span> </span>

View File

@@ -230,7 +230,7 @@ function ContactLogRow({
<div className="min-w-0 flex-1 space-y-1.5"> <div className="min-w-0 flex-1 space-y-1.5">
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<span className="text-sm font-medium text-foreground">{channelMeta.label}</span> <span className="text-sm font-medium text-foreground">{channelMeta.label}</span>
<Badge variant="outline" className="text-[10px] capitalize"> <Badge variant="outline" className="text-xs capitalize">
{entry.direction} {entry.direction}
</Badge> </Badge>
<span className="text-xs text-muted-foreground"> <span className="text-xs text-muted-foreground">
@@ -511,7 +511,7 @@ function ComposeDialogBody({
} }
onClick={() => (voice.isListening ? voice.stop() : voice.start())} onClick={() => (voice.isListening ? voice.stop() : voice.start())}
className={cn( className={cn(
'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium transition-colors', 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors',
voice.isListening voice.isListening
? 'border-rose-300 bg-rose-50 text-rose-800 animate-pulse' ? 'border-rose-300 bg-rose-50 text-rose-800 animate-pulse'
: 'border-border bg-muted/40 text-muted-foreground hover:bg-muted', : 'border-border bg-muted/40 text-muted-foreground hover:bg-muted',
@@ -532,7 +532,7 @@ function ComposeDialogBody({
) : ( ) : (
<span <span
title="Voice transcription isn't supported in this browser." title="Voice transcription isn't supported in this browser."
className="inline-flex items-center gap-1 text-[11px] text-muted-foreground" className="inline-flex items-center gap-1 text-xs text-muted-foreground"
> >
<MicOff className="size-3" aria-hidden /> <MicOff className="size-3" aria-hidden />
Voice unavailable Voice unavailable
@@ -547,10 +547,10 @@ function ComposeDialogBody({
onChange={(e) => setSummary(e.target.value)} onChange={(e) => setSummary(e.target.value)}
/> />
{voice.isListening && voice.interim ? ( {voice.isListening && voice.interim ? (
<p className="text-[11px] italic text-muted-foreground">{voice.interim}</p> <p className="text-xs italic text-muted-foreground">{voice.interim}</p>
) : null} ) : null}
{voice.error ? ( {voice.error ? (
<p className="text-[11px] text-rose-700">Voice error: {voice.error}</p> <p className="text-xs text-rose-700">Voice error: {voice.error}</p>
) : null} ) : null}
</div> </div>
@@ -589,7 +589,7 @@ function ComposeDialogBody({
onChange={setFollowUpAt} onChange={setFollowUpAt}
className="max-w-xs" className="max-w-xs"
/> />
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
A reminder is created on this interest for the time above. A reminder is created on this interest for the time above.
</p> </p>
</div> </div>

View File

@@ -414,7 +414,7 @@ function StatusBadge({ status }: { status: DocumentRow['status'] }) {
<Badge <Badge
variant="outline" variant="outline"
className={cn( className={cn(
'border-transparent text-[10px] font-semibold uppercase tracking-wide', 'border-transparent text-xs font-semibold uppercase tracking-wide',
STATUS_TONES[status], STATUS_TONES[status],
)} )}
> >

View File

@@ -276,7 +276,7 @@ export function InterestDetailHeader({ portSlug, interest }: InterestDetailHeade
)} )}
{(interest.activeReminderCount ?? 0) > 0 ? ( {(interest.activeReminderCount ?? 0) > 0 ? (
<span <span
className="inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-[11px] font-medium text-amber-800" className="inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-800"
title={`${interest.activeReminderCount} pending reminder${ title={`${interest.activeReminderCount} pending reminder${
interest.activeReminderCount === 1 ? '' : 's' interest.activeReminderCount === 1 ? '' : 's'
}`} }`}

View File

@@ -402,7 +402,7 @@ function ActiveEoiCard({
local preference here so the UI matches what was sent. */} local preference here so the UI matches what was sent. */}
<span <span
className={cn( className={cn(
'inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide', 'inline-flex items-center gap-1 rounded-full border px-1.5 py-0.5 text-xs font-medium uppercase tracking-wide',
signingOrder === 'SEQUENTIAL' signingOrder === 'SEQUENTIAL'
? 'border-indigo-200 bg-indigo-50 text-indigo-800' ? 'border-indigo-200 bg-indigo-50 text-indigo-800'
: 'border-sky-200 bg-sky-50 text-sky-800', : 'border-sky-200 bg-sky-50 text-sky-800',
@@ -665,7 +665,7 @@ function StatusBadge({ status }: { status: DocumentRow['status'] }) {
<Badge <Badge
variant="outline" variant="outline"
className={cn( className={cn(
'border-transparent text-[10px] font-semibold uppercase tracking-wide', 'border-transparent text-xs font-semibold uppercase tracking-wide',
STATUS_TONES[status], STATUS_TONES[status],
)} )}
> >

View File

@@ -355,7 +355,7 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
requestClose(); requestClose();
}} }}
> >
<SheetContent className="w-full sm:max-w-2xl overflow-y-auto"> <SheetContent className="w-full sm:max-w-2xl lg:max-w-4xl overflow-y-auto">
<SheetHeader> <SheetHeader>
<SheetTitle>{isEdit ? 'Edit Interest' : 'New Interest'}</SheetTitle> <SheetTitle>{isEdit ? 'Edit Interest' : 'New Interest'}</SheetTitle>
</SheetHeader> </SheetHeader>
@@ -533,7 +533,7 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
/> />
<span className="flex-1">{option.label}</span> <span className="flex-1">{option.label}</span>
{isPrimary && ( {isPrimary && (
<span className="ml-2 rounded bg-primary/15 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-primary"> <span className="ml-2 rounded bg-primary/15 px-1.5 py-0.5 text-xs font-medium uppercase tracking-wide text-primary">
primary primary
</span> </span>
)} )}
@@ -935,7 +935,7 @@ function DimensionInput({
}} }}
/> />
{altValue ? ( {altValue ? (
<p className="text-[11px] leading-tight text-muted-foreground"> {altValue}</p> <p className="text-xs leading-tight text-muted-foreground"> {altValue}</p>
) : null} ) : null}
</div> </div>
); );

View File

@@ -410,7 +410,7 @@ function StatusBadge({ status }: { status: DocumentRow['status'] }) {
<Badge <Badge
variant="outline" variant="outline"
className={cn( className={cn(
'border-transparent text-[10px] font-semibold uppercase tracking-wide', 'border-transparent text-xs font-semibold uppercase tracking-wide',
STATUS_TONES[status], STATUS_TONES[status],
)} )}
> >

View File

@@ -363,7 +363,7 @@ function MilestoneAdvanceButton({
onChange={setDate} onChange={setDate}
placeholder="Pick a date" placeholder="Pick a date"
/> />
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Defaults to today - back-date if the event happened earlier. Defaults to today - back-date if the event happened earlier.
</p> </p>
</div> </div>
@@ -429,7 +429,7 @@ function MilestoneBackfillButton({
onChange={setDate} onChange={setDate}
placeholder="Pick a date" placeholder="Pick a date"
/> />
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Records the date the milestone happened. Does not change the deal&apos;s pipeline stage. Records the date the milestone happened. Does not change the deal&apos;s pipeline stage.
</p> </p>
</div> </div>
@@ -491,14 +491,14 @@ function MilestoneSection({
<Icon className={cn('size-4', isActive ? 'text-brand-600' : 'text-muted-foreground')} /> <Icon className={cn('size-4', isActive ? 'text-brand-600' : 'text-muted-foreground')} />
<h3 className="text-sm font-semibold tracking-tight text-foreground">{title}</h3> <h3 className="text-sm font-semibold tracking-tight text-foreground">{title}</h3>
{isActive ? ( {isActive ? (
<span className="inline-flex items-center gap-1 rounded-full bg-brand-600 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-white shadow-sm"> <span className="inline-flex items-center gap-1 rounded-full bg-brand-600 px-2 py-0.5 text-xs font-semibold uppercase tracking-[0.08em] text-white shadow-sm">
<span className="size-1.5 rounded-full bg-white/90" aria-hidden /> <span className="size-1.5 rounded-full bg-white/90" aria-hidden />
Next step Next step
</span> </span>
) : null} ) : null}
</div> </div>
{status ? ( {status ? (
<span className="rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground"> <span className="rounded-full bg-muted px-2 py-0.5 text-xs font-medium uppercase tracking-wide text-muted-foreground">
{humanizeStatus(status)} {humanizeStatus(status)}
</span> </span>
) : null} ) : null}
@@ -1004,7 +1004,7 @@ function OverviewTab({
disabled={stageMutation.isPending} disabled={stageMutation.isPending}
onConfirm={(date) => advance('deposit_paid', date)} onConfirm={(date) => advance('deposit_paid', date)}
/> />
<span className="text-[11px] text-muted-foreground"> <span className="text-xs text-muted-foreground">
Or record a payment in the Payments section. Or record a payment in the Payments section.
</span> </span>
</div> </div>
@@ -1119,7 +1119,7 @@ function OverviewTab({
gates the actual stage move). */} gates the actual stage move). */}
{pastMilestones.length > 0 && ( {pastMilestones.length > 0 && (
<div className="rounded-lg border bg-muted/20"> <div className="rounded-lg border bg-muted/20">
<div className="flex items-center gap-2 border-b px-4 py-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <div className="flex items-center gap-2 border-b px-4 py-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
<span>Past</span> <span>Past</span>
</div> </div>
<Accordion type="multiple" className="px-4"> <Accordion type="multiple" className="px-4">
@@ -1129,7 +1129,7 @@ function OverviewTab({
<div className="flex flex-1 items-center gap-2 text-left text-muted-foreground"> <div className="flex flex-1 items-center gap-2 text-left text-muted-foreground">
<CheckCircle2 className="size-3 shrink-0 text-emerald-600" aria-hidden /> <CheckCircle2 className="size-3 shrink-0 text-emerald-600" aria-hidden />
<span className="font-medium text-foreground">{m.title}</span> <span className="font-medium text-foreground">{m.title}</span>
<span className="text-[10px]">·</span> <span className="text-xs">·</span>
<span className="truncate text-xs">{m.pastSummary}</span> <span className="truncate text-xs">{m.pastSummary}</span>
</div> </div>
</AccordionTrigger> </AccordionTrigger>
@@ -1406,7 +1406,7 @@ function OverviewTab({
a context hint. */} a context hint. */}
<span <span
className={cn( className={cn(
'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-[10px] font-medium', 'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-xs font-medium',
STAGE_BADGE[interest.pipelineStage as PipelineStage] ?? STAGE_BADGE[interest.pipelineStage as PipelineStage] ??
'bg-muted text-muted-foreground', 'bg-muted text-muted-foreground',
)} )}

View File

@@ -131,7 +131,7 @@ export function InterestTimeline({ interestId }: InterestTimelineProps) {
<p className="text-sm"> <p className="text-sm">
{event.description} {event.description}
{isAuto ? ( {isAuto ? (
<span className="ml-2 inline-flex items-center gap-1 rounded-full bg-muted px-1.5 py-0.5 align-middle text-[10px] font-medium uppercase tracking-wide text-muted-foreground"> <span className="ml-2 inline-flex items-center gap-1 rounded-full bg-muted px-1.5 py-0.5 align-middle text-xs font-medium uppercase tracking-wide text-muted-foreground">
<Bot className="h-3 w-3" aria-hidden /> <Bot className="h-3 w-3" aria-hidden />
Auto Auto
</span> </span>

View File

@@ -378,7 +378,7 @@ function LinkedBerthRowItem({
<HelpCircle className="h-3.5 w-3.5" aria-hidden /> <HelpCircle className="h-3.5 w-3.5" aria-hidden />
</button> </button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="top" className="max-w-xs text-[11px] leading-snug"> <TooltipContent side="top" className="max-w-xs text-xs leading-snug">
Mark this berth as one your client is actively considering. When on, the berth Mark this berth as one your client is actively considering. When on, the berth
appears as <strong>Under Offer</strong> on the public map and counts toward the appears as <strong>Under Offer</strong> on the public map and counts toward the
recommender&apos;s &quot;heat&quot; score. Turn off if the link is legal/EOI-only. recommender&apos;s &quot;heat&quot; score. Turn off if the link is legal/EOI-only.
@@ -413,7 +413,7 @@ function LinkedBerthRowItem({
<HelpCircle className="h-3.5 w-3.5" aria-hidden /> <HelpCircle className="h-3.5 w-3.5" aria-hidden />
</button> </button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side="top" className="max-w-xs text-[11px] leading-snug"> <TooltipContent side="top" className="max-w-xs text-xs leading-snug">
Include this berth in the EOI&apos;s signed berth range. When on, the berth is Include this berth in the EOI&apos;s signed berth range. When on, the berth is
covered by the same signature and shows up in the EOI&apos;s{' '} covered by the same signature and shows up in the EOI&apos;s{' '}
<strong>Berth Range</strong> form field (e.g. &quot;A1-A3, B5-B7&quot;). Turn off <strong>Berth Range</strong> form field (e.g. &quot;A1-A3, B5-B7&quot;). Turn off
@@ -786,7 +786,7 @@ function BerthSection({
) : null} ) : null}
</h4> </h4>
</div> </div>
<p className="text-[11px] text-muted-foreground">{hint}</p> <p className="text-xs text-muted-foreground">{hint}</p>
</div> </div>
{count === 0 && emptyText ? ( {count === 0 && emptyText ? (
<p className="rounded-md border border-dashed bg-muted/30 px-3 py-2 text-xs text-muted-foreground"> <p className="rounded-md border border-dashed bg-muted/30 px-3 py-2 text-xs text-muted-foreground">

View File

@@ -44,7 +44,7 @@ export function MultiEoiChip({ interestId }: { interestId: string }) {
return ( return (
<span <span
title={`This interest has ${inflight.length} in-flight EOI documents - review on the EOI tab.`} title={`This interest has ${inflight.length} in-flight EOI documents - review on the EOI tab.`}
className="inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-[11px] font-medium text-amber-800" className="inline-flex items-center gap-1 rounded-full border border-amber-200 bg-amber-50 px-2 py-0.5 text-xs font-medium text-amber-800"
> >
<FileSignature className="size-3" aria-hidden /> <FileSignature className="size-3" aria-hidden />
{inflight.length} EOIs {inflight.length} EOIs

View File

@@ -1,8 +1,8 @@
'use client'; 'use client';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Plus, Trash2, Receipt } from 'lucide-react'; import { ChevronDown, ChevronRight, Plus, Trash2, Receipt } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { DatePicker } from '@/components/ui/date-picker'; import { DatePicker } from '@/components/ui/date-picker';
@@ -77,6 +77,40 @@ function formatDate(iso: string): string {
return new Date(iso).toLocaleDateString(); return new Date(iso).toLocaleDateString();
} }
/**
* Per-interest collapse state. Persisted to localStorage so a rep
* who collapses the section on one deal sees it stay collapsed when
* they navigate away and back. Default collapsed (deposits are a
* reference history once the rep stops actively recording them); the
* "add a deposit" flow auto-expands so the new row is visible.
*/
function usePaymentsCollapsed(interestId: string): readonly [boolean, (v: boolean) => void] {
const storageKey = `pn-crm.payments-collapsed.v1.${interestId}`;
const [collapsed, setCollapsedState] = useState<boolean>(true);
// Canonical client-only hydration pattern: server renders the default
// (true), then on mount we read localStorage and reconcile. Initializing
// useState() lazily from localStorage would risk a hydration mismatch
// since the server has no access to the user's browser storage.
useEffect(() => {
try {
const raw = window.localStorage.getItem(storageKey);
// eslint-disable-next-line react-hooks/set-state-in-effect
if (raw !== null) setCollapsedState(raw === '1');
} catch {
// ignore — localStorage may be unavailable in private mode
}
}, [storageKey]);
function setCollapsed(v: boolean): void {
setCollapsedState(v);
try {
window.localStorage.setItem(storageKey, v ? '1' : '0');
} catch {
// ignore
}
}
return [collapsed, setCollapsed] as const;
}
export function PaymentsSection({ export function PaymentsSection({
interestId, interestId,
depositExpectedAmount, depositExpectedAmount,
@@ -88,6 +122,7 @@ export function PaymentsSection({
}) { }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [recordOpen, setRecordOpen] = useState(false); const [recordOpen, setRecordOpen] = useState(false);
const [collapsed, setCollapsed] = usePaymentsCollapsed(interestId);
const { data, isLoading } = useQuery<PaymentsResponse>({ const { data, isLoading } = useQuery<PaymentsResponse>({
queryKey: ['interest-payments', interestId], queryKey: ['interest-payments', interestId],
@@ -122,12 +157,65 @@ export function PaymentsSection({
? Math.max(0, expectedAmount - runningTotal) ? Math.max(0, expectedAmount - runningTotal)
: null; : null;
// Collapsed summary bar — drops the full section to one line so the
// milestone strip above gets the rep's primary visual focus. Click
// anywhere on the bar (or the chevron) to expand inline.
if (collapsed) {
const summaryText =
payments.length === 0
? 'Not received yet'
: `${formatMoney(total?.total ?? '0', expectedCurrency)} received · ${payments.length} payment${payments.length === 1 ? '' : 's'}`;
return (
<>
<button
type="button"
onClick={() => setCollapsed(false)}
className="group flex w-full items-center justify-between gap-3 rounded-lg border bg-card/40 px-4 py-3 text-left transition-colors hover:bg-card/60"
aria-expanded={false}
aria-controls={`payments-section-${interestId}`}
>
<span className="flex items-center gap-2">
<ChevronRight
className="size-4 text-muted-foreground transition-transform group-hover:translate-x-0.5"
aria-hidden
/>
<span className="text-sm font-semibold">Payments</span>
<span className="text-xs text-muted-foreground">· {summaryText}</span>
</span>
<span className="text-xs text-muted-foreground">Expand</span>
</button>
<RecordPaymentSheet
open={recordOpen}
onOpenChange={setRecordOpen}
interestId={interestId}
defaultCurrency={expectedCurrency}
onRecorded={() => setCollapsed(false)}
/>
</>
);
}
return ( return (
<section className="rounded-lg border bg-card/40 p-4 space-y-3"> <section
id={`payments-section-${interestId}`}
className="rounded-lg border bg-card/40 p-4 space-y-3"
>
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<div> <div className="flex-1">
<h3 className="text-sm font-semibold">Payments</h3> <button
<p className="text-xs text-muted-foreground"> type="button"
onClick={() => setCollapsed(true)}
className="group inline-flex items-center gap-2 text-left"
aria-expanded={true}
aria-controls={`payments-section-${interestId}`}
>
<ChevronDown
className="size-4 text-muted-foreground transition-transform group-hover:translate-y-0.5"
aria-hidden
/>
<h3 className="text-sm font-semibold">Payments</h3>
</button>
<p className="mt-0.5 text-xs text-muted-foreground">
Records that money was received or refunded. No invoices are issued - the bank handles Records that money was received or refunded. No invoices are issued - the bank handles
that. that.
</p> </p>
@@ -169,7 +257,7 @@ export function PaymentsSection({
<li key={p.id} className="flex items-center justify-between gap-3 px-3 py-2"> <li key={p.id} className="flex items-center justify-between gap-3 px-3 py-2">
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<span <span
className={`inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium ${ className={`inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium ${
TYPE_TINT[p.paymentType] ?? TYPE_TINT.other TYPE_TINT[p.paymentType] ?? TYPE_TINT.other
}`} }`}
> >
@@ -213,6 +301,7 @@ export function PaymentsSection({
onOpenChange={setRecordOpen} onOpenChange={setRecordOpen}
interestId={interestId} interestId={interestId}
defaultCurrency={expectedCurrency} defaultCurrency={expectedCurrency}
onRecorded={() => setCollapsed(false)}
/> />
</section> </section>
); );
@@ -223,11 +312,13 @@ function RecordPaymentSheet({
onOpenChange, onOpenChange,
interestId, interestId,
defaultCurrency, defaultCurrency,
onRecorded,
}: { }: {
open: boolean; open: boolean;
onOpenChange: (v: boolean) => void; onOpenChange: (v: boolean) => void;
interestId: string; interestId: string;
defaultCurrency: string; defaultCurrency: string;
onRecorded?: () => void;
}) { }) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [paymentType, setPaymentType] = useState<string>('deposit'); const [paymentType, setPaymentType] = useState<string>('deposit');
@@ -257,7 +348,7 @@ function RecordPaymentSheet({
queryClient.invalidateQueries({ queryKey: ['interest-payments', interestId] }); queryClient.invalidateQueries({ queryKey: ['interest-payments', interestId] });
queryClient.invalidateQueries({ queryKey: ['interests', interestId] }); queryClient.invalidateQueries({ queryKey: ['interests', interestId] });
onOpenChange(false); onOpenChange(false);
// Reset form for next use onRecorded?.();
setAmount(''); setAmount('');
setNotes(''); setNotes('');
setAcknowledgedNoReceipt(false); setAcknowledgedNoReceipt(false);

View File

@@ -188,7 +188,7 @@ export function QualificationChecklist({
</span> </span>
{c.autoSatisfied && ( {c.autoSatisfied && (
<span <span
className="rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200" className="rounded bg-emerald-100 px-1.5 py-0.5 text-xs font-medium uppercase tracking-wide text-emerald-800 dark:bg-emerald-950 dark:text-emerald-200"
title="System-derived from data on this interest" title="System-derived from data on this interest"
> >
Auto Auto

View File

@@ -174,7 +174,7 @@ export function SupplementalInfoRequestButton({ interestId, eoiStatus }: Props)
submitted. Hidden when the list is empty. */} submitted. Hidden when the list is empty. */}
{tokens.length > 0 ? ( {tokens.length > 0 ? (
<div className="space-y-1 border-t pt-2"> <div className="space-y-1 border-t pt-2">
<div className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <div className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Issuance history Issuance history
</div> </div>
<ul className="divide-y"> <ul className="divide-y">

View File

@@ -129,7 +129,7 @@ export function Inbox() {
key={combined} key={combined}
data-testid="inbox-bell-badge" data-testid="inbox-bell-badge"
className={cn( className={cn(
'absolute -top-0.5 -right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-bold text-white shadow-sm ring-2 ring-background animate-badge-pop', 'absolute -top-0.5 -right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-xs font-bold text-white shadow-sm ring-2 ring-background animate-badge-pop',
// Critical system alerts win the colour war - they need the // Critical system alerts win the colour war - they need the
// most attention. Otherwise the brand gradient. // most attention. Otherwise the brand gradient.
systemCritical > 0 ? 'bg-destructive' : 'bg-gradient-brand', systemCritical > 0 ? 'bg-destructive' : 'bg-gradient-brand',
@@ -149,7 +149,7 @@ export function Inbox() {
<Bell className="h-3 w-3" aria-hidden /> <Bell className="h-3 w-3" aria-hidden />
Personal Personal
{personalUnread > 0 ? ( {personalUnread > 0 ? (
<span className="ml-1 rounded-full bg-brand/15 px-1.5 text-[10px] font-semibold text-brand"> <span className="ml-1 rounded-full bg-brand/15 px-1.5 text-xs font-semibold text-brand">
{personalUnread > 99 ? '99+' : personalUnread} {personalUnread > 99 ? '99+' : personalUnread}
</span> </span>
) : null} ) : null}
@@ -160,7 +160,7 @@ export function Inbox() {
{systemTotal > 0 ? ( {systemTotal > 0 ? (
<span <span
className={cn( className={cn(
'ml-1 rounded-full px-1.5 text-[10px] font-semibold', 'ml-1 rounded-full px-1.5 text-xs font-semibold',
systemCritical > 0 systemCritical > 0
? 'bg-destructive/15 text-destructive' ? 'bg-destructive/15 text-destructive'
: 'bg-amber-500/15 text-amber-600', : 'bg-amber-500/15 text-amber-600',

View File

@@ -111,7 +111,7 @@ export function MoreSheet({
<div className="space-y-4 px-3 pb-4"> <div className="space-y-4 px-3 pb-4">
{groups.map((group) => ( {groups.map((group) => (
<section key={group.label}> <section key={group.label}>
<h3 className="mb-1.5 px-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <h3 className="mb-1.5 px-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{group.label} {group.label}
</h3> </h3>
<ul className="grid grid-cols-3 gap-2"> <ul className="grid grid-cols-3 gap-2">

View File

@@ -365,7 +365,7 @@ function SidebarContent({
<div key={section.title}> <div key={section.title}>
{!collapsed && ( {!collapsed && (
<div className="flex items-center justify-between px-1 mb-1"> <div className="flex items-center justify-between px-1 mb-1">
<span className="text-slate-500 text-[10px] font-semibold uppercase tracking-[0.12em]"> <span className="text-slate-500 text-xs font-semibold uppercase tracking-[0.12em]">
{section.title} {section.title}
</span> </span>
{section.adminRequired && ( {section.adminRequired && (
@@ -457,12 +457,12 @@ function SidebarContent({
</p> </p>
<Badge <Badge
variant="outline" variant="outline"
className="text-[10px] px-1.5 py-0 text-slate-500 border-slate-300 mt-0.5" className="text-xs px-1.5 py-0 text-slate-500 border-slate-300 mt-0.5"
> >
{isSuperAdmin ? 'Super Admin' : formatRole(portRoles[0]?.role?.name)} {isSuperAdmin ? 'Super Admin' : formatRole(portRoles[0]?.role?.name)}
</Badge> </Badge>
{currentPortName && ( {currentPortName && (
<p className="mt-1 text-[10px] text-slate-400 truncate">{currentPortName}</p> <p className="mt-1 text-xs text-slate-400 truncate">{currentPortName}</p>
)} )}
</div> </div>
</button> </button>

View File

@@ -80,7 +80,7 @@ export function NotificationBell() {
{unreadCount > 0 && ( {unreadCount > 0 && (
<span <span
key={unreadCount} key={unreadCount}
className="absolute -top-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-gradient-brand text-[10px] font-bold text-white shadow-sm ring-2 ring-background animate-badge-pop" className="absolute -top-0.5 -right-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-gradient-brand text-xs font-bold text-white shadow-sm ring-2 ring-background animate-badge-pop"
> >
{unreadCount > 99 ? '99+' : unreadCount} {unreadCount > 99 ? '99+' : unreadCount}
</span> </span>

View File

@@ -287,7 +287,7 @@ function ReminderFormBody({
placeholder="e.g. Follow up about EOI, Check insurance docs" placeholder="e.g. Follow up about EOI, Check insurance docs"
required required
/> />
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Short label so future-you knows what this is at a glance. Short label so future-you knows what this is at a glance.
</p> </p>
</div> </div>
@@ -378,7 +378,7 @@ function ReminderFormBody({
<Label className="text-xs text-muted-foreground"> <Label className="text-xs text-muted-foreground">
Attach to client / deal / berth / yacht Attach to client / deal / berth / yacht
</Label> </Label>
<p className="text-[11px] text-muted-foreground"> <p className="text-xs text-muted-foreground">
Linking a reminder pins it onto that record so anyone who opens the page sees it on Linking a reminder pins it onto that record so anyone who opens the page sees it on
the Reminders tab. Useful for &ldquo;chase this client for signed EOI&rdquo;, the Reminders tab. Useful for &ldquo;chase this client for signed EOI&rdquo;,
&ldquo;recheck B12 power capacity before contract&rdquo;, etc. Pick a client first to &ldquo;recheck B12 power capacity before contract&rdquo;, etc. Pick a client first to

View File

@@ -136,7 +136,7 @@ export function ReminderList({ embedded = false }: ReminderListProps = {}) {
header: '', header: '',
cell: ({ row }) => { cell: ({ row }) => {
const config = PRIORITY_CONFIG[row.original.priority]; const config = PRIORITY_CONFIG[row.original.priority];
return <Badge className={`${config.className} text-[10px] px-1.5`}>{config.label}</Badge>; return <Badge className={`${config.className} text-xs px-1.5`}>{config.label}</Badge>;
}, },
size: 70, size: 70,
}, },
@@ -150,7 +150,7 @@ export function ReminderList({ embedded = false }: ReminderListProps = {}) {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-medium">{row.original.title}</span> <span className="font-medium">{row.original.title}</span>
{row.original.autoGenerated && ( {row.original.autoGenerated && (
<Badge variant="outline" className="text-[10px]"> <Badge variant="outline" className="text-xs">
Auto Auto
</Badge> </Badge>
)} )}

View File

@@ -117,7 +117,7 @@ export function RemindersInline(props: RemindersInlineProps) {
<p className="truncate font-medium text-foreground">{r.title}</p> <p className="truncate font-medium text-foreground">{r.title}</p>
<p <p
className={cn( className={cn(
'text-[11px]', 'text-xs',
isPastDue ? 'text-rose-700 font-medium' : 'text-muted-foreground', isPastDue ? 'text-rose-700 font-medium' : 'text-muted-foreground',
)} )}
> >

View File

@@ -355,7 +355,7 @@ export function DashboardReportBuilder({ portSlug, initialFrom, initialTo }: Pro
if (items.length === 0) return null; if (items.length === 0) return null;
return ( return (
<div key={category} className="space-y-1"> <div key={category} className="space-y-1">
<div className="px-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"> <div className="px-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{label} {label}
</div> </div>
<div className="space-y-0.5"> <div className="space-y-0.5">

View File

@@ -118,14 +118,14 @@ export function ReportsList() {
<ReportStatusBadge status={report.status} /> <ReportStatusBadge status={report.status} />
</TableCell> </TableCell>
<TableCell className="text-sm text-muted-foreground"> <TableCell className="text-sm text-muted-foreground">
{new Date(report.createdAt).toLocaleString('en-GB', { {new Date(report.createdAt).toLocaleString(undefined, {
dateStyle: 'short', dateStyle: 'short',
timeStyle: 'short', timeStyle: 'short',
})} })}
</TableCell> </TableCell>
<TableCell className="text-sm text-muted-foreground"> <TableCell className="text-sm text-muted-foreground">
{report.completedAt ? ( {report.completedAt ? (
new Date(report.completedAt).toLocaleString('en-GB', { new Date(report.completedAt).toLocaleString(undefined, {
dateStyle: 'short', dateStyle: 'short',
timeStyle: 'short', timeStyle: 'short',
}) })

View File

@@ -203,7 +203,7 @@ export function ResidentialClientsList() {
<p className="font-medium text-sm truncate">{c.fullName}</p> <p className="font-medium text-sm truncate">{c.fullName}</p>
<span <span
className={cn( className={cn(
'shrink-0 inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide', 'shrink-0 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium uppercase tracking-wide',
c.status === 'active' c.status === 'active'
? 'bg-emerald-100 text-emerald-800' ? 'bg-emerald-100 text-emerald-800'
: c.status === 'inactive' : c.status === 'inactive'

View File

@@ -28,17 +28,15 @@ export function ResidentialInterestCard({
<div className="min-w-0"> <div className="min-w-0">
<p className="truncate text-sm font-medium">{interest.clientName ?? '-'}</p> <p className="truncate text-sm font-medium">{interest.clientName ?? '-'}</p>
<div className="mt-1 flex flex-wrap items-center gap-1.5"> <div className="mt-1 flex flex-wrap items-center gap-1.5">
<Badge variant="secondary" className="text-[10px]"> <Badge variant="secondary" className="text-xs">
{RESIDENTIAL_STAGE_LABELS[interest.pipelineStage] ?? interest.pipelineStage} {RESIDENTIAL_STAGE_LABELS[interest.pipelineStage] ?? interest.pipelineStage}
</Badge> </Badge>
{interest.source ? ( {interest.source ? (
<span className="text-[11px] capitalize text-muted-foreground"> <span className="text-xs capitalize text-muted-foreground">{interest.source}</span>
{interest.source}
</span>
) : null} ) : null}
</div> </div>
</div> </div>
<span className="shrink-0 text-[11px] text-muted-foreground"> <span className="shrink-0 text-xs text-muted-foreground">
{new Date(interest.updatedAt).toLocaleDateString()} {new Date(interest.updatedAt).toLocaleDateString()}
</span> </span>
</div> </div>
@@ -46,7 +44,7 @@ export function ResidentialInterestCard({
<p className="mt-1 line-clamp-2 text-xs text-muted-foreground">{interest.preferences}</p> <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">{interest.preferences}</p>
) : null} ) : null}
{interest.notes ? ( {interest.notes ? (
<p className="mt-1 line-clamp-1 text-xs text-muted-foreground/80">{interest.notes}</p> <p className="mt-1 line-clamp-1 text-xs text-muted-foreground">{interest.notes}</p>
) : null} ) : null}
</Link> </Link>
); );

View File

@@ -630,7 +630,7 @@ function ResultsRegion({
isFocused && 'bg-accent text-foreground', isFocused && 'bg-accent text-foreground',
)} )}
> >
<span className="text-[10px] uppercase tracking-wide text-muted-foreground/70"> <span className="text-xs uppercase tracking-wide text-muted-foreground">
{row.portName} {row.portName}
</span> </span>
<span className="truncate text-foreground"> <span className="truncate text-foreground">
@@ -659,7 +659,7 @@ function ZeroState({ query, portSlug }: { query: string; portSlug: string | null
<p className="text-sm text-muted-foreground mb-3"> <p className="text-sm text-muted-foreground mb-3">
No results for <span className="font-medium text-foreground">&ldquo;{query}&rdquo;</span> No results for <span className="font-medium text-foreground">&ldquo;{query}&rdquo;</span>
</p> </p>
<p className="text-xs uppercase tracking-wide text-muted-foreground/70 mb-2">Quick create</p> <p className="text-xs uppercase tracking-wide text-muted-foreground mb-2">Quick create</p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{/* Land on the list page with `?create=1&prefill_name=…`; the list {/* Land on the list page with `?create=1&prefill_name=…`; the list
page's `useCreateFromUrl` hook pops the create sheet with the page's `useCreateFromUrl` hook pops the create sheet with the
@@ -754,7 +754,7 @@ function ResultRow({
</div> </div>
)} )}
{row.relatedVia && ( {row.relatedVia && (
<div className="text-[11px] italic text-muted-foreground/80 truncate mt-0.5"> <div className="text-xs italic text-muted-foreground truncate mt-0.5">
via {row.relatedVia.label} via {row.relatedVia.label}
</div> </div>
)} )}
@@ -779,7 +779,7 @@ function Badge({
return ( return (
<span <span
className={cn( className={cn(
'inline-flex items-center rounded px-1.5 py-0 text-[10px] font-semibold uppercase tracking-wide', 'inline-flex items-center rounded px-1.5 py-0 text-xs font-semibold uppercase tracking-wide',
cls, cls,
)} )}
> >
@@ -790,7 +790,7 @@ function Badge({
function SectionHeading({ icon: Icon, children }: { icon: typeof User; children: ReactNode }) { function SectionHeading({ icon: Icon, children }: { icon: typeof User; children: ReactNode }) {
return ( return (
<div className="flex items-center gap-1.5 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <div className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
<Icon className="h-3 w-3" /> <Icon className="h-3 w-3" />
<span>{children}</span> <span>{children}</span>
</div> </div>

View File

@@ -483,7 +483,7 @@ function renderResultRows(
nodes.push( nodes.push(
<div <div
key={`__bucket_${row.bucket}_${i}`} key={`__bucket_${row.bucket}_${i}`}
className="px-3 pt-3 pb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground" className="px-3 pt-3 pb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground"
> >
{BUCKET_LABELS[row.bucket] ?? row.bucket} {BUCKET_LABELS[row.bucket] ?? row.bucket}
</div>, </div>,
@@ -500,7 +500,7 @@ function renderResultRows(
<> <>
{row.sub ? <HighlightMatch text={row.sub} query={query} /> : null} {row.sub ? <HighlightMatch text={row.sub} query={query} /> : null}
{row.relatedVia ? ( {row.relatedVia ? (
<span className="block text-[11px] italic text-muted-foreground/80"> <span className="block text-xs italic text-muted-foreground">
via {row.relatedVia.label} via {row.relatedVia.label}
</span> </span>
) : null} ) : null}
@@ -562,7 +562,7 @@ function Section({
}) { }) {
return ( return (
<section> <section>
<div className="flex items-center gap-1.5 px-3 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"> <div className="flex items-center gap-1.5 px-3 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
{icon} {icon}
{label} {label}
</div> </div>
@@ -604,7 +604,7 @@ function Row({
<span <span
key={b.label} key={b.label}
className={cn( className={cn(
'rounded-full px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide', 'rounded-full px-1.5 py-0.5 text-xs font-medium uppercase tracking-wide',
BADGE_TONE[b.tone], BADGE_TONE[b.tone],
)} )}
> >

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