@unlev/exeq 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -88
- package/dist/index.css +289 -5
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +22 -7
- package/dist/index.d.ts +22 -7
- package/dist/index.js +627 -339
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +599 -312
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -4
- package/dist/embed.css +0 -1
- package/dist/embed.global.js +0 -419
- package/dist/styles.css +0 -566
package/dist/index.js
CHANGED
|
@@ -45,12 +45,14 @@ __export(lib_exports, {
|
|
|
45
45
|
generateFilledPdf: () => generateFilledPdf,
|
|
46
46
|
getSignerColor: () => getSignerColor,
|
|
47
47
|
postPdfToCallback: () => postPdfToCallback,
|
|
48
|
-
renderPdfPages: () => renderPdfPages
|
|
48
|
+
renderPdfPages: () => renderPdfPages,
|
|
49
|
+
uniqueLabel: () => uniqueLabel
|
|
49
50
|
});
|
|
50
51
|
module.exports = __toCommonJS(lib_exports);
|
|
51
52
|
|
|
52
53
|
// src/components/pdf-builder/DesignerView.tsx
|
|
53
|
-
var
|
|
54
|
+
var import_react3 = require("react");
|
|
55
|
+
var import_react_dom = require("react-dom");
|
|
54
56
|
|
|
55
57
|
// src/types/pdf-builder.ts
|
|
56
58
|
function generateId() {
|
|
@@ -106,14 +108,44 @@ var FIELD_DEFAULTS = {
|
|
|
106
108
|
height: 5,
|
|
107
109
|
fontSize: 12,
|
|
108
110
|
placeholder: "Initials"
|
|
111
|
+
},
|
|
112
|
+
blackout: {
|
|
113
|
+
width: 20,
|
|
114
|
+
height: 3,
|
|
115
|
+
fontSize: 12,
|
|
116
|
+
placeholder: ""
|
|
117
|
+
},
|
|
118
|
+
whiteout: {
|
|
119
|
+
width: 20,
|
|
120
|
+
height: 3,
|
|
121
|
+
fontSize: 12,
|
|
122
|
+
placeholder: ""
|
|
109
123
|
}
|
|
110
124
|
};
|
|
111
|
-
|
|
125
|
+
var TYPE_LABELS = {
|
|
126
|
+
text: "Text Field",
|
|
127
|
+
signature: "Signature",
|
|
128
|
+
"signed-date": "Signed Date",
|
|
129
|
+
checkbox: "Checkbox",
|
|
130
|
+
initials: "Initials",
|
|
131
|
+
blackout: "Blackout",
|
|
132
|
+
whiteout: "Whiteout"
|
|
133
|
+
};
|
|
134
|
+
function uniqueLabel(desired, existingLabels) {
|
|
135
|
+
const lower = desired.toLowerCase();
|
|
136
|
+
if (!existingLabels.some((l) => l.toLowerCase() === lower)) return desired;
|
|
137
|
+
let i = 2;
|
|
138
|
+
while (existingLabels.some((l) => l.toLowerCase() === `${lower} ${i}`)) i++;
|
|
139
|
+
return `${desired} ${i}`;
|
|
140
|
+
}
|
|
141
|
+
function createField(type, assignee, page, x, y, existingFields) {
|
|
112
142
|
const defaults = FIELD_DEFAULTS[type];
|
|
143
|
+
const baseLabel = TYPE_LABELS[type];
|
|
144
|
+
const existingLabels = existingFields?.map((f) => f.label) ?? [];
|
|
113
145
|
return {
|
|
114
146
|
id: generateId(),
|
|
115
147
|
type,
|
|
116
|
-
label:
|
|
148
|
+
label: uniqueLabel(baseLabel, existingLabels),
|
|
117
149
|
placeholder: defaults.placeholder || "",
|
|
118
150
|
required: true,
|
|
119
151
|
assignee,
|
|
@@ -168,6 +200,7 @@ function PdfViewer({
|
|
|
168
200
|
onFieldMove,
|
|
169
201
|
onFieldResize,
|
|
170
202
|
onPageClick,
|
|
203
|
+
onDropField,
|
|
171
204
|
mode,
|
|
172
205
|
currentSigner,
|
|
173
206
|
renderFieldContent
|
|
@@ -185,6 +218,21 @@ function PdfViewer({
|
|
|
185
218
|
onSelectField(null);
|
|
186
219
|
}
|
|
187
220
|
}, [onPageClick, onSelectField]);
|
|
221
|
+
const handleDragOver = (0, import_react.useCallback)((e) => {
|
|
222
|
+
if (e.dataTransfer.types.includes("application/exeq-field-type")) {
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
e.dataTransfer.dropEffect = "copy";
|
|
225
|
+
}
|
|
226
|
+
}, []);
|
|
227
|
+
const handleDrop = (0, import_react.useCallback)((e, pageIndex) => {
|
|
228
|
+
const fieldType = e.dataTransfer.getData("application/exeq-field-type");
|
|
229
|
+
if (!fieldType || !onDropField) return;
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
232
|
+
const x = (e.clientX - rect.left) / rect.width * 100;
|
|
233
|
+
const y = (e.clientY - rect.top) / rect.height * 100;
|
|
234
|
+
onDropField(pageIndex, x, y, fieldType);
|
|
235
|
+
}, [onDropField]);
|
|
188
236
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pdf-viewer", ref: containerRef, children: pages.map((page, pageIndex) => {
|
|
189
237
|
const pageFields = fields.filter((f) => f.page === pageIndex);
|
|
190
238
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
@@ -193,6 +241,8 @@ function PdfViewer({
|
|
|
193
241
|
className: "pdf-page",
|
|
194
242
|
style: { aspectRatio: `${page.width} / ${page.height}` },
|
|
195
243
|
onClick: (e) => handlePageClick(e, pageIndex),
|
|
244
|
+
onDragOver: handleDragOver,
|
|
245
|
+
onDrop: (e) => handleDrop(e, pageIndex),
|
|
196
246
|
"data-page": pageIndex,
|
|
197
247
|
children: [
|
|
198
248
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -238,6 +288,7 @@ function FieldOverlayItem({
|
|
|
238
288
|
const dragStartRef = (0, import_react.useRef)(null);
|
|
239
289
|
const resizeStartRef = (0, import_react.useRef)(null);
|
|
240
290
|
const color = getSignerColor(field.assignee);
|
|
291
|
+
const isRedact = field.type === "blackout" || field.type === "whiteout";
|
|
241
292
|
const isEditable = mode === "designer" || mode === "signer" && field.assignee === currentSigner;
|
|
242
293
|
const isPreFilled = !isEditable && !!field.value;
|
|
243
294
|
const handleMouseDown = (0, import_react.useCallback)((e) => {
|
|
@@ -303,14 +354,15 @@ function FieldOverlayItem({
|
|
|
303
354
|
"div",
|
|
304
355
|
{
|
|
305
356
|
ref: overlayRef,
|
|
306
|
-
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""}`,
|
|
357
|
+
className: `field-overlay ${isSelected ? "selected" : ""} ${isEditable ? "editable" : "readonly"} ${isPreFilled ? "prefilled" : ""} ${isRedact ? "redact" : ""}`,
|
|
307
358
|
style: {
|
|
308
359
|
left: `${field.x}%`,
|
|
309
360
|
top: `${field.y}%`,
|
|
310
361
|
width: `${field.width}%`,
|
|
311
362
|
height: `${field.height}%`,
|
|
312
|
-
borderColor: color,
|
|
313
|
-
|
|
363
|
+
borderColor: isRedact ? field.type === "blackout" ? "#666" : "#bbb" : color,
|
|
364
|
+
borderStyle: isRedact ? "dashed" : "solid",
|
|
365
|
+
backgroundColor: isRedact ? field.type === "blackout" ? "#000000" : "#ffffff" : isSelected ? `${color}22` : `${color}11`,
|
|
314
366
|
cursor: mode === "designer" ? "move" : "default"
|
|
315
367
|
},
|
|
316
368
|
onClick: (e) => {
|
|
@@ -319,7 +371,7 @@ function FieldOverlayItem({
|
|
|
319
371
|
},
|
|
320
372
|
onMouseDown: handleMouseDown,
|
|
321
373
|
children: [
|
|
322
|
-
mode === "designer" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
|
|
374
|
+
mode === "designer" && !isRedact && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-label", style: { backgroundColor: color }, children: field.label }),
|
|
323
375
|
renderContent ? renderContent(field) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-overlay-placeholder", children: field.value || field.placeholder }),
|
|
324
376
|
mode === "designer" && isSelected && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
325
377
|
"div",
|
|
@@ -335,8 +387,15 @@ function FieldOverlayItem({
|
|
|
335
387
|
|
|
336
388
|
// src/components/pdf-builder/FieldPropertyPanel.tsx
|
|
337
389
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
390
|
+
var INK_COLORS = [
|
|
391
|
+
{ value: "#000000", label: "Black" },
|
|
392
|
+
{ value: "#1a56db", label: "Blue" }
|
|
393
|
+
];
|
|
338
394
|
function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
339
395
|
const color = getSignerColor(field.assignee);
|
|
396
|
+
const isRedactField = field.type === "blackout" || field.type === "whiteout";
|
|
397
|
+
const showFontSize = field.type === "text" || field.type === "signed-date";
|
|
398
|
+
const showInkColor = field.type === "text" || field.type === "signature" || field.type === "initials" || field.type === "signed-date";
|
|
340
399
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "field-property-panel", children: [
|
|
341
400
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-header", children: [
|
|
342
401
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { style: { color }, children: field.label }),
|
|
@@ -353,7 +412,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
353
412
|
}
|
|
354
413
|
)
|
|
355
414
|
] }),
|
|
356
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
415
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
357
416
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Field Type" }),
|
|
358
417
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
359
418
|
"select",
|
|
@@ -387,7 +446,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
387
446
|
}
|
|
388
447
|
)
|
|
389
448
|
] }),
|
|
390
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
449
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
391
450
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Assigned To" }),
|
|
392
451
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
393
452
|
"select",
|
|
@@ -398,7 +457,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
398
457
|
}
|
|
399
458
|
)
|
|
400
459
|
] }),
|
|
401
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
460
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
402
461
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Placeholder" }),
|
|
403
462
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
404
463
|
"input",
|
|
@@ -409,7 +468,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
409
468
|
}
|
|
410
469
|
)
|
|
411
470
|
] }),
|
|
412
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-field", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
471
|
+
!isRedactField && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "panel-field", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
413
472
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
414
473
|
"input",
|
|
415
474
|
{
|
|
@@ -420,7 +479,7 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
420
479
|
),
|
|
421
480
|
"Required"
|
|
422
481
|
] }) }),
|
|
423
|
-
|
|
482
|
+
showFontSize && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
424
483
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Font Size (pt)" }),
|
|
425
484
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
426
485
|
"input",
|
|
@@ -433,118 +492,26 @@ function FieldPropertyPanel({ field, signerRoles, onUpdate, onDelete }) {
|
|
|
433
492
|
}
|
|
434
493
|
)
|
|
435
494
|
] }),
|
|
436
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field
|
|
437
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
438
|
-
|
|
439
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
440
|
-
"input",
|
|
441
|
-
{
|
|
442
|
-
type: "number",
|
|
443
|
-
min: "1",
|
|
444
|
-
max: "100",
|
|
445
|
-
step: "0.5",
|
|
446
|
-
value: field.width,
|
|
447
|
-
onChange: (e) => onUpdate(field.id, { width: Number(e.target.value) })
|
|
448
|
-
}
|
|
449
|
-
)
|
|
450
|
-
] }),
|
|
451
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
452
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Height (%)" }),
|
|
453
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
454
|
-
"input",
|
|
455
|
-
{
|
|
456
|
-
type: "number",
|
|
457
|
-
min: "1",
|
|
458
|
-
max: "100",
|
|
459
|
-
step: "0.5",
|
|
460
|
-
value: field.height,
|
|
461
|
-
onChange: (e) => onUpdate(field.id, { height: Number(e.target.value) })
|
|
462
|
-
}
|
|
463
|
-
)
|
|
464
|
-
] })
|
|
465
|
-
] })
|
|
466
|
-
] });
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// src/components/pdf-builder/SignerRoleSelector.tsx
|
|
470
|
-
var import_react2 = require("react");
|
|
471
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
472
|
-
function SignerRoleSelector({
|
|
473
|
-
roles,
|
|
474
|
-
activeRole,
|
|
475
|
-
onSetActiveRole,
|
|
476
|
-
onAddRole,
|
|
477
|
-
onRemoveRole
|
|
478
|
-
}) {
|
|
479
|
-
const [isAdding, setIsAdding] = (0, import_react2.useState)(false);
|
|
480
|
-
const [newRoleName, setNewRoleName] = (0, import_react2.useState)("");
|
|
481
|
-
const handleAdd = () => {
|
|
482
|
-
if (newRoleName.trim()) {
|
|
483
|
-
onAddRole(newRoleName.trim());
|
|
484
|
-
setNewRoleName("");
|
|
485
|
-
setIsAdding(false);
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "signer-role-selector", children: [
|
|
489
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "signer-role-label", children: "Signer Roles" }),
|
|
490
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "signer-role-list", children: [
|
|
491
|
-
roles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
495
|
+
showInkColor && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "panel-field", children: [
|
|
496
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { children: "Ink Color" }),
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ink-color-picker", children: INK_COLORS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
492
498
|
"button",
|
|
493
499
|
{
|
|
494
|
-
className: `
|
|
495
|
-
style: {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
color: role === activeRole ? "#fff" : getSignerColor(role)
|
|
499
|
-
},
|
|
500
|
-
onClick: () => onSetActiveRole(role),
|
|
501
|
-
children: [
|
|
502
|
-
role,
|
|
503
|
-
roles.length > 1 && role !== "Sender" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
504
|
-
"span",
|
|
505
|
-
{
|
|
506
|
-
className: "signer-role-remove",
|
|
507
|
-
onClick: (e) => {
|
|
508
|
-
e.stopPropagation();
|
|
509
|
-
onRemoveRole(role);
|
|
510
|
-
},
|
|
511
|
-
children: "\xD7"
|
|
512
|
-
}
|
|
513
|
-
)
|
|
514
|
-
]
|
|
500
|
+
className: `ink-color-swatch ${(field.inkColor || "#000000") === c.value ? "active" : ""}`,
|
|
501
|
+
style: { backgroundColor: c.value },
|
|
502
|
+
onClick: () => onUpdate(field.id, { inkColor: c.value }),
|
|
503
|
+
title: c.label
|
|
515
504
|
},
|
|
516
|
-
|
|
517
|
-
))
|
|
518
|
-
isAdding ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "signer-role-add-input", children: [
|
|
519
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
520
|
-
"input",
|
|
521
|
-
{
|
|
522
|
-
type: "text",
|
|
523
|
-
value: newRoleName,
|
|
524
|
-
onChange: (e) => setNewRoleName(e.target.value),
|
|
525
|
-
onKeyDown: (e) => e.key === "Enter" && handleAdd(),
|
|
526
|
-
placeholder: "Role name",
|
|
527
|
-
autoFocus: true
|
|
528
|
-
}
|
|
529
|
-
),
|
|
530
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: handleAdd, children: "Add" }),
|
|
531
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { onClick: () => setIsAdding(false), children: "Cancel" })
|
|
532
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
533
|
-
"button",
|
|
534
|
-
{
|
|
535
|
-
className: "signer-role-add-btn",
|
|
536
|
-
onClick: () => setIsAdding(true),
|
|
537
|
-
children: "+ Add Role"
|
|
538
|
-
}
|
|
539
|
-
)
|
|
505
|
+
c.value
|
|
506
|
+
)) })
|
|
540
507
|
] })
|
|
541
508
|
] });
|
|
542
509
|
}
|
|
543
510
|
|
|
544
511
|
// src/components/pdf-builder/SignatureCanvas.tsx
|
|
545
|
-
var
|
|
512
|
+
var import_react2 = require("react");
|
|
546
513
|
var import_perfect_freehand = require("perfect-freehand");
|
|
547
|
-
var
|
|
514
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
548
515
|
function getSvgPathFromStroke(stroke) {
|
|
549
516
|
if (!stroke.length) return "";
|
|
550
517
|
const d = stroke.reduce(
|
|
@@ -563,13 +530,14 @@ function SignatureCanvas({
|
|
|
563
530
|
height = 150,
|
|
564
531
|
onSign,
|
|
565
532
|
initialValue,
|
|
566
|
-
className
|
|
533
|
+
className,
|
|
534
|
+
inkColor = "#000000"
|
|
567
535
|
}) {
|
|
568
|
-
const canvasRef = (0,
|
|
569
|
-
const [paths, setPaths] = (0,
|
|
570
|
-
const [currentPath, setCurrentPath] = (0,
|
|
571
|
-
const [isEmpty, setIsEmpty] = (0,
|
|
572
|
-
(0,
|
|
536
|
+
const canvasRef = (0, import_react2.useRef)(null);
|
|
537
|
+
const [paths, setPaths] = (0, import_react2.useState)([]);
|
|
538
|
+
const [currentPath, setCurrentPath] = (0, import_react2.useState)(null);
|
|
539
|
+
const [isEmpty, setIsEmpty] = (0, import_react2.useState)(!initialValue);
|
|
540
|
+
(0, import_react2.useEffect)(() => {
|
|
573
541
|
if (initialValue && canvasRef.current) {
|
|
574
542
|
const ctx = canvasRef.current.getContext("2d");
|
|
575
543
|
const img = new Image();
|
|
@@ -580,7 +548,7 @@ function SignatureCanvas({
|
|
|
580
548
|
img.src = initialValue;
|
|
581
549
|
}
|
|
582
550
|
}, [initialValue, width, height]);
|
|
583
|
-
(0,
|
|
551
|
+
(0, import_react2.useEffect)(() => {
|
|
584
552
|
const canvas = canvasRef.current;
|
|
585
553
|
if (!canvas) return;
|
|
586
554
|
const ctx = canvas.getContext("2d");
|
|
@@ -595,11 +563,11 @@ function SignatureCanvas({
|
|
|
595
563
|
});
|
|
596
564
|
const pathStr = getSvgPathFromStroke(stroke);
|
|
597
565
|
const path2d = new Path2D(pathStr);
|
|
598
|
-
ctx.fillStyle =
|
|
566
|
+
ctx.fillStyle = inkColor;
|
|
599
567
|
ctx.fill(path2d);
|
|
600
568
|
}
|
|
601
569
|
}, [paths, currentPath, width, height]);
|
|
602
|
-
const getPoint = (0,
|
|
570
|
+
const getPoint = (0, import_react2.useCallback)((e) => {
|
|
603
571
|
const rect = canvasRef.current.getBoundingClientRect();
|
|
604
572
|
return [
|
|
605
573
|
e.clientX - rect.left,
|
|
@@ -607,17 +575,17 @@ function SignatureCanvas({
|
|
|
607
575
|
e.pressure
|
|
608
576
|
];
|
|
609
577
|
}, []);
|
|
610
|
-
const handlePointerDown = (0,
|
|
578
|
+
const handlePointerDown = (0, import_react2.useCallback)((e) => {
|
|
611
579
|
e.preventDefault();
|
|
612
580
|
e.target.setPointerCapture(e.pointerId);
|
|
613
581
|
setCurrentPath([getPoint(e)]);
|
|
614
582
|
}, [getPoint]);
|
|
615
|
-
const handlePointerMove = (0,
|
|
583
|
+
const handlePointerMove = (0, import_react2.useCallback)((e) => {
|
|
616
584
|
if (!currentPath) return;
|
|
617
585
|
e.preventDefault();
|
|
618
586
|
setCurrentPath([...currentPath, getPoint(e)]);
|
|
619
587
|
}, [currentPath, getPoint]);
|
|
620
|
-
const handlePointerUp = (0,
|
|
588
|
+
const handlePointerUp = (0, import_react2.useCallback)(() => {
|
|
621
589
|
if (!currentPath) return;
|
|
622
590
|
setPaths((prev) => [...prev, currentPath]);
|
|
623
591
|
setCurrentPath(null);
|
|
@@ -628,7 +596,7 @@ function SignatureCanvas({
|
|
|
628
596
|
}
|
|
629
597
|
});
|
|
630
598
|
}, [currentPath, onSign]);
|
|
631
|
-
const handleClear = (0,
|
|
599
|
+
const handleClear = (0, import_react2.useCallback)(() => {
|
|
632
600
|
setPaths([]);
|
|
633
601
|
setCurrentPath(null);
|
|
634
602
|
setIsEmpty(true);
|
|
@@ -639,8 +607,8 @@ function SignatureCanvas({
|
|
|
639
607
|
onSign("");
|
|
640
608
|
}
|
|
641
609
|
}, [width, height, onSign]);
|
|
642
|
-
return /* @__PURE__ */ (0,
|
|
643
|
-
/* @__PURE__ */ (0,
|
|
610
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `signature-canvas-wrapper ${className || ""}`, children: [
|
|
611
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
644
612
|
"canvas",
|
|
645
613
|
{
|
|
646
614
|
ref: canvasRef,
|
|
@@ -653,29 +621,68 @@ function SignatureCanvas({
|
|
|
653
621
|
style: { touchAction: "none" }
|
|
654
622
|
}
|
|
655
623
|
),
|
|
656
|
-
/* @__PURE__ */ (0,
|
|
657
|
-
!isEmpty && /* @__PURE__ */ (0,
|
|
658
|
-
isEmpty && /* @__PURE__ */ (0,
|
|
624
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "signature-canvas-actions", children: [
|
|
625
|
+
!isEmpty && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", onClick: handleClear, className: "signature-clear-btn", children: "Clear" }),
|
|
626
|
+
isEmpty && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "signature-hint", children: "Draw your signature above" })
|
|
659
627
|
] })
|
|
660
628
|
] });
|
|
661
629
|
}
|
|
662
630
|
|
|
631
|
+
// src/utils/apiKey.ts
|
|
632
|
+
var VALID_KEYS = new Set([
|
|
633
|
+
"exeq_live_2024",
|
|
634
|
+
process.env.NEXT_PUBLIC_EXEQ_API_KEY
|
|
635
|
+
].filter(Boolean));
|
|
636
|
+
function isValidApiKey(key) {
|
|
637
|
+
if (!key) return false;
|
|
638
|
+
return VALID_KEYS.has(key);
|
|
639
|
+
}
|
|
640
|
+
|
|
663
641
|
// src/components/pdf-builder/DesignerView.tsx
|
|
664
|
-
var
|
|
642
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
643
|
+
var FIELD_TYPE_META = [
|
|
644
|
+
{ type: "text", label: "Text", icon: "T" },
|
|
645
|
+
{ type: "signature", label: "Signature", icon: "\u270D" },
|
|
646
|
+
{ type: "signed-date", label: "Date", icon: "\u{1F4C5}" },
|
|
647
|
+
{ type: "checkbox", label: "Checkbox", icon: "\u2611" },
|
|
648
|
+
{ type: "initials", label: "Initials", icon: "IN" },
|
|
649
|
+
{ type: "blackout", label: "Blackout", icon: "\u25A0", group: "redact" },
|
|
650
|
+
{ type: "whiteout", label: "Whiteout", icon: "\u25A1", group: "redact" }
|
|
651
|
+
];
|
|
665
652
|
function DesignerView({
|
|
653
|
+
apiKey,
|
|
666
654
|
initialPdfUrl,
|
|
667
655
|
initialTemplate,
|
|
668
|
-
onSave
|
|
656
|
+
onSave,
|
|
657
|
+
hideHeader,
|
|
658
|
+
headerPortalRef
|
|
669
659
|
} = {}) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
660
|
+
if (!isValidApiKey(apiKey)) {
|
|
661
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "designer-layout", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "empty-state", children: [
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: "Invalid API Key" }),
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { children: [
|
|
664
|
+
"A valid API key is required to use Exeq. Visit ",
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href: "https://exeq.org", children: "exeq.org" }),
|
|
666
|
+
" to get one."
|
|
667
|
+
] })
|
|
668
|
+
] }) });
|
|
669
|
+
}
|
|
670
|
+
const [pages, setPages] = (0, import_react3.useState)([]);
|
|
671
|
+
const [fields, setFields] = (0, import_react3.useState)(initialTemplate?.fields ?? []);
|
|
672
|
+
const [selectedFieldId, setSelectedFieldId] = (0, import_react3.useState)(null);
|
|
673
|
+
const [signerRoles, setSignerRoles] = (0, import_react3.useState)(initialTemplate?.signerRoles ?? [...DEFAULT_SIGNER_ROLES]);
|
|
674
|
+
const [activeRole, setActiveRole] = (0, import_react3.useState)("Sender");
|
|
675
|
+
const [activeFieldType, setActiveFieldType] = (0, import_react3.useState)("text");
|
|
676
|
+
const [loading, setLoading] = (0, import_react3.useState)(false);
|
|
677
|
+
const [pdfSource, setPdfSource] = (0, import_react3.useState)(null);
|
|
678
|
+
const [rightTab, setRightTab] = (0, import_react3.useState)("properties");
|
|
679
|
+
const [isAddingRole, setIsAddingRole] = (0, import_react3.useState)(false);
|
|
680
|
+
const [newRoleName, setNewRoleName] = (0, import_react3.useState)("");
|
|
681
|
+
const [draggingFieldType, setDraggingFieldType] = (0, import_react3.useState)(null);
|
|
682
|
+
const [panelWidth, setPanelWidth] = (0, import_react3.useState)(380);
|
|
683
|
+
const dragGhostRef = (0, import_react3.useRef)(null);
|
|
684
|
+
const resizingRef = (0, import_react3.useRef)(false);
|
|
685
|
+
(0, import_react3.useEffect)(() => {
|
|
679
686
|
const pdfUrl = initialPdfUrl || initialTemplate?.pdfUrl;
|
|
680
687
|
if (pdfUrl) {
|
|
681
688
|
loadPdf(pdfUrl);
|
|
@@ -699,7 +706,7 @@ function DesignerView({
|
|
|
699
706
|
window.addEventListener("message", handleMessage);
|
|
700
707
|
return () => window.removeEventListener("message", handleMessage);
|
|
701
708
|
}, [initialPdfUrl, initialTemplate]);
|
|
702
|
-
const loadPdf = (0,
|
|
709
|
+
const loadPdf = (0, import_react3.useCallback)(async (source) => {
|
|
703
710
|
setLoading(true);
|
|
704
711
|
try {
|
|
705
712
|
const rendered = await renderPdfPages(source);
|
|
@@ -711,7 +718,7 @@ function DesignerView({
|
|
|
711
718
|
setLoading(false);
|
|
712
719
|
}
|
|
713
720
|
}, []);
|
|
714
|
-
const handleFileUpload = (0,
|
|
721
|
+
const handleFileUpload = (0, import_react3.useCallback)((e) => {
|
|
715
722
|
const file = e.target.files?.[0];
|
|
716
723
|
if (!file) return;
|
|
717
724
|
const reader = new FileReader();
|
|
@@ -720,35 +727,45 @@ function DesignerView({
|
|
|
720
727
|
};
|
|
721
728
|
reader.readAsArrayBuffer(file);
|
|
722
729
|
}, [loadPdf]);
|
|
723
|
-
const handlePageClick = (0,
|
|
724
|
-
const field = createField(activeFieldType, activeRole, page, x, y);
|
|
730
|
+
const handlePageClick = (0, import_react3.useCallback)((page, x, y) => {
|
|
731
|
+
const field = createField(activeFieldType, activeRole, page, x, y, fields);
|
|
725
732
|
setFields((prev) => [...prev, field]);
|
|
726
733
|
setSelectedFieldId(field.id);
|
|
727
|
-
|
|
728
|
-
|
|
734
|
+
setRightTab("properties");
|
|
735
|
+
}, [activeFieldType, activeRole, fields]);
|
|
736
|
+
const handleFieldMove = (0, import_react3.useCallback)((id, page, x, y) => {
|
|
729
737
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, page, x, y } : f));
|
|
730
738
|
}, []);
|
|
731
|
-
const handleFieldResize = (0,
|
|
739
|
+
const handleFieldResize = (0, import_react3.useCallback)((id, width, height) => {
|
|
732
740
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, width, height } : f));
|
|
733
741
|
}, []);
|
|
734
|
-
const handleFieldUpdate = (0,
|
|
735
|
-
setFields((prev) =>
|
|
742
|
+
const handleFieldUpdate = (0, import_react3.useCallback)((id, updates) => {
|
|
743
|
+
setFields((prev) => {
|
|
744
|
+
if (updates.label !== void 0) {
|
|
745
|
+
const otherLabels = prev.filter((f) => f.id !== id).map((f) => f.label);
|
|
746
|
+
updates.label = uniqueLabel(updates.label, otherLabels);
|
|
747
|
+
}
|
|
748
|
+
return prev.map((f) => f.id === id ? { ...f, ...updates } : f);
|
|
749
|
+
});
|
|
736
750
|
}, []);
|
|
737
|
-
const handleFieldDelete = (0,
|
|
751
|
+
const handleFieldDelete = (0, import_react3.useCallback)((id) => {
|
|
738
752
|
setFields((prev) => prev.filter((f) => f.id !== id));
|
|
739
753
|
if (selectedFieldId === id) setSelectedFieldId(null);
|
|
740
754
|
}, [selectedFieldId]);
|
|
741
|
-
const handleAddRole = (0,
|
|
742
|
-
|
|
743
|
-
|
|
755
|
+
const handleAddRole = (0, import_react3.useCallback)(() => {
|
|
756
|
+
const name = newRoleName.trim();
|
|
757
|
+
if (name && !signerRoles.includes(name)) {
|
|
758
|
+
setSignerRoles((prev) => [...prev, name]);
|
|
744
759
|
}
|
|
745
|
-
|
|
746
|
-
|
|
760
|
+
setNewRoleName("");
|
|
761
|
+
setIsAddingRole(false);
|
|
762
|
+
}, [newRoleName, signerRoles]);
|
|
763
|
+
const handleRemoveRole = (0, import_react3.useCallback)((role) => {
|
|
747
764
|
setSignerRoles((prev) => prev.filter((r) => r !== role));
|
|
748
765
|
setFields((prev) => prev.map((f) => f.assignee === role ? { ...f, assignee: signerRoles[0] } : f));
|
|
749
766
|
if (activeRole === role) setActiveRole(signerRoles[0]);
|
|
750
767
|
}, [signerRoles, activeRole]);
|
|
751
|
-
const handleExport = (0,
|
|
768
|
+
const handleExport = (0, import_react3.useCallback)(() => {
|
|
752
769
|
const template = {
|
|
753
770
|
fields,
|
|
754
771
|
signerRoles,
|
|
@@ -768,160 +785,310 @@ function DesignerView({
|
|
|
768
785
|
}
|
|
769
786
|
window.parent?.postMessage({ type: "template-saved", template }, "*");
|
|
770
787
|
}, [fields, signerRoles, pdfSource, onSave]);
|
|
788
|
+
const handlePaletteDragStart = (0, import_react3.useCallback)((e, type) => {
|
|
789
|
+
setDraggingFieldType(type);
|
|
790
|
+
e.dataTransfer.setData("application/exeq-field-type", type);
|
|
791
|
+
e.dataTransfer.effectAllowed = "copy";
|
|
792
|
+
const ghost = document.createElement("div");
|
|
793
|
+
ghost.className = "palette-drag-ghost";
|
|
794
|
+
ghost.textContent = FIELD_TYPE_META.find((f) => f.type === type)?.label || type;
|
|
795
|
+
ghost.style.cssText = `
|
|
796
|
+
position: fixed; top: -200px; left: -200px;
|
|
797
|
+
padding: 6px 16px; background: ${getSignerColor(activeRole)};
|
|
798
|
+
color: #fff; border-radius: 4px; font-size: 12px;
|
|
799
|
+
font-family: system-ui; pointer-events: none; white-space: nowrap;
|
|
800
|
+
`;
|
|
801
|
+
document.body.appendChild(ghost);
|
|
802
|
+
e.dataTransfer.setDragImage(ghost, 40, 16);
|
|
803
|
+
dragGhostRef.current = ghost;
|
|
804
|
+
}, [activeRole]);
|
|
805
|
+
const handlePaletteDragEnd = (0, import_react3.useCallback)(() => {
|
|
806
|
+
setDraggingFieldType(null);
|
|
807
|
+
if (dragGhostRef.current) {
|
|
808
|
+
document.body.removeChild(dragGhostRef.current);
|
|
809
|
+
dragGhostRef.current = null;
|
|
810
|
+
}
|
|
811
|
+
}, []);
|
|
812
|
+
const handleDropOnPage = (0, import_react3.useCallback)((page, x, y, fieldType) => {
|
|
813
|
+
const field = createField(fieldType, activeRole, page, x, y, fields);
|
|
814
|
+
setFields((prev) => [...prev, field]);
|
|
815
|
+
setSelectedFieldId(field.id);
|
|
816
|
+
setRightTab("properties");
|
|
817
|
+
}, [activeRole, fields]);
|
|
818
|
+
(0, import_react3.useEffect)(() => {
|
|
819
|
+
const handleKeyDown = (e) => {
|
|
820
|
+
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
|
821
|
+
if (!selectedFieldId) return;
|
|
822
|
+
const tag = (document.activeElement?.tagName || "").toLowerCase();
|
|
823
|
+
if (tag === "input" || tag === "textarea" || tag === "select") return;
|
|
824
|
+
if (document.activeElement?.isContentEditable) return;
|
|
825
|
+
e.preventDefault();
|
|
826
|
+
if (window.confirm("Delete this field?")) {
|
|
827
|
+
handleFieldDelete(selectedFieldId);
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
831
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
832
|
+
}, [selectedFieldId, handleFieldDelete]);
|
|
833
|
+
const handleResizeStart = (0, import_react3.useCallback)((e) => {
|
|
834
|
+
e.preventDefault();
|
|
835
|
+
resizingRef.current = true;
|
|
836
|
+
const startX = e.clientX;
|
|
837
|
+
const startWidth = panelWidth;
|
|
838
|
+
const handleMouseMove = (e2) => {
|
|
839
|
+
if (!resizingRef.current) return;
|
|
840
|
+
const delta = startX - e2.clientX;
|
|
841
|
+
setPanelWidth(Math.max(280, Math.min(600, startWidth + delta)));
|
|
842
|
+
};
|
|
843
|
+
const handleMouseUp = () => {
|
|
844
|
+
resizingRef.current = false;
|
|
845
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
846
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
847
|
+
};
|
|
848
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
849
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
850
|
+
}, [panelWidth]);
|
|
851
|
+
const sortedFields = [...fields].sort((a, b) => {
|
|
852
|
+
if (a.page !== b.page) return a.page - b.page;
|
|
853
|
+
const bandThreshold = 2;
|
|
854
|
+
if (Math.abs(a.y - b.y) > bandThreshold) return a.y - b.y;
|
|
855
|
+
return a.x - b.x;
|
|
856
|
+
});
|
|
771
857
|
const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
|
|
772
|
-
const renderFieldContent = (0,
|
|
858
|
+
const renderFieldContent = (0, import_react3.useCallback)((field) => {
|
|
859
|
+
if (field.type === "blackout" || field.type === "whiteout") {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
773
862
|
if (field.type === "signature" || field.type === "initials") {
|
|
774
863
|
if (field.value) {
|
|
775
|
-
return /* @__PURE__ */ (0,
|
|
864
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("img", { src: field.value, alt: field.label, className: "field-signature-preview" });
|
|
776
865
|
}
|
|
777
866
|
}
|
|
778
|
-
|
|
867
|
+
const inkColor = field.inkColor || "#000000";
|
|
868
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
869
|
+
"div",
|
|
870
|
+
{
|
|
871
|
+
className: "field-overlay-placeholder",
|
|
872
|
+
style: { color: field.value ? inkColor : void 0, fontSize: `${field.fontSize * 0.6}px` },
|
|
873
|
+
children: field.value || field.placeholder
|
|
874
|
+
}
|
|
875
|
+
);
|
|
779
876
|
}, []);
|
|
780
|
-
|
|
781
|
-
/* @__PURE__ */ (0,
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
877
|
+
const headerButtons = pages.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
878
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "header-btn header-btn-outline", children: [
|
|
879
|
+
"Change PDF",
|
|
880
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
881
|
+
] }),
|
|
882
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: handleExport, className: "header-btn header-btn-primary", children: "Export Template" })
|
|
883
|
+
] }) : null;
|
|
884
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-layout", children: [
|
|
885
|
+
headerPortalRef?.current && headerButtons && (0, import_react_dom.createPortal)(headerButtons, headerPortalRef.current),
|
|
886
|
+
!hideHeader && !headerPortalRef && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "designer-header", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "designer-header-right", children: headerButtons }) }),
|
|
887
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-body", children: [
|
|
888
|
+
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-palette", children: [
|
|
889
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-heading", children: "New Field Role" }),
|
|
890
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "palette-role-section", children: [
|
|
891
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
892
|
+
"select",
|
|
794
893
|
{
|
|
795
|
-
className:
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
894
|
+
className: "palette-role-select",
|
|
895
|
+
value: activeRole,
|
|
896
|
+
onChange: (e) => setActiveRole(e.target.value),
|
|
897
|
+
style: { borderColor: getSignerColor(activeRole), color: getSignerColor(activeRole) },
|
|
898
|
+
children: signerRoles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: role, children: role }, role))
|
|
899
|
+
}
|
|
900
|
+
),
|
|
901
|
+
isAddingRole ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "palette-role-add-inline", children: [
|
|
902
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
903
|
+
"input",
|
|
904
|
+
{
|
|
905
|
+
type: "text",
|
|
906
|
+
value: newRoleName,
|
|
907
|
+
onChange: (e) => setNewRoleName(e.target.value),
|
|
908
|
+
onKeyDown: (e) => e.key === "Enter" && handleAddRole(),
|
|
909
|
+
placeholder: "Role name",
|
|
910
|
+
autoFocus: true
|
|
911
|
+
}
|
|
912
|
+
),
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: handleAddRole, children: "Add" }),
|
|
914
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: () => {
|
|
915
|
+
setIsAddingRole(false);
|
|
916
|
+
setNewRoleName("");
|
|
917
|
+
}, children: "Cancel" })
|
|
918
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "palette-role-add-link", onClick: () => setIsAddingRole(true), children: "+ Add Role" })
|
|
919
|
+
] }),
|
|
920
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-divider" }),
|
|
921
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-heading", children: "Fields" }),
|
|
922
|
+
FIELD_TYPE_META.filter((f) => !f.group).map(({ type, label, icon }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
923
|
+
"div",
|
|
924
|
+
{
|
|
925
|
+
className: `palette-item ${activeFieldType === type ? "active" : ""}`,
|
|
926
|
+
draggable: true,
|
|
927
|
+
onClick: () => setActiveFieldType(type),
|
|
928
|
+
onDragStart: (e) => handlePaletteDragStart(e, type),
|
|
929
|
+
onDragEnd: handlePaletteDragEnd,
|
|
930
|
+
children: [
|
|
931
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "palette-item-icon", children: icon }),
|
|
932
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "palette-item-label", children: label })
|
|
933
|
+
]
|
|
934
|
+
},
|
|
935
|
+
type
|
|
936
|
+
)),
|
|
937
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-divider" }),
|
|
938
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "palette-heading", children: "Redact" }),
|
|
939
|
+
FIELD_TYPE_META.filter((f) => f.group === "redact").map(({ type, label, icon }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
940
|
+
"div",
|
|
941
|
+
{
|
|
942
|
+
className: `palette-item ${activeFieldType === type ? "active" : ""}`,
|
|
943
|
+
draggable: true,
|
|
944
|
+
onClick: () => setActiveFieldType(type),
|
|
945
|
+
onDragStart: (e) => handlePaletteDragStart(e, type),
|
|
946
|
+
onDragEnd: handlePaletteDragEnd,
|
|
947
|
+
children: [
|
|
948
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "palette-item-icon", children: icon }),
|
|
949
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "palette-item-label", children: label })
|
|
950
|
+
]
|
|
951
|
+
},
|
|
952
|
+
type
|
|
953
|
+
))
|
|
802
954
|
] }),
|
|
803
|
-
/* @__PURE__ */ (0,
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
onAddRole: handleAddRole,
|
|
812
|
-
onRemoveRole: handleRemoveRole
|
|
813
|
-
}
|
|
814
|
-
),
|
|
815
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "designer-content", children: [
|
|
816
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "designer-pdf-area", children: [
|
|
817
|
-
loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "loading-indicator", children: "Loading PDF..." }),
|
|
818
|
-
!pages.length && !loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "empty-state", children: [
|
|
819
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { children: "Upload a PDF to get started" }),
|
|
820
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: "Upload a template PDF, then click on the page to place form fields." }),
|
|
821
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "upload-btn upload-btn-large", children: [
|
|
822
|
-
"Choose PDF File",
|
|
823
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
955
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-pdf-area", children: [
|
|
956
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "loading-indicator", children: "Loading PDF..." }),
|
|
957
|
+
!pages.length && !loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "empty-state", children: [
|
|
958
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: "Open a PDF to get started" }),
|
|
959
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "Select a PDF from your device to begin. Your file stays on your computer \u2014 nothing is uploaded." }),
|
|
960
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "upload-btn upload-btn-large", children: [
|
|
961
|
+
"Select PDF",
|
|
962
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "file", accept: ".pdf", onChange: handleFileUpload, hidden: true })
|
|
824
963
|
] })
|
|
825
964
|
] }),
|
|
826
|
-
pages.length > 0 && /* @__PURE__ */ (0,
|
|
965
|
+
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
827
966
|
PdfViewer,
|
|
828
967
|
{
|
|
829
968
|
pages,
|
|
830
969
|
fields,
|
|
831
970
|
selectedFieldId,
|
|
832
|
-
onSelectField:
|
|
971
|
+
onSelectField: (id) => {
|
|
972
|
+
setSelectedFieldId(id);
|
|
973
|
+
if (id) setRightTab("properties");
|
|
974
|
+
},
|
|
833
975
|
onFieldMove: handleFieldMove,
|
|
834
976
|
onFieldResize: handleFieldResize,
|
|
835
977
|
onPageClick: handlePageClick,
|
|
978
|
+
onDropField: handleDropOnPage,
|
|
836
979
|
mode: "designer",
|
|
837
980
|
renderFieldContent
|
|
838
981
|
}
|
|
839
982
|
)
|
|
840
983
|
] }),
|
|
841
|
-
/* @__PURE__ */ (0,
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
signerRoles,
|
|
848
|
-
onUpdate: handleFieldUpdate,
|
|
849
|
-
onDelete: handleFieldDelete
|
|
850
|
-
}
|
|
851
|
-
),
|
|
852
|
-
selectedField.assignee === activeRole && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "prefill-section", children: [
|
|
853
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h4", { children: "Pre-fill Value" }),
|
|
854
|
-
selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
855
|
-
SignatureCanvas,
|
|
984
|
+
pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
985
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-resize-handle", onMouseDown: handleResizeStart }),
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "designer-panel", style: { width: panelWidth }, children: [
|
|
987
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tabs", children: [
|
|
988
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
989
|
+
"button",
|
|
856
990
|
{
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
initialValue: selectedField.value
|
|
991
|
+
className: `panel-tab ${rightTab === "properties" ? "active" : ""}`,
|
|
992
|
+
onClick: () => setRightTab("properties"),
|
|
993
|
+
children: "Properties"
|
|
861
994
|
}
|
|
862
|
-
)
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
{
|
|
866
|
-
type: "checkbox",
|
|
867
|
-
checked: selectedField.value === "true",
|
|
868
|
-
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
|
|
869
|
-
}
|
|
870
|
-
),
|
|
871
|
-
"Checked"
|
|
872
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
873
|
-
"input",
|
|
995
|
+
),
|
|
996
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
997
|
+
"button",
|
|
874
998
|
{
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
999
|
+
className: `panel-tab ${rightTab === "fields" ? "active" : ""}`,
|
|
1000
|
+
onClick: () => setRightTab("fields"),
|
|
1001
|
+
children: [
|
|
1002
|
+
"All Fields",
|
|
1003
|
+
fields.length > 0 ? ` (${fields.length})` : ""
|
|
1004
|
+
]
|
|
880
1005
|
}
|
|
881
1006
|
)
|
|
882
|
-
] })
|
|
883
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "panel-empty", children: pages.length > 0 ? "Click on the PDF to place a field, or select an existing field to edit its properties." : "Upload a PDF to begin designing your template." }),
|
|
884
|
-
fields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "field-list", children: [
|
|
885
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("h4", { children: [
|
|
886
|
-
"All Fields (",
|
|
887
|
-
fields.length,
|
|
888
|
-
")"
|
|
889
1007
|
] }),
|
|
890
|
-
|
|
891
|
-
"
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1008
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "panel-tab-content", children: [
|
|
1009
|
+
rightTab === "properties" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: selectedField ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1010
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1011
|
+
FieldPropertyPanel,
|
|
1012
|
+
{
|
|
1013
|
+
field: selectedField,
|
|
1014
|
+
signerRoles,
|
|
1015
|
+
onUpdate: handleFieldUpdate,
|
|
1016
|
+
onDelete: handleFieldDelete
|
|
1017
|
+
}
|
|
1018
|
+
),
|
|
1019
|
+
selectedField.type !== "blackout" && selectedField.type !== "whiteout" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "prefill-section", children: [
|
|
1020
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "Pre-fill Value" }),
|
|
1021
|
+
selectedField.type === "signature" || selectedField.type === "initials" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1022
|
+
SignatureCanvas,
|
|
898
1023
|
{
|
|
899
|
-
|
|
900
|
-
|
|
1024
|
+
width: panelWidth - 40,
|
|
1025
|
+
height: selectedField.type === "initials" ? 100 : 150,
|
|
1026
|
+
onSign: (dataUrl) => handleFieldUpdate(selectedField.id, { value: dataUrl }),
|
|
1027
|
+
initialValue: selectedField.value,
|
|
1028
|
+
inkColor: selectedField.inkColor
|
|
901
1029
|
}
|
|
902
|
-
),
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1030
|
+
) : selectedField.type === "checkbox" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "panel-checkbox-label", children: [
|
|
1031
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1032
|
+
"input",
|
|
1033
|
+
{
|
|
1034
|
+
type: "checkbox",
|
|
1035
|
+
checked: selectedField.value === "true",
|
|
1036
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.checked ? "true" : "" })
|
|
1037
|
+
}
|
|
1038
|
+
),
|
|
1039
|
+
"Checked"
|
|
1040
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1041
|
+
"input",
|
|
1042
|
+
{
|
|
1043
|
+
type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
|
|
1044
|
+
value: selectedField.value,
|
|
1045
|
+
onChange: (e) => handleFieldUpdate(selectedField.id, { value: e.target.value }),
|
|
1046
|
+
placeholder: `Pre-fill ${selectedField.label}`,
|
|
1047
|
+
className: "prefill-input",
|
|
1048
|
+
style: { color: selectedField.inkColor || "#000000" }
|
|
1049
|
+
}
|
|
1050
|
+
)
|
|
1051
|
+
] })
|
|
1052
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-empty", children: "Click on the PDF to place a field, or select an existing field to edit its properties." }) }),
|
|
1053
|
+
rightTab === "fields" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fields.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "panel-empty", children: "No fields yet. Drag a field from the left palette onto the PDF, or click on the PDF to place one." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "field-list", children: sortedFields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1054
|
+
"div",
|
|
1055
|
+
{
|
|
1056
|
+
className: `field-list-item ${f.id === selectedFieldId ? "active" : ""}`,
|
|
1057
|
+
onClick: () => {
|
|
1058
|
+
setSelectedFieldId(f.id);
|
|
1059
|
+
setRightTab("properties");
|
|
1060
|
+
},
|
|
1061
|
+
children: [
|
|
1062
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1063
|
+
"span",
|
|
1064
|
+
{
|
|
1065
|
+
className: "field-list-dot",
|
|
1066
|
+
style: { backgroundColor: getSignerColor(f.assignee) }
|
|
1067
|
+
}
|
|
1068
|
+
),
|
|
1069
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-name", children: f.label }),
|
|
1070
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "field-list-page", children: [
|
|
1071
|
+
"p",
|
|
1072
|
+
f.page + 1
|
|
1073
|
+
] }),
|
|
1074
|
+
f.required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "field-list-required", children: "*" })
|
|
1075
|
+
]
|
|
1076
|
+
},
|
|
1077
|
+
f.id
|
|
1078
|
+
)) }) })
|
|
1079
|
+
] }),
|
|
1080
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "powered-by", children: [
|
|
1081
|
+
"Powered by ",
|
|
1082
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { href: "https://exeq.org", target: "_blank", rel: "noopener noreferrer", children: "Exeq.org" })
|
|
1083
|
+
] })
|
|
913
1084
|
] })
|
|
914
1085
|
] })
|
|
915
|
-
] }),
|
|
916
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "powered-by", children: [
|
|
917
|
-
"Powered by ",
|
|
918
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href: "https://exeq.org", target: "_blank", rel: "noopener noreferrer", children: "Exeq.org" })
|
|
919
1086
|
] })
|
|
920
1087
|
] });
|
|
921
1088
|
}
|
|
922
1089
|
|
|
923
1090
|
// src/components/pdf-builder/SignerView.tsx
|
|
924
|
-
var
|
|
1091
|
+
var import_react4 = require("react");
|
|
925
1092
|
|
|
926
1093
|
// src/utils/pdfFiller.ts
|
|
927
1094
|
var import_pdf_lib = require("pdf-lib");
|
|
@@ -936,8 +1103,13 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
936
1103
|
const pdfDoc = await import_pdf_lib.PDFDocument.load(pdfBytes);
|
|
937
1104
|
const font = await pdfDoc.embedFont(import_pdf_lib.StandardFonts.Helvetica);
|
|
938
1105
|
const pages = pdfDoc.getPages();
|
|
1106
|
+
function hexToRgb(hex) {
|
|
1107
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
1108
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
1109
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
1110
|
+
return (0, import_pdf_lib.rgb)(r, g, b);
|
|
1111
|
+
}
|
|
939
1112
|
for (const field of fields) {
|
|
940
|
-
if (!field.value) continue;
|
|
941
1113
|
const page = pages[field.page];
|
|
942
1114
|
if (!page) continue;
|
|
943
1115
|
const pageWidth = page.getWidth();
|
|
@@ -946,6 +1118,18 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
946
1118
|
const y = pageHeight - field.y / 100 * pageHeight - field.height / 100 * pageHeight;
|
|
947
1119
|
const w = field.width / 100 * pageWidth;
|
|
948
1120
|
const h = field.height / 100 * pageHeight;
|
|
1121
|
+
if (field.type === "blackout" || field.type === "whiteout") {
|
|
1122
|
+
page.drawRectangle({
|
|
1123
|
+
x,
|
|
1124
|
+
y,
|
|
1125
|
+
width: w,
|
|
1126
|
+
height: h,
|
|
1127
|
+
color: field.type === "blackout" ? (0, import_pdf_lib.rgb)(0, 0, 0) : (0, import_pdf_lib.rgb)(1, 1, 1)
|
|
1128
|
+
});
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
if (!field.value) continue;
|
|
1132
|
+
const inkColor = field.inkColor ? hexToRgb(field.inkColor) : (0, import_pdf_lib.rgb)(0, 0, 0);
|
|
949
1133
|
if (field.type === "checkbox") {
|
|
950
1134
|
if (field.value === "true") {
|
|
951
1135
|
const inset = Math.min(w, h) * 0.2;
|
|
@@ -954,13 +1138,13 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
954
1138
|
start: { x: x + inset, y: y + inset },
|
|
955
1139
|
end: { x: x + w - inset, y: y + h - inset },
|
|
956
1140
|
thickness: lineWidth,
|
|
957
|
-
color:
|
|
1141
|
+
color: inkColor
|
|
958
1142
|
});
|
|
959
1143
|
page.drawLine({
|
|
960
1144
|
start: { x: x + w - inset, y: y + inset },
|
|
961
1145
|
end: { x: x + inset, y: y + h - inset },
|
|
962
1146
|
thickness: lineWidth,
|
|
963
|
-
color:
|
|
1147
|
+
color: inkColor
|
|
964
1148
|
});
|
|
965
1149
|
}
|
|
966
1150
|
} else if (field.type === "signature" || field.type === "initials") {
|
|
@@ -984,7 +1168,7 @@ async function generateFilledPdf(pdfSource, fields) {
|
|
|
984
1168
|
y: y + h * 0.3,
|
|
985
1169
|
size: fontSize,
|
|
986
1170
|
font,
|
|
987
|
-
color:
|
|
1171
|
+
color: inkColor,
|
|
988
1172
|
maxWidth: w - 4
|
|
989
1173
|
});
|
|
990
1174
|
}
|
|
@@ -1011,7 +1195,7 @@ async function postPdfToCallback(bytes, callbackUrl, filename) {
|
|
|
1011
1195
|
}
|
|
1012
1196
|
|
|
1013
1197
|
// src/components/pdf-builder/FieldNavigator.tsx
|
|
1014
|
-
var
|
|
1198
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1015
1199
|
function FieldNavigator({
|
|
1016
1200
|
fields,
|
|
1017
1201
|
currentFieldId,
|
|
@@ -1037,15 +1221,15 @@ function FieldNavigator({
|
|
|
1037
1221
|
if (f.type === "checkbox") return true;
|
|
1038
1222
|
return !!f.value;
|
|
1039
1223
|
}).length;
|
|
1040
|
-
return /* @__PURE__ */ (0,
|
|
1041
|
-
/* @__PURE__ */ (0,
|
|
1224
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "field-navigator", children: [
|
|
1225
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "field-navigator-progress", children: [
|
|
1042
1226
|
filledCount,
|
|
1043
1227
|
" of ",
|
|
1044
1228
|
fields.length,
|
|
1045
1229
|
" fields completed"
|
|
1046
1230
|
] }),
|
|
1047
|
-
/* @__PURE__ */ (0,
|
|
1048
|
-
/* @__PURE__ */ (0,
|
|
1231
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "field-navigator-controls", children: [
|
|
1232
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1049
1233
|
"button",
|
|
1050
1234
|
{
|
|
1051
1235
|
onClick: handlePrev,
|
|
@@ -1054,8 +1238,8 @@ function FieldNavigator({
|
|
|
1054
1238
|
children: "Prev"
|
|
1055
1239
|
}
|
|
1056
1240
|
),
|
|
1057
|
-
/* @__PURE__ */ (0,
|
|
1058
|
-
/* @__PURE__ */ (0,
|
|
1241
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "field-navigator-position", children: currentIndex >= 0 ? `${currentIndex + 1} / ${fields.length}` : "-" }),
|
|
1242
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1059
1243
|
"button",
|
|
1060
1244
|
{
|
|
1061
1245
|
onClick: handleNext,
|
|
@@ -1065,7 +1249,7 @@ function FieldNavigator({
|
|
|
1065
1249
|
}
|
|
1066
1250
|
)
|
|
1067
1251
|
] }),
|
|
1068
|
-
/* @__PURE__ */ (0,
|
|
1252
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1069
1253
|
"button",
|
|
1070
1254
|
{
|
|
1071
1255
|
onClick: onSubmit,
|
|
@@ -1078,26 +1262,49 @@ function FieldNavigator({
|
|
|
1078
1262
|
}
|
|
1079
1263
|
|
|
1080
1264
|
// src/components/pdf-builder/SignerView.tsx
|
|
1081
|
-
var
|
|
1265
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1082
1266
|
function SignerView({
|
|
1267
|
+
apiKey,
|
|
1083
1268
|
initialPdfUrl,
|
|
1084
1269
|
initialTemplate,
|
|
1085
1270
|
initialSigner,
|
|
1086
1271
|
callbackUrl: initialCallbackUrl,
|
|
1087
|
-
onComplete
|
|
1272
|
+
onComplete,
|
|
1273
|
+
initialValues
|
|
1088
1274
|
} = {}) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1275
|
+
if (!isValidApiKey(apiKey)) {
|
|
1276
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "signer-layout", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "empty-state", children: [
|
|
1277
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { children: "Invalid API Key" }),
|
|
1278
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { children: [
|
|
1279
|
+
"A valid API key is required to use Exeq. Visit ",
|
|
1280
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href: "https://exeq.org", children: "exeq.org" }),
|
|
1281
|
+
" to get one."
|
|
1282
|
+
] })
|
|
1283
|
+
] }) });
|
|
1284
|
+
}
|
|
1285
|
+
const [pages, setPages] = (0, import_react4.useState)([]);
|
|
1286
|
+
const [fields, setFields] = (0, import_react4.useState)([]);
|
|
1287
|
+
const [selectedFieldId, setSelectedFieldId] = (0, import_react4.useState)(null);
|
|
1288
|
+
const [signer, setSigner] = (0, import_react4.useState)(initialSigner || "Signer 1");
|
|
1289
|
+
const [loading, setLoading] = (0, import_react4.useState)(false);
|
|
1290
|
+
const [submitting, setSubmitting] = (0, import_react4.useState)(false);
|
|
1291
|
+
const [pdfSource, setPdfSource] = (0, import_react4.useState)(null);
|
|
1292
|
+
const [callbackUrl, setCallbackUrl] = (0, import_react4.useState)(initialCallbackUrl || "");
|
|
1293
|
+
const containerRef = (0, import_react4.useRef)(null);
|
|
1294
|
+
(0, import_react4.useEffect)(() => {
|
|
1099
1295
|
if (initialTemplate) {
|
|
1100
|
-
|
|
1296
|
+
let templateFields = initialTemplate.fields;
|
|
1297
|
+
if (initialValues) {
|
|
1298
|
+
templateFields = templateFields.map((f) => {
|
|
1299
|
+
const byLabel = Object.entries(initialValues).find(
|
|
1300
|
+
([key]) => key.toLowerCase() === f.label.toLowerCase()
|
|
1301
|
+
);
|
|
1302
|
+
const byId = initialValues[f.id];
|
|
1303
|
+
const value = byLabel?.[1] ?? byId;
|
|
1304
|
+
return value !== void 0 ? { ...f, value } : f;
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
setFields(templateFields);
|
|
1101
1308
|
if (initialPdfUrl) loadPdf(initialPdfUrl);
|
|
1102
1309
|
return;
|
|
1103
1310
|
}
|
|
@@ -1123,7 +1330,7 @@ function SignerView({
|
|
|
1123
1330
|
window.addEventListener("message", handleMessage);
|
|
1124
1331
|
return () => window.removeEventListener("message", handleMessage);
|
|
1125
1332
|
}, [initialTemplate, initialPdfUrl]);
|
|
1126
|
-
const loadPdf = (0,
|
|
1333
|
+
const loadPdf = (0, import_react4.useCallback)(async (source) => {
|
|
1127
1334
|
setLoading(true);
|
|
1128
1335
|
try {
|
|
1129
1336
|
const rendered = await renderPdfPages(source);
|
|
@@ -1135,13 +1342,18 @@ function SignerView({
|
|
|
1135
1342
|
setLoading(false);
|
|
1136
1343
|
}
|
|
1137
1344
|
}, []);
|
|
1138
|
-
const editableFields = fields.filter((f) => f.assignee === signer)
|
|
1345
|
+
const editableFields = fields.filter((f) => f.assignee === signer).sort((a, b) => {
|
|
1346
|
+
if (a.page !== b.page) return a.page - b.page;
|
|
1347
|
+
const bandThreshold = 2;
|
|
1348
|
+
if (Math.abs(a.y - b.y) > bandThreshold) return a.y - b.y;
|
|
1349
|
+
return a.x - b.x;
|
|
1350
|
+
});
|
|
1139
1351
|
const selectedField = fields.find((f) => f.id === selectedFieldId) || null;
|
|
1140
1352
|
const isFieldEditable = selectedField ? selectedField.assignee === signer : false;
|
|
1141
|
-
const handleFieldUpdate = (0,
|
|
1353
|
+
const handleFieldUpdate = (0, import_react4.useCallback)((id, value) => {
|
|
1142
1354
|
setFields((prev) => prev.map((f) => f.id === id ? { ...f, value } : f));
|
|
1143
1355
|
}, []);
|
|
1144
|
-
const handleNavigate = (0,
|
|
1356
|
+
const handleNavigate = (0, import_react4.useCallback)((fieldId) => {
|
|
1145
1357
|
setSelectedFieldId(fieldId);
|
|
1146
1358
|
const field = fields.find((f) => f.id === fieldId);
|
|
1147
1359
|
if (field && containerRef.current) {
|
|
@@ -1156,7 +1368,7 @@ function SignerView({
|
|
|
1156
1368
|
if (f.type === "checkbox") return true;
|
|
1157
1369
|
return !!f.value;
|
|
1158
1370
|
});
|
|
1159
|
-
const handleSubmit = (0,
|
|
1371
|
+
const handleSubmit = (0, import_react4.useCallback)(async () => {
|
|
1160
1372
|
if (!pdfSource || !allRequiredFilled) return;
|
|
1161
1373
|
setSubmitting(true);
|
|
1162
1374
|
try {
|
|
@@ -1176,25 +1388,25 @@ function SignerView({
|
|
|
1176
1388
|
setSubmitting(false);
|
|
1177
1389
|
}
|
|
1178
1390
|
}, [pdfSource, fields, callbackUrl, allRequiredFilled, onComplete]);
|
|
1179
|
-
const renderFieldContent = (0,
|
|
1391
|
+
const renderFieldContent = (0, import_react4.useCallback)((field) => {
|
|
1180
1392
|
const editable = field.assignee === signer;
|
|
1181
1393
|
if (!editable) {
|
|
1182
1394
|
if (field.type === "signature" || field.type === "initials") {
|
|
1183
|
-
return field.value ? /* @__PURE__ */ (0,
|
|
1395
|
+
return field.value ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: field.value, alt: field.label, className: "field-signature-preview" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-overlay-placeholder readonly", children: field.placeholder });
|
|
1184
1396
|
}
|
|
1185
1397
|
if (field.type === "checkbox") {
|
|
1186
|
-
return /* @__PURE__ */ (0,
|
|
1398
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-checkbox-display readonly", children: field.value === "true" ? "\u2713" : "" });
|
|
1187
1399
|
}
|
|
1188
|
-
return /* @__PURE__ */ (0,
|
|
1400
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-overlay-placeholder readonly", children: field.value || field.placeholder });
|
|
1189
1401
|
}
|
|
1190
1402
|
if (field.type === "signature" || field.type === "initials") {
|
|
1191
1403
|
if (field.value) {
|
|
1192
|
-
return /* @__PURE__ */ (0,
|
|
1404
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-signature-filled", onClick: () => handleFieldUpdate(field.id, ""), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: field.value, alt: field.label, className: "field-signature-preview" }) });
|
|
1193
1405
|
}
|
|
1194
|
-
return /* @__PURE__ */ (0,
|
|
1406
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-overlay-placeholder editable", children: field.placeholder });
|
|
1195
1407
|
}
|
|
1196
1408
|
if (field.type === "checkbox") {
|
|
1197
|
-
return /* @__PURE__ */ (0,
|
|
1409
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1198
1410
|
"div",
|
|
1199
1411
|
{
|
|
1200
1412
|
className: "field-checkbox-display editable",
|
|
@@ -1207,9 +1419,9 @@ function SignerView({
|
|
|
1207
1419
|
);
|
|
1208
1420
|
}
|
|
1209
1421
|
if (field.type === "signed-date") {
|
|
1210
|
-
return /* @__PURE__ */ (0,
|
|
1422
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "field-overlay-value", children: field.value || (/* @__PURE__ */ new Date()).toLocaleDateString() });
|
|
1211
1423
|
}
|
|
1212
|
-
return /* @__PURE__ */ (0,
|
|
1424
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1213
1425
|
"input",
|
|
1214
1426
|
{
|
|
1215
1427
|
type: field.textSubtype === "email" ? "email" : field.textSubtype === "number" ? "number" : field.textSubtype === "phone" ? "tel" : field.textSubtype === "date" ? "date" : "text",
|
|
@@ -1222,7 +1434,7 @@ function SignerView({
|
|
|
1222
1434
|
}
|
|
1223
1435
|
);
|
|
1224
1436
|
}, [signer, handleFieldUpdate]);
|
|
1225
|
-
(0,
|
|
1437
|
+
(0, import_react4.useEffect)(() => {
|
|
1226
1438
|
const sigFields = fields.filter((f) => f.assignee === signer && f.type === "signature" && f.value);
|
|
1227
1439
|
if (sigFields.length > 0) {
|
|
1228
1440
|
const dateStr = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
@@ -1234,10 +1446,10 @@ function SignerView({
|
|
|
1234
1446
|
}));
|
|
1235
1447
|
}
|
|
1236
1448
|
}, [fields.filter((f) => f.type === "signature" && f.value).length]);
|
|
1237
|
-
return /* @__PURE__ */ (0,
|
|
1238
|
-
loading && /* @__PURE__ */ (0,
|
|
1239
|
-
/* @__PURE__ */ (0,
|
|
1240
|
-
/* @__PURE__ */ (0,
|
|
1449
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-layout", ref: containerRef, children: [
|
|
1450
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "loading-indicator", children: "Loading document..." }),
|
|
1451
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-content", children: [
|
|
1452
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "signer-pdf-area", children: pages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1241
1453
|
PdfViewer,
|
|
1242
1454
|
{
|
|
1243
1455
|
pages,
|
|
@@ -1249,11 +1461,11 @@ function SignerView({
|
|
|
1249
1461
|
renderFieldContent
|
|
1250
1462
|
}
|
|
1251
1463
|
) }),
|
|
1252
|
-
/* @__PURE__ */ (0,
|
|
1253
|
-
selectedField && isFieldEditable && /* @__PURE__ */ (0,
|
|
1254
|
-
/* @__PURE__ */ (0,
|
|
1255
|
-
selectedField.required && /* @__PURE__ */ (0,
|
|
1256
|
-
(selectedField.type === "signature" || selectedField.type === "initials") && /* @__PURE__ */ (0,
|
|
1464
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-panel", children: [
|
|
1465
|
+
selectedField && isFieldEditable && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-field-input", children: [
|
|
1466
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: selectedField.label }),
|
|
1467
|
+
selectedField.required && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "required-badge", children: "Required" }),
|
|
1468
|
+
(selectedField.type === "signature" || selectedField.type === "initials") && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1257
1469
|
SignatureCanvas,
|
|
1258
1470
|
{
|
|
1259
1471
|
width: 280,
|
|
@@ -1262,7 +1474,7 @@ function SignerView({
|
|
|
1262
1474
|
initialValue: selectedField.value
|
|
1263
1475
|
}
|
|
1264
1476
|
),
|
|
1265
|
-
selectedField.type === "text" && /* @__PURE__ */ (0,
|
|
1477
|
+
selectedField.type === "text" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1266
1478
|
"input",
|
|
1267
1479
|
{
|
|
1268
1480
|
type: selectedField.textSubtype === "email" ? "email" : selectedField.textSubtype === "number" ? "number" : selectedField.textSubtype === "phone" ? "tel" : selectedField.textSubtype === "date" ? "date" : "text",
|
|
@@ -1272,8 +1484,8 @@ function SignerView({
|
|
|
1272
1484
|
className: "signer-text-input"
|
|
1273
1485
|
}
|
|
1274
1486
|
),
|
|
1275
|
-
selectedField.type === "checkbox" && /* @__PURE__ */ (0,
|
|
1276
|
-
/* @__PURE__ */ (0,
|
|
1487
|
+
selectedField.type === "checkbox" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "signer-checkbox-label", children: [
|
|
1488
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1277
1489
|
"input",
|
|
1278
1490
|
{
|
|
1279
1491
|
type: "checkbox",
|
|
@@ -1283,14 +1495,14 @@ function SignerView({
|
|
|
1283
1495
|
),
|
|
1284
1496
|
selectedField.placeholder || "Check this box"
|
|
1285
1497
|
] }),
|
|
1286
|
-
selectedField.type === "signed-date" && /* @__PURE__ */ (0,
|
|
1498
|
+
selectedField.type === "signed-date" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "signer-date-display", children: selectedField.value || "Will auto-fill when you sign" })
|
|
1287
1499
|
] }),
|
|
1288
|
-
selectedField && !isFieldEditable && /* @__PURE__ */ (0,
|
|
1289
|
-
/* @__PURE__ */ (0,
|
|
1290
|
-
/* @__PURE__ */ (0,
|
|
1500
|
+
selectedField && !isFieldEditable && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "signer-field-readonly", children: [
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { children: selectedField.label }),
|
|
1502
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "This field belongs to another signer." })
|
|
1291
1503
|
] }),
|
|
1292
|
-
!selectedField && editableFields.length > 0 && /* @__PURE__ */ (0,
|
|
1293
|
-
/* @__PURE__ */ (0,
|
|
1504
|
+
!selectedField && editableFields.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "panel-empty", children: "Select a field to fill it in, or use the navigation below." }),
|
|
1505
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1294
1506
|
FieldNavigator,
|
|
1295
1507
|
{
|
|
1296
1508
|
fields: editableFields,
|
|
@@ -1300,12 +1512,87 @@ function SignerView({
|
|
|
1300
1512
|
onSubmit: handleSubmit
|
|
1301
1513
|
}
|
|
1302
1514
|
),
|
|
1303
|
-
submitting && /* @__PURE__ */ (0,
|
|
1515
|
+
submitting && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "loading-indicator", children: "Generating PDF..." })
|
|
1304
1516
|
] })
|
|
1305
1517
|
] }),
|
|
1306
|
-
/* @__PURE__ */ (0,
|
|
1518
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "powered-by", children: [
|
|
1307
1519
|
"Powered by ",
|
|
1308
|
-
/* @__PURE__ */ (0,
|
|
1520
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href: "https://exeq.org", target: "_blank", rel: "noopener noreferrer", children: "Exeq.org" })
|
|
1521
|
+
] })
|
|
1522
|
+
] });
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// src/components/pdf-builder/SignerRoleSelector.tsx
|
|
1526
|
+
var import_react5 = require("react");
|
|
1527
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1528
|
+
function SignerRoleSelector({
|
|
1529
|
+
roles,
|
|
1530
|
+
activeRole,
|
|
1531
|
+
onSetActiveRole,
|
|
1532
|
+
onAddRole,
|
|
1533
|
+
onRemoveRole
|
|
1534
|
+
}) {
|
|
1535
|
+
const [isAdding, setIsAdding] = (0, import_react5.useState)(false);
|
|
1536
|
+
const [newRoleName, setNewRoleName] = (0, import_react5.useState)("");
|
|
1537
|
+
const handleAdd = () => {
|
|
1538
|
+
if (newRoleName.trim()) {
|
|
1539
|
+
onAddRole(newRoleName.trim());
|
|
1540
|
+
setNewRoleName("");
|
|
1541
|
+
setIsAdding(false);
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "signer-role-selector", children: [
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "signer-role-label", children: "Signer Roles" }),
|
|
1546
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "signer-role-list", children: [
|
|
1547
|
+
roles.map((role) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
1548
|
+
"button",
|
|
1549
|
+
{
|
|
1550
|
+
className: `signer-role-chip ${role === activeRole ? "active" : ""}`,
|
|
1551
|
+
style: {
|
|
1552
|
+
borderColor: getSignerColor(role),
|
|
1553
|
+
backgroundColor: role === activeRole ? getSignerColor(role) : "transparent",
|
|
1554
|
+
color: role === activeRole ? "#fff" : getSignerColor(role)
|
|
1555
|
+
},
|
|
1556
|
+
onClick: () => onSetActiveRole(role),
|
|
1557
|
+
children: [
|
|
1558
|
+
role,
|
|
1559
|
+
roles.length > 1 && role !== "Sender" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1560
|
+
"span",
|
|
1561
|
+
{
|
|
1562
|
+
className: "signer-role-remove",
|
|
1563
|
+
onClick: (e) => {
|
|
1564
|
+
e.stopPropagation();
|
|
1565
|
+
onRemoveRole(role);
|
|
1566
|
+
},
|
|
1567
|
+
children: "\xD7"
|
|
1568
|
+
}
|
|
1569
|
+
)
|
|
1570
|
+
]
|
|
1571
|
+
},
|
|
1572
|
+
role
|
|
1573
|
+
)),
|
|
1574
|
+
isAdding ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "signer-role-add-input", children: [
|
|
1575
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1576
|
+
"input",
|
|
1577
|
+
{
|
|
1578
|
+
type: "text",
|
|
1579
|
+
value: newRoleName,
|
|
1580
|
+
onChange: (e) => setNewRoleName(e.target.value),
|
|
1581
|
+
onKeyDown: (e) => e.key === "Enter" && handleAdd(),
|
|
1582
|
+
placeholder: "Role name",
|
|
1583
|
+
autoFocus: true
|
|
1584
|
+
}
|
|
1585
|
+
),
|
|
1586
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { onClick: handleAdd, children: "Add" }),
|
|
1587
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { onClick: () => setIsAdding(false), children: "Cancel" })
|
|
1588
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1589
|
+
"button",
|
|
1590
|
+
{
|
|
1591
|
+
className: "signer-role-add-btn",
|
|
1592
|
+
onClick: () => setIsAdding(true),
|
|
1593
|
+
children: "+ Add Role"
|
|
1594
|
+
}
|
|
1595
|
+
)
|
|
1309
1596
|
] })
|
|
1310
1597
|
] });
|
|
1311
1598
|
}
|
|
@@ -1326,6 +1613,7 @@ function SignerView({
|
|
|
1326
1613
|
generateFilledPdf,
|
|
1327
1614
|
getSignerColor,
|
|
1328
1615
|
postPdfToCallback,
|
|
1329
|
-
renderPdfPages
|
|
1616
|
+
renderPdfPages,
|
|
1617
|
+
uniqueLabel
|
|
1330
1618
|
});
|
|
1331
1619
|
//# sourceMappingURL=index.js.map
|