@redacto.io/consent-sdk-react 3.1.0 → 3.3.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.
@@ -45,7 +45,10 @@ import { useMediaQuery } from "./useMediaQuery";
45
45
  * Supports both regular consent and reconsent UI modes.
46
46
  */
47
47
  const PurposeItem: React.FC<
48
- PurposeItemProps & { selectedLanguage: string }
48
+ PurposeItemProps & {
49
+ selectedLanguage: string;
50
+ initialDataElementSelections?: Record<string, boolean>;
51
+ }
49
52
  > = ({
50
53
  purpose,
51
54
  selectedPurposes,
@@ -60,6 +63,7 @@ const PurposeItem: React.FC<
60
63
  getTranslatedText,
61
64
  isAlreadyConsented,
62
65
  selectedLanguage,
66
+ initialDataElementSelections = {},
63
67
  }) => {
64
68
  const headingStyle = useMemo(
65
69
  () => ({
@@ -266,39 +270,71 @@ const PurposeItem: React.FC<
266
270
  />
267
271
  )}
268
272
  {isAlreadyConsented && (
269
- <div
270
- style={{
271
- display: "flex",
272
- alignItems: "center",
273
- justifyContent: "center",
274
- width: "16px",
275
- height: "16px",
276
- borderRadius: "50%",
277
- backgroundColor: "#10B981",
278
- }}
279
- aria-label={`Already consented to ${getTranslatedText(
280
- `data_elements.name`,
281
- dataElement.name,
282
- dataElement.uuid
283
- )}`}
284
- >
285
- <svg
286
- width="10"
287
- height="10"
288
- viewBox="0 0 10 10"
289
- fill="none"
290
- xmlns="http://www.w3.org/2000/svg"
291
- aria-hidden="true"
292
- >
293
- <path
294
- d="M3 5L4.5 6.5L7.5 3.5"
295
- stroke="white"
296
- strokeWidth="1.5"
297
- strokeLinecap="round"
298
- strokeLinejoin="round"
299
- />
300
- </svg>
301
- </div>
273
+ <>
274
+ {(() => {
275
+ const combinedId = `${purpose.uuid}-${dataElement.uuid}`;
276
+ const isCurrentlySelected =
277
+ selectedDataElements[combinedId] || false;
278
+ const wasOriginallySelected =
279
+ initialDataElementSelections[combinedId] || false;
280
+
281
+ // Show green tick only if it was originally selected
282
+ // Show checkbox if it's newly selected or unselected
283
+ if (isCurrentlySelected && wasOriginallySelected) {
284
+ return (
285
+ <div
286
+ style={{
287
+ display: "flex",
288
+ alignItems: "center",
289
+ justifyContent: "center",
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
+ );
335
+ }
336
+ })()}
337
+ </>
302
338
  )}
303
339
  </div>
304
340
  ))}
@@ -776,6 +812,8 @@ export const RedactoNoticeConsent = ({
776
812
  // Loading and submission states
777
813
  const [isLoading, setIsLoading] = useState(true);
778
814
  const [isSubmitting, setIsSubmitting] = useState(false);
815
+ const [isSubmittingAccept, setIsSubmittingAccept] = useState(false);
816
+ const [isSubmittingDecline, setIsSubmittingDecline] = useState(false);
779
817
 
780
818
  // Content and consent data
781
819
  const [content, setContent] = useState<
@@ -793,6 +831,9 @@ export const RedactoNoticeConsent = ({
793
831
  const [selectedDataElements, setSelectedDataElements] = useState<
794
832
  Record<string, boolean>
795
833
  >({});
834
+ // Track initial data element selections for already-consented purposes
835
+ const [initialDataElementSelections, setInitialDataElementSelections] =
836
+ useState<Record<string, boolean>>({});
796
837
  const [collapsedPurposes, setCollapsedPurposes] = useState<
797
838
  Record<string, boolean>
798
839
  >({});
@@ -987,11 +1028,14 @@ export const RedactoNoticeConsent = ({
987
1028
  setCategorizedPurposes(null);
988
1029
  setSelectedPurposes({});
989
1030
  setSelectedDataElements({});
1031
+ setInitialDataElementSelections({});
990
1032
  setCollapsedPurposes({});
991
1033
  setHasAlreadyConsented(false);
992
1034
  setErrorMessage(null);
993
1035
  setFetchError(null);
994
1036
  setIsLoading(true);
1037
+ setIsSubmittingAccept(false);
1038
+ setIsSubmittingDecline(false);
995
1039
 
996
1040
  // Clear age verification and guardian form state
997
1041
  setShowAgeVerification(false);
@@ -1119,17 +1163,16 @@ export const RedactoNoticeConsent = ({
1119
1163
  validate_against: validateAgainst,
1120
1164
  signal: abortControllerRef.current?.signal,
1121
1165
  });
1122
- setContent(consentContentData.detail.active_config);
1123
- if (consentContentData.detail.active_config.default_language) {
1166
+ const activeConfig = consentContentData.detail.active_config;
1167
+ setContent(activeConfig);
1168
+ if (activeConfig.default_language) {
1124
1169
  // Normalize "en" to "English" for consistency
1125
- const defaultLang =
1126
- consentContentData.detail.active_config.default_language;
1170
+ const defaultLang = activeConfig.default_language;
1127
1171
  setSelectedLanguage(
1128
1172
  defaultLang === "en" || defaultLang === "EN" ? "English" : defaultLang
1129
1173
  );
1130
1174
  }
1131
- const initialCollapsedState =
1132
- consentContentData.detail.active_config.purposes.reduce(
1175
+ const initialCollapsedState = activeConfig.purposes.reduce(
1133
1176
  (acc, purpose) => ({
1134
1177
  ...acc,
1135
1178
  [purpose.uuid]: true,
@@ -1145,8 +1188,7 @@ export const RedactoNoticeConsent = ({
1145
1188
  // Categorize purposes for reconsent UI
1146
1189
  const newCategorizedPurposes = isReconsentRequired
1147
1190
  ? {
1148
- alreadyConsented:
1149
- consentContentData.detail.active_config.purposes.filter(
1191
+ alreadyConsented: activeConfig.purposes.filter(
1150
1192
  (purpose) => {
1151
1193
  const selection = purposeSelections?.[purpose.uuid];
1152
1194
  return (
@@ -1157,8 +1199,7 @@ export const RedactoNoticeConsent = ({
1157
1199
  );
1158
1200
  }
1159
1201
  ),
1160
- needsConsent:
1161
- consentContentData.detail.active_config.purposes.filter(
1202
+ needsConsent: activeConfig.purposes.filter(
1162
1203
  (purpose) => {
1163
1204
  const selection = purposeSelections?.[purpose.uuid];
1164
1205
  return (
@@ -1174,7 +1215,7 @@ export const RedactoNoticeConsent = ({
1174
1215
  setCategorizedPurposes(newCategorizedPurposes);
1175
1216
 
1176
1217
  const initialPurposeState: Record<string, boolean> =
1177
- consentContentData.detail.active_config.purposes.reduce(
1218
+ activeConfig.purposes.reduce(
1178
1219
  (acc: Record<string, boolean>, purpose) => {
1179
1220
  if (isReconsentRequired && purposeSelections?.[purpose.uuid]) {
1180
1221
  // Pre-populate with existing selection for reconsent
@@ -1190,7 +1231,7 @@ export const RedactoNoticeConsent = ({
1190
1231
  setSelectedPurposes(initialPurposeState);
1191
1232
 
1192
1233
  const initialDataElementState: Record<string, boolean> =
1193
- consentContentData.detail.active_config.purposes.reduce(
1234
+ activeConfig.purposes.reduce(
1194
1235
  (acc: Record<string, boolean>, purpose) => {
1195
1236
  purpose.data_elements.forEach((element) => {
1196
1237
  const combinedId = `${purpose.uuid}-${element.uuid}`;
@@ -1213,6 +1254,27 @@ export const RedactoNoticeConsent = ({
1213
1254
  {}
1214
1255
  );
1215
1256
  setSelectedDataElements(initialDataElementState);
1257
+ // Store initial selections for already-consented purposes to track modifications
1258
+ if (isReconsentRequired && newCategorizedPurposes) {
1259
+ const initialSelections: Record<string, boolean> = {};
1260
+ newCategorizedPurposes.alreadyConsented.forEach((purpose) => {
1261
+ purpose.data_elements.forEach((element) => {
1262
+ const combinedId = `${purpose.uuid}-${element.uuid}`;
1263
+ if (
1264
+ purposeSelections?.[purpose.uuid]?.data_elements?.[element.uuid]
1265
+ ) {
1266
+ initialSelections[combinedId] =
1267
+ purposeSelections[purpose.uuid].data_elements[element.uuid]
1268
+ .selected;
1269
+ } else {
1270
+ initialSelections[combinedId] = false;
1271
+ }
1272
+ });
1273
+ });
1274
+ setInitialDataElementSelections(initialSelections);
1275
+ } else {
1276
+ setInitialDataElementSelections({});
1277
+ }
1216
1278
 
1217
1279
  // Check if age verification is required - set this after all content is loaded
1218
1280
  // but before setIsLoading(false) to ensure proper render order
@@ -1228,7 +1290,6 @@ export const RedactoNoticeConsent = ({
1228
1290
  return; // Don't update state for cancelled requests
1229
1291
  }
1230
1292
 
1231
- console.error(err);
1232
1293
  const error = err as Error & { status?: number };
1233
1294
  setFetchError(error);
1234
1295
  if (error.status === 409) {
@@ -1350,24 +1411,52 @@ export const RedactoNoticeConsent = ({
1350
1411
  const submitController = new AbortController();
1351
1412
 
1352
1413
  try {
1353
- setIsSubmitting(true);
1414
+ setIsSubmittingAccept(true);
1415
+ setIsSubmitting(true); // Keep for backward compatibility with acceptDisabled
1354
1416
  setErrorMessage(null);
1355
1417
  if (content) {
1356
1418
  await submitConsentEvent({
1357
1419
  accessToken,
1358
1420
  baseUrl,
1359
1421
  noticeUuid: content.notice_uuid,
1360
- purposes: content.purposes.map((purpose) => ({
1361
- ...purpose,
1362
- selected: selectedPurposes[purpose.uuid] || false,
1363
- data_elements: purpose.data_elements.map((element) => ({
1364
- ...element,
1365
- selected: element.required
1366
- ? true
1367
- : selectedDataElements[`${purpose.uuid}-${element.uuid}`] ||
1368
- false,
1369
- })),
1370
- })),
1422
+ purposes: content.purposes.map((purpose) => {
1423
+ // Check if this is an already-consented purpose that was modified
1424
+ const isAlreadyConsented =
1425
+ categorizedPurposes?.alreadyConsented.some(
1426
+ (p) => p.uuid === purpose.uuid
1427
+ ) || false;
1428
+
1429
+ let purposeSelected = selectedPurposes[purpose.uuid] || false;
1430
+
1431
+ // If it's an already-consented purpose, check if any data elements were modified
1432
+ if (isAlreadyConsented) {
1433
+ const wasModified = purpose.data_elements.some((element) => {
1434
+ const combinedId = `${purpose.uuid}-${element.uuid}`;
1435
+ const currentSelection =
1436
+ selectedDataElements[combinedId] || false;
1437
+ const initialSelection =
1438
+ initialDataElementSelections[combinedId] || false;
1439
+ return currentSelection !== initialSelection;
1440
+ });
1441
+
1442
+ // If modified, treat as new selection (mark purpose as selected)
1443
+ if (wasModified) {
1444
+ purposeSelected = true;
1445
+ }
1446
+ }
1447
+
1448
+ return {
1449
+ ...purpose,
1450
+ selected: purposeSelected,
1451
+ data_elements: purpose.data_elements.map((element) => ({
1452
+ ...element,
1453
+ selected: element.required
1454
+ ? true
1455
+ : selectedDataElements[`${purpose.uuid}-${element.uuid}`] ||
1456
+ false,
1457
+ })),
1458
+ };
1459
+ }),
1371
1460
  declined: false,
1372
1461
  meta_data: applicationId
1373
1462
  ? { specific_uuid: applicationId }
@@ -1382,45 +1471,50 @@ export const RedactoNoticeConsent = ({
1382
1471
  return;
1383
1472
  }
1384
1473
 
1385
- console.error("Error submitting consent:", error);
1386
1474
  const err = error as Error & { status?: number };
1387
- if (err.status === 500) {
1388
- setErrorMessage(
1389
- "An error occurred while submitting your consent. Please try again later."
1390
- );
1391
- }
1475
+ const message =
1476
+ err.status === 500
1477
+ ? "An error occurred while submitting your consent. Please try again later."
1478
+ : err.message || "Failed to submit consent. Please try again.";
1479
+ setErrorMessage(message);
1392
1480
  onError?.(error as Error);
1393
1481
  } finally {
1394
- setIsSubmitting(false);
1482
+ setIsSubmittingAccept(false);
1483
+ setIsSubmitting(false); // Keep for backward compatibility
1395
1484
  }
1396
1485
  };
1397
1486
 
1398
1487
  const handleDecline = useCallback(async () => {
1399
- // try {
1400
- // setIsSubmitting(true);
1401
- // if (content) {
1402
- // await submitConsentEvent(
1403
- // accessToken,
1404
- // content.notice_uuid,
1405
- // content.purposes.map((purpose) => ({
1406
- // ...purpose,
1407
- // selected: false,
1408
- // data_elements: purpose.data_elements.map((element) => ({
1409
- // ...element,
1410
- // selected: element.required ? true : false,
1411
- // })),
1412
- // })),
1413
- // true
1414
- // );
1415
- // }
1416
- // onDecline?.();
1417
- // } catch (error) {
1418
- // console.error("Error submitting consent:", error);
1419
- // onError?.(error as Error);
1420
- // } finally {
1421
- // setIsSubmitting(false);
1422
- // }
1423
- onDecline?.();
1488
+ try {
1489
+ setIsSubmittingDecline(true);
1490
+ setIsSubmitting(true); // Keep for backward compatibility
1491
+ // try {
1492
+ // if (content) {
1493
+ // await submitConsentEvent(
1494
+ // accessToken,
1495
+ // content.notice_uuid,
1496
+ // content.purposes.map((purpose) => ({
1497
+ // ...purpose,
1498
+ // selected: false,
1499
+ // data_elements: purpose.data_elements.map((element) => ({
1500
+ // ...element,
1501
+ // selected: element.required ? true : false,
1502
+ // })),
1503
+ // })),
1504
+ // true
1505
+ // );
1506
+ // }
1507
+ // } catch (error) {
1508
+ // console.error("Error submitting consent:", error);
1509
+ // onError?.(error as Error);
1510
+ // } finally {
1511
+ // setIsSubmitting(false);
1512
+ // }
1513
+ onDecline?.();
1514
+ } finally {
1515
+ setIsSubmittingDecline(false);
1516
+ setIsSubmitting(false); // Keep for backward compatibility
1517
+ }
1424
1518
  }, [onDecline]);
1425
1519
 
1426
1520
  // Guardian form handlers
@@ -1981,9 +2075,7 @@ export const RedactoNoticeConsent = ({
1981
2075
  })
1982
2076
  .catch((error) => {
1983
2077
  // Handle errors gracefully - don't break the UI if TTS API fails
1984
- // Only log if it's not an AbortError (which is expected when component unmounts or request is cancelled)
1985
2078
  if (error instanceof Error && error.name !== "AbortError") {
1986
- console.warn("Failed to fetch TTS audio URLs:", error.message);
1987
2079
  setIsTTSAvailable(false);
1988
2080
  setTtsAudioUrls(null);
1989
2081
  }
@@ -2606,6 +2698,7 @@ export const RedactoNoticeConsent = ({
2606
2698
  getTranslatedText={getTranslatedText}
2607
2699
  isAlreadyConsented={true}
2608
2700
  selectedLanguage={selectedLanguage}
2701
+ initialDataElementSelections={initialDataElementSelections}
2609
2702
  />
2610
2703
  ))}
2611
2704
 
@@ -2671,13 +2764,13 @@ export const RedactoNoticeConsent = ({
2671
2764
  opacity: acceptDisabled ? 0.5 : 1,
2672
2765
  }}
2673
2766
  onClick={handleAccept}
2674
- disabled={acceptDisabled}
2767
+ disabled={acceptDisabled || isSubmittingDecline}
2675
2768
  aria-label={getTranslatedText(
2676
2769
  "confirm_button_text",
2677
2770
  content?.confirm_button_text || "Accept"
2678
2771
  )}
2679
2772
  >
2680
- {isSubmitting
2773
+ {isSubmittingAccept
2681
2774
  ? "Processing..."
2682
2775
  : getTranslatedText(
2683
2776
  "confirm_button_text",
@@ -2691,15 +2784,16 @@ export const RedactoNoticeConsent = ({
2691
2784
  ...responsiveStyles.button,
2692
2785
  ...styles.cancelButton,
2693
2786
  ...declineButtonStyle,
2787
+ opacity: isSubmittingAccept ? 0.5 : 1,
2694
2788
  }}
2695
2789
  onClick={handleDecline}
2696
- disabled={isSubmitting}
2790
+ disabled={isSubmittingDecline || isSubmittingAccept}
2697
2791
  aria-label={getTranslatedText(
2698
2792
  "decline_button_text",
2699
2793
  content?.decline_button_text || "Decline"
2700
2794
  )}
2701
2795
  >
2702
- {isSubmitting
2796
+ {isSubmittingDecline
2703
2797
  ? "Processing..."
2704
2798
  : getTranslatedText(
2705
2799
  "decline_button_text",
@@ -5,11 +5,13 @@ import type {
5
5
  FetchConsentContentParams,
6
6
  FetchTTSAudioUrlsParams,
7
7
  GuardianInfoPayload,
8
+ LegacyConsentEventPayload,
8
9
  RedactoJwtPayload,
9
10
  SubmitConsentEventParams,
10
11
  SubmitGuardianInfoParams,
11
12
  TTSAudioUrlsResponse,
12
13
  } from "./types";
14
+ import { isLegacyApi } from "../../utils/isLegacyApi";
13
15
 
14
16
  const BASE_URL = "https://api.redacto.io/consent";
15
17
 
@@ -45,16 +47,6 @@ function decodeTokenSafely(token: string): RedactoJwtPayload | null {
45
47
  }
46
48
  }
47
49
 
48
- // Abstract localStorage access for better testability
49
- const storage = {
50
- getItem: (key: string): string | null => {
51
- if (typeof window !== 'undefined') {
52
- return localStorage.getItem(key);
53
- }
54
- return null;
55
- }
56
- };
57
-
58
50
  // Function to clear API cache (useful for testing or when switching users)
59
51
  export const clearApiCache = (): void => {
60
52
  apiCache.clear();
@@ -146,8 +138,6 @@ export const fetchConsentContent = async ({
146
138
 
147
139
  return data;
148
140
  } catch (error) {
149
- // Log error for debugging but don't expose sensitive information
150
- console.error("Error fetching consent content:", error instanceof Error ? error.message : error);
151
141
  throw error;
152
142
  }
153
143
  };
@@ -177,48 +167,72 @@ export const submitConsentEvent = async ({
177
167
  throw new Error("Invalid token: missing organization or workspace UUID");
178
168
  }
179
169
 
180
- const primaryEmail = storage.getItem("userEmail");
170
+ const apiBaseUrl = baseUrl || BASE_URL;
181
171
 
182
- // Validate purposes data
183
- const validatedPurposes = purposes.map((purpose) => {
184
- if (!purpose.uuid || !purpose.name) {
185
- throw new Error(`Invalid purpose data: missing uuid or name`);
186
- }
172
+ let payload: ConsentEventPayload | LegacyConsentEventPayload;
173
+
174
+ if (isLegacyApi(apiBaseUrl)) {
175
+ // @deprecated Legacy payload for production (.io) backend
176
+ const primaryEmail = typeof window !== "undefined" ? localStorage.getItem("userEmail") : null;
177
+ payload = {
178
+ primary_email: primaryEmail || undefined,
179
+ notice_uuid: noticeUuid,
180
+ source: "WEB",
181
+ declined: Boolean(declined),
182
+ consents: {
183
+ purposes: purposes.map((purpose) => {
184
+ if (!purpose.uuid) {
185
+ throw new Error(`Invalid purpose data: missing uuid`);
186
+ }
187
+ return {
188
+ uuid: purpose.uuid,
189
+ name: purpose.name,
190
+ description: purpose.description || "",
191
+ industries: purpose.industries || "",
192
+ selected: Boolean(purpose.selected),
193
+ data_elements: (purpose.data_elements || []).map((element) => ({
194
+ uuid: element.uuid,
195
+ name: element.name,
196
+ enabled: Boolean(element.enabled),
197
+ required: Boolean(element.required),
198
+ selected: element.required ? true : Boolean(element.selected),
199
+ })),
200
+ };
201
+ }),
202
+ },
203
+ meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid } : undefined,
204
+ };
205
+ } else {
206
+ // New payload for staging (.tech) / localhost
207
+ const validatedPurposes = purposes.map((purpose) => {
208
+ if (!purpose.uuid) {
209
+ throw new Error(`Invalid purpose data: missing uuid`);
210
+ }
187
211
 
188
- return {
189
- uuid: purpose.uuid,
190
- name: purpose.name,
191
- description: purpose.description || "",
192
- industries: purpose.industries || "",
193
- selected: Boolean(purpose.selected),
194
- data_elements: (purpose.data_elements || []).map((element) => {
195
- if (!element.uuid || !element.name) {
196
- throw new Error(`Invalid data element: missing uuid or name`);
197
- }
198
-
199
- return {
212
+ // Only include enabled data elements (disabled elements are not selectable)
213
+ const dataElements = (purpose.data_elements || [])
214
+ .filter((element) => element.enabled)
215
+ .map((element) => ({
200
216
  uuid: element.uuid,
201
- name: element.name,
202
- enabled: Boolean(element.enabled),
203
- required: Boolean(element.required),
204
217
  selected: element.required ? true : Boolean(element.selected),
205
- };
206
- }),
207
- };
208
- });
218
+ }));
209
219
 
210
- const payload: ConsentEventPayload = {
211
- primary_email: primaryEmail || undefined,
212
- notice_uuid: noticeUuid,
213
- source: "WEB",
214
- declined: Boolean(declined),
215
- consents: {
216
- purposes: validatedPurposes,
217
- },
218
- meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid } : undefined,
219
- };
220
+ return {
221
+ purpose_uuid: purpose.uuid,
222
+ selected: Boolean(purpose.selected),
223
+ data_elements: dataElements.length > 0 ? dataElements : undefined,
224
+ };
225
+ });
220
226
 
221
- const apiBaseUrl = baseUrl || BASE_URL;
227
+ payload = {
228
+ notice_uuid: noticeUuid,
229
+ purposes: validatedPurposes,
230
+ select_all_mandatory: false,
231
+ source: "WEB",
232
+ declined: Boolean(declined),
233
+ meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid } : undefined,
234
+ };
235
+ }
222
236
 
223
237
  const response = await fetch(
224
238
  `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/submit-consent`,
@@ -257,8 +271,6 @@ export const submitConsentEvent = async ({
257
271
  apiCache.clear();
258
272
 
259
273
  } catch (error) {
260
- // Log error for debugging but don't expose sensitive information
261
- console.error("Error submitting consent event:", error instanceof Error ? error.message : error);
262
274
  throw error;
263
275
  }
264
276
  };
@@ -414,8 +426,6 @@ export const fetchTTSAudioUrls = async ({
414
426
 
415
427
  return data;
416
428
  } catch (error) {
417
- // Log error for debugging but don't expose sensitive information
418
- console.error("Error fetching TTS audio URLs:", error instanceof Error ? error.message : error);
419
429
  throw error;
420
430
  }
421
431
  };
@@ -157,9 +157,26 @@ export type FetchConsentContentParams = {
157
157
  };
158
158
 
159
159
  export type ConsentEventPayload = {
160
- org_user_id?: string;
160
+ notice_uuid: string;
161
+ purposes: Array<{
162
+ purpose_uuid: string;
163
+ selected: boolean;
164
+ data_elements?: Array<{
165
+ uuid: string;
166
+ selected: boolean;
167
+ }>;
168
+ }>;
169
+ select_all_mandatory: boolean;
170
+ source?: string;
171
+ declined?: boolean;
172
+ meta_data?: {
173
+ specific_uuid: string;
174
+ };
175
+ };
176
+
177
+ // @deprecated Remove when production backend supports the new payload format.
178
+ export type LegacyConsentEventPayload = {
161
179
  primary_email?: string;
162
- primary_mobile?: string;
163
180
  notice_uuid: string;
164
181
  source: string;
165
182
  declined: boolean;
@@ -179,9 +196,7 @@ export type ConsentEventPayload = {
179
196
  }>;
180
197
  }>;
181
198
  };
182
- meta_data?: {
183
- specific_uuid: string;
184
- };
199
+ meta_data?: { specific_uuid: string };
185
200
  };
186
201
 
187
202
  export type DataElement = {