@redacto.io/dpdpa-report-sdk-js 0.2.0
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/LICENSE +176 -0
- package/README.md +182 -0
- package/dist/dpdpa-report.d.mts +62 -0
- package/dist/dpdpa-report.d.ts +62 -0
- package/dist/dpdpa-report.global.js +426 -0
- package/dist/dpdpa-report.global.js.map +1 -0
- package/dist/dpdpa-report.js +1685 -0
- package/dist/dpdpa-report.mjs +1662 -0
- package/package.json +48 -0
|
@@ -0,0 +1,1662 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://salazar.redacto.tech";
|
|
3
|
+
var PATHS = {
|
|
4
|
+
submit: "/webhook/dpdpa/generate",
|
|
5
|
+
status: "/webhook/dpdpa/status"
|
|
6
|
+
};
|
|
7
|
+
var DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
8
|
+
var DEFAULT_POLL_TIMEOUT_MS = 3e5;
|
|
9
|
+
var MAX_CONSECUTIVE_ERRORS = 3;
|
|
10
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
11
|
+
var STORAGE_KEY = "redacto_dpdpa_lead";
|
|
12
|
+
var DEFAULT_LOGO_URL = "https://cdn.redacto.io/assets/redacto-logo.svg";
|
|
13
|
+
var DEFAULT_BRAND = {
|
|
14
|
+
name: "Redacto",
|
|
15
|
+
primaryColor: "#4262ff",
|
|
16
|
+
accentColor: "#a259ff",
|
|
17
|
+
textColor: "#050038",
|
|
18
|
+
borderColor: "#e5e5e5",
|
|
19
|
+
backgroundColor: "#f7f7f7",
|
|
20
|
+
borderRadius: "8px",
|
|
21
|
+
fontFamily: "inherit"
|
|
22
|
+
};
|
|
23
|
+
var SUCCESS_COLOR = "#a6c03d";
|
|
24
|
+
var ERROR_COLOR = "#f24e1e";
|
|
25
|
+
var NAME_MIN_LENGTH = 2;
|
|
26
|
+
var NAME_MAX_LENGTH = 120;
|
|
27
|
+
var DOMAIN_REGEX = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.[a-z0-9-]{1,63})+$/i;
|
|
28
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
29
|
+
var FREEMAIL_DOMAINS = /* @__PURE__ */ new Set([
|
|
30
|
+
"gmail.com",
|
|
31
|
+
"googlemail.com",
|
|
32
|
+
"yahoo.com",
|
|
33
|
+
"yahoo.co.in",
|
|
34
|
+
"yahoo.co.uk",
|
|
35
|
+
"hotmail.com",
|
|
36
|
+
"outlook.com",
|
|
37
|
+
"live.com",
|
|
38
|
+
"icloud.com",
|
|
39
|
+
"me.com",
|
|
40
|
+
"aol.com",
|
|
41
|
+
"proton.me",
|
|
42
|
+
"protonmail.com",
|
|
43
|
+
"mail.com",
|
|
44
|
+
"gmx.com",
|
|
45
|
+
"zoho.com",
|
|
46
|
+
"rediffmail.com"
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// src/core/dom.ts
|
|
50
|
+
function el(tag, attrs, ...children) {
|
|
51
|
+
const element = document.createElement(tag);
|
|
52
|
+
if (attrs) applyAttrs(element, attrs);
|
|
53
|
+
appendChildren(element, children);
|
|
54
|
+
return element;
|
|
55
|
+
}
|
|
56
|
+
function svg(tag, attrs, ...children) {
|
|
57
|
+
const element = document.createElementNS(
|
|
58
|
+
"http://www.w3.org/2000/svg",
|
|
59
|
+
tag
|
|
60
|
+
);
|
|
61
|
+
if (attrs) {
|
|
62
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
63
|
+
if (value == null || value === false) continue;
|
|
64
|
+
if (key === "style" && typeof value === "object") {
|
|
65
|
+
applyStyles(element, value);
|
|
66
|
+
} else {
|
|
67
|
+
element.setAttribute(key, String(value));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
appendChildren(element, children);
|
|
72
|
+
return element;
|
|
73
|
+
}
|
|
74
|
+
function applyAttrs(element, attrs) {
|
|
75
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
76
|
+
if (value == null || value === false) continue;
|
|
77
|
+
if (key === "style" && typeof value === "object") {
|
|
78
|
+
applyStyles(element, value);
|
|
79
|
+
} else if (key === "className") {
|
|
80
|
+
element.className = value;
|
|
81
|
+
} else if (key === "dataset" && typeof value === "object") {
|
|
82
|
+
Object.assign(element.dataset, value);
|
|
83
|
+
} else if (key.startsWith("on") && typeof value === "function") {
|
|
84
|
+
const event = key.slice(2).toLowerCase();
|
|
85
|
+
element.addEventListener(event, value);
|
|
86
|
+
} else if (key === "checked" || key === "disabled" || key === "readOnly") {
|
|
87
|
+
element[key] = value;
|
|
88
|
+
} else {
|
|
89
|
+
element.setAttribute(key, String(value));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
var UNITLESS_PROPS = /* @__PURE__ */ new Set([
|
|
94
|
+
"animationIterationCount",
|
|
95
|
+
"aspectRatio",
|
|
96
|
+
"borderImageOutset",
|
|
97
|
+
"borderImageSlice",
|
|
98
|
+
"borderImageWidth",
|
|
99
|
+
"boxFlex",
|
|
100
|
+
"boxFlexGroup",
|
|
101
|
+
"boxOrdinalGroup",
|
|
102
|
+
"columnCount",
|
|
103
|
+
"columns",
|
|
104
|
+
"flex",
|
|
105
|
+
"flexGrow",
|
|
106
|
+
"flexPositive",
|
|
107
|
+
"flexShrink",
|
|
108
|
+
"flexNegative",
|
|
109
|
+
"flexOrder",
|
|
110
|
+
"fontWeight",
|
|
111
|
+
"gridArea",
|
|
112
|
+
"gridRow",
|
|
113
|
+
"gridRowEnd",
|
|
114
|
+
"gridRowSpan",
|
|
115
|
+
"gridRowStart",
|
|
116
|
+
"gridColumn",
|
|
117
|
+
"gridColumnEnd",
|
|
118
|
+
"gridColumnSpan",
|
|
119
|
+
"gridColumnStart",
|
|
120
|
+
"lineClamp",
|
|
121
|
+
"lineHeight",
|
|
122
|
+
"opacity",
|
|
123
|
+
"order",
|
|
124
|
+
"orphans",
|
|
125
|
+
"scale",
|
|
126
|
+
"tabSize",
|
|
127
|
+
"widows",
|
|
128
|
+
"zIndex",
|
|
129
|
+
"zoom"
|
|
130
|
+
]);
|
|
131
|
+
function applyStyles(element, styles) {
|
|
132
|
+
for (const [key, value] of Object.entries(styles)) {
|
|
133
|
+
if (value == null) continue;
|
|
134
|
+
const stringValue = typeof value === "number" && !UNITLESS_PROPS.has(key) ? `${value}px` : String(value);
|
|
135
|
+
element.style[key] = stringValue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function appendChildren(parent, children) {
|
|
139
|
+
for (const child of children) {
|
|
140
|
+
if (child == null || child === false) continue;
|
|
141
|
+
if (typeof child === "string") {
|
|
142
|
+
parent.appendChild(document.createTextNode(child));
|
|
143
|
+
} else {
|
|
144
|
+
parent.appendChild(child);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function clearElement(element) {
|
|
149
|
+
while (element.firstChild) {
|
|
150
|
+
element.removeChild(element.firstChild);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/core/state.ts
|
|
155
|
+
var Store = class {
|
|
156
|
+
constructor(initialState) {
|
|
157
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
158
|
+
this.state = { ...initialState };
|
|
159
|
+
}
|
|
160
|
+
getState() {
|
|
161
|
+
return { ...this.state };
|
|
162
|
+
}
|
|
163
|
+
setState(partial) {
|
|
164
|
+
const prevState = this.state;
|
|
165
|
+
this.state = { ...this.state, ...partial };
|
|
166
|
+
this.listeners.forEach((listener) => listener(this.state, prevState));
|
|
167
|
+
}
|
|
168
|
+
subscribe(listener) {
|
|
169
|
+
this.listeners.add(listener);
|
|
170
|
+
return () => {
|
|
171
|
+
this.listeners.delete(listener);
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
destroy() {
|
|
175
|
+
this.listeners.clear();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// src/api/types.ts
|
|
180
|
+
var ApiError = class extends Error {
|
|
181
|
+
constructor(message, status) {
|
|
182
|
+
super(message);
|
|
183
|
+
this.name = "ApiError";
|
|
184
|
+
this.status = status;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// src/api/index.ts
|
|
189
|
+
function timedFetch(url, init2 = {}) {
|
|
190
|
+
const controller = new AbortController();
|
|
191
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
192
|
+
return fetch(url, { ...init2, signal: controller.signal }).finally(
|
|
193
|
+
() => clearTimeout(timer)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function toNetworkError(err) {
|
|
197
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
198
|
+
return new ApiError("Request timed out", void 0);
|
|
199
|
+
}
|
|
200
|
+
return new ApiError(
|
|
201
|
+
err instanceof Error ? err.message : "Network error",
|
|
202
|
+
void 0
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
async function submitLead({
|
|
206
|
+
baseUrl,
|
|
207
|
+
body
|
|
208
|
+
}) {
|
|
209
|
+
let res;
|
|
210
|
+
try {
|
|
211
|
+
res = await timedFetch(`${baseUrl}${PATHS.submit}`, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: { "Content-Type": "application/json" },
|
|
214
|
+
body: JSON.stringify(body)
|
|
215
|
+
});
|
|
216
|
+
} catch (err) {
|
|
217
|
+
throw toNetworkError(err);
|
|
218
|
+
}
|
|
219
|
+
let json;
|
|
220
|
+
try {
|
|
221
|
+
json = await res.json();
|
|
222
|
+
} catch {
|
|
223
|
+
throw new ApiError(`Unexpected response (HTTP ${res.status})`, res.status);
|
|
224
|
+
}
|
|
225
|
+
if (!res.ok) {
|
|
226
|
+
const message = json && "message" in json && json.message ? json.message : `Request failed (HTTP ${res.status})`;
|
|
227
|
+
throw new ApiError(message, res.status);
|
|
228
|
+
}
|
|
229
|
+
return json;
|
|
230
|
+
}
|
|
231
|
+
async function fetchJobStatus({
|
|
232
|
+
baseUrl,
|
|
233
|
+
jobId
|
|
234
|
+
}) {
|
|
235
|
+
const url = `${baseUrl}${PATHS.status}?job_id=${encodeURIComponent(jobId)}`;
|
|
236
|
+
let res;
|
|
237
|
+
try {
|
|
238
|
+
res = await timedFetch(url, { method: "GET" });
|
|
239
|
+
} catch (err) {
|
|
240
|
+
throw toNetworkError(err);
|
|
241
|
+
}
|
|
242
|
+
let json;
|
|
243
|
+
try {
|
|
244
|
+
json = await res.json();
|
|
245
|
+
} catch {
|
|
246
|
+
throw new ApiError(`Unexpected response (HTTP ${res.status})`, res.status);
|
|
247
|
+
}
|
|
248
|
+
if (!res.ok) {
|
|
249
|
+
const message = json && "message" in json && json.message ? json.message : `Status check failed (HTTP ${res.status})`;
|
|
250
|
+
throw new ApiError(message, res.status);
|
|
251
|
+
}
|
|
252
|
+
return json;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/services/storage.ts
|
|
256
|
+
var isBrowser = () => typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
257
|
+
var saveLead = (lead) => {
|
|
258
|
+
if (!isBrowser()) return;
|
|
259
|
+
try {
|
|
260
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(lead));
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error("Failed to save lead:", error);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
var isCapturedLead = (v) => {
|
|
266
|
+
if (!v || typeof v !== "object") return false;
|
|
267
|
+
const o = v;
|
|
268
|
+
return typeof o.email === "string" && typeof o.company_name === "string" && typeof o.company_domain === "string" && (o.job_id === null || typeof o.job_id === "string") && typeof o.timestamp === "string";
|
|
269
|
+
};
|
|
270
|
+
var loadLead = () => {
|
|
271
|
+
if (!isBrowser()) return null;
|
|
272
|
+
try {
|
|
273
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
274
|
+
if (!raw) return null;
|
|
275
|
+
const parsed = JSON.parse(raw);
|
|
276
|
+
return isCapturedLead(parsed) ? parsed : null;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
console.error("Failed to load lead:", error);
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/styles/inject.ts
|
|
284
|
+
var STYLE_ID = "redacto-dpdpa-form-styles";
|
|
285
|
+
function buildCss(brand) {
|
|
286
|
+
return `
|
|
287
|
+
:host {
|
|
288
|
+
display: block;
|
|
289
|
+
width: 100%;
|
|
290
|
+
container-type: inline-size;
|
|
291
|
+
container-name: dpdpa-card;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
:host, :host > *, :host *, :host *::before, :host *::after {
|
|
295
|
+
box-sizing: border-box;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.dpdpa-card {
|
|
299
|
+
box-sizing: border-box;
|
|
300
|
+
width: 100%;
|
|
301
|
+
min-height: 520px;
|
|
302
|
+
display: flex;
|
|
303
|
+
flex-direction: column;
|
|
304
|
+
padding: 28px 24px;
|
|
305
|
+
background: ${brand.background};
|
|
306
|
+
color: ${brand.text};
|
|
307
|
+
border: 1px solid ${brand.border};
|
|
308
|
+
border-radius: ${brand.borderRadius};
|
|
309
|
+
font-family: ${brand.fontFamily};
|
|
310
|
+
font-size: 15px;
|
|
311
|
+
line-height: 1.5;
|
|
312
|
+
text-align: left;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@container dpdpa-card (max-width: 420px) {
|
|
316
|
+
.dpdpa-card {
|
|
317
|
+
padding: 20px 16px;
|
|
318
|
+
min-height: 480px;
|
|
319
|
+
}
|
|
320
|
+
.dpdpa-heading {
|
|
321
|
+
font-size: 18px;
|
|
322
|
+
}
|
|
323
|
+
.dpdpa-success-title,
|
|
324
|
+
.dpdpa-error-title {
|
|
325
|
+
font-size: 16px;
|
|
326
|
+
}
|
|
327
|
+
.dpdpa-header {
|
|
328
|
+
margin-bottom: 16px;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@supports not (container-type: inline-size) {
|
|
333
|
+
@media (max-width: 480px) {
|
|
334
|
+
.dpdpa-card {
|
|
335
|
+
padding: 20px 16px;
|
|
336
|
+
}
|
|
337
|
+
.dpdpa-heading {
|
|
338
|
+
font-size: 18px;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.dpdpa-header {
|
|
344
|
+
display: flex;
|
|
345
|
+
align-items: center;
|
|
346
|
+
flex-wrap: wrap;
|
|
347
|
+
gap: 10px;
|
|
348
|
+
margin-bottom: 20px;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.dpdpa-logo {
|
|
352
|
+
max-height: 28px;
|
|
353
|
+
max-width: 140px;
|
|
354
|
+
display: inline-block;
|
|
355
|
+
object-fit: contain;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.dpdpa-wordmark {
|
|
359
|
+
font-weight: 700;
|
|
360
|
+
font-size: 17px;
|
|
361
|
+
color: ${brand.text};
|
|
362
|
+
letter-spacing: 0.3px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.dpdpa-footer {
|
|
366
|
+
margin-top: 20px;
|
|
367
|
+
padding-top: 16px;
|
|
368
|
+
border-top: 1px solid ${brand.border};
|
|
369
|
+
font-size: 12px;
|
|
370
|
+
color: ${brand.text};
|
|
371
|
+
opacity: 0.7;
|
|
372
|
+
text-align: center;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.dpdpa-form {
|
|
376
|
+
flex: 1;
|
|
377
|
+
display: flex;
|
|
378
|
+
flex-direction: column;
|
|
379
|
+
justify-content: flex-start;
|
|
380
|
+
gap: 14px;
|
|
381
|
+
margin: 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.dpdpa-heading {
|
|
385
|
+
font-size: 20px;
|
|
386
|
+
font-weight: 700;
|
|
387
|
+
margin: 0 0 4px;
|
|
388
|
+
color: ${brand.text};
|
|
389
|
+
line-height: 1.3;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.dpdpa-subheading {
|
|
393
|
+
margin: 0 0 8px;
|
|
394
|
+
font-size: 14px;
|
|
395
|
+
color: ${brand.text};
|
|
396
|
+
opacity: 0.75;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.dpdpa-field {
|
|
400
|
+
display: flex;
|
|
401
|
+
flex-direction: column;
|
|
402
|
+
gap: 4px;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.dpdpa-label {
|
|
406
|
+
font-size: 13px;
|
|
407
|
+
font-weight: 600;
|
|
408
|
+
color: ${brand.text};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.dpdpa-input {
|
|
412
|
+
width: 100%;
|
|
413
|
+
min-width: 0;
|
|
414
|
+
padding: 11px 13px;
|
|
415
|
+
font-family: inherit;
|
|
416
|
+
font-size: 14px;
|
|
417
|
+
color: ${brand.text};
|
|
418
|
+
background: #ffffff;
|
|
419
|
+
border: 1px solid ${brand.border};
|
|
420
|
+
border-radius: ${brand.borderRadius};
|
|
421
|
+
outline: none;
|
|
422
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.dpdpa-input:focus {
|
|
426
|
+
border-color: ${brand.primary};
|
|
427
|
+
box-shadow: 0 0 0 3px ${brand.primaryFocus};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.dpdpa-input:disabled {
|
|
431
|
+
background: ${brand.border};
|
|
432
|
+
opacity: 0.7;
|
|
433
|
+
cursor: not-allowed;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.dpdpa-field-error,
|
|
437
|
+
.dpdpa-general-error {
|
|
438
|
+
font-size: 12px;
|
|
439
|
+
color: ${brand.errorColor};
|
|
440
|
+
line-height: 1.4;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.dpdpa-general-error {
|
|
444
|
+
padding: 10px 12px;
|
|
445
|
+
background: ${brand.errorColor}14;
|
|
446
|
+
border: 1px solid ${brand.errorColor}33;
|
|
447
|
+
border-radius: ${brand.borderRadius};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.dpdpa-btn-primary {
|
|
451
|
+
display: inline-flex;
|
|
452
|
+
align-items: center;
|
|
453
|
+
justify-content: center;
|
|
454
|
+
gap: 8px;
|
|
455
|
+
width: 100%;
|
|
456
|
+
padding: 12px 18px;
|
|
457
|
+
font-family: inherit;
|
|
458
|
+
font-size: 15px;
|
|
459
|
+
font-weight: 600;
|
|
460
|
+
color: ${brand.primaryText};
|
|
461
|
+
background: ${brand.primary};
|
|
462
|
+
border: none;
|
|
463
|
+
border-radius: ${brand.borderRadius};
|
|
464
|
+
cursor: pointer;
|
|
465
|
+
text-decoration: none;
|
|
466
|
+
transition: background-color 0.15s ease, transform 0.05s ease;
|
|
467
|
+
margin-top: 6px;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.dpdpa-btn-primary:hover:not(:disabled) {
|
|
471
|
+
background: ${brand.primaryHover};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.dpdpa-btn-primary:active:not(:disabled) {
|
|
475
|
+
transform: translateY(1px);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.dpdpa-btn-primary:disabled {
|
|
479
|
+
opacity: 0.7;
|
|
480
|
+
cursor: not-allowed;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.dpdpa-btn-link {
|
|
484
|
+
text-decoration: none;
|
|
485
|
+
color: ${brand.primaryText};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.dpdpa-spinner {
|
|
489
|
+
display: inline-block;
|
|
490
|
+
border-radius: 50%;
|
|
491
|
+
border-style: solid;
|
|
492
|
+
border-color: ${brand.primaryText}4D;
|
|
493
|
+
animation: dpdpa-spin 0.7s linear infinite;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.dpdpa-spinner-inline {
|
|
497
|
+
width: 14px;
|
|
498
|
+
height: 14px;
|
|
499
|
+
border-width: 2px;
|
|
500
|
+
border-top-color: ${brand.primaryText};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.dpdpa-polling {
|
|
504
|
+
flex: 1;
|
|
505
|
+
display: flex;
|
|
506
|
+
flex-direction: column;
|
|
507
|
+
justify-content: center;
|
|
508
|
+
padding: 4px 2px 8px;
|
|
509
|
+
gap: 16px;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.dpdpa-polling-sub {
|
|
513
|
+
margin: 0;
|
|
514
|
+
font-size: 13px;
|
|
515
|
+
color: ${brand.text};
|
|
516
|
+
opacity: 0.6;
|
|
517
|
+
text-align: center;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.dpdpa-steps {
|
|
521
|
+
display: flex;
|
|
522
|
+
flex-direction: column;
|
|
523
|
+
gap: 10px;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.dpdpa-step {
|
|
527
|
+
display: flex;
|
|
528
|
+
align-items: center;
|
|
529
|
+
gap: 10px;
|
|
530
|
+
font-size: 15px;
|
|
531
|
+
animation: dpdpa-step-in 0.35s ease-out both;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.dpdpa-step-done .dpdpa-step-label {
|
|
535
|
+
color: ${brand.text};
|
|
536
|
+
opacity: 0.6;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.dpdpa-step-icon {
|
|
540
|
+
flex: none;
|
|
541
|
+
width: 18px;
|
|
542
|
+
height: 18px;
|
|
543
|
+
border-radius: 50%;
|
|
544
|
+
display: inline-flex;
|
|
545
|
+
align-items: center;
|
|
546
|
+
justify-content: center;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
.dpdpa-step-icon-active {
|
|
550
|
+
position: relative;
|
|
551
|
+
width: 10px;
|
|
552
|
+
height: 10px;
|
|
553
|
+
margin: 0 4px;
|
|
554
|
+
box-shadow: 0 0 0 0 ${brand.primaryFocus};
|
|
555
|
+
animation: dpdpa-pulse 1.4s ease-out infinite;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.dpdpa-step-label {
|
|
559
|
+
color: ${brand.text};
|
|
560
|
+
font-weight: 500;
|
|
561
|
+
word-break: break-word;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.dpdpa-shimmer {
|
|
565
|
+
background: linear-gradient(
|
|
566
|
+
90deg,
|
|
567
|
+
${brand.text} 0%,
|
|
568
|
+
${brand.primary} 40%,
|
|
569
|
+
${brand.accent} 50%,
|
|
570
|
+
${brand.primary} 60%,
|
|
571
|
+
${brand.text} 100%
|
|
572
|
+
);
|
|
573
|
+
background-size: 200% 100%;
|
|
574
|
+
-webkit-background-clip: text;
|
|
575
|
+
background-clip: text;
|
|
576
|
+
-webkit-text-fill-color: transparent;
|
|
577
|
+
color: transparent;
|
|
578
|
+
animation: dpdpa-shimmer 2.2s linear infinite;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
.dpdpa-dots {
|
|
582
|
+
display: inline-flex;
|
|
583
|
+
align-items: center;
|
|
584
|
+
gap: 3px;
|
|
585
|
+
margin-left: 2px;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
.dpdpa-dots > span {
|
|
589
|
+
width: 4px;
|
|
590
|
+
height: 4px;
|
|
591
|
+
border-radius: 50%;
|
|
592
|
+
background: ${brand.primary};
|
|
593
|
+
opacity: 0.4;
|
|
594
|
+
animation: dpdpa-dot-bounce 1.2s ease-in-out infinite;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.dpdpa-dots > span:nth-child(2) { animation-delay: 0.15s; }
|
|
598
|
+
.dpdpa-dots > span:nth-child(3) { animation-delay: 0.3s; }
|
|
599
|
+
|
|
600
|
+
@keyframes dpdpa-shimmer {
|
|
601
|
+
from { background-position: 200% 0; }
|
|
602
|
+
to { background-position: -200% 0; }
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
@keyframes dpdpa-dot-bounce {
|
|
606
|
+
0%, 80%, 100% { opacity: 0.3; transform: translateY(0); }
|
|
607
|
+
40% { opacity: 1; transform: translateY(-3px); }
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
@keyframes dpdpa-pulse {
|
|
611
|
+
0% { box-shadow: 0 0 0 0 ${brand.primaryFocus}; }
|
|
612
|
+
70% { box-shadow: 0 0 0 8px rgba(0, 0, 0, 0); }
|
|
613
|
+
100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
@keyframes dpdpa-step-in {
|
|
617
|
+
from { opacity: 0; transform: translateY(4px); }
|
|
618
|
+
to { opacity: 1; transform: translateY(0); }
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.dpdpa-progress {
|
|
622
|
+
width: 100%;
|
|
623
|
+
height: 6px;
|
|
624
|
+
background: ${brand.border};
|
|
625
|
+
border-radius: 999px;
|
|
626
|
+
overflow: hidden;
|
|
627
|
+
margin-top: 4px;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.dpdpa-progress-fill {
|
|
631
|
+
height: 100%;
|
|
632
|
+
background: linear-gradient(to right, ${brand.primary}, ${brand.accent});
|
|
633
|
+
transition: width 0.5s ease;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.dpdpa-success {
|
|
637
|
+
flex: 1;
|
|
638
|
+
display: flex;
|
|
639
|
+
flex-direction: column;
|
|
640
|
+
justify-content: center;
|
|
641
|
+
align-items: center;
|
|
642
|
+
text-align: center;
|
|
643
|
+
gap: 12px;
|
|
644
|
+
padding: 8px 4px;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.dpdpa-check-circle {
|
|
648
|
+
width: 56px;
|
|
649
|
+
height: 56px;
|
|
650
|
+
border-radius: 50%;
|
|
651
|
+
display: flex;
|
|
652
|
+
align-items: center;
|
|
653
|
+
justify-content: center;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.dpdpa-success-title {
|
|
657
|
+
margin: 0;
|
|
658
|
+
font-size: 18px;
|
|
659
|
+
font-weight: 700;
|
|
660
|
+
color: ${brand.text};
|
|
661
|
+
line-height: 1.35;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.dpdpa-success-sub {
|
|
665
|
+
margin: 0;
|
|
666
|
+
font-size: 14px;
|
|
667
|
+
color: ${brand.text};
|
|
668
|
+
opacity: 0.75;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.dpdpa-reset-link {
|
|
672
|
+
font-size: 13px;
|
|
673
|
+
color: ${brand.primary};
|
|
674
|
+
text-decoration: none;
|
|
675
|
+
margin-top: 4px;
|
|
676
|
+
cursor: pointer;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.dpdpa-reset-link:hover {
|
|
680
|
+
text-decoration: underline;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.dpdpa-error-view {
|
|
684
|
+
flex: 1;
|
|
685
|
+
display: flex;
|
|
686
|
+
flex-direction: column;
|
|
687
|
+
justify-content: center;
|
|
688
|
+
gap: 12px;
|
|
689
|
+
text-align: center;
|
|
690
|
+
padding: 8px 4px;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.dpdpa-error-title {
|
|
694
|
+
margin: 0;
|
|
695
|
+
font-size: 18px;
|
|
696
|
+
font-weight: 700;
|
|
697
|
+
color: ${brand.errorColor};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
.dpdpa-error-message {
|
|
701
|
+
margin: 0;
|
|
702
|
+
font-size: 14px;
|
|
703
|
+
color: ${brand.text};
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
@keyframes dpdpa-spin {
|
|
707
|
+
from { transform: rotate(0deg); }
|
|
708
|
+
to { transform: rotate(360deg); }
|
|
709
|
+
}
|
|
710
|
+
`;
|
|
711
|
+
}
|
|
712
|
+
function injectFormStyles(root, brand) {
|
|
713
|
+
if (typeof document === "undefined") return;
|
|
714
|
+
if (root.querySelector(`#${STYLE_ID}`)) return;
|
|
715
|
+
const style = document.createElement("style");
|
|
716
|
+
style.id = STYLE_ID;
|
|
717
|
+
style.textContent = buildCss(brand);
|
|
718
|
+
root.appendChild(style);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// src/form/colors.ts
|
|
722
|
+
var HEX_RE = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
723
|
+
function normalizeHex(hex, fallback) {
|
|
724
|
+
if (!hex || !HEX_RE.test(hex.trim())) return fallback;
|
|
725
|
+
return hex.trim();
|
|
726
|
+
}
|
|
727
|
+
function hexToRgb(hex) {
|
|
728
|
+
const h = hex.replace("#", "").trim();
|
|
729
|
+
const full = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
|
|
730
|
+
return {
|
|
731
|
+
r: parseInt(full.slice(0, 2), 16),
|
|
732
|
+
g: parseInt(full.slice(2, 4), 16),
|
|
733
|
+
b: parseInt(full.slice(4, 6), 16)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function darken(hex, amount) {
|
|
737
|
+
const { r, g, b } = hexToRgb(hex);
|
|
738
|
+
const d = (c) => Math.max(0, Math.round(c * (1 - amount)));
|
|
739
|
+
return `rgb(${d(r)}, ${d(g)}, ${d(b)})`;
|
|
740
|
+
}
|
|
741
|
+
function rgba(hex, alpha) {
|
|
742
|
+
const { r, g, b } = hexToRgb(hex);
|
|
743
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
744
|
+
}
|
|
745
|
+
function relativeLuminance(hex) {
|
|
746
|
+
const { r, g, b } = hexToRgb(hex);
|
|
747
|
+
const norm = (c) => {
|
|
748
|
+
const s = c / 255;
|
|
749
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
750
|
+
};
|
|
751
|
+
return 0.2126 * norm(r) + 0.7152 * norm(g) + 0.0722 * norm(b);
|
|
752
|
+
}
|
|
753
|
+
function contrastingTextOn(bg) {
|
|
754
|
+
return relativeLuminance(bg) > 0.55 ? "#050038" : "#ffffff";
|
|
755
|
+
}
|
|
756
|
+
function resolveBranding(b) {
|
|
757
|
+
var _a;
|
|
758
|
+
const primary = normalizeHex(b == null ? void 0 : b.primaryColor, DEFAULT_BRAND.primaryColor);
|
|
759
|
+
const accent = normalizeHex(
|
|
760
|
+
(_a = b == null ? void 0 : b.accentColor) != null ? _a : b == null ? void 0 : b.primaryColor,
|
|
761
|
+
DEFAULT_BRAND.accentColor
|
|
762
|
+
);
|
|
763
|
+
return {
|
|
764
|
+
logo: (b == null ? void 0 : b.logo) || DEFAULT_LOGO_URL,
|
|
765
|
+
name: (b == null ? void 0 : b.name) || DEFAULT_BRAND.name,
|
|
766
|
+
primary,
|
|
767
|
+
accent,
|
|
768
|
+
primaryHover: darken(primary, 0.08),
|
|
769
|
+
primaryFocus: rgba(primary, 0.3),
|
|
770
|
+
primaryText: contrastingTextOn(primary),
|
|
771
|
+
text: (b == null ? void 0 : b.textColor) || DEFAULT_BRAND.textColor,
|
|
772
|
+
border: (b == null ? void 0 : b.borderColor) || DEFAULT_BRAND.borderColor,
|
|
773
|
+
background: (b == null ? void 0 : b.backgroundColor) || DEFAULT_BRAND.backgroundColor,
|
|
774
|
+
borderRadius: (b == null ? void 0 : b.borderRadius) || DEFAULT_BRAND.borderRadius,
|
|
775
|
+
fontFamily: (b == null ? void 0 : b.fontFamily) || DEFAULT_BRAND.fontFamily,
|
|
776
|
+
successColor: SUCCESS_COLOR,
|
|
777
|
+
errorColor: ERROR_COLOR
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// src/form/validation.ts
|
|
782
|
+
function normalizeDomain(raw) {
|
|
783
|
+
return raw.trim().toLowerCase().replace(/^https?:\/\//, "").replace(/^www\./, "").replace(/\/.*$/, "");
|
|
784
|
+
}
|
|
785
|
+
function validateField(field, rawValue) {
|
|
786
|
+
var _a;
|
|
787
|
+
const value = rawValue.trim();
|
|
788
|
+
switch (field) {
|
|
789
|
+
case "company_name": {
|
|
790
|
+
if (value.length < NAME_MIN_LENGTH) return "Please enter a company name";
|
|
791
|
+
if (value.length > NAME_MAX_LENGTH) return "Company name is too long";
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
case "company_domain": {
|
|
795
|
+
if (!value) return "Enter your company domain";
|
|
796
|
+
const normalized = normalizeDomain(value);
|
|
797
|
+
if (!DOMAIN_REGEX.test(normalized))
|
|
798
|
+
return "Enter a valid domain like example.com";
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
case "email": {
|
|
802
|
+
if (!value) return "Enter your work email";
|
|
803
|
+
if (!EMAIL_REGEX.test(value)) return "Enter a valid email";
|
|
804
|
+
const domain = (_a = value.split("@")[1]) == null ? void 0 : _a.toLowerCase();
|
|
805
|
+
if (domain && FREEMAIL_DOMAINS.has(domain))
|
|
806
|
+
return "Please use your work email";
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
function validateAll(fields) {
|
|
812
|
+
const errors = {};
|
|
813
|
+
Object.keys(fields).forEach((key) => {
|
|
814
|
+
const err = validateField(key, fields[key]);
|
|
815
|
+
if (err) errors[key] = err;
|
|
816
|
+
});
|
|
817
|
+
return errors;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/form/view.ts
|
|
821
|
+
function header(brand) {
|
|
822
|
+
const logoImg = el("img", {
|
|
823
|
+
src: brand.logo,
|
|
824
|
+
alt: `${brand.name} logo`,
|
|
825
|
+
className: "dpdpa-logo",
|
|
826
|
+
onerror: (ev) => {
|
|
827
|
+
const img = ev.currentTarget;
|
|
828
|
+
img.style.display = "none";
|
|
829
|
+
const wordmark2 = img.nextElementSibling;
|
|
830
|
+
if (wordmark2) wordmark2.style.display = "inline-block";
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
const wordmark = el(
|
|
834
|
+
"span",
|
|
835
|
+
{ className: "dpdpa-wordmark", style: { display: "none" } },
|
|
836
|
+
brand.name
|
|
837
|
+
);
|
|
838
|
+
return el("div", { className: "dpdpa-header" }, logoImg, wordmark);
|
|
839
|
+
}
|
|
840
|
+
function inputRow(brand, opts) {
|
|
841
|
+
const inputEl = el("input", {
|
|
842
|
+
type: opts.type,
|
|
843
|
+
className: "dpdpa-input",
|
|
844
|
+
placeholder: opts.placeholder,
|
|
845
|
+
value: opts.value,
|
|
846
|
+
autocomplete: opts.autocomplete,
|
|
847
|
+
"aria-label": opts.label,
|
|
848
|
+
onInput: (ev) => opts.onInput(ev.currentTarget.value),
|
|
849
|
+
onBlur: () => opts.onBlur()
|
|
850
|
+
});
|
|
851
|
+
const errorSlot = el(
|
|
852
|
+
"div",
|
|
853
|
+
{
|
|
854
|
+
className: "dpdpa-field-error",
|
|
855
|
+
role: "alert",
|
|
856
|
+
style: { display: opts.error ? "block" : "none" }
|
|
857
|
+
},
|
|
858
|
+
opts.error || ""
|
|
859
|
+
);
|
|
860
|
+
const labelEl = el("label", { className: "dpdpa-label" }, opts.label);
|
|
861
|
+
const row = el(
|
|
862
|
+
"div",
|
|
863
|
+
{ className: "dpdpa-field" },
|
|
864
|
+
labelEl,
|
|
865
|
+
inputEl,
|
|
866
|
+
errorSlot
|
|
867
|
+
);
|
|
868
|
+
return { row, input: inputEl, errorSlot };
|
|
869
|
+
}
|
|
870
|
+
function footer(brand) {
|
|
871
|
+
return el(
|
|
872
|
+
"div",
|
|
873
|
+
{ className: "dpdpa-footer" },
|
|
874
|
+
"Powered by ",
|
|
875
|
+
el("strong", null, brand.name)
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
function renderIdle(brand, fields, errors, generalErrorText, handlers) {
|
|
879
|
+
const nameRow = inputRow(brand, {
|
|
880
|
+
field: "company_name",
|
|
881
|
+
label: "Company name",
|
|
882
|
+
type: "text",
|
|
883
|
+
placeholder: "Acme Inc",
|
|
884
|
+
value: fields.company_name,
|
|
885
|
+
error: errors.company_name,
|
|
886
|
+
autocomplete: "organization",
|
|
887
|
+
onInput: (v) => handlers.onInput("company_name", v),
|
|
888
|
+
onBlur: () => handlers.onBlur("company_name")
|
|
889
|
+
});
|
|
890
|
+
const domainRow = inputRow(brand, {
|
|
891
|
+
field: "company_domain",
|
|
892
|
+
label: "Company domain",
|
|
893
|
+
type: "text",
|
|
894
|
+
placeholder: "acme.com",
|
|
895
|
+
value: fields.company_domain,
|
|
896
|
+
error: errors.company_domain,
|
|
897
|
+
autocomplete: "url",
|
|
898
|
+
onInput: (v) => handlers.onInput("company_domain", v),
|
|
899
|
+
onBlur: () => handlers.onBlur("company_domain")
|
|
900
|
+
});
|
|
901
|
+
const emailRow = inputRow(brand, {
|
|
902
|
+
field: "email",
|
|
903
|
+
label: "Work email",
|
|
904
|
+
type: "email",
|
|
905
|
+
placeholder: "you@acme.com",
|
|
906
|
+
value: fields.email,
|
|
907
|
+
error: errors.email,
|
|
908
|
+
autocomplete: "email",
|
|
909
|
+
onInput: (v) => handlers.onInput("email", v),
|
|
910
|
+
onBlur: () => handlers.onBlur("email")
|
|
911
|
+
});
|
|
912
|
+
const generalError = el(
|
|
913
|
+
"div",
|
|
914
|
+
{
|
|
915
|
+
className: "dpdpa-general-error",
|
|
916
|
+
role: "alert",
|
|
917
|
+
style: { display: generalErrorText ? "block" : "none" }
|
|
918
|
+
},
|
|
919
|
+
generalErrorText || ""
|
|
920
|
+
);
|
|
921
|
+
const submitBtn = el(
|
|
922
|
+
"button",
|
|
923
|
+
{
|
|
924
|
+
type: "submit",
|
|
925
|
+
className: "dpdpa-btn-primary"
|
|
926
|
+
},
|
|
927
|
+
"Get my free report"
|
|
928
|
+
);
|
|
929
|
+
const form = el(
|
|
930
|
+
"form",
|
|
931
|
+
{
|
|
932
|
+
className: "dpdpa-form",
|
|
933
|
+
novalidate: "true",
|
|
934
|
+
onSubmit: (ev) => {
|
|
935
|
+
ev.preventDefault();
|
|
936
|
+
handlers.onSubmit();
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
el(
|
|
940
|
+
"h2",
|
|
941
|
+
{ className: "dpdpa-heading" },
|
|
942
|
+
"Get your free DPDPA compliance report"
|
|
943
|
+
),
|
|
944
|
+
el(
|
|
945
|
+
"p",
|
|
946
|
+
{ className: "dpdpa-subheading" },
|
|
947
|
+
"We'll analyse your site and deliver a compliance assessment PDF."
|
|
948
|
+
),
|
|
949
|
+
generalError,
|
|
950
|
+
nameRow.row,
|
|
951
|
+
domainRow.row,
|
|
952
|
+
emailRow.row,
|
|
953
|
+
submitBtn
|
|
954
|
+
);
|
|
955
|
+
const card = el(
|
|
956
|
+
"div",
|
|
957
|
+
{ className: "dpdpa-card" },
|
|
958
|
+
header(brand),
|
|
959
|
+
form,
|
|
960
|
+
footer(brand)
|
|
961
|
+
);
|
|
962
|
+
return {
|
|
963
|
+
root: card,
|
|
964
|
+
fieldInputs: {
|
|
965
|
+
company_name: nameRow.input,
|
|
966
|
+
company_domain: domainRow.input,
|
|
967
|
+
email: emailRow.input
|
|
968
|
+
},
|
|
969
|
+
fieldErrors: {
|
|
970
|
+
company_name: nameRow.errorSlot,
|
|
971
|
+
company_domain: domainRow.errorSlot,
|
|
972
|
+
email: emailRow.errorSlot
|
|
973
|
+
},
|
|
974
|
+
submitBtn,
|
|
975
|
+
generalError
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function renderSubmitting(brand, fields) {
|
|
979
|
+
const refs = renderIdle(brand, fields, {}, null, {
|
|
980
|
+
onInput: () => {
|
|
981
|
+
},
|
|
982
|
+
onBlur: () => {
|
|
983
|
+
},
|
|
984
|
+
onSubmit: () => {
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
Object.values(refs.fieldInputs).forEach((inp) => {
|
|
988
|
+
inp.disabled = true;
|
|
989
|
+
});
|
|
990
|
+
refs.submitBtn.disabled = true;
|
|
991
|
+
refs.submitBtn.textContent = "";
|
|
992
|
+
refs.submitBtn.appendChild(
|
|
993
|
+
el("span", { className: "dpdpa-spinner dpdpa-spinner-inline" })
|
|
994
|
+
);
|
|
995
|
+
refs.submitBtn.appendChild(document.createTextNode(" Submitting\u2026"));
|
|
996
|
+
return refs.root;
|
|
997
|
+
}
|
|
998
|
+
function animatedDots() {
|
|
999
|
+
return el(
|
|
1000
|
+
"span",
|
|
1001
|
+
{ className: "dpdpa-dots", "aria-hidden": "true" },
|
|
1002
|
+
el("span"),
|
|
1003
|
+
el("span"),
|
|
1004
|
+
el("span")
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
function stepCheck(brand) {
|
|
1008
|
+
return el(
|
|
1009
|
+
"div",
|
|
1010
|
+
{
|
|
1011
|
+
className: "dpdpa-step-icon dpdpa-step-icon-done",
|
|
1012
|
+
style: { backgroundColor: brand.successColor }
|
|
1013
|
+
},
|
|
1014
|
+
svg(
|
|
1015
|
+
"svg",
|
|
1016
|
+
{ width: 12, height: 12, viewBox: "0 0 24 24", fill: "none" },
|
|
1017
|
+
svg("path", {
|
|
1018
|
+
d: "M5 12l5 5L20 7",
|
|
1019
|
+
stroke: "#ffffff",
|
|
1020
|
+
"stroke-width": 3,
|
|
1021
|
+
"stroke-linecap": "round",
|
|
1022
|
+
"stroke-linejoin": "round"
|
|
1023
|
+
})
|
|
1024
|
+
)
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
function stepPulse(brand) {
|
|
1028
|
+
return el("div", {
|
|
1029
|
+
className: "dpdpa-step-icon dpdpa-step-icon-active",
|
|
1030
|
+
style: { backgroundColor: brand.primary }
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
var STEP_ORDER = {
|
|
1034
|
+
pending: 0,
|
|
1035
|
+
researching: 0,
|
|
1036
|
+
analysis_complete: 2
|
|
1037
|
+
};
|
|
1038
|
+
var STEPS = [
|
|
1039
|
+
{ label: (company) => `Researching ${company}` },
|
|
1040
|
+
{ label: () => "Analysing DPDPA compliance" },
|
|
1041
|
+
{ label: () => "Generating your report" }
|
|
1042
|
+
];
|
|
1043
|
+
function renderPolling(brand, opts) {
|
|
1044
|
+
const pct = Math.min(100, Math.round(opts.elapsedMs / opts.pollTimeoutMs * 100));
|
|
1045
|
+
const progressFill = el("div", {
|
|
1046
|
+
className: "dpdpa-progress-fill",
|
|
1047
|
+
style: { width: `${pct}%` }
|
|
1048
|
+
});
|
|
1049
|
+
const progressBar = el(
|
|
1050
|
+
"div",
|
|
1051
|
+
{
|
|
1052
|
+
className: "dpdpa-progress",
|
|
1053
|
+
role: "progressbar",
|
|
1054
|
+
"aria-label": "Report generation progress",
|
|
1055
|
+
"aria-valuemin": "0",
|
|
1056
|
+
"aria-valuemax": "100",
|
|
1057
|
+
"aria-valuenow": String(pct)
|
|
1058
|
+
},
|
|
1059
|
+
progressFill
|
|
1060
|
+
);
|
|
1061
|
+
const currentIdx = STEP_ORDER[opts.subStatus];
|
|
1062
|
+
const stepsList = el("div", { className: "dpdpa-steps" });
|
|
1063
|
+
STEPS.forEach((step, idx) => {
|
|
1064
|
+
if (idx > currentIdx) return;
|
|
1065
|
+
const isActive = idx === currentIdx;
|
|
1066
|
+
const row = el(
|
|
1067
|
+
"div",
|
|
1068
|
+
{
|
|
1069
|
+
className: `dpdpa-step ${isActive ? "dpdpa-step-active" : "dpdpa-step-done"}`
|
|
1070
|
+
},
|
|
1071
|
+
isActive ? stepPulse(brand) : stepCheck(brand),
|
|
1072
|
+
el(
|
|
1073
|
+
"span",
|
|
1074
|
+
{
|
|
1075
|
+
className: isActive ? "dpdpa-step-label dpdpa-shimmer" : "dpdpa-step-label"
|
|
1076
|
+
},
|
|
1077
|
+
step.label(opts.companyName)
|
|
1078
|
+
)
|
|
1079
|
+
);
|
|
1080
|
+
if (isActive) row.appendChild(animatedDots());
|
|
1081
|
+
stepsList.appendChild(row);
|
|
1082
|
+
});
|
|
1083
|
+
const body = el(
|
|
1084
|
+
"div",
|
|
1085
|
+
{ className: "dpdpa-polling" },
|
|
1086
|
+
stepsList,
|
|
1087
|
+
el(
|
|
1088
|
+
"p",
|
|
1089
|
+
{ className: "dpdpa-polling-sub" },
|
|
1090
|
+
"This usually takes 60 to 120 seconds. Keep this tab open."
|
|
1091
|
+
),
|
|
1092
|
+
progressBar
|
|
1093
|
+
);
|
|
1094
|
+
return el("div", { className: "dpdpa-card" }, header(brand), body, footer(brand));
|
|
1095
|
+
}
|
|
1096
|
+
function checkIcon(brand) {
|
|
1097
|
+
return el(
|
|
1098
|
+
"div",
|
|
1099
|
+
{
|
|
1100
|
+
className: "dpdpa-check-circle",
|
|
1101
|
+
style: { backgroundColor: brand.successColor }
|
|
1102
|
+
},
|
|
1103
|
+
svg(
|
|
1104
|
+
"svg",
|
|
1105
|
+
{
|
|
1106
|
+
width: 28,
|
|
1107
|
+
height: 28,
|
|
1108
|
+
viewBox: "0 0 24 24",
|
|
1109
|
+
fill: "none"
|
|
1110
|
+
},
|
|
1111
|
+
svg("path", {
|
|
1112
|
+
d: "M5 12l5 5L20 7",
|
|
1113
|
+
stroke: "#ffffff",
|
|
1114
|
+
"stroke-width": 3,
|
|
1115
|
+
"stroke-linecap": "round",
|
|
1116
|
+
"stroke-linejoin": "round"
|
|
1117
|
+
})
|
|
1118
|
+
)
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
function isSafeUrl(raw) {
|
|
1122
|
+
const s = (raw || "").trim();
|
|
1123
|
+
if (!s) return false;
|
|
1124
|
+
if (/^https?:\/\//i.test(s)) return true;
|
|
1125
|
+
if (/^\/[^/]/.test(s) || s.startsWith("./") || s.startsWith("../"))
|
|
1126
|
+
return true;
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
1129
|
+
function renderComplete(brand, opts) {
|
|
1130
|
+
const safeHref = isSafeUrl(opts.reportUrl) ? opts.reportUrl : "#";
|
|
1131
|
+
const downloadBtn = el(
|
|
1132
|
+
"a",
|
|
1133
|
+
{
|
|
1134
|
+
href: safeHref,
|
|
1135
|
+
target: "_blank",
|
|
1136
|
+
rel: "noopener noreferrer",
|
|
1137
|
+
className: "dpdpa-btn-primary dpdpa-btn-link"
|
|
1138
|
+
},
|
|
1139
|
+
"Download PDF"
|
|
1140
|
+
);
|
|
1141
|
+
const resetLink = el(
|
|
1142
|
+
"a",
|
|
1143
|
+
{
|
|
1144
|
+
href: "#",
|
|
1145
|
+
className: "dpdpa-reset-link",
|
|
1146
|
+
onClick: (ev) => {
|
|
1147
|
+
ev.preventDefault();
|
|
1148
|
+
opts.onReset();
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
"Request another report"
|
|
1152
|
+
);
|
|
1153
|
+
const body = el(
|
|
1154
|
+
"div",
|
|
1155
|
+
{ className: "dpdpa-success" },
|
|
1156
|
+
checkIcon(brand),
|
|
1157
|
+
el(
|
|
1158
|
+
"h3",
|
|
1159
|
+
{ className: "dpdpa-success-title" },
|
|
1160
|
+
`Your DPDPA report for ${opts.companyName} is ready`
|
|
1161
|
+
),
|
|
1162
|
+
el(
|
|
1163
|
+
"p",
|
|
1164
|
+
{ className: "dpdpa-success-sub" },
|
|
1165
|
+
"Click below to download your compliance assessment PDF."
|
|
1166
|
+
),
|
|
1167
|
+
downloadBtn,
|
|
1168
|
+
resetLink
|
|
1169
|
+
);
|
|
1170
|
+
return el("div", { className: "dpdpa-card" }, header(brand), body, footer(brand));
|
|
1171
|
+
}
|
|
1172
|
+
function renderError(brand, opts) {
|
|
1173
|
+
const retryBtn = el(
|
|
1174
|
+
"button",
|
|
1175
|
+
{
|
|
1176
|
+
type: "button",
|
|
1177
|
+
className: "dpdpa-btn-primary",
|
|
1178
|
+
onClick: () => opts.retryable ? opts.onRetry() : opts.onBack()
|
|
1179
|
+
},
|
|
1180
|
+
opts.retryable ? "Try again" : "Edit details"
|
|
1181
|
+
);
|
|
1182
|
+
const body = el(
|
|
1183
|
+
"div",
|
|
1184
|
+
{ className: "dpdpa-error-view" },
|
|
1185
|
+
el("h3", { className: "dpdpa-error-title" }, "Something went wrong"),
|
|
1186
|
+
el("p", { className: "dpdpa-error-message" }, opts.message),
|
|
1187
|
+
retryBtn
|
|
1188
|
+
);
|
|
1189
|
+
return el("div", { className: "dpdpa-card" }, header(brand), body, footer(brand));
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// src/form/index.ts
|
|
1193
|
+
var EMPTY_FIELDS = {
|
|
1194
|
+
company_name: "",
|
|
1195
|
+
company_domain: "",
|
|
1196
|
+
email: ""
|
|
1197
|
+
};
|
|
1198
|
+
var FormController = class {
|
|
1199
|
+
constructor(options) {
|
|
1200
|
+
this.targetEl = null;
|
|
1201
|
+
this.hostEl = null;
|
|
1202
|
+
this.shadowRoot = null;
|
|
1203
|
+
this.idleRefs = null;
|
|
1204
|
+
this.pollTimer = null;
|
|
1205
|
+
this.pollStartedAt = 0;
|
|
1206
|
+
this.pollHiddenSince = null;
|
|
1207
|
+
this.pollHiddenTotalMs = 0;
|
|
1208
|
+
this.pollResumePending = false;
|
|
1209
|
+
this.lastSubStatus = null;
|
|
1210
|
+
this.consecutiveErrors = 0;
|
|
1211
|
+
this.destroyed = false;
|
|
1212
|
+
this.unsubscribe = null;
|
|
1213
|
+
this.visibilityHandler = null;
|
|
1214
|
+
this.options = options;
|
|
1215
|
+
this.brand = resolveBranding(options.branding);
|
|
1216
|
+
this.fields = { ...EMPTY_FIELDS };
|
|
1217
|
+
this.store = new Store({
|
|
1218
|
+
phase: "idle",
|
|
1219
|
+
subStatus: "pending",
|
|
1220
|
+
elapsedMs: 0,
|
|
1221
|
+
jobId: null,
|
|
1222
|
+
reportUrl: null,
|
|
1223
|
+
errors: {},
|
|
1224
|
+
generalError: null,
|
|
1225
|
+
errorMessage: "",
|
|
1226
|
+
retryable: true
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
async init() {
|
|
1230
|
+
if (typeof this.options.target === "string") {
|
|
1231
|
+
const found = document.querySelector(this.options.target);
|
|
1232
|
+
if (!found)
|
|
1233
|
+
throw new Error(
|
|
1234
|
+
`DPDPA report form: target element not found for selector "${this.options.target}"`
|
|
1235
|
+
);
|
|
1236
|
+
this.targetEl = found;
|
|
1237
|
+
} else {
|
|
1238
|
+
this.targetEl = this.options.target;
|
|
1239
|
+
}
|
|
1240
|
+
this.hostEl = document.createElement("div");
|
|
1241
|
+
this.hostEl.setAttribute("data-redacto-dpdpa-report", "");
|
|
1242
|
+
this.hostEl.style.display = "block";
|
|
1243
|
+
this.targetEl.appendChild(this.hostEl);
|
|
1244
|
+
this.shadowRoot = this.hostEl.attachShadow({ mode: "open" });
|
|
1245
|
+
injectFormStyles(this.shadowRoot, this.brand);
|
|
1246
|
+
this.unsubscribe = this.store.subscribe((state, prev) => {
|
|
1247
|
+
if (state.phase !== prev.phase) this.render();
|
|
1248
|
+
else if (state.phase === "polling" && state.subStatus !== prev.subStatus)
|
|
1249
|
+
this.render();
|
|
1250
|
+
else if (state.phase === "polling" && state.elapsedMs !== prev.elapsedMs)
|
|
1251
|
+
this.updateProgress();
|
|
1252
|
+
});
|
|
1253
|
+
this.visibilityHandler = () => this.handleVisibilityChange();
|
|
1254
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
1255
|
+
this.render();
|
|
1256
|
+
}
|
|
1257
|
+
destroy() {
|
|
1258
|
+
var _a;
|
|
1259
|
+
this.destroyed = true;
|
|
1260
|
+
this.clearPolling();
|
|
1261
|
+
(_a = this.unsubscribe) == null ? void 0 : _a.call(this);
|
|
1262
|
+
this.unsubscribe = null;
|
|
1263
|
+
if (this.visibilityHandler) {
|
|
1264
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
1265
|
+
this.visibilityHandler = null;
|
|
1266
|
+
}
|
|
1267
|
+
this.store.destroy();
|
|
1268
|
+
if (this.hostEl && this.hostEl.parentNode)
|
|
1269
|
+
this.hostEl.parentNode.removeChild(this.hostEl);
|
|
1270
|
+
this.hostEl = null;
|
|
1271
|
+
this.shadowRoot = null;
|
|
1272
|
+
this.targetEl = null;
|
|
1273
|
+
this.idleRefs = null;
|
|
1274
|
+
}
|
|
1275
|
+
render() {
|
|
1276
|
+
if (!this.shadowRoot) return;
|
|
1277
|
+
clearElement(this.shadowRoot);
|
|
1278
|
+
injectFormStyles(this.shadowRoot, this.brand);
|
|
1279
|
+
this.idleRefs = null;
|
|
1280
|
+
const s = this.store.getState();
|
|
1281
|
+
let node;
|
|
1282
|
+
switch (s.phase) {
|
|
1283
|
+
case "idle":
|
|
1284
|
+
this.idleRefs = renderIdle(
|
|
1285
|
+
this.brand,
|
|
1286
|
+
this.fields,
|
|
1287
|
+
s.errors,
|
|
1288
|
+
s.generalError,
|
|
1289
|
+
{
|
|
1290
|
+
onInput: (field, value) => this.handleInput(field, value),
|
|
1291
|
+
onBlur: (field) => this.handleBlur(field),
|
|
1292
|
+
onSubmit: () => this.handleSubmit()
|
|
1293
|
+
}
|
|
1294
|
+
);
|
|
1295
|
+
node = this.idleRefs.root;
|
|
1296
|
+
break;
|
|
1297
|
+
case "submitting":
|
|
1298
|
+
node = renderSubmitting(this.brand, this.fields);
|
|
1299
|
+
break;
|
|
1300
|
+
case "polling":
|
|
1301
|
+
node = renderPolling(this.brand, {
|
|
1302
|
+
companyName: this.fields.company_name,
|
|
1303
|
+
subStatus: s.subStatus,
|
|
1304
|
+
elapsedMs: s.elapsedMs,
|
|
1305
|
+
pollTimeoutMs: this.getPollTimeout()
|
|
1306
|
+
});
|
|
1307
|
+
break;
|
|
1308
|
+
case "complete":
|
|
1309
|
+
node = renderComplete(this.brand, {
|
|
1310
|
+
companyName: this.fields.company_name,
|
|
1311
|
+
reportUrl: s.reportUrl || "#",
|
|
1312
|
+
onReset: () => this.resetToIdle()
|
|
1313
|
+
});
|
|
1314
|
+
break;
|
|
1315
|
+
case "error":
|
|
1316
|
+
node = renderError(this.brand, {
|
|
1317
|
+
message: s.errorMessage,
|
|
1318
|
+
retryable: s.retryable,
|
|
1319
|
+
onRetry: () => this.submit(),
|
|
1320
|
+
onBack: () => {
|
|
1321
|
+
var _a;
|
|
1322
|
+
return this.backToIdle((_a = s.generalError) != null ? _a : s.errorMessage);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
1327
|
+
this.shadowRoot.appendChild(node);
|
|
1328
|
+
if (s.phase === "idle" && this.idleRefs) {
|
|
1329
|
+
const firstErrorField = Object.keys(s.errors).find(
|
|
1330
|
+
(f) => !!s.errors[f]
|
|
1331
|
+
);
|
|
1332
|
+
if (firstErrorField) this.idleRefs.fieldInputs[firstErrorField].focus();
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
updateProgress() {
|
|
1336
|
+
if (!this.shadowRoot) return;
|
|
1337
|
+
const bar = this.shadowRoot.querySelector(".dpdpa-progress-fill");
|
|
1338
|
+
const track = this.shadowRoot.querySelector(".dpdpa-progress");
|
|
1339
|
+
if (!bar) return;
|
|
1340
|
+
const pct = Math.min(
|
|
1341
|
+
100,
|
|
1342
|
+
Math.round(this.store.getState().elapsedMs / this.getPollTimeout() * 100)
|
|
1343
|
+
);
|
|
1344
|
+
bar.style.width = `${pct}%`;
|
|
1345
|
+
if (track) track.setAttribute("aria-valuenow", String(pct));
|
|
1346
|
+
}
|
|
1347
|
+
handleInput(field, value) {
|
|
1348
|
+
this.fields[field] = value;
|
|
1349
|
+
const prev = this.store.getState().errors[field];
|
|
1350
|
+
if (prev) {
|
|
1351
|
+
const next = { ...this.store.getState().errors };
|
|
1352
|
+
delete next[field];
|
|
1353
|
+
this.store.setState({ errors: next });
|
|
1354
|
+
if (this.idleRefs) this.clearFieldErrorSlot(field);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
handleBlur(field) {
|
|
1358
|
+
const err = validateField(field, this.fields[field]);
|
|
1359
|
+
const next = { ...this.store.getState().errors };
|
|
1360
|
+
if (err) next[field] = err;
|
|
1361
|
+
else delete next[field];
|
|
1362
|
+
this.store.setState({ errors: next });
|
|
1363
|
+
this.updateFieldErrorSlot(field, err);
|
|
1364
|
+
}
|
|
1365
|
+
updateFieldErrorSlot(field, err) {
|
|
1366
|
+
if (!this.idleRefs) return;
|
|
1367
|
+
const slot = this.idleRefs.fieldErrors[field];
|
|
1368
|
+
if (err) {
|
|
1369
|
+
slot.textContent = err;
|
|
1370
|
+
slot.style.display = "block";
|
|
1371
|
+
} else {
|
|
1372
|
+
slot.textContent = "";
|
|
1373
|
+
slot.style.display = "none";
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
clearFieldErrorSlot(field) {
|
|
1377
|
+
if (!this.idleRefs) return;
|
|
1378
|
+
const slot = this.idleRefs.fieldErrors[field];
|
|
1379
|
+
slot.textContent = "";
|
|
1380
|
+
slot.style.display = "none";
|
|
1381
|
+
}
|
|
1382
|
+
handleSubmit() {
|
|
1383
|
+
const normalizedFields = {
|
|
1384
|
+
company_name: this.fields.company_name.trim(),
|
|
1385
|
+
company_domain: normalizeDomain(this.fields.company_domain),
|
|
1386
|
+
email: this.fields.email.trim()
|
|
1387
|
+
};
|
|
1388
|
+
this.fields = normalizedFields;
|
|
1389
|
+
const errors = validateAll(normalizedFields);
|
|
1390
|
+
if (Object.keys(errors).length > 0) {
|
|
1391
|
+
this.store.setState({ errors, generalError: null });
|
|
1392
|
+
this.render();
|
|
1393
|
+
this.emitError("One or more fields need your attention.", "validation");
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
this.store.setState({ errors: {}, generalError: null });
|
|
1397
|
+
this.submit();
|
|
1398
|
+
}
|
|
1399
|
+
async submit() {
|
|
1400
|
+
var _a, _b, _c, _d;
|
|
1401
|
+
const lead = this.makeLead(null);
|
|
1402
|
+
try {
|
|
1403
|
+
(_b = (_a = this.options).onSubmit) == null ? void 0 : _b.call(_a, {
|
|
1404
|
+
email: lead.email,
|
|
1405
|
+
company_name: lead.company_name,
|
|
1406
|
+
company_domain: lead.company_domain,
|
|
1407
|
+
timestamp: lead.timestamp
|
|
1408
|
+
});
|
|
1409
|
+
} catch (err) {
|
|
1410
|
+
console.error("onSubmit handler threw:", err);
|
|
1411
|
+
}
|
|
1412
|
+
saveLead(lead);
|
|
1413
|
+
this.store.setState({ phase: "submitting" });
|
|
1414
|
+
try {
|
|
1415
|
+
const res = await submitLead({
|
|
1416
|
+
baseUrl: this.getBaseUrl(),
|
|
1417
|
+
body: {
|
|
1418
|
+
company_name: lead.company_name,
|
|
1419
|
+
company_domain: lead.company_domain
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
if (this.destroyed) return;
|
|
1423
|
+
if (res.status === "error") {
|
|
1424
|
+
this.showError(res.message || "We couldn't accept that submission.", false);
|
|
1425
|
+
this.emitError(res.message || "Submission rejected", "submit");
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
const acceptedLead = { ...lead, job_id: res.job_id };
|
|
1429
|
+
saveLead(acceptedLead);
|
|
1430
|
+
try {
|
|
1431
|
+
(_d = (_c = this.options).onJobAccepted) == null ? void 0 : _d.call(_c, res.job_id, acceptedLead);
|
|
1432
|
+
} catch (err) {
|
|
1433
|
+
console.error("onJobAccepted handler threw:", err);
|
|
1434
|
+
}
|
|
1435
|
+
this.startPolling(res.job_id);
|
|
1436
|
+
} catch (err) {
|
|
1437
|
+
if (this.destroyed) return;
|
|
1438
|
+
const apiErr = err;
|
|
1439
|
+
const retryable = !apiErr.status || apiErr.status >= 500;
|
|
1440
|
+
this.showError(
|
|
1441
|
+
apiErr.message || "We couldn't reach our server. Please try again.",
|
|
1442
|
+
retryable
|
|
1443
|
+
);
|
|
1444
|
+
this.emitError(apiErr.message || "Submit failed", "submit", apiErr.status);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
startPolling(jobId) {
|
|
1448
|
+
this.clearPolling();
|
|
1449
|
+
this.pollStartedAt = Date.now();
|
|
1450
|
+
this.pollHiddenSince = null;
|
|
1451
|
+
this.pollHiddenTotalMs = 0;
|
|
1452
|
+
this.lastSubStatus = null;
|
|
1453
|
+
this.consecutiveErrors = 0;
|
|
1454
|
+
this.store.setState({
|
|
1455
|
+
phase: "polling",
|
|
1456
|
+
subStatus: "pending",
|
|
1457
|
+
elapsedMs: 0,
|
|
1458
|
+
jobId
|
|
1459
|
+
});
|
|
1460
|
+
this.schedulePoll();
|
|
1461
|
+
}
|
|
1462
|
+
schedulePoll() {
|
|
1463
|
+
var _a;
|
|
1464
|
+
if (this.destroyed) return;
|
|
1465
|
+
if (typeof document !== "undefined" && document.hidden) {
|
|
1466
|
+
if (this.pollHiddenSince === null) this.pollHiddenSince = Date.now();
|
|
1467
|
+
this.pollResumePending = true;
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
const interval = (_a = this.options.pollIntervalMs) != null ? _a : DEFAULT_POLL_INTERVAL_MS;
|
|
1471
|
+
this.pollTimer = setTimeout(() => this.pollTick(), interval);
|
|
1472
|
+
}
|
|
1473
|
+
getActiveElapsed() {
|
|
1474
|
+
return Date.now() - this.pollStartedAt - this.pollHiddenTotalMs;
|
|
1475
|
+
}
|
|
1476
|
+
async pollTick() {
|
|
1477
|
+
var _a, _b, _c, _d;
|
|
1478
|
+
if (this.destroyed) return;
|
|
1479
|
+
const jobId = this.store.getState().jobId;
|
|
1480
|
+
if (!jobId) return;
|
|
1481
|
+
const activeElapsed = this.getActiveElapsed();
|
|
1482
|
+
if (activeElapsed >= this.getPollTimeout()) {
|
|
1483
|
+
this.showError(
|
|
1484
|
+
"Report generation is taking longer than expected. Our team has been notified.",
|
|
1485
|
+
false
|
|
1486
|
+
);
|
|
1487
|
+
this.emitError("Polling timed out", "timeout");
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
try {
|
|
1491
|
+
const res = await fetchJobStatus({ baseUrl: this.getBaseUrl(), jobId });
|
|
1492
|
+
if (this.destroyed) return;
|
|
1493
|
+
this.consecutiveErrors = 0;
|
|
1494
|
+
if ("status" in res && res.status === "error") {
|
|
1495
|
+
this.showError(res.message || "Report generation failed.", true);
|
|
1496
|
+
this.emitError(res.message || "Job errored", "poll");
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
if (res.status === "complete" && "report_url" in res && res.report_url) {
|
|
1500
|
+
const acceptedLead = this.makeLead(jobId);
|
|
1501
|
+
saveLead(acceptedLead);
|
|
1502
|
+
this.store.setState({
|
|
1503
|
+
phase: "complete",
|
|
1504
|
+
reportUrl: res.report_url
|
|
1505
|
+
});
|
|
1506
|
+
try {
|
|
1507
|
+
(_b = (_a = this.options).onComplete) == null ? void 0 : _b.call(_a, res.report_url, acceptedLead);
|
|
1508
|
+
} catch (err) {
|
|
1509
|
+
console.error("onComplete handler threw:", err);
|
|
1510
|
+
}
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const sub = res.status;
|
|
1514
|
+
if (sub !== this.lastSubStatus) {
|
|
1515
|
+
this.lastSubStatus = sub;
|
|
1516
|
+
try {
|
|
1517
|
+
(_d = (_c = this.options).onStatusChange) == null ? void 0 : _d.call(_c, sub);
|
|
1518
|
+
} catch (err) {
|
|
1519
|
+
console.error("onStatusChange handler threw:", err);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
this.store.setState({
|
|
1523
|
+
phase: "polling",
|
|
1524
|
+
subStatus: sub,
|
|
1525
|
+
elapsedMs: activeElapsed
|
|
1526
|
+
});
|
|
1527
|
+
this.schedulePoll();
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
if (this.destroyed) return;
|
|
1530
|
+
this.consecutiveErrors++;
|
|
1531
|
+
if (this.consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
1532
|
+
this.showError(
|
|
1533
|
+
"We can't reach our server right now. Please try again.",
|
|
1534
|
+
true
|
|
1535
|
+
);
|
|
1536
|
+
this.emitError(
|
|
1537
|
+
err instanceof Error ? err.message : "Poll failed",
|
|
1538
|
+
"poll"
|
|
1539
|
+
);
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
this.schedulePoll();
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
clearPolling() {
|
|
1546
|
+
if (this.pollTimer) {
|
|
1547
|
+
clearTimeout(this.pollTimer);
|
|
1548
|
+
this.pollTimer = null;
|
|
1549
|
+
}
|
|
1550
|
+
this.pollResumePending = false;
|
|
1551
|
+
}
|
|
1552
|
+
handleVisibilityChange() {
|
|
1553
|
+
if (this.destroyed) return;
|
|
1554
|
+
if (document.hidden) {
|
|
1555
|
+
if (this.store.getState().phase === "polling" && this.pollHiddenSince === null) {
|
|
1556
|
+
this.pollHiddenSince = Date.now();
|
|
1557
|
+
}
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
if (this.pollHiddenSince !== null) {
|
|
1561
|
+
this.pollHiddenTotalMs += Date.now() - this.pollHiddenSince;
|
|
1562
|
+
this.pollHiddenSince = null;
|
|
1563
|
+
}
|
|
1564
|
+
if (this.pollResumePending && this.store.getState().phase === "polling") {
|
|
1565
|
+
this.pollResumePending = false;
|
|
1566
|
+
this.pollTick();
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
showError(message, retryable) {
|
|
1570
|
+
this.store.setState({
|
|
1571
|
+
phase: "error",
|
|
1572
|
+
errorMessage: message,
|
|
1573
|
+
retryable
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
resetToIdle() {
|
|
1577
|
+
this.fields = { ...EMPTY_FIELDS };
|
|
1578
|
+
this.lastSubStatus = null;
|
|
1579
|
+
this.store.setState({
|
|
1580
|
+
phase: "idle",
|
|
1581
|
+
errors: {},
|
|
1582
|
+
generalError: null,
|
|
1583
|
+
errorMessage: "",
|
|
1584
|
+
reportUrl: null,
|
|
1585
|
+
jobId: null,
|
|
1586
|
+
subStatus: "pending",
|
|
1587
|
+
elapsedMs: 0
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
backToIdle(generalError) {
|
|
1591
|
+
this.store.setState({
|
|
1592
|
+
phase: "idle",
|
|
1593
|
+
errors: {},
|
|
1594
|
+
generalError,
|
|
1595
|
+
errorMessage: ""
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
emitError(message, phase, status) {
|
|
1599
|
+
var _a, _b;
|
|
1600
|
+
try {
|
|
1601
|
+
const err = Object.assign(new Error(message), { phase, status });
|
|
1602
|
+
(_b = (_a = this.options).onError) == null ? void 0 : _b.call(_a, err);
|
|
1603
|
+
} catch (e) {
|
|
1604
|
+
console.error("onError handler threw:", e);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
makeLead(jobId) {
|
|
1608
|
+
return {
|
|
1609
|
+
email: this.fields.email,
|
|
1610
|
+
company_name: this.fields.company_name,
|
|
1611
|
+
company_domain: this.fields.company_domain,
|
|
1612
|
+
job_id: jobId,
|
|
1613
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
getBaseUrl() {
|
|
1617
|
+
return (this.options.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
1618
|
+
}
|
|
1619
|
+
getPollTimeout() {
|
|
1620
|
+
var _a;
|
|
1621
|
+
return (_a = this.options.pollTimeoutMs) != null ? _a : DEFAULT_POLL_TIMEOUT_MS;
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1625
|
+
// src/index.ts
|
|
1626
|
+
var version = "0.1.0";
|
|
1627
|
+
var activeController = null;
|
|
1628
|
+
function init(options) {
|
|
1629
|
+
destroy();
|
|
1630
|
+
activeController = new FormController(options);
|
|
1631
|
+
activeController.init().catch((err) => {
|
|
1632
|
+
var _a;
|
|
1633
|
+
try {
|
|
1634
|
+
(_a = options.onError) == null ? void 0 : _a.call(
|
|
1635
|
+
options,
|
|
1636
|
+
Object.assign(err instanceof Error ? err : new Error(String(err)), {
|
|
1637
|
+
phase: "validation"
|
|
1638
|
+
})
|
|
1639
|
+
);
|
|
1640
|
+
} catch (handlerErr) {
|
|
1641
|
+
console.error("onError handler threw:", handlerErr);
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
function destroy() {
|
|
1646
|
+
if (activeController) {
|
|
1647
|
+
activeController.destroy();
|
|
1648
|
+
activeController = null;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
function getCapturedLead() {
|
|
1652
|
+
return loadLead();
|
|
1653
|
+
}
|
|
1654
|
+
var RedactoDpdpaReport = { init, destroy, getCapturedLead, version };
|
|
1655
|
+
var src_default = RedactoDpdpaReport;
|
|
1656
|
+
export {
|
|
1657
|
+
src_default as default,
|
|
1658
|
+
destroy,
|
|
1659
|
+
getCapturedLead,
|
|
1660
|
+
init,
|
|
1661
|
+
version
|
|
1662
|
+
};
|