@redacto.io/consent-sdk-react 10.1.0 → 10.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -420,6 +420,9 @@ var submitConsentEvent = async ({
420
420
  }));
421
421
  return {
422
422
  purpose_uuid: purpose.uuid,
423
+ // Omitted on a single-product notice, where the ledger applies the
424
+ // selection to every product the purpose covers.
425
+ product_uuid: purpose.product_uuid || void 0,
423
426
  selected: Boolean(purpose.selected),
424
427
  data_elements: dataElements.length > 0 ? dataElements : void 0
425
428
  };
@@ -679,6 +682,9 @@ var DPO_AUDIO_ELEMENT_MAP = [
679
682
  { audioKey: "dpo_text_audio_url", elementId: "dpo-dpo-text" },
680
683
  { audioKey: "dpo_anchor_text_audio_url", elementId: "dpo-dpo-link" }
681
684
  ];
685
+ var PRODUCT_SECTION_COPY = {
686
+ toggleSelection: (productName, purposeCount) => `Toggle all ${purposeCount} purposes for ${productName}`
687
+ };
682
688
 
683
689
  // src/RedactoNoticeConsent/styles.ts
684
690
  var styles = {
@@ -895,6 +901,28 @@ var styles = {
895
901
  padding: 0,
896
902
  boxSizing: "border-box"
897
903
  },
904
+ // A product section's heading. Its own tokens rather than the row's: sharing
905
+ // optionTitle rendered the group name at the same size, weight and indent as
906
+ // the purposes inside it, leaving nothing to mark it as a heading.
907
+ sectionHeader: {
908
+ display: "flex",
909
+ justifyContent: "space-between",
910
+ alignItems: "center",
911
+ margin: "0 0 8px",
912
+ padding: 0,
913
+ boxSizing: "border-box"
914
+ },
915
+ // No colour token: the header always overrides it with the notice's
916
+ // headingColor, so one here would imply a default that never applies.
917
+ sectionTitle: {
918
+ fontWeight: 600,
919
+ fontSize: "17px",
920
+ lineHeight: "150%",
921
+ letterSpacing: "0.2px",
922
+ margin: 0,
923
+ padding: 0,
924
+ textAlign: "left"
925
+ },
898
926
  optionTextContainer: {
899
927
  display: "flex",
900
928
  flexDirection: "column",
@@ -1236,18 +1264,76 @@ var NoticeFooter = ({
1236
1264
  ] });
1237
1265
  };
1238
1266
 
1267
+ // src/shared/productMatrix.ts
1268
+ var productPurposeKey = (productUuid, purposeUuid) => productUuid ? `${productUuid}:${purposeUuid}` : purposeUuid;
1269
+ var purposeCoversProduct = (purpose, productUuid) => {
1270
+ const scope = purpose.product_uuids;
1271
+ if (!scope || scope.length === 0) return true;
1272
+ return scope.includes(productUuid);
1273
+ };
1274
+ var isMultiProduct = (products) => Boolean(products && products.length > 1);
1275
+ var groupPurposesByProduct = (products, purposes, order) => {
1276
+ if (!products || products.length === 0) return [];
1277
+ return products.map((product) => {
1278
+ const covered = purposes.filter(
1279
+ (purpose) => purposeCoversProduct(purpose, product.uuid)
1280
+ );
1281
+ const saved = order == null ? void 0 : order[product.uuid];
1282
+ if (!saved || saved.length === 0) return { product, purposes: covered };
1283
+ const byUuid = new Map(covered.map((p) => [p.uuid, p]));
1284
+ const ordered = [];
1285
+ for (const uuid of saved) {
1286
+ const found = byUuid.get(uuid);
1287
+ if (found) ordered.push(found);
1288
+ }
1289
+ const rest = covered.filter((p) => !saved.includes(p.uuid));
1290
+ return { product, purposes: [...ordered, ...rest] };
1291
+ }).filter((group) => group.purposes.length > 0);
1292
+ };
1293
+ var expandPurposesByProduct = (products, purposes, order) => {
1294
+ if (!isMultiProduct(products)) return [];
1295
+ const rows = [];
1296
+ for (const group of groupPurposesByProduct(products, purposes, order)) {
1297
+ for (const purpose of group.purposes) {
1298
+ rows.push({ product: group.product, purpose });
1299
+ }
1300
+ }
1301
+ return rows;
1302
+ };
1303
+ var noticePurposeRows = (products, purposes, order) => {
1304
+ if (!isMultiProduct(products)) {
1305
+ return purposes.map((purpose) => ({ purpose, productUuid: void 0 }));
1306
+ }
1307
+ const rows = expandPurposesByProduct(products, purposes, order).map(
1308
+ (row) => ({
1309
+ purpose: row.purpose,
1310
+ productUuid: row.product.uuid
1311
+ })
1312
+ );
1313
+ const rendered = new Set(rows.map((row) => row.purpose.uuid));
1314
+ const orphaned = purposes.filter((purpose) => !rendered.has(purpose.uuid));
1315
+ if (orphaned.length > 0) {
1316
+ console.warn(
1317
+ "[redacto] purposes cover no product on this notice and will not be shown or submitted:",
1318
+ orphaned.map((purpose) => purpose.uuid)
1319
+ );
1320
+ }
1321
+ return rows;
1322
+ };
1323
+
1239
1324
  // src/RedactoNoticeConsent/utils/consentSelection.ts
