@nodii/onboarding 0.1.0-dev.0 → 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/README.md +124 -0
- package/dist/{chunk-4SWOVHZ2.js → chunk-A3DE2IAH.js} +155 -18
- package/dist/chunk-A3DE2IAH.js.map +1 -0
- package/dist/{chunk-AFCJLUPK.js → chunk-DWFZA5T6.js} +19 -8
- package/dist/chunk-DWFZA5T6.js.map +1 -0
- package/dist/chunk-FIRSA7EH.js +3 -0
- package/dist/{chunk-ZDY2I6KY.js.map → chunk-FIRSA7EH.js.map} +1 -1
- package/dist/chunk-JFDQZ3AC.js +3 -0
- package/dist/chunk-JFDQZ3AC.js.map +1 -0
- package/dist/{chunk-34BQX2KR.js → chunk-KFHVZGX6.js} +61 -6
- package/dist/chunk-KFHVZGX6.js.map +1 -0
- package/dist/chunk-NDSBIB3N.js +99 -0
- package/dist/chunk-NDSBIB3N.js.map +1 -0
- package/dist/chunk-OLWR5COK.js +10 -0
- package/dist/chunk-OLWR5COK.js.map +1 -0
- package/dist/{chunk-SWMIIUXR.js → chunk-OVULPWNZ.js} +917 -158
- package/dist/chunk-OVULPWNZ.js.map +1 -0
- package/dist/chunk-SEKNVHD3.js +59 -0
- package/dist/chunk-SEKNVHD3.js.map +1 -0
- package/dist/chunk-T7EWURTF.js +321 -0
- package/dist/chunk-T7EWURTF.js.map +1 -0
- package/dist/{chunk-BQCTDU5V.js → chunk-UHOFGWI7.js} +90 -15
- package/dist/chunk-UHOFGWI7.js.map +1 -0
- package/dist/chunk-WKN4U7QF.js +35 -0
- package/dist/chunk-WKN4U7QF.js.map +1 -0
- package/dist/{chunk-AW7W3HAC.js → chunk-XJTJPBJB.js} +40 -3
- package/dist/chunk-XJTJPBJB.js.map +1 -0
- package/dist/chunk-ZR6X3GSM.js +186 -0
- package/dist/chunk-ZR6X3GSM.js.map +1 -0
- package/dist/client-NvqPpfQP.d.ts +86 -0
- package/dist/consent/index.d.ts +59 -0
- package/dist/consent/index.js +7 -0
- package/dist/consent/index.js.map +1 -0
- package/dist/controller-BLH3wnxl.d.ts +177 -0
- package/dist/dynamic/index.d.ts +65 -6
- package/dist/dynamic/index.js +5 -2
- package/dist/errors-vJkye4a0.d.ts +110 -0
- package/dist/form/index.d.ts +12 -4
- package/dist/form/index.js +2 -2
- package/dist/index-BMSbQGWP.d.ts +103 -0
- package/dist/index.d.ts +13 -7
- package/dist/index.js +13 -8
- package/dist/model-BI_I-D_V.d.ts +267 -0
- package/dist/{model-IEyDI54w.d.ts → model-CEwvHlWA.d.ts} +212 -46
- package/dist/render-EWQXF73I.js +5 -0
- package/dist/render-EWQXF73I.js.map +1 -0
- package/dist/schema-BrtjpXfL.d.ts +257 -0
- package/dist/{signup-client-Bl6eUrxR.d.ts → signup-client-CS95nkVO.d.ts} +152 -19
- package/dist/spec/index.d.ts +10 -81
- package/dist/spec/index.js +3 -2
- package/dist/submit/index.d.ts +7 -71
- package/dist/submit/index.js +4 -3
- package/dist/viewer/index.d.ts +4 -0
- package/dist/viewer/index.js +5 -0
- package/dist/viewer/index.js.map +1 -0
- package/dist/widget/index.d.ts +102 -20
- package/dist/widget/index.js +10 -7
- package/package.json +21 -2
- package/dist/chunk-34BQX2KR.js.map +0 -1
- package/dist/chunk-4SWOVHZ2.js.map +0 -1
- package/dist/chunk-AFCJLUPK.js.map +0 -1
- package/dist/chunk-AW7W3HAC.js.map +0 -1
- package/dist/chunk-BIFANV7M.js +0 -22
- package/dist/chunk-BIFANV7M.js.map +0 -1
- package/dist/chunk-BQCTDU5V.js.map +0 -1
- package/dist/chunk-SWMIIUXR.js.map +0 -1
- package/dist/chunk-ZDY2I6KY.js +0 -3
- package/dist/schema-TfyBU6JW.d.ts +0 -107
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { createContactFormState } from './chunk-
|
|
2
|
-
import {
|
|
3
|
-
import { createSignupStepper, optionValue } from './chunk-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { createContactFormState } from './chunk-DWFZA5T6.js';
|
|
2
|
+
import { fetchOnboardingSpec, OnboardingSpecCache, defaultSpecCache, diffSpecFields } from './chunk-KFHVZGX6.js';
|
|
3
|
+
import { createSignupStepper, optionValue } from './chunk-A3DE2IAH.js';
|
|
4
|
+
import { isNavigableUrl } from './chunk-NDSBIB3N.js';
|
|
5
|
+
import { computePayloadFingerprint, generateIdempotencyKey, submitLead, computeSignupFingerprint, submitSignup } from './chunk-UHOFGWI7.js';
|
|
6
|
+
import { contactFormSchema } from './chunk-WKN4U7QF.js';
|
|
7
|
+
import { OnboardingBotChallengeError, OnboardingRateLimitError, OnboardingSpecStaleError, OnboardingConsentStaleError, OnboardingConsentIncompleteError, OnboardingSubdomainTakenError, OnboardingValidationError } from './chunk-XJTJPBJB.js';
|
|
6
8
|
import { loadRecaptcha, executeRecaptcha } from './chunk-7NS7ENJV.js';
|
|
7
9
|
|
|
8
10
|
// src/widget/dom-utils.ts
|
|
@@ -219,6 +221,35 @@ var DEFAULT_WIDGET_CSS = `
|
|
|
219
221
|
margin-bottom: var(--_spacing);
|
|
220
222
|
}
|
|
221
223
|
|
|
224
|
+
fieldset.nodii-onboarding-field {
|
|
225
|
+
border: none;
|
|
226
|
+
padding: 0;
|
|
227
|
+
margin-inline: 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.nodii-onboarding-choices {
|
|
231
|
+
display: flex;
|
|
232
|
+
flex-wrap: wrap;
|
|
233
|
+
gap: calc(var(--_spacing) * 0.5);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.nodii-onboarding-choice {
|
|
237
|
+
display: inline-flex;
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: calc(var(--_spacing) * 0.375);
|
|
240
|
+
padding: calc(var(--_spacing) * 0.375) calc(var(--_spacing) * 0.75);
|
|
241
|
+
border: 1px solid var(--_border);
|
|
242
|
+
border-radius: 999px;
|
|
243
|
+
background: var(--_input-bg);
|
|
244
|
+
font-size: 0.875rem;
|
|
245
|
+
color: var(--_label);
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
fieldset.nodii-onboarding-field.has-error .nodii-onboarding-choice {
|
|
250
|
+
border-color: var(--_error);
|
|
251
|
+
}
|
|
252
|
+
|
|
222
253
|
.nodii-onboarding-consent-row {
|
|
223
254
|
display: flex;
|
|
224
255
|
align-items: flex-start;
|
|
@@ -243,6 +274,193 @@ var DEFAULT_WIDGET_CSS = `
|
|
|
243
274
|
text-align: center;
|
|
244
275
|
color: var(--_label);
|
|
245
276
|
}
|
|
277
|
+
|
|
278
|
+
/* \u2500\u2500 D543 ConsentPack step (full-width, generous spacing) \u2500\u2500 */
|
|
279
|
+
|
|
280
|
+
.nodii-onboarding-consent-stage {
|
|
281
|
+
/* Full-bleed within the widget host \u2014 consent cards may span the page. */
|
|
282
|
+
width: 100%;
|
|
283
|
+
display: flex;
|
|
284
|
+
flex-direction: column;
|
|
285
|
+
gap: calc(var(--_spacing) * 2);
|
|
286
|
+
margin: calc(var(--_spacing) * 1.5) 0;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.nodii-onboarding-consent-intro {
|
|
290
|
+
font-size: 0.9375rem;
|
|
291
|
+
color: var(--_label);
|
|
292
|
+
margin: 0 0 calc(var(--_spacing) * 0.5) 0;
|
|
293
|
+
line-height: 1.6;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.nodii-onboarding-consent-card {
|
|
297
|
+
width: 100%;
|
|
298
|
+
border: 1px solid var(--_border);
|
|
299
|
+
border-radius: var(--_radius);
|
|
300
|
+
background: var(--_input-bg);
|
|
301
|
+
padding: calc(var(--_spacing) * 1.5);
|
|
302
|
+
display: flex;
|
|
303
|
+
flex-direction: column;
|
|
304
|
+
gap: var(--_spacing);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.nodii-onboarding-consent-card.is-stale {
|
|
308
|
+
border-color: #fcd34d;
|
|
309
|
+
background: #fffbeb;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.nodii-onboarding-consent-card.has-error {
|
|
313
|
+
border-color: var(--_error);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.nodii-onboarding-consent-title {
|
|
317
|
+
font-size: 1.125rem;
|
|
318
|
+
font-weight: 600;
|
|
319
|
+
color: var(--_label);
|
|
320
|
+
margin: 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.nodii-onboarding-consent-stale-tag {
|
|
324
|
+
font-size: 0.8125rem;
|
|
325
|
+
font-weight: 500;
|
|
326
|
+
color: #92400e;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.nodii-onboarding-consent-gate {
|
|
330
|
+
width: 100%;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.nodii-onboarding-consent-scrollbox {
|
|
334
|
+
width: 100%;
|
|
335
|
+
height: 360px;
|
|
336
|
+
overflow-y: auto;
|
|
337
|
+
border: 1px solid var(--_border);
|
|
338
|
+
border-radius: var(--_radius);
|
|
339
|
+
background: #fff;
|
|
340
|
+
position: relative;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.nodii-onboarding-consent-scrollbox:focus {
|
|
344
|
+
outline: none;
|
|
345
|
+
border-color: var(--_primary);
|
|
346
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--_primary) 20%, transparent);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* Inline, same-origin document content (no iframe). Rendered by the default
|
|
350
|
+
viewer: sanitized HTML/markdown, escaped plain text, or PDF.js canvases. */
|
|
351
|
+
.nodii-onboarding-consent-doc-body {
|
|
352
|
+
padding: var(--_spacing);
|
|
353
|
+
font-size: 0.9375rem;
|
|
354
|
+
line-height: 1.7;
|
|
355
|
+
color: var(--_label);
|
|
356
|
+
word-break: break-word;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.nodii-onboarding-consent-doc-body:not(.nodii-consent-pdf) {
|
|
360
|
+
white-space: pre-wrap;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.nodii-onboarding-consent-doc-loading {
|
|
364
|
+
padding: var(--_spacing);
|
|
365
|
+
color: #6b7280;
|
|
366
|
+
font-size: 0.875rem;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.nodii-consent-pdf {
|
|
370
|
+
display: flex;
|
|
371
|
+
flex-direction: column;
|
|
372
|
+
gap: var(--_spacing);
|
|
373
|
+
white-space: normal;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.nodii-consent-pdf-page {
|
|
377
|
+
max-width: 100%;
|
|
378
|
+
height: auto;
|
|
379
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.nodii-onboarding-consent-note {
|
|
383
|
+
font-size: 0.8125rem;
|
|
384
|
+
color: #6b7280;
|
|
385
|
+
margin: calc(var(--_spacing) * 0.5) 0 0 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/* A gated doc that could not be rendered inline: the gate stays unsatisfiable
|
|
389
|
+
(accept disabled); we surface a clear error + Retry. NO click-to-satisfy. */
|
|
390
|
+
.nodii-onboarding-consent-unrenderable {
|
|
391
|
+
display: flex;
|
|
392
|
+
flex-direction: column;
|
|
393
|
+
gap: calc(var(--_spacing) * 0.5);
|
|
394
|
+
padding: var(--_spacing);
|
|
395
|
+
border: 1px solid color-mix(in srgb, var(--_error) 30%, transparent);
|
|
396
|
+
border-radius: var(--_radius);
|
|
397
|
+
background: color-mix(in srgb, var(--_error) 6%, transparent);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.nodii-onboarding-consent-retry {
|
|
401
|
+
align-self: flex-start;
|
|
402
|
+
padding: calc(var(--_spacing) * 0.5) var(--_spacing);
|
|
403
|
+
background: transparent;
|
|
404
|
+
color: var(--_primary);
|
|
405
|
+
border: 1px solid var(--_primary);
|
|
406
|
+
border-radius: var(--_radius);
|
|
407
|
+
font-family: var(--_font);
|
|
408
|
+
font-size: 0.875rem;
|
|
409
|
+
cursor: pointer;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.nodii-onboarding-consent-open-row,
|
|
413
|
+
.nodii-onboarding-consent-preview-row {
|
|
414
|
+
display: flex;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.nodii-onboarding-consent-open {
|
|
418
|
+
display: inline-flex;
|
|
419
|
+
align-items: center;
|
|
420
|
+
padding: calc(var(--_spacing) * 0.625) var(--_spacing);
|
|
421
|
+
border: 1px solid var(--_primary);
|
|
422
|
+
border-radius: var(--_radius);
|
|
423
|
+
color: var(--_primary);
|
|
424
|
+
text-decoration: none;
|
|
425
|
+
font-size: 0.9375rem;
|
|
426
|
+
font-weight: 500;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.nodii-onboarding-consent-open:hover {
|
|
430
|
+
background: color-mix(in srgb, var(--_primary) 8%, transparent);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.nodii-onboarding-consent-preview {
|
|
434
|
+
font-size: 0.875rem;
|
|
435
|
+
color: var(--_primary);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.nodii-onboarding-consent-accept-row {
|
|
439
|
+
display: flex;
|
|
440
|
+
align-items: flex-start;
|
|
441
|
+
gap: calc(var(--_spacing) * 0.5);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.nodii-onboarding-consent-accept {
|
|
445
|
+
margin-top: 3px;
|
|
446
|
+
width: 1.125rem;
|
|
447
|
+
height: 1.125rem;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.nodii-onboarding-consent-accept:disabled {
|
|
451
|
+
cursor: not-allowed;
|
|
452
|
+
opacity: 0.5;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.nodii-onboarding-consent-accept-label {
|
|
456
|
+
font-size: 0.9375rem;
|
|
457
|
+
color: var(--_label);
|
|
458
|
+
line-height: 1.5;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.nodii-onboarding-consent-required {
|
|
462
|
+
color: var(--_error);
|
|
463
|
+
}
|
|
246
464
|
`;
|
|
247
465
|
function injectWidgetStyles() {
|
|
248
466
|
if (typeof document === "undefined") return;
|
|
@@ -269,59 +487,130 @@ function el(tag, attrs = {}, children = []) {
|
|
|
269
487
|
}
|
|
270
488
|
return element;
|
|
271
489
|
}
|
|
490
|
+
function errorSlot(id) {
|
|
491
|
+
return el("div", {
|
|
492
|
+
class: "nodii-onboarding-field-error",
|
|
493
|
+
id: `${id}-error`,
|
|
494
|
+
role: "alert",
|
|
495
|
+
"aria-live": "polite"
|
|
496
|
+
});
|
|
497
|
+
}
|
|
272
498
|
function createFieldEl(id, label, input, required) {
|
|
273
499
|
const labelEl = el(
|
|
274
500
|
"label",
|
|
275
501
|
{ for: id, class: `nodii-onboarding-label${required ? " required" : ""}` },
|
|
276
502
|
[label]
|
|
277
503
|
);
|
|
278
|
-
|
|
279
|
-
class: "nodii-onboarding-field-error",
|
|
280
|
-
id: `${id}-error`,
|
|
281
|
-
role: "alert",
|
|
282
|
-
"aria-live": "polite"
|
|
283
|
-
});
|
|
284
|
-
const field = el("div", { class: "nodii-onboarding-field" }, [
|
|
504
|
+
return el("div", { class: "nodii-onboarding-field" }, [
|
|
285
505
|
labelEl,
|
|
286
506
|
input,
|
|
287
|
-
|
|
507
|
+
errorSlot(id)
|
|
288
508
|
]);
|
|
289
|
-
return field;
|
|
290
509
|
}
|
|
291
510
|
function setFieldError(container, fieldId, message) {
|
|
292
511
|
const errorEl = container.querySelector(idSelector(`${fieldId}-error`));
|
|
293
512
|
if (errorEl) errorEl.textContent = message;
|
|
294
513
|
const inputEl = container.querySelector(idSelector(fieldId));
|
|
295
514
|
if (inputEl) inputEl.classList.add("has-error");
|
|
515
|
+
return errorEl !== null;
|
|
296
516
|
}
|
|
297
517
|
function clearFieldErrors(container) {
|
|
298
|
-
for (const
|
|
518
|
+
for (const el4 of container.querySelectorAll(
|
|
299
519
|
".nodii-onboarding-field-error"
|
|
300
520
|
)) {
|
|
301
|
-
|
|
521
|
+
el4.textContent = "";
|
|
302
522
|
}
|
|
303
|
-
for (const
|
|
304
|
-
|
|
523
|
+
for (const el4 of container.querySelectorAll(".has-error")) {
|
|
524
|
+
el4.classList.remove("has-error");
|
|
305
525
|
}
|
|
306
526
|
}
|
|
307
527
|
function applyErrors(container, errors) {
|
|
308
528
|
clearFieldErrors(container);
|
|
529
|
+
let unplaced = 0;
|
|
309
530
|
for (const err of errors) {
|
|
310
|
-
|
|
531
|
+
const field = err.path.split(".")[0] ?? err.path;
|
|
532
|
+
if (!setFieldError(container, `nodii-${field}`, err.message)) unplaced++;
|
|
311
533
|
}
|
|
534
|
+
return unplaced;
|
|
535
|
+
}
|
|
536
|
+
function assertHostValues(options) {
|
|
537
|
+
if (options.privacyUrl !== void 0 && !isNavigableUrl(options.privacyUrl)) {
|
|
538
|
+
throw new OnboardingValidationError(
|
|
539
|
+
"mountContactForm: invalid privacyUrl: must be an http(s) URL, absolute or relative"
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
const shape = contactFormSchema.shape;
|
|
543
|
+
for (const [name, schema, value] of [
|
|
544
|
+
["intakeKind", shape.intakeKind, options.intakeKind],
|
|
545
|
+
["attribution", shape.attribution, options.attribution]
|
|
546
|
+
]) {
|
|
547
|
+
if (value === void 0) continue;
|
|
548
|
+
const parsed = schema.safeParse(value);
|
|
549
|
+
if (!parsed.success) {
|
|
550
|
+
const issue = parsed.error.issues[0];
|
|
551
|
+
throw new OnboardingValidationError(
|
|
552
|
+
`mountContactForm: invalid ${[name, ...issue?.path ?? []].join(".")}: ${issue?.message ?? "invalid"}`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
var DEFAULT_SPEC_REFRESH_MS = 5 * 60 * 1e3;
|
|
558
|
+
function privacyDocumentUrl(spec) {
|
|
559
|
+
const url = spec?.consentPack?.documents.find(
|
|
560
|
+
(d) => d.docType === "privacy"
|
|
561
|
+
)?.downloadUrl;
|
|
562
|
+
return url && isNavigableUrl(url) ? url : void 0;
|
|
563
|
+
}
|
|
564
|
+
function choicesOf(spec, key) {
|
|
565
|
+
const field = spec?.forms.contact.fields.find((f) => f.key === key);
|
|
566
|
+
return (field?.options ?? []).map((option) => {
|
|
567
|
+
const value = optionValue(option);
|
|
568
|
+
const label = option !== null && typeof option === "object" && typeof option.label === "string" ? option.label : value;
|
|
569
|
+
return { value, label };
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
function isRequired(spec, key) {
|
|
573
|
+
return spec?.forms.contact.fields.find((f) => f.key === key)?.required === true;
|
|
574
|
+
}
|
|
575
|
+
function labelOf(spec, key, fallback) {
|
|
576
|
+
const label = spec?.forms.contact.fields.find((f) => f.key === key)?.label;
|
|
577
|
+
return label?.trim() ? label : fallback;
|
|
312
578
|
}
|
|
313
579
|
function mountContactForm(host, options) {
|
|
580
|
+
assertHostValues(options);
|
|
314
581
|
injectWidgetStyles();
|
|
315
|
-
const
|
|
316
|
-
|
|
582
|
+
const spec = options.spec;
|
|
583
|
+
const interests = choicesOf(spec, "interestModules");
|
|
584
|
+
const sizes = choicesOf(spec, "sizeBand");
|
|
585
|
+
const interestRequired = interests.length > 0 && isRequired(spec, "interestModules");
|
|
586
|
+
const privacyUrl = options.privacyUrl ?? privacyDocumentUrl(spec);
|
|
587
|
+
const siteKey = options.siteKey ?? spec?.botChallenge?.siteKey;
|
|
588
|
+
const formControl = createContactFormState({
|
|
589
|
+
requireInterest: interestRequired
|
|
317
590
|
});
|
|
591
|
+
formControl.setValue("intakeKind", options.intakeKind ?? "enquiry");
|
|
592
|
+
if (options.attribution) {
|
|
593
|
+
formControl.setValue("attribution", options.attribution);
|
|
594
|
+
}
|
|
595
|
+
if (siteKey) {
|
|
596
|
+
loadRecaptcha(siteKey).catch(() => {
|
|
597
|
+
});
|
|
598
|
+
}
|
|
318
599
|
const wrapper = el("div", { class: "nodii-onboarding-widget" });
|
|
319
|
-
const
|
|
600
|
+
const companyInput = el("input", {
|
|
320
601
|
type: "text",
|
|
321
|
-
id: "nodii-
|
|
322
|
-
name: "
|
|
602
|
+
id: "nodii-companyName",
|
|
603
|
+
name: "companyName",
|
|
604
|
+
class: "nodii-onboarding-input",
|
|
605
|
+
autocomplete: "organization",
|
|
606
|
+
maxlength: "200",
|
|
607
|
+
required: "required"
|
|
608
|
+
});
|
|
609
|
+
const contactInput = el("input", {
|
|
610
|
+
type: "text",
|
|
611
|
+
id: "nodii-contactName",
|
|
612
|
+
name: "contactName",
|
|
323
613
|
class: "nodii-onboarding-input",
|
|
324
|
-
placeholder: "Your name",
|
|
325
614
|
autocomplete: "name",
|
|
326
615
|
maxlength: "200",
|
|
327
616
|
required: "required"
|
|
@@ -340,8 +629,8 @@ function mountContactForm(host, options) {
|
|
|
340
629
|
id: "nodii-phone",
|
|
341
630
|
name: "phone",
|
|
342
631
|
class: "nodii-onboarding-input",
|
|
343
|
-
|
|
344
|
-
|
|
632
|
+
autocomplete: "tel",
|
|
633
|
+
maxlength: "40"
|
|
345
634
|
});
|
|
346
635
|
const messageInput = el("textarea", {
|
|
347
636
|
id: "nodii-message",
|
|
@@ -351,6 +640,34 @@ function mountContactForm(host, options) {
|
|
|
351
640
|
maxlength: "4000",
|
|
352
641
|
rows: "4"
|
|
353
642
|
});
|
|
643
|
+
const interestBoxes = interests.map(
|
|
644
|
+
(choice) => el("input", {
|
|
645
|
+
type: "checkbox",
|
|
646
|
+
name: "interestModules",
|
|
647
|
+
value: choice.value
|
|
648
|
+
})
|
|
649
|
+
);
|
|
650
|
+
const sizeSelect = el(
|
|
651
|
+
"select",
|
|
652
|
+
{ id: "nodii-sizeBand", name: "sizeBand", class: "nodii-onboarding-input" },
|
|
653
|
+
[
|
|
654
|
+
el("option", { value: "" }, ["Choose\u2026"]),
|
|
655
|
+
...sizes.map(
|
|
656
|
+
(choice) => el("option", { value: choice.value }, [choice.label])
|
|
657
|
+
)
|
|
658
|
+
]
|
|
659
|
+
);
|
|
660
|
+
const privacyBox = el("input", {
|
|
661
|
+
type: "checkbox",
|
|
662
|
+
id: "nodii-privacyNoticeAcknowledged",
|
|
663
|
+
name: "privacyNoticeAcknowledged",
|
|
664
|
+
required: "required"
|
|
665
|
+
});
|
|
666
|
+
const marketingBox = el("input", {
|
|
667
|
+
type: "checkbox",
|
|
668
|
+
id: "nodii-marketingOptIn",
|
|
669
|
+
name: "marketingOptIn"
|
|
670
|
+
});
|
|
354
671
|
const serverErrorEl = el("div", {
|
|
355
672
|
class: "nodii-onboarding-server-error",
|
|
356
673
|
role: "alert",
|
|
@@ -374,60 +691,165 @@ function mountContactForm(host, options) {
|
|
|
374
691
|
},
|
|
375
692
|
[options.submitLabel ?? "Get in touch"]
|
|
376
693
|
);
|
|
377
|
-
const recaptchaNotice = el(
|
|
378
|
-
"p",
|
|
379
|
-
{ class: "nodii-onboarding-recaptcha-notice" },
|
|
380
|
-
[
|
|
381
|
-
"Protected by reCAPTCHA. ",
|
|
382
|
-
Object.assign(
|
|
383
|
-
el("a", {
|
|
384
|
-
href: "https://policies.google.com/privacy",
|
|
385
|
-
target: "_blank",
|
|
386
|
-
rel: "noopener noreferrer"
|
|
387
|
-
}),
|
|
388
|
-
{ textContent: "Privacy" }
|
|
389
|
-
),
|
|
390
|
-
" & ",
|
|
391
|
-
Object.assign(
|
|
392
|
-
el("a", {
|
|
393
|
-
href: "https://policies.google.com/terms",
|
|
394
|
-
target: "_blank",
|
|
395
|
-
rel: "noopener noreferrer"
|
|
396
|
-
}),
|
|
397
|
-
{ textContent: "Terms" }
|
|
398
|
-
)
|
|
399
|
-
]
|
|
400
|
-
);
|
|
401
694
|
const form = el("form", {
|
|
402
695
|
novalidate: "true",
|
|
403
696
|
"aria-label": "Contact us"
|
|
404
697
|
});
|
|
405
|
-
form.appendChild(createFieldEl("nodii-name", "Name", nameInput, true));
|
|
406
|
-
form.appendChild(createFieldEl("nodii-email", "Email", emailInput, true));
|
|
407
698
|
form.appendChild(
|
|
408
|
-
createFieldEl(
|
|
699
|
+
createFieldEl(
|
|
700
|
+
"nodii-companyName",
|
|
701
|
+
labelOf(spec, "companyName", "Company name"),
|
|
702
|
+
companyInput,
|
|
703
|
+
true
|
|
704
|
+
)
|
|
409
705
|
);
|
|
410
706
|
form.appendChild(
|
|
411
|
-
createFieldEl(
|
|
707
|
+
createFieldEl(
|
|
708
|
+
"nodii-contactName",
|
|
709
|
+
labelOf(spec, "contactName", "Your name"),
|
|
710
|
+
contactInput,
|
|
711
|
+
true
|
|
712
|
+
)
|
|
713
|
+
);
|
|
714
|
+
form.appendChild(
|
|
715
|
+
createFieldEl(
|
|
716
|
+
"nodii-email",
|
|
717
|
+
labelOf(spec, "email", "Email"),
|
|
718
|
+
emailInput,
|
|
719
|
+
true
|
|
720
|
+
)
|
|
721
|
+
);
|
|
722
|
+
form.appendChild(
|
|
723
|
+
createFieldEl(
|
|
724
|
+
"nodii-phone",
|
|
725
|
+
`${labelOf(spec, "phone", "Phone")} (optional)`,
|
|
726
|
+
phoneInput,
|
|
727
|
+
false
|
|
728
|
+
)
|
|
729
|
+
);
|
|
730
|
+
if (interests.length > 0) {
|
|
731
|
+
form.appendChild(
|
|
732
|
+
el(
|
|
733
|
+
"fieldset",
|
|
734
|
+
{ id: "nodii-interestModules", class: "nodii-onboarding-field" },
|
|
735
|
+
[
|
|
736
|
+
el(
|
|
737
|
+
"legend",
|
|
738
|
+
{
|
|
739
|
+
class: `nodii-onboarding-label${interestRequired ? " required" : ""}`
|
|
740
|
+
},
|
|
741
|
+
[labelOf(spec, "interestModules", "What are you interested in?")]
|
|
742
|
+
),
|
|
743
|
+
el(
|
|
744
|
+
"div",
|
|
745
|
+
{ class: "nodii-onboarding-choices" },
|
|
746
|
+
interests.map(
|
|
747
|
+
(choice, i) => el("label", { class: "nodii-onboarding-choice" }, [
|
|
748
|
+
interestBoxes[i],
|
|
749
|
+
choice.label
|
|
750
|
+
])
|
|
751
|
+
)
|
|
752
|
+
),
|
|
753
|
+
errorSlot("nodii-interestModules")
|
|
754
|
+
]
|
|
755
|
+
)
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
if (sizes.length > 0) {
|
|
759
|
+
form.appendChild(
|
|
760
|
+
createFieldEl(
|
|
761
|
+
"nodii-sizeBand",
|
|
762
|
+
`${labelOf(spec, "sizeBand", "Team size")} (optional)`,
|
|
763
|
+
sizeSelect,
|
|
764
|
+
false
|
|
765
|
+
)
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
form.appendChild(
|
|
769
|
+
createFieldEl(
|
|
770
|
+
"nodii-message",
|
|
771
|
+
`${labelOf(spec, "message", "Message")} (optional)`,
|
|
772
|
+
messageInput,
|
|
773
|
+
false
|
|
774
|
+
)
|
|
775
|
+
);
|
|
776
|
+
const privacyLabel = ["I have read the "];
|
|
777
|
+
let privacyLink = null;
|
|
778
|
+
if (privacyUrl) {
|
|
779
|
+
privacyLink = Object.assign(
|
|
780
|
+
el("a", {
|
|
781
|
+
href: privacyUrl,
|
|
782
|
+
target: "_blank",
|
|
783
|
+
rel: "noopener noreferrer"
|
|
784
|
+
}),
|
|
785
|
+
{ textContent: "privacy notice" }
|
|
786
|
+
);
|
|
787
|
+
privacyLabel.push(privacyLink);
|
|
788
|
+
} else {
|
|
789
|
+
privacyLabel.push("privacy notice");
|
|
790
|
+
}
|
|
791
|
+
form.appendChild(
|
|
792
|
+
el("div", { class: "nodii-onboarding-field" }, [
|
|
793
|
+
el("label", { class: "nodii-onboarding-consent-row" }, [
|
|
794
|
+
privacyBox,
|
|
795
|
+
el("span", {}, privacyLabel)
|
|
796
|
+
]),
|
|
797
|
+
errorSlot("nodii-privacyNoticeAcknowledged")
|
|
798
|
+
])
|
|
799
|
+
);
|
|
800
|
+
form.appendChild(
|
|
801
|
+
el("label", { class: "nodii-onboarding-consent-row" }, [
|
|
802
|
+
marketingBox,
|
|
803
|
+
el("span", {}, [
|
|
804
|
+
labelOf(spec, "marketingOptIn", "Send me occasional product updates")
|
|
805
|
+
])
|
|
806
|
+
])
|
|
412
807
|
);
|
|
413
808
|
form.appendChild(serverErrorEl);
|
|
414
809
|
form.appendChild(submitBtn);
|
|
415
|
-
|
|
810
|
+
if (siteKey) {
|
|
811
|
+
form.appendChild(
|
|
812
|
+
el("p", { class: "nodii-onboarding-recaptcha-notice" }, [
|
|
813
|
+
"Protected by reCAPTCHA. ",
|
|
814
|
+
Object.assign(
|
|
815
|
+
el("a", {
|
|
816
|
+
href: "https://policies.google.com/privacy",
|
|
817
|
+
target: "_blank",
|
|
818
|
+
rel: "noopener noreferrer"
|
|
819
|
+
}),
|
|
820
|
+
{ textContent: "Privacy" }
|
|
821
|
+
),
|
|
822
|
+
" & ",
|
|
823
|
+
Object.assign(
|
|
824
|
+
el("a", {
|
|
825
|
+
href: "https://policies.google.com/terms",
|
|
826
|
+
target: "_blank",
|
|
827
|
+
rel: "noopener noreferrer"
|
|
828
|
+
}),
|
|
829
|
+
{ textContent: "Terms" }
|
|
830
|
+
)
|
|
831
|
+
])
|
|
832
|
+
);
|
|
833
|
+
}
|
|
416
834
|
wrapper.appendChild(successEl);
|
|
417
835
|
wrapper.appendChild(form);
|
|
418
836
|
host.appendChild(wrapper);
|
|
419
|
-
|
|
420
|
-
formControl.setValue("
|
|
421
|
-
|
|
422
|
-
emailInput.addEventListener("input", () => {
|
|
837
|
+
function syncValues() {
|
|
838
|
+
formControl.setValue("companyName", companyInput.value);
|
|
839
|
+
formControl.setValue("contactName", contactInput.value);
|
|
423
840
|
formControl.setValue("email", emailInput.value);
|
|
424
|
-
});
|
|
425
|
-
phoneInput.addEventListener("input", () => {
|
|
426
841
|
formControl.setValue("phone", phoneInput.value || void 0);
|
|
427
|
-
});
|
|
428
|
-
messageInput.addEventListener("input", () => {
|
|
429
842
|
formControl.setValue("message", messageInput.value || void 0);
|
|
430
|
-
|
|
843
|
+
formControl.setValue(
|
|
844
|
+
"interestModules",
|
|
845
|
+
interestBoxes.filter((box) => box.checked).map((box) => box.value)
|
|
846
|
+
);
|
|
847
|
+
formControl.setValue("sizeBand", sizeSelect.value || void 0);
|
|
848
|
+
formControl.setValue("privacyNoticeAcknowledged", privacyBox.checked);
|
|
849
|
+
formControl.setValue("marketingOptIn", marketingBox.checked);
|
|
850
|
+
}
|
|
851
|
+
form.addEventListener("input", syncValues);
|
|
852
|
+
form.addEventListener("change", syncValues);
|
|
431
853
|
const submitLabel = options.submitLabel ?? "Get in touch";
|
|
432
854
|
function renderSubmitButton(submitting) {
|
|
433
855
|
while (submitBtn.firstChild) {
|
|
@@ -445,9 +867,10 @@ function mountContactForm(host, options) {
|
|
|
445
867
|
const unsubscribe = formControl.subscribe((state) => {
|
|
446
868
|
submitBtn.disabled = state.status === "submitting";
|
|
447
869
|
renderSubmitButton(state.status === "submitting");
|
|
448
|
-
applyErrors(wrapper, state.errors);
|
|
449
|
-
|
|
450
|
-
|
|
870
|
+
const unplaced = applyErrors(wrapper, state.errors);
|
|
871
|
+
const formError = state.serverError ?? (unplaced > 0 ? "This form can\u2019t be sent right now." : null);
|
|
872
|
+
if (formError) {
|
|
873
|
+
serverErrorEl.textContent = formError;
|
|
451
874
|
serverErrorEl.style.display = "";
|
|
452
875
|
} else {
|
|
453
876
|
serverErrorEl.style.display = "none";
|
|
@@ -461,10 +884,7 @@ function mountContactForm(host, options) {
|
|
|
461
884
|
let lastFingerprint = null;
|
|
462
885
|
form.addEventListener("submit", async (event) => {
|
|
463
886
|
event.preventDefault();
|
|
464
|
-
|
|
465
|
-
formControl.setValue("email", emailInput.value);
|
|
466
|
-
formControl.setValue("phone", phoneInput.value || void 0);
|
|
467
|
-
formControl.setValue("message", messageInput.value || void 0);
|
|
887
|
+
syncValues();
|
|
468
888
|
const valid = formControl.validate();
|
|
469
889
|
if (!valid) return;
|
|
470
890
|
formControl.setSubmitting();
|
|
@@ -475,14 +895,18 @@ function mountContactForm(host, options) {
|
|
|
475
895
|
lastFingerprint = fingerprint;
|
|
476
896
|
}
|
|
477
897
|
let firstToken;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
898
|
+
if (siteKey) {
|
|
899
|
+
try {
|
|
900
|
+
firstToken = await executeRecaptcha(siteKey, "contact_submit", {
|
|
901
|
+
timeoutMs: options.timeoutMs
|
|
902
|
+
});
|
|
903
|
+
} catch (recaptchaErr) {
|
|
904
|
+
formControl.setError(
|
|
905
|
+
"Could not load bot protection. Please try again."
|
|
906
|
+
);
|
|
907
|
+
options.onError?.(recaptchaErr);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
486
910
|
}
|
|
487
911
|
let usedFirst = false;
|
|
488
912
|
const tokenProvider = async () => {
|
|
@@ -490,14 +914,14 @@ function mountContactForm(host, options) {
|
|
|
490
914
|
usedFirst = true;
|
|
491
915
|
return firstToken;
|
|
492
916
|
}
|
|
493
|
-
return executeRecaptcha(
|
|
917
|
+
return executeRecaptcha(siteKey, "contact_submit", {
|
|
494
918
|
timeoutMs: options.timeoutMs
|
|
495
919
|
});
|
|
496
920
|
};
|
|
497
921
|
try {
|
|
498
922
|
await submitLead(values, {
|
|
499
923
|
baseUrl: options.baseUrl,
|
|
500
|
-
botToken: tokenProvider,
|
|
924
|
+
...siteKey ? { botToken: tokenProvider } : {},
|
|
501
925
|
idempotencyKey,
|
|
502
926
|
fetch: options.fetch,
|
|
503
927
|
timeoutMs: options.timeoutMs
|
|
@@ -524,32 +948,286 @@ function mountContactForm(host, options) {
|
|
|
524
948
|
}
|
|
525
949
|
}
|
|
526
950
|
});
|
|
951
|
+
const refreshMs = options.specRefreshMs ?? DEFAULT_SPEC_REFRESH_MS;
|
|
952
|
+
let refreshing = false;
|
|
953
|
+
const specRefresh = privacyLink && !options.privacyUrl && refreshMs > 0 ? setInterval(() => {
|
|
954
|
+
if (refreshing || !wrapper.isConnected) return;
|
|
955
|
+
refreshing = true;
|
|
956
|
+
fetchOnboardingSpec({
|
|
957
|
+
baseUrl: options.baseUrl,
|
|
958
|
+
onboarder: options.onboarder,
|
|
959
|
+
fetch: options.fetch,
|
|
960
|
+
timeoutMs: options.timeoutMs,
|
|
961
|
+
cache: new OnboardingSpecCache()
|
|
962
|
+
}).then(
|
|
963
|
+
(fresh) => {
|
|
964
|
+
const url = privacyDocumentUrl(fresh);
|
|
965
|
+
if (url && privacyLink) privacyLink.href = url;
|
|
966
|
+
},
|
|
967
|
+
() => {
|
|
968
|
+
}
|
|
969
|
+
).finally(() => {
|
|
970
|
+
refreshing = false;
|
|
971
|
+
});
|
|
972
|
+
}, refreshMs) : void 0;
|
|
527
973
|
return {
|
|
528
974
|
unmount() {
|
|
975
|
+
clearInterval(specRefresh);
|
|
529
976
|
unsubscribe();
|
|
530
977
|
host.removeChild(wrapper);
|
|
531
978
|
},
|
|
532
979
|
reset() {
|
|
533
980
|
idempotencyKey = null;
|
|
534
981
|
lastFingerprint = null;
|
|
982
|
+
form.reset();
|
|
535
983
|
formControl.reset();
|
|
984
|
+
formControl.setValue("intakeKind", options.intakeKind ?? "enquiry");
|
|
985
|
+
if (options.attribution) {
|
|
986
|
+
formControl.setValue("attribution", options.attribution);
|
|
987
|
+
}
|
|
536
988
|
form.style.display = "";
|
|
537
989
|
successEl.style.display = "none";
|
|
538
|
-
nameInput.value = "";
|
|
539
|
-
emailInput.value = "";
|
|
540
|
-
phoneInput.value = "";
|
|
541
|
-
messageInput.value = "";
|
|
542
990
|
}
|
|
543
991
|
};
|
|
544
992
|
}
|
|
545
993
|
|
|
994
|
+
// src/widget/consent-step.ts
|
|
995
|
+
var CONSENT_ID_PREFIX = "nodii-consent-doc-";
|
|
996
|
+
function el2(tag, attrs = {}, children = []) {
|
|
997
|
+
const element = document.createElement(tag);
|
|
998
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
999
|
+
element.setAttribute(key, value);
|
|
1000
|
+
}
|
|
1001
|
+
for (const child of children) {
|
|
1002
|
+
if (typeof child === "string") {
|
|
1003
|
+
element.appendChild(document.createTextNode(child));
|
|
1004
|
+
} else {
|
|
1005
|
+
element.appendChild(child);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
return element;
|
|
1009
|
+
}
|
|
1010
|
+
function consentDocElementId(key) {
|
|
1011
|
+
return `${CONSENT_ID_PREFIX}${key.replace(/[^A-Za-z0-9_-]/g, "-")}`;
|
|
1012
|
+
}
|
|
1013
|
+
function buildAcceptRow(control, doc) {
|
|
1014
|
+
const id = consentDocElementId(doc.key);
|
|
1015
|
+
const checkbox = el2("input", {
|
|
1016
|
+
type: "checkbox",
|
|
1017
|
+
id,
|
|
1018
|
+
class: "nodii-onboarding-consent-accept"
|
|
1019
|
+
});
|
|
1020
|
+
checkbox.checked = doc.accepted;
|
|
1021
|
+
checkbox.disabled = !doc.gateSatisfied;
|
|
1022
|
+
checkbox.addEventListener("change", () => {
|
|
1023
|
+
control.setAccepted(doc.key, checkbox.checked);
|
|
1024
|
+
checkbox.checked = control.getState().documents.find((d) => d.key === doc.key)?.accepted ?? false;
|
|
1025
|
+
});
|
|
1026
|
+
const labelText = doc.acceptanceLevel === "checkbox" ? `I accept the ${doc.doc.title}` : `I have reviewed and accept the ${doc.doc.title}`;
|
|
1027
|
+
const label = el2(
|
|
1028
|
+
"label",
|
|
1029
|
+
{ for: id, class: "nodii-onboarding-consent-accept-label" },
|
|
1030
|
+
[labelText]
|
|
1031
|
+
);
|
|
1032
|
+
if (doc.required) {
|
|
1033
|
+
label.appendChild(
|
|
1034
|
+
el2("span", { class: "nodii-onboarding-consent-required" }, [" *"])
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
const row = el2("div", { class: "nodii-onboarding-consent-accept-row" }, [
|
|
1038
|
+
checkbox,
|
|
1039
|
+
label
|
|
1040
|
+
]);
|
|
1041
|
+
const errorEl = el2("div", {
|
|
1042
|
+
class: "nodii-onboarding-field-error nodii-onboarding-consent-error",
|
|
1043
|
+
id: `${id}-error`,
|
|
1044
|
+
role: "alert",
|
|
1045
|
+
"aria-live": "polite"
|
|
1046
|
+
});
|
|
1047
|
+
return { row, errorEl };
|
|
1048
|
+
}
|
|
1049
|
+
function buildOpenAffordance(control, doc) {
|
|
1050
|
+
const id = consentDocElementId(doc.key);
|
|
1051
|
+
const openLink = el2(
|
|
1052
|
+
"a",
|
|
1053
|
+
{
|
|
1054
|
+
class: "nodii-onboarding-consent-open",
|
|
1055
|
+
id: `${id}-open`,
|
|
1056
|
+
href: doc.doc.downloadUrl,
|
|
1057
|
+
target: "_blank",
|
|
1058
|
+
rel: "noopener noreferrer"
|
|
1059
|
+
},
|
|
1060
|
+
[`Open ${doc.doc.title}`]
|
|
1061
|
+
);
|
|
1062
|
+
const markOpened = () => control.markOpened(doc.key);
|
|
1063
|
+
openLink.addEventListener("click", markOpened);
|
|
1064
|
+
openLink.addEventListener("keydown", (event) => {
|
|
1065
|
+
if (event.key === "Enter") markOpened();
|
|
1066
|
+
});
|
|
1067
|
+
return el2("div", { class: "nodii-onboarding-consent-open-row" }, [openLink]);
|
|
1068
|
+
}
|
|
1069
|
+
function buildGateAffordance(control, doc, opts) {
|
|
1070
|
+
const id = consentDocElementId(doc.key);
|
|
1071
|
+
if (doc.acceptanceLevel === "open_then_accept") {
|
|
1072
|
+
return buildOpenAffordance(control, doc);
|
|
1073
|
+
}
|
|
1074
|
+
if (doc.acceptanceLevel === "checkbox") {
|
|
1075
|
+
return el2("div", { class: "nodii-onboarding-consent-preview-row" }, [
|
|
1076
|
+
el2(
|
|
1077
|
+
"a",
|
|
1078
|
+
{
|
|
1079
|
+
class: "nodii-onboarding-consent-preview",
|
|
1080
|
+
id: `${id}-preview`,
|
|
1081
|
+
href: doc.doc.downloadUrl,
|
|
1082
|
+
target: "_blank",
|
|
1083
|
+
rel: "noopener noreferrer"
|
|
1084
|
+
},
|
|
1085
|
+
[`View ${doc.doc.title}`]
|
|
1086
|
+
)
|
|
1087
|
+
]);
|
|
1088
|
+
}
|
|
1089
|
+
const container = el2("div", {
|
|
1090
|
+
class: "nodii-onboarding-consent-gate",
|
|
1091
|
+
id: `${id}-gate`
|
|
1092
|
+
});
|
|
1093
|
+
container.appendChild(
|
|
1094
|
+
el2("div", { class: "nodii-onboarding-consent-doc-loading" }, [
|
|
1095
|
+
"Loading document\u2026"
|
|
1096
|
+
])
|
|
1097
|
+
);
|
|
1098
|
+
const importViewer = opts.importViewer ?? (() => import('./render-EWQXF73I.js'));
|
|
1099
|
+
const loadViewer = () => {
|
|
1100
|
+
void (async () => {
|
|
1101
|
+
try {
|
|
1102
|
+
const { renderConsentDocument } = await importViewer();
|
|
1103
|
+
await renderConsentDocument(container, doc.doc, control, {
|
|
1104
|
+
consentKey: doc.key,
|
|
1105
|
+
fetch: opts.fetch,
|
|
1106
|
+
timeoutMs: opts.timeoutMs
|
|
1107
|
+
});
|
|
1108
|
+
} catch {
|
|
1109
|
+
while (container.firstChild) {
|
|
1110
|
+
container.removeChild(container.firstChild);
|
|
1111
|
+
}
|
|
1112
|
+
const wrap = el2("div", {
|
|
1113
|
+
class: "nodii-onboarding-consent-unrenderable",
|
|
1114
|
+
role: "alert"
|
|
1115
|
+
});
|
|
1116
|
+
wrap.appendChild(
|
|
1117
|
+
el2(
|
|
1118
|
+
"p",
|
|
1119
|
+
{
|
|
1120
|
+
// DISTINCT id (NOT `${id}-error`): buildAcceptRow already owns
|
|
1121
|
+
// `${id}-error`, and updateConsentStep's querySelector would match
|
|
1122
|
+
// THIS paragraph first and blank it on the next state change,
|
|
1123
|
+
// silently wiping the compliance message. A unique id keeps this
|
|
1124
|
+
// load-failure error persistent (P1b).
|
|
1125
|
+
class: "nodii-onboarding-field-error nodii-onboarding-consent-error",
|
|
1126
|
+
id: `${id}-load-error`
|
|
1127
|
+
},
|
|
1128
|
+
[
|
|
1129
|
+
`\u201C${doc.doc.title}\u201D could not be loaded, so it cannot be accepted yet. Please retry.`
|
|
1130
|
+
]
|
|
1131
|
+
)
|
|
1132
|
+
);
|
|
1133
|
+
const retry = el2(
|
|
1134
|
+
"button",
|
|
1135
|
+
{ type: "button", class: "nodii-onboarding-consent-retry" },
|
|
1136
|
+
["Retry loading document"]
|
|
1137
|
+
);
|
|
1138
|
+
retry.addEventListener("click", loadViewer);
|
|
1139
|
+
wrap.appendChild(retry);
|
|
1140
|
+
wrap.appendChild(
|
|
1141
|
+
el2(
|
|
1142
|
+
"a",
|
|
1143
|
+
{
|
|
1144
|
+
class: "nodii-onboarding-consent-open",
|
|
1145
|
+
href: doc.doc.downloadUrl,
|
|
1146
|
+
target: "_blank",
|
|
1147
|
+
rel: "noopener noreferrer"
|
|
1148
|
+
},
|
|
1149
|
+
[`Open ${doc.doc.title} in a new tab`]
|
|
1150
|
+
)
|
|
1151
|
+
);
|
|
1152
|
+
container.appendChild(wrap);
|
|
1153
|
+
}
|
|
1154
|
+
})();
|
|
1155
|
+
};
|
|
1156
|
+
loadViewer();
|
|
1157
|
+
return container;
|
|
1158
|
+
}
|
|
1159
|
+
function buildConsentCard(control, doc, stale, opts) {
|
|
1160
|
+
const card = el2("div", {
|
|
1161
|
+
class: `nodii-onboarding-consent-card${stale ? " is-stale" : ""}`,
|
|
1162
|
+
"data-consent-key": doc.key
|
|
1163
|
+
});
|
|
1164
|
+
card.appendChild(
|
|
1165
|
+
el2("h4", { class: "nodii-onboarding-consent-title" }, [doc.doc.title])
|
|
1166
|
+
);
|
|
1167
|
+
if (stale) {
|
|
1168
|
+
card.appendChild(
|
|
1169
|
+
el2(
|
|
1170
|
+
"div",
|
|
1171
|
+
{ class: "nodii-onboarding-consent-stale-tag", role: "status" },
|
|
1172
|
+
["Updated \u2014 please review and re-accept."]
|
|
1173
|
+
)
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
card.appendChild(buildGateAffordance(control, doc, opts));
|
|
1177
|
+
const { row, errorEl } = buildAcceptRow(control, doc);
|
|
1178
|
+
card.appendChild(row);
|
|
1179
|
+
card.appendChild(errorEl);
|
|
1180
|
+
return card;
|
|
1181
|
+
}
|
|
1182
|
+
function renderConsentStep(host, control, state, staleKeys = [], opts = {}) {
|
|
1183
|
+
while (host.firstChild) host.removeChild(host.firstChild);
|
|
1184
|
+
const staleSet = new Set(staleKeys);
|
|
1185
|
+
host.appendChild(
|
|
1186
|
+
el2("h3", { class: "nodii-onboarding-stage-title" }, [
|
|
1187
|
+
"Review & accept documents"
|
|
1188
|
+
])
|
|
1189
|
+
);
|
|
1190
|
+
host.appendChild(
|
|
1191
|
+
el2("p", { class: "nodii-onboarding-consent-intro" }, [
|
|
1192
|
+
"Please review each document below and accept to continue."
|
|
1193
|
+
])
|
|
1194
|
+
);
|
|
1195
|
+
for (const doc of state.documents) {
|
|
1196
|
+
host.appendChild(
|
|
1197
|
+
buildConsentCard(control, doc, staleSet.has(doc.key), opts)
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function updateConsentStep(host, state, consentErrors) {
|
|
1202
|
+
const errorByKey = new Map(consentErrors.map((e) => [e.path, e.message]));
|
|
1203
|
+
for (const doc of state.documents) {
|
|
1204
|
+
const id = consentDocElementId(doc.key);
|
|
1205
|
+
const checkbox = host.querySelector(
|
|
1206
|
+
idSelector(id)
|
|
1207
|
+
);
|
|
1208
|
+
if (checkbox) {
|
|
1209
|
+
checkbox.disabled = !doc.gateSatisfied;
|
|
1210
|
+
checkbox.checked = doc.accepted;
|
|
1211
|
+
}
|
|
1212
|
+
const errorNode = host.querySelector(idSelector(`${id}-error`));
|
|
1213
|
+
if (errorNode) errorNode.textContent = errorByKey.get(doc.key) ?? "";
|
|
1214
|
+
const card = host.querySelector(
|
|
1215
|
+
`[data-consent-key="${cssEscape(doc.key)}"]`
|
|
1216
|
+
);
|
|
1217
|
+
if (card) {
|
|
1218
|
+
if (errorByKey.has(doc.key)) card.classList.add("has-error");
|
|
1219
|
+
else card.classList.remove("has-error");
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
546
1224
|
// src/widget/signup-form.ts
|
|
547
1225
|
var STAGE_TITLES = {
|
|
548
1226
|
lead: "About you",
|
|
549
1227
|
prospect: "Your business",
|
|
550
1228
|
activation: "Choose your services"
|
|
551
1229
|
};
|
|
552
|
-
function
|
|
1230
|
+
function el3(tag, attrs = {}, children = []) {
|
|
553
1231
|
const element = document.createElement(tag);
|
|
554
1232
|
for (const [key, value] of Object.entries(attrs)) {
|
|
555
1233
|
element.setAttribute(key, value);
|
|
@@ -574,7 +1252,7 @@ function buildFieldInput(field, currentValue, onChange) {
|
|
|
574
1252
|
const id = `nodii-f-${field.key}`;
|
|
575
1253
|
switch (field.type) {
|
|
576
1254
|
case "textarea": {
|
|
577
|
-
const input =
|
|
1255
|
+
const input = el3("textarea", {
|
|
578
1256
|
id,
|
|
579
1257
|
class: "nodii-onboarding-textarea",
|
|
580
1258
|
rows: "4"
|
|
@@ -585,7 +1263,7 @@ function buildFieldInput(field, currentValue, onChange) {
|
|
|
585
1263
|
}
|
|
586
1264
|
case "checkbox":
|
|
587
1265
|
case "boolean": {
|
|
588
|
-
const input =
|
|
1266
|
+
const input = el3("input", {
|
|
589
1267
|
type: "checkbox",
|
|
590
1268
|
id
|
|
591
1269
|
});
|
|
@@ -595,14 +1273,14 @@ function buildFieldInput(field, currentValue, onChange) {
|
|
|
595
1273
|
}
|
|
596
1274
|
case "select":
|
|
597
1275
|
case "radio": {
|
|
598
|
-
const select =
|
|
1276
|
+
const select = el3("select", {
|
|
599
1277
|
id,
|
|
600
1278
|
class: "nodii-onboarding-input"
|
|
601
1279
|
});
|
|
602
|
-
select.appendChild(
|
|
1280
|
+
select.appendChild(el3("option", { value: "" }, ["\u2014 select \u2014"]));
|
|
603
1281
|
for (const opt of field.options ?? []) {
|
|
604
1282
|
const value = optionValue(opt);
|
|
605
|
-
const optionEl =
|
|
1283
|
+
const optionEl = el3("option", { value }, [optionLabel(opt)]);
|
|
606
1284
|
select.appendChild(optionEl);
|
|
607
1285
|
}
|
|
608
1286
|
if (typeof currentValue === "string") select.value = currentValue;
|
|
@@ -610,7 +1288,7 @@ function buildFieldInput(field, currentValue, onChange) {
|
|
|
610
1288
|
return select;
|
|
611
1289
|
}
|
|
612
1290
|
case "multiselect": {
|
|
613
|
-
const select =
|
|
1291
|
+
const select = el3("select", {
|
|
614
1292
|
id,
|
|
615
1293
|
class: "nodii-onboarding-input",
|
|
616
1294
|
multiple: "multiple"
|
|
@@ -618,7 +1296,7 @@ function buildFieldInput(field, currentValue, onChange) {
|
|
|
618
1296
|
const current = Array.isArray(currentValue) ? currentValue.map(String) : [];
|
|
619
1297
|
for (const opt of field.options ?? []) {
|
|
620
1298
|
const value = optionValue(opt);
|
|
621
|
-
const optionEl =
|
|
1299
|
+
const optionEl = el3("option", { value }, [
|
|
622
1300
|
optionLabel(opt)
|
|
623
1301
|
]);
|
|
624
1302
|
if (current.includes(value)) optionEl.selected = true;
|
|
@@ -635,7 +1313,7 @@ function buildFieldInput(field, currentValue, onChange) {
|
|
|
635
1313
|
}
|
|
636
1314
|
default: {
|
|
637
1315
|
const typeAttr = field.type === "email" || field.type === "tel" || field.type === "number" || field.type === "date" ? field.type : "text";
|
|
638
|
-
const input =
|
|
1316
|
+
const input = el3("input", {
|
|
639
1317
|
type: typeAttr,
|
|
640
1318
|
id,
|
|
641
1319
|
class: "nodii-onboarding-input"
|
|
@@ -649,7 +1327,7 @@ function buildFieldInput(field, currentValue, onChange) {
|
|
|
649
1327
|
}
|
|
650
1328
|
function fieldRow(field, input, idOverride) {
|
|
651
1329
|
const id = idOverride ?? `nodii-f-${field.key}`;
|
|
652
|
-
const labelEl =
|
|
1330
|
+
const labelEl = el3(
|
|
653
1331
|
"label",
|
|
654
1332
|
{
|
|
655
1333
|
for: id,
|
|
@@ -657,13 +1335,13 @@ function fieldRow(field, input, idOverride) {
|
|
|
657
1335
|
},
|
|
658
1336
|
[field.label]
|
|
659
1337
|
);
|
|
660
|
-
const errorEl =
|
|
1338
|
+
const errorEl = el3("div", {
|
|
661
1339
|
class: "nodii-onboarding-field-error",
|
|
662
1340
|
id: `${id}-error`,
|
|
663
1341
|
role: "alert",
|
|
664
1342
|
"aria-live": "polite"
|
|
665
1343
|
});
|
|
666
|
-
return
|
|
1344
|
+
return el3("div", { class: "nodii-onboarding-field" }, [
|
|
667
1345
|
labelEl,
|
|
668
1346
|
input,
|
|
669
1347
|
errorEl
|
|
@@ -671,8 +1349,8 @@ function fieldRow(field, input, idOverride) {
|
|
|
671
1349
|
}
|
|
672
1350
|
function mountSignupForm(host, options) {
|
|
673
1351
|
injectWidgetStyles();
|
|
674
|
-
const wrapper =
|
|
675
|
-
const loadingEl =
|
|
1352
|
+
const wrapper = el3("div", { class: "nodii-onboarding-widget" });
|
|
1353
|
+
const loadingEl = el3("div", { class: "nodii-onboarding-loading" }, [
|
|
676
1354
|
"Loading signup form\u2026"
|
|
677
1355
|
]);
|
|
678
1356
|
wrapper.appendChild(loadingEl);
|
|
@@ -686,27 +1364,28 @@ function mountSignupForm(host, options) {
|
|
|
686
1364
|
let lastFingerprint = null;
|
|
687
1365
|
let renderedStepIndex = -1;
|
|
688
1366
|
let renderedStale = false;
|
|
1367
|
+
let renderedConsentStale = false;
|
|
689
1368
|
let currentSpecVersion = null;
|
|
690
1369
|
let staleDiffSummary = "";
|
|
691
|
-
const stepArea =
|
|
692
|
-
const staleBanner =
|
|
1370
|
+
const stepArea = el3("div", {});
|
|
1371
|
+
const staleBanner = el3("div", {
|
|
693
1372
|
class: "nodii-onboarding-stale-banner",
|
|
694
1373
|
role: "alert",
|
|
695
1374
|
style: "display:none"
|
|
696
1375
|
});
|
|
697
|
-
const serverErrorEl =
|
|
1376
|
+
const serverErrorEl = el3("div", {
|
|
698
1377
|
class: "nodii-onboarding-server-error",
|
|
699
1378
|
role: "alert",
|
|
700
1379
|
"aria-live": "assertive",
|
|
701
1380
|
style: "display:none"
|
|
702
1381
|
});
|
|
703
|
-
const successEl =
|
|
1382
|
+
const successEl = el3("div", {
|
|
704
1383
|
class: "nodii-onboarding-success",
|
|
705
1384
|
role: "status",
|
|
706
1385
|
style: "display:none"
|
|
707
1386
|
});
|
|
708
|
-
const stepsIndicator =
|
|
709
|
-
const form =
|
|
1387
|
+
const stepsIndicator = el3("div", { class: "nodii-onboarding-steps" });
|
|
1388
|
+
const form = el3("form", { novalidate: "true", "aria-label": "Sign up" });
|
|
710
1389
|
function setFieldErrorsInPlace(container, errors, idPrefix) {
|
|
711
1390
|
for (const errEl of container.querySelectorAll(
|
|
712
1391
|
".nodii-onboarding-field-error"
|
|
@@ -726,7 +1405,7 @@ function mountSignupForm(host, options) {
|
|
|
726
1405
|
}
|
|
727
1406
|
function accountIdOf(path) {
|
|
728
1407
|
if (path === "email") return "nodii-account-email";
|
|
729
|
-
if (path === "
|
|
1408
|
+
if (path === "displayName") return "nodii-account-display-name";
|
|
730
1409
|
if (path === "tosAccepted") return "nodii-consent-tos";
|
|
731
1410
|
if (path === "privacyAccepted") return "nodii-consent-privacy";
|
|
732
1411
|
return `nodii-account-${path}`;
|
|
@@ -737,15 +1416,15 @@ function mountSignupForm(host, options) {
|
|
|
737
1416
|
}
|
|
738
1417
|
for (let i = 0; i < state.stepCount; i++) {
|
|
739
1418
|
stepsIndicator.appendChild(
|
|
740
|
-
|
|
1419
|
+
el3("div", {
|
|
741
1420
|
class: `nodii-onboarding-step-dot${i <= state.stepIndex ? " active" : ""}`
|
|
742
1421
|
})
|
|
743
1422
|
);
|
|
744
1423
|
}
|
|
745
1424
|
}
|
|
746
1425
|
function renderAccountSection(control, state) {
|
|
747
|
-
const section =
|
|
748
|
-
const emailInput =
|
|
1426
|
+
const section = el3("div", { class: "nodii-onboarding-account-section" });
|
|
1427
|
+
const emailInput = el3("input", {
|
|
749
1428
|
type: "email",
|
|
750
1429
|
id: "nodii-account-email",
|
|
751
1430
|
class: "nodii-onboarding-input",
|
|
@@ -768,32 +1447,54 @@ function mountSignupForm(host, options) {
|
|
|
768
1447
|
// label for + error id match the real input id
|
|
769
1448
|
)
|
|
770
1449
|
);
|
|
771
|
-
const
|
|
772
|
-
type: "
|
|
773
|
-
id: "nodii-account-
|
|
1450
|
+
const displayNameInput = el3("input", {
|
|
1451
|
+
type: "text",
|
|
1452
|
+
id: "nodii-account-display-name",
|
|
774
1453
|
class: "nodii-onboarding-input",
|
|
775
|
-
autocomplete: "
|
|
776
|
-
|
|
777
|
-
maxlength: "256"
|
|
1454
|
+
autocomplete: "name",
|
|
1455
|
+
maxlength: "200"
|
|
778
1456
|
});
|
|
779
|
-
|
|
780
|
-
|
|
1457
|
+
displayNameInput.value = state.credentials.displayName;
|
|
1458
|
+
displayNameInput.addEventListener(
|
|
781
1459
|
"input",
|
|
782
|
-
() => control.setCredentials({
|
|
1460
|
+
() => control.setCredentials({ displayName: displayNameInput.value })
|
|
783
1461
|
);
|
|
784
1462
|
section.appendChild(
|
|
785
1463
|
fieldRow(
|
|
786
1464
|
{
|
|
787
|
-
key: "account-
|
|
788
|
-
label: "
|
|
1465
|
+
key: "account-display-name",
|
|
1466
|
+
label: "Your name",
|
|
789
1467
|
required: true
|
|
790
1468
|
},
|
|
791
|
-
|
|
792
|
-
"nodii-account-
|
|
793
|
-
// label for + error id match the real input id
|
|
1469
|
+
displayNameInput,
|
|
1470
|
+
"nodii-account-display-name"
|
|
794
1471
|
)
|
|
795
1472
|
);
|
|
796
|
-
const
|
|
1473
|
+
for (const [key, label, autocomplete] of [
|
|
1474
|
+
["organisationName", "Organisation name", "organization"],
|
|
1475
|
+
["subdomain", "Workspace subdomain", "off"]
|
|
1476
|
+
]) {
|
|
1477
|
+
const input = el3("input", {
|
|
1478
|
+
type: "text",
|
|
1479
|
+
id: `nodii-account-${key}`,
|
|
1480
|
+
class: "nodii-onboarding-input",
|
|
1481
|
+
autocomplete,
|
|
1482
|
+
maxlength: key === "subdomain" ? "63" : "200"
|
|
1483
|
+
});
|
|
1484
|
+
input.value = state.credentials[key];
|
|
1485
|
+
input.addEventListener(
|
|
1486
|
+
"input",
|
|
1487
|
+
() => control.setCredentials({ [key]: input.value })
|
|
1488
|
+
);
|
|
1489
|
+
section.appendChild(
|
|
1490
|
+
fieldRow(
|
|
1491
|
+
{ key: `account-${key}`, label, required: true },
|
|
1492
|
+
input,
|
|
1493
|
+
`nodii-account-${key}`
|
|
1494
|
+
)
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
const tosCheckbox = el3("input", {
|
|
797
1498
|
type: "checkbox",
|
|
798
1499
|
id: "nodii-consent-tos"
|
|
799
1500
|
});
|
|
@@ -805,7 +1506,7 @@ function mountSignupForm(host, options) {
|
|
|
805
1506
|
const tosLabelChildren = ["I accept the "];
|
|
806
1507
|
if (options.tosUrl) {
|
|
807
1508
|
tosLabelChildren.push(
|
|
808
|
-
|
|
1509
|
+
el3(
|
|
809
1510
|
"a",
|
|
810
1511
|
{
|
|
811
1512
|
href: options.tosUrl,
|
|
@@ -819,19 +1520,19 @@ function mountSignupForm(host, options) {
|
|
|
819
1520
|
tosLabelChildren.push("Terms of Service");
|
|
820
1521
|
}
|
|
821
1522
|
section.appendChild(
|
|
822
|
-
|
|
1523
|
+
el3("div", { class: "nodii-onboarding-consent-row" }, [
|
|
823
1524
|
tosCheckbox,
|
|
824
|
-
|
|
1525
|
+
el3("label", { for: "nodii-consent-tos" }, tosLabelChildren)
|
|
825
1526
|
])
|
|
826
1527
|
);
|
|
827
1528
|
section.appendChild(
|
|
828
|
-
|
|
1529
|
+
el3("div", {
|
|
829
1530
|
class: "nodii-onboarding-field-error",
|
|
830
1531
|
id: "nodii-consent-tos-error",
|
|
831
1532
|
role: "alert"
|
|
832
1533
|
})
|
|
833
1534
|
);
|
|
834
|
-
const privacyCheckbox =
|
|
1535
|
+
const privacyCheckbox = el3("input", {
|
|
835
1536
|
type: "checkbox",
|
|
836
1537
|
id: "nodii-consent-privacy"
|
|
837
1538
|
});
|
|
@@ -843,7 +1544,7 @@ function mountSignupForm(host, options) {
|
|
|
843
1544
|
const privacyLabelChildren = ["I accept the "];
|
|
844
1545
|
if (options.privacyUrl) {
|
|
845
1546
|
privacyLabelChildren.push(
|
|
846
|
-
|
|
1547
|
+
el3(
|
|
847
1548
|
"a",
|
|
848
1549
|
{
|
|
849
1550
|
href: options.privacyUrl,
|
|
@@ -857,13 +1558,13 @@ function mountSignupForm(host, options) {
|
|
|
857
1558
|
privacyLabelChildren.push("Privacy Policy");
|
|
858
1559
|
}
|
|
859
1560
|
section.appendChild(
|
|
860
|
-
|
|
1561
|
+
el3("div", { class: "nodii-onboarding-consent-row" }, [
|
|
861
1562
|
privacyCheckbox,
|
|
862
|
-
|
|
1563
|
+
el3("label", { for: "nodii-consent-privacy" }, privacyLabelChildren)
|
|
863
1564
|
])
|
|
864
1565
|
);
|
|
865
1566
|
section.appendChild(
|
|
866
|
-
|
|
1567
|
+
el3("div", {
|
|
867
1568
|
class: "nodii-onboarding-field-error",
|
|
868
1569
|
id: "nodii-consent-privacy-error",
|
|
869
1570
|
role: "alert"
|
|
@@ -876,7 +1577,7 @@ function mountSignupForm(host, options) {
|
|
|
876
1577
|
const step = state.currentStep;
|
|
877
1578
|
if (step) {
|
|
878
1579
|
stepArea.appendChild(
|
|
879
|
-
|
|
1580
|
+
el3("h3", { class: "nodii-onboarding-stage-title" }, [
|
|
880
1581
|
STAGE_TITLES[step.stage] ?? step.stage
|
|
881
1582
|
])
|
|
882
1583
|
);
|
|
@@ -891,11 +1592,24 @@ function mountSignupForm(host, options) {
|
|
|
891
1592
|
}
|
|
892
1593
|
const isLast = control.isLastStep();
|
|
893
1594
|
if (isLast) {
|
|
1595
|
+
if (!control.consent.isEmpty()) {
|
|
1596
|
+
const consentStage = el3("div", {
|
|
1597
|
+
class: "nodii-onboarding-consent-stage"
|
|
1598
|
+
});
|
|
1599
|
+
renderConsentStep(
|
|
1600
|
+
consentStage,
|
|
1601
|
+
control.consent,
|
|
1602
|
+
state.consentState,
|
|
1603
|
+
state.staleConsentKeys,
|
|
1604
|
+
{ fetch: options.fetch, timeoutMs: options.timeoutMs }
|
|
1605
|
+
);
|
|
1606
|
+
stepArea.appendChild(consentStage);
|
|
1607
|
+
}
|
|
894
1608
|
stepArea.appendChild(renderAccountSection(control, state));
|
|
895
1609
|
}
|
|
896
|
-
const nav =
|
|
1610
|
+
const nav = el3("div", { class: "nodii-onboarding-nav" });
|
|
897
1611
|
if (!control.isFirstStep()) {
|
|
898
|
-
const backBtn =
|
|
1612
|
+
const backBtn = el3(
|
|
899
1613
|
"button",
|
|
900
1614
|
{ type: "button", class: "nodii-onboarding-back-btn" },
|
|
901
1615
|
["Back"]
|
|
@@ -903,7 +1617,7 @@ function mountSignupForm(host, options) {
|
|
|
903
1617
|
backBtn.addEventListener("click", () => control.back());
|
|
904
1618
|
nav.appendChild(backBtn);
|
|
905
1619
|
}
|
|
906
|
-
const forwardBtn =
|
|
1620
|
+
const forwardBtn = el3(
|
|
907
1621
|
"button",
|
|
908
1622
|
{
|
|
909
1623
|
type: isLast ? "submit" : "button",
|
|
@@ -940,12 +1654,27 @@ function mountSignupForm(host, options) {
|
|
|
940
1654
|
} else {
|
|
941
1655
|
staleDiffSummary = "";
|
|
942
1656
|
}
|
|
943
|
-
siteKey = options.siteKey ?? fresh.botChallenge
|
|
1657
|
+
siteKey = options.siteKey ?? fresh.botChallenge?.siteKey ?? "";
|
|
944
1658
|
currentSpecVersion = fresh.specVersion;
|
|
945
1659
|
control.applyNewSpec(fresh);
|
|
946
1660
|
idempotencyKey = null;
|
|
947
1661
|
lastFingerprint = null;
|
|
948
1662
|
}
|
|
1663
|
+
async function handleConsentStale(control) {
|
|
1664
|
+
const fresh = await fetchOnboardingSpec({
|
|
1665
|
+
baseUrl: options.baseUrl,
|
|
1666
|
+
onboarder: options.onboarder,
|
|
1667
|
+
fetch: options.fetch,
|
|
1668
|
+
timeoutMs: options.timeoutMs,
|
|
1669
|
+
cache
|
|
1670
|
+
});
|
|
1671
|
+
if (unmounted) return;
|
|
1672
|
+
siteKey = options.siteKey ?? fresh.botChallenge?.siteKey ?? "";
|
|
1673
|
+
currentSpecVersion = fresh.specVersion;
|
|
1674
|
+
control.applyNewConsentPack(fresh);
|
|
1675
|
+
idempotencyKey = null;
|
|
1676
|
+
lastFingerprint = null;
|
|
1677
|
+
}
|
|
949
1678
|
async function handleSubmit(control) {
|
|
950
1679
|
if (!control.validateAll()) return;
|
|
951
1680
|
control.acknowledgeSpecStale();
|
|
@@ -957,14 +1686,16 @@ function mountSignupForm(host, options) {
|
|
|
957
1686
|
lastFingerprint = fingerprint;
|
|
958
1687
|
}
|
|
959
1688
|
let firstToken;
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1689
|
+
if (siteKey) {
|
|
1690
|
+
try {
|
|
1691
|
+
firstToken = await executeRecaptcha(siteKey, "signup_submit", {
|
|
1692
|
+
timeoutMs: options.timeoutMs
|
|
1693
|
+
});
|
|
1694
|
+
} catch (recaptchaErr) {
|
|
1695
|
+
control.setError("Could not load bot protection. Please try again.");
|
|
1696
|
+
options.onError?.(recaptchaErr);
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
968
1699
|
}
|
|
969
1700
|
let usedFirst = false;
|
|
970
1701
|
const tokenProvider = async () => {
|
|
@@ -979,7 +1710,7 @@ function mountSignupForm(host, options) {
|
|
|
979
1710
|
try {
|
|
980
1711
|
const result = await submitSignup(body, {
|
|
981
1712
|
baseUrl: options.baseUrl,
|
|
982
|
-
botToken: tokenProvider,
|
|
1713
|
+
...siteKey ? { botToken: tokenProvider } : {},
|
|
983
1714
|
idempotencyKey,
|
|
984
1715
|
fetch: options.fetch,
|
|
985
1716
|
timeoutMs: options.timeoutMs
|
|
@@ -1000,6 +1731,27 @@ function mountSignupForm(host, options) {
|
|
|
1000
1731
|
return;
|
|
1001
1732
|
}
|
|
1002
1733
|
options.onError?.(err);
|
|
1734
|
+
} else if (err instanceof OnboardingConsentStaleError) {
|
|
1735
|
+
try {
|
|
1736
|
+
await handleConsentStale(control);
|
|
1737
|
+
} catch (refetchErr) {
|
|
1738
|
+
control.setError(
|
|
1739
|
+
"The legal documents changed and could not be reloaded. Please refresh the page."
|
|
1740
|
+
);
|
|
1741
|
+
options.onError?.(refetchErr);
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
options.onError?.(err);
|
|
1745
|
+
} else if (err instanceof OnboardingConsentIncompleteError) {
|
|
1746
|
+
control.validateAll();
|
|
1747
|
+
control.setError("Please accept all required documents to continue.");
|
|
1748
|
+
options.onError?.(err);
|
|
1749
|
+
} else if (err instanceof OnboardingSubdomainTakenError) {
|
|
1750
|
+
control.setAccountError(
|
|
1751
|
+
"subdomain",
|
|
1752
|
+
"This workspace address is already taken. Choose another one."
|
|
1753
|
+
);
|
|
1754
|
+
options.onError?.(err);
|
|
1003
1755
|
} else if (err instanceof OnboardingBotChallengeError) {
|
|
1004
1756
|
control.setError(
|
|
1005
1757
|
"Bot protection check failed. Please refresh the page and try again."
|
|
@@ -1017,14 +1769,15 @@ function mountSignupForm(host, options) {
|
|
|
1017
1769
|
}
|
|
1018
1770
|
}
|
|
1019
1771
|
function onStateChange(control, state) {
|
|
1020
|
-
if (state.stepIndex !== renderedStepIndex || state.specStale !== renderedStale) {
|
|
1772
|
+
if (state.stepIndex !== renderedStepIndex || state.specStale !== renderedStale || state.consentStale !== renderedConsentStale) {
|
|
1021
1773
|
renderedStepIndex = state.stepIndex;
|
|
1022
1774
|
renderedStale = state.specStale;
|
|
1775
|
+
renderedConsentStale = state.consentStale;
|
|
1023
1776
|
renderStep(control, state);
|
|
1024
1777
|
renderStepsIndicator(state);
|
|
1025
1778
|
}
|
|
1026
|
-
staleBanner.style.display = state.specStale ? "" : "none";
|
|
1027
|
-
staleBanner.textContent = state.specStale ? `This form was just updated by the provider${staleDiffSummary}. Please review your answers below and submit again.` : "";
|
|
1779
|
+
staleBanner.style.display = state.specStale || state.consentStale ? "" : "none";
|
|
1780
|
+
staleBanner.textContent = state.specStale ? `This form was just updated by the provider${staleDiffSummary}. Please review your answers below and submit again.` : state.consentStale ? "One or more legal documents were updated. Please review and re-accept the updated documents below, then submit again." : "";
|
|
1028
1781
|
setFieldErrorsInPlace(
|
|
1029
1782
|
stepArea,
|
|
1030
1783
|
state.errors,
|
|
@@ -1037,6 +1790,12 @@ function mountSignupForm(host, options) {
|
|
|
1037
1790
|
const inputNode = stepArea.querySelector(idSelector(id));
|
|
1038
1791
|
if (inputNode) inputNode.classList.add("has-error");
|
|
1039
1792
|
}
|
|
1793
|
+
const consentStage = stepArea.querySelector(
|
|
1794
|
+
".nodii-onboarding-consent-stage"
|
|
1795
|
+
);
|
|
1796
|
+
if (consentStage) {
|
|
1797
|
+
updateConsentStep(consentStage, state.consentState, state.consentErrors);
|
|
1798
|
+
}
|
|
1040
1799
|
if (state.serverError) {
|
|
1041
1800
|
serverErrorEl.textContent = state.serverError;
|
|
1042
1801
|
serverErrorEl.style.display = "";
|
|
@@ -1080,7 +1839,7 @@ function mountSignupForm(host, options) {
|
|
|
1080
1839
|
return;
|
|
1081
1840
|
}
|
|
1082
1841
|
if (unmounted) return;
|
|
1083
|
-
siteKey = options.siteKey ?? spec.botChallenge
|
|
1842
|
+
siteKey = options.siteKey ?? spec.botChallenge?.siteKey ?? "";
|
|
1084
1843
|
currentSpecVersion = spec.specVersion;
|
|
1085
1844
|
const control = createSignupStepper(spec);
|
|
1086
1845
|
stepper = control;
|
|
@@ -1144,12 +1903,12 @@ var OnboardingWidget = {
|
|
|
1144
1903
|
* });
|
|
1145
1904
|
* ```
|
|
1146
1905
|
*/
|
|
1147
|
-
mount(
|
|
1906
|
+
mount(el4, options) {
|
|
1148
1907
|
if (options.form === "contact") {
|
|
1149
|
-
return mountContactForm(
|
|
1908
|
+
return mountContactForm(el4, options);
|
|
1150
1909
|
}
|
|
1151
1910
|
if (options.form === "signup") {
|
|
1152
|
-
return mountSignupForm(
|
|
1911
|
+
return mountSignupForm(el4, options);
|
|
1153
1912
|
}
|
|
1154
1913
|
throw new Error(
|
|
1155
1914
|
`OnboardingWidget.mount: unknown form "${options.form}". Supported: "contact", "signup".`
|
|
@@ -1157,6 +1916,6 @@ var OnboardingWidget = {
|
|
|
1157
1916
|
}
|
|
1158
1917
|
};
|
|
1159
1918
|
|
|
1160
|
-
export { DEFAULT_WIDGET_CSS, OnboardingWidget, cssEscape, idSelector, injectWidgetStyles, mountContactForm, mountSignupForm };
|
|
1161
|
-
//# sourceMappingURL=chunk-
|
|
1162
|
-
//# sourceMappingURL=chunk-
|
|
1919
|
+
export { DEFAULT_WIDGET_CSS, OnboardingWidget, consentDocElementId, cssEscape, idSelector, injectWidgetStyles, mountContactForm, mountSignupForm, renderConsentStep, updateConsentStep };
|
|
1920
|
+
//# sourceMappingURL=chunk-OVULPWNZ.js.map
|
|
1921
|
+
//# sourceMappingURL=chunk-OVULPWNZ.js.map
|