chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged: - Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances) - country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk after the per-subpath dynamic-import approach silently failed in webpack) - Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index, redirects (ocr to ai, reports to dashboard, invitations to users), docs/admin-ia-proposal.md - Per-template email tester (registry + endpoint + UI on Email admin page) - Cancel-document mode picker (delete-from-Documenso vs keep-for-audit) - Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers - Customize-widgets per-region sortables at xl+ (charts/rails/feed); single flat sortable below xl when the layout stacks; per-viewport saved orders - Audit doc updates capturing each shipped item - Lint fixes: react-compiler immutability in DonutChart (reduce instead of let-reassign), set-state-in-effect disables in CountryFlag and UploadForSigning preview-bytes effect, unused 'confirm' destructures in interest contract + reservation tabs, unescaped apostrophe in test-template card copy
This commit is contained in:
@@ -10,6 +10,15 @@ const cancelBodySchema = z
|
||||
.object({
|
||||
reason: z.string().max(2000).optional().nullable(),
|
||||
notifyRecipients: z.array(z.string().uuid()).max(20).optional(),
|
||||
/**
|
||||
* Whether to also DELETE the document from Documenso. `delete` (the
|
||||
* default) frees the upstream envelope slot - useful for unclogging
|
||||
* the Documenso log when a draft was abandoned. `keep_remote`
|
||||
* leaves the envelope intact for audit purposes; only the local
|
||||
* row is marked `cancelled`. Audit-trail copy on the cancelled-doc
|
||||
* badge changes accordingly.
|
||||
*/
|
||||
cancelMode: z.enum(['delete', 'keep_remote']).optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional();
|
||||
@@ -17,7 +26,7 @@ const cancelBodySchema = z
|
||||
export const POST = withAuth(
|
||||
withPermission('documents', 'edit', async (req, ctx, params) => {
|
||||
try {
|
||||
// Body is optional — legacy callers POST with `{}`. parseBody returns
|
||||
// Body is optional - legacy callers POST with `{}`. parseBody returns
|
||||
// null when the request has no body; default to empty options.
|
||||
let body: z.infer<typeof cancelBodySchema> = undefined;
|
||||
try {
|
||||
@@ -37,6 +46,7 @@ export const POST = withAuth(
|
||||
{
|
||||
reason: body?.reason ?? null,
|
||||
notifyRecipients: body?.notifyRecipients ?? [],
|
||||
cancelMode: body?.cancelMode ?? 'delete',
|
||||
},
|
||||
);
|
||||
return NextResponse.json({ data: doc });
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* Lookup is keyed off the doc id; the slug embeds the current folder path +
|
||||
* filename so a forwarded link reads like `Deals 2026/Q1/contract.pdf` even
|
||||
* though the underlying storage key is a UUID. The slug is rebuilt from
|
||||
* current state and compared with the supplied path — a stale or
|
||||
* current state and compared with the supplied path - a stale or
|
||||
* hand-edited URL 404s rather than silently serving the wrong file.
|
||||
*/
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { createAuditLog } from '@/lib/audit';
|
||||
|
||||
/**
|
||||
* Per-document move endpoint. Moving a single document is a deliberate
|
||||
* user action so we DO bump `updated_at` here — different semantics from
|
||||
* user action so we DO bump `updated_at` here - different semantics from
|
||||
* the bulk soft-rescue in `deleteFolderSoftRescue` where the timestamp
|
||||
* stays put because reps did not act on the individual documents.
|
||||
*
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getPortDocumensoConfig } from '@/lib/services/port-config';
|
||||
import { errorResponse, NotFoundError, ValidationError } from '@/lib/errors';
|
||||
|
||||
const bodySchema = z.object({
|
||||
/** Optional — defaults to the next pending signer in signing-order. */
|
||||
/** Optional - defaults to the next pending signer in signing-order. */
|
||||
recipientId: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ export const POST = withAuth(
|
||||
|
||||
// Self-heal flow when target.signingUrl is null. Two scenarios:
|
||||
// 1. Envelope was created before the auto-distribute fix shipped
|
||||
// — never distributed, so we must call /envelope/distribute
|
||||
// - never distributed, so we must call /envelope/distribute
|
||||
// to mint URLs.
|
||||
// 2. Envelope WAS auto-distributed at generate time, but the
|
||||
// response we got didn't carry signingUrls into our DB row
|
||||
@@ -74,7 +74,7 @@ export const POST = withAuth(
|
||||
// Defensive flow: try `getEnvelope` FIRST (cheap, always works).
|
||||
// If recipients carry signingUrls, persist + skip distribute.
|
||||
// If not, fall through to distribute, but catch 4xx so we don't
|
||||
// surface a confusing "Documenso upstream error" to the rep —
|
||||
// surface a confusing "Documenso upstream error" to the rep -
|
||||
// instead we re-fetch via GET one more time and accept whatever
|
||||
// URLs the envelope has.
|
||||
if (!target.signingUrl && doc.documensoId) {
|
||||
@@ -116,7 +116,7 @@ export const POST = withAuth(
|
||||
recovered = true;
|
||||
}
|
||||
} catch {
|
||||
// ignore — fall through to distribute attempt
|
||||
// ignore - fall through to distribute attempt
|
||||
}
|
||||
|
||||
// Step 2: distribute, only if GET didn't recover URLs.
|
||||
@@ -125,7 +125,7 @@ export const POST = withAuth(
|
||||
const distributed = await distributeEnvelopeV2(doc.documensoId, ctx.portId);
|
||||
await persistUrlsForDocument(distributed.recipients);
|
||||
} catch {
|
||||
// Probably "already distributed" — last-ditch GET.
|
||||
// Probably "already distributed" - last-ditch GET.
|
||||
try {
|
||||
const fetched = await getDocument(doc.documensoId, ctx.portId);
|
||||
await persistUrlsForDocument(fetched.recipients);
|
||||
@@ -146,7 +146,7 @@ export const POST = withAuth(
|
||||
|
||||
if (!target.signingUrl) {
|
||||
throw new ValidationError(
|
||||
'Signer has no Documenso URL yet — try regenerating the EOI; v2 envelopes require distribution before the signing link exists.',
|
||||
'Signer has no Documenso URL yet - try regenerating the EOI; v2 envelopes require distribution before the signing link exists.',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ export const POST = withAuth(
|
||||
documentLabel: DOC_TYPE_LABEL[doc.documentType] ?? 'Expression of Interest',
|
||||
signerRole: (target.signerRole as SignerRole) ?? 'client',
|
||||
senderName: docCfg.developerName ?? null,
|
||||
// Phase 6 — surface the per-doc rep-authored note when set so
|
||||
// Phase 6 - surface the per-doc rep-authored note when set so
|
||||
// every cascaded invite and any manual resend show the same
|
||||
// copy. Falls back to the template default when null/empty.
|
||||
customMessage: doc.invitationMessage,
|
||||
|
||||
@@ -6,19 +6,19 @@ import { detectFields } from '@/lib/services/document-field-detector';
|
||||
import { isPdfMagic } from '@/lib/services/berth-pdf-parser';
|
||||
|
||||
/**
|
||||
* Phase 4 — Auto-detect anchor scanner endpoint.
|
||||
* Phase 4 - Auto-detect anchor scanner endpoint.
|
||||
*
|
||||
* POST `/api/v1/documents/auto-detect-fields`
|
||||
*
|
||||
* Body: multipart/form-data
|
||||
* - file: the source PDF the rep just uploaded
|
||||
*
|
||||
* Returns: `{ data: { fields: DetectedField[] } }` — seed state for the
|
||||
* Returns: `{ data: { fields: DetectedField[] } }` - seed state for the
|
||||
* drag-drop overlay. Empty array when the PDF has no extractable text
|
||||
* (image-only scan) — the dialog falls back to manual placement
|
||||
* (image-only scan) - the dialog falls back to manual placement
|
||||
* without an error toast.
|
||||
*
|
||||
* Permission: documents.send_for_signing — the only flow that calls
|
||||
* Permission: documents.send_for_signing - the only flow that calls
|
||||
* this endpoint is the upload-for-signing dialog, which already
|
||||
* requires that bit. Reusing it here means a custom role with the
|
||||
* upload bit but no send bit can't dry-run the detector to pull
|
||||
|
||||
@@ -10,11 +10,11 @@ import { getEoiTemplateSyncReport } from '@/lib/services/documenso-template-sync
|
||||
*
|
||||
* 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
|
||||
* 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
|
||||
* Permission: documents.send_for_signing - the only caller is the
|
||||
* upload-for-signing dialog which already requires this permission to
|
||||
* complete the flow.
|
||||
*/
|
||||
@@ -25,7 +25,7 @@ export const GET = withAuth(
|
||||
|
||||
// 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
|
||||
// - 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.
|
||||
@@ -53,7 +53,7 @@ export const GET = withAuth(
|
||||
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
|
||||
// Documenso but the CRM still says X" - they need to re-run
|
||||
// Sync to pull the change.
|
||||
signingOrderSource: syncReport?.templateMeta?.signingOrder
|
||||
? 'template'
|
||||
|
||||
Reference in New Issue
Block a user