@redacto.io/consent-sdk-react 3.2.0 → 3.4.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/CHANGELOG.md +12 -0
- package/README.md +11 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +423 -136
- package/dist/index.mjs +423 -136
- package/package.json +1 -1
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +123 -1
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +936 -671
- package/src/RedactoNoticeConsent/api/index.ts +67 -53
- package/src/RedactoNoticeConsent/api/types.ts +67 -13
- package/src/RedactoNoticeConsent/constants.ts +20 -0
- package/src/RedactoNoticeConsent/styles.ts +3 -6
- package/src/RedactoNoticeConsent/types.ts +11 -5
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +30 -26
- package/src/RedactoNoticeConsentInline/api/index.ts +65 -27
- package/src/RedactoNoticeConsentInline/api/types.ts +28 -11
- package/src/RedactoNoticeConsentInline/types.ts +9 -4
- package/src/types/react-jsx.d.ts +26 -0
- package/src/utils/isLegacyApi.ts +12 -0
- package/tests/mocks.ts +53 -7
- package/tsconfig.json +1 -5
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* collection and reconsent flows with visual distinction between already-consented
|
|
6
6
|
* and needs-consent purposes.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
// @ts-nocheck - React 19 @types/react has Element/ReactNode assignability issues; tsc passes
|
|
9
9
|
// React imports
|
|
10
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
11
|
|
|
12
12
|
// API imports
|
|
13
13
|
import {
|
|
@@ -29,6 +29,9 @@ import type { Props, TranslationObject } from "./types";
|
|
|
29
29
|
// Asset imports
|
|
30
30
|
import logo from "./assets/redacto-logo.png";
|
|
31
31
|
|
|
32
|
+
// Constants
|
|
33
|
+
import { DPO_AUDIO_ELEMENT_MAP } from "./constants";
|
|
34
|
+
|
|
32
35
|
// Utility imports
|
|
33
36
|
import { injectCheckboxStyles } from "./injectStyles";
|
|
34
37
|
import { styles } from "./styles";
|
|
@@ -65,284 +68,287 @@ const PurposeItem: React.FC<
|
|
|
65
68
|
selectedLanguage,
|
|
66
69
|
initialDataElementSelections = {},
|
|
67
70
|
}) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const headingStyle = useMemo(
|
|
72
|
+
() => ({
|
|
73
|
+
color: settings?.headingColor || "#323B4B",
|
|
74
|
+
fontFamily: settings?.font || "inherit",
|
|
75
|
+
}),
|
|
76
|
+
[settings]
|
|
77
|
+
);
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
const textStyle = useMemo(
|
|
80
|
+
() => ({
|
|
81
|
+
color: settings?.textColor || "#344054",
|
|
82
|
+
fontFamily: settings?.font || "inherit",
|
|
83
|
+
}),
|
|
84
|
+
[settings]
|
|
85
|
+
);
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<div
|
|
88
|
-
style={styles.optionLeft}
|
|
89
|
-
onClick={() => onPurposeCollapse(purpose.uuid)}
|
|
90
|
-
role="button"
|
|
91
|
-
tabIndex={0}
|
|
92
|
-
aria-expanded={!collapsedPurposes[purpose.uuid]}
|
|
93
|
-
aria-controls={`purpose-${purpose.uuid}`}
|
|
94
|
-
aria-label={`${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText(
|
|
95
|
-
`purposes.name`,
|
|
96
|
-
purpose.name,
|
|
97
|
-
purpose.uuid
|
|
98
|
-
)} details`}
|
|
99
|
-
onKeyDown={(e) => {
|
|
100
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
onPurposeCollapse(purpose.uuid);
|
|
103
|
-
}
|
|
104
|
-
}}
|
|
105
|
-
>
|
|
87
|
+
return (
|
|
88
|
+
<div key={purpose.uuid}>
|
|
89
|
+
<div style={styles.optionItem}>
|
|
106
90
|
<div
|
|
107
|
-
style={
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
91
|
+
style={styles.optionLeft}
|
|
92
|
+
onClick={() => onPurposeCollapse(purpose.uuid)}
|
|
93
|
+
role="button"
|
|
94
|
+
tabIndex={0}
|
|
95
|
+
aria-expanded={!collapsedPurposes[purpose.uuid]}
|
|
96
|
+
aria-controls={`purpose-${purpose.uuid}`}
|
|
97
|
+
aria-label={`${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText(
|
|
98
|
+
`purposes.name`,
|
|
99
|
+
purpose.name,
|
|
100
|
+
purpose.uuid
|
|
101
|
+
)} details`}
|
|
102
|
+
onKeyDown={(e) => {
|
|
103
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
onPurposeCollapse(purpose.uuid);
|
|
106
|
+
}
|
|
112
107
|
}}
|
|
113
108
|
>
|
|
114
|
-
<
|
|
115
|
-
width="7"
|
|
116
|
-
height="12"
|
|
117
|
-
viewBox="0 0 7 12"
|
|
118
|
-
fill="none"
|
|
119
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
120
|
-
stroke={settings?.headingColor || "#323B4B"}
|
|
121
|
-
strokeWidth="2"
|
|
122
|
-
strokeLinecap="round"
|
|
123
|
-
strokeLinejoin="round"
|
|
109
|
+
<div
|
|
124
110
|
style={{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
111
|
+
display: "flex",
|
|
112
|
+
alignItems: "center",
|
|
113
|
+
justifyContent: "center",
|
|
114
|
+
marginLeft: "5px",
|
|
129
115
|
}}
|
|
130
|
-
aria-hidden="true"
|
|
131
116
|
>
|
|
132
|
-
<
|
|
133
|
-
|
|
117
|
+
<svg
|
|
118
|
+
width="7"
|
|
119
|
+
height="12"
|
|
120
|
+
viewBox="0 0 7 12"
|
|
121
|
+
fill="none"
|
|
122
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
123
|
+
stroke={settings?.headingColor || "#323B4B"}
|
|
124
|
+
strokeWidth="2"
|
|
125
|
+
strokeLinecap={"round" as const}
|
|
126
|
+
strokeLinejoin={"round" as const}
|
|
127
|
+
style={{
|
|
128
|
+
transform: !collapsedPurposes[purpose.uuid]
|
|
129
|
+
? "rotate(90deg)"
|
|
130
|
+
: "rotate(0deg)",
|
|
131
|
+
transition: "transform 0.3s ease",
|
|
132
|
+
}}
|
|
133
|
+
aria-hidden="true"
|
|
134
|
+
>
|
|
135
|
+
<path d="M1 1L6 6L1 11" />
|
|
136
|
+
</svg>
|
|
137
|
+
</div>
|
|
138
|
+
<div style={styles.optionTextContainer}>
|
|
139
|
+
<h2
|
|
140
|
+
key={`purpose-name-${purpose.uuid}-${selectedLanguage}`}
|
|
141
|
+
id={`purpose-name-${purpose.uuid}`}
|
|
142
|
+
style={{
|
|
143
|
+
...styles.optionTitle,
|
|
144
|
+
...responsiveStyles.optionTitle,
|
|
145
|
+
...headingStyle,
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{getTranslatedText(`purposes.name`, purpose.name, purpose.uuid)}
|
|
149
|
+
</h2>
|
|
150
|
+
<h3
|
|
151
|
+
key={`purpose-description-${purpose.uuid}-${selectedLanguage}`}
|
|
152
|
+
id={`purpose-description-${purpose.uuid}`}
|
|
153
|
+
style={{
|
|
154
|
+
...styles.optionDescription,
|
|
155
|
+
...responsiveStyles.optionDescription,
|
|
156
|
+
...textStyle,
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
{getTranslatedText(
|
|
160
|
+
`purposes.description`,
|
|
161
|
+
purpose.description,
|
|
162
|
+
purpose.uuid
|
|
163
|
+
)}
|
|
164
|
+
</h3>
|
|
165
|
+
</div>
|
|
134
166
|
</div>
|
|
135
|
-
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
{!isAlreadyConsented && (
|
|
168
|
+
<input
|
|
169
|
+
className="redacto-checkbox-large"
|
|
170
|
+
type="checkbox"
|
|
171
|
+
checked={selectedPurposes[purpose.uuid] || false}
|
|
172
|
+
onChange={() => onPurposeToggle(purpose.uuid)}
|
|
173
|
+
aria-label={`Select all data elements for ${getTranslatedText(
|
|
174
|
+
`purposes.name`,
|
|
175
|
+
purpose.name,
|
|
176
|
+
purpose.uuid
|
|
177
|
+
)}`}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
{isAlreadyConsented && (
|
|
181
|
+
<div
|
|
150
182
|
style={{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
183
|
+
display: "flex",
|
|
184
|
+
alignItems: "center",
|
|
185
|
+
justifyContent: "center",
|
|
186
|
+
width: "20px",
|
|
187
|
+
height: "20px",
|
|
188
|
+
borderRadius: "50%",
|
|
189
|
+
backgroundColor: "#10B981",
|
|
190
|
+
marginLeft: "12px",
|
|
154
191
|
}}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
purpose.description,
|
|
192
|
+
aria-label={`Already consented to ${getTranslatedText(
|
|
193
|
+
`purposes.name`,
|
|
194
|
+
purpose.name,
|
|
159
195
|
purpose.uuid
|
|
160
|
-
)}
|
|
161
|
-
</h3>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
{!isAlreadyConsented && (
|
|
165
|
-
<input
|
|
166
|
-
className="redacto-checkbox-large"
|
|
167
|
-
type="checkbox"
|
|
168
|
-
checked={selectedPurposes[purpose.uuid] || false}
|
|
169
|
-
onChange={() => onPurposeToggle(purpose.uuid)}
|
|
170
|
-
aria-label={`Select all data elements for ${getTranslatedText(
|
|
171
|
-
`purposes.name`,
|
|
172
|
-
purpose.name,
|
|
173
|
-
purpose.uuid
|
|
174
|
-
)}`}
|
|
175
|
-
/>
|
|
176
|
-
)}
|
|
177
|
-
{isAlreadyConsented && (
|
|
178
|
-
<div
|
|
179
|
-
style={{
|
|
180
|
-
display: "flex",
|
|
181
|
-
alignItems: "center",
|
|
182
|
-
justifyContent: "center",
|
|
183
|
-
width: "20px",
|
|
184
|
-
height: "20px",
|
|
185
|
-
borderRadius: "50%",
|
|
186
|
-
backgroundColor: "#10B981",
|
|
187
|
-
marginLeft: "12px",
|
|
188
|
-
}}
|
|
189
|
-
aria-label={`Already consented to ${getTranslatedText(
|
|
190
|
-
`purposes.name`,
|
|
191
|
-
purpose.name,
|
|
192
|
-
purpose.uuid
|
|
193
|
-
)}`}
|
|
194
|
-
>
|
|
195
|
-
<svg
|
|
196
|
-
width="12"
|
|
197
|
-
height="12"
|
|
198
|
-
viewBox="0 0 12 12"
|
|
199
|
-
fill="none"
|
|
200
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
201
|
-
aria-hidden="true"
|
|
196
|
+
)}`}
|
|
202
197
|
>
|
|
203
|
-
<
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
</svg>
|
|
211
|
-
</div>
|
|
212
|
-
)}
|
|
213
|
-
</div>
|
|
214
|
-
{!collapsedPurposes[purpose.uuid] &&
|
|
215
|
-
purpose.data_elements &&
|
|
216
|
-
purpose.data_elements.length > 0 && (
|
|
217
|
-
<div
|
|
218
|
-
key={`purpose-${purpose.uuid}-container-${selectedLanguage}`}
|
|
219
|
-
id={`purpose-${purpose.uuid}`}
|
|
220
|
-
style={styles.dataElementsContainer}
|
|
221
|
-
>
|
|
222
|
-
{purpose.data_elements.map((dataElement: any) => (
|
|
223
|
-
<div
|
|
224
|
-
key={`${dataElement.uuid}-${selectedLanguage}`}
|
|
225
|
-
style={styles.dataElementItem}
|
|
198
|
+
<svg
|
|
199
|
+
width="12"
|
|
200
|
+
height="12"
|
|
201
|
+
viewBox="0 0 12 12"
|
|
202
|
+
fill="none"
|
|
203
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
204
|
+
aria-hidden="true"
|
|
226
205
|
>
|
|
227
|
-
<
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
206
|
+
<path
|
|
207
|
+
d="M4 6L5.5 7.5L8.5 4.5"
|
|
208
|
+
stroke="white"
|
|
209
|
+
strokeWidth="2"
|
|
210
|
+
strokeLinecap="round"
|
|
211
|
+
strokeLinejoin="round"
|
|
212
|
+
/>
|
|
213
|
+
</svg>
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
{!collapsedPurposes[purpose.uuid] &&
|
|
218
|
+
purpose.data_elements &&
|
|
219
|
+
purpose.data_elements.length > 0 && (
|
|
220
|
+
<div
|
|
221
|
+
key={`purpose-${purpose.uuid}-container-${selectedLanguage}`}
|
|
222
|
+
id={`purpose-${purpose.uuid}`}
|
|
223
|
+
style={styles.dataElementsContainer}
|
|
224
|
+
>
|
|
225
|
+
{purpose.data_elements.map((dataElement: any) => (
|
|
226
|
+
<div
|
|
227
|
+
key={`${dataElement.uuid}-${selectedLanguage}`}
|
|
228
|
+
style={styles.dataElementItem}
|
|
235
229
|
>
|
|
236
|
-
|
|
237
|
-
`
|
|
238
|
-
dataElement.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}}
|
|
247
|
-
aria-label="required"
|
|
248
|
-
>
|
|
249
|
-
*
|
|
250
|
-
</span>
|
|
251
|
-
)}
|
|
252
|
-
</span>
|
|
253
|
-
{!isAlreadyConsented && (
|
|
254
|
-
<input
|
|
255
|
-
className="redacto-checkbox-small"
|
|
256
|
-
type="checkbox"
|
|
257
|
-
checked={
|
|
258
|
-
selectedDataElements[
|
|
259
|
-
`${purpose.uuid}-${dataElement.uuid}`
|
|
260
|
-
] || false
|
|
261
|
-
}
|
|
262
|
-
onChange={() =>
|
|
263
|
-
onDataElementToggle(dataElement.uuid, purpose.uuid)
|
|
264
|
-
}
|
|
265
|
-
aria-label={`Select ${getTranslatedText(
|
|
230
|
+
<span
|
|
231
|
+
key={`data-element-${purpose.uuid}-${dataElement.uuid}-${selectedLanguage}`}
|
|
232
|
+
id={`data-element-${purpose.uuid}-${dataElement.uuid}`}
|
|
233
|
+
style={{
|
|
234
|
+
...styles.dataElementText,
|
|
235
|
+
...responsiveStyles.dataElementText,
|
|
236
|
+
...textStyle,
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
{getTranslatedText(
|
|
266
240
|
`data_elements.name`,
|
|
267
241
|
dataElement.name,
|
|
268
242
|
dataElement.uuid
|
|
269
|
-
)}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
width: "16px",
|
|
291
|
-
height: "16px",
|
|
292
|
-
borderRadius: "50%",
|
|
293
|
-
backgroundColor: "#10B981",
|
|
294
|
-
}}
|
|
295
|
-
aria-label={`Already consented to ${getTranslatedText(
|
|
296
|
-
`data_elements.name`,
|
|
297
|
-
dataElement.name,
|
|
298
|
-
dataElement.uuid
|
|
299
|
-
)}`}
|
|
300
|
-
>
|
|
301
|
-
<svg
|
|
302
|
-
width="10"
|
|
303
|
-
height="10"
|
|
304
|
-
viewBox="0 0 10 10"
|
|
305
|
-
fill="none"
|
|
306
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
307
|
-
aria-hidden="true"
|
|
308
|
-
>
|
|
309
|
-
<path
|
|
310
|
-
d="M3 5L4.5 6.5L7.5 3.5"
|
|
311
|
-
stroke="white"
|
|
312
|
-
strokeWidth="1.5"
|
|
313
|
-
strokeLinecap="round"
|
|
314
|
-
strokeLinejoin="round"
|
|
315
|
-
/>
|
|
316
|
-
</svg>
|
|
317
|
-
</div>
|
|
318
|
-
);
|
|
319
|
-
} else {
|
|
320
|
-
return (
|
|
321
|
-
<input
|
|
322
|
-
className="redacto-checkbox-small"
|
|
323
|
-
type="checkbox"
|
|
324
|
-
checked={isCurrentlySelected}
|
|
325
|
-
onChange={() =>
|
|
326
|
-
onDataElementToggle(dataElement.uuid, purpose.uuid)
|
|
327
|
-
}
|
|
328
|
-
aria-label={`Select ${getTranslatedText(
|
|
329
|
-
`data_elements.name`,
|
|
330
|
-
dataElement.name,
|
|
331
|
-
dataElement.uuid
|
|
332
|
-
)}${dataElement.required ? " (required)" : ""}`}
|
|
333
|
-
/>
|
|
334
|
-
);
|
|
243
|
+
)}
|
|
244
|
+
{dataElement.required && (
|
|
245
|
+
<span
|
|
246
|
+
style={{
|
|
247
|
+
color: "red",
|
|
248
|
+
marginLeft: "4px",
|
|
249
|
+
}}
|
|
250
|
+
aria-label="required"
|
|
251
|
+
>
|
|
252
|
+
*
|
|
253
|
+
</span>
|
|
254
|
+
)}
|
|
255
|
+
</span>
|
|
256
|
+
{!isAlreadyConsented && (
|
|
257
|
+
<input
|
|
258
|
+
className="redacto-checkbox-small"
|
|
259
|
+
type="checkbox"
|
|
260
|
+
checked={
|
|
261
|
+
selectedDataElements[
|
|
262
|
+
`${purpose.uuid}-${dataElement.uuid}`
|
|
263
|
+
] || false
|
|
335
264
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
265
|
+
onChange={() =>
|
|
266
|
+
onDataElementToggle(dataElement.uuid, purpose.uuid)
|
|
267
|
+
}
|
|
268
|
+
aria-label={`Select ${getTranslatedText(
|
|
269
|
+
`data_elements.name`,
|
|
270
|
+
dataElement.name,
|
|
271
|
+
dataElement.uuid
|
|
272
|
+
)}${dataElement.required ? " (required)" : ""}`}
|
|
273
|
+
/>
|
|
274
|
+
)}
|
|
275
|
+
{isAlreadyConsented && (
|
|
276
|
+
<>
|
|
277
|
+
{(() => {
|
|
278
|
+
const combinedId = `${purpose.uuid}-${dataElement.uuid}`;
|
|
279
|
+
const wasOriginallySelected =
|
|
280
|
+
initialDataElementSelections[combinedId] || false;
|
|
281
|
+
|
|
282
|
+
// Review mode: read-only display of what user consented to
|
|
283
|
+
// Green tick = consented, empty circle = not consented (no editing)
|
|
284
|
+
if (wasOriginallySelected) {
|
|
285
|
+
return (
|
|
286
|
+
<div
|
|
287
|
+
style={{
|
|
288
|
+
display: "flex",
|
|
289
|
+
alignItems: "center",
|
|
290
|
+
justifyContent: "center",
|
|
291
|
+
width: "16px",
|
|
292
|
+
height: "16px",
|
|
293
|
+
borderRadius: "50%",
|
|
294
|
+
backgroundColor: "#10B981",
|
|
295
|
+
}}
|
|
296
|
+
aria-label={`Consented to ${getTranslatedText(
|
|
297
|
+
`data_elements.name`,
|
|
298
|
+
dataElement.name,
|
|
299
|
+
dataElement.uuid
|
|
300
|
+
)}`}
|
|
301
|
+
>
|
|
302
|
+
<svg
|
|
303
|
+
width="10"
|
|
304
|
+
height="10"
|
|
305
|
+
viewBox="0 0 10 10"
|
|
306
|
+
fill="none"
|
|
307
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
308
|
+
aria-hidden="true"
|
|
309
|
+
>
|
|
310
|
+
<path
|
|
311
|
+
d="M3 5L4.5 6.5L7.5 3.5"
|
|
312
|
+
stroke="white"
|
|
313
|
+
strokeWidth="1.5"
|
|
314
|
+
strokeLinecap={"round" as const}
|
|
315
|
+
strokeLinejoin={"round" as const}
|
|
316
|
+
/>
|
|
317
|
+
</svg>
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
} else {
|
|
321
|
+
// Not consented: show empty circle (read-only, no checkbox)
|
|
322
|
+
return (
|
|
323
|
+
<div
|
|
324
|
+
style={{
|
|
325
|
+
display: "flex",
|
|
326
|
+
alignItems: "center",
|
|
327
|
+
justifyContent: "center",
|
|
328
|
+
width: "16px",
|
|
329
|
+
height: "16px",
|
|
330
|
+
borderRadius: "50%",
|
|
331
|
+
border: "2px solid #d0d5dd",
|
|
332
|
+
backgroundColor: "transparent",
|
|
333
|
+
}}
|
|
334
|
+
aria-label={`Not consented to ${getTranslatedText(
|
|
335
|
+
`data_elements.name`,
|
|
336
|
+
dataElement.name,
|
|
337
|
+
dataElement.uuid
|
|
338
|
+
)}`}
|
|
339
|
+
/>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
})()}
|
|
343
|
+
</>
|
|
344
|
+
)}
|
|
345
|
+
</div>
|
|
346
|
+
))}
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
};
|
|
346
352
|
|
|
347
353
|
/**
|
|
348
354
|
* GuardianForm Component
|
|
@@ -384,220 +390,220 @@ const GuardianForm: React.FC<{
|
|
|
384
390
|
declineButtonStyle,
|
|
385
391
|
logoUrl,
|
|
386
392
|
}) => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
393
|
+
const inputStyle = {
|
|
394
|
+
width: "100%",
|
|
395
|
+
padding: "10px 12px",
|
|
396
|
+
border: `1px solid ${settings?.borderColor || "#d0d5dd"}`,
|
|
397
|
+
borderRadius: settings?.borderRadius || "8px",
|
|
398
|
+
fontSize: "13px",
|
|
399
|
+
fontFamily: settings?.font || "inherit",
|
|
400
|
+
color: settings?.textColor || "#344054",
|
|
401
|
+
backgroundColor: settings?.backgroundColor || "#ffffff",
|
|
402
|
+
marginBottom: "4px",
|
|
403
|
+
boxSizing: "border-box" as const,
|
|
404
|
+
};
|
|
399
405
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
+
const errorStyle = {
|
|
407
|
+
color: "#DC2626",
|
|
408
|
+
fontSize: "12px",
|
|
409
|
+
marginTop: "4px",
|
|
410
|
+
marginBottom: "16px",
|
|
411
|
+
};
|
|
406
412
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
413
|
+
const labelStyle = {
|
|
414
|
+
...componentTextStyle,
|
|
415
|
+
fontSize: "13px",
|
|
416
|
+
fontWeight: "500",
|
|
417
|
+
marginBottom: "6px",
|
|
418
|
+
display: "block",
|
|
419
|
+
};
|
|
414
420
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
421
|
+
return (
|
|
422
|
+
<>
|
|
423
|
+
<div style={{ ...styles.topSection, ...componentTextStyle }}>
|
|
424
|
+
<div style={styles.topLeft}>
|
|
425
|
+
<img style={styles.logo} src={logoUrl} alt="Redacto Logo" />
|
|
426
|
+
<h2
|
|
427
|
+
id="guardian-form-title"
|
|
428
|
+
style={{
|
|
429
|
+
...styles.title,
|
|
430
|
+
...responsiveStyles.title,
|
|
431
|
+
...componentHeadingStyle,
|
|
432
|
+
fontSize: "16px",
|
|
433
|
+
fontWeight: 600,
|
|
434
|
+
}}
|
|
435
|
+
>
|
|
436
|
+
Guardian Information Required
|
|
437
|
+
</h2>
|
|
438
|
+
</div>
|
|
432
439
|
</div>
|
|
433
|
-
</div>
|
|
434
|
-
|
|
435
|
-
<div
|
|
436
|
-
style={{
|
|
437
|
-
...styles.middleSection,
|
|
438
|
-
...responsiveStyles.middleSection,
|
|
439
|
-
...componentTextStyle,
|
|
440
|
-
}}
|
|
441
|
-
>
|
|
442
|
-
<p
|
|
443
|
-
style={{
|
|
444
|
-
...styles.privacyText,
|
|
445
|
-
...responsiveStyles.privacyText,
|
|
446
|
-
...componentTextStyle,
|
|
447
|
-
fontSize: "14px",
|
|
448
|
-
lineHeight: "1.4",
|
|
449
|
-
}}
|
|
450
|
-
>
|
|
451
|
-
As you are under the age of consent, we need to collect information
|
|
452
|
-
about your legal guardian to proceed.
|
|
453
|
-
</p>
|
|
454
440
|
|
|
455
441
|
<div
|
|
456
442
|
style={{
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
marginTop: "16px",
|
|
443
|
+
...styles.middleSection,
|
|
444
|
+
...responsiveStyles.middleSection,
|
|
445
|
+
...componentTextStyle,
|
|
461
446
|
}}
|
|
462
447
|
>
|
|
463
|
-
<
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
style={{
|
|
476
|
-
...inputStyle,
|
|
477
|
-
borderColor: errors.guardianName
|
|
478
|
-
? "#DC2626"
|
|
479
|
-
: inputStyle.border,
|
|
480
|
-
}}
|
|
481
|
-
placeholder="Enter guardian's full name"
|
|
482
|
-
/>
|
|
483
|
-
{errors.guardianName && (
|
|
484
|
-
<div style={errorStyle}>{errors.guardianName}</div>
|
|
485
|
-
)}
|
|
486
|
-
</div>
|
|
487
|
-
|
|
488
|
-
<div>
|
|
489
|
-
<label
|
|
490
|
-
htmlFor="guardianContact"
|
|
491
|
-
style={{ ...labelStyle, marginBottom: "8px", display: "block" }}
|
|
492
|
-
>
|
|
493
|
-
Contact of Guardian *
|
|
494
|
-
</label>
|
|
495
|
-
<input
|
|
496
|
-
id="guardianContact"
|
|
497
|
-
type="text"
|
|
498
|
-
value={formData.guardianContact}
|
|
499
|
-
onChange={(e) => onChange("guardianContact", e.target.value)}
|
|
500
|
-
style={{
|
|
501
|
-
...inputStyle,
|
|
502
|
-
borderColor: errors.guardianContact
|
|
503
|
-
? "#DC2626"
|
|
504
|
-
: inputStyle.border,
|
|
505
|
-
}}
|
|
506
|
-
placeholder="Enter guardian's email or phone number"
|
|
507
|
-
/>
|
|
508
|
-
{errors.guardianContact && (
|
|
509
|
-
<div style={errorStyle}>{errors.guardianContact}</div>
|
|
510
|
-
)}
|
|
511
|
-
</div>
|
|
512
|
-
|
|
513
|
-
<div>
|
|
514
|
-
<label
|
|
515
|
-
htmlFor="guardianRelationship"
|
|
516
|
-
style={{ ...labelStyle, marginBottom: "8px", display: "block" }}
|
|
517
|
-
>
|
|
518
|
-
Relationship to Guardian *
|
|
519
|
-
</label>
|
|
520
|
-
<input
|
|
521
|
-
id="guardianRelationship"
|
|
522
|
-
type="text"
|
|
523
|
-
value={formData.guardianRelationship}
|
|
524
|
-
onChange={(e) => onChange("guardianRelationship", e.target.value)}
|
|
525
|
-
style={{
|
|
526
|
-
...inputStyle,
|
|
527
|
-
borderColor: errors.guardianRelationship
|
|
528
|
-
? "#DC2626"
|
|
529
|
-
: inputStyle.border,
|
|
530
|
-
}}
|
|
531
|
-
placeholder="e.g., Parent, Legal Guardian, etc."
|
|
532
|
-
/>
|
|
533
|
-
{errors.guardianRelationship && (
|
|
534
|
-
<div style={errorStyle}>{errors.guardianRelationship}</div>
|
|
535
|
-
)}
|
|
536
|
-
</div>
|
|
537
|
-
</div>
|
|
448
|
+
<p
|
|
449
|
+
style={{
|
|
450
|
+
...styles.privacyText,
|
|
451
|
+
...responsiveStyles.privacyText,
|
|
452
|
+
...componentTextStyle,
|
|
453
|
+
fontSize: "14px",
|
|
454
|
+
lineHeight: "1.4",
|
|
455
|
+
}}
|
|
456
|
+
>
|
|
457
|
+
As you are under the age of consent, we need to collect information
|
|
458
|
+
about your legal guardian to proceed.
|
|
459
|
+
</p>
|
|
538
460
|
|
|
539
|
-
{errors.general && (
|
|
540
461
|
<div
|
|
541
|
-
role="alert"
|
|
542
462
|
style={{
|
|
543
|
-
|
|
544
|
-
|
|
463
|
+
display: "flex",
|
|
464
|
+
flexDirection: "column",
|
|
465
|
+
gap: "12px",
|
|
545
466
|
marginTop: "16px",
|
|
546
|
-
padding: "12px",
|
|
547
|
-
borderRadius: "6px",
|
|
548
|
-
border: "1px solid #FCA5A5",
|
|
549
|
-
backgroundColor: "#FEF2F2",
|
|
550
|
-
textAlign: "center",
|
|
551
467
|
}}
|
|
552
468
|
>
|
|
553
|
-
|
|
469
|
+
<div>
|
|
470
|
+
<label
|
|
471
|
+
htmlFor="guardianName"
|
|
472
|
+
style={{ ...labelStyle, marginBottom: "8px", display: "block" }}
|
|
473
|
+
>
|
|
474
|
+
Name of Guardian *
|
|
475
|
+
</label>
|
|
476
|
+
<input
|
|
477
|
+
id="guardianName"
|
|
478
|
+
type="text"
|
|
479
|
+
value={formData.guardianName}
|
|
480
|
+
onChange={(e) => onChange("guardianName", e.target.value)}
|
|
481
|
+
style={{
|
|
482
|
+
...inputStyle,
|
|
483
|
+
borderColor: errors.guardianName
|
|
484
|
+
? "#DC2626"
|
|
485
|
+
: inputStyle.border,
|
|
486
|
+
}}
|
|
487
|
+
placeholder="Enter guardian's full name"
|
|
488
|
+
/>
|
|
489
|
+
{errors.guardianName && (
|
|
490
|
+
<div style={errorStyle}>{errors.guardianName}</div>
|
|
491
|
+
)}
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
<div>
|
|
495
|
+
<label
|
|
496
|
+
htmlFor="guardianContact"
|
|
497
|
+
style={{ ...labelStyle, marginBottom: "8px", display: "block" }}
|
|
498
|
+
>
|
|
499
|
+
Contact of Guardian *
|
|
500
|
+
</label>
|
|
501
|
+
<input
|
|
502
|
+
id="guardianContact"
|
|
503
|
+
type="text"
|
|
504
|
+
value={formData.guardianContact}
|
|
505
|
+
onChange={(e) => onChange("guardianContact", e.target.value)}
|
|
506
|
+
style={{
|
|
507
|
+
...inputStyle,
|
|
508
|
+
borderColor: errors.guardianContact
|
|
509
|
+
? "#DC2626"
|
|
510
|
+
: inputStyle.border,
|
|
511
|
+
}}
|
|
512
|
+
placeholder="Enter guardian's email or phone number"
|
|
513
|
+
/>
|
|
514
|
+
{errors.guardianContact && (
|
|
515
|
+
<div style={errorStyle}>{errors.guardianContact}</div>
|
|
516
|
+
)}
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
<div>
|
|
520
|
+
<label
|
|
521
|
+
htmlFor="guardianRelationship"
|
|
522
|
+
style={{ ...labelStyle, marginBottom: "8px", display: "block" }}
|
|
523
|
+
>
|
|
524
|
+
Relationship to Guardian *
|
|
525
|
+
</label>
|
|
526
|
+
<input
|
|
527
|
+
id="guardianRelationship"
|
|
528
|
+
type="text"
|
|
529
|
+
value={formData.guardianRelationship}
|
|
530
|
+
onChange={(e) => onChange("guardianRelationship", e.target.value)}
|
|
531
|
+
style={{
|
|
532
|
+
...inputStyle,
|
|
533
|
+
borderColor: errors.guardianRelationship
|
|
534
|
+
? "#DC2626"
|
|
535
|
+
: inputStyle.border,
|
|
536
|
+
}}
|
|
537
|
+
placeholder="e.g., Parent, Legal Guardian, etc."
|
|
538
|
+
/>
|
|
539
|
+
{errors.guardianRelationship && (
|
|
540
|
+
<div style={errorStyle}>{errors.guardianRelationship}</div>
|
|
541
|
+
)}
|
|
542
|
+
</div>
|
|
554
543
|
</div>
|
|
555
|
-
)}
|
|
556
|
-
</div>
|
|
557
544
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
? "Submitting guardian information"
|
|
579
|
-
: "Submit guardian information and proceed"
|
|
580
|
-
}
|
|
581
|
-
>
|
|
582
|
-
{isSubmitting ? "Submitting..." : "Next"}
|
|
583
|
-
</button>
|
|
584
|
-
<button
|
|
545
|
+
{errors.general && (
|
|
546
|
+
<div
|
|
547
|
+
role="alert"
|
|
548
|
+
style={{
|
|
549
|
+
color: "#DC2626",
|
|
550
|
+
fontSize: "14px",
|
|
551
|
+
marginTop: "16px",
|
|
552
|
+
padding: "12px",
|
|
553
|
+
borderRadius: "6px",
|
|
554
|
+
border: "1px solid #FCA5A5",
|
|
555
|
+
backgroundColor: "#FEF2F2",
|
|
556
|
+
textAlign: "center",
|
|
557
|
+
}}
|
|
558
|
+
>
|
|
559
|
+
{errors.general}
|
|
560
|
+
</div>
|
|
561
|
+
)}
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<div
|
|
585
565
|
style={{
|
|
586
|
-
...styles.
|
|
587
|
-
...responsiveStyles.
|
|
588
|
-
...
|
|
589
|
-
|
|
566
|
+
...styles.bottomSection,
|
|
567
|
+
...responsiveStyles.bottomSection,
|
|
568
|
+
...componentTextStyle,
|
|
569
|
+
paddingTop: "12px",
|
|
590
570
|
}}
|
|
591
|
-
onClick={onDecline}
|
|
592
|
-
type="button"
|
|
593
|
-
aria-label="Cancel guardian information form"
|
|
594
571
|
>
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
572
|
+
<button
|
|
573
|
+
style={{
|
|
574
|
+
...styles.button,
|
|
575
|
+
...responsiveStyles.button,
|
|
576
|
+
...styles.acceptButton,
|
|
577
|
+
...acceptButtonStyle,
|
|
578
|
+
}}
|
|
579
|
+
onClick={onNext}
|
|
580
|
+
disabled={isSubmitting}
|
|
581
|
+
type="button"
|
|
582
|
+
aria-label={
|
|
583
|
+
isSubmitting
|
|
584
|
+
? "Submitting guardian information"
|
|
585
|
+
: "Submit guardian information and proceed"
|
|
586
|
+
}
|
|
587
|
+
>
|
|
588
|
+
{isSubmitting ? "Submitting..." : "Next"}
|
|
589
|
+
</button>
|
|
590
|
+
<button
|
|
591
|
+
style={{
|
|
592
|
+
...styles.button,
|
|
593
|
+
...responsiveStyles.button,
|
|
594
|
+
...styles.cancelButton,
|
|
595
|
+
...declineButtonStyle,
|
|
596
|
+
}}
|
|
597
|
+
onClick={onDecline}
|
|
598
|
+
type="button"
|
|
599
|
+
aria-label="Cancel guardian information form"
|
|
600
|
+
>
|
|
601
|
+
Cancel
|
|
602
|
+
</button>
|
|
603
|
+
</div>
|
|
604
|
+
</>
|
|
605
|
+
);
|
|
606
|
+
};
|
|
601
607
|
|
|
602
608
|
/**
|
|
603
609
|
* AgeVerification Component
|
|
@@ -629,122 +635,122 @@ const AgeVerification: React.FC<{
|
|
|
629
635
|
logoUrl,
|
|
630
636
|
isMobile,
|
|
631
637
|
}) => {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
638
|
+
return (
|
|
639
|
+
<>
|
|
640
|
+
<div style={{ ...styles.topSection, ...componentTextStyle }}>
|
|
641
|
+
<div style={styles.topLeft}>
|
|
642
|
+
<img style={styles.logo} src={logoUrl} alt="Redacto Logo" />
|
|
643
|
+
<h2
|
|
644
|
+
id="age-verification-title"
|
|
645
|
+
style={{
|
|
646
|
+
...styles.title,
|
|
647
|
+
...responsiveStyles.title,
|
|
648
|
+
...componentHeadingStyle,
|
|
649
|
+
}}
|
|
650
|
+
>
|
|
651
|
+
Age Verification Required
|
|
652
|
+
</h2>
|
|
653
|
+
</div>
|
|
654
|
+
<button
|
|
655
|
+
onClick={onClose}
|
|
639
656
|
style={{
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
657
|
+
background: "none",
|
|
658
|
+
border: "none",
|
|
659
|
+
color: componentHeadingStyle?.color || "#323B4B",
|
|
660
|
+
cursor: "pointer",
|
|
661
|
+
padding: isMobile ? "2px" : "4px",
|
|
662
|
+
display: "flex",
|
|
663
|
+
alignItems: "center",
|
|
664
|
+
justifyContent: "center",
|
|
643
665
|
}}
|
|
666
|
+
aria-label="Close modal"
|
|
644
667
|
>
|
|
645
|
-
|
|
646
|
-
|
|
668
|
+
<svg
|
|
669
|
+
width={isMobile ? "18" : "20"}
|
|
670
|
+
height={isMobile ? "18" : "20"}
|
|
671
|
+
viewBox="0 0 20 20"
|
|
672
|
+
fill="none"
|
|
673
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
674
|
+
aria-hidden="true"
|
|
675
|
+
>
|
|
676
|
+
<path
|
|
677
|
+
d="M15 5L5 15M5 5L15 15"
|
|
678
|
+
stroke="currentColor"
|
|
679
|
+
strokeWidth="2"
|
|
680
|
+
strokeLinecap="round"
|
|
681
|
+
strokeLinejoin="round"
|
|
682
|
+
/>
|
|
683
|
+
</svg>
|
|
684
|
+
</button>
|
|
647
685
|
</div>
|
|
648
|
-
<button
|
|
649
|
-
onClick={onClose}
|
|
650
|
-
style={{
|
|
651
|
-
background: "none",
|
|
652
|
-
border: "none",
|
|
653
|
-
color: componentHeadingStyle?.color || "#323B4B",
|
|
654
|
-
cursor: "pointer",
|
|
655
|
-
padding: isMobile ? "2px" : "4px",
|
|
656
|
-
display: "flex",
|
|
657
|
-
alignItems: "center",
|
|
658
|
-
justifyContent: "center",
|
|
659
|
-
}}
|
|
660
|
-
aria-label="Close modal"
|
|
661
|
-
>
|
|
662
|
-
<svg
|
|
663
|
-
width={isMobile ? "18" : "20"}
|
|
664
|
-
height={isMobile ? "18" : "20"}
|
|
665
|
-
viewBox="0 0 20 20"
|
|
666
|
-
fill="none"
|
|
667
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
668
|
-
aria-hidden="true"
|
|
669
|
-
>
|
|
670
|
-
<path
|
|
671
|
-
d="M15 5L5 15M5 5L15 15"
|
|
672
|
-
stroke="currentColor"
|
|
673
|
-
strokeWidth="2"
|
|
674
|
-
strokeLinecap="round"
|
|
675
|
-
strokeLinejoin="round"
|
|
676
|
-
/>
|
|
677
|
-
</svg>
|
|
678
|
-
</button>
|
|
679
|
-
</div>
|
|
680
686
|
|
|
681
|
-
|
|
682
|
-
style={{
|
|
683
|
-
...styles.middleSection,
|
|
684
|
-
...responsiveStyles.middleSection,
|
|
685
|
-
...componentTextStyle,
|
|
686
|
-
}}
|
|
687
|
-
>
|
|
688
|
-
<p
|
|
687
|
+
<div
|
|
689
688
|
style={{
|
|
690
|
-
...styles.
|
|
691
|
-
...responsiveStyles.
|
|
689
|
+
...styles.middleSection,
|
|
690
|
+
...responsiveStyles.middleSection,
|
|
692
691
|
...componentTextStyle,
|
|
693
|
-
marginBottom: "16px",
|
|
694
692
|
}}
|
|
695
693
|
>
|
|
696
|
-
|
|
697
|
-
|
|
694
|
+
<p
|
|
695
|
+
style={{
|
|
696
|
+
...styles.privacyText,
|
|
697
|
+
...responsiveStyles.privacyText,
|
|
698
|
+
...componentTextStyle,
|
|
699
|
+
marginBottom: "16px",
|
|
700
|
+
}}
|
|
701
|
+
>
|
|
702
|
+
To proceed with this consent form, we need to verify your age.
|
|
703
|
+
</p>
|
|
698
704
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
705
|
+
<p
|
|
706
|
+
style={{
|
|
707
|
+
...styles.subTitle,
|
|
708
|
+
...componentTextStyle,
|
|
709
|
+
marginBottom: "24px",
|
|
710
|
+
textAlign: "left",
|
|
711
|
+
}}
|
|
712
|
+
>
|
|
713
|
+
Are you 18 years of age or older?
|
|
714
|
+
</p>
|
|
715
|
+
</div>
|
|
710
716
|
|
|
711
|
-
|
|
712
|
-
style={{
|
|
713
|
-
...styles.bottomSection,
|
|
714
|
-
...responsiveStyles.bottomSection,
|
|
715
|
-
...componentTextStyle,
|
|
716
|
-
}}
|
|
717
|
-
>
|
|
718
|
-
<button
|
|
719
|
-
style={{
|
|
720
|
-
...styles.button,
|
|
721
|
-
...responsiveStyles.button,
|
|
722
|
-
...styles.acceptButton,
|
|
723
|
-
...acceptButtonStyle,
|
|
724
|
-
}}
|
|
725
|
-
onClick={onYes}
|
|
726
|
-
type="button"
|
|
727
|
-
aria-label="Confirm that I am 18 years of age or older"
|
|
728
|
-
>
|
|
729
|
-
Yes, I am 18 or older
|
|
730
|
-
</button>
|
|
731
|
-
<button
|
|
717
|
+
<div
|
|
732
718
|
style={{
|
|
733
|
-
...styles.
|
|
734
|
-
...responsiveStyles.
|
|
735
|
-
...
|
|
736
|
-
...declineButtonStyle,
|
|
719
|
+
...styles.bottomSection,
|
|
720
|
+
...responsiveStyles.bottomSection,
|
|
721
|
+
...componentTextStyle,
|
|
737
722
|
}}
|
|
738
|
-
onClick={onNo}
|
|
739
|
-
type="button"
|
|
740
|
-
aria-label="Indicate that I am under 18 years of age"
|
|
741
723
|
>
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
724
|
+
<button
|
|
725
|
+
style={{
|
|
726
|
+
...styles.button,
|
|
727
|
+
...responsiveStyles.button,
|
|
728
|
+
...styles.acceptButton,
|
|
729
|
+
...acceptButtonStyle,
|
|
730
|
+
}}
|
|
731
|
+
onClick={onYes}
|
|
732
|
+
type="button"
|
|
733
|
+
aria-label="Confirm that I am 18 years of age or older"
|
|
734
|
+
>
|
|
735
|
+
Yes, I am 18 or older
|
|
736
|
+
</button>
|
|
737
|
+
<button
|
|
738
|
+
style={{
|
|
739
|
+
...styles.button,
|
|
740
|
+
...responsiveStyles.button,
|
|
741
|
+
...styles.cancelButton,
|
|
742
|
+
...declineButtonStyle,
|
|
743
|
+
}}
|
|
744
|
+
onClick={onNo}
|
|
745
|
+
type="button"
|
|
746
|
+
aria-label="Indicate that I am under 18 years of age"
|
|
747
|
+
>
|
|
748
|
+
No, I am under 18
|
|
749
|
+
</button>
|
|
750
|
+
</div>
|
|
751
|
+
</>
|
|
752
|
+
);
|
|
753
|
+
};
|
|
748
754
|
|
|
749
755
|
// =============================================================================
|
|
750
756
|
// MAIN COMPONENT
|
|
@@ -770,6 +776,8 @@ export const RedactoNoticeConsent = ({
|
|
|
770
776
|
settings,
|
|
771
777
|
applicationId,
|
|
772
778
|
validateAgainst = "all",
|
|
779
|
+
includeFullyConsentedData = false,
|
|
780
|
+
reviewModeButtonText = "Continue",
|
|
773
781
|
}: Props) => {
|
|
774
782
|
// =============================================================================
|
|
775
783
|
// PROP VALIDATION
|
|
@@ -838,6 +846,8 @@ export const RedactoNoticeConsent = ({
|
|
|
838
846
|
Record<string, boolean>
|
|
839
847
|
>({});
|
|
840
848
|
const [hasAlreadyConsented, setHasAlreadyConsented] = useState(false);
|
|
849
|
+
const [isFullyConsentedReviewMode, setIsFullyConsentedReviewMode] =
|
|
850
|
+
useState(false);
|
|
841
851
|
|
|
842
852
|
// UI states
|
|
843
853
|
const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = useState(false);
|
|
@@ -1058,6 +1068,12 @@ export const RedactoNoticeConsent = ({
|
|
|
1058
1068
|
}
|
|
1059
1069
|
}, [hasAlreadyConsented, onAccept]);
|
|
1060
1070
|
|
|
1071
|
+
const isDefaultLanguage = (lang: string): boolean => {
|
|
1072
|
+
if (!content) return true;
|
|
1073
|
+
if (lang === "English" || lang === "en" || lang === "EN") return true;
|
|
1074
|
+
return false;
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1061
1077
|
const getTranslatedText = (
|
|
1062
1078
|
key: string,
|
|
1063
1079
|
defaultText: string,
|
|
@@ -1065,8 +1081,8 @@ export const RedactoNoticeConsent = ({
|
|
|
1065
1081
|
): string => {
|
|
1066
1082
|
if (!content) return defaultText;
|
|
1067
1083
|
|
|
1068
|
-
// If
|
|
1069
|
-
if (selectedLanguage
|
|
1084
|
+
// If default language is selected, use the base content from active_config
|
|
1085
|
+
if (isDefaultLanguage(selectedLanguage)) {
|
|
1070
1086
|
// Check for specific keys in content object
|
|
1071
1087
|
if (
|
|
1072
1088
|
key === "privacy_policy_anchor_text" &&
|
|
@@ -1110,7 +1126,16 @@ export const RedactoNoticeConsent = ({
|
|
|
1110
1126
|
|
|
1111
1127
|
if (itemId) {
|
|
1112
1128
|
if (key === "purposes.name" || key === "purposes.description") {
|
|
1113
|
-
|
|
1129
|
+
const purposeData = translationMap.purposes?.[itemId];
|
|
1130
|
+
if (purposeData) {
|
|
1131
|
+
if (typeof purposeData === "string") {
|
|
1132
|
+
return key === "purposes.name" ? purposeData : defaultText;
|
|
1133
|
+
}
|
|
1134
|
+
return key === "purposes.name"
|
|
1135
|
+
? purposeData.name || defaultText
|
|
1136
|
+
: purposeData.description || defaultText;
|
|
1137
|
+
}
|
|
1138
|
+
return defaultText;
|
|
1114
1139
|
}
|
|
1115
1140
|
if (key.startsWith("data_elements.")) {
|
|
1116
1141
|
return translationMap.data_elements?.[itemId] || defaultText;
|
|
@@ -1118,13 +1143,37 @@ export const RedactoNoticeConsent = ({
|
|
|
1118
1143
|
}
|
|
1119
1144
|
|
|
1120
1145
|
const value = translationMap[key];
|
|
1121
|
-
if (typeof value === "string") {
|
|
1146
|
+
if (typeof value === "string" && value) {
|
|
1122
1147
|
return value;
|
|
1123
1148
|
}
|
|
1124
1149
|
|
|
1125
1150
|
return defaultText;
|
|
1126
1151
|
};
|
|
1127
1152
|
|
|
1153
|
+
const getTranslatedDpoText = (
|
|
1154
|
+
key: string,
|
|
1155
|
+
defaultText: string
|
|
1156
|
+
): string => {
|
|
1157
|
+
if (!content?.dpo_info) return defaultText;
|
|
1158
|
+
|
|
1159
|
+
if (isDefaultLanguage(selectedLanguage)) {
|
|
1160
|
+
return (content.dpo_info as Record<string, string>)[key] || defaultText;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (!content.supported_languages_and_translations) return defaultText;
|
|
1164
|
+
|
|
1165
|
+
const translationMap = content.supported_languages_and_translations[
|
|
1166
|
+
selectedLanguage
|
|
1167
|
+
] as TranslationObject | undefined;
|
|
1168
|
+
|
|
1169
|
+
if (translationMap?.dpo_info) {
|
|
1170
|
+
const translated = (translationMap.dpo_info as Record<string, string | undefined>)[key];
|
|
1171
|
+
if (translated) return translated;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
return (content.dpo_info as Record<string, string>)[key] || defaultText;
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1128
1177
|
// =============================================================================
|
|
1129
1178
|
// EVENT HANDLERS
|
|
1130
1179
|
// =============================================================================
|
|
@@ -1161,25 +1210,25 @@ export const RedactoNoticeConsent = ({
|
|
|
1161
1210
|
language,
|
|
1162
1211
|
specific_uuid: applicationId,
|
|
1163
1212
|
validate_against: validateAgainst,
|
|
1213
|
+
include_fully_consented_data: includeFullyConsentedData,
|
|
1164
1214
|
signal: abortControllerRef.current?.signal,
|
|
1165
1215
|
});
|
|
1166
|
-
|
|
1167
|
-
|
|
1216
|
+
const activeConfig = consentContentData.detail.active_config;
|
|
1217
|
+
setContent(activeConfig);
|
|
1218
|
+
if (activeConfig.default_language) {
|
|
1168
1219
|
// Normalize "en" to "English" for consistency
|
|
1169
|
-
const defaultLang =
|
|
1170
|
-
consentContentData.detail.active_config.default_language;
|
|
1220
|
+
const defaultLang = activeConfig.default_language;
|
|
1171
1221
|
setSelectedLanguage(
|
|
1172
1222
|
defaultLang === "en" || defaultLang === "EN" ? "English" : defaultLang
|
|
1173
1223
|
);
|
|
1174
1224
|
}
|
|
1175
|
-
const initialCollapsedState =
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
);
|
|
1225
|
+
const initialCollapsedState = activeConfig.purposes.reduce(
|
|
1226
|
+
(acc, purpose) => ({
|
|
1227
|
+
...acc,
|
|
1228
|
+
[purpose.uuid]: true,
|
|
1229
|
+
}),
|
|
1230
|
+
{}
|
|
1231
|
+
);
|
|
1183
1232
|
setCollapsedPurposes(initialCollapsedState);
|
|
1184
1233
|
|
|
1185
1234
|
// Initialize selected states - handle reconsent pre-population
|
|
@@ -1189,39 +1238,37 @@ export const RedactoNoticeConsent = ({
|
|
|
1189
1238
|
// Categorize purposes for reconsent UI
|
|
1190
1239
|
const newCategorizedPurposes = isReconsentRequired
|
|
1191
1240
|
? {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
),
|
|
1215
|
-
}
|
|
1241
|
+
alreadyConsented: activeConfig.purposes.filter(
|
|
1242
|
+
(purpose) => {
|
|
1243
|
+
const selection = purposeSelections?.[purpose.uuid];
|
|
1244
|
+
return (
|
|
1245
|
+
selection &&
|
|
1246
|
+
(selection.status === "ACTIVE" ||
|
|
1247
|
+
selection.status === "EXPIRED") &&
|
|
1248
|
+
!selection.needs_reconsent
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
),
|
|
1252
|
+
needsConsent: activeConfig.purposes.filter(
|
|
1253
|
+
(purpose) => {
|
|
1254
|
+
const selection = purposeSelections?.[purpose.uuid];
|
|
1255
|
+
return (
|
|
1256
|
+
!selection ||
|
|
1257
|
+
selection.status !== "ACTIVE" ||
|
|
1258
|
+
selection.needs_reconsent
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
),
|
|
1262
|
+
}
|
|
1216
1263
|
: null;
|
|
1217
1264
|
|
|
1218
1265
|
setCategorizedPurposes(newCategorizedPurposes);
|
|
1219
1266
|
|
|
1220
1267
|
const initialPurposeState: Record<string, boolean> =
|
|
1221
|
-
|
|
1268
|
+
activeConfig.purposes.reduce(
|
|
1222
1269
|
(acc: Record<string, boolean>, purpose) => {
|
|
1223
|
-
if (
|
|
1224
|
-
// Pre-populate with existing selection
|
|
1270
|
+
if (purposeSelections?.[purpose.uuid]) {
|
|
1271
|
+
// Pre-populate with existing selection (reconsent or magic link flow)
|
|
1225
1272
|
acc[purpose.uuid] = purposeSelections[purpose.uuid].selected;
|
|
1226
1273
|
} else {
|
|
1227
1274
|
// Default to false for new consent
|
|
@@ -1234,15 +1281,14 @@ export const RedactoNoticeConsent = ({
|
|
|
1234
1281
|
setSelectedPurposes(initialPurposeState);
|
|
1235
1282
|
|
|
1236
1283
|
const initialDataElementState: Record<string, boolean> =
|
|
1237
|
-
|
|
1284
|
+
activeConfig.purposes.reduce(
|
|
1238
1285
|
(acc: Record<string, boolean>, purpose) => {
|
|
1239
1286
|
purpose.data_elements.forEach((element) => {
|
|
1240
1287
|
const combinedId = `${purpose.uuid}-${element.uuid}`;
|
|
1241
1288
|
if (
|
|
1242
|
-
isReconsentRequired &&
|
|
1243
1289
|
purposeSelections?.[purpose.uuid]?.data_elements?.[element.uuid]
|
|
1244
1290
|
) {
|
|
1245
|
-
// Pre-populate with existing selection
|
|
1291
|
+
// Pre-populate with existing selection (reconsent or magic link flow)
|
|
1246
1292
|
acc[combinedId] =
|
|
1247
1293
|
purposeSelections[purpose.uuid].data_elements[
|
|
1248
1294
|
element.uuid
|
|
@@ -1259,6 +1305,7 @@ export const RedactoNoticeConsent = ({
|
|
|
1259
1305
|
setSelectedDataElements(initialDataElementState);
|
|
1260
1306
|
// Store initial selections for already-consented purposes to track modifications
|
|
1261
1307
|
if (isReconsentRequired && newCategorizedPurposes) {
|
|
1308
|
+
setIsFullyConsentedReviewMode(false);
|
|
1262
1309
|
const initialSelections: Record<string, boolean> = {};
|
|
1263
1310
|
newCategorizedPurposes.alreadyConsented.forEach((purpose) => {
|
|
1264
1311
|
purpose.data_elements.forEach((element) => {
|
|
@@ -1275,7 +1322,39 @@ export const RedactoNoticeConsent = ({
|
|
|
1275
1322
|
});
|
|
1276
1323
|
});
|
|
1277
1324
|
setInitialDataElementSelections(initialSelections);
|
|
1325
|
+
} else if (includeFullyConsentedData && purposeSelections) {
|
|
1326
|
+
// Fully consented review mode: all purposes show green ticks, need initialDataElementSelections
|
|
1327
|
+
const allPurposesSelected =
|
|
1328
|
+
activeConfig.purposes.length > 0 &&
|
|
1329
|
+
activeConfig.purposes.every(
|
|
1330
|
+
(p) => purposeSelections[p.uuid]?.selected === true
|
|
1331
|
+
);
|
|
1332
|
+
setIsFullyConsentedReviewMode(allPurposesSelected);
|
|
1333
|
+
if (allPurposesSelected) {
|
|
1334
|
+
const initialSelections: Record<string, boolean> = {};
|
|
1335
|
+
activeConfig.purposes.forEach(
|
|
1336
|
+
(purpose) => {
|
|
1337
|
+
purpose.data_elements.forEach((element) => {
|
|
1338
|
+
const combinedId = `${purpose.uuid}-${element.uuid}`;
|
|
1339
|
+
if (
|
|
1340
|
+
purposeSelections[purpose.uuid]?.data_elements?.[element.uuid]
|
|
1341
|
+
) {
|
|
1342
|
+
initialSelections[combinedId] =
|
|
1343
|
+
purposeSelections[purpose.uuid].data_elements[element.uuid]
|
|
1344
|
+
.selected;
|
|
1345
|
+
} else {
|
|
1346
|
+
initialSelections[combinedId] = false;
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
);
|
|
1351
|
+
setInitialDataElementSelections(initialSelections);
|
|
1352
|
+
} else {
|
|
1353
|
+
setIsFullyConsentedReviewMode(false);
|
|
1354
|
+
setInitialDataElementSelections({});
|
|
1355
|
+
}
|
|
1278
1356
|
} else {
|
|
1357
|
+
setIsFullyConsentedReviewMode(false);
|
|
1279
1358
|
setInitialDataElementSelections({});
|
|
1280
1359
|
}
|
|
1281
1360
|
|
|
@@ -1293,7 +1372,6 @@ export const RedactoNoticeConsent = ({
|
|
|
1293
1372
|
return; // Don't update state for cancelled requests
|
|
1294
1373
|
}
|
|
1295
1374
|
|
|
1296
|
-
console.error(err);
|
|
1297
1375
|
const error = err as Error & { status?: number };
|
|
1298
1376
|
setFetchError(error);
|
|
1299
1377
|
if (error.status === 409) {
|
|
@@ -1319,6 +1397,7 @@ export const RedactoNoticeConsent = ({
|
|
|
1319
1397
|
language,
|
|
1320
1398
|
applicationId,
|
|
1321
1399
|
validateAgainst,
|
|
1400
|
+
includeFullyConsentedData,
|
|
1322
1401
|
// Removed onError and isRefreshingToken from dependencies to prevent duplicate API calls
|
|
1323
1402
|
// onError is only called on errors, not needed for initial data fetching
|
|
1324
1403
|
]);
|
|
@@ -1457,7 +1536,7 @@ export const RedactoNoticeConsent = ({
|
|
|
1457
1536
|
selected: element.required
|
|
1458
1537
|
? true
|
|
1459
1538
|
: selectedDataElements[`${purpose.uuid}-${element.uuid}`] ||
|
|
1460
|
-
|
|
1539
|
+
false,
|
|
1461
1540
|
})),
|
|
1462
1541
|
};
|
|
1463
1542
|
}),
|
|
@@ -1475,13 +1554,12 @@ export const RedactoNoticeConsent = ({
|
|
|
1475
1554
|
return;
|
|
1476
1555
|
}
|
|
1477
1556
|
|
|
1478
|
-
console.error("Error submitting consent:", error);
|
|
1479
1557
|
const err = error as Error & { status?: number };
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
"An error occurred while submitting your consent. Please try again later."
|
|
1483
|
-
|
|
1484
|
-
|
|
1558
|
+
const message =
|
|
1559
|
+
err.status === 500
|
|
1560
|
+
? "An error occurred while submitting your consent. Please try again later."
|
|
1561
|
+
: err.message || "Failed to submit consent. Please try again.";
|
|
1562
|
+
setErrorMessage(message);
|
|
1485
1563
|
onError?.(error as Error);
|
|
1486
1564
|
} finally {
|
|
1487
1565
|
setIsSubmittingAccept(false);
|
|
@@ -1716,9 +1794,9 @@ export const RedactoNoticeConsent = ({
|
|
|
1716
1794
|
// Determine which purposes to include based on UI mode
|
|
1717
1795
|
const purposesToProcess = categorizedPurposes
|
|
1718
1796
|
? [
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1797
|
+
...categorizedPurposes.alreadyConsented,
|
|
1798
|
+
...categorizedPurposes.needsConsent,
|
|
1799
|
+
]
|
|
1722
1800
|
: content.purposes || [];
|
|
1723
1801
|
|
|
1724
1802
|
purposesToProcess.forEach((purpose) => {
|
|
@@ -1748,12 +1826,32 @@ export const RedactoNoticeConsent = ({
|
|
|
1748
1826
|
}
|
|
1749
1827
|
});
|
|
1750
1828
|
|
|
1751
|
-
// 7.
|
|
1829
|
+
// 7. Additional text audio
|
|
1830
|
+
if (content.additional_text && ttsAudioUrls.additional_text_audio_url) {
|
|
1831
|
+
sequence.push(ttsAudioUrls.additional_text_audio_url);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// 8. Privacy center anchor text audio
|
|
1835
|
+
if (content.privacy_center_url && ttsAudioUrls.privacy_center_anchor_text_audio_url) {
|
|
1836
|
+
sequence.push(ttsAudioUrls.privacy_center_anchor_text_audio_url);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// 9. DPO info audio
|
|
1840
|
+
if (content.dpo_info && ttsAudioUrls.dpo_info_audio) {
|
|
1841
|
+
const dpoAudio = ttsAudioUrls.dpo_info_audio;
|
|
1842
|
+
DPO_AUDIO_ELEMENT_MAP.forEach(({ audioKey }) => {
|
|
1843
|
+
if (dpoAudio[audioKey]) {
|
|
1844
|
+
sequence.push(dpoAudio[audioKey]);
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
// 10. Confirm button text audio
|
|
1752
1850
|
if (ttsAudioUrls.confirm_button_text_audio_url) {
|
|
1753
1851
|
sequence.push(ttsAudioUrls.confirm_button_text_audio_url);
|
|
1754
1852
|
}
|
|
1755
1853
|
|
|
1756
|
-
//
|
|
1854
|
+
// 11. Decline button text audio
|
|
1757
1855
|
if (ttsAudioUrls.decline_button_text_audio_url) {
|
|
1758
1856
|
sequence.push(ttsAudioUrls.decline_button_text_audio_url);
|
|
1759
1857
|
}
|
|
@@ -1795,9 +1893,9 @@ export const RedactoNoticeConsent = ({
|
|
|
1795
1893
|
// 6. Purposes and data elements
|
|
1796
1894
|
const purposesToProcess = categorizedPurposes
|
|
1797
1895
|
? [
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1896
|
+
...categorizedPurposes.alreadyConsented,
|
|
1897
|
+
...categorizedPurposes.needsConsent,
|
|
1898
|
+
]
|
|
1801
1899
|
: content.purposes || [];
|
|
1802
1900
|
|
|
1803
1901
|
purposesToProcess.forEach((purpose) => {
|
|
@@ -1823,12 +1921,32 @@ export const RedactoNoticeConsent = ({
|
|
|
1823
1921
|
}
|
|
1824
1922
|
});
|
|
1825
1923
|
|
|
1826
|
-
// 7.
|
|
1924
|
+
// 7. Additional text audio
|
|
1925
|
+
if (content.additional_text && ttsAudioUrls.additional_text_audio_url) {
|
|
1926
|
+
elementIds.push("additional-text");
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// 8. Privacy center anchor text audio
|
|
1930
|
+
if (content.privacy_center_url && ttsAudioUrls.privacy_center_anchor_text_audio_url) {
|
|
1931
|
+
elementIds.push("privacy-center-link");
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// 9. DPO info audio
|
|
1935
|
+
if (content.dpo_info && ttsAudioUrls.dpo_info_audio) {
|
|
1936
|
+
const dpoAudio = ttsAudioUrls.dpo_info_audio;
|
|
1937
|
+
DPO_AUDIO_ELEMENT_MAP.forEach(({ audioKey, elementId }) => {
|
|
1938
|
+
if (dpoAudio[audioKey]) {
|
|
1939
|
+
elementIds.push(elementId);
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
// 10. Confirm button text audio
|
|
1827
1945
|
if (ttsAudioUrls.confirm_button_text_audio_url) {
|
|
1828
1946
|
elementIds.push("confirm-button-text");
|
|
1829
1947
|
}
|
|
1830
1948
|
|
|
1831
|
-
//
|
|
1949
|
+
// 11. Decline button text audio
|
|
1832
1950
|
if (ttsAudioUrls.decline_button_text_audio_url) {
|
|
1833
1951
|
elementIds.push("decline-button-text");
|
|
1834
1952
|
}
|
|
@@ -1950,9 +2068,9 @@ export const RedactoNoticeConsent = ({
|
|
|
1950
2068
|
// Find which purpose we're on (rough estimate)
|
|
1951
2069
|
const purposesToProcess = categorizedPurposes
|
|
1952
2070
|
? [
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2071
|
+
...categorizedPurposes.alreadyConsented,
|
|
2072
|
+
...categorizedPurposes.needsConsent,
|
|
2073
|
+
]
|
|
1956
2074
|
: content.purposes || [];
|
|
1957
2075
|
|
|
1958
2076
|
let audioOffset = headerAudioCount;
|
|
@@ -2021,9 +2139,9 @@ export const RedactoNoticeConsent = ({
|
|
|
2021
2139
|
rect.top >= 0 &&
|
|
2022
2140
|
rect.left >= 0 &&
|
|
2023
2141
|
rect.bottom <=
|
|
2024
|
-
|
|
2142
|
+
(window.innerHeight || document.documentElement.clientHeight) &&
|
|
2025
2143
|
rect.right <=
|
|
2026
|
-
|
|
2144
|
+
(window.innerWidth || document.documentElement.clientWidth);
|
|
2027
2145
|
|
|
2028
2146
|
if (!isVisible) {
|
|
2029
2147
|
element.scrollIntoView({
|
|
@@ -2080,9 +2198,7 @@ export const RedactoNoticeConsent = ({
|
|
|
2080
2198
|
})
|
|
2081
2199
|
.catch((error) => {
|
|
2082
2200
|
// Handle errors gracefully - don't break the UI if TTS API fails
|
|
2083
|
-
// Only log if it's not an AbortError (which is expected when component unmounts or request is cancelled)
|
|
2084
2201
|
if (error instanceof Error && error.name !== "AbortError") {
|
|
2085
|
-
console.warn("Failed to fetch TTS audio URLs:", error.message);
|
|
2086
2202
|
setIsTTSAvailable(false);
|
|
2087
2203
|
setTtsAudioUrls(null);
|
|
2088
2204
|
}
|
|
@@ -2665,7 +2781,7 @@ export const RedactoNoticeConsent = ({
|
|
|
2665
2781
|
{getTranslatedText(
|
|
2666
2782
|
"privacy_policy_anchor_text",
|
|
2667
2783
|
content?.privacy_policy_anchor_text ||
|
|
2668
|
-
|
|
2784
|
+
"Privacy Policy"
|
|
2669
2785
|
)}
|
|
2670
2786
|
</a>
|
|
2671
2787
|
</span>
|
|
@@ -2746,12 +2862,142 @@ export const RedactoNoticeConsent = ({
|
|
|
2746
2862
|
onPurposeCollapse={togglePurposeCollapse}
|
|
2747
2863
|
onDataElementToggle={handleDataElementCheckboxChange}
|
|
2748
2864
|
getTranslatedText={getTranslatedText}
|
|
2749
|
-
isAlreadyConsented={
|
|
2865
|
+
isAlreadyConsented={isFullyConsentedReviewMode}
|
|
2750
2866
|
selectedLanguage={selectedLanguage}
|
|
2867
|
+
initialDataElementSelections={initialDataElementSelections}
|
|
2751
2868
|
/>
|
|
2752
2869
|
))}
|
|
2753
2870
|
</div>
|
|
2754
2871
|
)}
|
|
2872
|
+
|
|
2873
|
+
{(content?.privacy_center_url || content?.dpo_info) && (
|
|
2874
|
+
<div
|
|
2875
|
+
style={{
|
|
2876
|
+
...styles.privacyText,
|
|
2877
|
+
...responsiveStyles.privacyText,
|
|
2878
|
+
...componentTextStyle,
|
|
2879
|
+
fontSize: isMobile ? "13px" : "14px",
|
|
2880
|
+
lineHeight: "1.4",
|
|
2881
|
+
borderTop: "1px solid rgba(152, 162, 179, 0.3)",
|
|
2882
|
+
paddingTop: "12px",
|
|
2883
|
+
marginTop: "12px",
|
|
2884
|
+
}}
|
|
2885
|
+
>
|
|
2886
|
+
<p style={{ margin: 0, opacity: 0.6 }}>
|
|
2887
|
+
{content.privacy_center_url && (
|
|
2888
|
+
<>
|
|
2889
|
+
{content.additional_text && (
|
|
2890
|
+
<span id="additional-text">
|
|
2891
|
+
{getTranslatedText(
|
|
2892
|
+
"additional_text",
|
|
2893
|
+
content.additional_text
|
|
2894
|
+
)}
|
|
2895
|
+
</span>
|
|
2896
|
+
)}{" "}
|
|
2897
|
+
<a
|
|
2898
|
+
id="privacy-center-link"
|
|
2899
|
+
style={{ ...styles.link, ...linkStyle }}
|
|
2900
|
+
href={content.privacy_center_url}
|
|
2901
|
+
target="_blank"
|
|
2902
|
+
rel="noopener noreferrer"
|
|
2903
|
+
>
|
|
2904
|
+
{getTranslatedText(
|
|
2905
|
+
"privacy_center_anchor_text",
|
|
2906
|
+
content.privacy_center_anchor_text || "Privacy Center"
|
|
2907
|
+
)}
|
|
2908
|
+
</a>
|
|
2909
|
+
.
|
|
2910
|
+
</>
|
|
2911
|
+
)}
|
|
2912
|
+
{content.dpo_info && (
|
|
2913
|
+
<>
|
|
2914
|
+
{content.privacy_center_url && <br />}
|
|
2915
|
+
<span id="dpo-grievance-text">
|
|
2916
|
+
{getTranslatedDpoText(
|
|
2917
|
+
"grievance_text",
|
|
2918
|
+
content.dpo_info.grievance_text || ""
|
|
2919
|
+
)}
|
|
2920
|
+
</span>{" "}
|
|
2921
|
+
{content.dpo_info.grievance_url && (
|
|
2922
|
+
<a
|
|
2923
|
+
id="dpo-grievance-link"
|
|
2924
|
+
style={{ ...styles.link, ...linkStyle }}
|
|
2925
|
+
href={content.dpo_info.grievance_url}
|
|
2926
|
+
target="_blank"
|
|
2927
|
+
rel="noopener noreferrer"
|
|
2928
|
+
>
|
|
2929
|
+
{getTranslatedDpoText(
|
|
2930
|
+
"grievance_anchor_text",
|
|
2931
|
+
content.dpo_info.grievance_anchor_text || "click here"
|
|
2932
|
+
)}
|
|
2933
|
+
</a>
|
|
2934
|
+
)}
|
|
2935
|
+
{content.dpo_info.grievance_email && (
|
|
2936
|
+
<>
|
|
2937
|
+
{" "}
|
|
2938
|
+
<span id="dpo-grievance-email-connector">
|
|
2939
|
+
{getTranslatedDpoText(
|
|
2940
|
+
"grievance_email_connector_text",
|
|
2941
|
+
content.dpo_info.grievance_email_connector_text || "or email to"
|
|
2942
|
+
)}
|
|
2943
|
+
</span>
|
|
2944
|
+
{" "}
|
|
2945
|
+
<a
|
|
2946
|
+
id="dpo-grievance-email"
|
|
2947
|
+
style={{ ...styles.link, ...linkStyle }}
|
|
2948
|
+
href={`mailto:${content.dpo_info.grievance_email}`}
|
|
2949
|
+
>
|
|
2950
|
+
{content.dpo_info.grievance_email}
|
|
2951
|
+
</a>
|
|
2952
|
+
</>
|
|
2953
|
+
)}
|
|
2954
|
+
<br />
|
|
2955
|
+
<span id="dpo-dp-board-text">
|
|
2956
|
+
{getTranslatedDpoText(
|
|
2957
|
+
"dp_board_text",
|
|
2958
|
+
content.dpo_info.dp_board_text || ""
|
|
2959
|
+
)}
|
|
2960
|
+
</span>{" "}
|
|
2961
|
+
{content.dpo_info.dp_board_url && (
|
|
2962
|
+
<a
|
|
2963
|
+
id="dpo-dp-board-link"
|
|
2964
|
+
style={{ ...styles.link, ...linkStyle }}
|
|
2965
|
+
href={content.dpo_info.dp_board_url}
|
|
2966
|
+
target="_blank"
|
|
2967
|
+
rel="noopener noreferrer"
|
|
2968
|
+
>
|
|
2969
|
+
{getTranslatedDpoText(
|
|
2970
|
+
"dp_board_anchor_text",
|
|
2971
|
+
content.dpo_info.dp_board_anchor_text || "here"
|
|
2972
|
+
)}
|
|
2973
|
+
</a>
|
|
2974
|
+
)}
|
|
2975
|
+
<br />
|
|
2976
|
+
<span id="dpo-dpo-text">
|
|
2977
|
+
{getTranslatedDpoText(
|
|
2978
|
+
"dpo_text",
|
|
2979
|
+
content.dpo_info.dpo_text || ""
|
|
2980
|
+
)}
|
|
2981
|
+
</span>{" "}
|
|
2982
|
+
{content.dpo_info.dpo_url && (
|
|
2983
|
+
<a
|
|
2984
|
+
id="dpo-dpo-link"
|
|
2985
|
+
style={{ ...styles.link, ...linkStyle }}
|
|
2986
|
+
href={content.dpo_info.dpo_url}
|
|
2987
|
+
target="_blank"
|
|
2988
|
+
rel="noopener noreferrer"
|
|
2989
|
+
>
|
|
2990
|
+
{getTranslatedDpoText(
|
|
2991
|
+
"dpo_anchor_text",
|
|
2992
|
+
content.dpo_info.dpo_anchor_text || "Click here"
|
|
2993
|
+
)}
|
|
2994
|
+
</a>
|
|
2995
|
+
)}
|
|
2996
|
+
</>
|
|
2997
|
+
)}
|
|
2998
|
+
</p>
|
|
2999
|
+
</div>
|
|
3000
|
+
)}
|
|
2755
3001
|
</div>
|
|
2756
3002
|
<div
|
|
2757
3003
|
style={{
|
|
@@ -2760,53 +3006,72 @@ export const RedactoNoticeConsent = ({
|
|
|
2760
3006
|
...sectionStyle,
|
|
2761
3007
|
}}
|
|
2762
3008
|
>
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
3009
|
+
{isFullyConsentedReviewMode ? (
|
|
3010
|
+
<button
|
|
3011
|
+
ref={lastFocusableRef}
|
|
3012
|
+
id="review-mode-continue-button"
|
|
3013
|
+
style={{
|
|
3014
|
+
...styles.button,
|
|
3015
|
+
...responsiveStyles.button,
|
|
3016
|
+
...styles.acceptButton,
|
|
3017
|
+
...acceptButtonStyle,
|
|
3018
|
+
}}
|
|
3019
|
+
onClick={() => onAccept?.()}
|
|
3020
|
+
aria-label={reviewModeButtonText}
|
|
3021
|
+
>
|
|
3022
|
+
{reviewModeButtonText}
|
|
3023
|
+
</button>
|
|
3024
|
+
) : (
|
|
3025
|
+
<>
|
|
3026
|
+
<button
|
|
3027
|
+
ref={lastFocusableRef}
|
|
3028
|
+
id="confirm-button-text"
|
|
3029
|
+
style={{
|
|
3030
|
+
...styles.button,
|
|
3031
|
+
...responsiveStyles.button,
|
|
3032
|
+
...styles.acceptButton,
|
|
3033
|
+
...acceptButtonStyle,
|
|
3034
|
+
opacity: acceptDisabled ? 0.5 : 1,
|
|
3035
|
+
}}
|
|
3036
|
+
onClick={handleAccept}
|
|
3037
|
+
disabled={acceptDisabled || isSubmittingDecline}
|
|
3038
|
+
aria-label={getTranslatedText(
|
|
2783
3039
|
"confirm_button_text",
|
|
2784
3040
|
content?.confirm_button_text || "Accept"
|
|
2785
3041
|
)}
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
3042
|
+
>
|
|
3043
|
+
{isSubmittingAccept
|
|
3044
|
+
? "Processing..."
|
|
3045
|
+
: getTranslatedText(
|
|
3046
|
+
"confirm_button_text",
|
|
3047
|
+
content?.confirm_button_text || "Accept"
|
|
3048
|
+
)}
|
|
3049
|
+
</button>
|
|
3050
|
+
<button
|
|
3051
|
+
id="decline-button-text"
|
|
3052
|
+
style={{
|
|
3053
|
+
...styles.button,
|
|
3054
|
+
...responsiveStyles.button,
|
|
3055
|
+
...styles.cancelButton,
|
|
3056
|
+
...declineButtonStyle,
|
|
3057
|
+
opacity: isSubmittingAccept ? 0.5 : 1,
|
|
3058
|
+
}}
|
|
3059
|
+
onClick={handleDecline}
|
|
3060
|
+
disabled={isSubmittingDecline || isSubmittingAccept}
|
|
3061
|
+
aria-label={getTranslatedText(
|
|
2806
3062
|
"decline_button_text",
|
|
2807
3063
|
content?.decline_button_text || "Decline"
|
|
2808
3064
|
)}
|
|
2809
|
-
|
|
3065
|
+
>
|
|
3066
|
+
{isSubmittingDecline
|
|
3067
|
+
? "Processing..."
|
|
3068
|
+
: getTranslatedText(
|
|
3069
|
+
"decline_button_text",
|
|
3070
|
+
content?.decline_button_text || "Decline"
|
|
3071
|
+
)}
|
|
3072
|
+
</button>
|
|
3073
|
+
</>
|
|
3074
|
+
)}
|
|
2810
3075
|
</div>
|
|
2811
3076
|
</div>
|
|
2812
3077
|
)}
|