clickwrap
Version:
Complete clickwrap solution with built-in document viewing. Track agreements, signatures, and consents.
208 lines (207 loc) • 8.09 kB
JavaScript
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
};