1240
- var dataElementKey = (purposeUuid, elementUuid) => `${purposeUuid}-${elementUuid}`;
1241
- var buildSelectionForMode = (purposes, mode, current) => {
1325
+ var dataElementKey = (purposeUuid, elementUuid, productUuid) => `${productPurposeKey(productUuid, purposeUuid)}-${elementUuid}`;
1326
+ var buildSelectionForMode = (rows, mode, current) => {
1242
1327
  if (mode === "selected") {
1243
1328
  return current;
1244
1329
  }
1245
1330
  const selectedPurposes = {};
1246
1331
  const selectedDataElements = {};
1247
- purposes.forEach((purpose) => {
1248
- selectedPurposes[purpose.uuid] = true;
1332
+ rows.forEach(({ purpose, productUuid }) => {
1333
+ const key = productPurposeKey(productUuid, purpose.uuid);
1334
+ selectedPurposes[key] = true;
1249
1335
  purpose.data_elements.forEach((element) => {
1250
- selectedDataElements[dataElementKey(purpose.uuid, element.uuid)] = element.enabled;
1336
+ selectedDataElements[dataElementKey(purpose.uuid, element.uuid, productUuid)] = element.enabled;
1251
1337
  });
1252
1338
  });
1253
1339
  return { selectedPurposes, selectedDataElements };
@@ -1267,10 +1353,11 @@ var buildPreselection = (purposes, mode) => {
1267
1353
  const selectedPurposes = {};
1268
1354
  const selectedDataElements = {};
1269
1355
  if (mode === "ALL") {
1270
- return buildSelectionForMode(purposes, "all", {
1271
- selectedPurposes,
1272
- selectedDataElements
1273
- });
1356
+ return buildSelectionForMode(
1357
+ purposes.map((purpose) => ({ purpose, productUuid: void 0 })),
1358
+ "all",
1359
+ { selectedPurposes, selectedDataElements }
1360
+ );
1274
1361
  }
1275
1362
  purposes.forEach((purpose) => {
1276
1363
  let hasPreselectedElement = false;
@@ -1899,8 +1986,113 @@ var updateConsentState = (updates) => {
1899
1986
 
1900
1987
  // src/RedactoNoticeConsent/RedactoNoticeConsent.tsx
1901
1988
  var import_jsx_runtime3 = require("react/jsx-runtime");
1989
+ var DEFAULT_HEADING_COLOR = "#323B4B";
1990
+ var CHEVRON_BUTTON_STYLE = {
1991
+ background: "none",
1992
+ border: "none",
1993
+ padding: 0,
1994
+ margin: 0,
1995
+ cursor: "pointer",
1996
+ display: "flex",
1997
+ alignItems: "center"
1998
+ };
1999
+ var CHEVRON_WRAPPER_STYLE = {
2000
+ display: "flex",
2001
+ alignItems: "center",
2002
+ justifyContent: "center",
2003
+ marginLeft: "5px"
2004
+ };
2005
+ var CollapseChevron = ({ open, color }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: CHEVRON_WRAPPER_STYLE, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2006
+ "svg",
2007
+ {
2008
+ width: "7",
2009
+ height: "12",
2010
+ viewBox: "0 0 7 12",
2011
+ fill: "none",
2012
+ xmlns: "http://www.w3.org/2000/svg",
2013
+ stroke: color,
2014
+ strokeWidth: "2",
2015
+ strokeLinecap: "round",
2016
+ strokeLinejoin: "round",
2017
+ style: {
2018
+ transform: open ? "rotate(90deg)" : "rotate(0deg)",
2019
+ transition: "transform 0.3s ease"
2020
+ },
2021
+ "aria-hidden": "true",
2022
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M1 1L6 6L1 11" })
2023
+ }
2024
+ ) });
2025
+ var ProductSectionHeader = ({
2026
+ product,
2027
+ purposeCount,
2028
+ allSelected,
2029
+ someSelected,
2030
+ collapsed,
2031
+ locked,
2032
+ settings,
2033
+ styles: styles4,
2034
+ responsiveStyles,
2035
+ onToggleCollapse,
2036
+ onToggleSelect,
2037
+ getTranslatedText
2038
+ }) => {
2039
+ const checkboxRef = (0, import_react3.useRef)(null);
2040
+ const name = getTranslatedText("products.name", product.name, product.uuid);
2041
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || DEFAULT_HEADING_COLOR;
2042
+ (0, import_react3.useEffect)(() => {
2043
+ if (checkboxRef.current) {
2044
+ checkboxRef.current.indeterminate = someSelected && !allSelected;
2045
+ }
2046
+ }, [someSelected, allSelected]);
2047
+ const handleToggleCollapse = () => {
2048
+ onToggleCollapse(product.uuid);
2049
+ };
2050
+ const handleToggleSelect = () => {
2051
+ onToggleSelect(product.uuid);
2052
+ };
2053
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles4.sectionHeader, children: [
2054
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles4.optionLeft, children: [
2055
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2056
+ "button",
2057
+ {
2058
+ type: "button",
2059
+ onClick: handleToggleCollapse,
2060
+ "aria-expanded": !collapsed,
2061
+ "aria-controls": `product-${product.uuid}`,
2062
+ "aria-labelledby": `product-name-${product.uuid}`,
2063
+ style: CHEVRON_BUTTON_STYLE,
2064
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CollapseChevron, { open: !collapsed, color: headingColor })
2065
+ }
2066
+ ),
2067
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: styles4.optionTextContainer, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2068
+ "h2",
2069
+ {
2070
+ id: `product-name-${product.uuid}`,
2071
+ style: {
2072
+ ...styles4.sectionTitle,
2073
+ ...responsiveStyles.optionTitle,
2074
+ color: headingColor
2075
+ },
2076
+ children: name
2077
+ }
2078
+ ) })
2079
+ ] }),
2080
+ !locked && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2081
+ "input",
2082
+ {
2083
+ ref: checkboxRef,
2084
+ className: "redacto-checkbox-large",
2085
+ type: "checkbox",
2086
+ checked: allSelected,
2087
+ onChange: handleToggleSelect,
2088
+ "aria-label": PRODUCT_SECTION_COPY.toggleSelection(name, purposeCount)
2089
+ }
2090
+ )
2091
+ ] });
2092
+ };
1902
2093
  var PurposeItem = ({
1903
2094
  purpose,
2095
+ productUuid,
1904
2096
  selectedPurposes,
1905
2097
  collapsedPurposes,
1906
2098
  selectedDataElements,
@@ -1915,9 +2107,10 @@ var PurposeItem = ({
1915
2107
  selectedLanguage,
1916
2108
  initialDataElementSelections = {}
1917
2109
  }) => {
2110
+ const stateKey = productPurposeKey(productUuid, purpose.uuid);
1918
2111
  const headingStyle = (0, import_react3.useMemo)(
1919
2112
  () => ({
1920
- color: (settings == null ? void 0 : settings.headingColor) || "#323B4B"
2113
+ color: (settings == null ? void 0 : settings.headingColor) || DEFAULT_HEADING_COLOR
1921
2114
  }),
1922
2115
  [settings == null ? void 0 : settings.headingColor]
1923
2116
  );
@@ -1933,12 +2126,12 @@ var PurposeItem = ({
1933
2126
  "div",
1934
2127
  {
1935
2128
  style: styles4.optionLeft,
1936
- onClick: () => onPurposeCollapse(purpose.uuid),
2129
+ onClick: () => onPurposeCollapse(purpose.uuid, productUuid),
1937
2130
  role: "button",
1938
2131
  tabIndex: 0,
1939
- "aria-expanded": !collapsedPurposes[purpose.uuid],
2132
+ "aria-expanded": !collapsedPurposes[stateKey],
1940
2133
  "aria-controls": `purpose-${purpose.uuid}`,
1941
- "aria-label": `${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText(
2134
+ "aria-label": `${collapsedPurposes[stateKey] ? "Expand" : "Collapse"} ${getTranslatedText(
1942
2135
  `purposes.name`,
1943
2136
  purpose.name,
1944
2137
  purpose.uuid
@@ -1946,46 +2139,22 @@ var PurposeItem = ({
1946
2139
  onKeyDown: (e) => {
1947
2140
  if (e.key === "Enter" || e.key === " ") {
1948
2141
  e.preventDefault();
1949
- onPurposeCollapse(purpose.uuid);
2142
+ onPurposeCollapse(purpose.uuid, productUuid);
1950
2143
  }
1951
2144
  },
1952
2145
  children: [
1953
2146
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1954
- "div",
2147
+ CollapseChevron,
1955
2148
  {
1956
- style: {
1957
- display: "flex",
1958
- alignItems: "center",
1959
- justifyContent: "center",
1960
- marginLeft: "5px"
1961
- },
1962
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1963
- "svg",
1964
- {
1965
- width: "7",
1966
- height: "12",
1967
- viewBox: "0 0 7 12",
1968
- fill: "none",
1969
- xmlns: "http://www.w3.org/2000/svg",
1970
- stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
1971
- strokeWidth: "2",
1972
- strokeLinecap: "round",
1973
- strokeLinejoin: "round",
1974
- style: {
1975
- transform: !collapsedPurposes[purpose.uuid] ? "rotate(90deg)" : "rotate(0deg)",
1976
- transition: "transform 0.3s ease"
1977
- },
1978
- "aria-hidden": "true",
1979
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M1 1L6 6L1 11" })
1980
- }
1981
- )
2149
+ open: !collapsedPurposes[stateKey],
2150
+ color: (settings == null ? void 0 : settings.headingColor) || DEFAULT_HEADING_COLOR
1982
2151
  }
1983
2152
  ),
1984
2153
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles4.optionTextContainer, children: [
1985
2154
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1986
2155
  "h2",
1987
2156
  {
1988
- id: `purpose-name-${purpose.uuid}`,
2157
+ id: `purpose-name-${stateKey}`,
1989
2158
  style: {
1990
2159
  ...styles4.optionTitle,
1991
2160
  ...responsiveStyles.optionTitle,
@@ -2001,7 +2170,7 @@ var PurposeItem = ({
2001
2170
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2002
2171
  "h3",
2003
2172
  {
2004
- id: `purpose-description-${purpose.uuid}`,
2173
+ id: `purpose-description-${stateKey}`,
2005
2174
  style: {
2006
2175
  ...styles4.optionDescription,
2007
2176
  ...responsiveStyles.optionDescription,
@@ -2024,8 +2193,8 @@ var PurposeItem = ({
2024
2193
  {
2025
2194
  className: "redacto-checkbox-large",
2026
2195
  type: "checkbox",
2027
- checked: selectedPurposes[purpose.uuid] || false,
2028
- onChange: () => onPurposeToggle(purpose.uuid),
2196
+ checked: selectedPurposes[stateKey] || false,
2197
+ onChange: () => onPurposeToggle(purpose.uuid, productUuid),
2029
2198
  "aria-label": `Select all data elements for ${getTranslatedText(
2030
2199
  `purposes.name`,
2031
2200
  purpose.name,
@@ -2075,7 +2244,7 @@ var PurposeItem = ({
2075
2244
  }
2076
2245
  )
2077
2246
  ] }),
2078
- !collapsedPurposes[purpose.uuid] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2247
+ !collapsedPurposes[stateKey] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2079
2248
  "div",
2080
2249
  {
2081
2250
  id: `purpose-${purpose.uuid}`,
@@ -2088,7 +2257,7 @@ var PurposeItem = ({
2088
2257
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2089
2258
  "span",
2090
2259
  {
2091
- id: `data-element-${purpose.uuid}-${dataElement.uuid}`,
2260
+ id: `data-element-${stateKey}-${dataElement.uuid}`,
2092
2261
  style: {
2093
2262
  ...styles4.dataElementText,
2094
2263
  ...responsiveStyles.dataElementText,
@@ -2110,8 +2279,12 @@ var PurposeItem = ({
2110
2279
  {
2111
2280
  className: "redacto-checkbox-small",
2112
2281
  type: "checkbox",
2113
- checked: selectedDataElements[`${purpose.uuid}-${dataElement.uuid}`] || false,
2114
- onChange: () => onDataElementToggle(dataElement.uuid, purpose.uuid),
2282
+ checked: selectedDataElements[`${stateKey}-${dataElement.uuid}`] || false,
2283
+ onChange: () => onDataElementToggle(
2284
+ dataElement.uuid,
2285
+ purpose.uuid,
2286
+ productUuid
2287
+ ),
2115
2288
  "aria-label": `Select ${getTranslatedText(
2116
2289
  `data_elements.name`,
2117
2290
  dataElement.name,
@@ -2120,7 +2293,7 @@ var PurposeItem = ({
2120
2293
  }
2121
2294
  ),
2122
2295
  isAlreadyConsented && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: (() => {
2123
- const combinedId = `${purpose.uuid}-${dataElement.uuid}`;
2296
+ const combinedId = `${stateKey}-${dataElement.uuid}`;
2124
2297
  const wasOriginallySelected = initialDataElementSelections[combinedId] || false;
2125
2298
  if (wasOriginallySelected) {
2126
2299
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2258,7 +2431,9 @@ var GuardianForm = ({
2258
2431
  switch (e.key) {
2259
2432
  case "ArrowDown":
2260
2433
  e.preventDefault();
2261
- setFocusedIndex((prev) => Math.min(prev + 1, RELATIONSHIP_OPTIONS.length - 1));
2434
+ setFocusedIndex(
2435
+ (prev) => Math.min(prev + 1, RELATIONSHIP_OPTIONS.length - 1)
2436
+ );
2262
2437
  break;
2263
2438
  case "ArrowUp":
2264
2439
  e.preventDefault();
@@ -2267,7 +2442,8 @@ var GuardianForm = ({
2267
2442
  case "Enter":
2268
2443
  case " ":
2269
2444
  e.preventDefault();
2270
- if (focusedIndex >= 0) handleSelect(RELATIONSHIP_OPTIONS[focusedIndex].value);
2445
+ if (focusedIndex >= 0)
2446
+ handleSelect(RELATIONSHIP_OPTIONS[focusedIndex].value);
2271
2447
  break;
2272
2448
  case "Escape":
2273
2449
  case "Tab":
@@ -2332,7 +2508,9 @@ var GuardianForm = ({
2332
2508
  lineHeight: "1.5",
2333
2509
  color: "#1E40AF"
2334
2510
  };
2335
- const selectedLabel = (_a = RELATIONSHIP_OPTIONS.find((o) => o.value === formData.guardianRelationship)) == null ? void 0 : _a.label;
2511
+ const selectedLabel = (_a = RELATIONSHIP_OPTIONS.find(
2512
+ (o) => o.value === formData.guardianRelationship
2513
+ )) == null ? void 0 : _a.label;
2336
2514
  const hasError = Boolean(errors.guardianRelationship);
2337
2515
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
2338
2516
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { ...styles4.topSection, ...componentTextStyle }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles4.topLeft, children: [
@@ -2432,7 +2610,8 @@ var GuardianForm = ({
2432
2610
  ] }),
2433
2611
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
2434
2612
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { htmlFor: "guardianContact", style: labelStyle, children: [
2435
- "Guardian's Phone Number ",
2613
+ "Guardian's Phone Number",
2614
+ " ",
2436
2615
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: "#DC2626" }, children: "*" })
2437
2616
  ] }),
2438
2617
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2488,7 +2667,17 @@ var GuardianForm = ({
2488
2667
  transition: "border-color 0.15s, box-shadow 0.15s"
2489
2668
  },
2490
2669
  children: [
2491
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: selectedLabel || "Select relationship" }),
2670
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2671
+ "span",
2672
+ {
2673
+ style: {
2674
+ overflow: "hidden",
2675
+ textOverflow: "ellipsis",
2676
+ whiteSpace: "nowrap"
2677
+ },
2678
+ children: selectedLabel || "Select relationship"
2679
+ }
2680
+ ),
2492
2681
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2493
2682
  "svg",
2494
2683
  {
@@ -3156,6 +3345,7 @@ var RedactoNoticeConsent = ({
3156
3345
  const [selectedPurposes, setSelectedPurposes] = (0, import_react3.useState)({});
3157
3346
  const [selectedDataElements, setSelectedDataElements] = (0, import_react3.useState)({});
3158
3347
  const [initialDataElementSelections, setInitialDataElementSelections] = (0, import_react3.useState)({});
3348
+ const [collapsedProducts, setCollapsedProducts] = (0, import_react3.useState)({});
3159
3349
  const [collapsedPurposes, setCollapsedPurposes] = (0, import_react3.useState)({});
3160
3350
  const [hasAlreadyConsented, setHasAlreadyConsented] = (0, import_react3.useState)(false);
3161
3351
  const [isFullyConsentedReviewMode, setIsFullyConsentedReviewMode] = (0, import_react3.useState)(false);
@@ -3201,7 +3391,9 @@ var RedactoNoticeConsent = ({
3201
3391
  const statusPollIntervalRef = (0, import_react3.useRef)(null);
3202
3392
  const verificationPopupRef = (0, import_react3.useRef)(null);
3203
3393
  const pollingCleanupRef = (0, import_react3.useRef)(null);
3204
- const autoTransitionTimerRef = (0, import_react3.useRef)(null);
3394
+ const autoTransitionTimerRef = (0, import_react3.useRef)(
3395
+ null
3396
+ );
3205
3397
  const digilockerRedirectTimeoutRef = (0, import_react3.useRef)(null);
3206
3398
  const isInitialMountRef = (0, import_react3.useRef)(true);
3207
3399
  const isPollingStatusRef = (0, import_react3.useRef)(false);
@@ -3311,21 +3503,70 @@ var RedactoNoticeConsent = ({
3311
3503
  const logoRowStyle = (0, import_react3.useMemo)(
3312
3504
  () => ({
3313
3505
  ...styles.logoRow,
3314
- justifyContent: logoRowJustify(resolveLogoPosition(content == null ? void 0 : content.logo_position))
3506
+ justifyContent: logoRowJustify(
3507
+ resolveLogoPosition(content == null ? void 0 : content.logo_position)
3508
+ )
3315
3509
  }),
3316
3510
  [content == null ? void 0 : content.logo_position]
3317
3511
  );
3512
+ const purposeRows = (0, import_react3.useMemo)(
3513
+ () => {
3514
+ var _a2;
3515
+ return noticePurposeRows(
3516
+ content == null ? void 0 : content.products,
3517
+ (_a2 = content == null ? void 0 : content.purposes) != null ? _a2 : [],
3518
+ content == null ? void 0 : content.product_purpose_order
3519
+ );
3520
+ },
3521
+ [content]
3522
+ );
3523
+ const productGroups = (0, import_react3.useMemo)(
3524
+ () => {
3525
+ var _a2;
3526
+ return groupPurposesByProduct(
3527
+ content == null ? void 0 : content.products,
3528
+ (_a2 = content == null ? void 0 : content.purposes) != null ? _a2 : [],
3529
+ content == null ? void 0 : content.product_purpose_order
3530
+ );
3531
+ },
3532
+ [content]
3533
+ );
3318
3534
  const areAllRequiredElementsChecked = (0, import_react3.useMemo)(() => {
3535
+ var _a2;
3319
3536
  if (!content) return false;
3320
- return content.purposes.every((purpose) => {
3537
+ if (purposeRows.length === 0) return true;
3538
+ const isSatisfied = ({
3539
+ purpose,
3540
+ productUuid
3541
+ }) => {
3542
+ const key = productPurposeKey(productUuid, purpose.uuid);
3321
3543
  const requiredElements = purpose.data_elements.filter(
3322
3544
  (element) => element.required && element.enabled
3323
3545
  );
3324
3546
  return requiredElements.every(
3325
- (element) => selectedDataElements[`${purpose.uuid}-${element.uuid}`]
3547
+ (element) => selectedDataElements[`${key}-${element.uuid}`]
3326
3548
  );
3327
- });
3328
- }, [content, selectedDataElements]);
3549
+ };
3550
+ const rowsByProduct = /* @__PURE__ */ new Map();
3551
+ for (const row of purposeRows) {
3552
+ const groupKey = (_a2 = row.productUuid) != null ? _a2 : "";
3553
+ const group = rowsByProduct.get(groupKey);
3554
+ if (group) {
3555
+ group.push(row);
3556
+ } else {
3557
+ rowsByProduct.set(groupKey, [row]);
3558
+ }
3559
+ }
3560
+ const groups = [...rowsByProduct.values()];
3561
+ const everyProductHasMandatory = groups.every(
3562
+ (rows) => rows.some(
3563
+ ({ purpose }) => purpose.data_elements.some(
3564
+ (element) => element.required && element.enabled
3565
+ )
3566
+ )
3567
+ );
3568
+ return everyProductHasMandatory ? groups.some((rows) => rows.every(isSatisfied)) : purposeRows.every(isSatisfied);
3569
+ }, [content, purposeRows, selectedDataElements]);
3329
3570
  const acceptDisabled = isSubmitting || !areAllRequiredElementsChecked;
3330
3571
  (0, import_react3.useEffect)(() => {
3331
3572
  var _a2, _b2;
@@ -3337,6 +3578,7 @@ var RedactoNoticeConsent = ({
3337
3578
  setSelectedDataElements({});
3338
3579
  setInitialDataElementSelections({});
3339
3580
  setCollapsedPurposes({});
3581
+ setCollapsedProducts({});
3340
3582
  setHasAlreadyConsented(false);
3341
3583
  setErrorMessage(null);
3342
3584
  setFetchError(null);
@@ -3401,7 +3643,7 @@ var RedactoNoticeConsent = ({
3401
3643
  return false;
3402
3644
  };
3403
3645
  const getTranslatedText = (key, defaultText, itemId) => {
3404
- var _a2, _b2;
3646
+ var _a2, _b2, _c2;
3405
3647
  if (!content) return defaultText;
3406
3648
  if (isDefaultLanguage(selectedLanguage)) {
3407
3649
  if (key === "privacy_policy_anchor_text" && content.privacy_policy_anchor_text) {
@@ -3439,8 +3681,18 @@ var RedactoNoticeConsent = ({
3439
3681
  }
3440
3682
  return defaultText;
3441
3683
  }
3684
+ if (key === "products.name" || key === "products.description") {
3685
+ const productData = (_b2 = translationMap.products) == null ? void 0 : _b2[itemId];
3686
+ if (productData) {
3687
+ if (typeof productData === "string") {
3688
+ return key === "products.name" ? productData : defaultText;
3689
+ }
3690
+ return key === "products.name" ? productData.name || defaultText : productData.description || defaultText;
3691
+ }
3692
+ return defaultText;
3693
+ }
3442
3694
  if (key.startsWith("data_elements.")) {
3443
- return ((_b2 = translationMap.data_elements) == null ? void 0 : _b2[itemId]) || defaultText;
3695
+ return ((_c2 = translationMap.data_elements) == null ? void 0 : _c2[itemId]) || defaultText;
3444
3696
  }
3445
3697
  }
3446
3698
  const value = translationMap[key];
@@ -3498,63 +3750,76 @@ var RedactoNoticeConsent = ({
3498
3750
  defaultLang === "en" || defaultLang === "EN" ? "English" : defaultLang
3499
3751
  );
3500
3752
  }
3501
- const initialCollapsedState = activeConfig.purposes.reduce(
3502
- (acc, purpose) => ({
3753
+ const initialCollapsedState = noticePurposeRows(
3754
+ activeConfig.products,
3755
+ activeConfig.purposes,
3756
+ activeConfig.product_purpose_order
3757
+ ).reduce(
3758
+ (acc, { purpose, productUuid }) => ({
3503
3759
  ...acc,
3504
- [purpose.uuid]: true
3760
+ [productPurposeKey(productUuid, purpose.uuid)]: true
3505
3761
  }),
3506
3762
  {}
3507
3763
  );
3508
3764
  setCollapsedPurposes(initialCollapsedState);
3765
+ setCollapsedProducts({});
3509
3766
  const isReconsentRequired = consentContentData.detail.reconsent_required;
3510
3767
  const purposeSelections = consentContentData.detail.purpose_selections;
3511
3768
  const newCategorizedPurposes = isReconsentRequired ? {
3512
- alreadyConsented: activeConfig.purposes.filter(
3513
- (purpose) => {
3514
- const selection = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3515
- return selection && (selection.status === "ACTIVE" || selection.status === "EXPIRED") && !selection.needs_reconsent;
3516
- }
3517
- ),
3518
- needsConsent: activeConfig.purposes.filter(
3519
- (purpose) => {
3520
- const selection = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3521
- return !selection || selection.status !== "ACTIVE" || selection.needs_reconsent;
3522
- }
3523
- )
3769
+ alreadyConsented: activeConfig.purposes.filter((purpose) => {
3770
+ const selection = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3771
+ return selection && (selection.status === "ACTIVE" || selection.status === "EXPIRED") && !selection.needs_reconsent;
3772
+ }),
3773
+ needsConsent: activeConfig.purposes.filter((purpose) => {
3774
+ const selection = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3775
+ return !selection || selection.status !== "ACTIVE" || selection.needs_reconsent;
3776
+ })
3524
3777
  } : null;
3525
3778
  setCategorizedPurposes(newCategorizedPurposes);
3526
3779
  const preselection = buildPreselection(
3527
3780
  activeConfig.purposes,
3528
3781
  resolvePurposePreselection(activeConfig.purpose_preselection)
3529
3782
  );
3530
- const initialPurposeState = activeConfig.purposes.reduce(
3531
- (acc, purpose) => {
3783
+ const seedRows = noticePurposeRows(
3784
+ activeConfig.products,
3785
+ activeConfig.purposes,
3786
+ activeConfig.product_purpose_order
3787
+ );
3788
+ const recordedFor = (purposeUuid, productUuid) => {
3789
+ var _a3, _b2;
3790
+ const perProduct = productUuid ? (_b2 = (_a3 = activeConfig.product_purpose_selections) == null ? void 0 : _a3[productUuid]) == null ? void 0 : _b2[purposeUuid] : void 0;
3791
+ return perProduct != null ? perProduct : purposeSelections == null ? void 0 : purposeSelections[purposeUuid];
3792
+ };
3793
+ const initialPurposeState = seedRows.reduce(
3794
+ (acc, { purpose, productUuid }) => {
3532
3795
  var _a3;
3533
- const recorded = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3796
+ const key = productPurposeKey(productUuid, purpose.uuid);
3797
+ const recorded = recordedFor(purpose.uuid, productUuid);
3534
3798
  if (hasRecordedPurposeState(recorded)) {
3535
- acc[purpose.uuid] = recorded.selected;
3799
+ acc[key] = recorded.selected;
3536
3800
  } else {
3537
- acc[purpose.uuid] = (_a3 = preselection.selectedPurposes[purpose.uuid]) != null ? _a3 : false;
3801
+ acc[key] = (_a3 = preselection.selectedPurposes[purpose.uuid]) != null ? _a3 : false;
3538
3802
  }
3539
3803
  return acc;
3540
3804
  },
3541
3805
  {}
3542
3806
  );
3543
3807
  setSelectedPurposes(initialPurposeState);
3544
- const initialDataElementState = activeConfig.purposes.reduce(
3545
- (acc, purpose) => {
3546
- const hasRecordedSelection = hasRecordedPurposeState(
3547
- purposeSelections == null ? void 0 : purposeSelections[purpose.uuid]
3548
- );
3808
+ const initialDataElementState = seedRows.reduce(
3809
+ (acc, { purpose, productUuid }) => {
3810
+ const key = productPurposeKey(productUuid, purpose.uuid);
3811
+ const recorded = recordedFor(purpose.uuid, productUuid);
3812
+ const hasRecordedSelection = hasRecordedPurposeState(recorded);
3549
3813
  purpose.data_elements.forEach((element) => {
3550
- var _a3, _b2, _c2;
3551
- const combinedId = `${purpose.uuid}-${element.uuid}`;
3552
- if ((_b2 = (_a3 = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid]) == null ? void 0 : _a3.data_elements) == null ? void 0 : _b2[element.uuid]) {
3553
- acc[combinedId] = purposeSelections[purpose.uuid].data_elements[element.uuid].selected;
3814
+ var _a3, _b2;
3815
+ const combinedId = `${key}-${element.uuid}`;
3816
+ const recordedElement = (_a3 = recorded == null ? void 0 : recorded.data_elements) == null ? void 0 : _a3[element.uuid];
3817
+ if (recordedElement) {
3818
+ acc[combinedId] = recordedElement.selected;
3554
3819
  } else if (hasRecordedSelection) {
3555
3820
  acc[combinedId] = false;
3556
3821
  } else {
3557
- acc[combinedId] = (_c2 = preselection.selectedDataElements[combinedId]) != null ? _c2 : false;
3822
+ acc[combinedId] = (_b2 = preselection.selectedDataElements[`${purpose.uuid}-${element.uuid}`]) != null ? _b2 : false;
3558
3823
  }
3559
3824
  });
3560
3825
  return acc;
@@ -3565,10 +3830,14 @@ var RedactoNoticeConsent = ({
3565
3830
  if (isReconsentRequired && newCategorizedPurposes) {
3566
3831
  setIsFullyConsentedReviewMode(false);
3567
3832
  const initialSelections = {};
3568
- newCategorizedPurposes.alreadyConsented.forEach((purpose) => {
3833
+ const alreadyConsentedUuids = new Set(
3834
+ newCategorizedPurposes.alreadyConsented.map((p) => p.uuid)
3835
+ );
3836
+ seedRows.filter(({ purpose }) => alreadyConsentedUuids.has(purpose.uuid)).forEach(({ purpose, productUuid }) => {
3837
+ const rowKey = productPurposeKey(productUuid, purpose.uuid);
3569
3838
  purpose.data_elements.forEach((element) => {
3570
3839
  var _a3, _b2;
3571
- const combinedId = `${purpose.uuid}-${element.uuid}`;
3840
+ const combinedId = `${rowKey}-${element.uuid}`;
3572
3841
  if ((_b2 = (_a3 = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid]) == null ? void 0 : _a3.data_elements) == null ? void 0 : _b2[element.uuid]) {
3573
3842
  initialSelections[combinedId] = purposeSelections[purpose.uuid].data_elements[element.uuid].selected;
3574
3843
  } else {
@@ -3578,28 +3847,27 @@ var RedactoNoticeConsent = ({
3578
3847
  });
3579
3848
  setInitialDataElementSelections(initialSelections);
3580
3849
  } else if (includeFullyConsentedData && purposeSelections) {
3581
- const allPurposesSelected = activeConfig.purposes.length > 0 && activeConfig.purposes.every(
3582
- (p) => {
3850
+ const allPurposesSelected = seedRows.length > 0 && seedRows.every(
3851
+ ({ purpose, productUuid }) => {
3583
3852
  var _a3;
3584
- return ((_a3 = purposeSelections[p.uuid]) == null ? void 0 : _a3.selected) === true;
3853
+ return ((_a3 = recordedFor(purpose.uuid, productUuid)) == null ? void 0 : _a3.selected) === true;
3585
3854
  }
3586
3855
  );
3587
3856
  setIsFullyConsentedReviewMode(allPurposesSelected);
3588
3857
  if (allPurposesSelected) {
3589
3858
  const initialSelections = {};
3590
- activeConfig.purposes.forEach(
3591
- (purpose) => {
3592
- purpose.data_elements.forEach((element) => {
3593
- var _a3, _b2;
3594
- const combinedId = `${purpose.uuid}-${element.uuid}`;
3595
- if ((_b2 = (_a3 = purposeSelections[purpose.uuid]) == null ? void 0 : _a3.data_elements) == null ? void 0 : _b2[element.uuid]) {
3596
- initialSelections[combinedId] = purposeSelections[purpose.uuid].data_elements[element.uuid].selected;
3597
- } else {
3598
- initialSelections[combinedId] = false;
3599
- }
3600
- });
3601
- }
3602
- );
3859
+ seedRows.forEach(({ purpose, productUuid }) => {
3860
+ const rowKey = productPurposeKey(productUuid, purpose.uuid);
3861
+ purpose.data_elements.forEach((element) => {
3862
+ var _a3, _b2;
3863
+ const combinedId = `${rowKey}-${element.uuid}`;
3864
+ if ((_b2 = (_a3 = purposeSelections[purpose.uuid]) == null ? void 0 : _a3.data_elements) == null ? void 0 : _b2[element.uuid]) {
3865
+ initialSelections[combinedId] = purposeSelections[purpose.uuid].data_elements[element.uuid].selected;
3866
+ } else {
3867
+ initialSelections[combinedId] = false;
3868
+ }
3869
+ });
3870
+ });
3603
3871
  setInitialDataElementSelections(initialSelections);
3604
3872
  } else {
3605
3873
  setIsFullyConsentedReviewMode(false);
@@ -3649,10 +3917,11 @@ var RedactoNoticeConsent = ({
3649
3917
  // Removed onError and isRefreshingToken from dependencies to prevent duplicate API calls
3650
3918
  // onError is only called on errors, not needed for initial data fetching
3651
3919
  ]);
3652
- const togglePurposeCollapse = (purposeUuid) => {
3920
+ const togglePurposeCollapse = (purposeUuid, productUuid) => {
3921
+ const key = productPurposeKey(productUuid, purposeUuid);
3653
3922
  setCollapsedPurposes((prev) => ({
3654
3923
  ...prev,
3655
- [purposeUuid]: !prev[purposeUuid]
3924
+ [key]: !prev[key]
3656
3925
  }));
3657
3926
  };
3658
3927
  const toggleLanguageDropdown = () => {
@@ -3662,28 +3931,70 @@ var RedactoNoticeConsent = ({
3662
3931
  setSelectedLanguage(lang);
3663
3932
  setIsLanguageDropdownOpen(false);
3664
3933
  };
3665
- const handlePurposeCheckboxChange = (purposeUuid) => {
3934
+ const handlePurposeCheckboxChange = (purposeUuid, productUuid) => {
3666
3935
  if (content) {
3667
3936
  const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
3668
3937
  if (purpose) {
3669
- const newPurposeState = !selectedPurposes[purposeUuid];
3938
+ const key = productPurposeKey(productUuid, purposeUuid);
3939
+ const newPurposeState = !selectedPurposes[key];
3670
3940
  setSelectedPurposes((prev) => ({
3671
3941
  ...prev,
3672
- [purposeUuid]: newPurposeState
3942
+ [key]: newPurposeState
3673
3943
  }));
3674
3944
  setSelectedDataElements((prev) => {
3675
3945
  const updated = { ...prev };
3676
3946
  purpose.data_elements.forEach((el) => {
3677
- updated[`${purposeUuid}-${el.uuid}`] = newPurposeState;
3947
+ updated[`${key}-${el.uuid}`] = newPurposeState;
3678
3948
  });
3679
3949
  return updated;
3680
3950
  });
3681
3951
  }
3682
3952
  }
3683
3953
  };
3684
- const handleDataElementCheckboxChange = (elementUuid, purposeUuid) => {
3954
+ const toggleProductCollapse = (productUuid) => {
3955
+ setCollapsedProducts((prev) => ({
3956
+ ...prev,
3957
+ [productUuid]: !prev[productUuid]
3958
+ }));
3959
+ };
3960
+ const handleProductCheckboxChange = (productUuid) => {
3961
+ if (!content) {
3962
+ return;
3963
+ }
3964
+ const group = productGroups.find(
3965
+ (candidate) => candidate.product.uuid === productUuid
3966
+ );
3967
+ if (!group) {
3968
+ return;
3969
+ }
3970
+ const nextState = !group.purposes.every(
3971
+ (purpose) => selectedPurposes[productPurposeKey(productUuid, purpose.uuid)]
3972
+ );
3973
+ setSelectedPurposes((prev) => {
3974
+ const updated = { ...prev };
3975
+ group.purposes.forEach((purpose) => {
3976
+ updated[productPurposeKey(productUuid, purpose.uuid)] = nextState;
3977
+ });
3978
+ return updated;
3979
+ });
3980
+ setSelectedDataElements((prev) => {
3981
+ const updated = { ...prev };
3982
+ group.purposes.forEach((purpose) => {
3983
+ const key = productPurposeKey(productUuid, purpose.uuid);
3984
+ purpose.data_elements.forEach((element) => {
3985
+ updated[`${key}-${element.uuid}`] = nextState;
3986
+ });
3987
+ });
3988
+ return updated;
3989
+ });
3990
+ if (!nextState) {
3991
+ setCollapsedProducts((prev) => ({ ...prev, [productUuid]: false }));
3992
+ }
3993
+ };
3994
+ const handleDataElementCheckboxChange = (elementUuid, purposeUuid, productUuid) => {
3685
3995
  if (!content) return;
3686
- const combinedId = `${purposeUuid}-${elementUuid}`;
3996
+ const key = productPurposeKey(productUuid, purposeUuid);
3997
+ const combinedId = `${key}-${elementUuid}`;
3687
3998
  setSelectedDataElements((prev) => {
3688
3999
  const newState = {
3689
4000
  ...prev,
@@ -3697,16 +4008,16 @@ var RedactoNoticeConsent = ({
3697
4008
  let shouldCheckPurpose;
3698
4009
  if (requiredElements.length > 0) {
3699
4010
  shouldCheckPurpose = requiredElements.every(
3700
- (el) => newState[`${purposeUuid}-${el.uuid}`]
4011
+ (el) => newState[`${key}-${el.uuid}`]
3701
4012
  );
3702
4013
  } else {
3703
4014
  shouldCheckPurpose = purpose.data_elements.some(
3704
- (el) => newState[`${purposeUuid}-${el.uuid}`]
4015
+ (el) => newState[`${key}-${el.uuid}`]
3705
4016
  );
3706
4017
  }
3707
4018
  setSelectedPurposes((prevPurposes) => ({
3708
4019
  ...prevPurposes,
3709
- [purposeUuid]: shouldCheckPurpose
4020
+ [key]: shouldCheckPurpose
3710
4021
  }));
3711
4022
  }
3712
4023
  return newState;
@@ -3716,14 +4027,16 @@ var RedactoNoticeConsent = ({
3716
4027
  const submitController = new AbortController();
3717
4028
  try {
3718
4029
  if (isMinorFlow && !verificationReference) {
3719
- setErrorMessage("Guardian verification is required before consent can be submitted.");
4030
+ setErrorMessage(
4031
+ "Guardian verification is required before consent can be submitted."
4032
+ );
3720
4033
  return;
3721
4034
  }
3722
4035
  setIsSubmittingAccept(true);
3723
4036
  setIsSubmitting(true);
3724
4037
  setErrorMessage(null);
3725
4038
  if (content) {
3726
- const selection = buildSelectionForMode(content.purposes, mode, {
4039
+ const selection = buildSelectionForMode(purposeRows, mode, {
3727
4040
  selectedPurposes,
3728
4041
  selectedDataElements
3729
4042
  });
@@ -3736,14 +4049,20 @@ var RedactoNoticeConsent = ({
3736
4049
  baseUrl,
3737
4050
  ledgerBaseUrl,
3738
4051
  noticeUuid: content.notice_uuid,
3739
- purposes: content.purposes.map((purpose) => {
4052
+ // One entry per (product, purpose) once a notice covers several. The
4053
+ // ledger keys consent on the pair, and repeating a purpose under two
4054
+ // products is how one submission accepts it for one and declines it
4055
+ // for the other. Collapses to content.purposes on a single-product
4056
+ // notice, so that payload is byte-identical to before.
4057
+ purposes: purposeRows.map(({ purpose, productUuid }) => {
4058
+ const stateKey = productPurposeKey(productUuid, purpose.uuid);
3740
4059
  const isAlreadyConsented = (categorizedPurposes == null ? void 0 : categorizedPurposes.alreadyConsented.some(
3741
4060
  (p) => p.uuid === purpose.uuid
3742
4061
  )) || false;
3743
- let purposeSelected = selection.selectedPurposes[purpose.uuid] || false;
4062
+ let purposeSelected = selection.selectedPurposes[stateKey] || false;
3744
4063
  if (mode === "selected" && isAlreadyConsented) {
3745
4064
  const wasModified = purpose.data_elements.some((element) => {
3746
- const combinedId = `${purpose.uuid}-${element.uuid}`;
4065
+ const combinedId = `${stateKey}-${element.uuid}`;
3747
4066
  const currentSelection = selection.selectedDataElements[combinedId] || false;
3748
4067
  const initialSelection = initialDataElementSelections[combinedId] || false;
3749
4068
  return currentSelection !== initialSelection;
@@ -3754,10 +4073,11 @@ var RedactoNoticeConsent = ({
3754
4073
  }
3755
4074
  return {
3756
4075
  ...purpose,
4076
+ ...productUuid ? { product_uuid: productUuid } : {},
3757
4077
  selected: purposeSelected,
3758
4078
  data_elements: purpose.data_elements.map((element) => ({
3759
4079
  ...element,
3760
- selected: element.required ? true : selection.selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false
4080
+ selected: element.required ? true : selection.selectedDataElements[`${stateKey}-${element.uuid}`] || false
3761
4081
  }))
3762
4082
  };
3763
4083
  }),
@@ -3860,7 +4180,9 @@ var RedactoNoticeConsent = ({
3860
4180
  return;
3861
4181
  }
3862
4182
  if (!response.session_token || !response.digilocker_redirect_url) {
3863
- throw new Error("Invalid response from server: missing required fields");
4183
+ throw new Error(
4184
+ "Invalid response from server: missing required fields"
4185
+ );
3864
4186
  }
3865
4187
  setShowGuardianForm(false);
3866
4188
  setShowVerificationScreen(true);
@@ -3950,7 +4272,18 @@ var RedactoNoticeConsent = ({
3950
4272
  } finally {
3951
4273
  setIsSubmittingGuardian(false);
3952
4274
  }
3953
- }, [accessToken, sandboxParams, baseUrl, guardianFormData, digilockerMode, digilockerCallbackUrl, selectedPurposes, selectedDataElements, noticeId, onError]);
4275
+ }, [
4276
+ accessToken,
4277
+ sandboxParams,
4278
+ baseUrl,
4279
+ guardianFormData,
4280
+ digilockerMode,
4281
+ digilockerCallbackUrl,
4282
+ selectedPurposes,
4283
+ selectedDataElements,
4284
+ noticeId,
4285
+ onError
4286
+ ]);
3954
4287
  const handleAgeVerificationYes = (0, import_react3.useCallback)(() => {
3955
4288
  setShowAgeVerification(false);
3956
4289
  setIsMinorFlow(false);
@@ -4001,20 +4334,21 @@ var RedactoNoticeConsent = ({
4001
4334
  } catch {
4002
4335
  }
4003
4336
  }
4004
- setVerificationError(
4005
- "Verification window was closed. Please try again."
4006
- );
4337
+ setVerificationError("Verification window was closed. Please try again.");
4007
4338
  setIsPollingStatus(false);
4008
4339
  isPollingStatusRef.current = false;
4009
4340
  setIsInitiatingVerification(false);
4010
4341
  }, [accessToken, sandboxParams, baseUrl]);
4011
- const startPopupMonitoring = (0, import_react3.useCallback)((popup) => {
4012
- popupCheckIntervalRef.current = window.setInterval(() => {
4013
- if (popup.closed) {
4014
- handlePopupClosed();
4015
- }
4016
- }, 500);
4017
- }, [handlePopupClosed]);
4342
+ const startPopupMonitoring = (0, import_react3.useCallback)(
4343
+ (popup) => {
4344
+ popupCheckIntervalRef.current = window.setInterval(() => {
4345
+ if (popup.closed) {
4346
+ handlePopupClosed();
4347
+ }
4348
+ }, 500);
4349
+ },
4350
+ [handlePopupClosed]
4351
+ );
4018
4352
  const getVerificationErrorMessage = (0, import_react3.useCallback)(
4019
4353
  (errorCode, fallbackError) => {
4020
4354
  switch (errorCode) {
@@ -4116,7 +4450,10 @@ var RedactoNoticeConsent = ({
4116
4450
  return;
4117
4451
  }
4118
4452
  if (!paused) {
4119
- statusPollIntervalRef.current = window.setTimeout(poll, POLL_INTERVAL);
4453
+ statusPollIntervalRef.current = window.setTimeout(
4454
+ poll,
4455
+ POLL_INTERVAL
4456
+ );
4120
4457
  }
4121
4458
  } catch (error) {
4122
4459
  const err = error;
@@ -4137,7 +4474,10 @@ var RedactoNoticeConsent = ({
4137
4474
  );
4138
4475
  return;
4139
4476
  }
4140
- statusPollIntervalRef.current = window.setTimeout(poll, POLL_INTERVAL);
4477
+ statusPollIntervalRef.current = window.setTimeout(
4478
+ poll,
4479
+ POLL_INTERVAL
4480
+ );
4141
4481
  }
4142
4482
  };
4143
4483
  const onVisibilityChange = () => {
@@ -4438,12 +4778,19 @@ var RedactoNoticeConsent = ({
4438
4778
  const dataElementsAudioCount = ((_a2 = purpose.data_elements) == null ? void 0 : _a2.length) || 0;
4439
4779
  const totalPurposeAudioCount = purposeAudioCount + dataElementsAudioCount;
4440
4780
  if (currentIndex >= audioOffset && currentIndex < audioOffset + totalPurposeAudioCount) {
4441
- if (collapsedPurposes[purpose.uuid]) {
4442
- setCollapsedPurposes((prev) => ({
4443
- ...prev,
4444
- [purpose.uuid]: false
4445
- }));
4446
- }
4781
+ setCollapsedPurposes((prev) => {
4782
+ const next = { ...prev };
4783
+ let changed = false;
4784
+ for (const row of purposeRows) {
4785
+ if (row.purpose.uuid !== purpose.uuid) continue;
4786
+ const rowKey = productPurposeKey(row.productUuid, purpose.uuid);
4787
+ if (next[rowKey]) {
4788
+ next[rowKey] = false;
4789
+ changed = true;
4790
+ }
4791
+ }
4792
+ return changed ? next : prev;
4793
+ });
4447
4794
  break;
4448
4795
  }
4449
4796
  audioOffset += totalPurposeAudioCount;
@@ -4521,7 +4868,15 @@ var RedactoNoticeConsent = ({
4521
4868
  return () => {
4522
4869
  ttsAbortController.abort();
4523
4870
  };
4524
- }, [content, selectedLanguage, accessToken, sandboxParams, baseUrl, ledgerBaseUrl, getLanguageCode]);
4871
+ }, [
4872
+ content,
4873
+ selectedLanguage,
4874
+ accessToken,
4875
+ sandboxParams,
4876
+ baseUrl,
4877
+ ledgerBaseUrl,
4878
+ getLanguageCode
4879
+ ]);
4525
4880
  const modalStyle = {
4526
4881
  borderRadius: (settings == null ? void 0 : settings.borderRadius) || "8px",
4527
4882
  backgroundColor: (settings == null ? void 0 : settings.backgroundColor) || "#ffffff",
@@ -5220,7 +5575,7 @@ var RedactoNoticeConsent = ({
5220
5575
  },
5221
5576
  `purpose-section-heading-${selectedLanguage}`
5222
5577
  ),
5223
- categorizedPurposes ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles.optionsContainer, children: [
5578
+ categorizedPurposes && !isMultiProduct(content == null ? void 0 : content.products) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: styles.optionsContainer, children: [
5224
5579
  categorizedPurposes.alreadyConsented.map((purpose) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5225
5580
  PurposeItem,
5226
5581
  {
@@ -5262,7 +5617,61 @@ var RedactoNoticeConsent = ({
5262
5617
  ))
5263
5618
  ] }) : (
5264
5619
  /* Regular consent UI */
5265
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: styles.optionsContainer, children: (_y = content == null ? void 0 : content.purposes) == null ? void 0 : _y.map((purpose) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5620
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: styles.optionsContainer, children: isMultiProduct(content == null ? void 0 : content.products) ? productGroups.map((group) => {
5621
+ const selectedInGroup = group.purposes.filter(
5622
+ (purpose) => selectedPurposes[productPurposeKey(
5623
+ group.product.uuid,
5624
+ purpose.uuid
5625
+ )]
5626
+ ).length;
5627
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
5628
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5629
+ ProductSectionHeader,
5630
+ {
5631
+ product: group.product,
5632
+ purposeCount: group.purposes.length,
5633
+ allSelected: selectedInGroup === group.purposes.length,
5634
+ someSelected: selectedInGroup > 0,
5635
+ collapsed: collapsedProducts[group.product.uuid] || false,
5636
+ locked: isFullyConsentedReviewMode,
5637
+ settings,
5638
+ styles,
5639
+ responsiveStyles,
5640
+ onToggleCollapse: toggleProductCollapse,
5641
+ onToggleSelect: handleProductCheckboxChange,
5642
+ getTranslatedText
5643
+ }
5644
+ ),
5645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5646
+ "div",
5647
+ {
5648
+ id: `product-${group.product.uuid}`,
5649
+ hidden: collapsedProducts[group.product.uuid] || false,
5650
+ children: group.purposes.map((purpose) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5651
+ PurposeItem,
5652
+ {
5653
+ purpose,
5654
+ productUuid: group.product.uuid,
5655
+ selectedPurposes,
5656
+ collapsedPurposes,
5657
+ selectedDataElements,
5658
+ settings,
5659
+ styles,
5660
+ responsiveStyles,
5661
+ onPurposeToggle: handlePurposeCheckboxChange,
5662
+ onPurposeCollapse: togglePurposeCollapse,
5663
+ onDataElementToggle: handleDataElementCheckboxChange,
5664
+ getTranslatedText,
5665
+ isAlreadyConsented: isFullyConsentedReviewMode,
5666
+ selectedLanguage,
5667
+ initialDataElementSelections
5668
+ },
5669
+ `${group.product.uuid}-${purpose.uuid}-${selectedLanguage}`
5670
+ ))
5671
+ }
5672
+ )
5673
+ ] }, group.product.uuid);
5674
+ }) : (_y = content == null ? void 0 : content.purposes) == null ? void 0 : _y.map((purpose) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5266
5675
  PurposeItem,
5267
5676
  {
5268
5677
  purpose,
@@ -5973,7 +6382,6 @@ var RedactoConsentInlineComponent = ({
5973
6382
  settings,
5974
6383
  applicationId
5975
6384
  }) => {
5976
- var _a;
5977
6385
  const [content, setContent] = (0, import_react5.useState)(null);
5978
6386
  const [isLoading, setIsLoading] = (0, import_react5.useState)(true);
5979
6387
  const [isSubmitting, setIsSubmitting] = (0, import_react5.useState)(false);
@@ -6030,7 +6438,7 @@ var RedactoConsentInlineComponent = ({
6030
6438
  }
6031
6439
  }, [hasAlreadyConsented, onAccept]);
6032
6440
  const getTranslatedText = (key, defaultText, itemId) => {
6033
- var _a2, _b;
6441
+ var _a, _b, _c;
6034
6442
  if (!content) return defaultText;
6035
6443
  if (selectedLanguage === "English" || selectedLanguage === "en" || selectedLanguage === "EN") {
6036
6444
  if (key === "privacy_policy_anchor_text" && content.privacy_policy_anchor_text) {
@@ -6047,7 +6455,7 @@ var RedactoConsentInlineComponent = ({
6047
6455
  }
6048
6456
  if (itemId) {
6049
6457
  if (key === "purposes.name" || key === "purposes.description") {
6050
- const purposeData = (_a2 = translationMap.purposes) == null ? void 0 : _a2[itemId];
6458
+ const purposeData = (_a = translationMap.purposes) == null ? void 0 : _a[itemId];
6051
6459
  if (purposeData) {
6052
6460
  if (typeof purposeData === "string") {
6053
6461
  return key === "purposes.name" ? purposeData : defaultText;
@@ -6056,8 +6464,18 @@ var RedactoConsentInlineComponent = ({
6056
6464
  }
6057
6465
  return defaultText;
6058
6466
  }
6467
+ if (key === "products.name" || key === "products.description") {
6468
+ const productData = (_b = translationMap.products) == null ? void 0 : _b[itemId];
6469
+ if (productData) {
6470
+ if (typeof productData === "string") {
6471
+ return key === "products.name" ? productData : defaultText;
6472
+ }
6473
+ return key === "products.name" ? productData.name || defaultText : productData.description || defaultText;
6474
+ }
6475
+ return defaultText;
6476
+ }
6059
6477
  if (key.startsWith("data_elements.")) {
6060
- return ((_b = translationMap.data_elements) == null ? void 0 : _b[itemId]) || defaultText;
6478
+ return ((_c = translationMap.data_elements) == null ? void 0 : _c[itemId]) || defaultText;
6061
6479
  }
6062
6480
  }
6063
6481
  const value = translationMap[key];
@@ -6166,25 +6584,27 @@ var RedactoConsentInlineComponent = ({
6166
6584
  controller.abort();
6167
6585
  };
6168
6586
  }, [ledgerBaseUrl, applicationId, accessToken, content, notice_uuid]);
6169
- const togglePurposeCollapse = (purposeUuid) => {
6587
+ const togglePurposeCollapse = (purposeUuid, productUuid) => {
6588
+ const key = productPurposeKey(productUuid, purposeUuid);
6170
6589
  setCollapsedPurposes((prev) => ({
6171
6590
  ...prev,
6172
- [purposeUuid]: !prev[purposeUuid]
6591
+ [key]: !prev[key]
6173
6592
  }));
6174
6593
  };
6175
- const handlePurposeCheckboxChange = (purposeUuid) => {
6594
+ const handlePurposeCheckboxChange = (purposeUuid, productUuid) => {
6176
6595
  if (content) {
6177
6596
  const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
6178
6597
  if (purpose) {
6179
- const newPurposeState = !selectedPurposes[purposeUuid];
6598
+ const key = productPurposeKey(productUuid, purposeUuid);
6599
+ const newPurposeState = !selectedPurposes[key];
6180
6600
  setSelectedPurposes((prev) => ({
6181
6601
  ...prev,
6182
- [purposeUuid]: newPurposeState
6602
+ [key]: newPurposeState
6183
6603
  }));
6184
6604
  setSelectedDataElements((prev) => {
6185
6605
  const updated = { ...prev };
6186
6606
  purpose.data_elements.forEach((el) => {
6187
- const combinedId = `${purposeUuid}-${el.uuid}`;
6607
+ const combinedId = `${key}-${el.uuid}`;
6188
6608
  updated[combinedId] = newPurposeState;
6189
6609
  });
6190
6610
  return updated;
@@ -6192,7 +6612,7 @@ var RedactoConsentInlineComponent = ({
6192
6612
  }
6193
6613
  }
6194
6614
  };
6195
- const handleDataElementCheckboxChange = (elementUuid, purposeUuid) => {
6615
+ const handleDataElementCheckboxChange = (elementUuid, purposeUuid, productUuid) => {
6196
6616
  if (!content) return;
6197
6617
  const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
6198
6618
  if (!purpose) return;
@@ -6200,7 +6620,8 @@ var RedactoConsentInlineComponent = ({
6200
6620
  (el) => el.uuid === elementUuid
6201
6621
  );
6202
6622
  if (!dataElement) return;
6203
- const combinedId = `${purposeUuid}-${elementUuid}`;
6623
+ const key = productPurposeKey(productUuid, purposeUuid);
6624
+ const combinedId = `${key}-${elementUuid}`;
6204
6625
  const currentState = selectedDataElements[combinedId] || false;
6205
6626
  const newElementState = !currentState;
6206
6627
  setSelectedDataElements((prev) => {
@@ -6214,16 +6635,16 @@ var RedactoConsentInlineComponent = ({
6214
6635
  let shouldCheckPurpose;
6215
6636
  if (requiredElements.length > 0) {
6216
6637
  shouldCheckPurpose = requiredElements.every(
6217
- (el) => newState[`${purposeUuid}-${el.uuid}`]
6638
+ (el) => newState[`${key}-${el.uuid}`]
6218
6639
  );
6219
6640
  } else {
6220
6641
  shouldCheckPurpose = purpose.data_elements.some(
6221
- (el) => newState[`${purposeUuid}-${el.uuid}`]
6642
+ (el) => newState[`${key}-${el.uuid}`]
6222
6643
  );
6223
6644
  }
6224
6645
  setSelectedPurposes((prevPurposes) => ({
6225
6646
  ...prevPurposes,
6226
- [purposeUuid]: shouldCheckPurpose
6647
+ [key]: shouldCheckPurpose
6227
6648
  }));
6228
6649
  return newState;
6229
6650
  });
@@ -6244,20 +6665,36 @@ var RedactoConsentInlineComponent = ({
6244
6665
  baseUrl,
6245
6666
  ledgerBaseUrl,
6246
6667
  noticeUuid: content.notice_uuid,
6247
- purposes: content.purposes.map((purpose) => {
6668
+ // One entry per (product, purpose) once a notice covers several, so a
6669
+ // purpose can be accepted for one product and declined for another.
6670
+ // Collapses to content.purposes below two products, leaving that
6671
+ // payload byte-identical to before.
6672
+ purposes: (isMultiProduct(content.products) ? expandPurposesByProduct(
6673
+ content.products,
6674
+ content.purposes,
6675
+ content.product_purpose_order
6676
+ ).map((row) => ({
6677
+ purpose: row.purpose,
6678
+ productUuid: row.product.uuid
6679
+ })) : content.purposes.map((purpose) => ({
6680
+ purpose,
6681
+ productUuid: void 0
6682
+ }))).map(({ purpose, productUuid }) => {
6683
+ const key = productPurposeKey(productUuid, purpose.uuid);
6248
6684
  const hasSelectedRequiredElement = purpose.data_elements.some(
6249
- (element) => element.required && (selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false)
6685
+ (element) => element.required && (selectedDataElements[`${key}-${element.uuid}`] || false)
6250
6686
  );
6251
6687
  const hasSelectedDataElement = purpose.data_elements.some(
6252
- (element) => selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false
6688
+ (element) => selectedDataElements[`${key}-${element.uuid}`] || false
6253
6689
  );
6254
- const purposeSelected = selectedPurposes[purpose.uuid] || hasSelectedRequiredElement || hasSelectedDataElement;
6690
+ const purposeSelected = selectedPurposes[key] || hasSelectedRequiredElement || hasSelectedDataElement;
6255
6691
  return {
6256
6692
  ...purpose,
6693
+ ...productUuid ? { product_uuid: productUuid } : {},
6257
6694
  selected: purposeSelected,
6258
6695
  data_elements: purpose.data_elements.map((element) => ({
6259
6696
  ...element,
6260
- selected: selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false
6697
+ selected: selectedDataElements[`${key}-${element.uuid}`] || false
6261
6698
  }))
6262
6699
  };
6263
6700
  }),
@@ -6358,133 +6795,99 @@ var RedactoConsentInlineComponent = ({
6358
6795
  children: errorMessage
6359
6796
  }
6360
6797
  ),
6361
- (_a = content == null ? void 0 : content.purposes) == null ? void 0 : _a.map((purpose) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
6362
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: styles2.optionItem, children: [
6363
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
6364
- "div",
6365
- {
6366
- style: styles2.optionLeft,
6367
- onClick: () => togglePurposeCollapse(purpose.uuid),
6368
- role: "button",
6369
- tabIndex: 0,
6370
- "aria-expanded": !collapsedPurposes[purpose.uuid],
6371
- "aria-controls": `purpose-${purpose.uuid}`,
6372
- "aria-label": `${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText(
6373
- `purposes.name`,
6374
- purpose.name,
6375
- purpose.uuid
6376
- )} details`,
6377
- onKeyDown: (e) => {
6378
- if (e.key === "Enter" || e.key === " ") {
6379
- e.preventDefault();
6380
- togglePurposeCollapse(purpose.uuid);
6381
- }
6382
- },
6383
- children: [
6384
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6385
- "div",
6386
- {
6387
- style: {
6388
- display: "flex",
6389
- alignItems: "center",
6390
- justifyContent: "center",
6391
- marginLeft: "5px"
6392
- },
6393
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6394
- "svg",
6395
- {
6396
- width: "7",
6397
- height: "12",
6398
- viewBox: "0 0 7 12",
6399
- fill: "none",
6400
- xmlns: "http://www.w3.org/2000/svg",
6401
- stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
6402
- strokeWidth: "2",
6403
- strokeLinecap: "round",
6404
- strokeLinejoin: "round",
6405
- style: {
6406
- transform: !collapsedPurposes[purpose.uuid] ? "rotate(90deg)" : "rotate(0deg)",
6407
- transition: "transform 0.3s ease"
6408
- },
6409
- "aria-hidden": "true",
6410
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M1 1L6 6L1 11" })
6411
- }
6412
- )
6413
- }
6414
- ),
6415
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: styles2.optionTextContainer, children: [
6416
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
6417
- "h2",
6418
- {
6419
- style: {
6420
- ...styles2.optionTitle,
6421
- ...responsiveStyles.optionTitle,
6422
- ...headingStyle
6423
- },
6424
- children: [
6425
- getTranslatedText(
6426
- `purposes.name`,
6427
- purpose.name,
6428
- purpose.uuid
6429
- ),
6430
- hasRequiredDataElement(purpose) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RequiredMarker, {})
6431
- ]
6432
- }
6433
- ),
6434
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6435
- "h3",
6436
- {
6437
- style: {
6438
- ...styles2.optionDescription,
6439
- ...responsiveStyles.optionDescription,
6440
- ...textStyle
6441
- },
6442
- children: getTranslatedText(
6443
- `purposes.description`,
6444
- purpose.description,
6445
- purpose.uuid
6446
- )
6447
- }
6448
- )
6449
- ] })
6450
- ]
6451
- }
6452
- ),
6453
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6454
- "input",
6455
- {
6456
- type: "checkbox",
6457
- checked: selectedPurposes[purpose.uuid] || false,
6458
- onChange: () => handlePurposeCheckboxChange(purpose.uuid),
6459
- "aria-label": `Select all data elements for ${getTranslatedText(
6460
- `purposes.name`,
6461
- purpose.name,
6462
- purpose.uuid
6463
- )}`
6464
- }
6465
- )
6466
- ] }),
6467
- !collapsedPurposes[purpose.uuid] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6468
- "div",
6469
- {
6470
- id: `purpose-${purpose.uuid}`,
6471
- style: styles2.dataElementsContainer,
6472
- children: purpose.data_elements.map((dataElement) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: styles2.dataElementItem, children: [
6798
+ (() => {
6799
+ var _a, _b;
6800
+ const renderPurpose = (purpose, productUuid) => {
6801
+ const stateKey = productPurposeKey(productUuid, purpose.uuid);
6802
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
6803
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: styles2.optionItem, children: [
6473
6804
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
6474
- "span",
6805
+ "div",
6475
6806
  {
6476
- style: {
6477
- ...styles2.dataElementText,
6478
- ...responsiveStyles.dataElementText,
6479
- ...textStyle
6807
+ style: styles2.optionLeft,
6808
+ onClick: () => togglePurposeCollapse(purpose.uuid, productUuid),
6809
+ role: "button",
6810
+ tabIndex: 0,
6811
+ "aria-expanded": !collapsedPurposes[stateKey],
6812
+ "aria-controls": `purpose-${stateKey}`,
6813
+ "aria-label": `${collapsedPurposes[stateKey] ? "Expand" : "Collapse"} ${getTranslatedText(
6814
+ `purposes.name`,
6815
+ purpose.name,
6816
+ purpose.uuid
6817
+ )} details`,
6818
+ onKeyDown: (e) => {
6819
+ if (e.key === "Enter" || e.key === " ") {
6820
+ e.preventDefault();
6821
+ togglePurposeCollapse(purpose.uuid, productUuid);
6822
+ }
6480
6823
  },
6481
6824
  children: [
6482
- getTranslatedText(
6483
- `data_elements.name`,
6484
- dataElement.name,
6485
- dataElement.uuid
6825
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6826
+ "div",
6827
+ {
6828
+ style: {
6829
+ display: "flex",
6830
+ alignItems: "center",
6831
+ justifyContent: "center",
6832
+ marginLeft: "5px"
6833
+ },
6834
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6835
+ "svg",
6836
+ {
6837
+ width: "7",
6838
+ height: "12",
6839
+ viewBox: "0 0 7 12",
6840
+ fill: "none",
6841
+ xmlns: "http://www.w3.org/2000/svg",
6842
+ stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
6843
+ strokeWidth: "2",
6844
+ strokeLinecap: "round",
6845
+ strokeLinejoin: "round",
6846
+ style: {
6847
+ transform: !collapsedPurposes[stateKey] ? "rotate(90deg)" : "rotate(0deg)",
6848
+ transition: "transform 0.3s ease"
6849
+ },
6850
+ "aria-hidden": "true",
6851
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M1 1L6 6L1 11" })
6852
+ }
6853
+ )
6854
+ }
6486
6855
  ),
6487
- dataElement.required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RequiredMarker, {})
6856
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: styles2.optionTextContainer, children: [
6857
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
6858
+ "h2",
6859
+ {
6860
+ style: {
6861
+ ...styles2.optionTitle,
6862
+ ...responsiveStyles.optionTitle,
6863
+ ...headingStyle
6864
+ },
6865
+ children: [
6866
+ getTranslatedText(
6867
+ `purposes.name`,
6868
+ purpose.name,
6869
+ purpose.uuid
6870
+ ),
6871
+ hasRequiredDataElement(purpose) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RequiredMarker, {})
6872
+ ]
6873
+ }
6874
+ ),
6875
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6876
+ "h3",
6877
+ {
6878
+ style: {
6879
+ ...styles2.optionDescription,
6880
+ ...responsiveStyles.optionDescription,
6881
+ ...textStyle
6882
+ },
6883
+ children: getTranslatedText(
6884
+ `purposes.description`,
6885
+ purpose.description,
6886
+ purpose.uuid
6887
+ )
6888
+ }
6889
+ )
6890
+ ] })
6488
6891
  ]
6489
6892
  }
6490
6893
  ),
@@ -6492,22 +6895,87 @@ var RedactoConsentInlineComponent = ({
6492
6895
  "input",
6493
6896
  {
6494
6897
  type: "checkbox",
6495
- checked: selectedDataElements[`${purpose.uuid}-${dataElement.uuid}`] || false,
6496
- onChange: () => handleDataElementCheckboxChange(
6497
- dataElement.uuid,
6898
+ checked: selectedPurposes[stateKey] || false,
6899
+ onChange: () => handlePurposeCheckboxChange(purpose.uuid, productUuid),
6900
+ "aria-label": `Select all data elements for ${getTranslatedText(
6901
+ `purposes.name`,
6902
+ purpose.name,
6498
6903
  purpose.uuid
6499
- ),
6500
- "aria-label": `Select ${getTranslatedText(
6501
- `data_elements.name`,
6502
- dataElement.name,
6503
- dataElement.uuid
6504
- )}${dataElement.required ? " (required)" : ""}`
6904
+ )}`
6505
6905
  }
6506
6906
  )
6507
- ] }, dataElement.uuid))
6508
- }
6509
- )
6510
- ] }, purpose.uuid))
6907
+ ] }),
6908
+ !collapsedPurposes[stateKey] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6909
+ "div",
6910
+ {
6911
+ id: `purpose-${stateKey}`,
6912
+ style: styles2.dataElementsContainer,
6913
+ children: purpose.data_elements.map((dataElement) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
6914
+ "div",
6915
+ {
6916
+ style: styles2.dataElementItem,
6917
+ children: [
6918
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
6919
+ "span",
6920
+ {
6921
+ style: {
6922
+ ...styles2.dataElementText,
6923
+ ...responsiveStyles.dataElementText,
6924
+ ...textStyle
6925
+ },
6926
+ children: [
6927
+ getTranslatedText(
6928
+ `data_elements.name`,
6929
+ dataElement.name,
6930
+ dataElement.uuid
6931
+ ),
6932
+ dataElement.required && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RequiredMarker, {})
6933
+ ]
6934
+ }
6935
+ ),
6936
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
6937
+ "input",
6938
+ {
6939
+ type: "checkbox",
6940
+ checked: selectedDataElements[`${stateKey}-${dataElement.uuid}`] || false,
6941
+ onChange: () => handleDataElementCheckboxChange(
6942
+ dataElement.uuid,
6943
+ purpose.uuid,
6944
+ productUuid
6945
+ ),
6946
+ "aria-label": `Select ${getTranslatedText(
6947
+ `data_elements.name`,
6948
+ dataElement.name,
6949
+ dataElement.uuid
6950
+ )}${dataElement.required ? " (required)" : ""}`
6951
+ }
6952
+ )
6953
+ ]
6954
+ },
6955
+ dataElement.uuid
6956
+ ))
6957
+ }
6958
+ )
6959
+ ] }, stateKey);
6960
+ };
6961
+ if (!isMultiProduct(content == null ? void 0 : content.products)) {
6962
+ return (_a = content == null ? void 0 : content.purposes) == null ? void 0 : _a.map((purpose) => renderPurpose(purpose));
6963
+ }
6964
+ return groupPurposesByProduct(
6965
+ content == null ? void 0 : content.products,
6966
+ (_b = content == null ? void 0 : content.purposes) != null ? _b : [],
6967
+ content == null ? void 0 : content.product_purpose_order
6968
+ ).map((group) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
6969
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { ...styles2.optionTitle, margin: "0 0 8px" }, children: getTranslatedText(
6970
+ "products.name",
6971
+ group.product.name,
6972
+ group.product.uuid
6973
+ ) }),
6974
+ group.purposes.map(
6975
+ (purpose) => renderPurpose(purpose, group.product.uuid)
6976
+ )
6977
+ ] }, group.product.uuid));
6978
+ })()
6511
6979
  ] });
6512
6980
  };
6513
6981
  RedactoConsentInlineComponent.displayName = "RedactoConsentInline";
@@ -9649,10 +10117,14 @@ var RedactoNoticeAssisted = ({
9649
10117
  const handleConfirmStep = (0, import_react7.useCallback)(
9650
10118
  (mode = "selected") => {
9651
10119
  if (mode !== "selected" && content) {
9652
- const selection = buildSelectionForMode(content.purposes, mode, {
9653
- selectedPurposes,
9654
- selectedDataElements
9655
- });
10120
+ const selection = buildSelectionForMode(
10121
+ noticePurposeRows(void 0, content.purposes),
10122
+ mode,
10123
+ {
10124
+ selectedPurposes,
10125
+ selectedDataElements
10126
+ }
10127
+ );
9656
10128
  setSelectedPurposes(selection.selectedPurposes);
9657
10129
  setSelectedDataElements(selection.selectedDataElements);
9658
10130
  }
@@ -9853,10 +10325,14 @@ var RedactoNoticeAssisted = ({
9853
10325
  return;
9854
10326
  }
9855
10327
  }
9856
- const selection = buildSelectionForMode(content.purposes, acceptMode, {
9857
- selectedPurposes,
9858
- selectedDataElements
9859
- });
10328
+ const selection = buildSelectionForMode(
10329
+ noticePurposeRows(void 0, content.purposes),
10330
+ acceptMode,
10331
+ {
10332
+ selectedPurposes,
10333
+ selectedDataElements
10334
+ }
10335
+ );
9860
10336
  const submitPurposes = content.purposes.map((purpose) => ({
9861
10337
  ...purpose,
9862
10338
  selected: selection.selectedPurposes[purpose.uuid] || false,