@redacto.io/consent-sdk-react-native 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,2428 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/RedactoNoticeConsent/RedactoNoticeConsent.tsx
9
+ import {
10
+ useCallback,
11
+ useEffect,
12
+ useMemo,
13
+ useRef,
14
+ useState
15
+ } from "react";
16
+ import {
17
+ ActivityIndicator,
18
+ Image,
19
+ Linking,
20
+ Modal,
21
+ Pressable,
22
+ ScrollView,
23
+ StyleSheet as StyleSheet3,
24
+ Text,
25
+ TextInput,
26
+ TouchableOpacity as TouchableOpacity2,
27
+ View as View2
28
+ } from "react-native";
29
+ import Svg2, { Path as Path2, Rect } from "react-native-svg";
30
+
31
+ // src/RedactoNoticeConsent/api/index.ts
32
+ import { jwtDecode } from "jwt-decode";
33
+
34
+ // src/constants.ts
35
+ var DEFAULT_CONSENT_API_BASE_URL = "https://api.redacto.io/consent";
36
+
37
+ // src/RedactoNoticeConsent/api/index.ts
38
+ var apiCache = /* @__PURE__ */ new Map();
39
+ var CACHE_TTL = 5 * 60 * 1e3;
40
+ function getCachedData(key) {
41
+ const cached = apiCache.get(key);
42
+ if (cached && Date.now() - cached.timestamp < cached.ttl) {
43
+ return cached.data;
44
+ }
45
+ if (cached) {
46
+ apiCache.delete(key);
47
+ }
48
+ return null;
49
+ }
50
+ function setCachedData(key, data, ttl = CACHE_TTL) {
51
+ apiCache.set(key, { data, timestamp: Date.now(), ttl });
52
+ }
53
+ function decodeTokenSafely(token) {
54
+ try {
55
+ return jwtDecode(token);
56
+ } catch (error) {
57
+ console.warn("Failed to decode JWT token:", error);
58
+ return null;
59
+ }
60
+ }
61
+ var clearApiCache = () => {
62
+ apiCache.clear();
63
+ };
64
+ var fetchConsentContent = async ({
65
+ noticeId,
66
+ accessToken,
67
+ baseUrl,
68
+ language = "en",
69
+ specific_uuid,
70
+ validate_against = "all",
71
+ include_fully_consented_data = false,
72
+ signal
73
+ }) => {
74
+ if (!noticeId || !accessToken || !validate_against) {
75
+ throw new Error("noticeId and accessToken are required");
76
+ }
77
+ const decodedToken = decodeTokenSafely(accessToken);
78
+ if (!decodedToken) {
79
+ throw new Error("Invalid access token");
80
+ }
81
+ const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
82
+ if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
83
+ throw new Error("Invalid token: missing organization or workspace UUID");
84
+ }
85
+ const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
86
+ const cacheKey = `${accessToken}-${noticeId}-${validate_against}-${language}-${specific_uuid || ""}-${include_fully_consented_data}`;
87
+ if (validate_against === "all" && !specific_uuid) {
88
+ const cachedData = getCachedData(cacheKey);
89
+ if (cachedData) {
90
+ return cachedData;
91
+ }
92
+ }
93
+ const url = new URL(
94
+ `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/notices/${noticeId}`
95
+ );
96
+ if (specific_uuid) {
97
+ url.searchParams.append("specific_uuid", specific_uuid);
98
+ }
99
+ url.searchParams.append("validate_against", validate_against);
100
+ if (include_fully_consented_data) {
101
+ url.searchParams.append("include_fully_consented_data", "true");
102
+ }
103
+ const response = await fetch(url.toString(), {
104
+ method: "GET",
105
+ headers: {
106
+ Authorization: `Bearer ${accessToken}`,
107
+ "Accept-Language": language,
108
+ "Content-Type": "application/json"
109
+ },
110
+ signal
111
+ });
112
+ if (!response.ok) {
113
+ let errorMessage = `Failed to fetch consent content: ${response.status} ${response.statusText}`;
114
+ if (response.status === 401) {
115
+ errorMessage = "Unauthorized: Invalid or expired token";
116
+ } else if (response.status === 403) {
117
+ errorMessage = "Forbidden: Access denied";
118
+ } else if (response.status === 404) {
119
+ errorMessage = "Notice not found";
120
+ } else if (response.status === 409) {
121
+ errorMessage = "User has already provided consent";
122
+ } else if (response.status >= 500) {
123
+ errorMessage = "Server error: Please try again later";
124
+ }
125
+ const error = new Error(errorMessage);
126
+ error.status = response.status;
127
+ throw error;
128
+ }
129
+ const data = await response.json();
130
+ if (validate_against === "all" && !specific_uuid) {
131
+ setCachedData(cacheKey, data);
132
+ }
133
+ return data;
134
+ };
135
+ var submitConsentEvent = async ({
136
+ accessToken,
137
+ baseUrl,
138
+ noticeUuid,
139
+ purposes,
140
+ declined,
141
+ meta_data,
142
+ signal
143
+ }) => {
144
+ if (!noticeUuid || !accessToken || !Array.isArray(purposes)) {
145
+ throw new Error("noticeUuid, accessToken, and purposes array are required");
146
+ }
147
+ const decodedToken = decodeTokenSafely(accessToken);
148
+ if (!decodedToken) {
149
+ throw new Error("Invalid access token");
150
+ }
151
+ const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
152
+ if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
153
+ throw new Error("Invalid token: missing organization or workspace UUID");
154
+ }
155
+ const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
156
+ const validatedPurposes = purposes.map((purpose) => {
157
+ if (!purpose.uuid) {
158
+ throw new Error("Invalid purpose data: missing uuid");
159
+ }
160
+ const dataElements = (purpose.data_elements || []).filter((element) => element.enabled).map((element) => ({
161
+ uuid: element.uuid,
162
+ selected: element.required ? true : Boolean(element.selected)
163
+ }));
164
+ return {
165
+ purpose_uuid: purpose.uuid,
166
+ selected: Boolean(purpose.selected),
167
+ data_elements: dataElements.length > 0 ? dataElements : void 0
168
+ };
169
+ });
170
+ const payload = {
171
+ notice_uuid: noticeUuid,
172
+ purposes: validatedPurposes,
173
+ select_all_mandatory: false,
174
+ source: "MOBILE",
175
+ declined: Boolean(declined),
176
+ meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid } : void 0
177
+ };
178
+ const response = await fetch(
179
+ `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/submit-consent`,
180
+ {
181
+ method: "POST",
182
+ headers: {
183
+ Authorization: `Bearer ${accessToken}`,
184
+ "Content-Type": "application/json"
185
+ },
186
+ body: JSON.stringify(payload),
187
+ signal
188
+ }
189
+ );
190
+ if (!response.ok) {
191
+ let errorMessage = `Failed to submit consent: ${response.status} ${response.statusText}`;
192
+ if (response.status === 400) {
193
+ errorMessage = "Invalid request data";
194
+ } else if (response.status === 401) {
195
+ errorMessage = "Unauthorized: Invalid or expired token";
196
+ } else if (response.status === 403) {
197
+ errorMessage = "Forbidden: Access denied";
198
+ } else if (response.status === 404) {
199
+ errorMessage = "Notice not found";
200
+ } else if (response.status >= 500) {
201
+ errorMessage = "Server error: Please try again later";
202
+ }
203
+ const error = new Error(errorMessage);
204
+ error.status = response.status;
205
+ throw error;
206
+ }
207
+ apiCache.clear();
208
+ };
209
+ var submitGuardianInfo = async ({
210
+ accessToken,
211
+ baseUrl,
212
+ guardianName,
213
+ guardianContact,
214
+ guardianRelationship,
215
+ signal
216
+ }) => {
217
+ if (!accessToken) {
218
+ throw new Error("accessToken is required");
219
+ }
220
+ if (!(guardianName == null ? void 0 : guardianName.trim())) {
221
+ throw new Error("guardianName is required and cannot be empty");
222
+ }
223
+ if (!(guardianContact == null ? void 0 : guardianContact.trim())) {
224
+ throw new Error("guardianContact is required and cannot be empty");
225
+ }
226
+ if (!(guardianRelationship == null ? void 0 : guardianRelationship.trim())) {
227
+ throw new Error("guardianRelationship is required and cannot be empty");
228
+ }
229
+ const decodedToken = decodeTokenSafely(accessToken);
230
+ if (!decodedToken) {
231
+ throw new Error("Invalid access token");
232
+ }
233
+ const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
234
+ if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
235
+ throw new Error("Invalid token: missing organization or workspace UUID");
236
+ }
237
+ const payload = {
238
+ guardian_name: guardianName.trim(),
239
+ guardian_contact: guardianContact.trim(),
240
+ guardian_relationship: guardianRelationship.trim()
241
+ };
242
+ const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
243
+ const response = await fetch(
244
+ `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/guardian-info`,
245
+ {
246
+ method: "POST",
247
+ headers: {
248
+ Authorization: `Bearer ${accessToken}`,
249
+ "Content-Type": "application/json"
250
+ },
251
+ body: JSON.stringify(payload),
252
+ signal
253
+ }
254
+ );
255
+ if (!response.ok) {
256
+ let errorMessage = `Failed to submit guardian info: ${response.status} ${response.statusText}`;
257
+ if (response.status === 400) {
258
+ errorMessage = "Invalid request data";
259
+ } else if (response.status === 401) {
260
+ errorMessage = "Unauthorized: Invalid or expired token";
261
+ } else if (response.status === 403) {
262
+ errorMessage = "Forbidden: Access denied";
263
+ } else if (response.status === 404) {
264
+ errorMessage = "Organization or workspace not found";
265
+ } else if (response.status === 422) {
266
+ try {
267
+ const errorData = await response.json();
268
+ errorMessage = errorData.message || "Validation error";
269
+ } catch {
270
+ errorMessage = "Validation error: Please check your input";
271
+ }
272
+ } else if (response.status >= 500) {
273
+ errorMessage = "Server error: Please try again later";
274
+ }
275
+ const error = new Error(errorMessage);
276
+ error.status = response.status;
277
+ throw error;
278
+ }
279
+ };
280
+ var fetchTTSAudioUrls = async ({
281
+ accessToken,
282
+ baseUrl,
283
+ noticeUuid,
284
+ language,
285
+ signal
286
+ }) => {
287
+ if (!accessToken || !noticeUuid || !language) {
288
+ throw new Error("accessToken, noticeUuid, and language are required");
289
+ }
290
+ const decodedToken = decodeTokenSafely(accessToken);
291
+ if (!decodedToken) {
292
+ throw new Error("Invalid access token");
293
+ }
294
+ const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
295
+ if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
296
+ throw new Error("Invalid token: missing organization or workspace UUID");
297
+ }
298
+ const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
299
+ const url = `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/notices/${noticeUuid}/audio/${language}`;
300
+ const response = await fetch(url, {
301
+ method: "GET",
302
+ headers: {
303
+ Authorization: `Bearer ${accessToken}`,
304
+ Accept: "application/json",
305
+ "Content-Type": "application/json"
306
+ },
307
+ signal
308
+ });
309
+ if (!response.ok) {
310
+ let errorMessage = `Failed to fetch TTS audio URLs: ${response.status} ${response.statusText}`;
311
+ if (response.status === 401) {
312
+ errorMessage = "Unauthorized: Invalid or expired token";
313
+ } else if (response.status === 403) {
314
+ errorMessage = "Forbidden: Access denied";
315
+ } else if (response.status === 404) {
316
+ errorMessage = "Notice or audio not found";
317
+ } else if (response.status >= 500) {
318
+ errorMessage = "Server error: Please try again later";
319
+ }
320
+ const error = new Error(errorMessage);
321
+ error.status = response.status;
322
+ throw error;
323
+ }
324
+ return response.json();
325
+ };
326
+
327
+ // src/RedactoNoticeConsent/components/Checkbox.tsx
328
+ import { TouchableOpacity, View, StyleSheet } from "react-native";
329
+ import Svg, { Path } from "react-native-svg";
330
+ import { jsx } from "react/jsx-runtime";
331
+ var Checkbox = ({
332
+ checked,
333
+ onChange,
334
+ size = "large",
335
+ disabled = false,
336
+ accessibilityLabel
337
+ }) => {
338
+ const isLarge = size === "large";
339
+ const boxSize = isLarge ? 20 : 16;
340
+ const iconSize = isLarge ? 10 : 8;
341
+ return /* @__PURE__ */ jsx(
342
+ TouchableOpacity,
343
+ {
344
+ onPress: onChange,
345
+ disabled,
346
+ accessibilityRole: "checkbox",
347
+ accessibilityState: { checked, disabled },
348
+ accessibilityLabel,
349
+ hitSlop: { top: 6, bottom: 6, left: 6, right: 6 },
350
+ children: /* @__PURE__ */ jsx(
351
+ View,
352
+ {
353
+ style: [
354
+ styles.box,
355
+ { width: boxSize, height: boxSize, borderRadius: 5 },
356
+ checked ? styles.checked : styles.unchecked,
357
+ disabled && styles.disabled
358
+ ],
359
+ children: checked && /* @__PURE__ */ jsx(
360
+ Svg,
361
+ {
362
+ width: iconSize,
363
+ height: iconSize,
364
+ viewBox: "0 0 10 10",
365
+ fill: "none",
366
+ children: /* @__PURE__ */ jsx(
367
+ Path,
368
+ {
369
+ d: "M1.5 5L4 7.5L8.5 2.5",
370
+ stroke: "white",
371
+ strokeWidth: 1.8,
372
+ strokeLinecap: "round",
373
+ strokeLinejoin: "round"
374
+ }
375
+ )
376
+ }
377
+ )
378
+ }
379
+ )
380
+ }
381
+ );
382
+ };
383
+ var styles = StyleSheet.create({
384
+ box: {
385
+ alignItems: "center",
386
+ justifyContent: "center",
387
+ borderWidth: 2
388
+ },
389
+ checked: {
390
+ backgroundColor: "#4f87ff",
391
+ borderColor: "#4f87ff"
392
+ },
393
+ unchecked: {
394
+ backgroundColor: "transparent",
395
+ borderColor: "#d0d5dd"
396
+ },
397
+ disabled: {
398
+ opacity: 0.5
399
+ }
400
+ });
401
+
402
+ // src/RedactoNoticeConsent/styles.ts
403
+ import { StyleSheet as StyleSheet2, Platform } from "react-native";
404
+ var styles2 = StyleSheet2.create({
405
+ overlay: {
406
+ flex: 1,
407
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
408
+ alignItems: "center",
409
+ justifyContent: "center",
410
+ padding: 16
411
+ },
412
+ modal: {
413
+ width: "100%",
414
+ maxWidth: 700,
415
+ height: "90%",
416
+ borderRadius: 8,
417
+ backgroundColor: "#ffffff",
418
+ ...Platform.select({
419
+ ios: {
420
+ shadowColor: "#000",
421
+ shadowOffset: { width: 0, height: 2 },
422
+ shadowOpacity: 0.25,
423
+ shadowRadius: 4
424
+ },
425
+ android: {
426
+ elevation: 5
427
+ }
428
+ }),
429
+ maxHeight: "90%",
430
+ minHeight: 320
431
+ },
432
+ content: {
433
+ padding: 22,
434
+ flex: 1,
435
+ minHeight: 0
436
+ },
437
+ topSection: {
438
+ flexDirection: "row",
439
+ justifyContent: "space-between",
440
+ alignItems: "center",
441
+ paddingBottom: 15,
442
+ backgroundColor: "#ffffff",
443
+ flexShrink: 0
444
+ },
445
+ topLeft: {
446
+ flexDirection: "row",
447
+ alignItems: "center",
448
+ gap: 10,
449
+ flex: 1
450
+ },
451
+ logo: {
452
+ height: 32,
453
+ width: 80,
454
+ resizeMode: "contain"
455
+ },
456
+ title: {
457
+ fontSize: 18,
458
+ fontWeight: "700",
459
+ lineHeight: 27,
460
+ letterSpacing: 0.2,
461
+ color: "#101828",
462
+ flexShrink: 1
463
+ },
464
+ topRight: {
465
+ borderRadius: 8,
466
+ paddingVertical: 3,
467
+ paddingHorizontal: 9,
468
+ borderWidth: 1,
469
+ borderColor: "#d0d5dd",
470
+ flexDirection: "row",
471
+ alignItems: "center",
472
+ justifyContent: "center",
473
+ backgroundColor: "#ffffff",
474
+ gap: 5
475
+ },
476
+ topRightText: {
477
+ fontWeight: "400",
478
+ fontSize: 12,
479
+ color: "#344054"
480
+ },
481
+ middleSection: {
482
+ flex: 1,
483
+ gap: 12,
484
+ minHeight: 0
485
+ },
486
+ privacyText: {
487
+ fontSize: 16,
488
+ fontWeight: "400",
489
+ lineHeight: 24,
490
+ color: "#344054"
491
+ },
492
+ link: {
493
+ color: "#4f87ff",
494
+ textDecorationLine: "underline"
495
+ },
496
+ subTitle: {
497
+ fontWeight: "600",
498
+ fontSize: 16,
499
+ lineHeight: 24,
500
+ letterSpacing: 0.2,
501
+ color: "#101828"
502
+ },
503
+ optionsContainer: {
504
+ gap: 10
505
+ },
506
+ optionItem: {
507
+ flexDirection: "row",
508
+ justifyContent: "space-between",
509
+ alignItems: "center"
510
+ },
511
+ optionLeft: {
512
+ flexDirection: "row",
513
+ gap: 12,
514
+ alignItems: "center",
515
+ flex: 1
516
+ },
517
+ optionTextContainer: {
518
+ flex: 1
519
+ },
520
+ optionTitle: {
521
+ fontWeight: "500",
522
+ fontSize: 16,
523
+ lineHeight: 24,
524
+ letterSpacing: 0.2,
525
+ color: "#101828"
526
+ },
527
+ optionDescription: {
528
+ fontWeight: "400",
529
+ fontSize: 12,
530
+ lineHeight: 18,
531
+ letterSpacing: 0.2,
532
+ color: "#475467"
533
+ },
534
+ dataElementsContainer: {
535
+ paddingLeft: 22
536
+ },
537
+ dataElementItem: {
538
+ flexDirection: "row",
539
+ alignItems: "center",
540
+ gap: 10,
541
+ justifyContent: "space-between",
542
+ minHeight: 24,
543
+ paddingVertical: 2
544
+ },
545
+ dataElementText: {
546
+ fontWeight: "400",
547
+ fontSize: 14,
548
+ lineHeight: 21,
549
+ letterSpacing: 0.2,
550
+ color: "#344054",
551
+ flex: 1
552
+ },
553
+ bottomSection: {
554
+ flexDirection: "row",
555
+ justifyContent: "space-between",
556
+ gap: 12,
557
+ paddingTop: 15,
558
+ backgroundColor: "#ffffff",
559
+ flexShrink: 0
560
+ },
561
+ button: {
562
+ flex: 1,
563
+ borderRadius: 8,
564
+ paddingVertical: 12,
565
+ paddingHorizontal: 20,
566
+ alignItems: "center",
567
+ justifyContent: "center"
568
+ },
569
+ buttonText: {
570
+ fontWeight: "400",
571
+ fontSize: 16,
572
+ lineHeight: 24,
573
+ textAlign: "center"
574
+ },
575
+ acceptButton: {
576
+ backgroundColor: "#4f87ff"
577
+ },
578
+ acceptButtonText: {
579
+ color: "#ffffff"
580
+ },
581
+ cancelButton: {
582
+ borderWidth: 1,
583
+ borderColor: "#d0d5dd",
584
+ backgroundColor: "#ffffff"
585
+ },
586
+ cancelButtonText: {
587
+ color: "#000000"
588
+ },
589
+ languageSelectorContainer: {
590
+ position: "relative"
591
+ },
592
+ languageDropdown: {
593
+ position: "absolute",
594
+ top: "100%",
595
+ right: 0,
596
+ backgroundColor: "#ffffff",
597
+ borderWidth: 1,
598
+ borderColor: "#d0d5dd",
599
+ borderRadius: 4,
600
+ zIndex: 10,
601
+ minWidth: 120,
602
+ maxHeight: 200,
603
+ marginTop: 4,
604
+ ...Platform.select({
605
+ ios: {
606
+ shadowColor: "#000",
607
+ shadowOffset: { width: 0, height: 2 },
608
+ shadowOpacity: 0.1,
609
+ shadowRadius: 4
610
+ },
611
+ android: {
612
+ elevation: 4
613
+ }
614
+ })
615
+ },
616
+ languageItem: {
617
+ paddingVertical: 8,
618
+ paddingHorizontal: 12,
619
+ borderBottomWidth: 1,
620
+ borderBottomColor: "#e5e7eb"
621
+ },
622
+ languageItemText: {
623
+ fontSize: 12,
624
+ color: "#344054"
625
+ },
626
+ selectedLanguageItem: {
627
+ backgroundColor: "#f3f4f6"
628
+ },
629
+ selectedLanguageItemText: {
630
+ fontWeight: "600"
631
+ },
632
+ loadingContainer: {
633
+ flex: 1,
634
+ alignItems: "center",
635
+ justifyContent: "center",
636
+ padding: 32,
637
+ minHeight: 200
638
+ },
639
+ loadingText: {
640
+ marginTop: 16,
641
+ fontSize: 16,
642
+ color: "#344054"
643
+ },
644
+ errorContainer: {
645
+ backgroundColor: "#FEE2E2",
646
+ borderRadius: 6,
647
+ padding: 12,
648
+ marginBottom: 16,
649
+ borderWidth: 1,
650
+ borderColor: "#FCA5A5",
651
+ flexDirection: "row",
652
+ alignItems: "center",
653
+ justifyContent: "space-between"
654
+ },
655
+ errorText: {
656
+ color: "#DC2626",
657
+ fontSize: 14,
658
+ flex: 1
659
+ },
660
+ requiredAsterisk: {
661
+ color: "red"
662
+ },
663
+ divider: {
664
+ height: 1,
665
+ backgroundColor: "#e5e7eb",
666
+ marginVertical: 8
667
+ },
668
+ inputContainer: {
669
+ marginBottom: 12
670
+ },
671
+ inputLabel: {
672
+ fontSize: 13,
673
+ fontWeight: "500",
674
+ color: "#344054",
675
+ marginBottom: 6
676
+ },
677
+ textInput: {
678
+ borderWidth: 1,
679
+ borderColor: "#d0d5dd",
680
+ borderRadius: 8,
681
+ paddingVertical: 10,
682
+ paddingHorizontal: 12,
683
+ fontSize: 13,
684
+ color: "#344054",
685
+ backgroundColor: "#ffffff"
686
+ },
687
+ textInputError: {
688
+ borderColor: "#DC2626"
689
+ },
690
+ inputError: {
691
+ color: "#DC2626",
692
+ fontSize: 12,
693
+ marginTop: 4
694
+ },
695
+ consentedCheckmark: {
696
+ width: 20,
697
+ height: 20,
698
+ borderRadius: 10,
699
+ backgroundColor: "#10B981",
700
+ alignItems: "center",
701
+ justifyContent: "center",
702
+ marginLeft: 12
703
+ },
704
+ consentedCheckmarkSmall: {
705
+ width: 16,
706
+ height: 16,
707
+ borderRadius: 8,
708
+ backgroundColor: "#10B981",
709
+ alignItems: "center",
710
+ justifyContent: "center"
711
+ },
712
+ notConsentedCircle: {
713
+ width: 16,
714
+ height: 16,
715
+ borderRadius: 8,
716
+ borderWidth: 2,
717
+ borderColor: "#d0d5dd",
718
+ backgroundColor: "transparent"
719
+ },
720
+ purposeChevron: {
721
+ marginLeft: 5,
722
+ alignItems: "center",
723
+ justifyContent: "center"
724
+ }
725
+ });
726
+
727
+ // src/RedactoNoticeConsent/useResponsive.ts
728
+ import { useWindowDimensions } from "react-native";
729
+ var useResponsive = () => {
730
+ const { width, height } = useWindowDimensions();
731
+ return {
732
+ isMobile: width < 768,
733
+ width,
734
+ height
735
+ };
736
+ };
737
+
738
+ // src/RedactoNoticeConsent/RedactoNoticeConsent.tsx
739
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
740
+ function stripHtml(html) {
741
+ return html.replace(/<[^>]*>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").trim();
742
+ }
743
+ var expoAv = null;
744
+ try {
745
+ expoAv = __require("expo-av");
746
+ } catch {
747
+ }
748
+ var PurposeItem = ({
749
+ purpose,
750
+ selectedPurposes,
751
+ collapsedPurposes,
752
+ selectedDataElements,
753
+ settings,
754
+ onPurposeToggle,
755
+ onPurposeCollapse,
756
+ onDataElementToggle,
757
+ getTranslatedText,
758
+ isAlreadyConsented,
759
+ initialDataElementSelections = {}
760
+ }) => {
761
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || "#101828";
762
+ const textColor = (settings == null ? void 0 : settings.textColor) || "#344054";
763
+ return /* @__PURE__ */ jsxs(View2, { children: [
764
+ /* @__PURE__ */ jsxs(View2, { style: styles2.optionItem, children: [
765
+ /* @__PURE__ */ jsxs(
766
+ TouchableOpacity2,
767
+ {
768
+ style: styles2.optionLeft,
769
+ onPress: () => onPurposeCollapse(purpose.uuid),
770
+ accessibilityRole: "button",
771
+ accessibilityLabel: `${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)} details`,
772
+ children: [
773
+ /* @__PURE__ */ jsx2(View2, { style: styles2.purposeChevron, children: /* @__PURE__ */ jsx2(Svg2, { width: 7, height: 12, viewBox: "0 0 7 12", fill: "none", children: /* @__PURE__ */ jsx2(
774
+ Path2,
775
+ {
776
+ d: "M1 1L6 6L1 11",
777
+ stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
778
+ strokeWidth: 2,
779
+ strokeLinecap: "round",
780
+ strokeLinejoin: "round",
781
+ transform: !collapsedPurposes[purpose.uuid] ? "rotate(90 3.5 6)" : void 0
782
+ }
783
+ ) }) }),
784
+ /* @__PURE__ */ jsxs(View2, { style: styles2.optionTextContainer, children: [
785
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.optionTitle, { color: headingColor }], children: getTranslatedText("purposes.name", purpose.name, purpose.uuid) }),
786
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.optionDescription, { color: textColor }], children: getTranslatedText("purposes.description", purpose.description, purpose.uuid) })
787
+ ] })
788
+ ]
789
+ }
790
+ ),
791
+ !isAlreadyConsented && /* @__PURE__ */ jsx2(
792
+ Checkbox,
793
+ {
794
+ checked: selectedPurposes[purpose.uuid] || false,
795
+ onChange: () => onPurposeToggle(purpose.uuid),
796
+ size: "large",
797
+ accessibilityLabel: `Select all data elements for ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)}`
798
+ }
799
+ ),
800
+ isAlreadyConsented && /* @__PURE__ */ jsx2(View2, { style: styles2.consentedCheckmark, children: /* @__PURE__ */ jsx2(Svg2, { width: 12, height: 12, viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx2(
801
+ Path2,
802
+ {
803
+ d: "M4 6L5.5 7.5L8.5 4.5",
804
+ stroke: "white",
805
+ strokeWidth: 2,
806
+ strokeLinecap: "round",
807
+ strokeLinejoin: "round"
808
+ }
809
+ ) }) })
810
+ ] }),
811
+ !collapsedPurposes[purpose.uuid] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ jsx2(View2, { style: styles2.dataElementsContainer, children: purpose.data_elements.map((dataElement) => /* @__PURE__ */ jsxs(View2, { style: styles2.dataElementItem, children: [
812
+ /* @__PURE__ */ jsxs(Text, { style: [styles2.dataElementText, { color: textColor }], children: [
813
+ getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid),
814
+ dataElement.required && /* @__PURE__ */ jsx2(Text, { style: styles2.requiredAsterisk, children: " *" })
815
+ ] }),
816
+ !isAlreadyConsented && /* @__PURE__ */ jsx2(
817
+ Checkbox,
818
+ {
819
+ checked: selectedDataElements[`${purpose.uuid}-${dataElement.uuid}`] || false,
820
+ onChange: () => onDataElementToggle(dataElement.uuid, purpose.uuid),
821
+ size: "small",
822
+ accessibilityLabel: `Select ${getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid)}${dataElement.required ? " (required)" : ""}`
823
+ }
824
+ ),
825
+ isAlreadyConsented && (() => {
826
+ const wasSelected = initialDataElementSelections[`${purpose.uuid}-${dataElement.uuid}`] || false;
827
+ return wasSelected ? /* @__PURE__ */ jsx2(View2, { style: styles2.consentedCheckmarkSmall, children: /* @__PURE__ */ jsx2(Svg2, { width: 10, height: 10, viewBox: "0 0 10 10", fill: "none", children: /* @__PURE__ */ jsx2(
828
+ Path2,
829
+ {
830
+ d: "M3 5L4.5 6.5L7.5 3.5",
831
+ stroke: "white",
832
+ strokeWidth: 1.5,
833
+ strokeLinecap: "round",
834
+ strokeLinejoin: "round"
835
+ }
836
+ ) }) }) : /* @__PURE__ */ jsx2(View2, { style: styles2.notConsentedCircle });
837
+ })()
838
+ ] }, dataElement.uuid)) })
839
+ ] });
840
+ };
841
+ var GuardianForm = ({ formData, errors, isSubmitting, onChange, onNext, onDecline, settings, logoUrl }) => {
842
+ var _a, _b, _c, _d;
843
+ const borderColor = (settings == null ? void 0 : settings.borderColor) || "#d0d5dd";
844
+ const textColor = (settings == null ? void 0 : settings.textColor) || "#344054";
845
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
846
+ /* @__PURE__ */ jsx2(View2, { style: styles2.topSection, children: /* @__PURE__ */ jsxs(View2, { style: styles2.topLeft, children: [
847
+ logoUrl ? /* @__PURE__ */ jsx2(Image, { style: styles2.logo, source: { uri: logoUrl } }) : null,
848
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.title, { color: (settings == null ? void 0 : settings.headingColor) || "#101828" }], children: "Guardian Information Required" })
849
+ ] }) }),
850
+ /* @__PURE__ */ jsxs(ScrollView, { style: styles2.middleSection, showsVerticalScrollIndicator: false, children: [
851
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.privacyText, { color: textColor, fontSize: 14, lineHeight: 20 }], children: "As you are under the age of consent, we need to collect information about your legal guardian to proceed." }),
852
+ /* @__PURE__ */ jsxs(View2, { style: { marginTop: 16, gap: 12 }, children: [
853
+ /* @__PURE__ */ jsxs(View2, { style: styles2.inputContainer, children: [
854
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.inputLabel, { color: textColor }], children: "Name of Guardian *" }),
855
+ /* @__PURE__ */ jsx2(
856
+ TextInput,
857
+ {
858
+ value: formData.guardianName,
859
+ onChangeText: (v) => onChange("guardianName", v),
860
+ style: [
861
+ styles2.textInput,
862
+ { borderColor: errors.guardianName ? "#DC2626" : borderColor, color: textColor }
863
+ ],
864
+ placeholder: "Enter guardian's full name",
865
+ placeholderTextColor: "#9CA3AF"
866
+ }
867
+ ),
868
+ errors.guardianName ? /* @__PURE__ */ jsx2(Text, { style: styles2.inputError, children: errors.guardianName }) : null
869
+ ] }),
870
+ /* @__PURE__ */ jsxs(View2, { style: styles2.inputContainer, children: [
871
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.inputLabel, { color: textColor }], children: "Contact of Guardian *" }),
872
+ /* @__PURE__ */ jsx2(
873
+ TextInput,
874
+ {
875
+ value: formData.guardianContact,
876
+ onChangeText: (v) => onChange("guardianContact", v),
877
+ style: [
878
+ styles2.textInput,
879
+ { borderColor: errors.guardianContact ? "#DC2626" : borderColor, color: textColor }
880
+ ],
881
+ placeholder: "Enter guardian's email or phone number",
882
+ placeholderTextColor: "#9CA3AF",
883
+ keyboardType: "email-address",
884
+ autoCapitalize: "none"
885
+ }
886
+ ),
887
+ errors.guardianContact ? /* @__PURE__ */ jsx2(Text, { style: styles2.inputError, children: errors.guardianContact }) : null
888
+ ] }),
889
+ /* @__PURE__ */ jsxs(View2, { style: styles2.inputContainer, children: [
890
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.inputLabel, { color: textColor }], children: "Relationship to Guardian *" }),
891
+ /* @__PURE__ */ jsx2(
892
+ TextInput,
893
+ {
894
+ value: formData.guardianRelationship,
895
+ onChangeText: (v) => onChange("guardianRelationship", v),
896
+ style: [
897
+ styles2.textInput,
898
+ { borderColor: errors.guardianRelationship ? "#DC2626" : borderColor, color: textColor }
899
+ ],
900
+ placeholder: "e.g., Parent, Legal Guardian, etc.",
901
+ placeholderTextColor: "#9CA3AF"
902
+ }
903
+ ),
904
+ errors.guardianRelationship ? /* @__PURE__ */ jsx2(Text, { style: styles2.inputError, children: errors.guardianRelationship }) : null
905
+ ] }),
906
+ errors.general ? /* @__PURE__ */ jsx2(View2, { style: styles2.errorContainer, children: /* @__PURE__ */ jsx2(Text, { style: styles2.errorText, children: errors.general }) }) : null
907
+ ] })
908
+ ] }),
909
+ /* @__PURE__ */ jsxs(View2, { style: styles2.bottomSection, children: [
910
+ /* @__PURE__ */ jsx2(
911
+ TouchableOpacity2,
912
+ {
913
+ style: [
914
+ styles2.button,
915
+ styles2.acceptButton,
916
+ ((_a = settings == null ? void 0 : settings.button) == null ? void 0 : _a.accept) ? { backgroundColor: settings.button.accept.backgroundColor } : null
917
+ ],
918
+ onPress: onNext,
919
+ disabled: isSubmitting,
920
+ accessibilityLabel: "Submit guardian information and proceed",
921
+ children: /* @__PURE__ */ jsx2(Text, { style: [
922
+ styles2.buttonText,
923
+ styles2.acceptButtonText,
924
+ ((_b = settings == null ? void 0 : settings.button) == null ? void 0 : _b.accept) ? { color: settings.button.accept.textColor } : null
925
+ ], children: isSubmitting ? "Submitting..." : "Next" })
926
+ }
927
+ ),
928
+ /* @__PURE__ */ jsx2(
929
+ TouchableOpacity2,
930
+ {
931
+ style: [
932
+ styles2.button,
933
+ styles2.cancelButton,
934
+ ((_c = settings == null ? void 0 : settings.button) == null ? void 0 : _c.decline) ? { backgroundColor: settings.button.decline.backgroundColor, borderColor: settings.button.decline.backgroundColor } : null
935
+ ],
936
+ onPress: onDecline,
937
+ accessibilityLabel: "Cancel guardian information form",
938
+ children: /* @__PURE__ */ jsx2(Text, { style: [
939
+ styles2.buttonText,
940
+ styles2.cancelButtonText,
941
+ ((_d = settings == null ? void 0 : settings.button) == null ? void 0 : _d.decline) ? { color: settings.button.decline.textColor } : null
942
+ ], children: "Cancel" })
943
+ }
944
+ )
945
+ ] })
946
+ ] });
947
+ };
948
+ var AgeVerification = ({ onYes, onNo, settings, logoUrl }) => {
949
+ var _a, _b, _c, _d;
950
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
951
+ /* @__PURE__ */ jsx2(View2, { style: styles2.topSection, children: /* @__PURE__ */ jsxs(View2, { style: styles2.topLeft, children: [
952
+ logoUrl ? /* @__PURE__ */ jsx2(Image, { style: styles2.logo, source: { uri: logoUrl } }) : null,
953
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.title, { color: (settings == null ? void 0 : settings.headingColor) || "#101828" }], children: "Age Verification" })
954
+ ] }) }),
955
+ /* @__PURE__ */ jsxs(ScrollView, { style: styles2.middleSection, showsVerticalScrollIndicator: false, children: [
956
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.privacyText, { color: (settings == null ? void 0 : settings.textColor) || "#344054" }], children: "To proceed, we need to verify your age." }),
957
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.privacyText, { color: (settings == null ? void 0 : settings.textColor) || "#344054", marginTop: 12, fontWeight: "600" }], children: "Are you 18 years of age or older?" })
958
+ ] }),
959
+ /* @__PURE__ */ jsxs(View2, { style: styles2.bottomSection, children: [
960
+ /* @__PURE__ */ jsx2(
961
+ TouchableOpacity2,
962
+ {
963
+ style: [
964
+ styles2.button,
965
+ styles2.acceptButton,
966
+ ((_a = settings == null ? void 0 : settings.button) == null ? void 0 : _a.accept) ? { backgroundColor: settings.button.accept.backgroundColor } : null
967
+ ],
968
+ onPress: onYes,
969
+ accessibilityLabel: "Yes, I am 18 or older",
970
+ children: /* @__PURE__ */ jsx2(Text, { style: [
971
+ styles2.buttonText,
972
+ styles2.acceptButtonText,
973
+ ((_b = settings == null ? void 0 : settings.button) == null ? void 0 : _b.accept) ? { color: settings.button.accept.textColor } : null
974
+ ], children: "Yes" })
975
+ }
976
+ ),
977
+ /* @__PURE__ */ jsx2(
978
+ TouchableOpacity2,
979
+ {
980
+ style: [
981
+ styles2.button,
982
+ styles2.cancelButton,
983
+ ((_c = settings == null ? void 0 : settings.button) == null ? void 0 : _c.decline) ? { backgroundColor: settings.button.decline.backgroundColor, borderColor: settings.button.decline.backgroundColor } : null
984
+ ],
985
+ onPress: onNo,
986
+ accessibilityLabel: "No, I am under 18",
987
+ children: /* @__PURE__ */ jsx2(Text, { style: [
988
+ styles2.buttonText,
989
+ styles2.cancelButtonText,
990
+ ((_d = settings == null ? void 0 : settings.button) == null ? void 0 : _d.decline) ? { color: settings.button.decline.textColor } : null
991
+ ], children: "No" })
992
+ }
993
+ )
994
+ ] })
995
+ ] });
996
+ };
997
+ var RedactoNoticeConsent = ({
998
+ noticeId,
999
+ accessToken,
1000
+ refreshToken: _refreshToken,
1001
+ baseUrl,
1002
+ settings,
1003
+ language = "en",
1004
+ blockUI = false,
1005
+ onAccept,
1006
+ onDecline,
1007
+ onError,
1008
+ applicationId,
1009
+ validateAgainst = "all",
1010
+ includeFullyConsentedData = false,
1011
+ reviewModeButtonText
1012
+ }) => {
1013
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s;
1014
+ const { isMobile } = useResponsive();
1015
+ const [content, setContent] = useState(null);
1016
+ const [isLoading, setIsLoading] = useState(true);
1017
+ const [isSubmitting, setIsSubmitting] = useState(false);
1018
+ const [selectedLanguage, setSelectedLanguage] = useState(language);
1019
+ const [collapsedPurposes, setCollapsedPurposes] = useState({});
1020
+ const [selectedPurposes, setSelectedPurposes] = useState({});
1021
+ const [selectedDataElements, setSelectedDataElements] = useState({});
1022
+ const [initialDataElementSelections, setInitialDataElementSelections] = useState({});
1023
+ const [errorMessage, setErrorMessage] = useState(null);
1024
+ const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = useState(false);
1025
+ const [isVisible, setIsVisible] = useState(false);
1026
+ const [isReconsentMode, setIsReconsentMode] = useState(false);
1027
+ const [isReviewMode, setIsReviewMode] = useState(false);
1028
+ const [showAgeVerification, setShowAgeVerification] = useState(false);
1029
+ const [showGuardianForm, setShowGuardianForm] = useState(false);
1030
+ const [isMinor, setIsMinor] = useState(false);
1031
+ const [guardianFormData, setGuardianFormData] = useState({ guardianName: "", guardianContact: "", guardianRelationship: "" });
1032
+ const [guardianFormErrors, setGuardianFormErrors] = useState({});
1033
+ const [isSubmittingGuardian, setIsSubmittingGuardian] = useState(false);
1034
+ const [isTTSAvailable, setIsTTSAvailable] = useState(false);
1035
+ const [ttsAudioUrls, setTtsAudioUrls] = useState({});
1036
+ const [isPlaying, setIsPlaying] = useState(false);
1037
+ const [isPaused, setIsPaused] = useState(false);
1038
+ const soundRef = useRef(null);
1039
+ const ttsQueueRef = useRef([]);
1040
+ const ttsIndexRef = useRef(0);
1041
+ const abortControllerRef = useRef(null);
1042
+ const hasBootstrappedLanguageRef = useRef(false);
1043
+ const fetchContent = useCallback(async () => {
1044
+ if (abortControllerRef.current) {
1045
+ abortControllerRef.current.abort();
1046
+ }
1047
+ abortControllerRef.current = new AbortController();
1048
+ setIsLoading(true);
1049
+ setErrorMessage(null);
1050
+ try {
1051
+ const data = await fetchConsentContent({
1052
+ noticeId,
1053
+ accessToken,
1054
+ baseUrl,
1055
+ language: selectedLanguage,
1056
+ validate_against: validateAgainst,
1057
+ include_fully_consented_data: includeFullyConsentedData,
1058
+ specific_uuid: applicationId,
1059
+ signal: abortControllerRef.current.signal
1060
+ });
1061
+ const activeConfig2 = data.detail.active_config;
1062
+ const purposeSelections = data.detail.purpose_selections;
1063
+ const reconsentRequired = data.detail.reconsent_required || false;
1064
+ const noticeIsMinor = data.detail.is_minor || false;
1065
+ setIsMinor(noticeIsMinor);
1066
+ const initialCollapsed = {};
1067
+ const initialPurposeState = {};
1068
+ const initialDataElementState = {};
1069
+ const initialDataElementSelectionsMap = {};
1070
+ activeConfig2.purposes.forEach((purpose) => {
1071
+ initialCollapsed[purpose.uuid] = true;
1072
+ const sel = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
1073
+ const purposeSelected = sel ? sel.selected : false;
1074
+ initialPurposeState[purpose.uuid] = purposeSelected;
1075
+ purpose.data_elements.forEach((el) => {
1076
+ var _a2, _b2, _c2;
1077
+ const combinedId = `${purpose.uuid}-${el.uuid}`;
1078
+ const elementSel = (_c2 = (_b2 = (_a2 = sel == null ? void 0 : sel.data_elements) == null ? void 0 : _a2[el.uuid]) == null ? void 0 : _b2.selected) != null ? _c2 : false;
1079
+ initialDataElementState[combinedId] = elementSel;
1080
+ initialDataElementSelectionsMap[combinedId] = elementSel;
1081
+ });
1082
+ });
1083
+ setCollapsedPurposes(initialCollapsed);
1084
+ setSelectedPurposes(initialPurposeState);
1085
+ setSelectedDataElements(initialDataElementState);
1086
+ setInitialDataElementSelections(initialDataElementSelectionsMap);
1087
+ if (!hasBootstrappedLanguageRef.current) {
1088
+ hasBootstrappedLanguageRef.current = true;
1089
+ if (activeConfig2.default_language && activeConfig2.default_language !== selectedLanguage) {
1090
+ setSelectedLanguage(activeConfig2.default_language);
1091
+ }
1092
+ }
1093
+ setContent(data);
1094
+ setIsReconsentMode(reconsentRequired);
1095
+ if (noticeIsMinor) {
1096
+ setShowAgeVerification(true);
1097
+ }
1098
+ setIsVisible(true);
1099
+ } catch (err) {
1100
+ const error = err;
1101
+ if (error.name === "AbortError") return;
1102
+ if (error.status === 409) {
1103
+ onAccept();
1104
+ return;
1105
+ }
1106
+ if (error.status === 401) {
1107
+ setErrorMessage("Unauthorized: Invalid or expired token");
1108
+ } else {
1109
+ setErrorMessage(error.message || "Failed to load consent content");
1110
+ }
1111
+ onError == null ? void 0 : onError(error);
1112
+ } finally {
1113
+ setIsLoading(false);
1114
+ }
1115
+ }, [noticeId, accessToken, baseUrl, selectedLanguage, validateAgainst, includeFullyConsentedData, applicationId, onAccept, onError]);
1116
+ useEffect(() => {
1117
+ hasBootstrappedLanguageRef.current = false;
1118
+ }, [noticeId, accessToken]);
1119
+ useEffect(() => {
1120
+ fetchContent();
1121
+ return () => {
1122
+ var _a2;
1123
+ (_a2 = abortControllerRef.current) == null ? void 0 : _a2.abort();
1124
+ };
1125
+ }, [fetchContent]);
1126
+ const fetchTTS = useCallback(async () => {
1127
+ if (!expoAv || !content) return;
1128
+ try {
1129
+ const data = await fetchTTSAudioUrls({
1130
+ accessToken,
1131
+ baseUrl,
1132
+ noticeUuid: content.detail.active_config.notice_uuid,
1133
+ language: selectedLanguage
1134
+ });
1135
+ const urls = {
1136
+ notice_text: data.detail.notice_text_audio_url,
1137
+ additional_text: data.detail.additional_text_audio_url,
1138
+ notice_banner_heading: data.detail.notice_banner_heading_audio_url,
1139
+ purpose_section_heading: data.detail.purpose_section_heading_audio_url
1140
+ };
1141
+ content.detail.active_config.purposes.forEach((p) => {
1142
+ var _a2;
1143
+ const pa = (_a2 = data.detail.purposes_audio) == null ? void 0 : _a2[p.uuid];
1144
+ if (pa) {
1145
+ urls[`purpose_name_${p.uuid}`] = pa.name_audio_url;
1146
+ urls[`purpose_desc_${p.uuid}`] = pa.description_audio_url;
1147
+ }
1148
+ p.data_elements.forEach((el) => {
1149
+ var _a3;
1150
+ const ea = (_a3 = data.detail.data_elements_audio) == null ? void 0 : _a3[el.uuid];
1151
+ if (ea) urls[`element_${el.uuid}`] = ea.name_audio_url;
1152
+ });
1153
+ });
1154
+ setTtsAudioUrls(urls);
1155
+ setIsTTSAvailable(true);
1156
+ } catch {
1157
+ setIsTTSAvailable(false);
1158
+ }
1159
+ }, [content, accessToken, baseUrl, selectedLanguage]);
1160
+ useEffect(() => {
1161
+ if (content && expoAv) {
1162
+ fetchTTS();
1163
+ }
1164
+ }, [content, fetchTTS]);
1165
+ const stopAudio = useCallback(async () => {
1166
+ if (soundRef.current) {
1167
+ await soundRef.current.stopAsync();
1168
+ await soundRef.current.unloadAsync();
1169
+ soundRef.current = null;
1170
+ }
1171
+ setIsPlaying(false);
1172
+ setIsPaused(false);
1173
+ ttsIndexRef.current = 0;
1174
+ }, []);
1175
+ const playNextInQueue = useCallback(async () => {
1176
+ if (!expoAv) return;
1177
+ const queue = ttsQueueRef.current;
1178
+ if (ttsIndexRef.current >= queue.length) {
1179
+ await stopAudio();
1180
+ return;
1181
+ }
1182
+ const url = queue[ttsIndexRef.current];
1183
+ ttsIndexRef.current += 1;
1184
+ try {
1185
+ const { sound } = await expoAv.Audio.Sound.createAsync(
1186
+ { uri: url },
1187
+ { shouldPlay: true },
1188
+ (status) => {
1189
+ if (status.isLoaded && status.didJustFinish) {
1190
+ playNextInQueue();
1191
+ }
1192
+ }
1193
+ );
1194
+ soundRef.current = sound;
1195
+ } catch {
1196
+ playNextInQueue();
1197
+ }
1198
+ }, [stopAudio]);
1199
+ const startAudio = useCallback(async () => {
1200
+ if (!expoAv || !content) return;
1201
+ const ac = content.detail.active_config;
1202
+ const queue = [];
1203
+ if (ttsAudioUrls.notice_banner_heading) queue.push(ttsAudioUrls.notice_banner_heading);
1204
+ if (ttsAudioUrls.notice_text) queue.push(ttsAudioUrls.notice_text);
1205
+ if (ttsAudioUrls.additional_text) queue.push(ttsAudioUrls.additional_text);
1206
+ if (ttsAudioUrls.purpose_section_heading) queue.push(ttsAudioUrls.purpose_section_heading);
1207
+ ac.purposes.forEach((p) => {
1208
+ if (ttsAudioUrls[`purpose_name_${p.uuid}`]) queue.push(ttsAudioUrls[`purpose_name_${p.uuid}`]);
1209
+ if (ttsAudioUrls[`purpose_desc_${p.uuid}`]) queue.push(ttsAudioUrls[`purpose_desc_${p.uuid}`]);
1210
+ p.data_elements.forEach((el) => {
1211
+ if (ttsAudioUrls[`element_${el.uuid}`]) queue.push(ttsAudioUrls[`element_${el.uuid}`]);
1212
+ });
1213
+ });
1214
+ ttsQueueRef.current = queue;
1215
+ ttsIndexRef.current = 0;
1216
+ setIsPlaying(true);
1217
+ setIsPaused(false);
1218
+ await playNextInQueue();
1219
+ }, [content, ttsAudioUrls, playNextInQueue]);
1220
+ const toggleAudio = useCallback(async () => {
1221
+ var _a2, _b2;
1222
+ if (isPlaying && !isPaused) {
1223
+ await ((_a2 = soundRef.current) == null ? void 0 : _a2.pauseAsync());
1224
+ setIsPaused(true);
1225
+ } else if (isPaused) {
1226
+ await ((_b2 = soundRef.current) == null ? void 0 : _b2.playAsync());
1227
+ setIsPaused(false);
1228
+ } else {
1229
+ await startAudio();
1230
+ }
1231
+ }, [isPlaying, isPaused, startAudio]);
1232
+ useEffect(() => {
1233
+ return () => {
1234
+ stopAudio();
1235
+ };
1236
+ }, [stopAudio]);
1237
+ const getTranslatedText = useCallback(
1238
+ (key, defaultText, itemId) => {
1239
+ var _a2, _b2, _c2;
1240
+ if (!content) return defaultText;
1241
+ const activeConfig2 = content.detail.active_config;
1242
+ const translationMap = (_a2 = activeConfig2.supported_languages_and_translations) == null ? void 0 : _a2[selectedLanguage];
1243
+ if (!translationMap) return defaultText;
1244
+ if (itemId) {
1245
+ if (key === "purposes.name" || key === "purposes.description") {
1246
+ const purposeData = (_b2 = translationMap.purposes) == null ? void 0 : _b2[itemId];
1247
+ if (purposeData) {
1248
+ if (typeof purposeData === "string") {
1249
+ return key === "purposes.name" ? purposeData : defaultText;
1250
+ }
1251
+ return key === "purposes.name" ? purposeData.name || defaultText : purposeData.description || defaultText;
1252
+ }
1253
+ return defaultText;
1254
+ }
1255
+ if (key.startsWith("data_elements.")) {
1256
+ return ((_c2 = translationMap.data_elements) == null ? void 0 : _c2[itemId]) || defaultText;
1257
+ }
1258
+ }
1259
+ const value = translationMap[key];
1260
+ if (typeof value === "string" && value) return value;
1261
+ return defaultText;
1262
+ },
1263
+ [content, selectedLanguage]
1264
+ );
1265
+ const handlePurposeToggle = useCallback((purposeUuid) => {
1266
+ setSelectedPurposes((prev) => {
1267
+ const newState = !prev[purposeUuid];
1268
+ setSelectedDataElements((prevEl) => {
1269
+ var _a2;
1270
+ const updated = { ...prevEl };
1271
+ (_a2 = content == null ? void 0 : content.detail.active_config.purposes.find((p) => p.uuid === purposeUuid)) == null ? void 0 : _a2.data_elements.forEach((el) => {
1272
+ updated[`${purposeUuid}-${el.uuid}`] = newState;
1273
+ });
1274
+ return updated;
1275
+ });
1276
+ return { ...prev, [purposeUuid]: newState };
1277
+ });
1278
+ }, [content]);
1279
+ const handlePurposeCollapse = useCallback((purposeUuid) => {
1280
+ setCollapsedPurposes((prev) => ({ ...prev, [purposeUuid]: !prev[purposeUuid] }));
1281
+ }, []);
1282
+ const handleDataElementToggle = useCallback((elementUuid, purposeUuid) => {
1283
+ if (!content) return;
1284
+ const purpose = content.detail.active_config.purposes.find((p) => p.uuid === purposeUuid);
1285
+ if (!purpose) return;
1286
+ const combinedId = `${purposeUuid}-${elementUuid}`;
1287
+ setSelectedDataElements((prev) => {
1288
+ const newState = { ...prev, [combinedId]: !prev[combinedId] };
1289
+ const requiredElements = purpose.data_elements.filter((el) => el.required);
1290
+ const shouldCheckPurpose = requiredElements.length > 0 ? requiredElements.every((el) => newState[`${purposeUuid}-${el.uuid}`]) : purpose.data_elements.some((el) => newState[`${purposeUuid}-${el.uuid}`]);
1291
+ setSelectedPurposes((prev2) => ({ ...prev2, [purposeUuid]: shouldCheckPurpose }));
1292
+ return newState;
1293
+ });
1294
+ }, [content]);
1295
+ const handleAccept = useCallback(async () => {
1296
+ if (!content) return;
1297
+ try {
1298
+ setIsSubmitting(true);
1299
+ setErrorMessage(null);
1300
+ const activeConfig2 = content.detail.active_config;
1301
+ await submitConsentEvent({
1302
+ accessToken,
1303
+ baseUrl,
1304
+ noticeUuid: activeConfig2.notice_uuid,
1305
+ purposes: activeConfig2.purposes.map((purpose) => ({
1306
+ ...purpose,
1307
+ selected: selectedPurposes[purpose.uuid] || false,
1308
+ data_elements: purpose.data_elements.map((el) => ({
1309
+ ...el,
1310
+ selected: selectedDataElements[`${purpose.uuid}-${el.uuid}`] || false
1311
+ }))
1312
+ })),
1313
+ declined: false,
1314
+ meta_data: applicationId ? { specific_uuid: applicationId } : void 0
1315
+ });
1316
+ await stopAudio();
1317
+ clearApiCache();
1318
+ setIsVisible(false);
1319
+ onAccept();
1320
+ } catch (err) {
1321
+ const error = err;
1322
+ setErrorMessage(
1323
+ error.status === 500 ? "An error occurred. Please try again later." : error.message || "Failed to submit consent."
1324
+ );
1325
+ onError == null ? void 0 : onError(error);
1326
+ } finally {
1327
+ setIsSubmitting(false);
1328
+ }
1329
+ }, [content, accessToken, baseUrl, applicationId, selectedPurposes, selectedDataElements, stopAudio, onAccept, onError]);
1330
+ const handleDecline = useCallback(async () => {
1331
+ if (!content) return;
1332
+ if (blockUI) return;
1333
+ try {
1334
+ setIsSubmitting(true);
1335
+ const activeConfig2 = content.detail.active_config;
1336
+ await submitConsentEvent({
1337
+ accessToken,
1338
+ baseUrl,
1339
+ noticeUuid: activeConfig2.notice_uuid,
1340
+ purposes: activeConfig2.purposes.map((purpose) => ({
1341
+ ...purpose,
1342
+ selected: false,
1343
+ data_elements: purpose.data_elements.map((el) => ({ ...el, selected: false }))
1344
+ })),
1345
+ declined: true
1346
+ });
1347
+ await stopAudio();
1348
+ clearApiCache();
1349
+ setIsVisible(false);
1350
+ onDecline();
1351
+ } catch (err) {
1352
+ const error = err;
1353
+ setErrorMessage(error.message || "Failed to decline consent.");
1354
+ onError == null ? void 0 : onError(error);
1355
+ } finally {
1356
+ setIsSubmitting(false);
1357
+ }
1358
+ }, [content, accessToken, baseUrl, blockUI, stopAudio, onDecline, onError]);
1359
+ const handleGuardianFormChange = useCallback((field, value) => {
1360
+ setGuardianFormData((prev) => ({ ...prev, [field]: value }));
1361
+ setGuardianFormErrors((prev) => ({ ...prev, [field]: "" }));
1362
+ }, []);
1363
+ const handleGuardianFormNext = useCallback(async () => {
1364
+ const errors = {};
1365
+ if (!guardianFormData.guardianName.trim()) errors.guardianName = "Guardian name is required";
1366
+ if (!guardianFormData.guardianContact.trim()) errors.guardianContact = "Guardian contact is required";
1367
+ if (!guardianFormData.guardianRelationship.trim()) errors.guardianRelationship = "Relationship is required";
1368
+ if (Object.keys(errors).length > 0) {
1369
+ setGuardianFormErrors(errors);
1370
+ return;
1371
+ }
1372
+ try {
1373
+ setIsSubmittingGuardian(true);
1374
+ await submitGuardianInfo({
1375
+ accessToken,
1376
+ baseUrl,
1377
+ guardianName: guardianFormData.guardianName,
1378
+ guardianContact: guardianFormData.guardianContact,
1379
+ guardianRelationship: guardianFormData.guardianRelationship
1380
+ });
1381
+ setShowGuardianForm(false);
1382
+ } catch (err) {
1383
+ const error = err;
1384
+ setGuardianFormErrors({ general: error.message || "Failed to submit guardian info." });
1385
+ } finally {
1386
+ setIsSubmittingGuardian(false);
1387
+ }
1388
+ }, [guardianFormData, accessToken, baseUrl]);
1389
+ const activeConfig = content == null ? void 0 : content.detail.active_config;
1390
+ const logoUrl = activeConfig == null ? void 0 : activeConfig.logo_url;
1391
+ const supportedLanguages = useMemo(
1392
+ () => Object.keys((activeConfig == null ? void 0 : activeConfig.supported_languages_and_translations) || {}),
1393
+ [activeConfig]
1394
+ );
1395
+ const acceptButtonStyle = ((_a = settings == null ? void 0 : settings.button) == null ? void 0 : _a.accept) ? { backgroundColor: settings.button.accept.backgroundColor } : {};
1396
+ const acceptButtonTextStyle = ((_b = settings == null ? void 0 : settings.button) == null ? void 0 : _b.accept) ? { color: settings.button.accept.textColor } : {};
1397
+ const declineButtonStyle = ((_c = settings == null ? void 0 : settings.button) == null ? void 0 : _c.decline) ? { backgroundColor: settings.button.decline.backgroundColor, borderColor: settings.button.decline.backgroundColor } : {};
1398
+ const declineButtonTextStyle = ((_d = settings == null ? void 0 : settings.button) == null ? void 0 : _d.decline) ? { color: settings.button.decline.textColor } : {};
1399
+ const modalBg = (settings == null ? void 0 : settings.backgroundColor) ? { backgroundColor: settings.backgroundColor } : {};
1400
+ const translatedNoticeText = activeConfig ? stripHtml(getTranslatedText("notice_text", activeConfig.notice_text || "")) : "";
1401
+ const translatedAdditionalText = activeConfig ? stripHtml(getTranslatedText("additional_text", activeConfig.additional_text || "")) : "";
1402
+ if (!isVisible && !isLoading) return null;
1403
+ return /* @__PURE__ */ jsx2(
1404
+ Modal,
1405
+ {
1406
+ visible: isVisible || isLoading,
1407
+ transparent: true,
1408
+ animationType: "fade",
1409
+ onRequestClose: blockUI ? void 0 : handleDecline,
1410
+ accessibilityViewIsModal: true,
1411
+ children: /* @__PURE__ */ jsxs(View2, { style: styles2.overlay, children: [
1412
+ isLanguageDropdownOpen && /* @__PURE__ */ jsx2(
1413
+ Pressable,
1414
+ {
1415
+ style: [StyleSheet3.absoluteFillObject, { zIndex: 1 }],
1416
+ onPress: () => setIsLanguageDropdownOpen(false)
1417
+ }
1418
+ ),
1419
+ /* @__PURE__ */ jsx2(
1420
+ View2,
1421
+ {
1422
+ style: [
1423
+ styles2.modal,
1424
+ modalBg,
1425
+ { zIndex: 2 },
1426
+ (settings == null ? void 0 : settings.borderRadius) ? { borderRadius: parseInt(settings.borderRadius, 10) } : null
1427
+ ],
1428
+ children: isLoading ? /* @__PURE__ */ jsxs(View2, { style: styles2.loadingContainer, children: [
1429
+ /* @__PURE__ */ jsx2(ActivityIndicator, { size: "large", color: "#4f87ff" }),
1430
+ /* @__PURE__ */ jsx2(Text, { style: styles2.loadingText, children: "Loading..." })
1431
+ ] }) : showGuardianForm ? /* @__PURE__ */ jsx2(View2, { style: styles2.content, children: /* @__PURE__ */ jsx2(
1432
+ GuardianForm,
1433
+ {
1434
+ formData: guardianFormData,
1435
+ errors: guardianFormErrors,
1436
+ isSubmitting: isSubmittingGuardian,
1437
+ onChange: handleGuardianFormChange,
1438
+ onNext: handleGuardianFormNext,
1439
+ onDecline: handleDecline,
1440
+ settings,
1441
+ logoUrl
1442
+ }
1443
+ ) }) : showAgeVerification ? /* @__PURE__ */ jsx2(View2, { style: styles2.content, children: /* @__PURE__ */ jsx2(
1444
+ AgeVerification,
1445
+ {
1446
+ onYes: () => {
1447
+ setShowAgeVerification(false);
1448
+ },
1449
+ onNo: () => {
1450
+ setShowAgeVerification(false);
1451
+ if (isMinor) setShowGuardianForm(true);
1452
+ },
1453
+ settings,
1454
+ logoUrl
1455
+ }
1456
+ ) }) : /* @__PURE__ */ jsxs(View2, { style: styles2.content, children: [
1457
+ errorMessage ? /* @__PURE__ */ jsxs(View2, { style: styles2.errorContainer, children: [
1458
+ /* @__PURE__ */ jsx2(Text, { style: styles2.errorText, children: errorMessage }),
1459
+ /* @__PURE__ */ jsx2(TouchableOpacity2, { onPress: () => setErrorMessage(null), hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, children: /* @__PURE__ */ jsx2(Svg2, { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx2(Path2, { d: "M12 4L4 12M4 4L12 12", stroke: "#DC2626", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) }) })
1460
+ ] }) : null,
1461
+ /* @__PURE__ */ jsxs(View2, { style: styles2.topSection, children: [
1462
+ /* @__PURE__ */ jsxs(View2, { style: styles2.topLeft, children: [
1463
+ logoUrl ? /* @__PURE__ */ jsx2(Image, { style: styles2.logo, source: { uri: logoUrl } }) : null,
1464
+ /* @__PURE__ */ jsx2(
1465
+ Text,
1466
+ {
1467
+ style: [
1468
+ styles2.title,
1469
+ { color: (settings == null ? void 0 : settings.headingColor) || "#101828" },
1470
+ isMobile ? { fontSize: 16 } : null
1471
+ ],
1472
+ numberOfLines: 2,
1473
+ children: getTranslatedText(
1474
+ "notice_banner_heading",
1475
+ (activeConfig == null ? void 0 : activeConfig.notice_banner_heading) || "Privacy Notice"
1476
+ )
1477
+ }
1478
+ )
1479
+ ] }),
1480
+ /* @__PURE__ */ jsxs(View2, { style: { flexDirection: "row", gap: 8, alignItems: "center" }, children: [
1481
+ isTTSAvailable && /* @__PURE__ */ jsx2(
1482
+ TouchableOpacity2,
1483
+ {
1484
+ style: [styles2.topRight, ((_e = settings == null ? void 0 : settings.button) == null ? void 0 : _e.language) ? { backgroundColor: settings.button.language.backgroundColor, borderColor: settings.button.language.backgroundColor } : null],
1485
+ onPress: toggleAudio,
1486
+ accessibilityLabel: isPlaying && !isPaused ? "Pause audio" : isPaused ? "Resume audio" : "Start audio",
1487
+ children: isPlaying && !isPaused ? /* @__PURE__ */ jsxs(Svg2, { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", children: [
1488
+ /* @__PURE__ */ jsx2(Rect, { x: "6", y: "4", width: "4", height: "16", fill: ((_g = (_f = settings == null ? void 0 : settings.button) == null ? void 0 : _f.language) == null ? void 0 : _g.textColor) || "#344054" }),
1489
+ /* @__PURE__ */ jsx2(Rect, { x: "14", y: "4", width: "4", height: "16", fill: ((_i = (_h = settings == null ? void 0 : settings.button) == null ? void 0 : _h.language) == null ? void 0 : _i.textColor) || "#344054" })
1490
+ ] }) : /* @__PURE__ */ jsxs(Svg2, { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", children: [
1491
+ /* @__PURE__ */ jsx2(
1492
+ Path2,
1493
+ {
1494
+ d: "M11 5L6 9H2V15H6L11 19V5Z",
1495
+ stroke: ((_k = (_j = settings == null ? void 0 : settings.button) == null ? void 0 : _j.language) == null ? void 0 : _k.textColor) || "#344054",
1496
+ strokeWidth: 2,
1497
+ strokeLinecap: "round",
1498
+ strokeLinejoin: "round"
1499
+ }
1500
+ ),
1501
+ /* @__PURE__ */ jsx2(
1502
+ Path2,
1503
+ {
1504
+ d: "M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07",
1505
+ stroke: ((_m = (_l = settings == null ? void 0 : settings.button) == null ? void 0 : _l.language) == null ? void 0 : _m.textColor) || "#344054",
1506
+ strokeWidth: 2,
1507
+ strokeLinecap: "round",
1508
+ strokeLinejoin: "round"
1509
+ }
1510
+ )
1511
+ ] })
1512
+ }
1513
+ ),
1514
+ supportedLanguages.length > 0 && /* @__PURE__ */ jsxs(View2, { style: styles2.languageSelectorContainer, children: [
1515
+ /* @__PURE__ */ jsxs(
1516
+ TouchableOpacity2,
1517
+ {
1518
+ style: [
1519
+ styles2.topRight,
1520
+ ((_n = settings == null ? void 0 : settings.button) == null ? void 0 : _n.language) ? { backgroundColor: settings.button.language.backgroundColor, borderColor: settings.button.language.backgroundColor } : null
1521
+ ],
1522
+ onPress: () => setIsLanguageDropdownOpen((v) => !v),
1523
+ accessibilityLabel: `Select language. Current: ${selectedLanguage}`,
1524
+ children: [
1525
+ /* @__PURE__ */ jsx2(Text, { style: [styles2.topRightText, ((_o = settings == null ? void 0 : settings.button) == null ? void 0 : _o.language) ? { color: settings.button.language.textColor } : null], children: selectedLanguage }),
1526
+ /* @__PURE__ */ jsx2(Svg2, { width: 12, height: 12, viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx2(
1527
+ Path2,
1528
+ {
1529
+ d: "M4 6L8 10L12 6",
1530
+ stroke: ((_q = (_p = settings == null ? void 0 : settings.button) == null ? void 0 : _p.language) == null ? void 0 : _q.textColor) || "#344054",
1531
+ strokeWidth: 2,
1532
+ strokeLinecap: "round",
1533
+ strokeLinejoin: "round"
1534
+ }
1535
+ ) })
1536
+ ]
1537
+ }
1538
+ ),
1539
+ isLanguageDropdownOpen && /* @__PURE__ */ jsx2(View2, { style: styles2.languageDropdown, children: /* @__PURE__ */ jsx2(ScrollView, { children: supportedLanguages.map((lang) => /* @__PURE__ */ jsx2(
1540
+ TouchableOpacity2,
1541
+ {
1542
+ style: [
1543
+ styles2.languageItem,
1544
+ lang === selectedLanguage ? styles2.selectedLanguageItem : null
1545
+ ],
1546
+ onPress: () => {
1547
+ setSelectedLanguage(lang);
1548
+ setIsLanguageDropdownOpen(false);
1549
+ },
1550
+ children: /* @__PURE__ */ jsx2(
1551
+ Text,
1552
+ {
1553
+ style: [
1554
+ styles2.languageItemText,
1555
+ lang === selectedLanguage ? styles2.selectedLanguageItemText : null
1556
+ ],
1557
+ children: lang
1558
+ }
1559
+ )
1560
+ },
1561
+ lang
1562
+ )) }) })
1563
+ ] })
1564
+ ] })
1565
+ ] }),
1566
+ /* @__PURE__ */ jsxs(ScrollView, { style: styles2.middleSection, showsVerticalScrollIndicator: false, children: [
1567
+ translatedNoticeText ? /* @__PURE__ */ jsx2(Text, { style: [styles2.privacyText, (settings == null ? void 0 : settings.textColor) ? { color: settings.textColor } : null], children: translatedNoticeText }) : null,
1568
+ translatedAdditionalText ? /* @__PURE__ */ jsx2(Text, { style: [styles2.privacyText, { marginTop: 8 }, (settings == null ? void 0 : settings.textColor) ? { color: settings.textColor } : null], children: translatedAdditionalText }) : null,
1569
+ (activeConfig == null ? void 0 : activeConfig.privacy_policy_url) ? /* @__PURE__ */ jsx2(
1570
+ TouchableOpacity2,
1571
+ {
1572
+ onPress: () => Linking.openURL(activeConfig.privacy_policy_url),
1573
+ style: { marginTop: 8 },
1574
+ children: /* @__PURE__ */ jsxs(Text, { style: styles2.link, children: [
1575
+ getTranslatedText("privacy_policy_prefix_text", activeConfig.privacy_policy_prefix_text || ""),
1576
+ " ",
1577
+ /* @__PURE__ */ jsx2(Text, { style: styles2.link, children: getTranslatedText("privacy_policy_anchor_text", activeConfig.privacy_policy_anchor_text || "Privacy Policy") })
1578
+ ] })
1579
+ }
1580
+ ) : null,
1581
+ (activeConfig == null ? void 0 : activeConfig.purposes) && activeConfig.purposes.length > 0 ? /* @__PURE__ */ jsx2(Text, { style: [styles2.subTitle, { marginTop: 16, marginBottom: 8 }, (settings == null ? void 0 : settings.headingColor) ? { color: settings.headingColor } : null], children: getTranslatedText("purpose_section_heading", activeConfig.purpose_section_heading || "Data Purposes") }) : null,
1582
+ /* @__PURE__ */ jsx2(View2, { style: styles2.optionsContainer, children: activeConfig == null ? void 0 : activeConfig.purposes.map((purpose) => {
1583
+ var _a2;
1584
+ const purposeSelection = (_a2 = content == null ? void 0 : content.detail.purpose_selections) == null ? void 0 : _a2[purpose.uuid];
1585
+ const isAlreadyConsented = !isReconsentMode && ((purposeSelection == null ? void 0 : purposeSelection.selected) || false) && !(purposeSelection == null ? void 0 : purposeSelection.needs_reconsent);
1586
+ return /* @__PURE__ */ jsx2(
1587
+ PurposeItem,
1588
+ {
1589
+ purpose,
1590
+ selectedPurposes,
1591
+ collapsedPurposes,
1592
+ selectedDataElements,
1593
+ settings: { headingColor: settings == null ? void 0 : settings.headingColor, textColor: settings == null ? void 0 : settings.textColor, font: settings == null ? void 0 : settings.font },
1594
+ onPurposeToggle: handlePurposeToggle,
1595
+ onPurposeCollapse: handlePurposeCollapse,
1596
+ onDataElementToggle: handleDataElementToggle,
1597
+ getTranslatedText,
1598
+ isAlreadyConsented,
1599
+ initialDataElementSelections,
1600
+ selectedLanguage
1601
+ },
1602
+ purpose.uuid
1603
+ );
1604
+ }) }),
1605
+ (activeConfig == null ? void 0 : activeConfig.dpo_info) ? /* @__PURE__ */ jsx2(View2, { style: { marginTop: 16, gap: 4 }, children: /* @__PURE__ */ jsxs(Text, { style: [styles2.privacyText, { fontSize: 13 }, (settings == null ? void 0 : settings.textColor) ? { color: settings.textColor } : null], children: [
1606
+ activeConfig.dpo_info.grievance_text,
1607
+ " ",
1608
+ /* @__PURE__ */ jsx2(
1609
+ Text,
1610
+ {
1611
+ style: styles2.link,
1612
+ onPress: () => Linking.openURL(activeConfig.dpo_info.grievance_url),
1613
+ children: activeConfig.dpo_info.grievance_anchor_text
1614
+ }
1615
+ )
1616
+ ] }) }) : null
1617
+ ] }),
1618
+ /* @__PURE__ */ jsx2(View2, { style: styles2.bottomSection, children: isReviewMode ? /* @__PURE__ */ jsx2(
1619
+ TouchableOpacity2,
1620
+ {
1621
+ style: [styles2.button, styles2.acceptButton, acceptButtonStyle],
1622
+ onPress: () => setIsReviewMode(false),
1623
+ children: /* @__PURE__ */ jsx2(Text, { style: [styles2.buttonText, styles2.acceptButtonText, acceptButtonTextStyle], children: reviewModeButtonText || "Update Consent" })
1624
+ }
1625
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1626
+ /* @__PURE__ */ jsx2(
1627
+ TouchableOpacity2,
1628
+ {
1629
+ style: [
1630
+ styles2.button,
1631
+ styles2.acceptButton,
1632
+ acceptButtonStyle,
1633
+ isSubmitting ? { opacity: 0.6 } : null
1634
+ ],
1635
+ onPress: handleAccept,
1636
+ disabled: isSubmitting,
1637
+ accessibilityLabel: isSubmitting ? "Submitting consent" : getTranslatedText("confirm_button_text", (activeConfig == null ? void 0 : activeConfig.confirm_button_text) || "Accept"),
1638
+ children: isSubmitting ? /* @__PURE__ */ jsx2(ActivityIndicator, { size: "small", color: ((_s = (_r = settings == null ? void 0 : settings.button) == null ? void 0 : _r.accept) == null ? void 0 : _s.textColor) || "#ffffff" }) : /* @__PURE__ */ jsx2(Text, { style: [styles2.buttonText, styles2.acceptButtonText, acceptButtonTextStyle], children: getTranslatedText("confirm_button_text", (activeConfig == null ? void 0 : activeConfig.confirm_button_text) || "Accept") })
1639
+ }
1640
+ ),
1641
+ !blockUI && /* @__PURE__ */ jsx2(
1642
+ TouchableOpacity2,
1643
+ {
1644
+ style: [styles2.button, styles2.cancelButton, declineButtonStyle],
1645
+ onPress: handleDecline,
1646
+ disabled: isSubmitting,
1647
+ accessibilityLabel: getTranslatedText("decline_button_text", (activeConfig == null ? void 0 : activeConfig.decline_button_text) || "Decline"),
1648
+ children: /* @__PURE__ */ jsx2(Text, { style: [styles2.buttonText, styles2.cancelButtonText, declineButtonTextStyle], children: getTranslatedText("decline_button_text", (activeConfig == null ? void 0 : activeConfig.decline_button_text) || "Decline") })
1649
+ }
1650
+ )
1651
+ ] }) })
1652
+ ] })
1653
+ }
1654
+ )
1655
+ ] })
1656
+ }
1657
+ );
1658
+ };
1659
+
1660
+ // src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx
1661
+ import {
1662
+ useCallback as useCallback2,
1663
+ useEffect as useEffect2,
1664
+ useMemo as useMemo2,
1665
+ useRef as useRef2,
1666
+ useState as useState2,
1667
+ memo
1668
+ } from "react";
1669
+ import {
1670
+ ActivityIndicator as ActivityIndicator2,
1671
+ ScrollView as ScrollView2,
1672
+ Text as Text2,
1673
+ TouchableOpacity as TouchableOpacity3,
1674
+ View as View3
1675
+ } from "react-native";
1676
+ import Svg3, { Path as Path3 } from "react-native-svg";
1677
+
1678
+ // src/RedactoNoticeConsentInline/api/index.ts
1679
+ var fetchConsentContent2 = async ({
1680
+ org_uuid,
1681
+ workspace_uuid,
1682
+ notice_uuid,
1683
+ baseUrl,
1684
+ language = "en",
1685
+ specific_uuid,
1686
+ signal
1687
+ }) => {
1688
+ const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
1689
+ const url = new URL(
1690
+ `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/notices/get-notice/${notice_uuid}`
1691
+ );
1692
+ if (specific_uuid) {
1693
+ url.searchParams.append("specific_uuid", specific_uuid);
1694
+ }
1695
+ const response = await fetch(url.toString(), {
1696
+ method: "GET",
1697
+ headers: {
1698
+ "Content-Type": "application/json",
1699
+ "Accept-Language": language
1700
+ },
1701
+ signal
1702
+ });
1703
+ if (!response.ok) {
1704
+ let errorMessage = `Failed to fetch consent content: ${response.status} ${response.statusText}`;
1705
+ if (response.status === 400) {
1706
+ errorMessage = "Invalid request data";
1707
+ } else if (response.status === 401) {
1708
+ errorMessage = "Unauthorized: Invalid or expired token";
1709
+ } else if (response.status === 403) {
1710
+ errorMessage = "Forbidden: Access denied";
1711
+ } else if (response.status === 404) {
1712
+ errorMessage = "Notice not found";
1713
+ } else if (response.status === 409) {
1714
+ errorMessage = "User has already provided consent";
1715
+ } else if (response.status >= 500) {
1716
+ errorMessage = "Server error: Please try again later";
1717
+ }
1718
+ const error = new Error(errorMessage);
1719
+ error.status = response.status;
1720
+ throw error;
1721
+ }
1722
+ return response.json();
1723
+ };
1724
+ var submitConsentEvent2 = async ({
1725
+ org_uuid,
1726
+ workspace_uuid,
1727
+ accessToken,
1728
+ baseUrl,
1729
+ noticeUuid,
1730
+ purposes,
1731
+ declined,
1732
+ meta_data
1733
+ }) => {
1734
+ var _a, _b;
1735
+ const apiBaseUrl = baseUrl || DEFAULT_CONSENT_API_BASE_URL;
1736
+ const payload = {
1737
+ notice_uuid: noticeUuid,
1738
+ purposes: purposes.map((purpose) => {
1739
+ if (!purpose.uuid) {
1740
+ throw new Error("Invalid purpose data: missing uuid");
1741
+ }
1742
+ const dataElements = purpose.data_elements.filter((element) => element.enabled).map((element) => ({
1743
+ uuid: element.uuid,
1744
+ selected: element.required ? true : Boolean(element.selected)
1745
+ }));
1746
+ return {
1747
+ purpose_uuid: purpose.uuid,
1748
+ selected: Boolean(purpose.selected),
1749
+ data_elements: dataElements.length > 0 ? dataElements : void 0
1750
+ };
1751
+ }),
1752
+ select_all_mandatory: false,
1753
+ source: "MOBILE",
1754
+ declined,
1755
+ meta_data
1756
+ };
1757
+ const response = await fetch(
1758
+ `${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/submit-consent`,
1759
+ {
1760
+ method: "POST",
1761
+ headers: {
1762
+ Authorization: `Bearer ${accessToken}`,
1763
+ "Content-Type": "application/json"
1764
+ },
1765
+ body: JSON.stringify(payload)
1766
+ }
1767
+ );
1768
+ if (!response.ok) {
1769
+ let errorData;
1770
+ try {
1771
+ errorData = await response.json();
1772
+ } catch {
1773
+ errorData = { message: response.statusText };
1774
+ }
1775
+ if (response.status === 409) {
1776
+ const error2 = new Error(
1777
+ ((_a = errorData.detail) == null ? void 0 : _a.message) || errorData.message || "User has already provided consent"
1778
+ );
1779
+ error2.status = 409;
1780
+ error2.code = errorData.code || 409;
1781
+ error2.detail = errorData.detail;
1782
+ throw error2;
1783
+ }
1784
+ const error = new Error(
1785
+ ((_b = errorData.detail) == null ? void 0 : _b.message) || errorData.message || `Failed to submit consent event: ${response.statusText}`
1786
+ );
1787
+ error.status = response.status;
1788
+ error.code = errorData.code;
1789
+ error.detail = errorData.detail;
1790
+ throw error;
1791
+ }
1792
+ };
1793
+
1794
+ // src/RedactoNoticeConsentInline/styles.ts
1795
+ import { StyleSheet as StyleSheet4, Platform as Platform2 } from "react-native";
1796
+ var styles3 = StyleSheet4.create({
1797
+ container: {
1798
+ width: "100%",
1799
+ borderRadius: 8,
1800
+ backgroundColor: "#ffffff",
1801
+ borderWidth: 1,
1802
+ borderColor: "#e5e7eb",
1803
+ ...Platform2.select({
1804
+ ios: {
1805
+ shadowColor: "#000",
1806
+ shadowOffset: { width: 0, height: 1 },
1807
+ shadowOpacity: 0.1,
1808
+ shadowRadius: 3
1809
+ },
1810
+ android: {
1811
+ elevation: 2
1812
+ }
1813
+ })
1814
+ },
1815
+ content: {
1816
+ padding: 22
1817
+ },
1818
+ topSection: {
1819
+ flexDirection: "row",
1820
+ justifyContent: "space-between",
1821
+ alignItems: "center",
1822
+ paddingBottom: 15,
1823
+ borderBottomWidth: 1,
1824
+ borderBottomColor: "#e5e7eb",
1825
+ backgroundColor: "#ffffff"
1826
+ },
1827
+ topLeft: {
1828
+ flexDirection: "row",
1829
+ alignItems: "center",
1830
+ gap: 10,
1831
+ flex: 1
1832
+ },
1833
+ logo: {
1834
+ height: 32,
1835
+ width: 80,
1836
+ resizeMode: "contain"
1837
+ },
1838
+ title: {
1839
+ fontSize: 18,
1840
+ fontWeight: "700",
1841
+ lineHeight: 27,
1842
+ letterSpacing: 0.2,
1843
+ color: "#101828",
1844
+ flexShrink: 1
1845
+ },
1846
+ topRight: {
1847
+ borderRadius: 8,
1848
+ paddingVertical: 3,
1849
+ paddingHorizontal: 9,
1850
+ borderWidth: 1,
1851
+ borderColor: "#d0d5dd",
1852
+ flexDirection: "row",
1853
+ alignItems: "center",
1854
+ justifyContent: "center",
1855
+ backgroundColor: "#ffffff"
1856
+ },
1857
+ topRightText: {
1858
+ fontWeight: "400",
1859
+ fontSize: 12,
1860
+ color: "#344054"
1861
+ },
1862
+ middleSection: {
1863
+ marginTop: 20,
1864
+ gap: 12
1865
+ },
1866
+ privacyText: {
1867
+ fontSize: 16,
1868
+ fontWeight: "400",
1869
+ lineHeight: 24,
1870
+ letterSpacing: 0.2,
1871
+ color: "#344054"
1872
+ },
1873
+ link: {
1874
+ color: "#4f87ff",
1875
+ textDecorationLine: "underline"
1876
+ },
1877
+ subTitle: {
1878
+ fontWeight: "600",
1879
+ fontSize: 16,
1880
+ lineHeight: 24,
1881
+ letterSpacing: 0.2,
1882
+ color: "#101828"
1883
+ },
1884
+ optionsContainer: {
1885
+ gap: 14
1886
+ },
1887
+ optionItem: {
1888
+ flexDirection: "row",
1889
+ justifyContent: "space-between",
1890
+ alignItems: "center"
1891
+ },
1892
+ optionLeft: {
1893
+ flexDirection: "row",
1894
+ gap: 12,
1895
+ alignItems: "center",
1896
+ flex: 1
1897
+ },
1898
+ optionTextContainer: {
1899
+ flex: 1
1900
+ },
1901
+ optionTitle: {
1902
+ fontWeight: "500",
1903
+ fontSize: 16,
1904
+ lineHeight: 24,
1905
+ letterSpacing: 0.2,
1906
+ color: "#101828"
1907
+ },
1908
+ optionDescription: {
1909
+ fontWeight: "400",
1910
+ fontSize: 12,
1911
+ lineHeight: 18,
1912
+ letterSpacing: 0.2,
1913
+ color: "#475467"
1914
+ },
1915
+ dataElementsContainer: {
1916
+ paddingLeft: 22,
1917
+ marginTop: 4
1918
+ },
1919
+ dataElementItem: {
1920
+ flexDirection: "row",
1921
+ alignItems: "center",
1922
+ gap: 10,
1923
+ justifyContent: "space-between",
1924
+ minHeight: 24,
1925
+ paddingVertical: 2
1926
+ },
1927
+ dataElementText: {
1928
+ fontWeight: "400",
1929
+ fontSize: 14,
1930
+ lineHeight: 21,
1931
+ letterSpacing: 0.2,
1932
+ color: "#344054",
1933
+ flex: 1
1934
+ },
1935
+ bottomSection: {
1936
+ flexDirection: "row",
1937
+ justifyContent: "space-between",
1938
+ gap: 12,
1939
+ paddingTop: 15,
1940
+ marginTop: 16,
1941
+ borderTopWidth: 1,
1942
+ borderTopColor: "#e5e7eb",
1943
+ backgroundColor: "#ffffff"
1944
+ },
1945
+ button: {
1946
+ flex: 1,
1947
+ borderRadius: 8,
1948
+ paddingVertical: 12,
1949
+ paddingHorizontal: 20,
1950
+ alignItems: "center",
1951
+ justifyContent: "center"
1952
+ },
1953
+ buttonText: {
1954
+ fontWeight: "400",
1955
+ fontSize: 16,
1956
+ lineHeight: 24,
1957
+ textAlign: "center"
1958
+ },
1959
+ acceptButton: {
1960
+ backgroundColor: "#4f87ff"
1961
+ },
1962
+ acceptButtonText: {
1963
+ color: "#ffffff"
1964
+ },
1965
+ cancelButton: {
1966
+ borderWidth: 1,
1967
+ borderColor: "#d0d5dd",
1968
+ backgroundColor: "#ffffff"
1969
+ },
1970
+ cancelButtonText: {
1971
+ color: "#000000"
1972
+ },
1973
+ loadingContainer: {
1974
+ alignItems: "center",
1975
+ justifyContent: "center",
1976
+ padding: 32,
1977
+ minHeight: 200
1978
+ },
1979
+ loadingText: {
1980
+ marginTop: 16,
1981
+ fontSize: 16,
1982
+ color: "#344054"
1983
+ },
1984
+ errorContainer: {
1985
+ backgroundColor: "#FEE2E2",
1986
+ borderRadius: 6,
1987
+ padding: 12,
1988
+ marginBottom: 12,
1989
+ borderWidth: 1,
1990
+ borderColor: "#FCA5A5"
1991
+ },
1992
+ errorText: {
1993
+ color: "#DC2626",
1994
+ fontSize: 14
1995
+ },
1996
+ requiredAsterisk: {
1997
+ color: "red"
1998
+ },
1999
+ purposeChevron: {
2000
+ marginLeft: 5,
2001
+ alignItems: "center",
2002
+ justifyContent: "center"
2003
+ },
2004
+ languageSelectorContainer: {
2005
+ position: "relative"
2006
+ },
2007
+ languageDropdown: {
2008
+ position: "absolute",
2009
+ top: "100%",
2010
+ right: 0,
2011
+ backgroundColor: "#ffffff",
2012
+ borderWidth: 1,
2013
+ borderColor: "#d0d5dd",
2014
+ borderRadius: 4,
2015
+ zIndex: 10,
2016
+ minWidth: 120,
2017
+ maxHeight: 200,
2018
+ marginTop: 4,
2019
+ ...Platform2.select({
2020
+ ios: {
2021
+ shadowColor: "#000",
2022
+ shadowOffset: { width: 0, height: 2 },
2023
+ shadowOpacity: 0.1,
2024
+ shadowRadius: 4
2025
+ },
2026
+ android: {
2027
+ elevation: 4
2028
+ }
2029
+ })
2030
+ },
2031
+ languageItem: {
2032
+ paddingVertical: 8,
2033
+ paddingHorizontal: 12,
2034
+ borderBottomWidth: 1,
2035
+ borderBottomColor: "#e5e7eb"
2036
+ },
2037
+ languageItemText: {
2038
+ fontSize: 12,
2039
+ color: "#344054"
2040
+ },
2041
+ selectedLanguageItem: {
2042
+ backgroundColor: "#f3f4f6"
2043
+ },
2044
+ selectedLanguageItemText: {
2045
+ fontWeight: "600"
2046
+ }
2047
+ });
2048
+
2049
+ // src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx
2050
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2051
+ var RedactoConsentInlineComponent = ({
2052
+ org_uuid,
2053
+ workspace_uuid,
2054
+ notice_uuid,
2055
+ accessToken,
2056
+ baseUrl,
2057
+ language = "en",
2058
+ onAccept,
2059
+ onDecline: _onDecline,
2060
+ onError,
2061
+ onValidationChange,
2062
+ settings,
2063
+ applicationId
2064
+ }) => {
2065
+ var _a, _b, _c, _d;
2066
+ const { isMobile } = useResponsive();
2067
+ const [content, setContent] = useState2(null);
2068
+ const [isLoading, setIsLoading] = useState2(true);
2069
+ const [isSubmitting, setIsSubmitting] = useState2(false);
2070
+ const [selectedLanguage, setSelectedLanguage] = useState2(language);
2071
+ const [collapsedPurposes, setCollapsedPurposes] = useState2({});
2072
+ const [selectedPurposes, setSelectedPurposes] = useState2({});
2073
+ const [selectedDataElements, setSelectedDataElements] = useState2({});
2074
+ const [hasAlreadyConsented, setHasAlreadyConsented] = useState2(false);
2075
+ const [fetchError, setFetchError] = useState2(null);
2076
+ const [errorMessage, setErrorMessage] = useState2(null);
2077
+ const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = useState2(false);
2078
+ const hasSubmittedRef = useRef2(false);
2079
+ const getTranslatedText = useCallback2(
2080
+ (key, defaultText, itemId) => {
2081
+ var _a2, _b2, _c2;
2082
+ if (!content) return defaultText;
2083
+ if (["en", "EN", "English"].includes(selectedLanguage)) {
2084
+ if (key === "privacy_policy_anchor_text" && content.privacy_policy_anchor_text) {
2085
+ return content.privacy_policy_anchor_text;
2086
+ }
2087
+ return defaultText;
2088
+ }
2089
+ const translationMap = (_a2 = content.supported_languages_and_translations) == null ? void 0 : _a2[selectedLanguage];
2090
+ if (!translationMap) return defaultText;
2091
+ if (itemId) {
2092
+ if (key === "purposes.name" || key === "purposes.description") {
2093
+ const purposeData = (_b2 = translationMap.purposes) == null ? void 0 : _b2[itemId];
2094
+ if (purposeData) {
2095
+ if (typeof purposeData === "string") {
2096
+ return key === "purposes.name" ? purposeData : defaultText;
2097
+ }
2098
+ return key === "purposes.name" ? purposeData.name || defaultText : purposeData.description || defaultText;
2099
+ }
2100
+ return defaultText;
2101
+ }
2102
+ if (key.startsWith("data_elements.")) {
2103
+ return ((_c2 = translationMap.data_elements) == null ? void 0 : _c2[itemId]) || defaultText;
2104
+ }
2105
+ }
2106
+ const value = translationMap[key];
2107
+ if (typeof value === "string" && value) return value;
2108
+ return defaultText;
2109
+ },
2110
+ [content, selectedLanguage]
2111
+ );
2112
+ const areAllRequiredElementsChecked = useMemo2(() => {
2113
+ if (!content) return false;
2114
+ return content.purposes.every((purpose) => {
2115
+ const requiredElements = purpose.data_elements.filter((el) => el.required);
2116
+ return requiredElements.every(
2117
+ (el) => selectedDataElements[`${purpose.uuid}-${el.uuid}`]
2118
+ );
2119
+ });
2120
+ }, [content, selectedDataElements]);
2121
+ useEffect2(() => {
2122
+ onValidationChange == null ? void 0 : onValidationChange(areAllRequiredElementsChecked);
2123
+ }, [areAllRequiredElementsChecked, onValidationChange]);
2124
+ const acceptDisabled = isSubmitting || !areAllRequiredElementsChecked || !accessToken;
2125
+ useEffect2(() => {
2126
+ if (hasAlreadyConsented) {
2127
+ onAccept == null ? void 0 : onAccept();
2128
+ }
2129
+ }, [hasAlreadyConsented, onAccept]);
2130
+ const fetchNotice = useCallback2(async (signal) => {
2131
+ setIsLoading(true);
2132
+ setFetchError(null);
2133
+ try {
2134
+ const consentContentData = await fetchConsentContent2({
2135
+ org_uuid,
2136
+ workspace_uuid,
2137
+ notice_uuid,
2138
+ baseUrl,
2139
+ language,
2140
+ specific_uuid: applicationId,
2141
+ signal
2142
+ });
2143
+ if (signal == null ? void 0 : signal.aborted) {
2144
+ return;
2145
+ }
2146
+ const activeConfig = consentContentData.detail.active_config;
2147
+ setContent(activeConfig);
2148
+ if (activeConfig.default_language) {
2149
+ setSelectedLanguage(activeConfig.default_language);
2150
+ }
2151
+ const initialCollapsedState = activeConfig.purposes.reduce(
2152
+ (acc, purpose) => ({ ...acc, [purpose.uuid]: true }),
2153
+ {}
2154
+ );
2155
+ setCollapsedPurposes(initialCollapsedState);
2156
+ const initialDataElementState = activeConfig.purposes.reduce(
2157
+ (acc, purpose) => {
2158
+ purpose.data_elements.forEach((el) => {
2159
+ acc[`${purpose.uuid}-${el.uuid}`] = false;
2160
+ });
2161
+ return acc;
2162
+ },
2163
+ {}
2164
+ );
2165
+ setSelectedDataElements(initialDataElementState);
2166
+ const initialPurposeState = activeConfig.purposes.reduce(
2167
+ (acc, purpose) => ({ ...acc, [purpose.uuid]: false }),
2168
+ {}
2169
+ );
2170
+ setSelectedPurposes(initialPurposeState);
2171
+ } catch (err) {
2172
+ if ((signal == null ? void 0 : signal.aborted) || err instanceof Error && err.name === "AbortError") {
2173
+ return;
2174
+ }
2175
+ const error = err;
2176
+ setFetchError(error);
2177
+ if (error.status === 409) {
2178
+ setHasAlreadyConsented(true);
2179
+ } else {
2180
+ onError == null ? void 0 : onError(error);
2181
+ }
2182
+ } finally {
2183
+ if (!(signal == null ? void 0 : signal.aborted)) {
2184
+ setIsLoading(false);
2185
+ }
2186
+ }
2187
+ }, [org_uuid, workspace_uuid, notice_uuid, baseUrl, language, applicationId, onError]);
2188
+ useEffect2(() => {
2189
+ const controller = new AbortController();
2190
+ void fetchNotice(controller.signal);
2191
+ return () => {
2192
+ controller.abort();
2193
+ };
2194
+ }, [fetchNotice]);
2195
+ const togglePurposeCollapse = useCallback2((purposeUuid) => {
2196
+ setCollapsedPurposes((prev) => ({ ...prev, [purposeUuid]: !prev[purposeUuid] }));
2197
+ }, []);
2198
+ const handlePurposeCheckboxChange = useCallback2(
2199
+ (purposeUuid) => {
2200
+ if (!content) return;
2201
+ const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
2202
+ if (!purpose) return;
2203
+ const newPurposeState = !selectedPurposes[purposeUuid];
2204
+ setSelectedPurposes((prev) => ({ ...prev, [purposeUuid]: newPurposeState }));
2205
+ setSelectedDataElements((prev) => {
2206
+ const updated = { ...prev };
2207
+ purpose.data_elements.forEach((el) => {
2208
+ updated[`${purposeUuid}-${el.uuid}`] = newPurposeState;
2209
+ });
2210
+ return updated;
2211
+ });
2212
+ },
2213
+ [content, selectedPurposes]
2214
+ );
2215
+ const handleDataElementCheckboxChange = useCallback2(
2216
+ (elementUuid, purposeUuid) => {
2217
+ if (!content) return;
2218
+ const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
2219
+ if (!purpose) return;
2220
+ const combinedId = `${purposeUuid}-${elementUuid}`;
2221
+ setSelectedDataElements((prev) => {
2222
+ const newState = { ...prev, [combinedId]: !prev[combinedId] };
2223
+ const requiredElements = purpose.data_elements.filter((el) => el.required);
2224
+ const shouldCheckPurpose = requiredElements.length > 0 ? requiredElements.every((el) => newState[`${purposeUuid}-${el.uuid}`]) : purpose.data_elements.some((el) => newState[`${purposeUuid}-${el.uuid}`]);
2225
+ setSelectedPurposes((prev2) => ({ ...prev2, [purposeUuid]: shouldCheckPurpose }));
2226
+ return newState;
2227
+ });
2228
+ },
2229
+ [content]
2230
+ );
2231
+ const handleAcceptWithToken = useCallback2(
2232
+ async (tokenToUse) => {
2233
+ if (!tokenToUse || !content) {
2234
+ setErrorMessage("Unable to submit consent. Please try again.");
2235
+ throw new Error("Access token or content not available");
2236
+ }
2237
+ try {
2238
+ setIsSubmitting(true);
2239
+ setErrorMessage(null);
2240
+ await submitConsentEvent2({
2241
+ org_uuid,
2242
+ workspace_uuid,
2243
+ accessToken: tokenToUse,
2244
+ baseUrl,
2245
+ noticeUuid: content.notice_uuid,
2246
+ purposes: content.purposes.map((purpose) => {
2247
+ const hasSelectedRequiredElement = purpose.data_elements.some(
2248
+ (el) => el.required && (selectedDataElements[`${purpose.uuid}-${el.uuid}`] || false)
2249
+ );
2250
+ const hasSelectedDataElement = purpose.data_elements.some(
2251
+ (el) => selectedDataElements[`${purpose.uuid}-${el.uuid}`] || false
2252
+ );
2253
+ const purposeSelected = selectedPurposes[purpose.uuid] || hasSelectedRequiredElement || hasSelectedDataElement;
2254
+ return {
2255
+ ...purpose,
2256
+ selected: purposeSelected,
2257
+ data_elements: purpose.data_elements.map((el) => ({
2258
+ ...el,
2259
+ selected: selectedDataElements[`${purpose.uuid}-${el.uuid}`] || false
2260
+ }))
2261
+ };
2262
+ }),
2263
+ declined: false,
2264
+ meta_data: applicationId ? { specific_uuid: applicationId } : void 0
2265
+ });
2266
+ onAccept == null ? void 0 : onAccept();
2267
+ hasSubmittedRef.current = false;
2268
+ } catch (error) {
2269
+ const err = error;
2270
+ 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.";
2271
+ setErrorMessage(message);
2272
+ hasSubmittedRef.current = false;
2273
+ throw error;
2274
+ } finally {
2275
+ setIsSubmitting(false);
2276
+ }
2277
+ },
2278
+ [
2279
+ content,
2280
+ org_uuid,
2281
+ workspace_uuid,
2282
+ baseUrl,
2283
+ selectedDataElements,
2284
+ selectedPurposes,
2285
+ applicationId,
2286
+ onAccept
2287
+ ]
2288
+ );
2289
+ useEffect2(() => {
2290
+ if (accessToken && content && areAllRequiredElementsChecked && !isSubmitting && !hasSubmittedRef.current) {
2291
+ hasSubmittedRef.current = true;
2292
+ const autoSubmit = async () => {
2293
+ try {
2294
+ await handleAcceptWithToken(accessToken);
2295
+ } catch (err) {
2296
+ hasSubmittedRef.current = false;
2297
+ onError == null ? void 0 : onError(err);
2298
+ }
2299
+ };
2300
+ autoSubmit();
2301
+ }
2302
+ }, [accessToken, content, areAllRequiredElementsChecked, isSubmitting, handleAcceptWithToken, onError]);
2303
+ useEffect2(() => {
2304
+ if (!accessToken) {
2305
+ hasSubmittedRef.current = false;
2306
+ }
2307
+ }, [accessToken]);
2308
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || "#101828";
2309
+ const textColor = (settings == null ? void 0 : settings.textColor) || "#344054";
2310
+ const supportedLanguages = useMemo2(
2311
+ () => Object.keys((content == null ? void 0 : content.supported_languages_and_translations) || {}),
2312
+ [content]
2313
+ );
2314
+ if (hasAlreadyConsented || fetchError) return null;
2315
+ if (isLoading) {
2316
+ return /* @__PURE__ */ jsxs2(View3, { style: styles3.loadingContainer, children: [
2317
+ /* @__PURE__ */ jsx3(ActivityIndicator2, { size: "large", color: "#4f87ff" }),
2318
+ /* @__PURE__ */ jsx3(Text2, { style: [styles3.loadingText, { color: textColor }], children: "Loading..." })
2319
+ ] });
2320
+ }
2321
+ if (!content) return null;
2322
+ return /* @__PURE__ */ jsxs2(View3, { style: { gap: 16 }, children: [
2323
+ errorMessage ? /* @__PURE__ */ jsx3(View3, { style: styles3.errorContainer, children: /* @__PURE__ */ jsx3(Text2, { style: styles3.errorText, children: errorMessage }) }) : null,
2324
+ supportedLanguages.length > 0 && /* @__PURE__ */ jsxs2(View3, { style: [styles3.topSection, { borderBottomWidth: 0, paddingBottom: 0 }], children: [
2325
+ /* @__PURE__ */ jsx3(View3, {}),
2326
+ /* @__PURE__ */ jsxs2(View3, { style: styles3.languageSelectorContainer, children: [
2327
+ /* @__PURE__ */ jsxs2(
2328
+ TouchableOpacity3,
2329
+ {
2330
+ style: [
2331
+ styles3.topRight,
2332
+ ((_a = settings == null ? void 0 : settings.button) == null ? void 0 : _a.language) ? { backgroundColor: settings.button.language.backgroundColor, borderColor: settings.button.language.backgroundColor } : null
2333
+ ],
2334
+ onPress: () => setIsLanguageDropdownOpen((v) => !v),
2335
+ children: [
2336
+ /* @__PURE__ */ jsx3(Text2, { style: [styles3.topRightText, ((_b = settings == null ? void 0 : settings.button) == null ? void 0 : _b.language) ? { color: settings.button.language.textColor } : null], children: selectedLanguage }),
2337
+ /* @__PURE__ */ jsx3(Svg3, { width: 12, height: 12, viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx3(
2338
+ Path3,
2339
+ {
2340
+ d: "M4 6L8 10L12 6",
2341
+ stroke: ((_d = (_c = settings == null ? void 0 : settings.button) == null ? void 0 : _c.language) == null ? void 0 : _d.textColor) || "#344054",
2342
+ strokeWidth: 2,
2343
+ strokeLinecap: "round",
2344
+ strokeLinejoin: "round"
2345
+ }
2346
+ ) })
2347
+ ]
2348
+ }
2349
+ ),
2350
+ isLanguageDropdownOpen && /* @__PURE__ */ jsx3(View3, { style: styles3.languageDropdown, children: /* @__PURE__ */ jsx3(ScrollView2, { children: supportedLanguages.map((lang) => /* @__PURE__ */ jsx3(
2351
+ TouchableOpacity3,
2352
+ {
2353
+ style: [
2354
+ styles3.languageItem,
2355
+ lang === selectedLanguage ? styles3.selectedLanguageItem : null
2356
+ ],
2357
+ onPress: () => {
2358
+ setSelectedLanguage(lang);
2359
+ setIsLanguageDropdownOpen(false);
2360
+ },
2361
+ children: /* @__PURE__ */ jsx3(Text2, { style: [styles3.languageItemText, lang === selectedLanguage ? styles3.selectedLanguageItemText : null], children: lang })
2362
+ },
2363
+ lang
2364
+ )) }) })
2365
+ ] })
2366
+ ] }),
2367
+ content.purposes.map((purpose) => /* @__PURE__ */ jsxs2(View3, { children: [
2368
+ /* @__PURE__ */ jsxs2(View3, { style: styles3.optionItem, children: [
2369
+ /* @__PURE__ */ jsxs2(
2370
+ TouchableOpacity3,
2371
+ {
2372
+ style: styles3.optionLeft,
2373
+ onPress: () => togglePurposeCollapse(purpose.uuid),
2374
+ accessibilityRole: "button",
2375
+ accessibilityLabel: `${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)}`,
2376
+ children: [
2377
+ /* @__PURE__ */ jsx3(View3, { style: styles3.purposeChevron, children: /* @__PURE__ */ jsx3(Svg3, { width: 7, height: 12, viewBox: "0 0 7 12", fill: "none", children: /* @__PURE__ */ jsx3(
2378
+ Path3,
2379
+ {
2380
+ d: "M1 1L6 6L1 11",
2381
+ stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
2382
+ strokeWidth: 2,
2383
+ strokeLinecap: "round",
2384
+ strokeLinejoin: "round",
2385
+ transform: !collapsedPurposes[purpose.uuid] ? "rotate(90 3.5 6)" : void 0
2386
+ }
2387
+ ) }) }),
2388
+ /* @__PURE__ */ jsxs2(View3, { style: styles3.optionTextContainer, children: [
2389
+ /* @__PURE__ */ jsx3(Text2, { style: [styles3.optionTitle, { color: headingColor }, isMobile ? { fontSize: 14 } : null], children: getTranslatedText("purposes.name", purpose.name, purpose.uuid) }),
2390
+ /* @__PURE__ */ jsx3(Text2, { style: [styles3.optionDescription, { color: textColor }, isMobile ? { fontSize: 11 } : null], children: getTranslatedText("purposes.description", purpose.description, purpose.uuid) })
2391
+ ] })
2392
+ ]
2393
+ }
2394
+ ),
2395
+ /* @__PURE__ */ jsx3(
2396
+ Checkbox,
2397
+ {
2398
+ checked: selectedPurposes[purpose.uuid] || false,
2399
+ onChange: () => handlePurposeCheckboxChange(purpose.uuid),
2400
+ size: "large",
2401
+ accessibilityLabel: `Select all data elements for ${getTranslatedText("purposes.name", purpose.name, purpose.uuid)}`
2402
+ }
2403
+ )
2404
+ ] }),
2405
+ !collapsedPurposes[purpose.uuid] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ jsx3(View3, { style: styles3.dataElementsContainer, children: purpose.data_elements.map((dataElement) => /* @__PURE__ */ jsxs2(View3, { style: styles3.dataElementItem, children: [
2406
+ /* @__PURE__ */ jsxs2(Text2, { style: [styles3.dataElementText, { color: textColor }, isMobile ? { fontSize: 12 } : null], children: [
2407
+ getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid),
2408
+ dataElement.required && /* @__PURE__ */ jsx3(Text2, { style: styles3.requiredAsterisk, children: " *" })
2409
+ ] }),
2410
+ /* @__PURE__ */ jsx3(
2411
+ Checkbox,
2412
+ {
2413
+ checked: selectedDataElements[`${purpose.uuid}-${dataElement.uuid}`] || false,
2414
+ onChange: () => handleDataElementCheckboxChange(dataElement.uuid, purpose.uuid),
2415
+ size: "small",
2416
+ accessibilityLabel: `Select ${getTranslatedText("data_elements.name", dataElement.name, dataElement.uuid)}${dataElement.required ? " (required)" : ""}`
2417
+ }
2418
+ )
2419
+ ] }, dataElement.uuid)) })
2420
+ ] }, purpose.uuid))
2421
+ ] });
2422
+ };
2423
+ RedactoConsentInlineComponent.displayName = "RedactoConsentInline";
2424
+ var RedactoConsentInline = memo(RedactoConsentInlineComponent);
2425
+ export {
2426
+ RedactoNoticeConsent,
2427
+ RedactoConsentInline as RedactoNoticeConsentInline
2428
+ };