diff --git a/eslint.config.mjs b/eslint.config.mjs index 6b175387..7cf2b5c9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -44,7 +44,10 @@ const eslintConfig = [ files: ['src/components/**/*.tsx', 'src/app/**/*.tsx'], rules: { 'no-restricted-syntax': [ - 'warn', + // Bumped from warn → error after the 2026-05-21 sweep cleared + // the existing 108 instances. New code reintroducing em-dashes + // now fails the lint gate. + 'error', { selector: "JSXText[value=/\\u2014/]", message: diff --git a/src/app/(auth)/setup/page.tsx b/src/app/(auth)/setup/page.tsx index 6fd399da..e02b4fa3 100644 --- a/src/app/(auth)/setup/page.tsx +++ b/src/app/(auth)/setup/page.tsx @@ -91,7 +91,7 @@ export default function SetupPage() { password: data.password, }, }); - toast.success('Administrator account created — sign in to continue.'); + toast.success('Administrator account created - sign in to continue.'); router.replace('/login'); } catch (err) { toast.error(err instanceof Error ? err.message : 'Failed to create administrator account'); @@ -114,7 +114,7 @@ export default function SetupPage() {

Welcome to {appName}

- No administrator account exists yet. Create one to get started — you’ll be the + No administrator account exists yet. Create one to get started - you’ll be the super-administrator for this installation.

diff --git a/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx b/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx index 96bb151f..e0ba4d7f 100644 --- a/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx +++ b/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx @@ -28,7 +28,7 @@ const CONTRACT_RESERVATION_FIELDS: SettingFieldDef[] = [ key: 'documenso_reservation_template_id', label: 'Reservation agreement Documenso template ID (optional)', description: - 'Numeric template ID for reservation agreements. Same logic — leave blank to upload per interest.', + 'Numeric template ID for reservation agreements. Same logic - leave blank to upload per interest.', type: 'string', placeholder: '', defaultValue: '', @@ -44,11 +44,11 @@ const V2_FEATURE_FIELDS: SettingFieldDef[] = [ key: 'documenso_signing_order', label: 'Signing order', description: - 'Whether all signers receive the invitation at once (PARALLEL — anyone can sign first) or only the next pending signer gets the email once the previous one finishes (SEQUENTIAL). Applied at envelope-create time on both v1 and v2: v1 honours meta.signingOrder on /templates/{id}/generate-document; v2 honours it via /envelope/update right after /template/use.', + 'Whether all signers receive the invitation at once (PARALLEL - anyone can sign first) or only the next pending signer gets the email once the previous one finishes (SEQUENTIAL). Applied at envelope-create time on both v1 and v2: v1 honours meta.signingOrder on /templates/{id}/generate-document; v2 honours it via /envelope/update right after /template/use.', type: 'select', options: [ - { value: 'PARALLEL', label: 'PARALLEL — all signers invited at once' }, - { value: 'SEQUENTIAL', label: 'SEQUENTIAL — one at a time in order' }, + { value: 'PARALLEL', label: 'PARALLEL - all signers invited at once' }, + { value: 'SEQUENTIAL', label: 'SEQUENTIAL - one at a time in order' }, ], defaultValue: 'PARALLEL', }, @@ -75,14 +75,14 @@ export default function DocumensoSettingsPage() {

The CRM supports both Documenso 1.13.x (v1) and 2.x (v2). v1 is the default for backwards compatibility. v2 is recommended for new ports and unlocks the features below. - Switching versions does not require any code changes — version-aware + Switching versions does not require any code changes - version-aware client methods pick the right endpoint per port. Switch, save, then run the test-connection button to confirm the chosen instance is actually on the matching Documenso version. @@ -111,7 +111,7 @@ export default function DocumensoSettingsPage() { /> Percent-based field coordinates. No page-dimension lookup needed - — coordinates are portable across page sizes. v1 requires us to assume A4 for + - coordinates are portable across page sizes. v1 requires us to assume A4 for auto-placed fields. @@ -122,7 +122,7 @@ export default function DocumensoSettingsPage() { /> Richer field metadata. TEXT labels & required flags, NUMBER - min/max + format, CHECKBOX/DROPDOWN/RADIO option lists with defaults — all ignored + min/max + format, CHECKBOX/DROPDOWN/RADIO option lists with defaults - all ignored by v1, surfaced by v2 in the signing UI. @@ -134,7 +134,7 @@ export default function DocumensoSettingsPage() { v2-flavoured webhook events. RECIPIENT_VIEWED,{' '} RECIPIENT_SIGNED, DOCUMENT_RECIPIENT_COMPLETED,{' '} - DOCUMENT_DECLINED, DOCUMENT_REMINDER_SENT — all routed + DOCUMENT_DECLINED, DOCUMENT_REMINDER_SENT - all routed through the same dedup + audit pipeline as v1 events. @@ -147,9 +147,9 @@ export default function DocumensoSettingsPage() { Envelope CRUD endpoints. GET, DELETE, POST /envelope/create (multipart),{' '} POST /envelope/distribute, POST /envelope/redistribute,{' '} - GET /envelope/{'{id}'}/download — all routed through{' '} + GET /envelope/{'{id}'}/download - all routed through{' '} /api/v2/envelope/... when v2 is selected. The template-generate path - is intentionally still v1 (relies on Documenso 2.x's backward-compat window — + is intentionally still v1 (relies on Documenso 2.x's backward-compat window - see the deferred-roadmap below). @@ -160,7 +160,7 @@ export default function DocumensoSettingsPage() { /> One-call send. v2's /envelope/distribute{' '} - returns per-recipient signingUrl in the same response — v1 requires a + returns per-recipient signingUrl in the same response - v1 requires a separate GET to fetch them. Faster send flow on the rep side. @@ -186,7 +186,7 @@ export default function DocumensoSettingsPage() { behaviour" card; Documenso redirects the signer to that URL after they complete signing. Use to land clients on the marketing site's success page or back in the portal instead of Documenso's default thank-you page. (v1 honours - this too — listed here because the admin setting was added with the v2 work.) + this too - listed here because the admin setting was added with the v2 work.) @@ -201,7 +201,7 @@ export default function DocumensoSettingsPage() { Single-shot /template/use {' '} - with v2 prefillFields by ID — current EOI flow uses{' '} + with v2 prefillFields by ID - current EOI flow uses{' '} /api/v1/templates/{'{id}'}/generate-document with{' '} formValues keyed by name. v2 instances accept both during their backward-compat window; full migration requires per-template field-ID capture in @@ -211,17 +211,17 @@ export default function DocumensoSettingsPage() { Update envelope metadata after creation (/envelope/update) {' '} - — change title / subject / redirectUrl on a doc already in DRAFT/PENDING without + - change title / subject / redirectUrl on a doc already in DRAFT/PENDING without re-generating.

  • - Non-SIGNER recipient roles (CC / VIEWER) — APPROVER role is already + Non-SIGNER recipient roles (CC / VIEWER) - APPROVER role is already used by the EOI template; CC + VIEWER not yet exposed in the recipient builder. Useful for sales managers who want a copy without a signature slot.
  • - Sequential signing and post-signing redirect URL are now wired — see + Sequential signing and post-signing redirect URL are now wired - see the new "v2 signing behaviour" card below to configure them.

    @@ -244,13 +244,13 @@ export default function DocumensoSettingsPage() { } /> diff --git a/src/app/(dashboard)/[portSlug]/admin/errors/codes/page.tsx b/src/app/(dashboard)/[portSlug]/admin/errors/codes/page.tsx index 61952a0d..3c514039 100644 --- a/src/app/(dashboard)/[portSlug]/admin/errors/codes/page.tsx +++ b/src/app/(dashboard)/[portSlug]/admin/errors/codes/page.tsx @@ -69,7 +69,7 @@ export default function ErrorCodeReferencePage() {

    Every error code the platform can return, with its HTTP status and the plain-language - message a user sees. Codes are stable identifiers — once shipped, they never get + message a user sees. Codes are stable identifiers - once shipped, they never get renamed.

    diff --git a/src/app/(dashboard)/[portSlug]/admin/pipeline-rules/page.tsx b/src/app/(dashboard)/[portSlug]/admin/pipeline-rules/page.tsx index 9b4257f1..b0d07bf4 100644 --- a/src/app/(dashboard)/[portSlug]/admin/pipeline-rules/page.tsx +++ b/src/app/(dashboard)/[portSlug]/admin/pipeline-rules/page.tsx @@ -30,34 +30,34 @@ const TRIGGERS: Array<{ { key: 'eoi_sent', label: 'EOI sent', - description: 'Rep generates an EOI for signing — moves the deal to "EOI" stage.', + description: 'Rep generates an EOI for signing - moves the deal to "EOI" stage.', defaultMode: 'auto', }, { key: 'eoi_signed', label: 'EOI signed (all parties)', description: - 'All signatories complete the EOI — moves the deal to "Reservation" stage. Conventional CRM behaviour.', + 'All signatories complete the EOI - moves the deal to "Reservation" stage. Conventional CRM behaviour.', defaultMode: 'auto', }, { key: 'reservation_signed', label: 'Reservation agreement signed', description: - 'Reservation paperwork signed by all parties — keeps the deal at "Reservation" with sub-status signed.', + 'Reservation paperwork signed by all parties - keeps the deal at "Reservation" with sub-status signed.', defaultMode: 'auto', }, { key: 'deposit_received', label: 'Deposit received in full', description: - 'Deposit total reaches the expected amount — moves the deal to "Deposit Paid" stage.', + 'Deposit total reaches the expected amount - moves the deal to "Deposit Paid" stage.', defaultMode: 'auto', }, { key: 'contract_signed', label: 'Sales contract signed', - description: 'Final contract signed by all parties — moves the deal to "Contract" stage.', + description: 'Final contract signed by all parties - moves the deal to "Contract" stage.', defaultMode: 'auto', }, ]; @@ -166,7 +166,7 @@ export default function PipelineRulesPage() { >

    Custom

    - Mix and match — the per-trigger toggles below override the preset. + Mix and match - the per-trigger toggles below override the preset.

    diff --git a/src/app/(dashboard)/[portSlug]/admin/pulse/page.tsx b/src/app/(dashboard)/[portSlug]/admin/pulse/page.tsx index 4faba48a..2003201d 100644 --- a/src/app/(dashboard)/[portSlug]/admin/pulse/page.tsx +++ b/src/app/(dashboard)/[portSlug]/admin/pulse/page.tsx @@ -24,7 +24,7 @@ export default function PulseAdminPage() {

    Every interest row carries a small coloured chip in the detail header. It scores the deal from 0–100 using rule-based signals (no AI). Click the chip on any interest to see - the per-signal breakdown — every +N or -N traces back to a dated event on the deal. + the per-signal breakdown - every +N or -N traces back to a dated event on the deal.

    Positive signals (recent EOI sent, deposit received, contract signed) push the score up. diff --git a/src/app/(dashboard)/[portSlug]/expenses/scan/page.tsx b/src/app/(dashboard)/[portSlug]/expenses/scan/page.tsx index 3e124914..228f70cf 100644 --- a/src/app/(dashboard)/[portSlug]/expenses/scan/page.tsx +++ b/src/app/(dashboard)/[portSlug]/expenses/scan/page.tsx @@ -206,14 +206,14 @@ export default function ScanReceiptPage() { )} {uploadMutation.isError && ( - Receipt upload failed — save will still create the expense without an image. + Receipt upload failed - save will still create the expense without an image. )} ) : (

    - {/* Camera button — available on mobile devices that surface the + {/* Camera button - available on mobile devices that surface the built-in capture flow when an `image/*` input has the `capture` attribute. Hidden on desktop where it's a no-op. */} - {/* File picker — works on every platform. Phrased so the copy + {/* File picker - works on every platform. Phrased so the copy fits both mobile (library/files) and desktop (drag and drop). */}
    )} - {/* `image/*` is the broadest accept — includes HEIC on iOS, + {/* `image/*` is the broadest accept - includes HEIC on iOS, JPEG/PNG/WebP everywhere. The capture attribute on the second input invokes the native camera flow on mobile. */} Couldn't read this receipt automatically.{' '} - You can still fill in the details manually below — the receipt image will save with + You can still fill in the details manually below - the receipt image will save with the expense. )} diff --git a/src/app/(portal)/portal/profile/page.tsx b/src/app/(portal)/portal/profile/page.tsx index 847bed88..c5fb3990 100644 --- a/src/app/(portal)/portal/profile/page.tsx +++ b/src/app/(portal)/portal/profile/page.tsx @@ -25,7 +25,7 @@ export default async function PortalProfilePage() { {session.email}

    - To update name, phone, or address, please contact your port team — they keep the records + To update name, phone, or address, please contact your port team - they keep the records authoritative.

    diff --git a/src/app/docs/deal-pulse/page.tsx b/src/app/docs/deal-pulse/page.tsx index d8362559..8aad3b79 100644 --- a/src/app/docs/deal-pulse/page.tsx +++ b/src/app/docs/deal-pulse/page.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Deal Pulse & Heat — Port Nimara CRM', + title: 'Deal Pulse & Heat - Port Nimara CRM', description: 'How the deal pulse chip + heat score work: signals, calibration, and what to do when a deal goes cold.', }; @@ -35,7 +35,7 @@ export default function DealPulseDocsPage() {

    The colored chip on each interest is a fast read of{' '} how hot the deal is right now based on what's been happening on it - lately — not a prediction, not an AI score, just a mechanical rollup of recent activity. + lately - not a prediction, not an AI score, just a mechanical rollup of recent activity.

    @@ -53,7 +53,7 @@ export default function DealPulseDocsPage() {
    Warm
    Activity in the last 14–30 days. The deal isn't neglected but the cadence has - slowed — usually means a follow-up reminder is the right next action. + slowed - usually means a follow-up reminder is the right next action.
    @@ -86,7 +86,7 @@ export default function DealPulseDocsPage() {
  • Time at current stage. Stagnation drags the score down even if other - signals look good — a deal stuck at Reservation for six weeks should not read hot. + signals look good - a deal stuck at Reservation for six weeks should not read hot.
  • @@ -128,7 +128,7 @@ export default function DealPulseDocsPage() { Can I override the chip on a specific deal?

    - Not directly — the chip is a read-only summary. To change it, change the inputs: log a + Not directly - the chip is a read-only summary. To change it, change the inputs: log a contact, advance a stage, or close the deal.

    diff --git a/src/components/admin/audit/audit-log-list.tsx b/src/components/admin/audit/audit-log-list.tsx index ee3c4ff4..6602f41a 100644 --- a/src/components/admin/audit/audit-log-list.tsx +++ b/src/components/admin/audit/audit-log-list.tsx @@ -354,7 +354,7 @@ export function AuditLogList() { row.original.ipAddress ? ( {row.original.ipAddress} ) : ( - + - ), size: 130, }, @@ -457,7 +457,7 @@ export function AuditLogList() { Job failed Cron run {/* L-AU02: actions that fire in the code but were missing from - the dropdown — reps couldn't filter on them. */} + the dropdown - reps couldn't filter on them. */} Password change Portal invite Portal activate @@ -585,7 +585,7 @@ export function AuditLogList() { {dateRangeInvalid && (

    - From date must be on or before To date — date filter ignored. + From date must be on or before To date - date filter ignored.

    )} @@ -642,7 +642,7 @@ export function AuditLogList() { <> - {detailEntry.action.replace(/_/g, ' ')} — {detailEntry.entityType} + {detailEntry.action.replace(/_/g, ' ')} - {detailEntry.entityType} {formatDate(detailEntry.createdAt, 'datetime.medium')} diff --git a/src/components/admin/backup-admin-panel.tsx b/src/components/admin/backup-admin-panel.tsx index f889abac..df9b8d0b 100644 --- a/src/components/admin/backup-admin-panel.tsx +++ b/src/components/admin/backup-admin-panel.tsx @@ -121,7 +121,7 @@ export function BackupAdminPanel() { Backups land at backups/<id>.dump via{' '} - getStorageBackend().put(). Restore is intentionally not exposed in the UI — + getStorageBackend().put(). Restore is intentionally not exposed in the UI - download the .dump file and run pg_restore manually. diff --git a/src/components/admin/bulk-add-berths-wizard.tsx b/src/components/admin/bulk-add-berths-wizard.tsx index 3b0e9613..85b10cf5 100644 --- a/src/components/admin/bulk-add-berths-wizard.tsx +++ b/src/components/admin/bulk-add-berths-wizard.tsx @@ -192,7 +192,7 @@ export function BulkAddBerthsWizard() { return ( - Step 1 — Sequence + Step 1 - Sequence Pick the dock letter and the mooring-number range. Tenure + status apply to every row; everything else (dimensions, pricing, pontoon) is filled per row in Step 2. @@ -265,7 +265,7 @@ export function BulkAddBerthsWizard() { return ( - Step 2 — Fill in each row + Step 2 - Fill in each row Per-row dimensions, pricing, pontoon. Use the “Apply to all” inputs in the header to copy a value down every row at once. @@ -435,7 +435,7 @@ export function BulkAddBerthsWizard() { - + - {sidePontoonOptions.filter(Boolean).map((p) => ( {p} diff --git a/src/components/admin/custom-fields/custom-fields-manager.tsx b/src/components/admin/custom-fields/custom-fields-manager.tsx index 03442d6d..d8906a75 100644 --- a/src/components/admin/custom-fields/custom-fields-manager.tsx +++ b/src/components/admin/custom-fields/custom-fields-manager.tsx @@ -171,7 +171,7 @@ export function CustomFieldsManager() { the form {`{{custom.fieldName}}`} now expand in EOI/contract/email templates for client/interest/berth contexts. They still don’t plug into the global search index, the berth recommender, or the entity-diff - audit log — use them for rep-only annotations and template-merge values, but anything + audit log - use them for rep-only annotations and template-merge values, but anything load-bearing for the deal flow still needs a first-class column. diff --git a/src/components/admin/documenso/embedded-signing-card.tsx b/src/components/admin/documenso/embedded-signing-card.tsx index 0b91fa9d..ef239409 100644 --- a/src/components/admin/documenso/embedded-signing-card.tsx +++ b/src/components/admin/documenso/embedded-signing-card.tsx @@ -72,7 +72,7 @@ export function EmbeddedSigningCard() { }; setResult({ ...res.data, at: new Date() }); if (res.data.ok) toast.success('Embedded signing host reachable.'); - else toast.error('Embedded signing host probe failed — see card.'); + else toast.error('Embedded signing host probe failed - see card.'); } catch (err) { toastError(err); setResult({ @@ -200,7 +200,7 @@ export function EmbeddedSigningCard() {

    The marketing site needs to handle /sign/[role]/[token] by forwarding to the underlying Documenso signing URL (or embedding it in an iframe). Role is one - of client / developer / approver — useful for + of client / developer / approver - useful for tracking which slot the signer is in.

    Minimum Next.js example:

    @@ -228,7 +228,7 @@ export default function SignPage({ params }) {

    Use the Test connection button to verify / and{' '} /sign/success return 2xx. If either fails, the marketing site - isn't ready — fix the route before flipping live or signers will land on a 404 + isn't ready - fix the route before flipping live or signers will land on a 404 page from outbound emails.

    diff --git a/src/components/admin/documenso/template-sync-button.tsx b/src/components/admin/documenso/template-sync-button.tsx index d114b774..41266400 100644 --- a/src/components/admin/documenso/template-sync-button.tsx +++ b/src/components/admin/documenso/template-sync-button.tsx @@ -106,7 +106,7 @@ export function TemplateSyncButton() { onSuccess: (result) => { setLastResult(result); toast.success( - `Synced "${result.title}" — ${result.recipients.length} recipients, ${result.fieldCount} fields cached`, + `Synced "${result.title}" - ${result.recipients.length} recipients, ${result.fieldCount} fields cached`, ); void queryClient.invalidateQueries({ queryKey: ['settings', 'resolved'] }); void queryClient.invalidateQueries({ @@ -218,7 +218,7 @@ export function TemplateSyncButton() {
    Template-level settings

    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 -{' '} /template/use does not accept overrides for these. Change them in Documenso's template editor.

    @@ -236,7 +236,7 @@ export function TemplateSyncButton() { {lastResult.templateMeta.distributionMethod === 'EMAIL' && ( - ⚠️ Documenso will email recipients directly — the CRM's branded email + ⚠️ Documenso will email recipients directly - the CRM's branded email is in addition. Set to NONE on the template to let the CRM be the sole sender. @@ -256,7 +256,7 @@ export function TemplateSyncButton() { Fields: {lastResult.fieldCount} cached for prefillFields {lastResult.fieldCount === 0 && ( - — that's fine if your template is a fillable PDF (AcroForm). The CRM will + - that's fine if your template is a fillable PDF (AcroForm). The CRM will fill it via formValues-by-name instead, same as on v1.{' '} prefillFields is only needed if you placed field overlays directly in the Documenso template editor. @@ -314,7 +314,7 @@ export function TemplateSyncButton() {

    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.

    {lastResult.acroForm.map((report) => ( @@ -427,7 +427,7 @@ export function TemplateSyncButton() { {sync.isError && !lastResult && (
    - Sync failed — check the Documenso credentials above and + Sync failed - check the Documenso credentials above and confirm the template exists on the configured instance.
    diff --git a/src/components/admin/document-templates/template-form.tsx b/src/components/admin/document-templates/template-form.tsx index 7cdde4fd..fa1510e5 100644 --- a/src/components/admin/document-templates/template-form.tsx +++ b/src/components/admin/document-templates/template-form.tsx @@ -155,7 +155,7 @@ export function TemplateForm({ open, onOpenChange, template, onSuccess }: Templa

    Paste or edit TipTap JSON. Use{' '} {'{{scope.field}}'} tokens for - dynamic content — see the list below. + dynamic content - see the list below.