UNPKG

clickwrap

Version:

Complete clickwrap solution with built-in document viewing. Track agreements, signatures, and consents.

208 lines (207 loc) 8.09 kB
import { SuperDoc as r } from "@harbour-enterprises/superdoc"; class h { constructor(t = {}) { this.config = { // Document configuration documentId: null, document: null, // URL or File object for SuperDoc documentElement: null, // Container element for SuperDoc superdocConfig: {}, // Additional SuperDoc config // UI Components (bring your own) signaturePad: null, consentElements: [], acceptButton: null, declineButton: null, // Callbacks onReady: null, onProgress: null, onAccept: null, onDecline: null, // Spread user config ...t, // Merge requirements properly requirements: { scrollThreshold: 0.95, signature: !1, consents: [], ...t.requirements || {} } }, this.state = { scrollProgress: 0, hasValidSignature: !1, consentStates: {}, startTime: /* @__PURE__ */ new Date(), isReady: !1 }, this.superdoc = null, this.init(); } async init() { if (this.config.document && this.config.documentElement) await this.initializeSuperdoc(); else if (this.config.documentElement) { const t = typeof this.config.documentElement == "string" ? document.querySelector(this.config.documentElement) : this.config.documentElement; t && this.trackScroll(t); } this.setupUI(); } async initializeSuperdoc() { try { this.superdoc = new r({ selector: this.config.documentElement, // SuperDoc expects a selector string document: this.config.document, documentMode: "viewing", // Always viewing for agreements pagination: !0, toolbar: !1, // No editing tools for agreements rulers: !1, ...this.config.superdocConfig, // Allow custom SuperDoc config onReady: () => { console.log("SuperDoc ready"), this.onSuperdocReady(); }, onException: (t) => { console.error("SuperDoc error:", t), this.handleDocumentError(t); } }); } catch (t) { console.error("Failed to initialize SuperDoc:", t), this.handleDocumentError(t); } } onSuperdocReady() { var e, s; const t = this.getSuperdocScrollElement(); t && this.trackScroll(t), this.state.isReady = !0, (s = (e = this.config).onReady) == null || s.call(e); } getSuperdocScrollElement() { const t = typeof this.config.documentElement == "string" ? document.querySelector(this.config.documentElement) : this.config.documentElement; return t ? t.querySelector(".super-editor-scroll-container") || t.querySelector("[data-scroll-container]") || t.querySelector(".superdoc-content") || t : null; } setupUI() { var t, e, s; this.config.signaturePad && this.trackSignature(this.config.signaturePad), ((t = this.config.consentElements) == null ? void 0 : t.length) > 0 && this.trackConsents(this.config.consentElements), (e = this.config.acceptButton) == null || e.addEventListener("click", () => { this.validate() && this.handleAccept(); }), (s = this.config.declineButton) == null || s.addEventListener("click", () => { this.handleDecline(); }), this.validate(); } handleDocumentError(t) { const e = typeof this.config.documentElement == "string" ? document.querySelector(this.config.documentElement) : this.config.documentElement; e && (e.innerHTML = ` <div style="padding: 2rem; text-align: center; color: #ef4444;"> <h3>Document Load Error</h3> <p>Unable to load the agreement document.</p> <p style="font-size: 0.875rem; color: #6b7280; margin-top: 1rem;"> ${(t == null ? void 0 : t.message) || "Please try refreshing the page."} </p> </div> `); } trackScroll(t) { let e; const s = () => { const { scrollTop: n, scrollHeight: i, clientHeight: o } = t, c = Math.min((n + o) / i, 1); c > this.state.scrollProgress && (this.state.scrollProgress = c, this.validate()); }; t.addEventListener("scroll", () => { clearTimeout(e), e = setTimeout(s, 100); }), s(); } trackSignature(t) { const e = () => { this.state.hasValidSignature = !t.isEmpty(), this.validate(); }; if (t.addEventListener("endStroke", e), t.clear) { const s = t.clear.bind(t); t.clear = () => { s(), this.state.hasValidSignature = !1, this.validate(); }; } } trackConsents(t) { Array.from(t).forEach((e) => { const s = e.name || e.dataset.name; s && (this.state.consentStates[s] = e.checked || !1, e.addEventListener("change", (n) => { this.state.consentStates[s] = n.target.checked, this.validate(); })); }); } validate() { var n, i, o; const { requirements: t } = this.config, e = {}; t.scrollThreshold > 0 && (this.superdoc || this.config.documentElement) && (e.scroll = this.state.scrollProgress >= t.scrollThreshold), t.signature && (e.signature = this.state.hasValidSignature), ((n = t.consents) == null ? void 0 : n.length) > 0 && (e.consents = t.consents.every( (c) => this.state.consentStates[c] === !0 )); const s = Object.values(e).every(Boolean); return this.config.acceptButton && (this.config.acceptButton.disabled = !s), (o = (i = this.config).onProgress) == null || o.call(i, { checks: e, isValid: s, progress: this.getProgress() }), s; } getData() { const t = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), documentId: this.config.documentId, documentUrl: typeof this.config.document == "string" ? this.config.document : null, duration: Math.round((Date.now() - this.state.startTime) / 1e3), scrollProgress: Math.round(this.state.scrollProgress * 100) + "%", consents: { ...this.state.consentStates } }; return this.config.signaturePad && !this.config.signaturePad.isEmpty() && (this.config.signaturePad.toDataURL ? t.signature = this.config.signaturePad.toDataURL() : this.config.signaturePad.toSVG && (t.signature = this.config.signaturePad.toSVG())), t; } async handleAccept() { if (!this.validate()) return !1; const t = { ...this.getData(), action: "accepted" }; return this.config.onAccept && await this.config.onAccept(t), t; } async handleDecline() { const t = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), documentId: this.config.documentId, action: "declined" }; return this.config.onDecline && await this.config.onDecline(t), t; } reset() { var e; this.state = { scrollProgress: 0, hasValidSignature: !1, consentStates: {}, startTime: /* @__PURE__ */ new Date(), isReady: this.state.isReady }; let t = null; this.superdoc ? t = this.getSuperdocScrollElement() : t = typeof this.config.documentElement == "string" ? document.querySelector(this.config.documentElement) : this.config.documentElement, t && (t.scrollTop = 0), (e = this.config.signaturePad) != null && e.clear && this.config.signaturePad.clear(), Array.from(this.config.consentElements || []).forEach((s) => { s.checked = !1; }), this.validate(); } destroy() { var t, e; this.superdoc && ((e = (t = this.superdoc).destroy) == null || e.call(t), this.superdoc = null), this.state = { scrollProgress: 0, hasValidSignature: !1, consentStates: {}, startTime: null, isReady: !1 }; } getProgress() { var n; const { requirements: t } = this.config; let e = 0, s = 0; return t.scrollThreshold > 0 && (s++, this.state.scrollProgress >= t.scrollThreshold && e++), t.signature && (s++, this.state.hasValidSignature && e++), ((n = t.consents) == null ? void 0 : n.length) > 0 && (s += t.consents.length, e += t.consents.filter((i) => this.state.consentStates[i]).length), { completed: e, total: s, percentage: s > 0 ? Math.round(e / s * 100) : 0 }; } } export { h as Clickwrap, h as default };