diff --git a/src/app/(portal)/portal/interests/page.tsx b/src/app/(portal)/portal/interests/page.tsx
index 4a7b0b3d..491d0b4b 100644
--- a/src/app/(portal)/portal/interests/page.tsx
+++ b/src/app/(portal)/portal/interests/page.tsx
@@ -86,11 +86,10 @@ export default async function PortalInterestsPage() {
- {interest.berthArea}
)}
- {interest.leadCategory && (
-
- {interest.leadCategory.replace(/_/g, ' ')}
-
- )}
+ {/* leadCategory ("hot_lead" / "qualified_lead" / etc.)
+ is a staff classification — never render to clients.
+ Privacy + optics: we shouldn't be telling the
+ prospect they're a "hot lead". */}
{interest.dateFirstContact && (
diff --git a/src/components/admin/custom-fields/custom-field-form.tsx b/src/components/admin/custom-fields/custom-field-form.tsx
index 3735943c..17b520a3 100644
--- a/src/components/admin/custom-fields/custom-field-form.tsx
+++ b/src/components/admin/custom-fields/custom-field-form.tsx
@@ -319,7 +319,7 @@ function CustomFieldFormBody({ open, onOpenChange, field, onSuccess }: CustomFie
Cancel
- {loading ? 'Saving...' : isEdit ? 'Save Changes' : 'Create Field'}
+ {loading ? 'Saving…' : isEdit ? 'Save changes' : 'Create Field'}
diff --git a/src/components/admin/document-templates/template-form.tsx b/src/components/admin/document-templates/template-form.tsx
index a98f448c..7cdde4fd 100644
--- a/src/components/admin/document-templates/template-form.tsx
+++ b/src/components/admin/document-templates/template-form.tsx
@@ -197,7 +197,7 @@ export function TemplateForm({ open, onOpenChange, template, onSuccess }: Templa
Cancel
- {loading ? 'Saving…' : isEdit ? 'Save Changes' : 'Create Template'}
+ {loading ? 'Saving…' : isEdit ? 'Save changes' : 'Create Template'}
diff --git a/src/components/admin/ports/port-form.tsx b/src/components/admin/ports/port-form.tsx
index 162ed9a5..d5a34ddc 100644
--- a/src/components/admin/ports/port-form.tsx
+++ b/src/components/admin/ports/port-form.tsx
@@ -208,7 +208,7 @@ function PortFormBody({ open, onOpenChange, port, onSuccess }: PortFormProps) {
Cancel
- {loading ? 'Saving...' : isEdit ? 'Save Changes' : 'Create Port'}
+ {loading ? 'Saving…' : isEdit ? 'Save changes' : 'Create Port'}
diff --git a/src/components/admin/roles/role-form.tsx b/src/components/admin/roles/role-form.tsx
index f2d2a120..b6560511 100644
--- a/src/components/admin/roles/role-form.tsx
+++ b/src/components/admin/roles/role-form.tsx
@@ -307,7 +307,7 @@ function RoleFormBody({ open, onOpenChange, role, onSuccess }: RoleFormProps) {
Cancel
- {loading ? 'Saving...' : isEdit ? 'Save Changes' : 'Create Role'}
+ {loading ? 'Saving…' : isEdit ? 'Save changes' : 'Create Role'}
diff --git a/src/components/admin/settings/settings-manager.tsx b/src/components/admin/settings/settings-manager.tsx
index 7a90047c..58156eb2 100644
--- a/src/components/admin/settings/settings-manager.tsx
+++ b/src/components/admin/settings/settings-manager.tsx
@@ -503,7 +503,7 @@ export function SettingsManager() {
}
}}
>
- {saving === setting.key ? 'Saving...' : 'Save'}
+ {saving === setting.key ? 'Saving…' : 'Save'}
);
diff --git a/src/components/admin/tags/tag-form.tsx b/src/components/admin/tags/tag-form.tsx
index 4a80cccb..07452b9d 100644
--- a/src/components/admin/tags/tag-form.tsx
+++ b/src/components/admin/tags/tag-form.tsx
@@ -144,7 +144,7 @@ export function TagForm({ open, onOpenChange, tag, onSuccess }: TagFormProps) {
Cancel
- {loading ? 'Saving...' : isEdit ? 'Save Changes' : 'Create Tag'}
+ {loading ? 'Saving…' : isEdit ? 'Save changes' : 'Create Tag'}
diff --git a/src/components/admin/users/user-form.tsx b/src/components/admin/users/user-form.tsx
index e54bc323..4dc5f658 100644
--- a/src/components/admin/users/user-form.tsx
+++ b/src/components/admin/users/user-form.tsx
@@ -327,7 +327,7 @@ function UserFormBody({ open, onOpenChange, user, onSuccess }: UserFormProps) {
Cancel
- {loading ? 'Saving...' : isEdit ? 'Save changes' : 'Create user'}
+ {loading ? 'Saving…' : isEdit ? 'Save changes' : 'Create user'}
diff --git a/src/components/admin/webhooks/webhook-form.tsx b/src/components/admin/webhooks/webhook-form.tsx
index 520cceb6..3eebcecd 100644
--- a/src/components/admin/webhooks/webhook-form.tsx
+++ b/src/components/admin/webhooks/webhook-form.tsx
@@ -142,7 +142,7 @@ export function WebhookForm({ open, onOpenChange, webhook, onSuccess }: WebhookF
type="submit"
disabled={loading || !name.trim() || !url.trim() || events.length === 0}
>
- {loading ? 'Saving...' : isEdit ? 'Save Changes' : 'Create Webhook'}
+ {loading ? 'Saving…' : isEdit ? 'Save changes' : 'Create Webhook'}
diff --git a/src/components/berths/berth-detail-header.tsx b/src/components/berths/berth-detail-header.tsx
index 7b5368a0..976e6952 100644
--- a/src/components/berths/berth-detail-header.tsx
+++ b/src/components/berths/berth-detail-header.tsx
@@ -222,16 +222,16 @@ function StatusChangeDialog({
{showInterestPicker && (
-
Linked prospect (optional)
+
Linked interest (optional)
setValue('interestId', id ?? undefined)}
/>
- Link this status change to the prospect (interest) it relates to. The change will
- appear on that interest's timeline, and the berth gets attached to the prospect
- automatically if it wasn't already.
+ Link this status change to the interest it relates to. The change will appear on
+ that interest's timeline, and the berth gets attached to it automatically if it
+ wasn't already.
)}
@@ -240,7 +240,7 @@ function StatusChangeDialog({
Cancel
- {isSubmitting ? 'Saving...' : 'Update Status'}
+ {isSubmitting ? 'Saving…' : 'Update Status'}
diff --git a/src/components/berths/berth-form.tsx b/src/components/berths/berth-form.tsx
index 89a750e5..5a240f33 100644
--- a/src/components/berths/berth-form.tsx
+++ b/src/components/berths/berth-form.tsx
@@ -474,7 +474,7 @@ export function BerthForm({ berth, open, onOpenChange }: BerthFormProps) {
Cancel
- {isSubmitting ? 'Saving...' : 'Save Changes'}
+ {isSubmitting ? 'Saving…' : 'Save changes'}
diff --git a/src/components/berths/berth-interests-tab.tsx b/src/components/berths/berth-interests-tab.tsx
index 06fcde97..248eb93e 100644
--- a/src/components/berths/berth-interests-tab.tsx
+++ b/src/components/berths/berth-interests-tab.tsx
@@ -41,9 +41,9 @@ const CATEGORY_RANK: Record = {
};
const CATEGORY_LABELS: Record = {
- hot_lead: 'Hot Lead',
- specific_qualified: 'Specific Qualified',
- general_interest: 'General Interest',
+ hot_lead: 'Hot lead',
+ specific_qualified: 'Specific qualified',
+ general_interest: 'General interest',
};
interface ListResponse {
diff --git a/src/components/berths/berth-tabs.tsx b/src/components/berths/berth-tabs.tsx
index 51dceedd..9452c50a 100644
--- a/src/components/berths/berth-tabs.tsx
+++ b/src/components/berths/berth-tabs.tsx
@@ -461,7 +461,7 @@ export function buildBerthTabs(berth: BerthData): DetailTab[] {
},
{
id: 'deal-documents',
- label: 'Deal Documents',
+ label: 'Interest Documents',
content: ,
},
// Waiting List + Maintenance Log tabs were stubs ("coming soon")
diff --git a/src/components/clients/bulk-archive-wizard.tsx b/src/components/clients/bulk-archive-wizard.tsx
index 37241b37..a72d2b3d 100644
--- a/src/components/clients/bulk-archive-wizard.tsx
+++ b/src/components/clients/bulk-archive-wizard.tsx
@@ -169,7 +169,7 @@ function BulkArchiveWizardBody({ open, onOpenChange, clientIds, onSuccess }: Pro
Low-stakes defaults: release available/under-offer berths, keep sold ones, cancel
- reservations, leave invoices/signing envelopes alone. Yachts stay on the archived
+ reservations, leave invoices/signing requests alone. Yachts stay on the archived
client. To customise per-client, archive that client individually instead.
diff --git a/src/components/clients/client-form.tsx b/src/components/clients/client-form.tsx
index 83a636bd..5e981d0a 100644
--- a/src/components/clients/client-form.tsx
+++ b/src/components/clients/client-form.tsx
@@ -421,7 +421,7 @@ export function ClientForm({ open, onOpenChange, client, onUseExistingClient }:
{(isSubmitting || mutation.isPending) && (
)}
- {isEdit ? 'Save Changes' : 'Create Client'}
+ {isEdit ? 'Save changes' : 'Create Client'}
diff --git a/src/components/clients/smart-archive-dialog.tsx b/src/components/clients/smart-archive-dialog.tsx
index b3e6ed13..fc9bde5e 100644
--- a/src/components/clients/smart-archive-dialog.tsx
+++ b/src/components/clients/smart-archive-dialog.tsx
@@ -522,12 +522,12 @@ function SmartArchiveDialogBody({
)}
- {/* In-flight signing envelopes */}
+ {/* In-flight signing requests */}
{dossier.documents.filter((d) => d.isInFlight).length > 0 && (
- In-flight signing envelopes
+ In-flight signing requests
@@ -546,8 +546,8 @@ function SmartArchiveDialogBody({
}))
}
>
- Leave envelope pending
- Void the signing envelope
+ Leave signing request pending
+ Cancel the signing request
))}
diff --git a/src/components/companies/company-form.tsx b/src/components/companies/company-form.tsx
index 5189d4d4..dae13853 100644
--- a/src/components/companies/company-form.tsx
+++ b/src/components/companies/company-form.tsx
@@ -461,7 +461,7 @@ export function CompanyForm({ open, onOpenChange, company }: CompanyFormProps) {
{(isSubmitting || mutation.isPending) && (
)}
- {isEdit ? 'Save Changes' : 'Create Company'}
+ {isEdit ? 'Save changes' : 'Create Company'}
diff --git a/src/components/documents/document-detail.tsx b/src/components/documents/document-detail.tsx
index 568ea552..50b49a24 100644
--- a/src/components/documents/document-detail.tsx
+++ b/src/components/documents/document-detail.tsx
@@ -161,7 +161,7 @@ export function DocumentDetail({ documentId, portSlug }: DocumentDetailProps) {
const handleCancel = async () => {
const ok = await confirm({
title: 'Cancel document',
- description: 'Cancel this document? This voids the signing envelope and cannot be undone.',
+ description: 'Cancel this document? This cancels the signing request and cannot be undone.',
confirmLabel: 'Cancel document',
});
if (!ok) return;
diff --git a/src/components/expenses/expense-form-dialog.tsx b/src/components/expenses/expense-form-dialog.tsx
index 2d76e8c6..3480a4cd 100644
--- a/src/components/expenses/expense-form-dialog.tsx
+++ b/src/components/expenses/expense-form-dialog.tsx
@@ -418,7 +418,7 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
{(isSubmitting || mutation.isPending) && (
)}
- {isEdit ? 'Save Changes' : 'Create Expense'}
+ {isEdit ? 'Save changes' : 'Create Expense'}
diff --git a/src/components/interests/interest-contract-tab.tsx b/src/components/interests/interest-contract-tab.tsx
index 81ebfb35..98da4ddc 100644
--- a/src/components/interests/interest-contract-tab.tsx
+++ b/src/components/interests/interest-contract-tab.tsx
@@ -23,6 +23,11 @@ import { SigningProgress } from '@/components/documents/signing-progress';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';
import { useConfirmation } from '@/hooks/use-confirmation';
+import {
+ DOCUMENT_STATUS_ACTIVE,
+ DOCUMENT_STATUS_LABELS,
+ type DocumentStatus,
+} from '@/lib/labels/document-status';
import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/ui-store';
@@ -35,7 +40,7 @@ interface DocumentRow {
id: string;
documentType: string;
title: string;
- status: 'draft' | 'sent' | 'partially_signed' | 'completed' | 'expired' | 'cancelled';
+ status: DocumentStatus;
createdAt: string;
signers?: Array<{ status: string }>;
}
@@ -50,16 +55,9 @@ interface DocumentSigner {
signedAt?: string | null;
}
-const STATUS_LABELS: Record = {
- draft: 'Draft',
- sent: 'Awaiting signatures',
- partially_signed: 'Partially signed',
- completed: 'Signed',
- expired: 'Expired',
- cancelled: 'Cancelled',
-};
+const STATUS_LABELS = DOCUMENT_STATUS_LABELS;
-const STATUS_TONES: Record = {
+const STATUS_TONES: Record = {
draft: 'bg-slate-100 text-slate-700',
sent: 'bg-blue-100 text-blue-700',
partially_signed: 'bg-amber-100 text-amber-800',
@@ -68,7 +66,7 @@ const STATUS_TONES: Record = {
cancelled: 'bg-slate-200 text-slate-600',
};
-const ACTIVE_STATUSES = new Set(['draft', 'sent', 'partially_signed']);
+const ACTIVE_STATUSES = DOCUMENT_STATUS_ACTIVE;
/**
* Dedicated Contract workspace tab. Mirrors the EOI tab pattern but
diff --git a/src/components/interests/interest-detail-header.tsx b/src/components/interests/interest-detail-header.tsx
index 939aee71..f3c0439e 100644
--- a/src/components/interests/interest-detail-header.tsx
+++ b/src/components/interests/interest-detail-header.tsx
@@ -54,8 +54,8 @@ function resolveOutcomeBadge(outcome: string | null | undefined) {
const CATEGORY_LABELS: Record = {
general_interest: 'General',
- specific_qualified: 'Specific Qualified',
- hot_lead: 'Hot Lead',
+ specific_qualified: 'Specific qualified',
+ hot_lead: 'Hot lead',
};
interface InterestDetailHeaderProps {
diff --git a/src/components/interests/interest-eoi-tab.tsx b/src/components/interests/interest-eoi-tab.tsx
index 14064a00..317b6d4e 100644
--- a/src/components/interests/interest-eoi-tab.tsx
+++ b/src/components/interests/interest-eoi-tab.tsx
@@ -24,6 +24,11 @@ import { SigningProgress } from '@/components/documents/signing-progress';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';
import { useConfirmation } from '@/hooks/use-confirmation';
+import {
+ DOCUMENT_STATUS_ACTIVE,
+ DOCUMENT_STATUS_LABELS,
+ type DocumentStatus,
+} from '@/lib/labels/document-status';
import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/ui-store';
@@ -37,7 +42,7 @@ interface DocumentRow {
id: string;
documentType: string;
title: string;
- status: 'draft' | 'sent' | 'partially_signed' | 'completed' | 'expired' | 'cancelled';
+ status: DocumentStatus;
createdAt: string;
signers?: Array<{ status: string }>;
}
@@ -52,16 +57,9 @@ interface DocumentSigner {
signedAt?: string | null;
}
-const STATUS_LABELS: Record = {
- draft: 'Draft',
- sent: 'Awaiting signatures',
- partially_signed: 'Partially signed',
- completed: 'Signed',
- expired: 'Expired',
- cancelled: 'Cancelled',
-};
+const STATUS_LABELS = DOCUMENT_STATUS_LABELS;
-const STATUS_TONES: Record = {
+const STATUS_TONES: Record = {
draft: 'bg-slate-100 text-slate-700',
sent: 'bg-blue-100 text-blue-700',
partially_signed: 'bg-amber-100 text-amber-800',
@@ -70,7 +68,7 @@ const STATUS_TONES: Record = {
cancelled: 'bg-slate-200 text-slate-600',
};
-const ACTIVE_STATUSES = new Set(['draft', 'sent', 'partially_signed']);
+const ACTIVE_STATUSES = DOCUMENT_STATUS_ACTIVE;
/**
* Dedicated EOI workspace tab. The user's "where do I generate / track
diff --git a/src/components/interests/interest-filters.tsx b/src/components/interests/interest-filters.tsx
index 96df579c..0c74f7c8 100644
--- a/src/components/interests/interest-filters.tsx
+++ b/src/components/interests/interest-filters.tsx
@@ -2,9 +2,9 @@ import type { FilterDefinition } from '@/components/shared/filter-bar';
import { PIPELINE_STAGES, STAGE_LABELS, LEAD_CATEGORIES } from '@/lib/constants';
const CATEGORY_LABELS: Record = {
- general_interest: 'General Interest',
- specific_qualified: 'Specific Qualified',
- hot_lead: 'Hot Lead',
+ general_interest: 'General interest',
+ specific_qualified: 'Specific qualified',
+ hot_lead: 'Hot lead',
};
export const interestFilterDefinitions: FilterDefinition[] = [
diff --git a/src/components/interests/interest-form.tsx b/src/components/interests/interest-form.tsx
index 3dbb60ef..389a6c33 100644
--- a/src/components/interests/interest-form.tsx
+++ b/src/components/interests/interest-form.tsx
@@ -51,9 +51,9 @@ import { PIPELINE_STAGES, STAGE_LABELS, LEAD_CATEGORIES, SOURCES } from '@/lib/c
import { cn } from '@/lib/utils';
const CATEGORY_LABELS: Record = {
- general_interest: 'General Interest',
- specific_qualified: 'Specific Qualified',
- hot_lead: 'Hot Lead',
+ general_interest: 'General interest',
+ specific_qualified: 'Specific qualified',
+ hot_lead: 'Hot lead',
};
interface InterestFormProps {
@@ -583,7 +583,7 @@ export function InterestForm({ open, onOpenChange, defaultClientId, interest }:
{(isSubmitting || mutation.isPending) && (
)}
- {isEdit ? 'Save Changes' : 'Create Interest'}
+ {isEdit ? 'Save changes' : 'Create Interest'}
diff --git a/src/components/interests/interest-reservation-tab.tsx b/src/components/interests/interest-reservation-tab.tsx
index f31db964..22757ae6 100644
--- a/src/components/interests/interest-reservation-tab.tsx
+++ b/src/components/interests/interest-reservation-tab.tsx
@@ -23,6 +23,11 @@ import { SigningProgress } from '@/components/documents/signing-progress';
import { apiFetch } from '@/lib/api/client';
import { toastError } from '@/lib/api/toast-error';
import { useConfirmation } from '@/hooks/use-confirmation';
+import {
+ DOCUMENT_STATUS_ACTIVE,
+ DOCUMENT_STATUS_LABELS,
+ type DocumentStatus,
+} from '@/lib/labels/document-status';
import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/ui-store';
@@ -35,7 +40,7 @@ interface DocumentRow {
id: string;
documentType: string;
title: string;
- status: 'draft' | 'sent' | 'partially_signed' | 'completed' | 'expired' | 'cancelled';
+ status: DocumentStatus;
createdAt: string;
signers?: Array<{ status: string }>;
}
@@ -50,16 +55,9 @@ interface DocumentSigner {
signedAt?: string | null;
}
-const STATUS_LABELS: Record = {
- draft: 'Draft',
- sent: 'Awaiting signatures',
- partially_signed: 'Partially signed',
- completed: 'Signed',
- expired: 'Expired',
- cancelled: 'Cancelled',
-};
+const STATUS_LABELS = DOCUMENT_STATUS_LABELS;
-const STATUS_TONES: Record = {
+const STATUS_TONES: Record = {
draft: 'bg-slate-100 text-slate-700',
sent: 'bg-blue-100 text-blue-700',
partially_signed: 'bg-amber-100 text-amber-800',
@@ -68,7 +66,7 @@ const STATUS_TONES: Record = {
cancelled: 'bg-slate-200 text-slate-600',
};
-const ACTIVE_STATUSES = new Set(['draft', 'sent', 'partially_signed']);
+const ACTIVE_STATUSES = DOCUMENT_STATUS_ACTIVE;
/**
* Dedicated Reservation workspace tab. Mirrors the EOI tab pattern but
diff --git a/src/components/reminders/reminder-form.tsx b/src/components/reminders/reminder-form.tsx
index 0c85c436..c7f173c0 100644
--- a/src/components/reminders/reminder-form.tsx
+++ b/src/components/reminders/reminder-form.tsx
@@ -263,7 +263,7 @@ function ReminderFormBody({
Cancel
- {loading ? 'Saving...' : isEdit ? 'Save Changes' : 'Create Reminder'}
+ {loading ? 'Saving…' : isEdit ? 'Save changes' : 'Create Reminder'}
diff --git a/src/components/yachts/yacht-form.tsx b/src/components/yachts/yacht-form.tsx
index aaf38ac5..55d299b9 100644
--- a/src/components/yachts/yacht-form.tsx
+++ b/src/components/yachts/yacht-form.tsx
@@ -361,7 +361,7 @@ export function YachtForm({ open, onOpenChange, yacht, initialOwner }: YachtForm
{(isSubmitting || mutation.isPending) && (
)}
- {isEdit ? 'Save Changes' : 'Create Yacht'}
+ {isEdit ? 'Save changes' : 'Create Yacht'}
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index 2f3b1f0a..2c25b29d 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -184,9 +184,9 @@ const LABEL_OVERRIDES: Record = {
under_offer: 'Under Offer',
fixed_term: 'Fixed Term',
reservation_agreement: 'Reservation Agreement',
- hot_lead: 'Hot Lead',
- general_interest: 'General Interest',
- specific_qualified: 'Specific Qualified',
+ hot_lead: 'Hot lead',
+ general_interest: 'General interest',
+ specific_qualified: 'Specific qualified',
};
function humanizeEnum(raw: string): string {
diff --git a/src/lib/labels/document-status.ts b/src/lib/labels/document-status.ts
new file mode 100644
index 00000000..0b5105da
--- /dev/null
+++ b/src/lib/labels/document-status.ts
@@ -0,0 +1,87 @@
+/**
+ * Canonical labels + StatusPill tones for the signing-document lifecycle.
+ *
+ * Six surfaces previously carried divergent label sets (interest-eoi-tab,
+ * interest-contract-tab, interest-reservation-tab, documents-hub,
+ * signing-progress, notification-digest, realtime-toast). A signer would
+ * see "Partially signed", "partially_signed", and "EOI fully signed" for
+ * the same enum state across one session. This module is the single
+ * source of truth — import from here, do not redefine inline.
+ *
+ * If a new lifecycle state arrives in the schema, add it here once.
+ */
+
+import type { StatusPillStatus } from '@/components/ui/status-pill';
+
+export type DocumentStatus =
+ | 'draft'
+ | 'sent'
+ | 'partially_signed'
+ | 'completed'
+ | 'expired'
+ | 'cancelled';
+
+/**
+ * Human label rendered in CRM UI (staff-facing). Use the portal-specific
+ * mapping in `documentStatusLabelForPortal` when rendering to clients —
+ * "Awaiting signatures" reads fine on the inside; clients want
+ * "Awaiting your signature".
+ */
+export const DOCUMENT_STATUS_LABELS: Record = {
+ draft: 'Draft',
+ sent: 'Awaiting signatures',
+ partially_signed: 'Partially signed',
+ completed: 'Signed',
+ expired: 'Expired',
+ cancelled: 'Cancelled',
+};
+
+/**
+ * Client-portal labels. The portal previously rendered
+ * `eoiStatus.replace(/_/g, ' ')` so a client saw "EOI: partially signed".
+ * Map to action-oriented copy that the client can act on.
+ */
+export const DOCUMENT_STATUS_LABELS_PORTAL: Record = {
+ draft: 'Pending',
+ sent: 'Awaiting your signature',
+ partially_signed: 'Signed (other parties remaining)',
+ completed: 'Signed',
+ expired: 'Expired',
+ cancelled: 'Cancelled',
+};
+
+/**
+ * StatusPill variant per state. Pairs with ``
+ * via the shared primitive so the colour palette stays consistent with
+ * non-document status pills (berth/user/etc).
+ */
+export const DOCUMENT_STATUS_PILL: Record = {
+ draft: 'draft',
+ sent: 'sent',
+ partially_signed: 'partial',
+ completed: 'completed',
+ expired: 'expired',
+ cancelled: 'cancelled',
+};
+
+/**
+ * The "in-flight" set — useful for hero treatment, banners, "we're
+ * waiting on action" UI. completed/expired/cancelled are terminal.
+ */
+export const DOCUMENT_STATUS_ACTIVE: ReadonlySet = new Set([
+ 'draft',
+ 'sent',
+ 'partially_signed',
+]);
+
+export function documentStatusLabel(status: string): string {
+ return DOCUMENT_STATUS_LABELS[status as DocumentStatus] ?? status;
+}
+
+export function documentStatusLabelForPortal(status: string): string {
+ return DOCUMENT_STATUS_LABELS_PORTAL[status as DocumentStatus] ?? status;
+}
+
+export function documentStatusPill(status: string): StatusPillStatus {
+ return DOCUMENT_STATUS_PILL[status as DocumentStatus] ?? 'pending';
+}