@redacto.io/consent-sdk-js 0.1.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.
@@ -0,0 +1,4747 @@
1
+ // src/core/state.ts
2
+ var Store = class {
3
+ constructor(initialState) {
4
+ this.listeners = /* @__PURE__ */ new Set();
5
+ this.state = { ...initialState };
6
+ }
7
+ getState() {
8
+ return this.state;
9
+ }
10
+ setState(partial) {
11
+ const prevState = this.state;
12
+ this.state = { ...this.state, ...partial };
13
+ this.listeners.forEach((listener) => listener(this.state, prevState));
14
+ }
15
+ subscribe(listener) {
16
+ this.listeners.add(listener);
17
+ return () => {
18
+ this.listeners.delete(listener);
19
+ };
20
+ }
21
+ destroy() {
22
+ this.listeners.clear();
23
+ }
24
+ };
25
+
26
+ // src/core/dom.ts
27
+ function el(tag, attrs, ...children) {
28
+ const element = document.createElement(tag);
29
+ if (attrs) applyAttrs(element, attrs);
30
+ appendChildren(element, children);
31
+ return element;
32
+ }
33
+ function svg(tag, attrs, ...children) {
34
+ const element = document.createElementNS(
35
+ "http://www.w3.org/2000/svg",
36
+ tag
37
+ );
38
+ if (attrs) {
39
+ for (const [key, value] of Object.entries(attrs)) {
40
+ if (value == null || value === false) continue;
41
+ if (key === "style" && typeof value === "object") {
42
+ applyStyles(element, value);
43
+ } else {
44
+ element.setAttribute(key, String(value));
45
+ }
46
+ }
47
+ }
48
+ appendChildren(element, children);
49
+ return element;
50
+ }
51
+ function applyAttrs(element, attrs) {
52
+ for (const [key, value] of Object.entries(attrs)) {
53
+ if (value == null || value === false) continue;
54
+ if (key === "style" && typeof value === "object") {
55
+ applyStyles(element, value);
56
+ } else if (key === "className") {
57
+ element.className = value;
58
+ } else if (key === "dataset" && typeof value === "object") {
59
+ Object.assign(element.dataset, value);
60
+ } else if (key.startsWith("on") && typeof value === "function") {
61
+ const event = key.slice(2).toLowerCase();
62
+ element.addEventListener(event, value);
63
+ } else if (key === "checked" || key === "disabled" || key === "readOnly") {
64
+ element[key] = value;
65
+ } else {
66
+ element.setAttribute(key, String(value));
67
+ }
68
+ }
69
+ }
70
+ function applyStyles(element, styles2) {
71
+ for (const [key, value] of Object.entries(styles2)) {
72
+ if (value == null) continue;
73
+ element.style[key] = typeof value === "number" ? `${value}px` : value;
74
+ }
75
+ }
76
+ function appendChildren(parent, children) {
77
+ for (const child of children) {
78
+ if (child == null || child === false) continue;
79
+ if (typeof child === "string") {
80
+ parent.appendChild(document.createTextNode(child));
81
+ } else {
82
+ parent.appendChild(child);
83
+ }
84
+ }
85
+ }
86
+ function clearElement(element) {
87
+ while (element.firstChild) {
88
+ element.removeChild(element.firstChild);
89
+ }
90
+ }
91
+
92
+ // src/core/media-query.ts
93
+ function createMediaQuery(query) {
94
+ const mql = window.matchMedia(query);
95
+ let currentMatches = mql.matches;
96
+ const listeners = /* @__PURE__ */ new Set();
97
+ const handler = (e) => {
98
+ currentMatches = e.matches;
99
+ listeners.forEach((cb) => cb(currentMatches));
100
+ };
101
+ mql.addEventListener("change", handler);
102
+ return {
103
+ get matches() {
104
+ return currentMatches;
105
+ },
106
+ subscribe(cb) {
107
+ listeners.add(cb);
108
+ },
109
+ destroy() {
110
+ mql.removeEventListener("change", handler);
111
+ listeners.clear();
112
+ }
113
+ };
114
+ }
115
+
116
+ // src/core/jwt-decode.ts
117
+ import { jwtDecode } from "jwt-decode";
118
+ function decodeTokenSafely(token) {
119
+ try {
120
+ return jwtDecode(token);
121
+ } catch (error) {
122
+ console.warn("Failed to decode JWT token:", error);
123
+ return null;
124
+ }
125
+ }
126
+
127
+ // src/services/cache.ts
128
+ var apiCache = /* @__PURE__ */ new Map();
129
+ var CACHE_TTL = 5 * 60 * 1e3;
130
+ function getCachedData(key) {
131
+ const cached = apiCache.get(key);
132
+ if (cached && Date.now() - cached.timestamp < cached.ttl) {
133
+ return cached.data;
134
+ }
135
+ if (cached) {
136
+ apiCache.delete(key);
137
+ }
138
+ return null;
139
+ }
140
+ function setCachedData(key, data, ttl = CACHE_TTL) {
141
+ apiCache.set(key, { data, timestamp: Date.now(), ttl });
142
+ }
143
+ function clearApiCache() {
144
+ apiCache.clear();
145
+ }
146
+
147
+ // src/api/index.ts
148
+ var BASE_URL = "https://api.redacto.io/consent";
149
+ var BASE_URL_PUBLIC = "https://api.redacto.tech/consent";
150
+ var fetchConsentContent = async ({
151
+ noticeId,
152
+ accessToken,
153
+ baseUrl,
154
+ language = "en",
155
+ specific_uuid,
156
+ validate_against = "all",
157
+ include_fully_consented_data = false,
158
+ signal
159
+ }) => {
160
+ if (!noticeId || !accessToken || !validate_against) {
161
+ throw new Error("noticeId and accessToken are required");
162
+ }
163
+ const decodedToken = decodeTokenSafely(accessToken);
164
+ if (!decodedToken) throw new Error("Invalid access token");
165
+ const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
166
+ if (!ORG || !WS) throw new Error("Invalid token: missing organization or workspace UUID");
167
+ const apiBaseUrl = baseUrl || BASE_URL;
168
+ const cacheKey = `${accessToken}-${noticeId}-${validate_against}-${language}-${specific_uuid || ""}-${include_fully_consented_data}`;
169
+ if (validate_against === "all" && !specific_uuid) {
170
+ const cached = getCachedData(cacheKey);
171
+ if (cached) return cached;
172
+ }
173
+ const url = new URL(`${apiBaseUrl}/public/organisations/${ORG}/workspaces/${WS}/notices/${noticeId}`);
174
+ if (specific_uuid) url.searchParams.append("specific_uuid", specific_uuid);
175
+ url.searchParams.append("validate_against", validate_against);
176
+ if (include_fully_consented_data) url.searchParams.append("include_fully_consented_data", "true");
177
+ const response = await fetch(url.toString(), {
178
+ method: "GET",
179
+ headers: {
180
+ Authorization: `Bearer ${accessToken}`,
181
+ "Accept-Language": language
182
+ },
183
+ signal
184
+ });
185
+ if (!response.ok) {
186
+ let errorMessage = `Failed to fetch consent content: ${response.status} ${response.statusText}`;
187
+ if (response.status === 401) errorMessage = "Unauthorized: Invalid or expired token";
188
+ else if (response.status === 403) errorMessage = "Forbidden: Access denied";
189
+ else if (response.status === 404) errorMessage = "Notice not found";
190
+ else if (response.status === 409) errorMessage = "User has already provided consent";
191
+ else if (response.status >= 500) errorMessage = "Server error: Please try again later";
192
+ const error = new Error(errorMessage);
193
+ error.status = response.status;
194
+ throw error;
195
+ }
196
+ const data = await response.json();
197
+ if (validate_against === "all" && !specific_uuid) {
198
+ setCachedData(cacheKey, data);
199
+ }
200
+ return data;
201
+ };
202
+ var fetchConsentContentPublic = async ({
203
+ org_uuid,
204
+ workspace_uuid,
205
+ notice_uuid,
206
+ baseUrl,
207
+ language = "en",
208
+ specific_uuid
209
+ }) => {
210
+ const apiBaseUrl = baseUrl || BASE_URL_PUBLIC;
211
+ const url = new URL(
212
+ `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/notices/get-notice/${notice_uuid}`
213
+ );
214
+ if (specific_uuid) url.searchParams.append("specific_uuid", specific_uuid);
215
+ const response = await fetch(url.toString(), {
216
+ method: "GET",
217
+ headers: { "Content-Type": "application/json" }
218
+ });
219
+ if (response.status === 401) {
220
+ const error = new Error("Unauthorized");
221
+ error.status = 401;
222
+ throw error;
223
+ }
224
+ if (response.status === 409) {
225
+ const error = new Error("User has already provided consent");
226
+ error.status = 409;
227
+ throw error;
228
+ }
229
+ if (!response.ok) {
230
+ throw new Error(`Failed to fetch consent content: ${response.statusText}`);
231
+ }
232
+ return await response.json();
233
+ };
234
+ var submitConsentEvent = async ({
235
+ accessToken,
236
+ baseUrl,
237
+ noticeUuid,
238
+ purposes,
239
+ declined,
240
+ meta_data,
241
+ guardianVerificationReference,
242
+ selfDeclaredAdult,
243
+ signal
244
+ }) => {
245
+ if (!noticeUuid || !accessToken || !Array.isArray(purposes)) {
246
+ throw new Error("noticeUuid, accessToken, and purposes array are required");
247
+ }
248
+ const decodedToken = decodeTokenSafely(accessToken);
249
+ if (!decodedToken) throw new Error("Invalid access token");
250
+ const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
251
+ if (!ORG || !WS) throw new Error("Invalid token: missing organization or workspace UUID");
252
+ const apiBaseUrl = baseUrl || BASE_URL;
253
+ const validatedPurposes = purposes.map((purpose) => {
254
+ if (!purpose.uuid) throw new Error("Invalid purpose data: missing uuid");
255
+ const dataElements = (purpose.data_elements || []).filter((element) => element.enabled).map((element) => ({
256
+ uuid: element.uuid,
257
+ selected: element.required ? true : Boolean(element.selected)
258
+ }));
259
+ return {
260
+ purpose_uuid: purpose.uuid,
261
+ selected: Boolean(purpose.selected),
262
+ data_elements: dataElements.length > 0 ? dataElements : void 0
263
+ };
264
+ });
265
+ const payload = {
266
+ notice_uuid: noticeUuid,
267
+ purposes: validatedPurposes,
268
+ select_all_mandatory: false,
269
+ source: "WEB",
270
+ declined: Boolean(declined),
271
+ meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid, minor_age: meta_data.minor_age } : void 0,
272
+ guardian_verification_reference: guardianVerificationReference,
273
+ self_declared_adult: selfDeclaredAdult
274
+ };
275
+ const response = await fetch(
276
+ `${apiBaseUrl}/public/organisations/${ORG}/workspaces/${WS}/submit-consent`,
277
+ {
278
+ method: "POST",
279
+ headers: {
280
+ Authorization: `Bearer ${accessToken}`,
281
+ "Content-Type": "application/json"
282
+ },
283
+ body: JSON.stringify(payload),
284
+ signal
285
+ }
286
+ );
287
+ if (!response.ok) {
288
+ let errorMessage = `Failed to submit consent: ${response.status} ${response.statusText}`;
289
+ if (response.status === 400) {
290
+ try {
291
+ const errorData = await response.json();
292
+ errorMessage = errorData.message || "Invalid request data";
293
+ } catch {
294
+ errorMessage = "Invalid request data";
295
+ }
296
+ } else if (response.status === 401) errorMessage = "Unauthorized: Invalid or expired token";
297
+ else if (response.status === 403) errorMessage = "Forbidden: Access denied";
298
+ else if (response.status === 404) errorMessage = "Notice not found";
299
+ else if (response.status >= 500) errorMessage = "Server error: Please try again later";
300
+ const error = new Error(errorMessage);
301
+ error.status = response.status;
302
+ throw error;
303
+ }
304
+ clearApiCache();
305
+ };
306
+ var submitConsentEventPublic = async ({
307
+ org_uuid,
308
+ workspace_uuid,
309
+ accessToken,
310
+ baseUrl,
311
+ noticeUuid,
312
+ purposes,
313
+ declined,
314
+ meta_data
315
+ }) => {
316
+ var _a, _b;
317
+ const apiBaseUrl = baseUrl || BASE_URL_PUBLIC;
318
+ const payload = {
319
+ notice_uuid: noticeUuid,
320
+ purposes: purposes.map((purpose) => {
321
+ if (!purpose.uuid) throw new Error("Invalid purpose data: missing uuid");
322
+ const dataElements = purpose.data_elements.filter((element) => element.enabled).map((element) => ({
323
+ uuid: element.uuid,
324
+ selected: element.required ? true : Boolean(element.selected)
325
+ }));
326
+ return {
327
+ purpose_uuid: purpose.uuid,
328
+ selected: Boolean(purpose.selected),
329
+ data_elements: dataElements.length > 0 ? dataElements : void 0
330
+ };
331
+ }),
332
+ select_all_mandatory: false,
333
+ source: "WEB",
334
+ declined,
335
+ meta_data
336
+ };
337
+ const response = await fetch(
338
+ `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/submit-consent`,
339
+ {
340
+ method: "POST",
341
+ headers: {
342
+ Authorization: `Bearer ${accessToken}`,
343
+ "Content-Type": "application/json"
344
+ },
345
+ body: JSON.stringify(payload)
346
+ }
347
+ );
348
+ if (!response.ok) {
349
+ let errorData;
350
+ try {
351
+ errorData = await response.json();
352
+ } catch {
353
+ errorData = { message: response.statusText };
354
+ }
355
+ if (response.status === 409) {
356
+ const error2 = new Error(
357
+ ((_a = errorData.detail) == null ? void 0 : _a.message) || errorData.message || "User has already provided consent"
358
+ );
359
+ error2.status = 409;
360
+ error2.code = errorData.code || 409;
361
+ throw error2;
362
+ }
363
+ const error = new Error(
364
+ ((_b = errorData.detail) == null ? void 0 : _b.message) || errorData.message || `Failed to submit consent event: ${response.statusText}`
365
+ );
366
+ error.status = response.status;
367
+ error.code = errorData.code;
368
+ throw error;
369
+ }
370
+ };
371
+ var fetchTTSAudioUrls = async ({
372
+ accessToken,
373
+ baseUrl,
374
+ noticeUuid,
375
+ language,
376
+ signal
377
+ }) => {
378
+ if (!accessToken || !noticeUuid || !language) {
379
+ throw new Error("accessToken, noticeUuid, and language are required");
380
+ }
381
+ const decodedToken = decodeTokenSafely(accessToken);
382
+ if (!decodedToken) throw new Error("Invalid access token");
383
+ const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
384
+ if (!ORG || !WS) throw new Error("Invalid token: missing organization or workspace UUID");
385
+ const apiBaseUrl = baseUrl || BASE_URL;
386
+ const url = `${apiBaseUrl}/public/organisations/${ORG}/workspaces/${WS}/notices/${noticeUuid}/audio/${language}`;
387
+ const response = await fetch(url, {
388
+ method: "GET",
389
+ headers: {
390
+ Authorization: `Bearer ${accessToken}`,
391
+ Accept: "application/json"
392
+ },
393
+ signal
394
+ });
395
+ if (!response.ok) {
396
+ let errorMessage = `Failed to fetch TTS audio URLs: ${response.status} ${response.statusText}`;
397
+ if (response.status === 401) errorMessage = "Unauthorized: Invalid or expired token";
398
+ else if (response.status === 403) errorMessage = "Forbidden: Access denied";
399
+ else if (response.status === 404) errorMessage = "Notice or audio not found";
400
+ else if (response.status >= 500) errorMessage = "Server error: Please try again later";
401
+ const error = new Error(errorMessage);
402
+ error.status = response.status;
403
+ throw error;
404
+ }
405
+ return await response.json();
406
+ };
407
+ var initiateGuardianVerification = async ({
408
+ accessToken,
409
+ baseUrl,
410
+ guardianName,
411
+ guardianContact,
412
+ guardianRelationship,
413
+ frontendCallbackUrl,
414
+ metadata,
415
+ signal
416
+ }) => {
417
+ var _a;
418
+ if (!accessToken) throw new Error("accessToken is required");
419
+ if (!(guardianName == null ? void 0 : guardianName.trim())) throw new Error("Guardian name is required");
420
+ if (!(guardianContact == null ? void 0 : guardianContact.trim())) throw new Error("Guardian contact is required");
421
+ if (!(guardianRelationship == null ? void 0 : guardianRelationship.trim())) throw new Error("Guardian relationship is required");
422
+ const decodedToken = decodeTokenSafely(accessToken);
423
+ if (!decodedToken) throw new Error("Invalid access token");
424
+ const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
425
+ if (!ORG || !WS) throw new Error("Invalid token: missing organization or workspace UUID");
426
+ const apiBaseUrl = baseUrl || BASE_URL;
427
+ const payload = {
428
+ guardian_name: guardianName.trim(),
429
+ guardian_contact: guardianContact.trim(),
430
+ guardian_relationship: guardianRelationship.trim()
431
+ };
432
+ if (frontendCallbackUrl) payload.frontend_callback_url = frontendCallbackUrl;
433
+ if (metadata) payload.metadata = metadata;
434
+ const response = await fetch(
435
+ `${apiBaseUrl}/public/organisations/${ORG}/workspaces/${WS}/guardian/initiate-verification`,
436
+ {
437
+ method: "POST",
438
+ headers: {
439
+ Authorization: `Bearer ${accessToken}`,
440
+ "Content-Type": "application/json"
441
+ },
442
+ body: JSON.stringify(payload),
443
+ signal
444
+ }
445
+ );
446
+ if (!response.ok) {
447
+ let errorMessage = `Failed to initiate verification: ${response.status} ${response.statusText}`;
448
+ if (response.status === 400) {
449
+ try {
450
+ const errorData = await response.json();
451
+ errorMessage = errorData.message || "Invalid request data";
452
+ } catch {
453
+ errorMessage = "Invalid request data";
454
+ }
455
+ } else if (response.status === 401) errorMessage = "Unauthorized: Invalid or expired token";
456
+ else if (response.status === 404) errorMessage = "Organization or workspace not found";
457
+ else if (response.status === 422) {
458
+ try {
459
+ const errorData = await response.json();
460
+ errorMessage = errorData.message || "Validation error: Please check your input";
461
+ } catch {
462
+ errorMessage = "Validation error: Please check your input";
463
+ }
464
+ } else if (response.status >= 500) errorMessage = "Server error: Please try again later";
465
+ const error = new Error(errorMessage);
466
+ error.status = response.status;
467
+ throw error;
468
+ }
469
+ const responseData = await response.json();
470
+ const detail = responseData.detail || responseData;
471
+ if (detail.already_verified) {
472
+ return {
473
+ success: true,
474
+ guardian_info_uuid: detail.guardian_info_uuid,
475
+ already_verified: true,
476
+ verification_reference: detail.verification_reference
477
+ };
478
+ }
479
+ if (!detail.session_token || !detail.digilocker_redirect_url) {
480
+ throw new Error("Invalid response: missing session_token or digilocker_redirect_url");
481
+ }
482
+ return {
483
+ success: (_a = detail.success) != null ? _a : true,
484
+ guardian_info_uuid: detail.guardian_info_uuid,
485
+ already_verified: false,
486
+ session_token: detail.session_token,
487
+ digilocker_redirect_url: detail.digilocker_redirect_url,
488
+ expires_at: detail.expires_at
489
+ };
490
+ };
491
+ var verifyGuardianStatus = async ({
492
+ accessToken,
493
+ baseUrl,
494
+ sessionToken,
495
+ signal
496
+ }) => {
497
+ var _a, _b, _c, _d, _e, _f, _g, _h;
498
+ if (!accessToken || !sessionToken) {
499
+ throw new Error("accessToken and sessionToken are required");
500
+ }
501
+ const decodedToken = decodeTokenSafely(accessToken);
502
+ if (!decodedToken) throw new Error("Invalid access token");
503
+ const { organisation_uuid: ORG, workspace_uuid: WS } = decodedToken;
504
+ if (!ORG || !WS) throw new Error("Invalid token: missing organization or workspace UUID");
505
+ const apiBaseUrl = baseUrl || BASE_URL;
506
+ const response = await fetch(
507
+ `${apiBaseUrl}/public/organisations/${ORG}/workspaces/${WS}/guardian/verify-status`,
508
+ {
509
+ method: "POST",
510
+ headers: {
511
+ Authorization: `Bearer ${accessToken}`,
512
+ "Content-Type": "application/json"
513
+ },
514
+ body: JSON.stringify({ session_token: sessionToken }),
515
+ signal
516
+ }
517
+ );
518
+ if (!response.ok) {
519
+ let errorMessage = `Failed to check verification status: ${response.status} ${response.statusText}`;
520
+ if (response.status === 400) {
521
+ try {
522
+ const errorData = await response.json();
523
+ errorMessage = errorData.message || ((_a = errorData.detail) == null ? void 0 : _a.message) || "Invalid session token";
524
+ } catch {
525
+ errorMessage = "Invalid session token";
526
+ }
527
+ } else if (response.status === 401) errorMessage = "Unauthorized: Invalid or expired token";
528
+ else if (response.status === 404)
529
+ errorMessage = "Verification session not found or expired. Please restart the verification process.";
530
+ else if (response.status === 410)
531
+ errorMessage = "Verification session has expired. Please restart the verification process.";
532
+ else if (response.status >= 500) errorMessage = "Server error: Please try again later";
533
+ const error = new Error(errorMessage);
534
+ error.status = response.status;
535
+ throw error;
536
+ }
537
+ const responseData = await response.json();
538
+ const detail = responseData.detail || responseData;
539
+ return {
540
+ status: (_b = detail.status) != null ? _b : "in_progress",
541
+ guardian_info_uuid: (_c = detail.guardian_info_uuid) != null ? _c : null,
542
+ verification_reference: (_d = detail.verification_reference) != null ? _d : null,
543
+ guardian_details: (_e = detail.guardian_details) != null ? _e : null,
544
+ error: (_f = detail.error) != null ? _f : null,
545
+ error_code: (_g = detail.error_code) != null ? _g : null,
546
+ can_retry: (_h = detail.can_retry) != null ? _h : true
547
+ };
548
+ };
549
+
550
+ // src/styles/inject.ts
551
+ var stylesInjected = false;
552
+ var injectCheckboxStyles = () => {
553
+ if (stylesInjected || typeof document === "undefined") {
554
+ return;
555
+ }
556
+ const STYLE_ID = "redacto-consent-checkbox-styles";
557
+ if (document.getElementById(STYLE_ID)) {
558
+ stylesInjected = true;
559
+ return;
560
+ }
561
+ const style = document.createElement("style");
562
+ style.id = STYLE_ID;
563
+ style.textContent = `
564
+ /* Redacto Consent SDK Checkbox Styles */
565
+ .redacto-checkbox-large {
566
+ appearance: none !important;
567
+ -webkit-appearance: none !important;
568
+ -moz-appearance: none !important;
569
+ height: 20px !important;
570
+ width: 20px !important;
571
+ border: 2px solid #d0d5dd !important;
572
+ border-radius: 5px !important;
573
+ background-color: transparent !important;
574
+ cursor: pointer !important;
575
+ position: relative !important;
576
+ box-sizing: border-box !important;
577
+ margin: 0 !important;
578
+ padding: 4px !important;
579
+ }
580
+
581
+ .redacto-checkbox-small {
582
+ appearance: none !important;
583
+ -webkit-appearance: none !important;
584
+ -moz-appearance: none !important;
585
+ height: 16px !important;
586
+ width: 16px !important;
587
+ border: 2px solid #d0d5dd !important;
588
+ border-radius: 5px !important;
589
+ background-color: transparent !important;
590
+ cursor: pointer !important;
591
+ position: relative !important;
592
+ box-sizing: border-box !important;
593
+ margin: 0 !important;
594
+ padding: 4px !important;
595
+ }
596
+
597
+ .redacto-checkbox-large:checked {
598
+ background-color: #4f87ff !important;
599
+ border-color: #4f87ff !important;
600
+ }
601
+
602
+ .redacto-checkbox-small:checked {
603
+ background-color: #4f87ff !important;
604
+ border-color: #4f87ff !important;
605
+ }
606
+
607
+ .redacto-checkbox-large:checked::after {
608
+ content: '' !important;
609
+ position: absolute !important;
610
+ left: 50% !important;
611
+ top: 50% !important;
612
+ transform: translate(-50%, -50%) rotate(45deg) !important;
613
+ width: 4px !important;
614
+ height: 8px !important;
615
+ border: solid white !important;
616
+ border-width: 0 2px 2px 0 !important;
617
+ box-sizing: border-box !important;
618
+ }
619
+
620
+ .redacto-checkbox-small:checked::after {
621
+ content: '' !important;
622
+ position: absolute !important;
623
+ left: 50% !important;
624
+ top: 50% !important;
625
+ transform: translate(-50%, -50%) rotate(45deg) !important;
626
+ width: 3px !important;
627
+ height: 6px !important;
628
+ border: solid white !important;
629
+ border-width: 0 1.5px 1.5px 0 !important;
630
+ box-sizing: border-box !important;
631
+ }
632
+
633
+ .redacto-checkbox-large:focus {
634
+ outline: 2px solid #4f87ff !important;
635
+ outline-offset: 2px !important;
636
+ }
637
+
638
+ .redacto-checkbox-small:focus {
639
+ outline: 2px solid #4f87ff !important;
640
+ outline-offset: 2px !important;
641
+ }
642
+
643
+ /* TTS Highlight Styles */
644
+ .redacto-tts-highlight {
645
+ position: relative !important;
646
+ transition: background-color 0.3s ease, outline 0.3s ease !important;
647
+ }
648
+
649
+ h2.redacto-tts-highlight,
650
+ h3.redacto-tts-highlight {
651
+ background-color: #FFF9C4 !important;
652
+ border-radius: 3px !important;
653
+ padding: 2px 4px !important;
654
+ margin: -2px -4px !important;
655
+ }
656
+
657
+ span.redacto-tts-highlight,
658
+ a.redacto-tts-highlight {
659
+ background-color: #FFF9C4 !important;
660
+ border-radius: 3px !important;
661
+ padding: 2px 4px !important;
662
+ margin: -2px -4px !important;
663
+ }
664
+
665
+ p.redacto-tts-highlight {
666
+ background-color: #FFF9C4 !important;
667
+ border-radius: 3px !important;
668
+ padding: 2px 4px !important;
669
+ margin: -2px -4px !important;
670
+ }
671
+
672
+ button.redacto-tts-highlight {
673
+ outline: 3px solid #FFF9C4 !important;
674
+ outline-offset: 2px !important;
675
+ }
676
+
677
+ /* Overlay fade-in */
678
+ @keyframes overlayFadeIn {
679
+ from { opacity: 0; }
680
+ to { opacity: 1; }
681
+ }
682
+
683
+ /* Spinner animation */
684
+ @keyframes spin {
685
+ from { transform: rotate(0deg); }
686
+ to { transform: rotate(360deg); }
687
+ }
688
+
689
+ /* DigiLocker callback retry button hover */
690
+ .redacto-dl-retry-btn:hover {
691
+ background-color: #2563eb !important;
692
+ }
693
+ `;
694
+ document.head.appendChild(style);
695
+ stylesInjected = true;
696
+ };
697
+
698
+ // src/styles/modal.ts
699
+ var styles = {
700
+ overlay: {
701
+ position: "fixed",
702
+ top: 0,
703
+ left: 0,
704
+ width: "100%",
705
+ height: "100%",
706
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
707
+ display: "flex",
708
+ alignItems: "center",
709
+ justifyContent: "center",
710
+ zIndex: 999,
711
+ animation: "overlayFadeIn 0.2s cubic-bezier(0.16, 1, 0.3, 1)",
712
+ pointerEvents: "all",
713
+ margin: 0,
714
+ padding: 0,
715
+ boxSizing: "border-box"
716
+ },
717
+ modal: {
718
+ position: "fixed",
719
+ top: "50%",
720
+ left: "50%",
721
+ transform: "translate(-50%, -50%)",
722
+ width: "700px",
723
+ borderRadius: "8px",
724
+ backgroundColor: "#ffffff",
725
+ boxShadow: "4px 0px 4px 0px #00000040",
726
+ border: "none",
727
+ display: "flex",
728
+ flexDirection: "column",
729
+ maxHeight: "90vh",
730
+ zIndex: 1e3,
731
+ margin: 0,
732
+ padding: 0,
733
+ boxSizing: "border-box",
734
+ textAlign: "left",
735
+ fontFamily: "inherit"
736
+ },
737
+ content: {
738
+ margin: "22px",
739
+ display: "flex",
740
+ flexDirection: "column",
741
+ flexGrow: 1,
742
+ overflow: "hidden",
743
+ minHeight: 0,
744
+ boxSizing: "border-box",
745
+ textAlign: "left"
746
+ },
747
+ topSection: {
748
+ display: "flex",
749
+ justifyContent: "space-between",
750
+ alignItems: "center",
751
+ paddingBottom: "15px",
752
+ borderBottom: "none",
753
+ backgroundColor: "#ffffff",
754
+ margin: 0,
755
+ boxSizing: "border-box",
756
+ textAlign: "left"
757
+ },
758
+ topLeft: {
759
+ display: "flex",
760
+ alignItems: "center",
761
+ gap: "10px",
762
+ margin: 0,
763
+ padding: 0,
764
+ boxSizing: "border-box"
765
+ },
766
+ logo: {
767
+ height: "32px",
768
+ width: "auto",
769
+ objectFit: "contain",
770
+ margin: 0,
771
+ padding: 0,
772
+ display: "block"
773
+ },
774
+ title: {
775
+ fontSize: "18px",
776
+ fontWeight: 700,
777
+ lineHeight: "150%",
778
+ letterSpacing: "0.2px",
779
+ verticalAlign: "middle",
780
+ color: "#101828",
781
+ margin: 0,
782
+ padding: 0,
783
+ textAlign: "left"
784
+ },
785
+ topRight: {
786
+ borderRadius: "8px",
787
+ padding: "3px 9px",
788
+ border: "1px solid #d0d5dd",
789
+ display: "flex",
790
+ alignItems: "center",
791
+ justifyContent: "center",
792
+ fontWeight: 400,
793
+ fontSize: "12px",
794
+ lineHeight: "150%",
795
+ letterSpacing: "0.2px",
796
+ verticalAlign: "middle",
797
+ color: "#344054",
798
+ gap: "5px",
799
+ margin: 0,
800
+ boxSizing: "border-box",
801
+ textAlign: "left",
802
+ cursor: "pointer",
803
+ backgroundColor: "#ffffff"
804
+ },
805
+ middleSection: {
806
+ margin: "0px",
807
+ display: "flex",
808
+ flexDirection: "column",
809
+ gap: "12px",
810
+ flexGrow: 1,
811
+ overflowY: "auto",
812
+ minHeight: 0,
813
+ paddingRight: "15px",
814
+ boxSizing: "border-box",
815
+ textAlign: "left"
816
+ },
817
+ privacyText: {
818
+ fontSize: "16px",
819
+ fontWeight: 400,
820
+ lineHeight: "150%",
821
+ color: "#344054",
822
+ margin: 0,
823
+ padding: 0,
824
+ textAlign: "left"
825
+ },
826
+ link: {
827
+ color: "#4f87ff",
828
+ textDecoration: "none",
829
+ margin: 0,
830
+ padding: 0,
831
+ boxSizing: "border-box"
832
+ },
833
+ subTitle: {
834
+ fontWeight: 600,
835
+ fontSize: "16px",
836
+ lineHeight: "150%",
837
+ letterSpacing: "0.2px",
838
+ color: "#101828",
839
+ margin: 0,
840
+ padding: 0,
841
+ textAlign: "left"
842
+ },
843
+ optionsContainer: {
844
+ display: "flex",
845
+ flexDirection: "column",
846
+ gap: "10px",
847
+ margin: 0,
848
+ padding: 0,
849
+ boxSizing: "border-box"
850
+ },
851
+ optionItem: {
852
+ display: "flex",
853
+ justifyContent: "space-between",
854
+ alignItems: "center",
855
+ margin: 0,
856
+ padding: 0,
857
+ boxSizing: "border-box"
858
+ },
859
+ optionLeft: {
860
+ display: "flex",
861
+ gap: "12px",
862
+ alignItems: "center",
863
+ cursor: "pointer",
864
+ margin: 0,
865
+ padding: 0,
866
+ boxSizing: "border-box"
867
+ },
868
+ optionTextContainer: {
869
+ display: "flex",
870
+ flexDirection: "column",
871
+ margin: 0,
872
+ padding: 0,
873
+ boxSizing: "border-box"
874
+ },
875
+ optionTitle: {
876
+ fontWeight: 500,
877
+ fontSize: "16px",
878
+ lineHeight: "150%",
879
+ letterSpacing: "0.2px",
880
+ color: "#101828",
881
+ margin: 0,
882
+ padding: 0,
883
+ textAlign: "left"
884
+ },
885
+ optionDescription: {
886
+ fontWeight: 400,
887
+ fontSize: "12px",
888
+ lineHeight: "150%",
889
+ letterSpacing: "0.2px",
890
+ color: "#475467",
891
+ verticalAlign: "middle",
892
+ margin: 0,
893
+ padding: 0,
894
+ textAlign: "left"
895
+ },
896
+ checkboxLarge: {
897
+ height: "20px",
898
+ width: "20px",
899
+ padding: "4px",
900
+ borderRadius: "5px",
901
+ border: "2px solid #d0d5dd",
902
+ margin: 0,
903
+ boxSizing: "border-box",
904
+ appearance: "none",
905
+ WebkitAppearance: "none",
906
+ MozAppearance: "none",
907
+ cursor: "pointer",
908
+ position: "relative",
909
+ backgroundColor: "transparent"
910
+ },
911
+ dataElementsContainer: {
912
+ marginLeft: "27px",
913
+ display: "flex",
914
+ flexDirection: "column",
915
+ margin: 0,
916
+ padding: 0,
917
+ boxSizing: "border-box",
918
+ paddingLeft: "22px"
919
+ },
920
+ dataElementItem: {
921
+ display: "flex",
922
+ alignItems: "center",
923
+ gap: "10px",
924
+ justifyContent: "space-between",
925
+ margin: 0,
926
+ padding: 0,
927
+ boxSizing: "border-box",
928
+ minHeight: "24px"
929
+ },
930
+ dataElementText: {
931
+ fontWeight: 400,
932
+ fontSize: "14px",
933
+ lineHeight: "150%",
934
+ letterSpacing: "0.2px",
935
+ color: "#344054",
936
+ margin: 0,
937
+ padding: 0,
938
+ textAlign: "left"
939
+ },
940
+ checkboxSmall: {
941
+ height: "16px",
942
+ width: "16px",
943
+ padding: "4px",
944
+ borderRadius: "5px",
945
+ border: "2px solid #d0d5dd",
946
+ margin: 0,
947
+ boxSizing: "border-box",
948
+ appearance: "none",
949
+ WebkitAppearance: "none",
950
+ MozAppearance: "none",
951
+ cursor: "pointer",
952
+ position: "relative",
953
+ backgroundColor: "transparent"
954
+ },
955
+ bottomSection: {
956
+ display: "flex",
957
+ justifyContent: "space-between",
958
+ gap: "29px",
959
+ paddingTop: "15px",
960
+ borderTop: "none",
961
+ backgroundColor: "#ffffff",
962
+ margin: 0,
963
+ boxSizing: "border-box"
964
+ },
965
+ button: {
966
+ width: "100%",
967
+ borderRadius: "8px",
968
+ padding: "9px 45px",
969
+ fontWeight: 400,
970
+ fontSize: "16px",
971
+ lineHeight: "150%",
972
+ letterSpacing: "0.2px",
973
+ cursor: "pointer",
974
+ margin: 0,
975
+ boxSizing: "border-box",
976
+ textAlign: "center"
977
+ },
978
+ acceptButton: {
979
+ backgroundColor: "#4f87ff",
980
+ border: "none",
981
+ color: "#ffffff"
982
+ },
983
+ cancelButton: {
984
+ border: "1px solid #d0d5dd",
985
+ backgroundColor: "#ffffff",
986
+ color: "#000000"
987
+ },
988
+ languageSelectorContainer: {
989
+ position: "relative",
990
+ margin: 0,
991
+ padding: 0,
992
+ boxSizing: "border-box"
993
+ },
994
+ languageDropdown: {
995
+ position: "absolute",
996
+ top: "100%",
997
+ right: 0,
998
+ backgroundColor: "#ffffff",
999
+ border: "1px solid #d0d5dd",
1000
+ borderRadius: "4px",
1001
+ boxShadow: "0px 2px 4px rgba(0, 0, 0, 0.1)",
1002
+ zIndex: 10,
1003
+ width: "max-content",
1004
+ maxHeight: "200px",
1005
+ overflowY: "auto",
1006
+ marginTop: "4px",
1007
+ padding: 0,
1008
+ boxSizing: "border-box",
1009
+ textAlign: "left"
1010
+ },
1011
+ languageItem: {
1012
+ padding: "8px 12px",
1013
+ cursor: "pointer",
1014
+ fontSize: "12px",
1015
+ color: "#344054",
1016
+ backgroundColor: "#ffffff",
1017
+ borderBottom: "1px solid #e5e7eb",
1018
+ margin: 0,
1019
+ boxSizing: "border-box",
1020
+ textAlign: "left"
1021
+ },
1022
+ selectedLanguageItem: {
1023
+ backgroundColor: "#f3f4f6",
1024
+ fontWeight: 600
1025
+ },
1026
+ loadingContainer: {
1027
+ display: "flex",
1028
+ flexDirection: "column",
1029
+ alignItems: "center",
1030
+ justifyContent: "center",
1031
+ padding: "2rem",
1032
+ minHeight: "200px",
1033
+ margin: 0,
1034
+ boxSizing: "border-box"
1035
+ },
1036
+ loadingSpinner: {
1037
+ width: "40px",
1038
+ height: "40px",
1039
+ border: "4px solid #f3f3f3",
1040
+ borderTop: "4px solid #3498db",
1041
+ borderRadius: "50%",
1042
+ animation: "spin 1s linear infinite",
1043
+ marginBottom: "1rem",
1044
+ boxSizing: "border-box"
1045
+ }
1046
+ };
1047
+
1048
+ // src/modal/purposes.ts
1049
+ function renderModalPurposes(purposes, options) {
1050
+ var _a, _b;
1051
+ const {
1052
+ selectedPurposes,
1053
+ collapsedPurposes,
1054
+ selectedDataElements,
1055
+ initialDataElementSelections = {},
1056
+ settings,
1057
+ isMobile,
1058
+ isAlreadyConsented,
1059
+ getTranslatedText,
1060
+ onPurposeToggle,
1061
+ onPurposeCollapse,
1062
+ onDataElementToggle
1063
+ } = options;
1064
+ const checkboxes = /* @__PURE__ */ new Map();
1065
+ const collapseButtons = /* @__PURE__ */ new Map();
1066
+ const dataElementContainers = /* @__PURE__ */ new Map();
1067
+ const chevrons = /* @__PURE__ */ new Map();
1068
+ const titleElements = /* @__PURE__ */ new Map();
1069
+ const descriptionElements = /* @__PURE__ */ new Map();
1070
+ const dataElementLabels = /* @__PURE__ */ new Map();
1071
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || "#323B4B";
1072
+ const textColor = (settings == null ? void 0 : settings.textColor) || "#344054";
1073
+ const font = (settings == null ? void 0 : settings.font) || "inherit";
1074
+ const container = el("div", { style: styles.optionsContainer });
1075
+ for (const purpose of purposes) {
1076
+ const purposeWrapper = el("div");
1077
+ const chevron = svg(
1078
+ "svg",
1079
+ {
1080
+ width: "7",
1081
+ height: "12",
1082
+ viewBox: "0 0 7 12",
1083
+ fill: "none",
1084
+ xmlns: "http://www.w3.org/2000/svg",
1085
+ stroke: headingColor,
1086
+ "stroke-width": "2",
1087
+ "stroke-linecap": "round",
1088
+ "stroke-linejoin": "round",
1089
+ style: {
1090
+ transform: !collapsedPurposes[purpose.uuid] ? "rotate(90deg)" : "rotate(0deg)",
1091
+ transition: "transform 0.3s ease"
1092
+ },
1093
+ "aria-hidden": "true"
1094
+ },
1095
+ svg("path", { d: "M1 1L6 6L1 11" })
1096
+ );
1097
+ chevrons.set(purpose.uuid, chevron);
1098
+ const chevronContainer = el(
1099
+ "div",
1100
+ { style: { display: "flex", alignItems: "center", justifyContent: "center", marginLeft: "5px" } },
1101
+ chevron
1102
+ );
1103
+ const titleEl = el("h2", {
1104
+ id: `purpose-name-${purpose.uuid}`,
1105
+ style: {
1106
+ ...styles.optionTitle,
1107
+ fontSize: isMobile ? "14px" : "16px",
1108
+ color: headingColor,
1109
+ fontFamily: font
1110
+ }
1111
+ });
1112
+ titleEl.textContent = getTranslatedText("purposes.name", purpose.name, purpose.uuid);
1113
+ titleElements.set(purpose.uuid, titleEl);
1114
+ const descEl = el("h3", {
1115
+ id: `purpose-description-${purpose.uuid}`,
1116
+ style: {
1117
+ ...styles.optionDescription,
1118
+ fontSize: isMobile ? "11px" : "12px",
1119
+ color: textColor,
1120
+ fontFamily: font
1121
+ }
1122
+ });
1123
+ descEl.textContent = getTranslatedText("purposes.description", purpose.description, purpose.uuid);
1124
+ descriptionElements.set(purpose.uuid, descEl);
1125
+ const textContainer = el("div", { style: styles.optionTextContainer }, titleEl, descEl);
1126
+ const collapseBtn = el("div", {
1127
+ style: styles.optionLeft,
1128
+ role: "button",
1129
+ tabIndex: "0",
1130
+ "aria-expanded": String(!collapsedPurposes[purpose.uuid]),
1131
+ "aria-controls": `purpose-${purpose.uuid}`,
1132
+ "aria-label": `${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)} details`,
1133
+ onClick: () => onPurposeCollapse(purpose.uuid),
1134
+ onKeydown: (e) => {
1135
+ if (e.key === "Enter" || e.key === " ") {
1136
+ e.preventDefault();
1137
+ onPurposeCollapse(purpose.uuid);
1138
+ }
1139
+ }
1140
+ }, chevronContainer, textContainer);
1141
+ collapseButtons.set(purpose.uuid, collapseBtn);
1142
+ let rightElement;
1143
+ if (!isAlreadyConsented) {
1144
+ const purposeCheckbox = el("input", {
1145
+ className: "redacto-checkbox-large",
1146
+ type: "checkbox",
1147
+ checked: selectedPurposes[purpose.uuid] || false,
1148
+ "aria-label": `Select all data elements for ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)}`,
1149
+ onChange: () => onPurposeToggle(purpose.uuid)
1150
+ });
1151
+ checkboxes.set(purpose.uuid, purposeCheckbox);
1152
+ rightElement = purposeCheckbox;
1153
+ } else {
1154
+ rightElement = createGreenCheckmark(
1155
+ 20,
1156
+ `Already consented to ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)}`
1157
+ );
1158
+ applyStyles(rightElement, { marginLeft: "12px" });
1159
+ }
1160
+ const optionRow = el("div", { style: styles.optionItem }, collapseBtn, rightElement);
1161
+ const deContainer = el("div", {
1162
+ id: `purpose-${purpose.uuid}`,
1163
+ style: styles.dataElementsContainer
1164
+ });
1165
+ if (((_a = purpose.data_elements) == null ? void 0 : _a.length) > 0) {
1166
+ for (const dataElement of purpose.data_elements) {
1167
+ const combinedId = `${purpose.uuid}-${dataElement.uuid}`;
1168
+ const labelSpan = el("span", {
1169
+ id: `data-element-${purpose.uuid}-${dataElement.uuid}`,
1170
+ style: {
1171
+ ...styles.dataElementText,
1172
+ fontSize: isMobile ? "12px" : "14px",
1173
+ color: textColor,
1174
+ fontFamily: font
1175
+ }
1176
+ });
1177
+ labelSpan.textContent = getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid);
1178
+ dataElementLabels.set(combinedId, labelSpan);
1179
+ if (dataElement.required) {
1180
+ const asterisk = el("span", { style: { color: "red", marginLeft: "4px" }, "aria-label": "required" });
1181
+ asterisk.textContent = "*";
1182
+ labelSpan.appendChild(asterisk);
1183
+ }
1184
+ let deRightElement;
1185
+ if (!isAlreadyConsented) {
1186
+ const deCheckbox = el("input", {
1187
+ className: "redacto-checkbox-small",
1188
+ type: "checkbox",
1189
+ checked: selectedDataElements[combinedId] || false,
1190
+ "aria-label": `Select ${getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid)}${dataElement.required ? " (required)" : ""}`,
1191
+ onChange: () => onDataElementToggle(dataElement.uuid, purpose.uuid)
1192
+ });
1193
+ checkboxes.set(combinedId, deCheckbox);
1194
+ deRightElement = deCheckbox;
1195
+ } else {
1196
+ const wasSelected = initialDataElementSelections[combinedId] || false;
1197
+ if (wasSelected) {
1198
+ deRightElement = createGreenCheckmark(
1199
+ 16,
1200
+ `Consented to ${getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid)}`
1201
+ );
1202
+ } else {
1203
+ deRightElement = el("div", {
1204
+ style: {
1205
+ display: "flex",
1206
+ alignItems: "center",
1207
+ justifyContent: "center",
1208
+ width: "16px",
1209
+ height: "16px",
1210
+ borderRadius: "50%",
1211
+ border: "2px solid #d0d5dd",
1212
+ backgroundColor: "transparent"
1213
+ },
1214
+ "aria-label": `Not consented to ${getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid)}`
1215
+ });
1216
+ }
1217
+ }
1218
+ const deRow = el("div", { style: styles.dataElementItem }, labelSpan, deRightElement);
1219
+ deContainer.appendChild(deRow);
1220
+ }
1221
+ }
1222
+ dataElementContainers.set(purpose.uuid, deContainer);
1223
+ deContainer.style.display = !collapsedPurposes[purpose.uuid] && ((_b = purpose.data_elements) == null ? void 0 : _b.length) > 0 ? "flex" : "none";
1224
+ purposeWrapper.appendChild(optionRow);
1225
+ purposeWrapper.appendChild(deContainer);
1226
+ container.appendChild(purposeWrapper);
1227
+ }
1228
+ return {
1229
+ container,
1230
+ checkboxes,
1231
+ collapseButtons,
1232
+ dataElementContainers,
1233
+ chevrons,
1234
+ titleElements,
1235
+ descriptionElements,
1236
+ dataElementLabels
1237
+ };
1238
+ }
1239
+ function createGreenCheckmark(size, ariaLabel) {
1240
+ const svgSize = size === 20 ? 12 : 10;
1241
+ const strokeWidth = size === 20 ? "2" : "1.5";
1242
+ const pathD = size === 20 ? "M4 6L5.5 7.5L8.5 4.5" : "M3 5L4.5 6.5L7.5 3.5";
1243
+ const checkSvg = svg(
1244
+ "svg",
1245
+ {
1246
+ width: String(svgSize),
1247
+ height: String(svgSize),
1248
+ viewBox: `0 0 ${svgSize} ${svgSize}`,
1249
+ fill: "none",
1250
+ xmlns: "http://www.w3.org/2000/svg",
1251
+ "aria-hidden": "true"
1252
+ },
1253
+ svg("path", {
1254
+ d: pathD,
1255
+ stroke: "white",
1256
+ "stroke-width": strokeWidth,
1257
+ "stroke-linecap": "round",
1258
+ "stroke-linejoin": "round"
1259
+ })
1260
+ );
1261
+ return el("div", {
1262
+ style: {
1263
+ display: "flex",
1264
+ alignItems: "center",
1265
+ justifyContent: "center",
1266
+ width: `${size}px`,
1267
+ height: `${size}px`,
1268
+ borderRadius: "50%",
1269
+ backgroundColor: "#10B981"
1270
+ },
1271
+ "aria-label": ariaLabel
1272
+ }, checkSvg);
1273
+ }
1274
+
1275
+ // src/modal/language-selector.ts
1276
+ function renderLanguageSelector(options) {
1277
+ var _a, _b, _c, _d;
1278
+ const { languages, selectedLanguage, settings, onSelect } = options;
1279
+ let isOpen = false;
1280
+ const buttonText = el("span");
1281
+ buttonText.textContent = selectedLanguage;
1282
+ const chevronSvg = svg(
1283
+ "svg",
1284
+ {
1285
+ width: "8",
1286
+ height: "5",
1287
+ viewBox: "0 0 8 5",
1288
+ fill: "none",
1289
+ xmlns: "http://www.w3.org/2000/svg"
1290
+ },
1291
+ svg("path", {
1292
+ d: "M1 1L4 4L7 1",
1293
+ stroke: ((_b = (_a = settings == null ? void 0 : settings.button) == null ? void 0 : _a.language) == null ? void 0 : _b.textColor) || "#344054",
1294
+ "stroke-width": "1.5",
1295
+ "stroke-linecap": "round",
1296
+ "stroke-linejoin": "round"
1297
+ })
1298
+ );
1299
+ const buttonStyle = { ...styles.topRight };
1300
+ if ((_c = settings == null ? void 0 : settings.button) == null ? void 0 : _c.language) {
1301
+ buttonStyle.backgroundColor = settings.button.language.backgroundColor;
1302
+ buttonStyle.color = settings.button.language.textColor;
1303
+ }
1304
+ const button = el("div", {
1305
+ style: buttonStyle,
1306
+ role: "button",
1307
+ tabIndex: "0",
1308
+ "aria-haspopup": "listbox",
1309
+ "aria-expanded": "false",
1310
+ "aria-label": `Language selector, current: ${selectedLanguage}`,
1311
+ onClick: () => toggleDropdown(),
1312
+ onKeydown: (e) => {
1313
+ if (e.key === "Enter" || e.key === " ") {
1314
+ e.preventDefault();
1315
+ toggleDropdown();
1316
+ } else if (e.key === "Escape") {
1317
+ closeDropdown();
1318
+ }
1319
+ }
1320
+ }, buttonText, chevronSvg);
1321
+ const dropdown = el("div", {
1322
+ style: {
1323
+ position: "fixed",
1324
+ backgroundColor: "#ffffff",
1325
+ border: "1px solid #d0d5dd",
1326
+ borderRadius: "4px",
1327
+ boxShadow: "0px 4px 12px rgba(0, 0, 0, 0.15)",
1328
+ zIndex: 2147483647,
1329
+ // max int — always on top
1330
+ minWidth: "140px",
1331
+ maxHeight: "200px",
1332
+ overflowY: "auto",
1333
+ padding: 0,
1334
+ boxSizing: "border-box",
1335
+ textAlign: "left",
1336
+ display: "none"
1337
+ },
1338
+ role: "listbox",
1339
+ "aria-label": "Select language"
1340
+ });
1341
+ for (const lang of languages) {
1342
+ const isSelected = lang === selectedLanguage;
1343
+ const itemStyle = isSelected ? { ...styles.languageItem, ...styles.selectedLanguageItem } : { ...styles.languageItem };
1344
+ if ((_d = settings == null ? void 0 : settings.button) == null ? void 0 : _d.language) {
1345
+ if (isSelected && settings.button.language.selectedBackgroundColor) {
1346
+ itemStyle.backgroundColor = settings.button.language.selectedBackgroundColor;
1347
+ }
1348
+ if (isSelected && settings.button.language.selectedTextColor) {
1349
+ itemStyle.color = settings.button.language.selectedTextColor;
1350
+ }
1351
+ }
1352
+ const item = el("div", {
1353
+ style: itemStyle,
1354
+ role: "option",
1355
+ "aria-selected": String(isSelected),
1356
+ onClick: () => {
1357
+ onSelect(lang);
1358
+ closeDropdown();
1359
+ },
1360
+ onMouseenter: (e) => {
1361
+ e.target.style.backgroundColor = "#f3f4f6";
1362
+ },
1363
+ onMouseleave: (e) => {
1364
+ e.target.style.backgroundColor = isSelected ? "#f3f4f6" : "#ffffff";
1365
+ }
1366
+ });
1367
+ item.textContent = lang;
1368
+ dropdown.appendChild(item);
1369
+ }
1370
+ const container = el("div", { style: styles.languageSelectorContainer }, button);
1371
+ if (typeof document !== "undefined") {
1372
+ document.body.appendChild(dropdown);
1373
+ }
1374
+ function positionDropdown() {
1375
+ const rect = button.getBoundingClientRect();
1376
+ dropdown.style.top = `${rect.bottom + 4}px`;
1377
+ dropdown.style.right = `${window.innerWidth - rect.right}px`;
1378
+ dropdown.style.left = "auto";
1379
+ }
1380
+ const clickOutsideHandler = (e) => {
1381
+ const target = e.target;
1382
+ if (!container.contains(target) && !dropdown.contains(target)) {
1383
+ closeDropdown();
1384
+ }
1385
+ };
1386
+ const repositionHandler = () => {
1387
+ if (isOpen) positionDropdown();
1388
+ };
1389
+ function toggleDropdown() {
1390
+ isOpen ? closeDropdown() : openDropdown();
1391
+ }
1392
+ function openDropdown() {
1393
+ isOpen = true;
1394
+ positionDropdown();
1395
+ dropdown.style.display = "block";
1396
+ button.setAttribute("aria-expanded", "true");
1397
+ setTimeout(() => {
1398
+ document.addEventListener("click", clickOutsideHandler);
1399
+ }, 0);
1400
+ window.addEventListener("scroll", repositionHandler, true);
1401
+ window.addEventListener("resize", repositionHandler);
1402
+ }
1403
+ function closeDropdown() {
1404
+ isOpen = false;
1405
+ dropdown.style.display = "none";
1406
+ button.setAttribute("aria-expanded", "false");
1407
+ document.removeEventListener("click", clickOutsideHandler);
1408
+ window.removeEventListener("scroll", repositionHandler, true);
1409
+ window.removeEventListener("resize", repositionHandler);
1410
+ }
1411
+ return { container, button, dropdown, buttonText };
1412
+ }
1413
+
1414
+ // src/modal/keyboard.ts
1415
+ function setupKeyboardTrap(modalEl, onEscape) {
1416
+ const handler = (e) => {
1417
+ if (e.key === "Escape") {
1418
+ e.preventDefault();
1419
+ onEscape();
1420
+ return;
1421
+ }
1422
+ if (e.key === "Tab") {
1423
+ const focusableElements = modalEl.querySelectorAll(
1424
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1425
+ );
1426
+ const focusable = Array.from(focusableElements).filter(
1427
+ (el2) => !el2.hasAttribute("disabled") && el2.offsetParent !== null
1428
+ );
1429
+ if (focusable.length === 0) {
1430
+ e.preventDefault();
1431
+ return;
1432
+ }
1433
+ const first = focusable[0];
1434
+ const last = focusable[focusable.length - 1];
1435
+ if (e.shiftKey) {
1436
+ if (document.activeElement === first) {
1437
+ e.preventDefault();
1438
+ last.focus();
1439
+ }
1440
+ } else {
1441
+ if (document.activeElement === last) {
1442
+ e.preventDefault();
1443
+ first.focus();
1444
+ }
1445
+ }
1446
+ }
1447
+ };
1448
+ document.addEventListener("keydown", handler);
1449
+ requestAnimationFrame(() => {
1450
+ const firstFocusable = modalEl.querySelector(
1451
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
1452
+ );
1453
+ firstFocusable == null ? void 0 : firstFocusable.focus();
1454
+ });
1455
+ return () => {
1456
+ document.removeEventListener("keydown", handler);
1457
+ };
1458
+ }
1459
+
1460
+ // src/services/storage.ts
1461
+ var STORAGE_KEY = "redacto_consent_sdk_state";
1462
+ var STATE_EXPIRY_MS = 30 * 60 * 1e3;
1463
+ var isBrowser = () => {
1464
+ return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
1465
+ };
1466
+ var saveConsentState = (state) => {
1467
+ if (!isBrowser()) {
1468
+ console.warn("SessionStorage not available - state not persisted");
1469
+ return;
1470
+ }
1471
+ try {
1472
+ const stateWithTimestamp = {
1473
+ ...state,
1474
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1475
+ };
1476
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(stateWithTimestamp));
1477
+ } catch (error) {
1478
+ console.error("Failed to save consent state:", error);
1479
+ }
1480
+ };
1481
+ var getConsentState = () => {
1482
+ if (!isBrowser()) return null;
1483
+ try {
1484
+ const savedState = sessionStorage.getItem(STORAGE_KEY);
1485
+ if (!savedState) return null;
1486
+ const parsed = JSON.parse(savedState);
1487
+ const stateAge = Date.now() - new Date(parsed.timestamp).getTime();
1488
+ if (stateAge > STATE_EXPIRY_MS) {
1489
+ clearConsentState();
1490
+ return null;
1491
+ }
1492
+ return parsed;
1493
+ } catch (error) {
1494
+ console.error("Failed to get consent state:", error);
1495
+ return null;
1496
+ }
1497
+ };
1498
+ var clearConsentState = () => {
1499
+ if (!isBrowser()) return;
1500
+ try {
1501
+ sessionStorage.removeItem(STORAGE_KEY);
1502
+ } catch (error) {
1503
+ console.error("Failed to clear consent state:", error);
1504
+ }
1505
+ };
1506
+ var updateConsentState = (updates) => {
1507
+ const currentState = getConsentState();
1508
+ if (currentState) {
1509
+ saveConsentState({
1510
+ ...currentState,
1511
+ ...updates,
1512
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1513
+ });
1514
+ }
1515
+ };
1516
+
1517
+ // src/constants.ts
1518
+ var DPO_AUDIO_ELEMENT_MAP = [
1519
+ { audioKey: "grievance_text_audio_url", elementId: "dpo-grievance-text" },
1520
+ { audioKey: "grievance_anchor_text_audio_url", elementId: "dpo-grievance-link" },
1521
+ { audioKey: "grievance_email_connector_text_audio_url", elementId: "dpo-grievance-email-connector" },
1522
+ { audioKey: "grievance_email_audio_url", elementId: "dpo-grievance-email" },
1523
+ { audioKey: "dp_board_text_audio_url", elementId: "dpo-dp-board-text" },
1524
+ { audioKey: "dp_board_anchor_text_audio_url", elementId: "dpo-dp-board-link" },
1525
+ { audioKey: "dpo_text_audio_url", elementId: "dpo-dpo-text" },
1526
+ { audioKey: "dpo_anchor_text_audio_url", elementId: "dpo-dpo-link" }
1527
+ ];
1528
+
1529
+ // src/modal/tts.ts
1530
+ var TTSManager = class {
1531
+ constructor(onStateChange) {
1532
+ this.audioUrls = null;
1533
+ this.audioSequence = [];
1534
+ this.elementIdSequence = [];
1535
+ this.currentAudio = null;
1536
+ this.currentIndex = 0;
1537
+ this._isPlaying = false;
1538
+ this._isPaused = false;
1539
+ this._isAvailable = false;
1540
+ this.highlightedElementId = null;
1541
+ this.abortController = null;
1542
+ this.onStateChange = onStateChange;
1543
+ }
1544
+ get isPlaying() {
1545
+ return this._isPlaying;
1546
+ }
1547
+ get isPaused() {
1548
+ return this._isPaused;
1549
+ }
1550
+ get isAvailable() {
1551
+ return this._isAvailable;
1552
+ }
1553
+ async fetchAudioUrls(params) {
1554
+ var _a;
1555
+ this.stop();
1556
+ this._isAvailable = false;
1557
+ this.audioUrls = null;
1558
+ this.onStateChange();
1559
+ (_a = this.abortController) == null ? void 0 : _a.abort();
1560
+ this.abortController = new AbortController();
1561
+ try {
1562
+ const response = await fetchTTSAudioUrls({
1563
+ accessToken: params.accessToken,
1564
+ baseUrl: params.baseUrl,
1565
+ noticeUuid: params.noticeUuid,
1566
+ language: params.language,
1567
+ signal: this.abortController.signal
1568
+ });
1569
+ this.audioUrls = response.detail;
1570
+ this._isAvailable = true;
1571
+ this.buildSequences(params.content);
1572
+ this.onStateChange();
1573
+ } catch (error) {
1574
+ if (error.name !== "AbortError") {
1575
+ this._isAvailable = false;
1576
+ this.audioUrls = null;
1577
+ this.onStateChange();
1578
+ }
1579
+ }
1580
+ }
1581
+ buildSequences(content) {
1582
+ var _a, _b;
1583
+ if (!this.audioUrls) return;
1584
+ const urls = this.audioUrls;
1585
+ const audioSeq = [];
1586
+ const elementSeq = [];
1587
+ const push = (url, elementId) => {
1588
+ if (url) {
1589
+ audioSeq.push(url);
1590
+ elementSeq.push(elementId);
1591
+ }
1592
+ };
1593
+ push(urls.notice_banner_heading_audio_url, "privacy-notice-title");
1594
+ push(urls.notice_text_audio_url, "privacy-notice-text");
1595
+ push(urls.privacy_policy_prefix_text_audio_url, "privacy-policy-text");
1596
+ push(urls.privacy_policy_anchor_text_audio_url, "privacy-policy-link");
1597
+ push(urls.purpose_section_heading_audio_url, "purpose-section-heading");
1598
+ for (const purpose of content.purposes) {
1599
+ const purposeAudio = (_a = urls.purposes_audio) == null ? void 0 : _a[purpose.uuid];
1600
+ if (purposeAudio) {
1601
+ push(purposeAudio.name_audio_url, `purpose-name-${purpose.uuid}`);
1602
+ push(purposeAudio.description_audio_url, `purpose-description-${purpose.uuid}`);
1603
+ }
1604
+ for (const de of purpose.data_elements) {
1605
+ const deAudio = (_b = urls.data_elements_audio) == null ? void 0 : _b[de.uuid];
1606
+ if (deAudio) {
1607
+ push(deAudio.name_audio_url, `data-element-${purpose.uuid}-${de.uuid}`);
1608
+ }
1609
+ }
1610
+ }
1611
+ push(urls.additional_text_audio_url, "additional-text");
1612
+ push(urls.privacy_center_anchor_text_audio_url, "privacy-center-link");
1613
+ if (urls.dpo_info_audio) {
1614
+ for (const mapping of DPO_AUDIO_ELEMENT_MAP) {
1615
+ const audioUrl = urls.dpo_info_audio[mapping.audioKey];
1616
+ push(audioUrl, mapping.elementId);
1617
+ }
1618
+ }
1619
+ push(urls.confirm_button_text_audio_url, "confirm-button-text");
1620
+ push(urls.decline_button_text_audio_url, "decline-button-text");
1621
+ this.audioSequence = audioSeq;
1622
+ this.elementIdSequence = elementSeq;
1623
+ }
1624
+ toggle() {
1625
+ this._isPlaying ? this.pause() : this.play();
1626
+ }
1627
+ play() {
1628
+ if (this.audioSequence.length === 0) return;
1629
+ if (this._isPaused && this.currentAudio) {
1630
+ this.currentAudio.play().then(() => {
1631
+ this._isPlaying = true;
1632
+ this._isPaused = false;
1633
+ this.onStateChange();
1634
+ }).catch(() => {
1635
+ });
1636
+ } else {
1637
+ const startIndex = this._isPlaying ? this.currentIndex : 0;
1638
+ this.playNextAudio(startIndex);
1639
+ }
1640
+ }
1641
+ pause() {
1642
+ if (this.currentAudio && this._isPlaying) {
1643
+ this.currentAudio.pause();
1644
+ this._isPlaying = false;
1645
+ this._isPaused = true;
1646
+ this.onStateChange();
1647
+ }
1648
+ }
1649
+ stop() {
1650
+ if (this.currentAudio) {
1651
+ this.currentAudio.pause();
1652
+ this.currentAudio.currentTime = 0;
1653
+ this.currentAudio = null;
1654
+ }
1655
+ this._isPlaying = false;
1656
+ this._isPaused = false;
1657
+ this.currentIndex = 0;
1658
+ this.removeHighlights();
1659
+ this.highlightedElementId = null;
1660
+ this.onStateChange();
1661
+ }
1662
+ destroy() {
1663
+ var _a;
1664
+ (_a = this.abortController) == null ? void 0 : _a.abort();
1665
+ this.stop();
1666
+ }
1667
+ playNextAudio(index) {
1668
+ if (index >= this.audioSequence.length) {
1669
+ this.stop();
1670
+ return;
1671
+ }
1672
+ if (this.currentAudio) {
1673
+ this.currentAudio.pause();
1674
+ this.currentAudio = null;
1675
+ }
1676
+ const audio = new Audio(this.audioSequence[index]);
1677
+ this.currentAudio = audio;
1678
+ this.currentIndex = index;
1679
+ this._isPlaying = true;
1680
+ this._isPaused = false;
1681
+ const elementId = this.elementIdSequence[index];
1682
+ if (elementId) {
1683
+ this.highlightElement(elementId);
1684
+ }
1685
+ audio.onended = () => this.playNextAudio(index + 1);
1686
+ audio.onerror = () => {
1687
+ console.warn(`Failed to load audio at index ${index}`);
1688
+ this.playNextAudio(index + 1);
1689
+ };
1690
+ audio.play().catch((error) => {
1691
+ console.warn(`Failed to play audio at index ${index}:`, error);
1692
+ this._isPlaying = false;
1693
+ this.onStateChange();
1694
+ });
1695
+ this.onStateChange();
1696
+ }
1697
+ highlightElement(elementId) {
1698
+ this.removeHighlights();
1699
+ this.highlightedElementId = elementId;
1700
+ setTimeout(() => {
1701
+ const element = document.getElementById(elementId);
1702
+ if (element) {
1703
+ element.classList.add("redacto-tts-highlight");
1704
+ const rect = element.getBoundingClientRect();
1705
+ const isVisible = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
1706
+ if (!isVisible) {
1707
+ element.scrollIntoView({
1708
+ behavior: "smooth",
1709
+ block: "center",
1710
+ inline: "nearest"
1711
+ });
1712
+ }
1713
+ }
1714
+ }, 50);
1715
+ }
1716
+ removeHighlights() {
1717
+ document.querySelectorAll(".redacto-tts-highlight").forEach((el2) => {
1718
+ el2.classList.remove("redacto-tts-highlight");
1719
+ });
1720
+ }
1721
+ };
1722
+
1723
+ // src/modal/age-verification.ts
1724
+ function renderAgeVerification(options) {
1725
+ const { settings, colors, isMobile, logoUrl, onYes, onNo, onClose } = options;
1726
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || "#101828";
1727
+ const textColor = colors.text;
1728
+ const font = colors.font;
1729
+ const logo = el("img", { src: logoUrl, style: styles.logo, alt: "Redacto Logo" });
1730
+ const title = el("h2", {
1731
+ id: "age-verification-title",
1732
+ style: {
1733
+ ...styles.title,
1734
+ fontSize: isMobile ? "16px" : "18px",
1735
+ color: headingColor,
1736
+ fontFamily: font
1737
+ }
1738
+ });
1739
+ title.textContent = "Age Verification Required";
1740
+ const topLeft = el("div", { style: styles.topLeft }, logo, title);
1741
+ const closeBtn = el("button", {
1742
+ style: {
1743
+ background: "none",
1744
+ border: "none",
1745
+ color: headingColor,
1746
+ cursor: "pointer",
1747
+ padding: isMobile ? "2px" : "4px",
1748
+ display: "flex",
1749
+ alignItems: "center",
1750
+ justifyContent: "center"
1751
+ },
1752
+ type: "button",
1753
+ "aria-label": "Close modal",
1754
+ onClick: onClose
1755
+ });
1756
+ const closeSvg = svg(
1757
+ "svg",
1758
+ {
1759
+ width: isMobile ? "18" : "20",
1760
+ height: isMobile ? "18" : "20",
1761
+ viewBox: "0 0 20 20",
1762
+ fill: "none",
1763
+ xmlns: "http://www.w3.org/2000/svg",
1764
+ "aria-hidden": "true"
1765
+ },
1766
+ svg("path", {
1767
+ d: "M15 5L5 15M5 5L15 15",
1768
+ stroke: "currentColor",
1769
+ "stroke-width": "2",
1770
+ "stroke-linecap": "round",
1771
+ "stroke-linejoin": "round"
1772
+ })
1773
+ );
1774
+ closeBtn.appendChild(closeSvg);
1775
+ const topSection = el("div", {
1776
+ style: { ...styles.topSection, color: textColor, fontFamily: font }
1777
+ }, topLeft, closeBtn);
1778
+ const description = el("p", {
1779
+ style: {
1780
+ ...styles.privacyText,
1781
+ fontSize: isMobile ? "14px" : "16px",
1782
+ color: textColor,
1783
+ fontFamily: font,
1784
+ marginBottom: "16px"
1785
+ }
1786
+ });
1787
+ description.textContent = "To proceed with this consent form, we need to verify your age.";
1788
+ const question = el("p", {
1789
+ style: {
1790
+ ...styles.subTitle,
1791
+ color: textColor,
1792
+ fontFamily: font,
1793
+ marginBottom: "24px",
1794
+ textAlign: "left"
1795
+ }
1796
+ });
1797
+ question.textContent = "Are you 18 years of age or older?";
1798
+ const middleSection = el("div", {
1799
+ style: { ...styles.middleSection, color: textColor, fontFamily: font }
1800
+ }, description, question);
1801
+ const btnBase = {
1802
+ ...styles.button,
1803
+ fontSize: isMobile ? "14px" : "16px",
1804
+ padding: isMobile ? "10px 20px" : "9px 45px"
1805
+ };
1806
+ const yesBtn = el("button", {
1807
+ style: {
1808
+ ...btnBase,
1809
+ ...styles.acceptButton,
1810
+ backgroundColor: colors.acceptBg,
1811
+ color: colors.acceptText,
1812
+ borderRadius: colors.borderRadius
1813
+ },
1814
+ type: "button",
1815
+ "aria-label": "Confirm that I am 18 years of age or older",
1816
+ onClick: onYes
1817
+ });
1818
+ yesBtn.textContent = "Yes, I am 18 or older";
1819
+ const noBtn = el("button", {
1820
+ style: {
1821
+ ...btnBase,
1822
+ ...styles.cancelButton,
1823
+ backgroundColor: colors.declineBg,
1824
+ color: colors.declineText,
1825
+ borderColor: colors.declineBorder,
1826
+ borderRadius: colors.borderRadius
1827
+ },
1828
+ type: "button",
1829
+ "aria-label": "Indicate that I am under 18 years of age",
1830
+ onClick: onNo
1831
+ });
1832
+ noBtn.textContent = "No, I am under 18";
1833
+ const bottomSection = el("div", {
1834
+ style: { ...styles.bottomSection, color: textColor, fontFamily: font }
1835
+ }, yesBtn, noBtn);
1836
+ const container = el("div", { style: styles.content }, topSection, middleSection, bottomSection);
1837
+ return { container };
1838
+ }
1839
+
1840
+ // src/modal/guardian-form.ts
1841
+ var RELATIONSHIP_OPTIONS = [
1842
+ { value: "Father", label: "Father" },
1843
+ { value: "Mother", label: "Mother" },
1844
+ { value: "Legal Guardian", label: "Legal Guardian" },
1845
+ { value: "Grandparent", label: "Grandparent" }
1846
+ ];
1847
+ function renderGuardianForm(options) {
1848
+ var _a;
1849
+ const { formData, settings, colors, isMobile, logoUrl, isSubmitting, onSubmit, onCancel, onFieldChange } = options;
1850
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || "#101828";
1851
+ const textColor = colors.text;
1852
+ const borderColor = colors.declineBorder;
1853
+ const bgColor = (settings == null ? void 0 : settings.backgroundColor) || "#ffffff";
1854
+ const radius = colors.borderRadius;
1855
+ const font = colors.font;
1856
+ const accentColor = colors.acceptBg;
1857
+ const inputStyle = {
1858
+ width: "100%",
1859
+ padding: "10px 12px",
1860
+ border: `1px solid ${borderColor}`,
1861
+ borderRadius: radius,
1862
+ fontSize: "13px",
1863
+ fontFamily: font,
1864
+ color: textColor,
1865
+ backgroundColor: bgColor,
1866
+ marginBottom: "2px",
1867
+ boxSizing: "border-box",
1868
+ outline: "none"
1869
+ };
1870
+ const errorStyle = {
1871
+ color: "#DC2626",
1872
+ fontSize: "11px",
1873
+ marginTop: "4px",
1874
+ marginBottom: "0px",
1875
+ display: "none"
1876
+ };
1877
+ const labelStyle = {
1878
+ fontSize: "13px",
1879
+ fontWeight: 500,
1880
+ color: textColor,
1881
+ fontFamily: font,
1882
+ marginBottom: "4px",
1883
+ display: "block"
1884
+ };
1885
+ const hintStyle = {
1886
+ fontSize: "11px",
1887
+ color: "#667085",
1888
+ marginTop: "2px",
1889
+ marginBottom: "0px",
1890
+ lineHeight: "1.4"
1891
+ };
1892
+ const logo = el("img", { src: logoUrl, style: styles.logo, alt: "Logo" });
1893
+ const title = el("h2", {
1894
+ id: "guardian-form-title",
1895
+ style: {
1896
+ ...styles.title,
1897
+ fontSize: "16px",
1898
+ fontWeight: 600,
1899
+ color: headingColor,
1900
+ fontFamily: font
1901
+ }
1902
+ });
1903
+ title.textContent = "Guardian Verification";
1904
+ const topLeft = el("div", { style: styles.topLeft }, logo, title);
1905
+ const topSection = el("div", {
1906
+ style: { ...styles.topSection, color: textColor, fontFamily: font }
1907
+ }, topLeft);
1908
+ const subtitle = el("p", {
1909
+ style: {
1910
+ ...styles.privacyText,
1911
+ fontSize: "13px",
1912
+ lineHeight: "1.5",
1913
+ color: textColor,
1914
+ fontFamily: font,
1915
+ marginBottom: "12px"
1916
+ }
1917
+ });
1918
+ subtitle.textContent = "Since you are under the age of consent, a legal guardian must verify their identity via DigiLocker before you can proceed.";
1919
+ const infoIcon = svg(
1920
+ "svg",
1921
+ {
1922
+ width: "16",
1923
+ height: "16",
1924
+ viewBox: "0 0 24 24",
1925
+ fill: "none",
1926
+ stroke: "#2563EB",
1927
+ "stroke-width": "2",
1928
+ "stroke-linecap": "round",
1929
+ "stroke-linejoin": "round"
1930
+ },
1931
+ svg("circle", { cx: "12", cy: "12", r: "10" }),
1932
+ svg("line", { x1: "12", y1: "16", x2: "12", y2: "12" }),
1933
+ svg("line", { x1: "12", y1: "8", x2: "12.01", y2: "8" })
1934
+ );
1935
+ infoIcon.style.cssText = "flex-shrink:0;margin-top:1px";
1936
+ const infoText = el("span");
1937
+ infoText.textContent = "The guardian's name will be matched against their DigiLocker identity document (e.g. Aadhaar). Please enter the name exactly as it appears on the document.";
1938
+ const infoBanner = el("div", {
1939
+ style: {
1940
+ display: "flex",
1941
+ alignItems: "flex-start",
1942
+ gap: "8px",
1943
+ padding: "10px 12px",
1944
+ backgroundColor: "#EFF6FF",
1945
+ border: "1px solid #BFDBFE",
1946
+ borderRadius: radius,
1947
+ marginBottom: "16px",
1948
+ fontSize: "12px",
1949
+ lineHeight: "1.5",
1950
+ color: "#1E40AF"
1951
+ }
1952
+ }, infoIcon, infoText);
1953
+ const nameLabel = el("label", { for: "guardianName", style: labelStyle });
1954
+ nameLabel.innerHTML = `Guardian's Full Name <span style="color:#DC2626">*</span>`;
1955
+ const nameInput = el("input", {
1956
+ id: "guardianName",
1957
+ type: "text",
1958
+ style: inputStyle,
1959
+ value: formData.guardianName,
1960
+ placeholder: "e.g. Rajesh Kumar",
1961
+ autoComplete: "name",
1962
+ "aria-describedby": "guardianName-hint",
1963
+ onInput: (e) => onFieldChange("guardianName", e.target.value)
1964
+ });
1965
+ const nameHint = el("p", { id: "guardianName-hint", style: hintStyle });
1966
+ nameHint.textContent = "Enter the full name as per government ID (Aadhaar, PAN, etc.)";
1967
+ const nameError = el("div", { style: errorStyle, role: "alert" });
1968
+ const nameGroup = el("div", null, nameLabel, nameInput, nameHint, nameError);
1969
+ const contactLabel = el("label", { for: "guardianContact", style: labelStyle });
1970
+ contactLabel.innerHTML = `Guardian's Phone Number <span style="color:#DC2626">*</span>`;
1971
+ const contactInput = el("input", {
1972
+ id: "guardianContact",
1973
+ type: "tel",
1974
+ inputMode: "tel",
1975
+ style: inputStyle,
1976
+ value: formData.guardianContact,
1977
+ placeholder: "e.g. 9876543210",
1978
+ autoComplete: "tel",
1979
+ "aria-describedby": "guardianContact-hint",
1980
+ onInput: (e) => onFieldChange("guardianContact", e.target.value)
1981
+ });
1982
+ const contactHint = el("p", { id: "guardianContact-hint", style: hintStyle });
1983
+ contactHint.textContent = "Mobile number linked to guardian's Aadhaar";
1984
+ const contactError = el("div", { style: errorStyle, role: "alert" });
1985
+ const contactGroup = el("div", null, contactLabel, contactInput, contactHint, contactError);
1986
+ const relLabel = el("label", { id: "guardianRelationship-label", style: labelStyle });
1987
+ relLabel.innerHTML = `Relationship <span style="color:#DC2626">*</span>`;
1988
+ const selectedLabel = (_a = RELATIONSHIP_OPTIONS.find((o) => o.value === formData.guardianRelationship)) == null ? void 0 : _a.label;
1989
+ const chevronSvg = svg(
1990
+ "svg",
1991
+ {
1992
+ width: "14",
1993
+ height: "14",
1994
+ viewBox: "0 0 24 24",
1995
+ fill: "none",
1996
+ stroke: "#667085",
1997
+ "stroke-width": "2",
1998
+ "stroke-linecap": "round",
1999
+ "stroke-linejoin": "round"
2000
+ },
2001
+ svg("polyline", { points: "6 9 12 15 18 9" })
2002
+ );
2003
+ chevronSvg.style.cssText = "flex-shrink:0;margin-left:8px;transition:transform 0.2s";
2004
+ const selectedSpan = el("span", {
2005
+ style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }
2006
+ });
2007
+ selectedSpan.textContent = selectedLabel || "Select relationship";
2008
+ const relationshipDisplay = el("div", {
2009
+ role: "combobox",
2010
+ "aria-expanded": "false",
2011
+ "aria-haspopup": "listbox",
2012
+ "aria-labelledby": "guardianRelationship-label",
2013
+ tabIndex: "0",
2014
+ style: {
2015
+ ...inputStyle,
2016
+ color: selectedLabel ? textColor : "#9CA3AF",
2017
+ cursor: "pointer",
2018
+ display: "flex",
2019
+ alignItems: "center",
2020
+ justifyContent: "space-between",
2021
+ userSelect: "none",
2022
+ transition: "border-color 0.15s, box-shadow 0.15s"
2023
+ }
2024
+ }, selectedSpan, chevronSvg);
2025
+ const dropdownList = el("div", {
2026
+ id: "guardianRelationship-listbox",
2027
+ role: "listbox",
2028
+ "aria-labelledby": "guardianRelationship-label",
2029
+ style: {
2030
+ position: "absolute",
2031
+ top: "calc(100% + 4px)",
2032
+ left: 0,
2033
+ right: 0,
2034
+ backgroundColor: bgColor,
2035
+ border: `1px solid ${borderColor}`,
2036
+ borderRadius: radius,
2037
+ boxShadow: "0 4px 12px rgba(0,0,0,0.1), 0 1px 3px rgba(0,0,0,0.06)",
2038
+ zIndex: 50,
2039
+ overflow: "hidden",
2040
+ padding: "4px 0",
2041
+ maxHeight: "220px",
2042
+ overflowY: "auto",
2043
+ display: "none"
2044
+ }
2045
+ });
2046
+ for (let i = 0; i < RELATIONSHIP_OPTIONS.length; i++) {
2047
+ const option = RELATIONSHIP_OPTIONS[i];
2048
+ const isSelected = formData.guardianRelationship === option.value;
2049
+ const optionEl = el("div", {
2050
+ id: `guardianRelationship-option-${i}`,
2051
+ role: "option",
2052
+ "aria-selected": String(isSelected),
2053
+ style: {
2054
+ padding: "9px 12px",
2055
+ fontSize: "13px",
2056
+ fontFamily: font,
2057
+ color: isSelected ? accentColor : textColor,
2058
+ backgroundColor: "transparent",
2059
+ cursor: "pointer",
2060
+ display: "flex",
2061
+ alignItems: "center",
2062
+ justifyContent: "space-between",
2063
+ transition: "background-color 0.1s"
2064
+ },
2065
+ onClick: () => {
2066
+ onFieldChange("guardianRelationship", option.value);
2067
+ selectedSpan.textContent = option.label;
2068
+ relationshipDisplay.style.color = textColor;
2069
+ doClose();
2070
+ },
2071
+ onMouseenter: (e) => {
2072
+ e.target.style.backgroundColor = "#F3F4F6";
2073
+ },
2074
+ onMouseleave: (e) => {
2075
+ e.target.style.backgroundColor = "transparent";
2076
+ }
2077
+ });
2078
+ const label = el("span");
2079
+ label.textContent = option.label;
2080
+ optionEl.appendChild(label);
2081
+ if (isSelected) {
2082
+ const checkSvg = svg(
2083
+ "svg",
2084
+ {
2085
+ width: "14",
2086
+ height: "14",
2087
+ viewBox: "0 0 24 24",
2088
+ fill: "none",
2089
+ stroke: accentColor,
2090
+ "stroke-width": "2.5",
2091
+ "stroke-linecap": "round",
2092
+ "stroke-linejoin": "round"
2093
+ },
2094
+ svg("polyline", { points: "20 6 9 17 4 12" })
2095
+ );
2096
+ checkSvg.style.flexShrink = "0";
2097
+ optionEl.appendChild(checkSvg);
2098
+ }
2099
+ dropdownList.appendChild(optionEl);
2100
+ }
2101
+ let dropdownOpen = false;
2102
+ const relContainer = el("div", { style: { position: "relative" } });
2103
+ const handleClickOutside = (e) => {
2104
+ if (!relContainer.contains(e.target)) {
2105
+ doClose();
2106
+ }
2107
+ };
2108
+ function doOpen() {
2109
+ dropdownOpen = true;
2110
+ dropdownList.style.display = "block";
2111
+ relationshipDisplay.setAttribute("aria-expanded", "true");
2112
+ chevronSvg.style.transform = "rotate(180deg)";
2113
+ relationshipDisplay.style.borderColor = accentColor;
2114
+ relationshipDisplay.style.boxShadow = `0 0 0 1px ${accentColor}`;
2115
+ const rect = relContainer.getBoundingClientRect();
2116
+ const spaceBelow = window.innerHeight - rect.bottom;
2117
+ const dropdownHeight = 220;
2118
+ const shouldOpenUpward = spaceBelow < dropdownHeight && rect.top > dropdownHeight;
2119
+ if (shouldOpenUpward) {
2120
+ dropdownList.style.top = "auto";
2121
+ dropdownList.style.bottom = "calc(100% + 4px)";
2122
+ dropdownList.style.boxShadow = "0 -4px 12px rgba(0,0,0,0.1), 0 -1px 3px rgba(0,0,0,0.06)";
2123
+ } else {
2124
+ dropdownList.style.top = "calc(100% + 4px)";
2125
+ dropdownList.style.bottom = "auto";
2126
+ dropdownList.style.boxShadow = "0 4px 12px rgba(0,0,0,0.1), 0 1px 3px rgba(0,0,0,0.06)";
2127
+ }
2128
+ requestAnimationFrame(() => {
2129
+ dropdownList.scrollIntoView({ behavior: "smooth", block: "nearest" });
2130
+ });
2131
+ setTimeout(() => document.addEventListener("mousedown", handleClickOutside), 0);
2132
+ }
2133
+ function doClose() {
2134
+ dropdownOpen = false;
2135
+ dropdownList.style.display = "none";
2136
+ relationshipDisplay.setAttribute("aria-expanded", "false");
2137
+ chevronSvg.style.transform = "rotate(0deg)";
2138
+ relationshipDisplay.style.borderColor = borderColor;
2139
+ relationshipDisplay.style.boxShadow = "none";
2140
+ document.removeEventListener("mousedown", handleClickOutside);
2141
+ }
2142
+ relationshipDisplay.addEventListener("click", () => {
2143
+ dropdownOpen ? doClose() : doOpen();
2144
+ });
2145
+ relationshipDisplay.addEventListener("keydown", (e) => {
2146
+ if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
2147
+ e.preventDefault();
2148
+ if (!dropdownOpen) doOpen();
2149
+ } else if (e.key === "Escape" || e.key === "Tab") {
2150
+ doClose();
2151
+ }
2152
+ });
2153
+ const relationshipError = el("div", { style: errorStyle, role: "alert" });
2154
+ relContainer.appendChild(relLabel);
2155
+ relContainer.appendChild(relationshipDisplay);
2156
+ relContainer.appendChild(dropdownList);
2157
+ relContainer.appendChild(relationshipError);
2158
+ const generalError = el("div", {
2159
+ role: "alert",
2160
+ style: {
2161
+ color: "#DC2626",
2162
+ fontSize: "13px",
2163
+ marginTop: "16px",
2164
+ padding: "12px",
2165
+ borderRadius: "6px",
2166
+ border: "1px solid #FCA5A5",
2167
+ backgroundColor: "#FEF2F2",
2168
+ textAlign: "center",
2169
+ display: "none"
2170
+ }
2171
+ });
2172
+ const fieldsContainer = el("div", {
2173
+ style: { display: "flex", flexDirection: "column", gap: "14px" }
2174
+ }, nameGroup, contactGroup, relContainer);
2175
+ const middleSection = el("div", {
2176
+ style: { ...styles.middleSection, overflow: "visible", color: textColor, fontFamily: font }
2177
+ }, subtitle, infoBanner, fieldsContainer, generalError);
2178
+ const btnBase = {
2179
+ ...styles.button,
2180
+ fontSize: isMobile ? "14px" : "16px",
2181
+ padding: isMobile ? "10px 20px" : "9px 45px"
2182
+ };
2183
+ const submitBtn = el("button", {
2184
+ style: {
2185
+ ...btnBase,
2186
+ ...styles.acceptButton,
2187
+ backgroundColor: colors.acceptBg,
2188
+ color: colors.acceptText,
2189
+ borderRadius: colors.borderRadius
2190
+ },
2191
+ type: "button",
2192
+ disabled: isSubmitting,
2193
+ "aria-label": isSubmitting ? "Verifying guardian identity" : "Proceed to guardian verification",
2194
+ onClick: onSubmit
2195
+ });
2196
+ submitBtn.textContent = isSubmitting ? "Verifying..." : "Verify via DigiLocker";
2197
+ const cancelBtn = el("button", {
2198
+ style: {
2199
+ ...btnBase,
2200
+ ...styles.cancelButton,
2201
+ backgroundColor: colors.declineBg,
2202
+ color: colors.declineText,
2203
+ borderColor: colors.declineBorder,
2204
+ borderRadius: colors.borderRadius
2205
+ },
2206
+ type: "button",
2207
+ "aria-label": "Cancel",
2208
+ onClick: onCancel
2209
+ });
2210
+ cancelBtn.textContent = "Cancel";
2211
+ const bottomSection = el("div", {
2212
+ style: { ...styles.bottomSection, paddingTop: "12px", color: textColor, fontFamily: font }
2213
+ }, submitBtn, cancelBtn);
2214
+ const container = el("div", { style: { ...styles.content, overflow: "visible" } }, topSection, middleSection, bottomSection);
2215
+ return {
2216
+ container,
2217
+ nameInput,
2218
+ contactInput,
2219
+ relationshipDisplay,
2220
+ nameError,
2221
+ contactError,
2222
+ relationshipError,
2223
+ submitBtn,
2224
+ generalError
2225
+ };
2226
+ }
2227
+ function validateGuardianForm(data) {
2228
+ const errors = {};
2229
+ if (!data.guardianName.trim()) errors.guardianName = "Guardian name is required";
2230
+ if (!data.guardianContact.trim()) errors.guardianContact = "Guardian contact is required";
2231
+ if (!data.guardianRelationship.trim()) errors.guardianRelationship = "Relationship to guardian is required";
2232
+ return errors;
2233
+ }
2234
+
2235
+ // src/modal/verification-screen.ts
2236
+ function getVerificationErrorMessage(errorCode, fallbackError) {
2237
+ switch (errorCode) {
2238
+ case "GUARDIAN_UNDER_18":
2239
+ return "The guardian must be 18 years or older. Please provide details of a different guardian.";
2240
+ case "TOKEN_FAILED":
2241
+ return "DigiLocker verification could not be completed. Please try again.";
2242
+ case "DIGILOCKER_AUTH_FAILED":
2243
+ return "DigiLocker authorization was cancelled or failed. Please try again.";
2244
+ case "NO_AUTH_CODE":
2245
+ return "DigiLocker did not return an authorization code. Please try again.";
2246
+ case "SESSION_EXPIRED":
2247
+ return "Verification session has expired. Please try again.";
2248
+ default:
2249
+ return fallbackError || "Verification failed. Please try again.";
2250
+ }
2251
+ }
2252
+ function renderVerificationScreen(options) {
2253
+ const {
2254
+ settings,
2255
+ colors,
2256
+ isMobile,
2257
+ logoUrl,
2258
+ isInitiating,
2259
+ isPolling,
2260
+ isComplete,
2261
+ isAutoTransitioning,
2262
+ error,
2263
+ errorCode,
2264
+ canRetry,
2265
+ onContinue,
2266
+ onBack
2267
+ } = options;
2268
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || "#101828";
2269
+ const textColor = colors.text;
2270
+ const font = colors.font;
2271
+ const accentColor = colors.acceptBg;
2272
+ const logo = el("img", { src: logoUrl, style: styles.logo, alt: "Logo" });
2273
+ const title = el("h2", {
2274
+ id: "verification-title",
2275
+ style: {
2276
+ ...styles.title,
2277
+ fontSize: "16px",
2278
+ fontWeight: 600,
2279
+ color: headingColor,
2280
+ fontFamily: font
2281
+ }
2282
+ });
2283
+ title.textContent = "Guardian Verification";
2284
+ const topLeft = el("div", { style: styles.topLeft }, logo, title);
2285
+ const topSection = el("div", {
2286
+ style: { ...styles.topSection, color: textColor, fontFamily: font }
2287
+ }, topLeft);
2288
+ const middleSection = el("div", {
2289
+ style: {
2290
+ ...styles.middleSection,
2291
+ color: textColor,
2292
+ fontFamily: font,
2293
+ textAlign: "center"
2294
+ }
2295
+ });
2296
+ if (isComplete && !error) {
2297
+ const successCircle = el("div", {
2298
+ style: {
2299
+ width: "64px",
2300
+ height: "64px",
2301
+ borderRadius: "50%",
2302
+ backgroundColor: "#D1FAE5",
2303
+ display: "flex",
2304
+ alignItems: "center",
2305
+ justifyContent: "center"
2306
+ }
2307
+ });
2308
+ const checkSvg = svg(
2309
+ "svg",
2310
+ {
2311
+ width: "32",
2312
+ height: "32",
2313
+ viewBox: "0 0 24 24",
2314
+ fill: "none",
2315
+ stroke: "#059669",
2316
+ "stroke-width": "2",
2317
+ "stroke-linecap": "round",
2318
+ "stroke-linejoin": "round"
2319
+ },
2320
+ svg("polyline", { points: "20 6 9 17 4 12" })
2321
+ );
2322
+ successCircle.appendChild(checkSvg);
2323
+ const successText = el("p", {
2324
+ style: {
2325
+ color: headingColor,
2326
+ fontFamily: font,
2327
+ fontSize: "16px",
2328
+ fontWeight: 600,
2329
+ lineHeight: "1.5",
2330
+ marginTop: "12px",
2331
+ marginBottom: "24px"
2332
+ }
2333
+ });
2334
+ successText.textContent = "Verification Completed";
2335
+ const wrapper = el("div", {
2336
+ style: {
2337
+ display: "flex",
2338
+ flexDirection: "column",
2339
+ alignItems: "center",
2340
+ justifyContent: "center",
2341
+ flex: "1"
2342
+ }
2343
+ }, successCircle, successText);
2344
+ middleSection.appendChild(wrapper);
2345
+ } else if ((isInitiating || isPolling) && !error) {
2346
+ const spinner = el("div", {
2347
+ style: {
2348
+ width: "48px",
2349
+ height: "48px",
2350
+ border: "4px solid #e5e7eb",
2351
+ borderTopColor: accentColor,
2352
+ borderRadius: "50%",
2353
+ animation: "spin 1s linear infinite",
2354
+ margin: "24px auto"
2355
+ }
2356
+ });
2357
+ const statusMsg = el("div", {
2358
+ style: {
2359
+ color: "#059669",
2360
+ fontSize: "14px",
2361
+ marginTop: "16px",
2362
+ padding: "12px",
2363
+ borderRadius: "6px",
2364
+ border: "1px solid #A7F3D0",
2365
+ backgroundColor: "#D1FAE5",
2366
+ textAlign: "center"
2367
+ }
2368
+ });
2369
+ statusMsg.textContent = isInitiating ? "Opening DigiLocker in a new tab..." : "Please complete the verification in the DigiLocker tab. This page will update automatically when verification is complete.";
2370
+ middleSection.appendChild(spinner);
2371
+ middleSection.appendChild(statusMsg);
2372
+ }
2373
+ if (error) {
2374
+ const errorCircle = el("div", {
2375
+ style: {
2376
+ width: "64px",
2377
+ height: "64px",
2378
+ borderRadius: "50%",
2379
+ backgroundColor: "#fee2e2",
2380
+ display: "flex",
2381
+ alignItems: "center",
2382
+ justifyContent: "center",
2383
+ margin: "24px auto"
2384
+ }
2385
+ });
2386
+ const xSvg = svg(
2387
+ "svg",
2388
+ {
2389
+ width: "32",
2390
+ height: "32",
2391
+ viewBox: "0 0 24 24",
2392
+ fill: "none",
2393
+ stroke: "#dc2626",
2394
+ "stroke-width": "2",
2395
+ "stroke-linecap": "round",
2396
+ "stroke-linejoin": "round"
2397
+ },
2398
+ svg("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
2399
+ svg("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
2400
+ );
2401
+ errorCircle.appendChild(xSvg);
2402
+ const failedText = el("p", {
2403
+ style: {
2404
+ ...styles.privacyText,
2405
+ color: textColor,
2406
+ fontFamily: font,
2407
+ fontSize: "14px",
2408
+ lineHeight: "1.5",
2409
+ marginBottom: "12px"
2410
+ }
2411
+ });
2412
+ failedText.textContent = "Verification failed";
2413
+ const errorBox = el("div", {
2414
+ style: {
2415
+ color: "#DC2626",
2416
+ fontSize: "14px",
2417
+ marginTop: "16px",
2418
+ padding: "12px",
2419
+ borderRadius: "6px",
2420
+ border: "1px solid #FCA5A5",
2421
+ backgroundColor: "#FEF2F2",
2422
+ textAlign: "center"
2423
+ }
2424
+ });
2425
+ errorBox.textContent = error;
2426
+ middleSection.appendChild(errorCircle);
2427
+ middleSection.appendChild(failedText);
2428
+ middleSection.appendChild(errorBox);
2429
+ }
2430
+ const btnBase = {
2431
+ ...styles.button,
2432
+ fontSize: isMobile ? "14px" : "16px",
2433
+ padding: isMobile ? "10px 20px" : "9px 45px"
2434
+ };
2435
+ const bottomSection = el("div", {
2436
+ style: {
2437
+ ...styles.bottomSection,
2438
+ paddingTop: "12px",
2439
+ display: "flex",
2440
+ gap: "12px",
2441
+ justifyContent: "center",
2442
+ color: textColor,
2443
+ fontFamily: font
2444
+ }
2445
+ });
2446
+ const acceptBtnStyle = {
2447
+ backgroundColor: colors.acceptBg,
2448
+ color: colors.acceptText,
2449
+ borderRadius: colors.borderRadius
2450
+ };
2451
+ const declineBtnStyle = {
2452
+ backgroundColor: colors.declineBg,
2453
+ color: colors.declineText,
2454
+ borderColor: colors.declineBorder,
2455
+ borderRadius: colors.borderRadius
2456
+ };
2457
+ if (!isAutoTransitioning) {
2458
+ if (isComplete && !error) {
2459
+ const continueBtn = el("button", {
2460
+ style: { ...btnBase, ...acceptBtnStyle },
2461
+ type: "button",
2462
+ "aria-label": "Continue to consent form",
2463
+ onClick: onContinue
2464
+ });
2465
+ continueBtn.textContent = "Continue";
2466
+ bottomSection.appendChild(continueBtn);
2467
+ } else if (error && canRetry) {
2468
+ const retryBtn = el("button", {
2469
+ style: { ...btnBase, ...styles.acceptButton, ...acceptBtnStyle },
2470
+ type: "button",
2471
+ "aria-label": errorCode === "GUARDIAN_UNDER_18" ? "Change guardian details" : "Try verification again",
2472
+ onClick: () => onBack(errorCode === "GUARDIAN_UNDER_18")
2473
+ });
2474
+ retryBtn.textContent = errorCode === "GUARDIAN_UNDER_18" ? "Change Guardian" : "Try Again";
2475
+ bottomSection.appendChild(retryBtn);
2476
+ } else if (error) {
2477
+ const goBackBtn = el("button", {
2478
+ style: { ...btnBase, ...styles.cancelButton, ...declineBtnStyle },
2479
+ type: "button",
2480
+ "aria-label": "Go back",
2481
+ onClick: () => onBack(false)
2482
+ });
2483
+ goBackBtn.textContent = "Go Back";
2484
+ bottomSection.appendChild(goBackBtn);
2485
+ } else {
2486
+ const cancelBtn = el("button", {
2487
+ style: { ...btnBase, ...styles.cancelButton, ...declineBtnStyle },
2488
+ type: "button",
2489
+ "aria-label": "Cancel verification",
2490
+ onClick: () => onBack(false)
2491
+ });
2492
+ cancelBtn.textContent = "Cancel";
2493
+ bottomSection.appendChild(cancelBtn);
2494
+ }
2495
+ }
2496
+ const container = el("div", { style: styles.content }, topSection, middleSection, bottomSection);
2497
+ return { container };
2498
+ }
2499
+
2500
+ // src/modal/colors.ts
2501
+ function resolveColors(settings, activeConfig) {
2502
+ var _a, _b, _c, _d, _e, _f, _g, _h;
2503
+ const primaryColor = (activeConfig == null ? void 0 : activeConfig.primary_color) || "#4f87ff";
2504
+ const secondaryColor = (activeConfig == null ? void 0 : activeConfig.secondary_color) || "#4f87ff";
2505
+ return {
2506
+ acceptBg: ((_b = (_a = settings == null ? void 0 : settings.button) == null ? void 0 : _a.accept) == null ? void 0 : _b.backgroundColor) || primaryColor,
2507
+ acceptText: ((_d = (_c = settings == null ? void 0 : settings.button) == null ? void 0 : _c.accept) == null ? void 0 : _d.textColor) || "#ffffff",
2508
+ declineBg: ((_f = (_e = settings == null ? void 0 : settings.button) == null ? void 0 : _e.decline) == null ? void 0 : _f.backgroundColor) || "#ffffff",
2509
+ declineText: ((_h = (_g = settings == null ? void 0 : settings.button) == null ? void 0 : _g.decline) == null ? void 0 : _h.textColor) || "#000000",
2510
+ declineBorder: (settings == null ? void 0 : settings.borderColor) || "#d0d5dd",
2511
+ link: (settings == null ? void 0 : settings.link) || secondaryColor,
2512
+ heading: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
2513
+ text: (settings == null ? void 0 : settings.textColor) || "#344054",
2514
+ font: (settings == null ? void 0 : settings.font) || "inherit",
2515
+ borderRadius: (settings == null ? void 0 : settings.borderRadius) || "8px",
2516
+ primaryColor,
2517
+ secondaryColor
2518
+ };
2519
+ }
2520
+
2521
+ // src/modal/index.ts
2522
+ var ModalController = class {
2523
+ constructor(options) {
2524
+ this.overlayEl = null;
2525
+ this.modalEl = null;
2526
+ this.purposeRefs = null;
2527
+ this.alreadyConsentedPurposeRefs = null;
2528
+ this.langSelectorRefs = null;
2529
+ this.keyboardCleanup = null;
2530
+ this.abortController = null;
2531
+ this.unsubscribe = null;
2532
+ this.guardianFormRefs = null;
2533
+ this.statusPollTimeout = null;
2534
+ this.popupCheckInterval = null;
2535
+ this.verificationPopup = null;
2536
+ this.verificationSessionToken = null;
2537
+ this.visibilityChangeHandler = null;
2538
+ // DOM refs for targeted updates
2539
+ this.acceptBtn = null;
2540
+ this.declineBtn = null;
2541
+ this.middleSection = null;
2542
+ this.errorEl = null;
2543
+ this.noticeTitleEl = null;
2544
+ this.noticeTextEl = null;
2545
+ this.purposeHeadingEl = null;
2546
+ this.additionalTextEl = null;
2547
+ this.privacyPolicyLinkEl = null;
2548
+ this.privacyCenterLinkEl = null;
2549
+ this.ttsButton = null;
2550
+ this.options = options;
2551
+ this.store = new Store({
2552
+ isLoading: true,
2553
+ isSubmitting: false,
2554
+ isSubmittingAccept: false,
2555
+ isSubmittingDecline: false,
2556
+ content: null,
2557
+ selectedPurposes: {},
2558
+ selectedDataElements: {},
2559
+ initialDataElementSelections: {},
2560
+ collapsedPurposes: {},
2561
+ hasAlreadyConsented: false,
2562
+ isFullyConsentedReviewMode: false,
2563
+ selectedLanguage: options.language || "en",
2564
+ errorMessage: null,
2565
+ fetchError: null,
2566
+ needsConsentPurposes: [],
2567
+ alreadyConsentedPurposes: [],
2568
+ isMinorFlow: false,
2569
+ selfDeclaredAdult: false,
2570
+ showAgeVerification: false,
2571
+ showGuardianForm: false,
2572
+ showVerificationScreen: false,
2573
+ verificationReference: null,
2574
+ isVerificationComplete: false,
2575
+ isInitiatingVerification: false,
2576
+ isPollingStatus: false,
2577
+ verificationError: null,
2578
+ verificationErrorCode: null,
2579
+ canRetryVerification: false,
2580
+ guardianFormData: { guardianName: "", guardianContact: "", guardianRelationship: "" }
2581
+ });
2582
+ this.mediaQuery = createMediaQuery("(max-width: 768px)");
2583
+ this.ttsManager = new TTSManager(() => this.updateTTSButton());
2584
+ }
2585
+ async init() {
2586
+ injectCheckboxStyles();
2587
+ this.unsubscribe = this.store.subscribe((state, prevState) => {
2588
+ this.handleStateChange(state, prevState);
2589
+ });
2590
+ this.mediaQuery.subscribe(() => this.rebuildUI());
2591
+ this.renderOverlayWithLoading();
2592
+ await this.fetchNotice();
2593
+ }
2594
+ destroy() {
2595
+ var _a, _b, _c, _d;
2596
+ (_a = this.abortController) == null ? void 0 : _a.abort();
2597
+ (_b = this.keyboardCleanup) == null ? void 0 : _b.call(this);
2598
+ (_c = this.unsubscribe) == null ? void 0 : _c.call(this);
2599
+ this.mediaQuery.destroy();
2600
+ this.ttsManager.destroy();
2601
+ this.stopPolling();
2602
+ this.store.destroy();
2603
+ if (((_d = this.langSelectorRefs) == null ? void 0 : _d.dropdown) && document.body.contains(this.langSelectorRefs.dropdown)) {
2604
+ document.body.removeChild(this.langSelectorRefs.dropdown);
2605
+ }
2606
+ if (this.overlayEl && document.body.contains(this.overlayEl)) {
2607
+ document.body.removeChild(this.overlayEl);
2608
+ }
2609
+ if (this.modalEl && document.body.contains(this.modalEl)) {
2610
+ document.body.removeChild(this.modalEl);
2611
+ }
2612
+ this.overlayEl = null;
2613
+ this.modalEl = null;
2614
+ this.purposeRefs = null;
2615
+ this.langSelectorRefs = null;
2616
+ }
2617
+ // ─── Private ───
2618
+ async fetchNotice() {
2619
+ var _a, _b, _c;
2620
+ this.abortController = new AbortController();
2621
+ this.store.setState({ isLoading: true, fetchError: null });
2622
+ try {
2623
+ const data = await fetchConsentContent({
2624
+ noticeId: this.options.noticeId,
2625
+ accessToken: this.options.accessToken,
2626
+ refreshToken: this.options.refreshToken,
2627
+ baseUrl: this.options.baseUrl,
2628
+ language: this.options.language,
2629
+ specific_uuid: this.options.applicationId,
2630
+ validate_against: this.options.validateAgainst || "all",
2631
+ include_fully_consented_data: this.options.includeFullyConsentedData,
2632
+ signal: this.abortController.signal
2633
+ });
2634
+ const activeConfig = data.detail.active_config;
2635
+ const purposeSelections = data.detail.purpose_selections;
2636
+ const initialCollapsed = {};
2637
+ const initialPurposes = {};
2638
+ const initialElements = {};
2639
+ const initialDataElementSelections = {};
2640
+ let needsConsentPurposes = [];
2641
+ let alreadyConsentedPurposes = [];
2642
+ let isFullyConsentedReviewMode = false;
2643
+ if (purposeSelections && data.detail.reconsent_required) {
2644
+ for (const purpose of activeConfig.purposes) {
2645
+ const selection = purposeSelections[purpose.uuid];
2646
+ initialCollapsed[purpose.uuid] = true;
2647
+ if (selection && selection.selected && !selection.needs_reconsent) {
2648
+ alreadyConsentedPurposes.push(purpose);
2649
+ initialPurposes[purpose.uuid] = true;
2650
+ for (const de of purpose.data_elements) {
2651
+ const combinedId = `${purpose.uuid}-${de.uuid}`;
2652
+ const deSelection = (_a = selection.data_elements) == null ? void 0 : _a[de.uuid];
2653
+ const wasSelected = (deSelection == null ? void 0 : deSelection.selected) || false;
2654
+ initialElements[combinedId] = wasSelected;
2655
+ initialDataElementSelections[combinedId] = wasSelected;
2656
+ }
2657
+ } else {
2658
+ needsConsentPurposes.push(purpose);
2659
+ initialPurposes[purpose.uuid] = false;
2660
+ for (const de of purpose.data_elements) {
2661
+ initialElements[`${purpose.uuid}-${de.uuid}`] = false;
2662
+ }
2663
+ }
2664
+ }
2665
+ } else {
2666
+ needsConsentPurposes = activeConfig.purposes;
2667
+ for (const purpose of activeConfig.purposes) {
2668
+ initialCollapsed[purpose.uuid] = true;
2669
+ initialPurposes[purpose.uuid] = false;
2670
+ for (const de of purpose.data_elements) {
2671
+ initialElements[`${purpose.uuid}-${de.uuid}`] = false;
2672
+ }
2673
+ }
2674
+ }
2675
+ if (this.options.includeFullyConsentedData && needsConsentPurposes.length === 0 && alreadyConsentedPurposes.length > 0) {
2676
+ isFullyConsentedReviewMode = true;
2677
+ }
2678
+ let selectedLanguage = this.options.language || "en";
2679
+ if (activeConfig.default_language) {
2680
+ selectedLanguage = activeConfig.default_language;
2681
+ }
2682
+ let isMinorFlow = false;
2683
+ let showAgeVerification = false;
2684
+ let restoreState = {};
2685
+ let pollingTokenToResume = null;
2686
+ if (data.detail.is_minor) {
2687
+ isMinorFlow = true;
2688
+ const persistedState = getConsentState();
2689
+ if ((persistedState == null ? void 0 : persistedState.noticeId) && persistedState.noticeId !== this.options.noticeId) {
2690
+ clearConsentState();
2691
+ } else if ((persistedState == null ? void 0 : persistedState.flowState) === "digilocker-pending" && persistedState.sessionToken) {
2692
+ restoreState = {
2693
+ showVerificationScreen: true,
2694
+ isPollingStatus: true
2695
+ };
2696
+ pollingTokenToResume = persistedState.sessionToken;
2697
+ } else if ((persistedState == null ? void 0 : persistedState.verificationResult) === "verified" && persistedState.verificationReference) {
2698
+ restoreState = {
2699
+ showVerificationScreen: true,
2700
+ isVerificationComplete: true,
2701
+ verificationReference: persistedState.verificationReference
2702
+ };
2703
+ clearConsentState();
2704
+ } else if ((persistedState == null ? void 0 : persistedState.verificationResult) === "failed" || (persistedState == null ? void 0 : persistedState.verificationResult) === "expired") {
2705
+ restoreState = {
2706
+ showVerificationScreen: true,
2707
+ verificationError: persistedState.verificationResult === "expired" ? "Verification session has expired. Please try again." : "Verification failed. Please try again.",
2708
+ canRetryVerification: true
2709
+ };
2710
+ clearConsentState();
2711
+ } else {
2712
+ showAgeVerification = true;
2713
+ }
2714
+ }
2715
+ this.store.setState({
2716
+ content: data,
2717
+ isLoading: false,
2718
+ selectedLanguage,
2719
+ collapsedPurposes: initialCollapsed,
2720
+ selectedPurposes: initialPurposes,
2721
+ selectedDataElements: initialElements,
2722
+ initialDataElementSelections,
2723
+ needsConsentPurposes,
2724
+ alreadyConsentedPurposes,
2725
+ isFullyConsentedReviewMode,
2726
+ isMinorFlow,
2727
+ showAgeVerification,
2728
+ ...restoreState
2729
+ });
2730
+ if (pollingTokenToResume) {
2731
+ this.startStatusPolling(pollingTokenToResume);
2732
+ }
2733
+ } catch (err) {
2734
+ if (err.name === "AbortError") return;
2735
+ const error = err;
2736
+ if (error.status === 409) {
2737
+ this.store.setState({ hasAlreadyConsented: true, isLoading: false });
2738
+ this.options.onAccept();
2739
+ } else {
2740
+ this.store.setState({ fetchError: error, isLoading: false });
2741
+ (_c = (_b = this.options).onError) == null ? void 0 : _c.call(_b, error);
2742
+ }
2743
+ }
2744
+ }
2745
+ handleStateChange(state, prevState) {
2746
+ if (prevState.isLoading && !state.isLoading && state.content) {
2747
+ this.rebuildUI();
2748
+ return;
2749
+ }
2750
+ if (state.hasAlreadyConsented && !prevState.hasAlreadyConsented) {
2751
+ this.destroy();
2752
+ return;
2753
+ }
2754
+ if (state.fetchError && !prevState.fetchError) {
2755
+ this.renderError(state.fetchError.message);
2756
+ return;
2757
+ }
2758
+ if (state.showAgeVerification !== prevState.showAgeVerification || state.showGuardianForm !== prevState.showGuardianForm || state.showVerificationScreen !== prevState.showVerificationScreen) {
2759
+ this.rebuildUI();
2760
+ return;
2761
+ }
2762
+ if (state.showVerificationScreen && (state.isVerificationComplete !== prevState.isVerificationComplete || state.verificationError !== prevState.verificationError || state.isPollingStatus !== prevState.isPollingStatus || state.isInitiatingVerification !== prevState.isInitiatingVerification)) {
2763
+ this.rebuildUI();
2764
+ return;
2765
+ }
2766
+ if (state.selectedPurposes !== prevState.selectedPurposes || state.selectedDataElements !== prevState.selectedDataElements) {
2767
+ this.updateCheckboxes(state);
2768
+ }
2769
+ if (state.collapsedPurposes !== prevState.collapsedPurposes) {
2770
+ this.updateCollapseState(state);
2771
+ }
2772
+ if (state.errorMessage !== prevState.errorMessage) {
2773
+ this.updateErrorMessage(state);
2774
+ }
2775
+ if (state.isSubmittingAccept !== prevState.isSubmittingAccept || state.isSubmittingDecline !== prevState.isSubmittingDecline) {
2776
+ this.updateButtonStates(state);
2777
+ }
2778
+ }
2779
+ renderOverlayWithLoading() {
2780
+ const blockUI = this.options.blockUI !== false;
2781
+ if (blockUI) {
2782
+ this.overlayEl = el("div", {
2783
+ style: styles.overlay,
2784
+ role: "presentation"
2785
+ });
2786
+ document.body.appendChild(this.overlayEl);
2787
+ }
2788
+ const settingsObj = this.options.settings;
2789
+ const modalStyle = { ...styles.modal };
2790
+ if (settingsObj == null ? void 0 : settingsObj.backgroundColor) modalStyle.backgroundColor = settingsObj.backgroundColor;
2791
+ if (settingsObj == null ? void 0 : settingsObj.borderRadius) modalStyle.borderRadius = settingsObj.borderRadius;
2792
+ if (settingsObj == null ? void 0 : settingsObj.borderColor) modalStyle.border = `1px solid ${settingsObj.borderColor}`;
2793
+ if (settingsObj == null ? void 0 : settingsObj.font) modalStyle.fontFamily = settingsObj.font;
2794
+ if (this.mediaQuery.matches) {
2795
+ modalStyle.width = "90%";
2796
+ modalStyle.maxWidth = "90%";
2797
+ }
2798
+ this.modalEl = el("div", {
2799
+ style: modalStyle,
2800
+ role: "dialog",
2801
+ "aria-modal": "true",
2802
+ "aria-label": "Privacy consent"
2803
+ });
2804
+ const loadingEl = el(
2805
+ "div",
2806
+ { style: styles.loadingContainer },
2807
+ el("div", { style: styles.loadingSpinner }),
2808
+ el("p", { style: { ...styles.privacyText, color: (settingsObj == null ? void 0 : settingsObj.textColor) || "#344054" } }, "Loading...")
2809
+ );
2810
+ const contentEl = el("div", { style: styles.content }, loadingEl);
2811
+ this.modalEl.appendChild(contentEl);
2812
+ document.body.appendChild(this.modalEl);
2813
+ }
2814
+ rebuildUI() {
2815
+ var _a, _b, _c, _d, _e, _f, _g;
2816
+ const state = this.store.getState();
2817
+ if (!state.content || !this.modalEl) return;
2818
+ const activeConfig = state.content.detail.active_config;
2819
+ const settingsObj = this.options.settings;
2820
+ const isMobile = this.mediaQuery.matches;
2821
+ const colors = resolveColors(settingsObj, activeConfig);
2822
+ if (state.showAgeVerification) {
2823
+ clearElement(this.modalEl);
2824
+ const ageRefs = renderAgeVerification({
2825
+ settings: settingsObj,
2826
+ colors,
2827
+ isMobile,
2828
+ logoUrl: activeConfig.logo_url,
2829
+ onYes: () => this.handleAgeVerificationYes(),
2830
+ onNo: () => this.handleAgeVerificationNo(),
2831
+ onClose: () => this.handleDecline()
2832
+ });
2833
+ this.modalEl.appendChild(ageRefs.container);
2834
+ (_a = this.keyboardCleanup) == null ? void 0 : _a.call(this);
2835
+ this.keyboardCleanup = setupKeyboardTrap(this.modalEl, () => this.handleDecline());
2836
+ return;
2837
+ }
2838
+ if (state.showGuardianForm) {
2839
+ clearElement(this.modalEl);
2840
+ this.guardianFormRefs = renderGuardianForm({
2841
+ formData: state.guardianFormData,
2842
+ settings: settingsObj,
2843
+ colors,
2844
+ isMobile,
2845
+ logoUrl: activeConfig.logo_url,
2846
+ isSubmitting: state.isInitiatingVerification,
2847
+ onSubmit: () => this.handleGuardianFormSubmit(),
2848
+ onCancel: () => this.handleDecline(),
2849
+ onFieldChange: (field, value) => {
2850
+ const s = this.store.getState();
2851
+ this.store.setState({
2852
+ guardianFormData: { ...s.guardianFormData, [field]: value }
2853
+ });
2854
+ }
2855
+ });
2856
+ this.modalEl.appendChild(this.guardianFormRefs.container);
2857
+ (_b = this.keyboardCleanup) == null ? void 0 : _b.call(this);
2858
+ this.keyboardCleanup = setupKeyboardTrap(this.modalEl, () => this.handleDecline());
2859
+ return;
2860
+ }
2861
+ if (state.showVerificationScreen) {
2862
+ clearElement(this.modalEl);
2863
+ const verRefs = renderVerificationScreen({
2864
+ settings: settingsObj,
2865
+ colors,
2866
+ isMobile,
2867
+ logoUrl: activeConfig.logo_url,
2868
+ isInitiating: state.isInitiatingVerification,
2869
+ isPolling: state.isPollingStatus,
2870
+ isComplete: state.isVerificationComplete,
2871
+ error: state.verificationError,
2872
+ errorCode: state.verificationErrorCode,
2873
+ canRetry: state.canRetryVerification,
2874
+ onContinue: () => this.handleVerificationContinue(),
2875
+ onBack: (clearName) => {
2876
+ if (clearName) {
2877
+ this.handleChangeGuardian();
2878
+ } else if (state.verificationError && state.canRetryVerification) {
2879
+ this.handleVerificationRetry();
2880
+ } else {
2881
+ this.handleDecline();
2882
+ }
2883
+ }
2884
+ });
2885
+ this.modalEl.appendChild(verRefs.container);
2886
+ (_c = this.keyboardCleanup) == null ? void 0 : _c.call(this);
2887
+ this.keyboardCleanup = setupKeyboardTrap(this.modalEl, () => {
2888
+ });
2889
+ return;
2890
+ }
2891
+ clearElement(this.modalEl);
2892
+ const headingColor = (settingsObj == null ? void 0 : settingsObj.headingColor) || "#101828";
2893
+ const textColor = colors.text;
2894
+ const linkColor = colors.link;
2895
+ const font = colors.font;
2896
+ const logoImg = activeConfig.logo_url ? el("img", { src: activeConfig.logo_url, style: styles.logo, alt: "Logo" }) : null;
2897
+ this.noticeTitleEl = el("span", {
2898
+ id: "privacy-notice-title",
2899
+ style: { ...styles.title, color: headingColor, fontFamily: font, fontSize: isMobile ? "16px" : "18px" }
2900
+ });
2901
+ this.noticeTitleEl.textContent = this.getTranslatedText("notice_banner_heading", activeConfig.notice_banner_heading);
2902
+ const topLeft = el("div", { style: styles.topLeft }, logoImg, this.noticeTitleEl);
2903
+ if (((_d = this.langSelectorRefs) == null ? void 0 : _d.dropdown) && document.body.contains(this.langSelectorRefs.dropdown)) {
2904
+ document.body.removeChild(this.langSelectorRefs.dropdown);
2905
+ }
2906
+ const supportedLangs = Object.keys(activeConfig.supported_languages_and_translations || {});
2907
+ const allLanguages = [activeConfig.default_language || "English", ...supportedLangs.filter((l) => l !== activeConfig.default_language)];
2908
+ this.langSelectorRefs = renderLanguageSelector({
2909
+ languages: allLanguages,
2910
+ selectedLanguage: state.selectedLanguage,
2911
+ settings: settingsObj,
2912
+ onSelect: (lang) => this.handleLanguageSelect(lang)
2913
+ });
2914
+ const ttsStrokeColor = ((_f = (_e = settingsObj == null ? void 0 : settingsObj.button) == null ? void 0 : _e.language) == null ? void 0 : _f.textColor) || "currentColor";
2915
+ const speakerSvg = svg(
2916
+ "svg",
2917
+ {
2918
+ xmlns: "http://www.w3.org/2000/svg",
2919
+ width: "16",
2920
+ height: "16",
2921
+ viewBox: "0 0 24 24",
2922
+ fill: "none",
2923
+ stroke: ttsStrokeColor,
2924
+ "stroke-width": "2",
2925
+ "stroke-linecap": "round",
2926
+ "stroke-linejoin": "round",
2927
+ "aria-hidden": "true"
2928
+ },
2929
+ svg("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
2930
+ svg("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07" })
2931
+ );
2932
+ this.ttsButton = el("button", {
2933
+ style: {
2934
+ ...styles.topRight,
2935
+ display: "none",
2936
+ padding: "3px 9px",
2937
+ alignItems: "center",
2938
+ justifyContent: "center"
2939
+ },
2940
+ type: "button",
2941
+ "aria-label": "Start audio playback",
2942
+ title: "Start Talkback",
2943
+ onClick: () => this.ttsManager.toggle()
2944
+ });
2945
+ this.ttsButton.appendChild(speakerSvg);
2946
+ this.ttsManager.fetchAudioUrls({
2947
+ accessToken: this.options.accessToken,
2948
+ baseUrl: this.options.baseUrl,
2949
+ noticeUuid: activeConfig.notice_uuid,
2950
+ language: this.getLanguageCode(state.selectedLanguage),
2951
+ content: activeConfig
2952
+ });
2953
+ const topRight = el("div", {
2954
+ style: { display: "flex", alignItems: "center", gap: "8px" }
2955
+ }, this.ttsButton, this.langSelectorRefs.container);
2956
+ const topSection = el("div", { style: styles.topSection }, topLeft, topRight);
2957
+ this.middleSection = el("div", { style: styles.middleSection });
2958
+ const baseTextStyle = { ...styles.privacyText, color: textColor, fontFamily: font, fontSize: isMobile ? "14px" : "16px" };
2959
+ const noticeTextSpan = el("span", { id: "privacy-notice-text" });
2960
+ noticeTextSpan.textContent = this.getTranslatedText("notice_text", activeConfig.notice_text);
2961
+ const policyPrefixText = this.getTranslatedText("privacy_policy_prefix_text", activeConfig.privacy_policy_prefix_text);
2962
+ const policyAnchorText = this.getTranslatedText("privacy_policy_anchor_text", activeConfig.privacy_policy_anchor_text);
2963
+ this.privacyPolicyLinkEl = el("a", {
2964
+ id: "privacy-policy-link",
2965
+ href: activeConfig.privacy_policy_url,
2966
+ target: "_blank",
2967
+ rel: "noopener noreferrer",
2968
+ style: { ...styles.link, color: linkColor, fontFamily: font },
2969
+ "aria-label": "Privacy Policy (opens in new tab)"
2970
+ });
2971
+ this.privacyPolicyLinkEl.textContent = policyAnchorText;
2972
+ const policySpan = el(
2973
+ "span",
2974
+ { id: "privacy-policy-text" },
2975
+ `${policyPrefixText} `,
2976
+ this.privacyPolicyLinkEl
2977
+ );
2978
+ const noticeAndPolicyP = el(
2979
+ "p",
2980
+ { style: baseTextStyle },
2981
+ noticeTextSpan,
2982
+ el("br"),
2983
+ policySpan
2984
+ );
2985
+ this.purposeHeadingEl = el("h2", {
2986
+ id: "purpose-section-heading",
2987
+ style: { ...styles.subTitle, color: headingColor, fontFamily: font }
2988
+ });
2989
+ this.purposeHeadingEl.textContent = this.getTranslatedText("purpose_section_heading", activeConfig.purpose_section_heading);
2990
+ this.middleSection.appendChild(noticeAndPolicyP);
2991
+ this.middleSection.appendChild(this.purposeHeadingEl);
2992
+ if (state.needsConsentPurposes.length > 0) {
2993
+ this.purposeRefs = renderModalPurposes(state.needsConsentPurposes, {
2994
+ selectedPurposes: state.selectedPurposes,
2995
+ collapsedPurposes: state.collapsedPurposes,
2996
+ selectedDataElements: state.selectedDataElements,
2997
+ settings: settingsObj,
2998
+ isMobile,
2999
+ isAlreadyConsented: false,
3000
+ getTranslatedText: (key, defaultText, itemId) => this.getTranslatedText(key, defaultText, itemId),
3001
+ onPurposeToggle: (uuid) => this.handlePurposeCheckboxChange(uuid),
3002
+ onPurposeCollapse: (uuid) => this.togglePurposeCollapse(uuid),
3003
+ onDataElementToggle: (euuid, puuid) => this.handleDataElementCheckboxChange(euuid, puuid)
3004
+ });
3005
+ this.middleSection.appendChild(this.purposeRefs.container);
3006
+ }
3007
+ if (state.alreadyConsentedPurposes.length > 0) {
3008
+ this.alreadyConsentedPurposeRefs = renderModalPurposes(state.alreadyConsentedPurposes, {
3009
+ selectedPurposes: state.selectedPurposes,
3010
+ collapsedPurposes: state.collapsedPurposes,
3011
+ selectedDataElements: state.selectedDataElements,
3012
+ initialDataElementSelections: state.initialDataElementSelections,
3013
+ settings: settingsObj,
3014
+ isMobile,
3015
+ isAlreadyConsented: true,
3016
+ getTranslatedText: (key, defaultText, itemId) => this.getTranslatedText(key, defaultText, itemId),
3017
+ onPurposeToggle: () => {
3018
+ },
3019
+ onPurposeCollapse: (uuid) => this.togglePurposeCollapse(uuid),
3020
+ onDataElementToggle: () => {
3021
+ }
3022
+ });
3023
+ this.middleSection.appendChild(this.alreadyConsentedPurposeRefs.container);
3024
+ }
3025
+ if (activeConfig.privacy_center_url || activeConfig.dpo_info) {
3026
+ const footerDiv = el("div", {
3027
+ style: {
3028
+ ...baseTextStyle,
3029
+ fontSize: isMobile ? "13px" : "14px",
3030
+ lineHeight: "1.4",
3031
+ borderTop: "1px solid rgba(152, 162, 179, 0.3)",
3032
+ paddingTop: "12px",
3033
+ marginTop: "12px"
3034
+ }
3035
+ });
3036
+ const footerP = el("p", { style: { margin: "0", opacity: "0.6" } });
3037
+ if (activeConfig.privacy_center_url) {
3038
+ if (activeConfig.additional_text) {
3039
+ const addlSpan = el("span", { id: "additional-text" });
3040
+ addlSpan.textContent = this.getTranslatedText("additional_text", activeConfig.additional_text);
3041
+ footerP.appendChild(addlSpan);
3042
+ footerP.appendChild(document.createTextNode(" "));
3043
+ }
3044
+ const pcAnchorText = this.getTranslatedText("privacy_center_anchor_text", activeConfig.privacy_center_anchor_text || "Privacy Center");
3045
+ this.privacyCenterLinkEl = el("a", {
3046
+ id: "privacy-center-link",
3047
+ href: activeConfig.privacy_center_url,
3048
+ target: "_blank",
3049
+ rel: "noopener noreferrer",
3050
+ style: { ...styles.link, color: linkColor, fontFamily: font }
3051
+ });
3052
+ this.privacyCenterLinkEl.textContent = pcAnchorText;
3053
+ footerP.appendChild(this.privacyCenterLinkEl);
3054
+ footerP.appendChild(document.createTextNode("."));
3055
+ }
3056
+ if (activeConfig.dpo_info) {
3057
+ const dpo = activeConfig.dpo_info;
3058
+ if (activeConfig.privacy_center_url) {
3059
+ footerP.appendChild(el("br"));
3060
+ }
3061
+ if (dpo.grievance_text) {
3062
+ const gText = el("span", { id: "dpo-grievance-text" });
3063
+ gText.textContent = this.getTranslatedDpoText("grievance_text", dpo.grievance_text);
3064
+ footerP.appendChild(gText);
3065
+ footerP.appendChild(document.createTextNode(" "));
3066
+ if (dpo.grievance_url) {
3067
+ const gLink = el("a", {
3068
+ id: "dpo-grievance-link",
3069
+ href: dpo.grievance_url,
3070
+ target: "_blank",
3071
+ rel: "noopener noreferrer",
3072
+ style: { ...styles.link, color: linkColor, fontFamily: font }
3073
+ });
3074
+ gLink.textContent = this.getTranslatedDpoText("grievance_anchor_text", dpo.grievance_anchor_text);
3075
+ footerP.appendChild(gLink);
3076
+ }
3077
+ if (dpo.grievance_email) {
3078
+ footerP.appendChild(document.createTextNode(" "));
3079
+ const connectorSpan = el("span", { id: "dpo-grievance-email-connector" });
3080
+ connectorSpan.textContent = this.getTranslatedDpoText(
3081
+ "grievance_email_connector_text",
3082
+ dpo.grievance_email_connector_text || "or email to"
3083
+ );
3084
+ footerP.appendChild(connectorSpan);
3085
+ footerP.appendChild(document.createTextNode(" "));
3086
+ const emailLink = el("a", {
3087
+ id: "dpo-grievance-email",
3088
+ href: `mailto:${dpo.grievance_email}`,
3089
+ style: { ...styles.link, color: linkColor, fontFamily: font }
3090
+ });
3091
+ emailLink.textContent = dpo.grievance_email;
3092
+ footerP.appendChild(emailLink);
3093
+ }
3094
+ }
3095
+ if (dpo.dp_board_text) {
3096
+ footerP.appendChild(el("br"));
3097
+ const dpText = el("span", { id: "dpo-dp-board-text" });
3098
+ dpText.textContent = this.getTranslatedDpoText("dp_board_text", dpo.dp_board_text);
3099
+ footerP.appendChild(dpText);
3100
+ footerP.appendChild(document.createTextNode(" "));
3101
+ if (dpo.dp_board_url) {
3102
+ const dpLink = el("a", {
3103
+ id: "dpo-dp-board-link",
3104
+ href: dpo.dp_board_url,
3105
+ target: "_blank",
3106
+ rel: "noopener noreferrer",
3107
+ style: { ...styles.link, color: linkColor, fontFamily: font }
3108
+ });
3109
+ dpLink.textContent = this.getTranslatedDpoText("dp_board_anchor_text", dpo.dp_board_anchor_text);
3110
+ footerP.appendChild(dpLink);
3111
+ }
3112
+ }
3113
+ if (dpo.dpo_text) {
3114
+ footerP.appendChild(el("br"));
3115
+ const dpoTextSpan = el("span", { id: "dpo-dpo-text" });
3116
+ dpoTextSpan.textContent = this.getTranslatedDpoText("dpo_text", dpo.dpo_text);
3117
+ footerP.appendChild(dpoTextSpan);
3118
+ footerP.appendChild(document.createTextNode(" "));
3119
+ if (dpo.dpo_url) {
3120
+ const dpoLink = el("a", {
3121
+ id: "dpo-dpo-link",
3122
+ href: dpo.dpo_url,
3123
+ target: "_blank",
3124
+ rel: "noopener noreferrer",
3125
+ style: { ...styles.link, color: linkColor, fontFamily: font }
3126
+ });
3127
+ dpoLink.textContent = this.getTranslatedDpoText("dpo_anchor_text", dpo.dpo_anchor_text);
3128
+ footerP.appendChild(dpoLink);
3129
+ }
3130
+ }
3131
+ }
3132
+ footerDiv.appendChild(footerP);
3133
+ this.middleSection.appendChild(footerDiv);
3134
+ }
3135
+ this.errorEl = el("div", {
3136
+ role: "alert",
3137
+ style: {
3138
+ backgroundColor: "#FEE2E2",
3139
+ color: "#DC2626",
3140
+ padding: "12px",
3141
+ borderRadius: "6px",
3142
+ fontSize: "14px",
3143
+ border: "1px solid #FCA5A5",
3144
+ display: "none"
3145
+ }
3146
+ });
3147
+ const acceptBtnText = state.isFullyConsentedReviewMode ? this.options.reviewModeButtonText || "Continue" : this.getTranslatedText("confirm_button_text", activeConfig.confirm_button_text);
3148
+ const acceptStyle = {
3149
+ ...styles.button,
3150
+ ...styles.acceptButton,
3151
+ backgroundColor: colors.acceptBg,
3152
+ color: colors.acceptText,
3153
+ borderRadius: colors.borderRadius,
3154
+ fontSize: isMobile ? "14px" : "16px",
3155
+ padding: isMobile ? "10px 20px" : "9px 45px"
3156
+ };
3157
+ this.acceptBtn = el("button", {
3158
+ id: "confirm-button-text",
3159
+ style: acceptStyle,
3160
+ type: "button",
3161
+ onClick: () => this.handleAccept()
3162
+ });
3163
+ this.acceptBtn.textContent = acceptBtnText;
3164
+ const declineBtnText = this.getTranslatedText("decline_button_text", activeConfig.decline_button_text);
3165
+ const declineStyle = {
3166
+ ...styles.button,
3167
+ ...styles.cancelButton,
3168
+ backgroundColor: colors.declineBg,
3169
+ color: colors.declineText,
3170
+ borderColor: colors.declineBorder,
3171
+ borderRadius: colors.borderRadius,
3172
+ fontSize: isMobile ? "14px" : "16px",
3173
+ padding: isMobile ? "10px 20px" : "9px 45px"
3174
+ };
3175
+ this.declineBtn = el("button", {
3176
+ id: "decline-button-text",
3177
+ style: declineStyle,
3178
+ type: "button",
3179
+ onClick: () => this.handleDecline()
3180
+ });
3181
+ if (!state.isFullyConsentedReviewMode) {
3182
+ this.declineBtn.textContent = declineBtnText;
3183
+ }
3184
+ const bottomSection = el("div", { style: styles.bottomSection });
3185
+ if (!state.isFullyConsentedReviewMode) {
3186
+ bottomSection.appendChild(this.acceptBtn);
3187
+ bottomSection.appendChild(this.declineBtn);
3188
+ } else {
3189
+ bottomSection.appendChild(this.acceptBtn);
3190
+ }
3191
+ const contentWrapper = el(
3192
+ "div",
3193
+ { style: styles.content },
3194
+ topSection,
3195
+ this.errorEl,
3196
+ this.middleSection,
3197
+ bottomSection
3198
+ );
3199
+ this.modalEl.appendChild(contentWrapper);
3200
+ this.updateAcceptButtonState(state);
3201
+ (_g = this.keyboardCleanup) == null ? void 0 : _g.call(this);
3202
+ this.keyboardCleanup = setupKeyboardTrap(this.modalEl, () => this.handleDecline());
3203
+ }
3204
+ getLanguageCode(displayLanguage) {
3205
+ var _a, _b;
3206
+ const content = (_a = this.store.getState().content) == null ? void 0 : _a.detail.active_config;
3207
+ if (!content) return "English";
3208
+ if (displayLanguage === "English" || displayLanguage === "en" || displayLanguage === "EN") {
3209
+ return "English";
3210
+ }
3211
+ if ((_b = content.supported_languages_and_translations) == null ? void 0 : _b[displayLanguage]) {
3212
+ return displayLanguage;
3213
+ }
3214
+ return content.default_language === "en" || content.default_language === "EN" ? "English" : content.default_language || "English";
3215
+ }
3216
+ getTranslatedDpoText(key, defaultText) {
3217
+ var _a;
3218
+ const state = this.store.getState();
3219
+ const content = (_a = state.content) == null ? void 0 : _a.detail.active_config;
3220
+ if (!content) return defaultText;
3221
+ const lang = state.selectedLanguage;
3222
+ if (lang === "English" || lang === "en" || lang === "EN") return defaultText;
3223
+ const translations = content.supported_languages_and_translations;
3224
+ if (!translations) return defaultText;
3225
+ const translationMap = translations[lang];
3226
+ if (!(translationMap == null ? void 0 : translationMap.dpo_info)) return defaultText;
3227
+ const value = translationMap.dpo_info[key];
3228
+ return typeof value === "string" && value ? value : defaultText;
3229
+ }
3230
+ renderError(message) {
3231
+ if (!this.modalEl) return;
3232
+ clearElement(this.modalEl);
3233
+ const errorContent = el(
3234
+ "div",
3235
+ { style: { ...styles.content, alignItems: "center", justifyContent: "center" } },
3236
+ el("div", {
3237
+ role: "alert",
3238
+ style: {
3239
+ backgroundColor: "#FEE2E2",
3240
+ color: "#DC2626",
3241
+ padding: "16px",
3242
+ borderRadius: "8px",
3243
+ fontSize: "14px",
3244
+ border: "1px solid #FCA5A5",
3245
+ textAlign: "center",
3246
+ maxWidth: "400px"
3247
+ }
3248
+ }, message)
3249
+ );
3250
+ this.modalEl.appendChild(errorContent);
3251
+ }
3252
+ getTranslatedText(key, defaultText, itemId) {
3253
+ var _a, _b, _c;
3254
+ const state = this.store.getState();
3255
+ const content = (_a = state.content) == null ? void 0 : _a.detail.active_config;
3256
+ if (!content) return defaultText;
3257
+ const lang = state.selectedLanguage;
3258
+ if (lang === "English" || lang === "en" || lang === "EN") {
3259
+ if (key === "privacy_policy_anchor_text" && content.privacy_policy_anchor_text) {
3260
+ return content.privacy_policy_anchor_text;
3261
+ }
3262
+ return defaultText;
3263
+ }
3264
+ const translations = content.supported_languages_and_translations;
3265
+ if (!translations) return defaultText;
3266
+ const translationMap = translations[lang];
3267
+ if (!translationMap) return defaultText;
3268
+ if (itemId) {
3269
+ if (key === "purposes.name" || key === "purposes.description") {
3270
+ const purposeData = (_b = translationMap.purposes) == null ? void 0 : _b[itemId];
3271
+ if (purposeData) {
3272
+ if (typeof purposeData === "string") {
3273
+ return key === "purposes.name" ? purposeData : defaultText;
3274
+ }
3275
+ return key === "purposes.name" ? purposeData.name || defaultText : purposeData.description || defaultText;
3276
+ }
3277
+ return defaultText;
3278
+ }
3279
+ if (key.startsWith("data_elements.")) {
3280
+ return ((_c = translationMap.data_elements) == null ? void 0 : _c[itemId]) || defaultText;
3281
+ }
3282
+ }
3283
+ const value = translationMap[key];
3284
+ if (typeof value === "string" && value) return value;
3285
+ return defaultText;
3286
+ }
3287
+ handleLanguageSelect(lang) {
3288
+ this.store.setState({ selectedLanguage: lang });
3289
+ this.rebuildUI();
3290
+ }
3291
+ togglePurposeCollapse(purposeUuid) {
3292
+ const state = this.store.getState();
3293
+ this.store.setState({
3294
+ collapsedPurposes: {
3295
+ ...state.collapsedPurposes,
3296
+ [purposeUuid]: !state.collapsedPurposes[purposeUuid]
3297
+ }
3298
+ });
3299
+ }
3300
+ handlePurposeCheckboxChange(purposeUuid) {
3301
+ var _a;
3302
+ const state = this.store.getState();
3303
+ const content = (_a = state.content) == null ? void 0 : _a.detail.active_config;
3304
+ if (!content) return;
3305
+ const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
3306
+ if (!purpose) return;
3307
+ const newPurposeState = !state.selectedPurposes[purposeUuid];
3308
+ const updatedElements = { ...state.selectedDataElements };
3309
+ for (const de of purpose.data_elements) {
3310
+ updatedElements[`${purposeUuid}-${de.uuid}`] = newPurposeState;
3311
+ }
3312
+ this.store.setState({
3313
+ selectedPurposes: { ...state.selectedPurposes, [purposeUuid]: newPurposeState },
3314
+ selectedDataElements: updatedElements
3315
+ });
3316
+ }
3317
+ handleDataElementCheckboxChange(elementUuid, purposeUuid) {
3318
+ var _a;
3319
+ const state = this.store.getState();
3320
+ const content = (_a = state.content) == null ? void 0 : _a.detail.active_config;
3321
+ if (!content) return;
3322
+ const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
3323
+ if (!purpose) return;
3324
+ const combinedId = `${purposeUuid}-${elementUuid}`;
3325
+ const newDataElements = {
3326
+ ...state.selectedDataElements,
3327
+ [combinedId]: !state.selectedDataElements[combinedId]
3328
+ };
3329
+ const requiredElements = purpose.data_elements.filter((el2) => el2.required);
3330
+ let shouldCheckPurpose;
3331
+ if (requiredElements.length > 0) {
3332
+ shouldCheckPurpose = requiredElements.every(
3333
+ (el2) => newDataElements[`${purposeUuid}-${el2.uuid}`]
3334
+ );
3335
+ } else {
3336
+ shouldCheckPurpose = purpose.data_elements.some(
3337
+ (el2) => newDataElements[`${purposeUuid}-${el2.uuid}`]
3338
+ );
3339
+ }
3340
+ this.store.setState({
3341
+ selectedDataElements: newDataElements,
3342
+ selectedPurposes: { ...state.selectedPurposes, [purposeUuid]: shouldCheckPurpose }
3343
+ });
3344
+ }
3345
+ async handleAccept() {
3346
+ var _a, _b, _c;
3347
+ const state = this.store.getState();
3348
+ if (state.isSubmitting) return;
3349
+ if (state.isFullyConsentedReviewMode) {
3350
+ this.options.onAccept();
3351
+ this.destroy();
3352
+ return;
3353
+ }
3354
+ if (state.isMinorFlow && !state.verificationReference && !state.selfDeclaredAdult) {
3355
+ this.store.setState({
3356
+ errorMessage: "Guardian verification is required before consent can be submitted."
3357
+ });
3358
+ return;
3359
+ }
3360
+ const content = (_a = state.content) == null ? void 0 : _a.detail.active_config;
3361
+ if (!content) return;
3362
+ this.abortController = new AbortController();
3363
+ this.store.setState({ isSubmitting: true, isSubmittingAccept: true, errorMessage: null });
3364
+ try {
3365
+ const purposes = content.purposes.map((purpose) => {
3366
+ const dataElements = purpose.data_elements.filter((de) => de.enabled).map((de) => ({
3367
+ uuid: de.uuid,
3368
+ name: de.name,
3369
+ enabled: de.enabled,
3370
+ required: de.required,
3371
+ selected: state.selectedDataElements[`${purpose.uuid}-${de.uuid}`] || false
3372
+ }));
3373
+ return {
3374
+ uuid: purpose.uuid,
3375
+ name: purpose.name,
3376
+ description: purpose.description,
3377
+ selected: state.selectedPurposes[purpose.uuid] || false,
3378
+ data_elements: dataElements
3379
+ };
3380
+ });
3381
+ await submitConsentEvent({
3382
+ accessToken: this.options.accessToken,
3383
+ baseUrl: this.options.baseUrl,
3384
+ noticeUuid: state.content.detail.uuid,
3385
+ purposes,
3386
+ declined: false,
3387
+ meta_data: this.options.applicationId ? { specific_uuid: this.options.applicationId } : void 0,
3388
+ guardianVerificationReference: state.verificationReference || void 0,
3389
+ selfDeclaredAdult: state.selfDeclaredAdult || void 0,
3390
+ signal: this.abortController.signal
3391
+ });
3392
+ this.options.onAccept();
3393
+ this.destroy();
3394
+ } catch (err) {
3395
+ if (err.name === "AbortError") return;
3396
+ const error = err;
3397
+ const message = error.status === 409 ? "You have already provided consent." : error.status && error.status >= 500 ? "Server error: Please try again later." : error.message || "Failed to submit consent.";
3398
+ this.store.setState({ errorMessage: message, isSubmitting: false, isSubmittingAccept: false });
3399
+ (_c = (_b = this.options).onError) == null ? void 0 : _c.call(_b, error);
3400
+ }
3401
+ }
3402
+ handleDecline() {
3403
+ const state = this.store.getState();
3404
+ if (state.isSubmitting) return;
3405
+ this.stopPolling();
3406
+ clearConsentState();
3407
+ this.options.onDecline();
3408
+ this.destroy();
3409
+ }
3410
+ updateCheckboxes(state) {
3411
+ if (this.purposeRefs) {
3412
+ for (const [key, checkbox] of this.purposeRefs.checkboxes) {
3413
+ if (key in state.selectedPurposes) {
3414
+ checkbox.checked = state.selectedPurposes[key] || false;
3415
+ } else {
3416
+ checkbox.checked = state.selectedDataElements[key] || false;
3417
+ }
3418
+ }
3419
+ }
3420
+ this.updateAcceptButtonState(state);
3421
+ }
3422
+ areAllRequiredChecked(state) {
3423
+ var _a, _b;
3424
+ const content = (_a = state.content) == null ? void 0 : _a.detail.active_config;
3425
+ if (!((_b = content == null ? void 0 : content.purposes) == null ? void 0 : _b.length)) return false;
3426
+ return content.purposes.every((purpose) => {
3427
+ const requiredElements = purpose.data_elements.filter((de) => de.required && de.enabled);
3428
+ if (requiredElements.length === 0) return true;
3429
+ return requiredElements.every(
3430
+ (de) => state.selectedDataElements[`${purpose.uuid}-${de.uuid}`]
3431
+ );
3432
+ });
3433
+ }
3434
+ updateAcceptButtonState(state) {
3435
+ if (!this.acceptBtn || state.isFullyConsentedReviewMode) return;
3436
+ const allRequiredChecked = this.areAllRequiredChecked(state);
3437
+ const disabled = !allRequiredChecked || state.isSubmittingAccept || state.isSubmittingDecline;
3438
+ this.acceptBtn.disabled = disabled;
3439
+ this.acceptBtn.style.opacity = disabled ? "0.5" : "1";
3440
+ this.acceptBtn.style.cursor = disabled ? "not-allowed" : "pointer";
3441
+ }
3442
+ updateCollapseState(state) {
3443
+ const updateRefs = (refs) => {
3444
+ var _a, _b;
3445
+ if (!refs) return;
3446
+ for (const [purposeUuid, container] of refs.dataElementContainers) {
3447
+ const content = (_a = state.content) == null ? void 0 : _a.detail.active_config;
3448
+ const purpose = content == null ? void 0 : content.purposes.find((p) => p.uuid === purposeUuid);
3449
+ const isCollapsed = state.collapsedPurposes[purposeUuid];
3450
+ container.style.display = !isCollapsed && ((_b = purpose == null ? void 0 : purpose.data_elements) == null ? void 0 : _b.length) ? "flex" : "none";
3451
+ const chevron = refs.chevrons.get(purposeUuid);
3452
+ if (chevron) {
3453
+ chevron.style.transform = !isCollapsed ? "rotate(90deg)" : "rotate(0deg)";
3454
+ }
3455
+ const collapseBtn = refs.collapseButtons.get(purposeUuid);
3456
+ if (collapseBtn) {
3457
+ collapseBtn.setAttribute("aria-expanded", String(!isCollapsed));
3458
+ }
3459
+ }
3460
+ };
3461
+ updateRefs(this.purposeRefs);
3462
+ updateRefs(this.alreadyConsentedPurposeRefs);
3463
+ }
3464
+ updateErrorMessage(state) {
3465
+ if (!this.errorEl) return;
3466
+ if (state.errorMessage) {
3467
+ this.errorEl.textContent = state.errorMessage;
3468
+ this.errorEl.style.display = "block";
3469
+ } else {
3470
+ this.errorEl.style.display = "none";
3471
+ }
3472
+ }
3473
+ // ─── Age Verification Handlers ───
3474
+ handleAgeVerificationYes() {
3475
+ this.store.setState({
3476
+ isMinorFlow: false,
3477
+ selfDeclaredAdult: true,
3478
+ showAgeVerification: false
3479
+ });
3480
+ }
3481
+ handleAgeVerificationNo() {
3482
+ this.store.setState({
3483
+ showAgeVerification: false,
3484
+ showGuardianForm: true
3485
+ });
3486
+ }
3487
+ // ─── Guardian Form Handlers ───
3488
+ async handleGuardianFormSubmit() {
3489
+ const state = this.store.getState();
3490
+ const errors = validateGuardianForm(state.guardianFormData);
3491
+ if (Object.keys(errors).length > 0) {
3492
+ if (this.guardianFormRefs) {
3493
+ if (errors.guardianName) {
3494
+ this.guardianFormRefs.nameError.textContent = errors.guardianName;
3495
+ this.guardianFormRefs.nameError.style.display = "block";
3496
+ }
3497
+ if (errors.guardianContact) {
3498
+ this.guardianFormRefs.contactError.textContent = errors.guardianContact;
3499
+ this.guardianFormRefs.contactError.style.display = "block";
3500
+ }
3501
+ if (errors.guardianRelationship) {
3502
+ this.guardianFormRefs.relationshipError.textContent = errors.guardianRelationship;
3503
+ this.guardianFormRefs.relationshipError.style.display = "block";
3504
+ }
3505
+ }
3506
+ return;
3507
+ }
3508
+ this.store.setState({ isInitiatingVerification: true });
3509
+ const digilockerMode = this.options.digilockerMode || "redirect";
3510
+ const callbackUrl = digilockerMode === "popup" ? new URL(this.options.digilockerCallbackUrl || "/digilocker-callback", window.location.origin).href : void 0;
3511
+ try {
3512
+ const response = await initiateGuardianVerification({
3513
+ accessToken: this.options.accessToken,
3514
+ baseUrl: this.options.baseUrl,
3515
+ guardianName: state.guardianFormData.guardianName,
3516
+ guardianContact: state.guardianFormData.guardianContact,
3517
+ guardianRelationship: state.guardianFormData.guardianRelationship,
3518
+ frontendCallbackUrl: callbackUrl
3519
+ });
3520
+ if (response.already_verified && response.verification_reference) {
3521
+ this.store.setState({
3522
+ verificationReference: response.verification_reference,
3523
+ showGuardianForm: false,
3524
+ showVerificationScreen: true,
3525
+ isInitiatingVerification: false,
3526
+ isVerificationComplete: true
3527
+ });
3528
+ setTimeout(() => {
3529
+ this.store.setState({
3530
+ showVerificationScreen: false,
3531
+ isVerificationComplete: false
3532
+ });
3533
+ }, 1e3);
3534
+ return;
3535
+ }
3536
+ saveConsentState({
3537
+ flowState: "digilocker-pending",
3538
+ sessionToken: response.session_token,
3539
+ guardianInfoUuid: response.guardian_info_uuid,
3540
+ selectedPurposes: state.selectedPurposes,
3541
+ selectedDataElements: state.selectedDataElements,
3542
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3543
+ originalUrl: window.location.href,
3544
+ noticeId: this.options.noticeId,
3545
+ baseUrl: this.options.baseUrl
3546
+ });
3547
+ this.store.setState({
3548
+ showGuardianForm: false,
3549
+ showVerificationScreen: true,
3550
+ isInitiatingVerification: true,
3551
+ isPollingStatus: false
3552
+ });
3553
+ if (digilockerMode === "popup" && response.digilocker_redirect_url) {
3554
+ const popup = window.open(
3555
+ response.digilocker_redirect_url,
3556
+ "digilocker_verification",
3557
+ "width=600,height=700,scrollbars=yes,resizable=yes"
3558
+ );
3559
+ if (popup) {
3560
+ this.verificationPopup = popup;
3561
+ this.startPopupMonitoring(popup);
3562
+ } else {
3563
+ this.store.setState({
3564
+ isInitiatingVerification: false,
3565
+ verificationError: "Popup blocked. Please allow popups for this site.",
3566
+ canRetryVerification: true
3567
+ });
3568
+ return;
3569
+ }
3570
+ } else if (response.digilocker_redirect_url) {
3571
+ const openedWindow = window.open(response.digilocker_redirect_url, "_blank");
3572
+ if (!openedWindow) {
3573
+ this.store.setState({
3574
+ isInitiatingVerification: false,
3575
+ verificationError: "Unable to open DigiLocker. Please allow popups.",
3576
+ canRetryVerification: true
3577
+ });
3578
+ return;
3579
+ }
3580
+ try {
3581
+ openedWindow.opener = null;
3582
+ } catch {
3583
+ }
3584
+ }
3585
+ this.store.setState({
3586
+ isInitiatingVerification: false,
3587
+ isPollingStatus: true
3588
+ });
3589
+ this.startStatusPolling(response.session_token);
3590
+ } catch (error) {
3591
+ this.store.setState({ isInitiatingVerification: false });
3592
+ const err = error;
3593
+ if (this.guardianFormRefs) {
3594
+ this.guardianFormRefs.generalError.textContent = err.message || "Failed to initiate verification.";
3595
+ this.guardianFormRefs.generalError.style.display = "block";
3596
+ }
3597
+ }
3598
+ }
3599
+ // ─── Verification Polling ───
3600
+ startStatusPolling(token) {
3601
+ this.verificationSessionToken = token;
3602
+ let attempts = 0;
3603
+ let consecutiveErrors = 0;
3604
+ let paused = false;
3605
+ const MAX_ATTEMPTS = 40;
3606
+ const MAX_CONSECUTIVE_ERRORS = 3;
3607
+ const POLL_INTERVAL = 3e3;
3608
+ const poll = async () => {
3609
+ var _a;
3610
+ if (paused) return;
3611
+ attempts++;
3612
+ if (attempts > MAX_ATTEMPTS) {
3613
+ this.stopPolling();
3614
+ this.store.setState({
3615
+ verificationError: "Verification is taking longer than expected. Please try again.",
3616
+ verificationErrorCode: "SESSION_EXPIRED",
3617
+ canRetryVerification: true,
3618
+ isPollingStatus: false
3619
+ });
3620
+ return;
3621
+ }
3622
+ try {
3623
+ const response = await verifyGuardianStatus({
3624
+ accessToken: this.options.accessToken,
3625
+ baseUrl: this.options.baseUrl,
3626
+ sessionToken: token
3627
+ });
3628
+ consecutiveErrors = 0;
3629
+ if (response.status === "verified") {
3630
+ this.stopPolling();
3631
+ const ref = response.verification_reference || ((_a = response.guardian_details) == null ? void 0 : _a.verification_reference);
3632
+ if (!ref) {
3633
+ this.store.setState({
3634
+ verificationError: "Verification completed but no reference received. Please try again.",
3635
+ canRetryVerification: true,
3636
+ isPollingStatus: false
3637
+ });
3638
+ return;
3639
+ }
3640
+ this.store.setState({
3641
+ verificationReference: ref,
3642
+ isVerificationComplete: true,
3643
+ isPollingStatus: false
3644
+ });
3645
+ clearConsentState();
3646
+ return;
3647
+ }
3648
+ if (response.status === "failed" || response.status === "expired") {
3649
+ this.stopPolling();
3650
+ this.store.setState({
3651
+ verificationError: getVerificationErrorMessage(response.error_code, response.error),
3652
+ verificationErrorCode: response.error_code,
3653
+ canRetryVerification: response.can_retry,
3654
+ isPollingStatus: false
3655
+ });
3656
+ return;
3657
+ }
3658
+ if (!paused) {
3659
+ this.statusPollTimeout = setTimeout(poll, POLL_INTERVAL);
3660
+ }
3661
+ } catch (error) {
3662
+ const err = error;
3663
+ if (err.status === 404 || err.status === 410) {
3664
+ this.stopPolling();
3665
+ this.store.setState({
3666
+ verificationError: "Verification session not found or expired.",
3667
+ verificationErrorCode: "SESSION_EXPIRED",
3668
+ canRetryVerification: true,
3669
+ isPollingStatus: false
3670
+ });
3671
+ return;
3672
+ }
3673
+ consecutiveErrors++;
3674
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
3675
+ this.stopPolling();
3676
+ this.store.setState({
3677
+ verificationError: err.message || "Unable to check verification status.",
3678
+ canRetryVerification: true,
3679
+ isPollingStatus: false
3680
+ });
3681
+ return;
3682
+ }
3683
+ this.statusPollTimeout = setTimeout(poll, POLL_INTERVAL);
3684
+ }
3685
+ };
3686
+ this.visibilityChangeHandler = () => {
3687
+ if (document.hidden) {
3688
+ paused = true;
3689
+ if (this.statusPollTimeout) {
3690
+ clearTimeout(this.statusPollTimeout);
3691
+ this.statusPollTimeout = null;
3692
+ }
3693
+ } else {
3694
+ paused = false;
3695
+ poll();
3696
+ }
3697
+ };
3698
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
3699
+ this.statusPollTimeout = setTimeout(poll, POLL_INTERVAL);
3700
+ }
3701
+ stopPolling() {
3702
+ var _a;
3703
+ if (this.statusPollTimeout) {
3704
+ clearTimeout(this.statusPollTimeout);
3705
+ this.statusPollTimeout = null;
3706
+ }
3707
+ if (this.popupCheckInterval) {
3708
+ clearInterval(this.popupCheckInterval);
3709
+ this.popupCheckInterval = null;
3710
+ }
3711
+ if (this.visibilityChangeHandler) {
3712
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
3713
+ this.visibilityChangeHandler = null;
3714
+ }
3715
+ (_a = this.verificationPopup) == null ? void 0 : _a.close();
3716
+ this.verificationPopup = null;
3717
+ }
3718
+ startPopupMonitoring(popup) {
3719
+ this.popupCheckInterval = setInterval(() => {
3720
+ if (popup.closed) {
3721
+ if (this.popupCheckInterval) {
3722
+ clearInterval(this.popupCheckInterval);
3723
+ this.popupCheckInterval = null;
3724
+ }
3725
+ }
3726
+ }, 500);
3727
+ }
3728
+ // ─── Verification Screen Handlers ───
3729
+ handleVerificationContinue() {
3730
+ this.store.setState({
3731
+ showVerificationScreen: false,
3732
+ isVerificationComplete: false
3733
+ });
3734
+ }
3735
+ handleVerificationRetry() {
3736
+ this.store.setState({
3737
+ showVerificationScreen: false,
3738
+ showGuardianForm: true,
3739
+ verificationError: null,
3740
+ verificationErrorCode: null,
3741
+ canRetryVerification: false,
3742
+ isVerificationComplete: false
3743
+ });
3744
+ }
3745
+ handleChangeGuardian() {
3746
+ this.store.setState({
3747
+ showVerificationScreen: false,
3748
+ showGuardianForm: true,
3749
+ verificationError: null,
3750
+ verificationErrorCode: null,
3751
+ canRetryVerification: false,
3752
+ isVerificationComplete: false,
3753
+ guardianFormData: { guardianName: "", guardianContact: "", guardianRelationship: "" }
3754
+ });
3755
+ }
3756
+ // ─── TTS ───
3757
+ updateTTSButton() {
3758
+ var _a, _b, _c;
3759
+ if (!this.ttsButton) return;
3760
+ if (this.ttsManager.isAvailable) {
3761
+ this.ttsButton.style.display = "flex";
3762
+ clearElement(this.ttsButton);
3763
+ const strokeColor = ((_c = (_b = (_a = this.options.settings) == null ? void 0 : _a.button) == null ? void 0 : _b.language) == null ? void 0 : _c.textColor) || "currentColor";
3764
+ if (this.ttsManager.isPlaying) {
3765
+ const pauseSvg = svg(
3766
+ "svg",
3767
+ {
3768
+ xmlns: "http://www.w3.org/2000/svg",
3769
+ width: "16",
3770
+ height: "16",
3771
+ viewBox: "0 0 24 24",
3772
+ fill: "none",
3773
+ stroke: strokeColor,
3774
+ "stroke-width": "2",
3775
+ "stroke-linecap": "round",
3776
+ "stroke-linejoin": "round",
3777
+ "aria-hidden": "true"
3778
+ },
3779
+ svg("rect", { x: "6", y: "4", width: "4", height: "16" }),
3780
+ svg("rect", { x: "14", y: "4", width: "4", height: "16" })
3781
+ );
3782
+ this.ttsButton.appendChild(pauseSvg);
3783
+ this.ttsButton.setAttribute("aria-label", "Pause audio playback");
3784
+ this.ttsButton.setAttribute("title", "Pause Talkback");
3785
+ } else {
3786
+ const speakerSvg = svg(
3787
+ "svg",
3788
+ {
3789
+ xmlns: "http://www.w3.org/2000/svg",
3790
+ width: "16",
3791
+ height: "16",
3792
+ viewBox: "0 0 24 24",
3793
+ fill: "none",
3794
+ stroke: strokeColor,
3795
+ "stroke-width": "2",
3796
+ "stroke-linecap": "round",
3797
+ "stroke-linejoin": "round",
3798
+ "aria-hidden": "true"
3799
+ },
3800
+ svg("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5" }),
3801
+ svg("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07" })
3802
+ );
3803
+ this.ttsButton.appendChild(speakerSvg);
3804
+ this.ttsButton.setAttribute("aria-label", this.ttsManager.isPaused ? "Resume audio playback" : "Start audio playback");
3805
+ this.ttsButton.setAttribute("title", this.ttsManager.isPaused ? "Resume Talkback" : "Start Talkback");
3806
+ }
3807
+ }
3808
+ }
3809
+ updateButtonStates(state) {
3810
+ var _a;
3811
+ if (this.acceptBtn) {
3812
+ if (state.isSubmittingAccept) {
3813
+ this.acceptBtn.textContent = "Submitting...";
3814
+ this.acceptBtn.disabled = true;
3815
+ this.acceptBtn.style.opacity = "0.7";
3816
+ this.acceptBtn.style.cursor = "not-allowed";
3817
+ } else {
3818
+ const content = (_a = state.content) == null ? void 0 : _a.detail.active_config;
3819
+ this.acceptBtn.textContent = state.isFullyConsentedReviewMode ? this.options.reviewModeButtonText || "Continue" : this.getTranslatedText("confirm_button_text", (content == null ? void 0 : content.confirm_button_text) || "Accept");
3820
+ this.acceptBtn.disabled = false;
3821
+ this.acceptBtn.style.opacity = "1";
3822
+ this.acceptBtn.style.cursor = "pointer";
3823
+ }
3824
+ }
3825
+ if (this.declineBtn) {
3826
+ this.declineBtn.disabled = state.isSubmitting;
3827
+ this.declineBtn.style.opacity = state.isSubmitting ? "0.7" : "1";
3828
+ this.declineBtn.style.cursor = state.isSubmitting ? "not-allowed" : "pointer";
3829
+ }
3830
+ }
3831
+ };
3832
+
3833
+ // src/styles/inline.ts
3834
+ var inlineStyles = {
3835
+ optionItem: {
3836
+ display: "flex",
3837
+ justifyContent: "space-between",
3838
+ alignItems: "center",
3839
+ margin: 0,
3840
+ padding: 0,
3841
+ boxSizing: "border-box"
3842
+ },
3843
+ optionLeft: {
3844
+ display: "flex",
3845
+ gap: "12px",
3846
+ alignItems: "center",
3847
+ cursor: "pointer",
3848
+ margin: 0,
3849
+ padding: 0,
3850
+ boxSizing: "border-box"
3851
+ },
3852
+ optionTextContainer: {
3853
+ display: "flex",
3854
+ flexDirection: "column",
3855
+ margin: 0,
3856
+ padding: 0,
3857
+ boxSizing: "border-box"
3858
+ },
3859
+ optionTitle: {
3860
+ fontWeight: 500,
3861
+ fontSize: "16px",
3862
+ lineHeight: "150%",
3863
+ letterSpacing: "0.2px",
3864
+ color: "#101828",
3865
+ margin: 0,
3866
+ padding: 0,
3867
+ textAlign: "left"
3868
+ },
3869
+ optionDescription: {
3870
+ fontWeight: 400,
3871
+ fontSize: "12px",
3872
+ lineHeight: "150%",
3873
+ letterSpacing: "0.2px",
3874
+ color: "#475467",
3875
+ verticalAlign: "middle",
3876
+ margin: 0,
3877
+ padding: 0,
3878
+ textAlign: "left"
3879
+ },
3880
+ checkboxLarge: {
3881
+ height: "20px",
3882
+ width: "20px",
3883
+ padding: "4px",
3884
+ borderRadius: "5px",
3885
+ border: "2px solid #d0d5dd",
3886
+ margin: 0,
3887
+ boxSizing: "border-box",
3888
+ appearance: "none",
3889
+ WebkitAppearance: "none",
3890
+ MozAppearance: "none",
3891
+ cursor: "pointer",
3892
+ position: "relative",
3893
+ backgroundColor: "transparent"
3894
+ },
3895
+ dataElementsContainer: {
3896
+ marginLeft: "27px",
3897
+ display: "flex",
3898
+ flexDirection: "column",
3899
+ margin: 0,
3900
+ padding: 0,
3901
+ boxSizing: "border-box",
3902
+ paddingLeft: "22px"
3903
+ },
3904
+ dataElementItem: {
3905
+ display: "flex",
3906
+ alignItems: "center",
3907
+ gap: "10px",
3908
+ justifyContent: "space-between",
3909
+ margin: 0,
3910
+ padding: 0,
3911
+ boxSizing: "border-box",
3912
+ minHeight: "24px"
3913
+ },
3914
+ dataElementText: {
3915
+ fontWeight: 400,
3916
+ fontSize: "14px",
3917
+ lineHeight: "150%",
3918
+ letterSpacing: "0.2px",
3919
+ color: "#344054",
3920
+ margin: 0,
3921
+ padding: 0,
3922
+ textAlign: "left"
3923
+ },
3924
+ checkboxSmall: {
3925
+ height: "16px",
3926
+ width: "16px",
3927
+ padding: "4px",
3928
+ borderRadius: "5px",
3929
+ border: "2px solid #d0d5dd",
3930
+ margin: 0,
3931
+ boxSizing: "border-box",
3932
+ appearance: "none",
3933
+ WebkitAppearance: "none",
3934
+ MozAppearance: "none",
3935
+ cursor: "pointer",
3936
+ position: "relative",
3937
+ backgroundColor: "transparent"
3938
+ },
3939
+ loadingContainer: {
3940
+ display: "flex",
3941
+ flexDirection: "column",
3942
+ alignItems: "center",
3943
+ justifyContent: "center",
3944
+ padding: "2rem",
3945
+ minHeight: "200px",
3946
+ margin: 0,
3947
+ boxSizing: "border-box"
3948
+ },
3949
+ loadingSpinner: {
3950
+ width: "40px",
3951
+ height: "40px",
3952
+ border: "4px solid #f3f3f3",
3953
+ borderTop: "4px solid #3498db",
3954
+ borderRadius: "50%",
3955
+ animation: "spin 1s linear infinite",
3956
+ marginBottom: "1rem",
3957
+ boxSizing: "border-box"
3958
+ }
3959
+ };
3960
+
3961
+ // src/inline/purposes.ts
3962
+ function renderInlinePurposes(purposes, options) {
3963
+ var _a;
3964
+ const {
3965
+ selectedPurposes,
3966
+ collapsedPurposes,
3967
+ selectedDataElements,
3968
+ settings,
3969
+ isMobile,
3970
+ getTranslatedText,
3971
+ onPurposeToggle,
3972
+ onPurposeCollapse,
3973
+ onDataElementToggle
3974
+ } = options;
3975
+ const checkboxes = /* @__PURE__ */ new Map();
3976
+ const collapseButtons = /* @__PURE__ */ new Map();
3977
+ const dataElementContainers = /* @__PURE__ */ new Map();
3978
+ const chevrons = /* @__PURE__ */ new Map();
3979
+ const titleElements = /* @__PURE__ */ new Map();
3980
+ const descriptionElements = /* @__PURE__ */ new Map();
3981
+ const dataElementLabels = /* @__PURE__ */ new Map();
3982
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || "#101828";
3983
+ const textColor = (settings == null ? void 0 : settings.textColor) || "#344054";
3984
+ const container = el("div", {
3985
+ style: { display: "flex", flexDirection: "column", gap: "16px" }
3986
+ });
3987
+ for (const purpose of purposes) {
3988
+ const purposeWrapper = el("div");
3989
+ const chevron = svg(
3990
+ "svg",
3991
+ {
3992
+ width: "7",
3993
+ height: "12",
3994
+ viewBox: "0 0 7 12",
3995
+ fill: "none",
3996
+ xmlns: "http://www.w3.org/2000/svg",
3997
+ stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
3998
+ "stroke-width": "2",
3999
+ "stroke-linecap": "round",
4000
+ "stroke-linejoin": "round",
4001
+ style: {
4002
+ transform: !collapsedPurposes[purpose.uuid] ? "rotate(90deg)" : "rotate(0deg)",
4003
+ transition: "transform 0.3s ease"
4004
+ },
4005
+ "aria-hidden": "true"
4006
+ },
4007
+ svg("path", { d: "M1 1L6 6L1 11" })
4008
+ );
4009
+ chevrons.set(purpose.uuid, chevron);
4010
+ const chevronContainer = el(
4011
+ "div",
4012
+ {
4013
+ style: {
4014
+ display: "flex",
4015
+ alignItems: "center",
4016
+ justifyContent: "center",
4017
+ marginLeft: "5px"
4018
+ }
4019
+ },
4020
+ chevron
4021
+ );
4022
+ const titleEl = el("h2", {
4023
+ style: {
4024
+ ...inlineStyles.optionTitle,
4025
+ fontSize: isMobile ? "14px" : "16px",
4026
+ color: headingColor
4027
+ }
4028
+ });
4029
+ titleEl.textContent = getTranslatedText("purposes.name", purpose.name, purpose.uuid);
4030
+ titleElements.set(purpose.uuid, titleEl);
4031
+ const descEl = el("h3", {
4032
+ style: {
4033
+ ...inlineStyles.optionDescription,
4034
+ fontSize: isMobile ? "11px" : "12px",
4035
+ color: textColor
4036
+ }
4037
+ });
4038
+ descEl.textContent = getTranslatedText("purposes.description", purpose.description, purpose.uuid);
4039
+ descriptionElements.set(purpose.uuid, descEl);
4040
+ const textContainer = el("div", { style: inlineStyles.optionTextContainer }, titleEl, descEl);
4041
+ const collapseBtn = el("div", {
4042
+ style: inlineStyles.optionLeft,
4043
+ role: "button",
4044
+ tabIndex: "0",
4045
+ "aria-expanded": String(!collapsedPurposes[purpose.uuid]),
4046
+ "aria-controls": `purpose-${purpose.uuid}`,
4047
+ "aria-label": `${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)} details`,
4048
+ onClick: () => onPurposeCollapse(purpose.uuid),
4049
+ onKeydown: (e) => {
4050
+ if (e.key === "Enter" || e.key === " ") {
4051
+ e.preventDefault();
4052
+ onPurposeCollapse(purpose.uuid);
4053
+ }
4054
+ }
4055
+ }, chevronContainer, textContainer);
4056
+ collapseButtons.set(purpose.uuid, collapseBtn);
4057
+ const purposeCheckbox = el("input", {
4058
+ type: "checkbox",
4059
+ checked: selectedPurposes[purpose.uuid] || false,
4060
+ "aria-label": `Select all data elements for ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)}`,
4061
+ onChange: () => onPurposeToggle(purpose.uuid)
4062
+ });
4063
+ checkboxes.set(purpose.uuid, purposeCheckbox);
4064
+ const optionRow = el("div", { style: inlineStyles.optionItem }, collapseBtn, purposeCheckbox);
4065
+ const deContainer = el("div", {
4066
+ id: `purpose-${purpose.uuid}`,
4067
+ style: inlineStyles.dataElementsContainer
4068
+ });
4069
+ if (purpose.data_elements && purpose.data_elements.length > 0) {
4070
+ for (const dataElement of purpose.data_elements) {
4071
+ const combinedId = `${purpose.uuid}-${dataElement.uuid}`;
4072
+ const labelSpan = el("span", {
4073
+ style: {
4074
+ ...inlineStyles.dataElementText,
4075
+ fontSize: isMobile ? "12px" : "14px",
4076
+ color: textColor
4077
+ }
4078
+ });
4079
+ labelSpan.textContent = getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid);
4080
+ dataElementLabels.set(combinedId, labelSpan);
4081
+ if (dataElement.required) {
4082
+ const asterisk = el("span", {
4083
+ style: { color: "red", marginLeft: "4px" },
4084
+ "aria-label": "required"
4085
+ });
4086
+ asterisk.textContent = "*";
4087
+ labelSpan.appendChild(asterisk);
4088
+ }
4089
+ const deCheckbox = el("input", {
4090
+ type: "checkbox",
4091
+ checked: selectedDataElements[combinedId] || false,
4092
+ "aria-label": `Select ${getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid)}${dataElement.required ? " (required)" : ""}`,
4093
+ onChange: () => onDataElementToggle(dataElement.uuid, purpose.uuid)
4094
+ });
4095
+ checkboxes.set(combinedId, deCheckbox);
4096
+ const deRow = el("div", { style: inlineStyles.dataElementItem }, labelSpan, deCheckbox);
4097
+ deContainer.appendChild(deRow);
4098
+ }
4099
+ }
4100
+ dataElementContainers.set(purpose.uuid, deContainer);
4101
+ if (!collapsedPurposes[purpose.uuid] && ((_a = purpose.data_elements) == null ? void 0 : _a.length) > 0) {
4102
+ deContainer.style.display = "flex";
4103
+ } else {
4104
+ deContainer.style.display = "none";
4105
+ }
4106
+ purposeWrapper.appendChild(optionRow);
4107
+ purposeWrapper.appendChild(deContainer);
4108
+ container.appendChild(purposeWrapper);
4109
+ }
4110
+ return {
4111
+ container,
4112
+ checkboxes,
4113
+ collapseButtons,
4114
+ dataElementContainers,
4115
+ chevrons,
4116
+ titleElements,
4117
+ descriptionElements,
4118
+ dataElementLabels
4119
+ };
4120
+ }
4121
+
4122
+ // src/inline/index.ts
4123
+ var InlineController = class {
4124
+ constructor(options) {
4125
+ this.targetEl = null;
4126
+ this.rootEl = null;
4127
+ this.purposeRefs = null;
4128
+ this.errorEl = null;
4129
+ this.hasSubmitted = false;
4130
+ this.unsubscribe = null;
4131
+ this.options = options;
4132
+ this.store = new Store({
4133
+ content: null,
4134
+ isLoading: true,
4135
+ isSubmitting: false,
4136
+ selectedLanguage: options.language || "en",
4137
+ collapsedPurposes: {},
4138
+ selectedPurposes: {},
4139
+ selectedDataElements: {},
4140
+ hasAlreadyConsented: false,
4141
+ fetchError: null,
4142
+ errorMessage: null
4143
+ });
4144
+ this.mediaQuery = createMediaQuery("(max-width: 768px)");
4145
+ }
4146
+ async init() {
4147
+ injectCheckboxStyles();
4148
+ if (typeof this.options.target === "string") {
4149
+ this.targetEl = document.querySelector(this.options.target);
4150
+ if (!this.targetEl) {
4151
+ throw new Error(`Target element not found: ${this.options.target}`);
4152
+ }
4153
+ } else {
4154
+ this.targetEl = this.options.target;
4155
+ }
4156
+ this.unsubscribe = this.store.subscribe((state, prevState) => {
4157
+ this.handleStateChange(state, prevState);
4158
+ });
4159
+ this.mediaQuery.subscribe(() => this.rebuildUI());
4160
+ this.renderLoading();
4161
+ await this.fetchNotice();
4162
+ }
4163
+ destroy() {
4164
+ var _a;
4165
+ (_a = this.unsubscribe) == null ? void 0 : _a.call(this);
4166
+ this.mediaQuery.destroy();
4167
+ this.store.destroy();
4168
+ if (this.targetEl && this.rootEl) {
4169
+ this.targetEl.removeChild(this.rootEl);
4170
+ }
4171
+ this.rootEl = null;
4172
+ this.purposeRefs = null;
4173
+ }
4174
+ updateToken(accessToken) {
4175
+ this.options = { ...this.options, accessToken };
4176
+ this.tryAutoSubmit();
4177
+ }
4178
+ // ─── Private ───
4179
+ async fetchNotice() {
4180
+ var _a, _b, _c, _d;
4181
+ this.store.setState({ isLoading: true, fetchError: null });
4182
+ try {
4183
+ const data = await fetchConsentContentPublic({
4184
+ org_uuid: this.options.org_uuid,
4185
+ workspace_uuid: this.options.workspace_uuid,
4186
+ notice_uuid: this.options.notice_uuid,
4187
+ baseUrl: this.options.baseUrl,
4188
+ language: this.options.language,
4189
+ specific_uuid: this.options.applicationId
4190
+ });
4191
+ const activeConfig = data.detail.active_config;
4192
+ const initialCollapsed = {};
4193
+ const initialPurposes = {};
4194
+ const initialElements = {};
4195
+ for (const purpose of activeConfig.purposes) {
4196
+ initialCollapsed[purpose.uuid] = true;
4197
+ initialPurposes[purpose.uuid] = false;
4198
+ for (const de of purpose.data_elements) {
4199
+ initialElements[`${purpose.uuid}-${de.uuid}`] = false;
4200
+ }
4201
+ }
4202
+ this.store.setState({
4203
+ content: activeConfig,
4204
+ isLoading: false,
4205
+ selectedLanguage: activeConfig.default_language || this.options.language || "en",
4206
+ collapsedPurposes: initialCollapsed,
4207
+ selectedPurposes: initialPurposes,
4208
+ selectedDataElements: initialElements
4209
+ });
4210
+ } catch (err) {
4211
+ const error = err;
4212
+ if (error.status === 409) {
4213
+ this.store.setState({ hasAlreadyConsented: true, isLoading: false });
4214
+ (_b = (_a = this.options).onAccept) == null ? void 0 : _b.call(_a);
4215
+ } else {
4216
+ this.store.setState({ fetchError: error, isLoading: false });
4217
+ (_d = (_c = this.options).onError) == null ? void 0 : _d.call(_c, error);
4218
+ }
4219
+ }
4220
+ }
4221
+ handleStateChange(state, prevState) {
4222
+ if (prevState.isLoading && !state.isLoading && state.content) {
4223
+ this.rebuildUI();
4224
+ }
4225
+ if (state.hasAlreadyConsented || state.fetchError) {
4226
+ if (this.rootEl && this.targetEl) {
4227
+ this.targetEl.removeChild(this.rootEl);
4228
+ this.rootEl = null;
4229
+ }
4230
+ return;
4231
+ }
4232
+ if (state.selectedPurposes !== prevState.selectedPurposes || state.selectedDataElements !== prevState.selectedDataElements) {
4233
+ this.updateCheckboxes(state);
4234
+ this.notifyValidation(state);
4235
+ this.tryAutoSubmit();
4236
+ }
4237
+ if (state.collapsedPurposes !== prevState.collapsedPurposes) {
4238
+ this.updateCollapseState(state);
4239
+ }
4240
+ if (state.errorMessage !== prevState.errorMessage) {
4241
+ this.updateErrorMessage(state);
4242
+ }
4243
+ }
4244
+ renderLoading() {
4245
+ var _a;
4246
+ if (!this.targetEl) return;
4247
+ const textColor = ((_a = this.options.settings) == null ? void 0 : _a.textColor) || "#344054";
4248
+ this.rootEl = el(
4249
+ "div",
4250
+ { style: inlineStyles.loadingContainer },
4251
+ el("div", { style: inlineStyles.loadingSpinner }),
4252
+ el("p", { style: { fontSize: "16px", fontWeight: 400, color: textColor } }, "Loading...")
4253
+ );
4254
+ this.targetEl.appendChild(this.rootEl);
4255
+ }
4256
+ rebuildUI() {
4257
+ const state = this.store.getState();
4258
+ if (!state.content || !this.targetEl) return;
4259
+ if (this.rootEl && this.targetEl.contains(this.rootEl)) {
4260
+ this.targetEl.removeChild(this.rootEl);
4261
+ }
4262
+ this.errorEl = el("div", {
4263
+ role: "alert",
4264
+ style: {
4265
+ backgroundColor: "#FEE2E2",
4266
+ color: "#DC2626",
4267
+ padding: "12px",
4268
+ borderRadius: "6px",
4269
+ fontSize: "14px",
4270
+ border: "1px solid #FCA5A5",
4271
+ display: state.errorMessage ? "block" : "none"
4272
+ }
4273
+ });
4274
+ if (state.errorMessage) {
4275
+ this.errorEl.textContent = state.errorMessage;
4276
+ }
4277
+ this.purposeRefs = renderInlinePurposes(state.content.purposes, {
4278
+ selectedPurposes: state.selectedPurposes,
4279
+ collapsedPurposes: state.collapsedPurposes,
4280
+ selectedDataElements: state.selectedDataElements,
4281
+ settings: this.options.settings,
4282
+ isMobile: this.mediaQuery.matches,
4283
+ getTranslatedText: (key, defaultText, itemId) => this.getTranslatedText(key, defaultText, itemId),
4284
+ onPurposeToggle: (uuid) => this.handlePurposeCheckboxChange(uuid),
4285
+ onPurposeCollapse: (uuid) => this.togglePurposeCollapse(uuid),
4286
+ onDataElementToggle: (elementUuid, purposeUuid) => this.handleDataElementCheckboxChange(elementUuid, purposeUuid)
4287
+ });
4288
+ this.rootEl = el(
4289
+ "div",
4290
+ { style: { display: "flex", flexDirection: "column", gap: "16px" } },
4291
+ this.errorEl,
4292
+ this.purposeRefs.container
4293
+ );
4294
+ this.targetEl.appendChild(this.rootEl);
4295
+ this.notifyValidation(state);
4296
+ }
4297
+ getTranslatedText(key, defaultText, itemId) {
4298
+ var _a, _b;
4299
+ const state = this.store.getState();
4300
+ const content = state.content;
4301
+ if (!content) return defaultText;
4302
+ const lang = state.selectedLanguage;
4303
+ if (lang === "English" || lang === "en" || lang === "EN") {
4304
+ if (key === "privacy_policy_anchor_text" && content.privacy_policy_anchor_text) {
4305
+ return content.privacy_policy_anchor_text;
4306
+ }
4307
+ return defaultText;
4308
+ }
4309
+ const translations = content.supported_languages_and_translations;
4310
+ if (!translations) return defaultText;
4311
+ const translationMap = translations[lang];
4312
+ if (!translationMap) return defaultText;
4313
+ if (itemId) {
4314
+ if (key === "purposes.name" || key === "purposes.description") {
4315
+ const purposeData = (_a = translationMap.purposes) == null ? void 0 : _a[itemId];
4316
+ if (purposeData) {
4317
+ if (typeof purposeData === "string") {
4318
+ return key === "purposes.name" ? purposeData : defaultText;
4319
+ }
4320
+ return key === "purposes.name" ? purposeData.name || defaultText : purposeData.description || defaultText;
4321
+ }
4322
+ return defaultText;
4323
+ }
4324
+ if (key.startsWith("data_elements.")) {
4325
+ return ((_b = translationMap.data_elements) == null ? void 0 : _b[itemId]) || defaultText;
4326
+ }
4327
+ }
4328
+ const value = translationMap[key];
4329
+ if (typeof value === "string" && value) return value;
4330
+ return defaultText;
4331
+ }
4332
+ togglePurposeCollapse(purposeUuid) {
4333
+ const state = this.store.getState();
4334
+ this.store.setState({
4335
+ collapsedPurposes: {
4336
+ ...state.collapsedPurposes,
4337
+ [purposeUuid]: !state.collapsedPurposes[purposeUuid]
4338
+ }
4339
+ });
4340
+ }
4341
+ handlePurposeCheckboxChange(purposeUuid) {
4342
+ const state = this.store.getState();
4343
+ const content = state.content;
4344
+ if (!content) return;
4345
+ const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
4346
+ if (!purpose) return;
4347
+ const newPurposeState = !state.selectedPurposes[purposeUuid];
4348
+ const updatedElements = { ...state.selectedDataElements };
4349
+ for (const de of purpose.data_elements) {
4350
+ updatedElements[`${purposeUuid}-${de.uuid}`] = newPurposeState;
4351
+ }
4352
+ this.store.setState({
4353
+ selectedPurposes: { ...state.selectedPurposes, [purposeUuid]: newPurposeState },
4354
+ selectedDataElements: updatedElements
4355
+ });
4356
+ }
4357
+ handleDataElementCheckboxChange(elementUuid, purposeUuid) {
4358
+ const state = this.store.getState();
4359
+ const content = state.content;
4360
+ if (!content) return;
4361
+ const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
4362
+ if (!purpose) return;
4363
+ const combinedId = `${purposeUuid}-${elementUuid}`;
4364
+ const newElementState = !state.selectedDataElements[combinedId];
4365
+ const newDataElements = {
4366
+ ...state.selectedDataElements,
4367
+ [combinedId]: newElementState
4368
+ };
4369
+ const requiredElements = purpose.data_elements.filter((el2) => el2.required);
4370
+ let shouldCheckPurpose;
4371
+ if (requiredElements.length > 0) {
4372
+ shouldCheckPurpose = requiredElements.every(
4373
+ (el2) => newDataElements[`${purposeUuid}-${el2.uuid}`]
4374
+ );
4375
+ } else {
4376
+ shouldCheckPurpose = purpose.data_elements.some(
4377
+ (el2) => newDataElements[`${purposeUuid}-${el2.uuid}`]
4378
+ );
4379
+ }
4380
+ this.store.setState({
4381
+ selectedDataElements: newDataElements,
4382
+ selectedPurposes: { ...state.selectedPurposes, [purposeUuid]: shouldCheckPurpose }
4383
+ });
4384
+ }
4385
+ updateCheckboxes(state) {
4386
+ if (!this.purposeRefs) return;
4387
+ for (const [key, checkbox] of this.purposeRefs.checkboxes) {
4388
+ if (key in state.selectedPurposes) {
4389
+ checkbox.checked = state.selectedPurposes[key] || false;
4390
+ } else {
4391
+ checkbox.checked = state.selectedDataElements[key] || false;
4392
+ }
4393
+ }
4394
+ }
4395
+ updateCollapseState(state) {
4396
+ var _a;
4397
+ if (!this.purposeRefs) return;
4398
+ for (const [purposeUuid, container] of this.purposeRefs.dataElementContainers) {
4399
+ const content = this.store.getState().content;
4400
+ const purpose = content == null ? void 0 : content.purposes.find((p) => p.uuid === purposeUuid);
4401
+ const isCollapsed = state.collapsedPurposes[purposeUuid];
4402
+ container.style.display = !isCollapsed && ((_a = purpose == null ? void 0 : purpose.data_elements) == null ? void 0 : _a.length) ? "flex" : "none";
4403
+ const chevron = this.purposeRefs.chevrons.get(purposeUuid);
4404
+ if (chevron) {
4405
+ chevron.style.transform = !isCollapsed ? "rotate(90deg)" : "rotate(0deg)";
4406
+ }
4407
+ const collapseBtn = this.purposeRefs.collapseButtons.get(purposeUuid);
4408
+ if (collapseBtn) {
4409
+ collapseBtn.setAttribute("aria-expanded", String(!isCollapsed));
4410
+ }
4411
+ }
4412
+ }
4413
+ updateErrorMessage(state) {
4414
+ if (!this.errorEl) return;
4415
+ if (state.errorMessage) {
4416
+ this.errorEl.textContent = state.errorMessage;
4417
+ this.errorEl.style.display = "block";
4418
+ } else {
4419
+ this.errorEl.style.display = "none";
4420
+ }
4421
+ }
4422
+ areAllRequiredElementsChecked() {
4423
+ const state = this.store.getState();
4424
+ const content = state.content;
4425
+ if (!content) return false;
4426
+ return content.purposes.every((purpose) => {
4427
+ const requiredElements = purpose.data_elements.filter((el2) => el2.required);
4428
+ return requiredElements.every(
4429
+ (el2) => state.selectedDataElements[`${purpose.uuid}-${el2.uuid}`]
4430
+ );
4431
+ });
4432
+ }
4433
+ notifyValidation(state) {
4434
+ var _a, _b;
4435
+ const isValid = this.areAllRequiredElementsChecked();
4436
+ (_b = (_a = this.options).onValidationChange) == null ? void 0 : _b.call(_a, isValid);
4437
+ }
4438
+ async tryAutoSubmit() {
4439
+ var _a, _b;
4440
+ const state = this.store.getState();
4441
+ const accessToken = this.options.accessToken;
4442
+ if (accessToken && state.content && this.areAllRequiredElementsChecked() && !state.isSubmitting && !this.hasSubmitted) {
4443
+ this.hasSubmitted = true;
4444
+ try {
4445
+ await this.handleSubmit(accessToken);
4446
+ } catch (err) {
4447
+ this.hasSubmitted = false;
4448
+ (_b = (_a = this.options).onError) == null ? void 0 : _b.call(_a, err);
4449
+ }
4450
+ }
4451
+ }
4452
+ async handleSubmit(tokenToUse) {
4453
+ var _a, _b;
4454
+ const state = this.store.getState();
4455
+ const content = state.content;
4456
+ if (!tokenToUse || !content) {
4457
+ this.store.setState({ errorMessage: "Unable to submit consent. Please try again." });
4458
+ throw new Error("Access token or content not available");
4459
+ }
4460
+ try {
4461
+ this.store.setState({ isSubmitting: true, errorMessage: null });
4462
+ await submitConsentEventPublic({
4463
+ org_uuid: this.options.org_uuid,
4464
+ workspace_uuid: this.options.workspace_uuid,
4465
+ accessToken: tokenToUse,
4466
+ baseUrl: this.options.baseUrl,
4467
+ noticeUuid: content.notice_uuid,
4468
+ purposes: content.purposes.map((purpose) => {
4469
+ const hasSelectedRequired = purpose.data_elements.some(
4470
+ (el2) => el2.required && (state.selectedDataElements[`${purpose.uuid}-${el2.uuid}`] || false)
4471
+ );
4472
+ const hasSelectedAny = purpose.data_elements.some(
4473
+ (el2) => state.selectedDataElements[`${purpose.uuid}-${el2.uuid}`] || false
4474
+ );
4475
+ const purposeSelected = state.selectedPurposes[purpose.uuid] || hasSelectedRequired || hasSelectedAny;
4476
+ return {
4477
+ ...purpose,
4478
+ selected: purposeSelected,
4479
+ data_elements: purpose.data_elements.map((element) => ({
4480
+ ...element,
4481
+ selected: state.selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false
4482
+ }))
4483
+ };
4484
+ }),
4485
+ declined: false,
4486
+ meta_data: this.options.applicationId ? { specific_uuid: this.options.applicationId } : void 0
4487
+ });
4488
+ (_b = (_a = this.options).onAccept) == null ? void 0 : _b.call(_a);
4489
+ this.hasSubmitted = false;
4490
+ } catch (error) {
4491
+ const err = error;
4492
+ const message = err.status === 500 ? "An error occurred while submitting your consent. Please try again later." : err.message || "Failed to submit consent. Please try again.";
4493
+ this.store.setState({ errorMessage: message });
4494
+ this.hasSubmitted = false;
4495
+ throw error;
4496
+ } finally {
4497
+ this.store.setState({ isSubmitting: false });
4498
+ }
4499
+ }
4500
+ };
4501
+
4502
+ // src/modal/digilocker-callback.ts
4503
+ function getErrorMessage(errorCode) {
4504
+ switch (errorCode) {
4505
+ case "GUARDIAN_UNDER_18":
4506
+ return "Guardian verification failed: The guardian must be 18 years or older.";
4507
+ case "SESSION_EXPIRED":
4508
+ return "Verification session has expired. Please restart the verification process.";
4509
+ case "TOKEN_FAILED":
4510
+ return "DigiLocker authentication failed. Please try again.";
4511
+ case "DIGILOCKER_AUTH_FAILED":
4512
+ return "DigiLocker authorization was cancelled or failed. Please try again.";
4513
+ case "NO_AUTH_CODE":
4514
+ return "Missing authorization code from DigiLocker. Please try again.";
4515
+ default:
4516
+ return "Verification failed. Please try again.";
4517
+ }
4518
+ }
4519
+ function handleDigilockerCallback(options) {
4520
+ var _a, _b, _c;
4521
+ injectCheckboxStyles();
4522
+ const params = new URLSearchParams(window.location.search);
4523
+ const sessionToken = params.get("session_token");
4524
+ const urlStatus = params.get("status");
4525
+ const verificationReference = params.get("verification_reference");
4526
+ const errorCode = params.get("error_code");
4527
+ const persistedState = getConsentState();
4528
+ const originalUrl = (persistedState == null ? void 0 : persistedState.originalUrl) || "/";
4529
+ const storedBaseUrl = persistedState == null ? void 0 : persistedState.baseUrl;
4530
+ const storedSessionToken = persistedState == null ? void 0 : persistedState.sessionToken;
4531
+ const activeSessionToken = sessionToken || storedSessionToken;
4532
+ const apiBaseUrl = options.baseUrl || storedBaseUrl;
4533
+ let isInIframe = false;
4534
+ try {
4535
+ isInIframe = window.self !== window.top;
4536
+ } catch {
4537
+ isInIframe = true;
4538
+ }
4539
+ const container = el("div", {
4540
+ style: {
4541
+ display: "flex",
4542
+ flexDirection: "column",
4543
+ alignItems: "center",
4544
+ justifyContent: "center",
4545
+ minHeight: "200px",
4546
+ padding: "32px",
4547
+ fontFamily: "system-ui, -apple-system, sans-serif"
4548
+ }
4549
+ });
4550
+ const statusEl = el("p", {
4551
+ style: { fontSize: "16px", fontWeight: 600, color: "#101828", textAlign: "center" }
4552
+ });
4553
+ statusEl.textContent = "Processing verification...";
4554
+ const spinnerEl = el("div", {
4555
+ style: {
4556
+ width: "40px",
4557
+ height: "40px",
4558
+ border: "4px solid #f3f3f3",
4559
+ borderTop: "4px solid #3498db",
4560
+ borderRadius: "50%",
4561
+ animation: "spin 1s linear infinite",
4562
+ marginBottom: "16px"
4563
+ }
4564
+ });
4565
+ container.appendChild(spinnerEl);
4566
+ container.appendChild(statusEl);
4567
+ document.body.appendChild(container);
4568
+ const redirectToOriginal = (result, ref) => {
4569
+ updateConsentState({
4570
+ verificationResult: result,
4571
+ verificationReference: ref,
4572
+ flowState: result === "verified" ? "consent-form" : "error"
4573
+ });
4574
+ statusEl.textContent = result === "verified" ? "Verification successful! Redirecting..." : "Verification failed. Redirecting...";
4575
+ spinnerEl.style.display = "none";
4576
+ setTimeout(() => {
4577
+ window.location.href = originalUrl;
4578
+ }, 1500);
4579
+ };
4580
+ if (urlStatus === "verified" && verificationReference) {
4581
+ if (isInIframe && window.parent) {
4582
+ window.parent.postMessage({
4583
+ type: "DIGILOCKER_VERIFICATION_STATUS",
4584
+ status: "verified",
4585
+ verificationReference
4586
+ }, window.location.origin);
4587
+ statusEl.textContent = "Verification successful!";
4588
+ spinnerEl.style.display = "none";
4589
+ return;
4590
+ }
4591
+ (_a = options.onSuccess) == null ? void 0 : _a.call(options, verificationReference);
4592
+ redirectToOriginal("verified", verificationReference);
4593
+ return;
4594
+ }
4595
+ if (urlStatus === "failed" || urlStatus === "expired") {
4596
+ const errorMsg = getErrorMessage(errorCode);
4597
+ if (isInIframe && window.parent) {
4598
+ window.parent.postMessage({
4599
+ type: "DIGILOCKER_VERIFICATION_STATUS",
4600
+ status: urlStatus,
4601
+ errorCode,
4602
+ error: errorMsg
4603
+ }, window.location.origin);
4604
+ statusEl.textContent = errorMsg;
4605
+ statusEl.style.color = "#DC2626";
4606
+ spinnerEl.style.display = "none";
4607
+ return;
4608
+ }
4609
+ (_b = options.onError) == null ? void 0 : _b.call(options, new Error(errorMsg), errorCode || void 0);
4610
+ redirectToOriginal(urlStatus);
4611
+ return;
4612
+ }
4613
+ if (!activeSessionToken) {
4614
+ statusEl.textContent = "No verification session found.";
4615
+ statusEl.style.color = "#DC2626";
4616
+ spinnerEl.style.display = "none";
4617
+ (_c = options.onError) == null ? void 0 : _c.call(options, new Error("No session token available"));
4618
+ return;
4619
+ }
4620
+ let retryCount = 0;
4621
+ const MAX_RETRIES = 3;
4622
+ const pollStatus = async () => {
4623
+ var _a2, _b2, _c2, _d, _e;
4624
+ try {
4625
+ const response = await verifyGuardianStatus({
4626
+ accessToken: options.accessToken,
4627
+ baseUrl: apiBaseUrl,
4628
+ sessionToken: activeSessionToken
4629
+ });
4630
+ if (response.status === "verified") {
4631
+ const ref = response.verification_reference || ((_a2 = response.guardian_details) == null ? void 0 : _a2.verification_reference);
4632
+ if (isInIframe && window.parent) {
4633
+ window.parent.postMessage({
4634
+ type: "DIGILOCKER_VERIFICATION_STATUS",
4635
+ status: "verified",
4636
+ verificationReference: ref
4637
+ }, window.location.origin);
4638
+ statusEl.textContent = "Verification successful!";
4639
+ spinnerEl.style.display = "none";
4640
+ return;
4641
+ }
4642
+ if (ref) (_b2 = options.onSuccess) == null ? void 0 : _b2.call(options, ref);
4643
+ redirectToOriginal("verified", ref || void 0);
4644
+ return;
4645
+ }
4646
+ if (response.status === "failed" || response.status === "expired") {
4647
+ const errorMsg = getVerificationError(response.error_code, response.error);
4648
+ if (isInIframe && window.parent) {
4649
+ window.parent.postMessage({
4650
+ type: "DIGILOCKER_VERIFICATION_STATUS",
4651
+ status: response.status,
4652
+ errorCode: response.error_code,
4653
+ error: errorMsg
4654
+ }, window.location.origin);
4655
+ statusEl.textContent = errorMsg;
4656
+ statusEl.style.color = "#DC2626";
4657
+ spinnerEl.style.display = "none";
4658
+ return;
4659
+ }
4660
+ (_c2 = options.onError) == null ? void 0 : _c2.call(options, new Error(errorMsg), response.error_code || void 0);
4661
+ redirectToOriginal(response.status);
4662
+ return;
4663
+ }
4664
+ retryCount++;
4665
+ if (retryCount > MAX_RETRIES) {
4666
+ const timeoutMsg = "Verification is taking too long. Please return to the original page.";
4667
+ statusEl.textContent = timeoutMsg;
4668
+ statusEl.style.color = "#DC2626";
4669
+ spinnerEl.style.display = "none";
4670
+ (_d = options.onError) == null ? void 0 : _d.call(options, new Error(timeoutMsg));
4671
+ redirectToOriginal("failed");
4672
+ return;
4673
+ }
4674
+ const delay = 3e3 * Math.pow(2, retryCount - 1);
4675
+ statusEl.textContent = "Still processing... Please wait.";
4676
+ setTimeout(pollStatus, delay);
4677
+ } catch (error) {
4678
+ retryCount++;
4679
+ if (retryCount > MAX_RETRIES) {
4680
+ const errMsg = error.message || "Failed to check verification status.";
4681
+ statusEl.textContent = errMsg;
4682
+ statusEl.style.color = "#DC2626";
4683
+ spinnerEl.style.display = "none";
4684
+ (_e = options.onError) == null ? void 0 : _e.call(options, error);
4685
+ redirectToOriginal("failed");
4686
+ return;
4687
+ }
4688
+ const delay = 3e3 * Math.pow(2, retryCount - 1);
4689
+ setTimeout(pollStatus, delay);
4690
+ }
4691
+ };
4692
+ setTimeout(pollStatus, 3e3);
4693
+ }
4694
+ function getVerificationError(errorCode, fallbackError) {
4695
+ return getErrorMessage(errorCode) || fallbackError || "Verification failed. Please try again.";
4696
+ }
4697
+
4698
+ // src/index.ts
4699
+ var activeModal = null;
4700
+ var activeInline = null;
4701
+ function init(options) {
4702
+ destroy();
4703
+ activeModal = new ModalController(options);
4704
+ activeModal.init().catch((err) => {
4705
+ var _a;
4706
+ (_a = options.onError) == null ? void 0 : _a.call(options, err);
4707
+ });
4708
+ }
4709
+ function inline(options) {
4710
+ if (activeInline) {
4711
+ activeInline.destroy();
4712
+ activeInline = null;
4713
+ }
4714
+ activeInline = new InlineController(options);
4715
+ activeInline.init().catch((err) => {
4716
+ var _a;
4717
+ (_a = options.onError) == null ? void 0 : _a.call(options, err);
4718
+ });
4719
+ }
4720
+ function destroy() {
4721
+ if (activeModal) {
4722
+ activeModal.destroy();
4723
+ activeModal = null;
4724
+ }
4725
+ if (activeInline) {
4726
+ activeInline.destroy();
4727
+ activeInline = null;
4728
+ }
4729
+ }
4730
+ function digilockerCallback(options) {
4731
+ handleDigilockerCallback(options);
4732
+ }
4733
+ var RedactoConsent = {
4734
+ init,
4735
+ inline,
4736
+ destroy,
4737
+ digilockerCallback,
4738
+ version: "0.1.0"
4739
+ };
4740
+ var src_default = RedactoConsent;
4741
+ export {
4742
+ src_default as default,
4743
+ destroy,
4744
+ digilockerCallback,
4745
+ init,
4746
+ inline
4747
+ };