@redacto.io/consent-sdk-react 3.3.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.
@@ -59,6 +59,7 @@ export const fetchConsentContent = async ({
59
59
  language = "en",
60
60
  specific_uuid,
61
61
  validate_against = "all",
62
+ include_fully_consented_data = false,
62
63
  signal, // AbortSignal for request cancellation
63
64
  }: FetchConsentContentParams & { signal?: AbortSignal }): Promise<ConsentContent> => {
64
65
  try {
@@ -80,7 +81,7 @@ export const fetchConsentContent = async ({
80
81
  const apiBaseUrl = baseUrl || BASE_URL;
81
82
 
82
83
  // Create cache key for this request - include accessToken to ensure different users get different cache
83
- const cacheKey = `${accessToken}-${noticeId}-${validate_against}-${language}-${specific_uuid || ''}`;
84
+ const cacheKey = `${accessToken}-${noticeId}-${validate_against}-${language}-${specific_uuid || ''}-${include_fully_consented_data}`;
84
85
 
85
86
  // Check cache first (skip for reconsent requests as they need fresh data)
86
87
  if (validate_against === "all" && !specific_uuid) {
@@ -98,6 +99,9 @@ export const fetchConsentContent = async ({
98
99
  url.searchParams.append("specific_uuid", specific_uuid);
99
100
  }
100
101
  url.searchParams.append("validate_against", validate_against);
102
+ if (include_fully_consented_data) {
103
+ url.searchParams.append("include_fully_consented_data", "true");
104
+ }
101
105
 
102
106
  const response = await fetch(url.toString(), {
103
107
  method: "GET",
@@ -49,7 +49,7 @@ export type ConsentContent = {
49
49
  decline_button_text: string;
50
50
  logo_url: string;
51
51
  privacy_policy_url: string;
52
- sub_processors_url: string;
52
+ privacy_center_url: string;
53
53
  primary_color: string;
54
54
  secondary_color: string;
55
55
  font_preference: string;
@@ -75,28 +75,28 @@ export type ConsentContent = {
75
75
  confirm_button_text: string;
76
76
  decline_button_text: string;
77
77
  privacy_policy_prefix_text: string;
78
- vendor_list_prefix_text: string;
79
78
  privacy_policy_anchor_text: string;
80
- vendors_list_anchor_text: string;
79
+ privacy_center_anchor_text?: string;
81
80
  purpose_section_heading: string;
82
81
  notice_banner_heading: string;
83
82
  data_elements: {
84
83
  [key: string]: string;
85
84
  };
86
85
  purposes: {
87
- [key: string]: string;
86
+ [key: string]: string | { name: string; description: string };
88
87
  };
88
+ dpo_info?: DpoInfoTranslation;
89
89
  };
90
90
  };
91
91
  created_at: string;
92
92
  updated_at: string;
93
93
  deployed_at: string;
94
94
  privacy_policy_prefix_text: string;
95
- vendor_list_prefix_text: string;
96
95
  privacy_policy_anchor_text: string;
97
- vendors_list_anchor_text: string;
96
+ privacy_center_anchor_text?: string;
98
97
  purpose_section_heading: string;
99
98
  notice_banner_heading: string;
99
+ dpo_info?: DpoInfo;
100
100
  };
101
101
  notice_type?: string;
102
102
  compliance_requirement?: string;
@@ -154,6 +154,7 @@ export type FetchConsentContentParams = {
154
154
  language?: string;
155
155
  specific_uuid?: string;
156
156
  validate_against?: "all" | "required";
157
+ include_fully_consented_data?: boolean;
157
158
  };
158
159
 
159
160
  export type ConsentEventPayload = {
@@ -278,6 +279,31 @@ export type PurposeItemProps = {
278
279
  isAlreadyConsented: boolean;
279
280
  };
280
281
 
282
+ // DPO Info Types
283
+ export type DpoInfo = {
284
+ grievance_text: string;
285
+ grievance_anchor_text: string;
286
+ grievance_url: string;
287
+ grievance_email: string;
288
+ grievance_email_connector_text?: string;
289
+ dp_board_text: string;
290
+ dp_board_anchor_text: string;
291
+ dp_board_url: string;
292
+ dpo_text: string;
293
+ dpo_anchor_text: string;
294
+ dpo_url: string;
295
+ };
296
+
297
+ export type DpoInfoTranslation = {
298
+ grievance_text?: string;
299
+ grievance_anchor_text?: string;
300
+ grievance_email_connector_text?: string;
301
+ dp_board_text?: string;
302
+ dp_board_anchor_text?: string;
303
+ dpo_text?: string;
304
+ dpo_anchor_text?: string;
305
+ };
306
+
281
307
  // TTS Audio Types
282
308
  export type PurposeAudioUrls = {
283
309
  name_audio_url: string;
@@ -300,16 +326,29 @@ export type TTSAudioUrlsResponse = {
300
326
  confirm_button_text_audio_url: string;
301
327
  decline_button_text_audio_url: string;
302
328
  privacy_policy_prefix_text_audio_url: string;
303
- vendor_list_prefix_text_audio_url: string;
304
329
  privacy_policy_anchor_text_audio_url: string;
305
- vendors_list_anchor_text_audio_url: string;
330
+ privacy_center_anchor_text_audio_url?: string;
306
331
  purpose_section_heading_audio_url: string;
307
332
  notice_banner_heading_audio_url: string;
308
333
  purposes_audio: Record<string, PurposeAudioUrls>;
309
334
  data_elements_audio: Record<string, DataElementAudioUrls>;
335
+ dpo_info_audio?: DpoInfoAudio;
310
336
  };
311
337
  };
312
338
 
339
+ export type DpoInfoAudioKey = keyof DpoInfoAudio;
340
+
341
+ export type DpoInfoAudio = {
342
+ grievance_text_audio_url?: string;
343
+ grievance_anchor_text_audio_url?: string;
344
+ dp_board_text_audio_url?: string;
345
+ dp_board_anchor_text_audio_url?: string;
346
+ dpo_text_audio_url?: string;
347
+ dpo_anchor_text_audio_url?: string;
348
+ grievance_email_connector_text_audio_url?: string;
349
+ grievance_email_audio_url?: string;
350
+ };
351
+
313
352
  export type FetchTTSAudioUrlsParams = {
314
353
  accessToken: string;
315
354
  baseUrl?: string;
@@ -0,0 +1,20 @@
1
+ import type { DpoInfoAudioKey } from "./api/types";
2
+
3
+ /**
4
+ * Maps DPO info audio URL keys to their corresponding DOM element IDs.
5
+ * Used by both audioSequence and elementIdSequence builders to keep them in sync.
6
+ * Order determines playback sequence: grievance → dp board → dpo.
7
+ */
8
+ export const DPO_AUDIO_ELEMENT_MAP: ReadonlyArray<{
9
+ audioKey: DpoInfoAudioKey;
10
+ elementId: string;
11
+ }> = [
12
+ { audioKey: "grievance_text_audio_url", elementId: "dpo-grievance-text" },
13
+ { audioKey: "grievance_anchor_text_audio_url", elementId: "dpo-grievance-link" },
14
+ { audioKey: "grievance_email_connector_text_audio_url", elementId: "dpo-grievance-email-connector" },
15
+ { audioKey: "grievance_email_audio_url", elementId: "dpo-grievance-email" },
16
+ { audioKey: "dp_board_text_audio_url", elementId: "dpo-dp-board-text" },
17
+ { audioKey: "dp_board_anchor_text_audio_url", elementId: "dpo-dp-board-link" },
18
+ { audioKey: "dpo_text_audio_url", elementId: "dpo-dpo-text" },
19
+ { audioKey: "dpo_anchor_text_audio_url", elementId: "dpo-dpo-link" },
20
+ ] as const;
@@ -54,8 +54,7 @@ export const styles = {
54
54
  justifyContent: "space-between",
55
55
  alignItems: "center",
56
56
  paddingBottom: "15px",
57
- borderBottom: "1px solid #e5e7eb",
58
- boxShadow: "0 1px 2px rgba(0, 0, 0, 0.03)",
57
+ borderBottom: "none",
59
58
  backgroundColor: "#ffffff",
60
59
  margin: 0,
61
60
  boxSizing: "border-box",
@@ -114,7 +113,7 @@ export const styles = {
114
113
  },
115
114
 
116
115
  middleSection: {
117
- margin: "20px 0px",
116
+ margin: "0px",
118
117
  display: "flex",
119
118
  flexDirection: "column",
120
119
  gap: "12px",
@@ -130,7 +129,6 @@ export const styles = {
130
129
  fontSize: "16px",
131
130
  fontWeight: 400,
132
131
  lineHeight: "150%",
133
- letterSpacing: "0.2px",
134
132
  color: "#344054",
135
133
  margin: 0,
136
134
  padding: 0,
@@ -284,8 +282,7 @@ export const styles = {
284
282
  justifyContent: "space-between",
285
283
  gap: "29px",
286
284
  paddingTop: "15px",
287
- borderTop: "1px solid #e5e7eb",
288
- boxShadow: "0 -1px 2px rgba(0, 0, 0, 0.03)",
285
+ borderTop: "none",
289
286
  backgroundColor: "#ffffff",
290
287
  margin: 0,
291
288
  boxSizing: "border-box",
@@ -1,4 +1,4 @@
1
- import type { Settings } from "./api/types";
1
+ import type { Settings, DpoInfoTranslation } from "./api/types";
2
2
 
3
3
  export type Props = Readonly<{
4
4
  noticeId: string;
@@ -13,10 +13,14 @@ export type Props = Readonly<{
13
13
  onError?: (error: Error) => void;
14
14
  applicationId?: string;
15
15
  validateAgainst?: "all" | "required";
16
+ includeFullyConsentedData?: boolean;
17
+ reviewModeButtonText?: string;
16
18
  }>;
17
19
 
20
+ export type PurposeTranslation = string | { name: string; description: string };
21
+
18
22
  export type PurposeTranslations = {
19
- [purposeId: string]: string;
23
+ [purposeId: string]: PurposeTranslation;
20
24
  };
21
25
 
22
26
  export type DataElementTranslations = {
@@ -33,13 +37,15 @@ export type TranslationObject = {
33
37
  purpose_section_heading?: string;
34
38
  notice_banner_heading?: string;
35
39
  privacy_policy_prefix_text?: string;
36
- vendor_list_prefix_text?: string;
37
40
  privacy_policy_anchor_text?: string;
38
- vendors_list_anchor_text?: string;
41
+ privacy_center_anchor_text?: string;
42
+ dpo_info?: DpoInfoTranslation;
39
43
  [key: string]:
40
44
  | string
41
45
  | PurposeTranslations
42
46
  | DataElementTranslations
43
- | Record<string, string>
47
+ | DpoInfoTranslation
48
+ | Record<string, string | undefined>
49
+ | Record<string, string | { name: string; description: string }>
44
50
  | undefined;
45
51
  };
@@ -103,7 +103,7 @@ const RedactoConsentInlineComponent = ({
103
103
  ): string => {
104
104
  if (!content) return defaultText;
105
105
 
106
- if (selectedLanguage === content.default_language) {
106
+ if (selectedLanguage === "English" || selectedLanguage === "en" || selectedLanguage === "EN") {
107
107
  if (
108
108
  key === "privacy_policy_anchor_text" &&
109
109
  content.privacy_policy_anchor_text
@@ -127,7 +127,16 @@ const RedactoConsentInlineComponent = ({
127
127
 
128
128
  if (itemId) {
129
129
  if (key === "purposes.name" || key === "purposes.description") {
130
- return translationMap.purposes?.[itemId] || defaultText;
130
+ const purposeData = translationMap.purposes?.[itemId];
131
+ if (purposeData) {
132
+ if (typeof purposeData === "string") {
133
+ return key === "purposes.name" ? purposeData : defaultText;
134
+ }
135
+ return key === "purposes.name"
136
+ ? purposeData.name || defaultText
137
+ : purposeData.description || defaultText;
138
+ }
139
+ return defaultText;
131
140
  }
132
141
  if (key.startsWith("data_elements.")) {
133
142
  return translationMap.data_elements?.[itemId] || defaultText;
@@ -135,7 +144,7 @@ const RedactoConsentInlineComponent = ({
135
144
  }
136
145
 
137
146
  const value = translationMap[key];
138
- if (typeof value === "string") {
147
+ if (typeof value === "string" && value) {
139
148
  return value;
140
149
  }
141
150
 
@@ -1,3 +1,5 @@
1
+ import type { DpoInfo, DpoInfoTranslation } from "../../RedactoNoticeConsent/api/types";
2
+
1
3
  export type RedactoJwtPayload = {
2
4
  organisation_uuid: string;
3
5
  workspace_uuid: string;
@@ -33,7 +35,7 @@ export type ConsentContent = {
33
35
  decline_button_text: string;
34
36
  logo_url: string;
35
37
  privacy_policy_url: string;
36
- sub_processors_url: string;
38
+ privacy_center_url: string;
37
39
  primary_color: string;
38
40
  secondary_color: string;
39
41
  font_preference: string;
@@ -59,26 +61,26 @@ export type ConsentContent = {
59
61
  confirm_button_text: string;
60
62
  decline_button_text: string;
61
63
  privacy_policy_prefix_text: string;
62
- vendor_list_prefix_text: string;
63
64
  privacy_policy_anchor_text: string;
64
- vendors_list_anchor_text: string;
65
+ privacy_center_anchor_text?: string;
65
66
  purpose_section_heading: string;
66
67
  data_elements: {
67
68
  [key: string]: string;
68
69
  };
69
70
  purposes: {
70
- [key: string]: string;
71
+ [key: string]: string | { name: string; description: string };
71
72
  };
73
+ dpo_info?: DpoInfoTranslation;
72
74
  };
73
75
  };
74
76
  created_at: string;
75
77
  updated_at: string;
76
78
  deployed_at: string;
77
79
  privacy_policy_prefix_text: string;
78
- vendor_list_prefix_text: string;
79
80
  privacy_policy_anchor_text: string;
80
- vendors_list_anchor_text: string;
81
+ privacy_center_anchor_text?: string;
81
82
  purpose_section_heading: string;
83
+ dpo_info?: DpoInfo;
82
84
  };
83
85
  created_at: string;
84
86
  updated_at: string;
@@ -1,4 +1,5 @@
1
1
  import type { Settings } from "./api/types";
2
+ import type { DpoInfoTranslation } from "../RedactoNoticeConsent/api/types";
2
3
 
3
4
  export type Props = Readonly<{
4
5
  org_uuid: string;
@@ -16,8 +17,10 @@ export type Props = Readonly<{
16
17
  applicationId?: string;
17
18
  }>;
18
19
 
20
+ export type PurposeTranslation = string | { name: string; description: string };
21
+
19
22
  export type PurposeTranslations = {
20
- [purposeId: string]: string;
23
+ [purposeId: string]: PurposeTranslation;
21
24
  };
22
25
 
23
26
  export type DataElementTranslations = {
@@ -33,13 +36,15 @@ export type TranslationObject = {
33
36
  data_elements?: DataElementTranslations;
34
37
  purpose_section_heading?: string;
35
38
  privacy_policy_prefix_text?: string;
36
- vendor_list_prefix_text?: string;
37
39
  privacy_policy_anchor_text?: string;
38
- vendors_list_anchor_text?: string;
40
+ privacy_center_anchor_text?: string;
41
+ dpo_info?: DpoInfoTranslation;
39
42
  [key: string]:
40
43
  | string
41
44
  | PurposeTranslations
42
45
  | DataElementTranslations
43
- | Record<string, string>
46
+ | DpoInfoTranslation
47
+ | Record<string, string | undefined>
48
+ | Record<string, string | { name: string; description: string }>
44
49
  | undefined;
45
50
  };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * React 19 JSX compatibility: ensures intrinsic elements (div, button, etc.)
3
+ * and Element type align with React's types to fix "Element is not assignable to ReactNode".
4
+ * @see https://github.com/DefinitelyTyped/DefinitelyTyped/discussions/71395
5
+ */
6
+ import type React from "react";
7
+
8
+ declare global {
9
+ namespace JSX {
10
+ type Element = React.ReactElement;
11
+ }
12
+ }
13
+
14
+ declare module "react/jsx-runtime" {
15
+ namespace JSX {
16
+ type Element = React.ReactElement;
17
+ interface IntrinsicElements extends React.JSX.IntrinsicElements {}
18
+ }
19
+ }
20
+
21
+ declare module "react/jsx-dev-runtime" {
22
+ namespace JSX {
23
+ type Element = React.ReactElement;
24
+ interface IntrinsicElements extends React.JSX.IntrinsicElements {}
25
+ }
26
+ }
package/tests/mocks.ts CHANGED
@@ -24,16 +24,28 @@ export const mockConsentContent: ConsentContent = {
24
24
  decline_button_text: "Decline",
25
25
  logo_url: "https://example.com/logo.png",
26
26
  privacy_policy_url: "https://example.com/privacy",
27
- sub_processors_url: "https://example.com/vendors",
27
+ privacy_center_url: "https://example.com/privacy-center",
28
28
  primary_color: "#4f87ff",
29
29
  secondary_color: "#ffffff",
30
30
  font_preference: "Arial",
31
31
  privacy_policy_prefix_text: "",
32
- vendor_list_prefix_text: "",
33
32
  privacy_policy_anchor_text: "Privacy Policy",
34
- vendors_list_anchor_text: "Vendors List",
33
+ privacy_center_anchor_text: "Privacy Center",
35
34
  purpose_section_heading: "Manage Consent",
36
35
  notice_banner_heading: "Privacy Notice",
36
+ dpo_info: {
37
+ grievance_text: "If you have any grievances related to data processing,",
38
+ grievance_anchor_text: "click here",
39
+ grievance_url: "https://example.com/grievance",
40
+ grievance_email: "grievance@example.com",
41
+ grievance_email_connector_text: "or email to",
42
+ dp_board_text: "In case we were not able to resolve your grievance, you can reach out to the Data Protection Board",
43
+ dp_board_anchor_text: "here",
44
+ dp_board_url: "https://example.com/dp-board",
45
+ dpo_text: "To connect with the Data Protection Officer,",
46
+ dpo_anchor_text: "Click here",
47
+ dpo_url: "https://example.com/dpo",
48
+ },
37
49
  created_at: "2023-01-01T00:00:00Z",
38
50
  updated_at: "2023-01-01T00:00:00Z",
39
51
  deployed_at: "2023-01-01T00:00:00Z",
@@ -42,21 +54,28 @@ export const mockConsentContent: ConsentContent = {
42
54
  notice_text: "Nos importa tu privacidad.",
43
55
  additional_text: "",
44
56
  privacy_policy_prefix_text: "",
45
- vendor_list_prefix_text: "",
46
57
  privacy_policy_anchor_text: "Política de Privacidad",
47
- vendors_list_anchor_text: "Lista de Proveedores",
48
58
  confirm_button_text: "Aceptar",
49
59
  decline_button_text: "Rechazar",
50
60
  purpose_section_heading: "Gestionar Consentimiento",
51
61
  notice_banner_heading: "Aviso de Privacidad",
62
+ privacy_center_anchor_text: "Centro de Privacidad",
52
63
  purposes: {
53
- "purpose-1": "Marketing",
54
- "purpose-1-description": "Usamos datos para marketing.",
64
+ "purpose-1": { name: "Marketing", description: "Usamos datos para marketing." },
55
65
  },
56
66
  data_elements: {
57
67
  "element-1": "Email",
58
68
  "element-2": "Phone",
59
69
  },
70
+ dpo_info: {
71
+ grievance_text: "Si tiene alguna queja relacionada con el procesamiento de datos,",
72
+ grievance_anchor_text: "haga clic aquí",
73
+ grievance_email_connector_text: "o envíe un correo a",
74
+ dp_board_text: "En caso de que no hayamos podido resolver su queja, puede comunicarse con la Junta de Protección de Datos",
75
+ dp_board_anchor_text: "aquí",
76
+ dpo_text: "Para comunicarse con el Oficial de Protección de Datos,",
77
+ dpo_anchor_text: "Haga clic aquí",
78
+ },
60
79
  },
61
80
  },
62
81
  purposes: [
@@ -176,6 +195,33 @@ export const mockReconsentContent: ConsentContent = {
176
195
  },
177
196
  } as const;
178
197
 
198
+ export const mockMagicLinkContent: ConsentContent = {
199
+ ...mockConsentContent,
200
+ detail: {
201
+ ...mockConsentContent.detail,
202
+ reconsent_required: false,
203
+ purpose_selections: {
204
+ "purpose-1": {
205
+ selected: true,
206
+ status: "ACTIVE",
207
+ needs_reconsent: false,
208
+ data_elements: {
209
+ "element-1": {
210
+ selected: true,
211
+ enabled: true,
212
+ required: false,
213
+ },
214
+ "element-2": {
215
+ selected: true,
216
+ enabled: true,
217
+ required: true,
218
+ },
219
+ },
220
+ },
221
+ },
222
+ },
223
+ } as const;
224
+
179
225
  export const mockMinorConsentContent: ConsentContent = {
180
226
  ...mockConsentContent,
181
227
  detail: {
package/tsconfig.json CHANGED
@@ -8,11 +8,7 @@
8
8
  "moduleResolution": "node",
9
9
  "esModuleInterop": true,
10
10
  "skipLibCheck": true,
11
- "plugins": [
12
- {
13
- "name": "typescript-plugin-css-modules"
14
- }
15
- ]
11
+ "plugins": [{ "name": "typescript-plugin-css-modules" }]
16
12
  },
17
13
  "include": ["src"]
18
14
  }