Bundles the prior session's 50-task fix sweep (Documenso v2 + EOI/signing-
progress redesign + env-to-admin migration + dev-mode banner) with the
2026-05-18 audit fix wave (3 CRITICAL, 14 HIGH, 28 MEDIUM, 6 LOW).
CRITICAL (3):
- C-01 interest-berths INNER JOIN -> LEFT JOIN so hard-deleted berths
no longer silently drop interest links
- C-02 /setup added to PUBLIC_PATHS; fresh-deploy bootstrap loop fixed
- C-03 generic PATCH /interests/[id] no longer accepts pipelineStage —
callers must go through /stage with the override-guard chain
HIGH (14/15):
- H-01 explicit ON DELETE on previously-implicit NO ACTION FKs across
interests/documents/reservations/reminders/invoices (migration 0070)
- H-02 login page reads ?redirect= param with same-origin guard
- H-03 CRM invite token moves to URL fragment so it never lands in
nginx access logs / Referer headers
- H-04 Retry-After header on sign-in-by-identifier 429 (RFC 6585 §4)
- H-05 toggleAccount writes an audit row
- H-06 upsertSetting masks any value whose key ends with _encrypted
- H-07 archiveClient cascade fires per-interest audit rows
- H-08 createSalesTransporter applies SMTP_TIMEOUTS
- H-09 AppShell stable children — viewport flip across breakpoint no
longer destroys in-progress form drafts
- H-10 portal documents page swaps Unicode glyph status icons for
Lucide CheckCircle2/XCircle/Circle + aria-labels
- H-12 list components swap alert(...) for toast.warning(...)
- H-13 5 icon-only buttons gain aria-label
- H-14 parseBody treats empty bodies as {}
- H-15 admin layout renders a 403 panel instead of silent bounce
- H-11 not applicable — mobile-search-overlay IS a mobile bottom-sheet
MEDIUM (28+):
- M-MT01-05 defense-in-depth port_id/parent-id filters on UPDATE/DELETE
WHEREs across custom-fields, notes (all 6 entity types x update +
delete), client-contacts, yacht ownerClient lookup, webhook reads
- M-D01 documents-hub realtime event-name typo (file:created -> uploaded)
- M-EM01 portal-auth emails thread through portId
- M-EM02 sendEmail accepts cc/bcc params
- M-EM04 notification_digest catalog key
- M-IN01 portal presigned download URLs use 4h TTL
- M-IN02 OpenAI client lazy-instantiated
- M-IN04 stale pdfme refs updated to pdf-lib AcroForm
- M-IN05 umami.testConnection returns tagged union
- M-L01 reservations tenure_type unified with berths
- M-L02 report-generators canonicalize stage values
- M-AU01 audit log placeholder copy fixed
- M-AU04 outcome_set / outcome_cleared distinct audit verbs
- M-NEW-2 activity feed entity name+type separator
- M-R01 portal allowlist narrowed + portal_session backstop in proxy
- M-SC02 companies archived partial index
- M-SC04 audit_logs.searchText documented as DB-managed
- M-S01 storage_s3_access_key_encrypted admin field
- M-U01 audit log empty state uses <EmptyState>
- M-U09 invoice delete dialog -> <AlertDialog>
- M-U10 toast.success on ClientForm + InterestForm create/edit
- M-U11 settings-form-card logo preview alt text
- M-U14 mobile topbar title on clients/yachts/interests/berths
- M-U15 Invoices in mobile More-sheet
LOW (6/8):
- L-AU01 severity defaults for security-relevant verbs
- L-AU02 +13 missing actions in admin audit filter
- L-AU03 +7 missing entity types in admin audit filter
- L-AU04 dead listAuditLogs stubbed
- L-D02 CLAUDE.md Owner-wins chain tightened
Bonus — Document detail polish (#67 partial, 3/6 deliverables):
- state-aware action button per signer
- watcher Add UI with display-name resolution
- cleanSignerName cleanup
Prior session work bundled in:
- Documenso v2 webhook + envelope-ID normalization + sequential signing
- SigningProgress UI redesign (avatars, per-signer state, timestamps)
- env->admin settings registry + RegistryDrivenForm + encrypted creds
- Embedded-signing card + Test connection + setup help
- Dev-mode EMAIL_REDIRECT_TO banner
- Pipeline rules admin page
- Sales email config card
- Audit log details Sheet
- EOI tab: Finalising badge, absolute timestamps, sequential indicator
- Notes pipeline_stage_at_creation (migration 0069)
- Documenso numeric ID dual-key webhook (migration 0068)
- Dimensions criterion copy (migration 0067)
Tests: 1374/1374 vitest pass. tsc clean. lint clean.
See docs/AUDIT-FIX-WAVE-2026-05-18.md for the full progress report and
the user-input items still pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
267 lines
13 KiB
TypeScript
267 lines
13 KiB
TypeScript
import { CheckCircle2, Info } from 'lucide-react';
|
|
|
|
import {
|
|
SettingsFormCard,
|
|
type SettingFieldDef,
|
|
} from '@/components/admin/shared/settings-form-card';
|
|
import { RegistryDrivenForm } from '@/components/admin/shared/registry-driven-form';
|
|
import { DocumensoTestButton } from '@/components/admin/documenso/documenso-test-button';
|
|
import { EmbeddedSigningCard } from '@/components/admin/documenso/embedded-signing-card';
|
|
import { TemplateSyncButton } from '@/components/admin/documenso/template-sync-button';
|
|
import { PageHeader } from '@/components/shared/page-header';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
|
// API_FIELDS removed — replaced by <RegistryDrivenForm sections={['documenso.api']} />
|
|
// which adds the new webhook-secret field + AES encrypts the API key at rest.
|
|
|
|
const CONTRACT_RESERVATION_FIELDS: SettingFieldDef[] = [
|
|
{
|
|
key: 'documenso_contract_template_id',
|
|
label: 'Contract Documenso template ID (optional)',
|
|
description:
|
|
'Numeric template ID for sales contract generation. Leave blank to use the per-interest upload-and-place-fields flow instead (the typical path for contracts, since they are usually drafted custom per client).',
|
|
type: 'string',
|
|
placeholder: '',
|
|
defaultValue: '',
|
|
},
|
|
{
|
|
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.',
|
|
type: 'string',
|
|
placeholder: '',
|
|
defaultValue: '',
|
|
},
|
|
];
|
|
|
|
// Embedded signing field config + Test + Setup help all live inside
|
|
// `<EmbeddedSigningCard />` (imported above). Kept out of the field list
|
|
// here so the admin page reads as a flat sequence of cards.
|
|
|
|
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.',
|
|
type: 'select',
|
|
options: [
|
|
{ value: 'PARALLEL', label: 'PARALLEL — all signers invited at once' },
|
|
{ value: 'SEQUENTIAL', label: 'SEQUENTIAL — one at a time in order' },
|
|
],
|
|
defaultValue: 'PARALLEL',
|
|
},
|
|
{
|
|
key: 'documenso_redirect_url',
|
|
label: 'Post-signing redirect URL',
|
|
description:
|
|
"URL Documenso redirects the signer to after they complete signing. Typically the marketing site's success page so signers land on a branded thank-you rather than Documenso's own page. Leave blank to use Documenso's default. v1 and v2 both honour this. Example: https://portnimara.com/sign/success",
|
|
type: 'string',
|
|
placeholder: 'https://portnimara.com/sign/success',
|
|
defaultValue: '',
|
|
},
|
|
];
|
|
|
|
export default function DocumensoSettingsPage() {
|
|
return (
|
|
<div className="space-y-6">
|
|
<PageHeader
|
|
title="Documenso & EOI"
|
|
description="API credentials, signer identities, and document generation behaviour. Use the test-connection button to verify a saved configuration before relying on it."
|
|
/>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
<Info className="h-4 w-4" aria-hidden="true" />
|
|
v1 vs v2 — what changes when you flip the API version
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4 text-sm">
|
|
<p className="text-muted-foreground">
|
|
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 <strong>not</strong> 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.
|
|
</p>
|
|
|
|
<div className="rounded-md border border-border bg-muted/40 p-3">
|
|
<p className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
v2-only capabilities the CRM already uses when you pick v2
|
|
</p>
|
|
<ul className="space-y-1.5">
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2
|
|
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
|
|
aria-hidden="true"
|
|
/>
|
|
<span>
|
|
<strong>Bulk field placement.</strong> One API call per envelope vs. v1's
|
|
per-field POST loop. Faster contract generation, fewer transient retries on
|
|
multi-field uploaded contracts.
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2
|
|
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
|
|
aria-hidden="true"
|
|
/>
|
|
<span>
|
|
<strong>Percent-based field coordinates.</strong> No page-dimension lookup needed
|
|
— coordinates are portable across page sizes. v1 requires us to assume A4 for
|
|
auto-placed fields.
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2
|
|
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
|
|
aria-hidden="true"
|
|
/>
|
|
<span>
|
|
<strong>Richer field metadata.</strong> TEXT labels & required flags, NUMBER
|
|
min/max + format, CHECKBOX/DROPDOWN/RADIO option lists with defaults — all ignored
|
|
by v1, surfaced by v2 in the signing UI.
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2
|
|
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
|
|
aria-hidden="true"
|
|
/>
|
|
<span>
|
|
<strong>v2-flavoured webhook events.</strong> <code>RECIPIENT_VIEWED</code>,{' '}
|
|
<code>RECIPIENT_SIGNED</code>, <code>DOCUMENT_RECIPIENT_COMPLETED</code>,{' '}
|
|
<code>DOCUMENT_DECLINED</code>, <code>DOCUMENT_REMINDER_SENT</code> — all routed
|
|
through the same dedup + audit pipeline as v1 events.
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2
|
|
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
|
|
aria-hidden="true"
|
|
/>
|
|
<span>
|
|
<strong>Envelope CRUD endpoints.</strong> <code>GET</code>, <code>DELETE</code>,
|
|
<code>POST /envelope/create</code> (multipart),{' '}
|
|
<code>POST /envelope/distribute</code>, <code>POST /envelope/redistribute</code>,{' '}
|
|
<code>GET /envelope/{'{id}'}/download</code> — all routed through{' '}
|
|
<code>/api/v2/envelope/...</code> when v2 is selected. The template-generate path
|
|
is intentionally still v1 (relies on Documenso 2.x's backward-compat window —
|
|
see the deferred-roadmap below).
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2
|
|
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
|
|
aria-hidden="true"
|
|
/>
|
|
<span>
|
|
<strong>One-call send.</strong> v2's <code>/envelope/distribute</code>{' '}
|
|
returns per-recipient <code>signingUrl</code> in the same response — v1 requires a
|
|
separate GET to fetch them. Faster send flow on the rep side.
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2
|
|
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
|
|
aria-hidden="true"
|
|
/>
|
|
<span>
|
|
<strong>Sequential signing enforcement.</strong> Pick SEQUENTIAL in the "v2
|
|
signing behaviour" card below and Documenso 2.x refuses to email recipient
|
|
N+1 until recipient N has signed. Eliminates the "approver signed before the
|
|
developer did" race on EOIs.
|
|
</span>
|
|
</li>
|
|
<li className="flex items-start gap-2">
|
|
<CheckCircle2
|
|
className="mt-0.5 h-4 w-4 shrink-0 text-emerald-600"
|
|
aria-hidden="true"
|
|
/>
|
|
<span>
|
|
<strong>Post-signing redirect URL.</strong> Set in the "v2 signing
|
|
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.)
|
|
</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div className="rounded-md border border-amber-200 bg-amber-50/60 p-3 dark:border-amber-900/40 dark:bg-amber-950/30">
|
|
<p className="mb-2 text-xs font-semibold uppercase tracking-wider text-amber-700 dark:text-amber-400">
|
|
v2 capabilities deferred (would need new code paths)
|
|
</p>
|
|
<ul className="space-y-1.5 text-muted-foreground">
|
|
<li>
|
|
<strong>
|
|
Single-shot <code>/template/use</code>
|
|
</strong>{' '}
|
|
with v2 <code>prefillFields</code> by ID — current EOI flow uses{' '}
|
|
<code>/api/v1/templates/{'{id}'}/generate-document</code> with{' '}
|
|
<code>formValues</code> keyed by name. v2 instances accept both during their
|
|
backward-compat window; full migration requires per-template field-ID capture in
|
|
admin settings.
|
|
</li>
|
|
<li>
|
|
<strong>
|
|
Update envelope metadata after creation (<code>/envelope/update</code>)
|
|
</strong>{' '}
|
|
— change title / subject / redirectUrl on a doc already in DRAFT/PENDING without
|
|
re-generating.
|
|
</li>
|
|
<li>
|
|
<strong>Non-SIGNER recipient roles (CC / VIEWER)</strong> — 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.
|
|
</li>
|
|
</ul>
|
|
<p className="mt-2 text-xs text-muted-foreground">
|
|
Sequential signing and post-signing redirect URL <strong>are now wired</strong> — see
|
|
the new "v2 signing behaviour" card below to configure them.
|
|
</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<RegistryDrivenForm
|
|
title="Documenso API"
|
|
description="Per-port API credentials. AES-encrypted at rest. Leave blank to inherit from the env fallback (badged below each field)."
|
|
sections={['documenso.api']}
|
|
extra={<DocumensoTestButton />}
|
|
/>
|
|
|
|
<SettingsFormCard
|
|
title="v2 signing behaviour"
|
|
description="Cross-cutting settings that apply to EOIs + uploaded contracts/reservations. Sequential signing is v2-only (v1 instances ignore it). Redirect URL is honoured by both v1 and v2 instances."
|
|
fields={V2_FEATURE_FIELDS}
|
|
/>
|
|
|
|
<RegistryDrivenForm
|
|
sections={['documenso.signers']}
|
|
title="Signers (developer + approver)"
|
|
description="Identity bound to the developer (signing order 2) and approver (signing order 3) slots in your Documenso templates. Leave name + email blank to fall through to whatever you set on the Documenso template itself; set them here to override the template's stored values at send time. Recipient IDs are populated automatically by 'Sync from Documenso' below. Linking a CRM user is optional — when set, the platform fires an in-CRM notification for that user when it's their turn to sign."
|
|
/>
|
|
|
|
<RegistryDrivenForm
|
|
sections={['documenso.templates']}
|
|
title="EOI generation"
|
|
description="Default pathway, template, and email behaviour when an interest's EOI is generated. Recipient + field discovery happens via 'Sync from Documenso' below — that also populates the template ID for you."
|
|
extra={<TemplateSyncButton />}
|
|
/>
|
|
|
|
<SettingsFormCard
|
|
title="Contract & reservation templates (optional)"
|
|
description="Most ports leave these blank because contracts/reservations are drafted per interest and uploaded for signing. Set a template ID only if you have a standardised contract/reservation Documenso template."
|
|
fields={CONTRACT_RESERVATION_FIELDS}
|
|
/>
|
|
|
|
<EmbeddedSigningCard />
|
|
</div>
|
|
);
|
|
}
|