Files
pn-new-crm/src/app/api/v1/documents/signing-defaults/route.ts

70 lines
2.8 KiB
TypeScript
Raw Normal View History

feat(documenso-phase-4): recipient configurator + field placement UI Phase 4 lands the visual half of the Documenso build — the upload- for-signing dialog the Contract + Reservation tabs hand off to. Four files of new code; the existing tab placeholders point at it. Files added: - lib/services/document-field-detector.ts — Phase 4c auto-detect scanner. Uses pdfjs-dist to extract per-page text + positions, then matches anchor patterns (Signature, Date, Initials, Email, Name, underscore-runs) and produces percent-coordinate DetectedField rows. Recipient label inference walks ±100pt of each match for Buyer/Seller/Client/Witness/Notary keywords. Returns [] when the PDF is image-only; UI falls back to manual placement without an error. 6 unit tests pin the matching + coordinate math. - app/api/v1/documents/auto-detect-fields/route.ts — multipart POST endpoint that delegates to detectFields(). Permission-gated by documents.send_for_signing. - app/api/v1/documents/signing-defaults/route.ts — GET endpoint that surfaces just the per-port developer + approver display name/email + sendMode flag. No secrets exposed; lets the dialog prefill the recipient configurator without an admin-scoped settings read. - components/documents/upload-for-signing-dialog.tsx — the Phase 4 UI. Three-step state machine inside a single Dialog: 1. select-file: drop/click PDF picker + title input 2. configure-recipients: client + developer + approver prefilled, rep can add/remove/reorder + change role (SIGNER/APPROVER/CC) 3. place-fields: react-pdf renders the source PDF; auto-detect runs in the background on file load and seeds the overlay; rep places, drags, resizes, deletes, reassigns fields via the palette + side panel. Native DOM drag (no dnd-kit dependency added — the coordinate math stays obvious). Send fires POST /api/v1/interests/[id]/upload-for-signing (Phase 3 service); success toast reflects port sendMode (auto fires the invite immediately, manual leaves it for the rep). Files modified: - components/interests/interest-contract-tab.tsx + reservation-tab.tsx: swap the ComingSoonDialog placeholder for the real UploadForSigningDialog with the matching documentType prop. The placeholder ComingSoonDialog helper is deleted from both. - scripts/tsc-staged.mjs: pull src/types/**/*.d.ts into the temp staged-only tsconfig so side-effect CSS imports (e.g. react-pdf/dist/Page/AnnotationLayer.css) resolve via the existing declare-module shim. Without this fix the staged compile reports TS2882 even though the full tsc --noEmit pass passes. Design choices noted in code comments: - Native drag over dnd-kit: the field overlay's percent-based coordinate math is short enough that adding a drag library adds complexity without saving lines. - Auto-detect on file-load (not on demand): runs immediately so the rep doesn't have to click a second button — empty result drops back to manual placement silently. - Per-recipient color swatches indexed by signingOrder. - Recipient seed via useMemo + user-event handler instead of useEffect → setRecipients (Wave 3 set-state-in-effect avoidance). Server-side, Phase 3 plumbing handles the rest: tenant guard, magic- byte verify, Documenso round-trip with per-port v1/v2 routing, recipient signingToken capture for Phase 2 webhook cascade, auto- send when port.sendMode === 'auto'. Tests: 1334 → 1340 ✅ (6 new for the detector); tsc clean. Deferred polish (Phase 6): - Per-field metadata side panel for DROPDOWN/RADIO option lists - Pinch-zoom + zoom-out controls on the field-placement canvas - Recipient drag-reorder via dnd-kit - Required toggle per field Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:03:27 +02:00
import { NextResponse } from 'next/server';
import { withAuth, withPermission } from '@/lib/api/helpers';
import { errorResponse } from '@/lib/errors';
import { getPortDocumensoConfig } from '@/lib/services/port-config';
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish 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>
2026-05-18 13:28:50 +02:00
import { getEoiTemplateSyncReport } from '@/lib/services/documenso-template-sync.service';
feat(documenso-phase-4): recipient configurator + field placement UI Phase 4 lands the visual half of the Documenso build — the upload- for-signing dialog the Contract + Reservation tabs hand off to. Four files of new code; the existing tab placeholders point at it. Files added: - lib/services/document-field-detector.ts — Phase 4c auto-detect scanner. Uses pdfjs-dist to extract per-page text + positions, then matches anchor patterns (Signature, Date, Initials, Email, Name, underscore-runs) and produces percent-coordinate DetectedField rows. Recipient label inference walks ±100pt of each match for Buyer/Seller/Client/Witness/Notary keywords. Returns [] when the PDF is image-only; UI falls back to manual placement without an error. 6 unit tests pin the matching + coordinate math. - app/api/v1/documents/auto-detect-fields/route.ts — multipart POST endpoint that delegates to detectFields(). Permission-gated by documents.send_for_signing. - app/api/v1/documents/signing-defaults/route.ts — GET endpoint that surfaces just the per-port developer + approver display name/email + sendMode flag. No secrets exposed; lets the dialog prefill the recipient configurator without an admin-scoped settings read. - components/documents/upload-for-signing-dialog.tsx — the Phase 4 UI. Three-step state machine inside a single Dialog: 1. select-file: drop/click PDF picker + title input 2. configure-recipients: client + developer + approver prefilled, rep can add/remove/reorder + change role (SIGNER/APPROVER/CC) 3. place-fields: react-pdf renders the source PDF; auto-detect runs in the background on file load and seeds the overlay; rep places, drags, resizes, deletes, reassigns fields via the palette + side panel. Native DOM drag (no dnd-kit dependency added — the coordinate math stays obvious). Send fires POST /api/v1/interests/[id]/upload-for-signing (Phase 3 service); success toast reflects port sendMode (auto fires the invite immediately, manual leaves it for the rep). Files modified: - components/interests/interest-contract-tab.tsx + reservation-tab.tsx: swap the ComingSoonDialog placeholder for the real UploadForSigningDialog with the matching documentType prop. The placeholder ComingSoonDialog helper is deleted from both. - scripts/tsc-staged.mjs: pull src/types/**/*.d.ts into the temp staged-only tsconfig so side-effect CSS imports (e.g. react-pdf/dist/Page/AnnotationLayer.css) resolve via the existing declare-module shim. Without this fix the staged compile reports TS2882 even though the full tsc --noEmit pass passes. Design choices noted in code comments: - Native drag over dnd-kit: the field overlay's percent-based coordinate math is short enough that adding a drag library adds complexity without saving lines. - Auto-detect on file-load (not on demand): runs immediately so the rep doesn't have to click a second button — empty result drops back to manual placement silently. - Per-recipient color swatches indexed by signingOrder. - Recipient seed via useMemo + user-event handler instead of useEffect → setRecipients (Wave 3 set-state-in-effect avoidance). Server-side, Phase 3 plumbing handles the rest: tenant guard, magic- byte verify, Documenso round-trip with per-port v1/v2 routing, recipient signingToken capture for Phase 2 webhook cascade, auto- send when port.sendMode === 'auto'. Tests: 1334 → 1340 ✅ (6 new for the detector); tsc clean. Deferred polish (Phase 6): - Per-field metadata side panel for DROPDOWN/RADIO option lists - Pinch-zoom + zoom-out controls on the field-placement canvas - Recipient drag-reorder via dnd-kit - Required toggle per field Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:03:27 +02:00
/**
* GET `/api/v1/documents/signing-defaults`
*
* Returns the per-port developer + approver defaults the
* UploadForSigningDialog uses to prefill the recipient configurator.
* No secrets are exposed just the display name, email, and the
* sendMode flag so the UI can show the right CTA copy ("Send now" vs
* "Save draft, send manually").
*
* Permission: documents.send_for_signing the only caller is the
* upload-for-signing dialog which already requires this permission to
* complete the flow.
*/
export const GET = withAuth(
withPermission('documents', 'send_for_signing', async (_req, ctx) => {
try {
const cfg = await getPortDocumensoConfig(ctx.portId);
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish 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>
2026-05-18 13:28:50 +02:00
// Signing order resolution chain (highest → lowest priority):
// 1. Cached `documento_eoi_template_sync_report.templateMeta.signingOrder`
// — populated by the admin "Sync from Documenso" button and
// represents the live template's bound order. On v2 this is the
// authoritative value because `/template/use` doesn't accept a
// per-call override.
// 2. Per-port `documenso_signing_order` setting from
// getPortDocumensoConfig (used by v1 + as a UI fallback when the
// admin hasn't run a sync yet).
// 3. Documenso's own default (`PARALLEL` = concurrent signing).
const syncReport = await getEoiTemplateSyncReport(ctx.portId).catch(() => null);
const signingOrder: 'PARALLEL' | 'SEQUENTIAL' =
syncReport?.templateMeta?.signingOrder ?? cfg.signingOrder ?? 'PARALLEL';
feat(documenso-phase-4): recipient configurator + field placement UI Phase 4 lands the visual half of the Documenso build — the upload- for-signing dialog the Contract + Reservation tabs hand off to. Four files of new code; the existing tab placeholders point at it. Files added: - lib/services/document-field-detector.ts — Phase 4c auto-detect scanner. Uses pdfjs-dist to extract per-page text + positions, then matches anchor patterns (Signature, Date, Initials, Email, Name, underscore-runs) and produces percent-coordinate DetectedField rows. Recipient label inference walks ±100pt of each match for Buyer/Seller/Client/Witness/Notary keywords. Returns [] when the PDF is image-only; UI falls back to manual placement without an error. 6 unit tests pin the matching + coordinate math. - app/api/v1/documents/auto-detect-fields/route.ts — multipart POST endpoint that delegates to detectFields(). Permission-gated by documents.send_for_signing. - app/api/v1/documents/signing-defaults/route.ts — GET endpoint that surfaces just the per-port developer + approver display name/email + sendMode flag. No secrets exposed; lets the dialog prefill the recipient configurator without an admin-scoped settings read. - components/documents/upload-for-signing-dialog.tsx — the Phase 4 UI. Three-step state machine inside a single Dialog: 1. select-file: drop/click PDF picker + title input 2. configure-recipients: client + developer + approver prefilled, rep can add/remove/reorder + change role (SIGNER/APPROVER/CC) 3. place-fields: react-pdf renders the source PDF; auto-detect runs in the background on file load and seeds the overlay; rep places, drags, resizes, deletes, reassigns fields via the palette + side panel. Native DOM drag (no dnd-kit dependency added — the coordinate math stays obvious). Send fires POST /api/v1/interests/[id]/upload-for-signing (Phase 3 service); success toast reflects port sendMode (auto fires the invite immediately, manual leaves it for the rep). Files modified: - components/interests/interest-contract-tab.tsx + reservation-tab.tsx: swap the ComingSoonDialog placeholder for the real UploadForSigningDialog with the matching documentType prop. The placeholder ComingSoonDialog helper is deleted from both. - scripts/tsc-staged.mjs: pull src/types/**/*.d.ts into the temp staged-only tsconfig so side-effect CSS imports (e.g. react-pdf/dist/Page/AnnotationLayer.css) resolve via the existing declare-module shim. Without this fix the staged compile reports TS2882 even though the full tsc --noEmit pass passes. Design choices noted in code comments: - Native drag over dnd-kit: the field overlay's percent-based coordinate math is short enough that adding a drag library adds complexity without saving lines. - Auto-detect on file-load (not on demand): runs immediately so the rep doesn't have to click a second button — empty result drops back to manual placement silently. - Per-recipient color swatches indexed by signingOrder. - Recipient seed via useMemo + user-event handler instead of useEffect → setRecipients (Wave 3 set-state-in-effect avoidance). Server-side, Phase 3 plumbing handles the rest: tenant guard, magic- byte verify, Documenso round-trip with per-port v1/v2 routing, recipient signingToken capture for Phase 2 webhook cascade, auto- send when port.sendMode === 'auto'. Tests: 1334 → 1340 ✅ (6 new for the detector); tsc clean. Deferred polish (Phase 6): - Per-field metadata side panel for DROPDOWN/RADIO option lists - Pinch-zoom + zoom-out controls on the field-placement canvas - Recipient drag-reorder via dnd-kit - Required toggle per field Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:03:27 +02:00
return NextResponse.json({
data: {
developer: {
name: cfg.developerName ?? '',
email: cfg.developerEmail ?? '',
label: cfg.developerLabel ?? 'Developer',
},
approver: {
name: cfg.approverName ?? '',
email: cfg.approverEmail ?? '',
label: cfg.approverLabel ?? 'Approver',
},
sendMode: cfg.sendMode,
fix(audit): comprehensive 2026-05-15 audit fix wave + Documenso v2 polish 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>
2026-05-18 13:28:50 +02:00
signingOrder,
// Surface where the value came from so the UI tooltip can be
// honest about the source. Helps reps debug "I changed it in
// Documenso but the CRM still says X" — they need to re-run
// Sync to pull the change.
signingOrderSource: syncReport?.templateMeta?.signingOrder
? 'template'
: cfg.signingOrder
? 'port-setting'
: 'default',
feat(documenso-phase-4): recipient configurator + field placement UI Phase 4 lands the visual half of the Documenso build — the upload- for-signing dialog the Contract + Reservation tabs hand off to. Four files of new code; the existing tab placeholders point at it. Files added: - lib/services/document-field-detector.ts — Phase 4c auto-detect scanner. Uses pdfjs-dist to extract per-page text + positions, then matches anchor patterns (Signature, Date, Initials, Email, Name, underscore-runs) and produces percent-coordinate DetectedField rows. Recipient label inference walks ±100pt of each match for Buyer/Seller/Client/Witness/Notary keywords. Returns [] when the PDF is image-only; UI falls back to manual placement without an error. 6 unit tests pin the matching + coordinate math. - app/api/v1/documents/auto-detect-fields/route.ts — multipart POST endpoint that delegates to detectFields(). Permission-gated by documents.send_for_signing. - app/api/v1/documents/signing-defaults/route.ts — GET endpoint that surfaces just the per-port developer + approver display name/email + sendMode flag. No secrets exposed; lets the dialog prefill the recipient configurator without an admin-scoped settings read. - components/documents/upload-for-signing-dialog.tsx — the Phase 4 UI. Three-step state machine inside a single Dialog: 1. select-file: drop/click PDF picker + title input 2. configure-recipients: client + developer + approver prefilled, rep can add/remove/reorder + change role (SIGNER/APPROVER/CC) 3. place-fields: react-pdf renders the source PDF; auto-detect runs in the background on file load and seeds the overlay; rep places, drags, resizes, deletes, reassigns fields via the palette + side panel. Native DOM drag (no dnd-kit dependency added — the coordinate math stays obvious). Send fires POST /api/v1/interests/[id]/upload-for-signing (Phase 3 service); success toast reflects port sendMode (auto fires the invite immediately, manual leaves it for the rep). Files modified: - components/interests/interest-contract-tab.tsx + reservation-tab.tsx: swap the ComingSoonDialog placeholder for the real UploadForSigningDialog with the matching documentType prop. The placeholder ComingSoonDialog helper is deleted from both. - scripts/tsc-staged.mjs: pull src/types/**/*.d.ts into the temp staged-only tsconfig so side-effect CSS imports (e.g. react-pdf/dist/Page/AnnotationLayer.css) resolve via the existing declare-module shim. Without this fix the staged compile reports TS2882 even though the full tsc --noEmit pass passes. Design choices noted in code comments: - Native drag over dnd-kit: the field overlay's percent-based coordinate math is short enough that adding a drag library adds complexity without saving lines. - Auto-detect on file-load (not on demand): runs immediately so the rep doesn't have to click a second button — empty result drops back to manual placement silently. - Per-recipient color swatches indexed by signingOrder. - Recipient seed via useMemo + user-event handler instead of useEffect → setRecipients (Wave 3 set-state-in-effect avoidance). Server-side, Phase 3 plumbing handles the rest: tenant guard, magic- byte verify, Documenso round-trip with per-port v1/v2 routing, recipient signingToken capture for Phase 2 webhook cascade, auto- send when port.sendMode === 'auto'. Tests: 1334 → 1340 ✅ (6 new for the detector); tsc clean. Deferred polish (Phase 6): - Per-field metadata side panel for DROPDOWN/RADIO option lists - Pinch-zoom + zoom-out controls on the field-placement canvas - Recipient drag-reorder via dnd-kit - Required toggle per field Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:03:27 +02:00
},
});
} catch (error) {
return errorResponse(error);
}
}),
);