@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.mjs CHANGED
@@ -17,7 +17,13 @@ import {
17
17
  } from "./chunk-FT5G2PXK.mjs";
18
18
 
19
19
  // src/RedactoNoticeConsent/RedactoNoticeConsent.tsx
20
- import { useCallback, useEffect as useEffect2, useMemo as useMemo2, useRef, useState } from "react";
20
+ import {
21
+ useCallback,
22
+ useEffect as useEffect2,
23
+ useMemo as useMemo2,
24
+ useRef,
25
+ useState
26
+ } from "react";
21
27
 
22
28
  // src/RedactoNoticeConsent/api/index.ts
23
29
  import { jwtDecode as jwtDecode2 } from "jwt-decode";
@@ -347,6 +353,9 @@ var submitConsentEvent = async ({
347
353
  }));
348
354
  return {
349
355
  purpose_uuid: purpose.uuid,
356
+ // Omitted on a single-product notice, where the ledger applies the
357
+ // selection to every product the purpose covers.
358
+ product_uuid: purpose.product_uuid || void 0,
350
359
  selected: Boolean(purpose.selected),
351
360
  data_elements: dataElements.length > 0 ? dataElements : void 0
352
361
  };
@@ -606,6 +615,9 @@ var DPO_AUDIO_ELEMENT_MAP = [
606
615
  { audioKey: "dpo_text_audio_url", elementId: "dpo-dpo-text" },
607
616
  { audioKey: "dpo_anchor_text_audio_url", elementId: "dpo-dpo-link" }
608
617
  ];
618
+ var PRODUCT_SECTION_COPY = {
619
+ toggleSelection: (productName, purposeCount) => `Toggle all ${purposeCount} purposes for ${productName}`
620
+ };
609
621
 
610
622
  // src/RedactoNoticeConsent/styles.ts
611
623
  var styles = {
@@ -822,6 +834,28 @@ var styles = {
822
834
  padding: 0,
823
835
  boxSizing: "border-box"
824
836
  },
837
+ // A product section's heading. Its own tokens rather than the row's: sharing
838
+ // optionTitle rendered the group name at the same size, weight and indent as
839
+ // the purposes inside it, leaving nothing to mark it as a heading.
840
+ sectionHeader: {
841
+ display: "flex",
842
+ justifyContent: "space-between",
843
+ alignItems: "center",
844
+ margin: "0 0 8px",
845
+ padding: 0,
846
+ boxSizing: "border-box"
847
+ },
848
+ // No colour token: the header always overrides it with the notice's
849
+ // headingColor, so one here would imply a default that never applies.
850
+ sectionTitle: {
851
+ fontWeight: 600,
852
+ fontSize: "17px",
853
+ lineHeight: "150%",
854
+ letterSpacing: "0.2px",
855
+ margin: 0,
856
+ padding: 0,
857
+ textAlign: "left"
858
+ },
825
859
  optionTextContainer: {
826
860
  display: "flex",
827
861
  flexDirection: "column",
@@ -1163,18 +1197,76 @@ var NoticeFooter = ({
1163
1197
  ] });
1164
1198
  };
1165
1199
 
1200
+ // src/shared/productMatrix.ts
1201
+ var productPurposeKey = (productUuid, purposeUuid) => productUuid ? `${productUuid}:${purposeUuid}` : purposeUuid;
1202
+ var purposeCoversProduct = (purpose, productUuid) => {
1203
+ const scope = purpose.product_uuids;
1204
+ if (!scope || scope.length === 0) return true;
1205
+ return scope.includes(productUuid);
1206
+ };
1207
+ var isMultiProduct = (products) => Boolean(products && products.length > 1);
1208
+ var groupPurposesByProduct = (products, purposes, order) => {
1209
+ if (!products || products.length === 0) return [];
1210
+ return products.map((product) => {
1211
+ const covered = purposes.filter(
1212
+ (purpose) => purposeCoversProduct(purpose, product.uuid)
1213
+ );
1214
+ const saved = order == null ? void 0 : order[product.uuid];
1215
+ if (!saved || saved.length === 0) return { product, purposes: covered };
1216
+ const byUuid = new Map(covered.map((p) => [p.uuid, p]));
1217
+ const ordered = [];
1218
+ for (const uuid of saved) {
1219
+ const found = byUuid.get(uuid);
1220
+ if (found) ordered.push(found);
1221
+ }
1222
+ const rest = covered.filter((p) => !saved.includes(p.uuid));
1223
+ return { product, purposes: [...ordered, ...rest] };
1224
+ }).filter((group) => group.purposes.length > 0);
1225
+ };
1226
+ var expandPurposesByProduct = (products, purposes, order) => {
1227
+ if (!isMultiProduct(products)) return [];
1228
+ const rows = [];
1229
+ for (const group of groupPurposesByProduct(products, purposes, order)) {
1230
+ for (const purpose of group.purposes) {
1231
+ rows.push({ product: group.product, purpose });
1232
+ }
1233
+ }
1234
+ return rows;
1235
+ };
1236
+ var noticePurposeRows = (products, purposes, order) => {
1237
+ if (!isMultiProduct(products)) {
1238
+ return purposes.map((purpose) => ({ purpose, productUuid: void 0 }));
1239
+ }
1240
+ const rows = expandPurposesByProduct(products, purposes, order).map(
1241
+ (row) => ({
1242
+ purpose: row.purpose,
1243
+ productUuid: row.product.uuid
1244
+ })
1245
+ );
1246
+ const rendered = new Set(rows.map((row) => row.purpose.uuid));
1247
+ const orphaned = purposes.filter((purpose) => !rendered.has(purpose.uuid));
1248
+ if (orphaned.length > 0) {
1249
+ console.warn(
1250
+ "[redacto] purposes cover no product on this notice and will not be shown or submitted:",
1251
+ orphaned.map((purpose) => purpose.uuid)
1252
+ );
1253
+ }
1254
+ return rows;
1255
+ };
1256
+
1166
1257
  // src/RedactoNoticeConsent/utils/consentSelection.ts
1167
- var dataElementKey = (purposeUuid, elementUuid) => `${purposeUuid}-${elementUuid}`;
1168
- var buildSelectionForMode = (purposes, mode, current) => {
1258
+ var dataElementKey = (purposeUuid, elementUuid, productUuid) => `${productPurposeKey(productUuid, purposeUuid)}-${elementUuid}`;
1259
+ var buildSelectionForMode = (rows, mode, current) => {
1169
1260
  if (mode === "selected") {
1170
1261
  return current;
1171
1262
  }
1172
1263
  const selectedPurposes = {};
1173
1264
  const selectedDataElements = {};
1174
- purposes.forEach((purpose) => {
1175
- selectedPurposes[purpose.uuid] = true;
1265
+ rows.forEach(({ purpose, productUuid }) => {
1266
+ const key = productPurposeKey(productUuid, purpose.uuid);
1267
+ selectedPurposes[key] = true;
1176
1268
  purpose.data_elements.forEach((element) => {
1177
- selectedDataElements[dataElementKey(purpose.uuid, element.uuid)] = element.enabled;
1269
+ selectedDataElements[dataElementKey(purpose.uuid, element.uuid, productUuid)] = element.enabled;
1178
1270
  });
1179
1271
  });
1180
1272
  return { selectedPurposes, selectedDataElements };
@@ -1194,10 +1286,11 @@ var buildPreselection = (purposes, mode) => {
1194
1286
  const selectedPurposes = {};
1195
1287
  const selectedDataElements = {};
1196
1288
  if (mode === "ALL") {
1197
- return buildSelectionForMode(purposes, "all", {
1198
- selectedPurposes,
1199
- selectedDataElements
1200
- });
1289
+ return buildSelectionForMode(
1290
+ purposes.map((purpose) => ({ purpose, productUuid: void 0 })),
1291
+ "all",
1292
+ { selectedPurposes, selectedDataElements }
1293
+ );
1201
1294
  }
1202
1295
  purposes.forEach((purpose) => {
1203
1296
  let hasPreselectedElement = false;
@@ -1625,8 +1718,113 @@ var updateConsentState = (updates) => {
1625
1718
 
1626
1719
  // src/RedactoNoticeConsent/RedactoNoticeConsent.tsx
1627
1720
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1721
+ var DEFAULT_HEADING_COLOR = "#323B4B";
1722
+ var CHEVRON_BUTTON_STYLE = {
1723
+ background: "none",
1724
+ border: "none",
1725
+ padding: 0,
1726
+ margin: 0,
1727
+ cursor: "pointer",
1728
+ display: "flex",
1729
+ alignItems: "center"
1730
+ };
1731
+ var CHEVRON_WRAPPER_STYLE = {
1732
+ display: "flex",
1733
+ alignItems: "center",
1734
+ justifyContent: "center",
1735
+ marginLeft: "5px"
1736
+ };
1737
+ var CollapseChevron = ({ open, color }) => /* @__PURE__ */ jsx3("div", { style: CHEVRON_WRAPPER_STYLE, children: /* @__PURE__ */ jsx3(
1738
+ "svg",
1739
+ {
1740
+ width: "7",
1741
+ height: "12",
1742
+ viewBox: "0 0 7 12",
1743
+ fill: "none",
1744
+ xmlns: "http://www.w3.org/2000/svg",
1745
+ stroke: color,
1746
+ strokeWidth: "2",
1747
+ strokeLinecap: "round",
1748
+ strokeLinejoin: "round",
1749
+ style: {
1750
+ transform: open ? "rotate(90deg)" : "rotate(0deg)",
1751
+ transition: "transform 0.3s ease"
1752
+ },
1753
+ "aria-hidden": "true",
1754
+ children: /* @__PURE__ */ jsx3("path", { d: "M1 1L6 6L1 11" })
1755
+ }
1756
+ ) });
1757
+ var ProductSectionHeader = ({
1758
+ product,
1759
+ purposeCount,
1760
+ allSelected,
1761
+ someSelected,
1762
+ collapsed,
1763
+ locked,
1764
+ settings,
1765
+ styles: styles4,
1766
+ responsiveStyles,
1767
+ onToggleCollapse,
1768
+ onToggleSelect,
1769
+ getTranslatedText
1770
+ }) => {
1771
+ const checkboxRef = useRef(null);
1772
+ const name = getTranslatedText("products.name", product.name, product.uuid);
1773
+ const headingColor = (settings == null ? void 0 : settings.headingColor) || DEFAULT_HEADING_COLOR;
1774
+ useEffect2(() => {
1775
+ if (checkboxRef.current) {
1776
+ checkboxRef.current.indeterminate = someSelected && !allSelected;
1777
+ }
1778
+ }, [someSelected, allSelected]);
1779
+ const handleToggleCollapse = () => {
1780
+ onToggleCollapse(product.uuid);
1781
+ };
1782
+ const handleToggleSelect = () => {
1783
+ onToggleSelect(product.uuid);
1784
+ };
1785
+ return /* @__PURE__ */ jsxs2("div", { style: styles4.sectionHeader, children: [
1786
+ /* @__PURE__ */ jsxs2("div", { style: styles4.optionLeft, children: [
1787
+ /* @__PURE__ */ jsx3(
1788
+ "button",
1789
+ {
1790
+ type: "button",
1791
+ onClick: handleToggleCollapse,
1792
+ "aria-expanded": !collapsed,
1793
+ "aria-controls": `product-${product.uuid}`,
1794
+ "aria-labelledby": `product-name-${product.uuid}`,
1795
+ style: CHEVRON_BUTTON_STYLE,
1796
+ children: /* @__PURE__ */ jsx3(CollapseChevron, { open: !collapsed, color: headingColor })
1797
+ }
1798
+ ),
1799
+ /* @__PURE__ */ jsx3("div", { style: styles4.optionTextContainer, children: /* @__PURE__ */ jsx3(
1800
+ "h2",
1801
+ {
1802
+ id: `product-name-${product.uuid}`,
1803
+ style: {
1804
+ ...styles4.sectionTitle,
1805
+ ...responsiveStyles.optionTitle,
1806
+ color: headingColor
1807
+ },
1808
+ children: name
1809
+ }
1810
+ ) })
1811
+ ] }),
1812
+ !locked && /* @__PURE__ */ jsx3(
1813
+ "input",
1814
+ {
1815
+ ref: checkboxRef,
1816
+ className: "redacto-checkbox-large",
1817
+ type: "checkbox",
1818
+ checked: allSelected,
1819
+ onChange: handleToggleSelect,
1820
+ "aria-label": PRODUCT_SECTION_COPY.toggleSelection(name, purposeCount)
1821
+ }
1822
+ )
1823
+ ] });
1824
+ };
1628
1825
  var PurposeItem = ({
1629
1826
  purpose,
1827
+ productUuid,
1630
1828
  selectedPurposes,
1631
1829
  collapsedPurposes,
1632
1830
  selectedDataElements,
@@ -1641,9 +1839,10 @@ var PurposeItem = ({
1641
1839
  selectedLanguage,
1642
1840
  initialDataElementSelections = {}
1643
1841
  }) => {
1842
+ const stateKey = productPurposeKey(productUuid, purpose.uuid);
1644
1843
  const headingStyle = useMemo2(
1645
1844
  () => ({
1646
- color: (settings == null ? void 0 : settings.headingColor) || "#323B4B"
1845
+ color: (settings == null ? void 0 : settings.headingColor) || DEFAULT_HEADING_COLOR
1647
1846
  }),
1648
1847
  [settings == null ? void 0 : settings.headingColor]
1649
1848
  );
@@ -1659,12 +1858,12 @@ var PurposeItem = ({
1659
1858
  "div",
1660
1859
  {
1661
1860
  style: styles4.optionLeft,
1662
- onClick: () => onPurposeCollapse(purpose.uuid),
1861
+ onClick: () => onPurposeCollapse(purpose.uuid, productUuid),
1663
1862
  role: "button",
1664
1863
  tabIndex: 0,
1665
- "aria-expanded": !collapsedPurposes[purpose.uuid],
1864
+ "aria-expanded": !collapsedPurposes[stateKey],
1666
1865
  "aria-controls": `purpose-${purpose.uuid}`,
1667
- "aria-label": `${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText(
1866
+ "aria-label": `${collapsedPurposes[stateKey] ? "Expand" : "Collapse"} ${getTranslatedText(
1668
1867
  `purposes.name`,
1669
1868
  purpose.name,
1670
1869
  purpose.uuid
@@ -1672,46 +1871,22 @@ var PurposeItem = ({
1672
1871
  onKeyDown: (e) => {
1673
1872
  if (e.key === "Enter" || e.key === " ") {
1674
1873
  e.preventDefault();
1675
- onPurposeCollapse(purpose.uuid);
1874
+ onPurposeCollapse(purpose.uuid, productUuid);
1676
1875
  }
1677
1876
  },
1678
1877
  children: [
1679
1878
  /* @__PURE__ */ jsx3(
1680
- "div",
1879
+ CollapseChevron,
1681
1880
  {
1682
- style: {
1683
- display: "flex",
1684
- alignItems: "center",
1685
- justifyContent: "center",
1686
- marginLeft: "5px"
1687
- },
1688
- children: /* @__PURE__ */ jsx3(
1689
- "svg",
1690
- {
1691
- width: "7",
1692
- height: "12",
1693
- viewBox: "0 0 7 12",
1694
- fill: "none",
1695
- xmlns: "http://www.w3.org/2000/svg",
1696
- stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
1697
- strokeWidth: "2",
1698
- strokeLinecap: "round",
1699
- strokeLinejoin: "round",
1700
- style: {
1701
- transform: !collapsedPurposes[purpose.uuid] ? "rotate(90deg)" : "rotate(0deg)",
1702
- transition: "transform 0.3s ease"
1703
- },
1704
- "aria-hidden": "true",
1705
- children: /* @__PURE__ */ jsx3("path", { d: "M1 1L6 6L1 11" })
1706
- }
1707
- )
1881
+ open: !collapsedPurposes[stateKey],
1882
+ color: (settings == null ? void 0 : settings.headingColor) || DEFAULT_HEADING_COLOR
1708
1883
  }
1709
1884
  ),
1710
1885
  /* @__PURE__ */ jsxs2("div", { style: styles4.optionTextContainer, children: [
1711
1886
  /* @__PURE__ */ jsxs2(
1712
1887
  "h2",
1713
1888
  {
1714
- id: `purpose-name-${purpose.uuid}`,
1889
+ id: `purpose-name-${stateKey}`,
1715
1890
  style: {
1716
1891
  ...styles4.optionTitle,
1717
1892
  ...responsiveStyles.optionTitle,
@@ -1727,7 +1902,7 @@ var PurposeItem = ({
1727
1902
  /* @__PURE__ */ jsx3(
1728
1903
  "h3",
1729
1904
  {
1730
- id: `purpose-description-${purpose.uuid}`,
1905
+ id: `purpose-description-${stateKey}`,
1731
1906
  style: {
1732
1907
  ...styles4.optionDescription,
1733
1908
  ...responsiveStyles.optionDescription,
@@ -1750,8 +1925,8 @@ var PurposeItem = ({
1750
1925
  {
1751
1926
  className: "redacto-checkbox-large",
1752
1927
  type: "checkbox",
1753
- checked: selectedPurposes[purpose.uuid] || false,
1754
- onChange: () => onPurposeToggle(purpose.uuid),
1928
+ checked: selectedPurposes[stateKey] || false,
1929
+ onChange: () => onPurposeToggle(purpose.uuid, productUuid),
1755
1930
  "aria-label": `Select all data elements for ${getTranslatedText(
1756
1931
  `purposes.name`,
1757
1932
  purpose.name,
@@ -1801,7 +1976,7 @@ var PurposeItem = ({
1801
1976
  }
1802
1977
  )
1803
1978
  ] }),
1804
- !collapsedPurposes[purpose.uuid] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ jsx3(
1979
+ !collapsedPurposes[stateKey] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ jsx3(
1805
1980
  "div",
1806
1981
  {
1807
1982
  id: `purpose-${purpose.uuid}`,
@@ -1814,7 +1989,7 @@ var PurposeItem = ({
1814
1989
  /* @__PURE__ */ jsxs2(
1815
1990
  "span",
1816
1991
  {
1817
- id: `data-element-${purpose.uuid}-${dataElement.uuid}`,
1992
+ id: `data-element-${stateKey}-${dataElement.uuid}`,
1818
1993
  style: {
1819
1994
  ...styles4.dataElementText,
1820
1995
  ...responsiveStyles.dataElementText,
@@ -1836,8 +2011,12 @@ var PurposeItem = ({
1836
2011
  {
1837
2012
  className: "redacto-checkbox-small",
1838
2013
  type: "checkbox",
1839
- checked: selectedDataElements[`${purpose.uuid}-${dataElement.uuid}`] || false,
1840
- onChange: () => onDataElementToggle(dataElement.uuid, purpose.uuid),
2014
+ checked: selectedDataElements[`${stateKey}-${dataElement.uuid}`] || false,
2015
+ onChange: () => onDataElementToggle(
2016
+ dataElement.uuid,
2017
+ purpose.uuid,
2018
+ productUuid
2019
+ ),
1841
2020
  "aria-label": `Select ${getTranslatedText(
1842
2021
  `data_elements.name`,
1843
2022
  dataElement.name,
@@ -1846,7 +2025,7 @@ var PurposeItem = ({
1846
2025
  }
1847
2026
  ),
1848
2027
  isAlreadyConsented && /* @__PURE__ */ jsx3(Fragment2, { children: (() => {
1849
- const combinedId = `${purpose.uuid}-${dataElement.uuid}`;
2028
+ const combinedId = `${stateKey}-${dataElement.uuid}`;
1850
2029
  const wasOriginallySelected = initialDataElementSelections[combinedId] || false;
1851
2030
  if (wasOriginallySelected) {
1852
2031
  return /* @__PURE__ */ jsx3(
@@ -1984,7 +2163,9 @@ var GuardianForm = ({
1984
2163
  switch (e.key) {
1985
2164
  case "ArrowDown":
1986
2165
  e.preventDefault();
1987
- setFocusedIndex((prev) => Math.min(prev + 1, RELATIONSHIP_OPTIONS.length - 1));
2166
+ setFocusedIndex(
2167
+ (prev) => Math.min(prev + 1, RELATIONSHIP_OPTIONS.length - 1)
2168
+ );
1988
2169
  break;
1989
2170
  case "ArrowUp":
1990
2171
  e.preventDefault();
@@ -1993,7 +2174,8 @@ var GuardianForm = ({
1993
2174
  case "Enter":
1994
2175
  case " ":
1995
2176
  e.preventDefault();
1996
- if (focusedIndex >= 0) handleSelect(RELATIONSHIP_OPTIONS[focusedIndex].value);
2177
+ if (focusedIndex >= 0)
2178
+ handleSelect(RELATIONSHIP_OPTIONS[focusedIndex].value);
1997
2179
  break;
1998
2180
  case "Escape":
1999
2181
  case "Tab":
@@ -2058,7 +2240,9 @@ var GuardianForm = ({
2058
2240
  lineHeight: "1.5",
2059
2241
  color: "#1E40AF"
2060
2242
  };
2061
- const selectedLabel = (_a = RELATIONSHIP_OPTIONS.find((o) => o.value === formData.guardianRelationship)) == null ? void 0 : _a.label;
2243
+ const selectedLabel = (_a = RELATIONSHIP_OPTIONS.find(
2244
+ (o) => o.value === formData.guardianRelationship
2245
+ )) == null ? void 0 : _a.label;
2062
2246
  const hasError = Boolean(errors.guardianRelationship);
2063
2247
  return /* @__PURE__ */ jsxs2(Fragment2, { children: [
2064
2248
  /* @__PURE__ */ jsx3("div", { style: { ...styles4.topSection, ...componentTextStyle }, children: /* @__PURE__ */ jsxs2("div", { style: styles4.topLeft, children: [
@@ -2158,7 +2342,8 @@ var GuardianForm = ({
2158
2342
  ] }),
2159
2343
  /* @__PURE__ */ jsxs2("div", { children: [
2160
2344
  /* @__PURE__ */ jsxs2("label", { htmlFor: "guardianContact", style: labelStyle, children: [
2161
- "Guardian's Phone Number ",
2345
+ "Guardian's Phone Number",
2346
+ " ",
2162
2347
  /* @__PURE__ */ jsx3("span", { style: { color: "#DC2626" }, children: "*" })
2163
2348
  ] }),
2164
2349
  /* @__PURE__ */ jsx3(
@@ -2214,7 +2399,17 @@ var GuardianForm = ({
2214
2399
  transition: "border-color 0.15s, box-shadow 0.15s"
2215
2400
  },
2216
2401
  children: [
2217
- /* @__PURE__ */ jsx3("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: selectedLabel || "Select relationship" }),
2402
+ /* @__PURE__ */ jsx3(
2403
+ "span",
2404
+ {
2405
+ style: {
2406
+ overflow: "hidden",
2407
+ textOverflow: "ellipsis",
2408
+ whiteSpace: "nowrap"
2409
+ },
2410
+ children: selectedLabel || "Select relationship"
2411
+ }
2412
+ ),
2218
2413
  /* @__PURE__ */ jsx3(
2219
2414
  "svg",
2220
2415
  {
@@ -2882,6 +3077,7 @@ var RedactoNoticeConsent = ({
2882
3077
  const [selectedPurposes, setSelectedPurposes] = useState({});
2883
3078
  const [selectedDataElements, setSelectedDataElements] = useState({});
2884
3079
  const [initialDataElementSelections, setInitialDataElementSelections] = useState({});
3080
+ const [collapsedProducts, setCollapsedProducts] = useState({});
2885
3081
  const [collapsedPurposes, setCollapsedPurposes] = useState({});
2886
3082
  const [hasAlreadyConsented, setHasAlreadyConsented] = useState(false);
2887
3083
  const [isFullyConsentedReviewMode, setIsFullyConsentedReviewMode] = useState(false);
@@ -2927,7 +3123,9 @@ var RedactoNoticeConsent = ({
2927
3123
  const statusPollIntervalRef = useRef(null);
2928
3124
  const verificationPopupRef = useRef(null);
2929
3125
  const pollingCleanupRef = useRef(null);
2930
- const autoTransitionTimerRef = useRef(null);
3126
+ const autoTransitionTimerRef = useRef(
3127
+ null
3128
+ );
2931
3129
  const digilockerRedirectTimeoutRef = useRef(null);
2932
3130
  const isInitialMountRef = useRef(true);
2933
3131
  const isPollingStatusRef = useRef(false);
@@ -3037,21 +3235,70 @@ var RedactoNoticeConsent = ({
3037
3235
  const logoRowStyle = useMemo2(
3038
3236
  () => ({
3039
3237
  ...styles.logoRow,
3040
- justifyContent: logoRowJustify(resolveLogoPosition(content == null ? void 0 : content.logo_position))
3238
+ justifyContent: logoRowJustify(
3239
+ resolveLogoPosition(content == null ? void 0 : content.logo_position)
3240
+ )
3041
3241
  }),
3042
3242
  [content == null ? void 0 : content.logo_position]
3043
3243
  );
3244
+ const purposeRows = useMemo2(
3245
+ () => {
3246
+ var _a2;
3247
+ return noticePurposeRows(
3248
+ content == null ? void 0 : content.products,
3249
+ (_a2 = content == null ? void 0 : content.purposes) != null ? _a2 : [],
3250
+ content == null ? void 0 : content.product_purpose_order
3251
+ );
3252
+ },
3253
+ [content]
3254
+ );
3255
+ const productGroups = useMemo2(
3256
+ () => {
3257
+ var _a2;
3258
+ return groupPurposesByProduct(
3259
+ content == null ? void 0 : content.products,
3260
+ (_a2 = content == null ? void 0 : content.purposes) != null ? _a2 : [],
3261
+ content == null ? void 0 : content.product_purpose_order
3262
+ );
3263
+ },
3264
+ [content]
3265
+ );
3044
3266
  const areAllRequiredElementsChecked = useMemo2(() => {
3267
+ var _a2;
3045
3268
  if (!content) return false;
3046
- return content.purposes.every((purpose) => {
3269
+ if (purposeRows.length === 0) return true;
3270
+ const isSatisfied = ({
3271
+ purpose,
3272
+ productUuid
3273
+ }) => {
3274
+ const key = productPurposeKey(productUuid, purpose.uuid);
3047
3275
  const requiredElements = purpose.data_elements.filter(
3048
3276
  (element) => element.required && element.enabled
3049
3277
  );
3050
3278
  return requiredElements.every(
3051
- (element) => selectedDataElements[`${purpose.uuid}-${element.uuid}`]
3279
+ (element) => selectedDataElements[`${key}-${element.uuid}`]
3052
3280
  );
3053
- });
3054
- }, [content, selectedDataElements]);
3281
+ };
3282
+ const rowsByProduct = /* @__PURE__ */ new Map();
3283
+ for (const row of purposeRows) {
3284
+ const groupKey = (_a2 = row.productUuid) != null ? _a2 : "";
3285
+ const group = rowsByProduct.get(groupKey);
3286
+ if (group) {
3287
+ group.push(row);
3288
+ } else {
3289
+ rowsByProduct.set(groupKey, [row]);
3290
+ }
3291
+ }
3292
+ const groups = [...rowsByProduct.values()];
3293
+ const everyProductHasMandatory = groups.every(
3294
+ (rows) => rows.some(
3295
+ ({ purpose }) => purpose.data_elements.some(
3296
+ (element) => element.required && element.enabled
3297
+ )
3298
+ )
3299
+ );
3300
+ return everyProductHasMandatory ? groups.some((rows) => rows.every(isSatisfied)) : purposeRows.every(isSatisfied);
3301
+ }, [content, purposeRows, selectedDataElements]);
3055
3302
  const acceptDisabled = isSubmitting || !areAllRequiredElementsChecked;
3056
3303
  useEffect2(() => {
3057
3304
  var _a2, _b2;
@@ -3063,6 +3310,7 @@ var RedactoNoticeConsent = ({
3063
3310
  setSelectedDataElements({});
3064
3311
  setInitialDataElementSelections({});
3065
3312
  setCollapsedPurposes({});
3313
+ setCollapsedProducts({});
3066
3314
  setHasAlreadyConsented(false);
3067
3315
  setErrorMessage(null);
3068
3316
  setFetchError(null);
@@ -3127,7 +3375,7 @@ var RedactoNoticeConsent = ({
3127
3375
  return false;
3128
3376
  };
3129
3377
  const getTranslatedText = (key, defaultText, itemId) => {
3130
- var _a2, _b2;
3378
+ var _a2, _b2, _c2;
3131
3379
  if (!content) return defaultText;
3132
3380
  if (isDefaultLanguage(selectedLanguage)) {
3133
3381
  if (key === "privacy_policy_anchor_text" && content.privacy_policy_anchor_text) {
@@ -3165,8 +3413,18 @@ var RedactoNoticeConsent = ({
3165
3413
  }
3166
3414
  return defaultText;
3167
3415
  }
3416
+ if (key === "products.name" || key === "products.description") {
3417
+ const productData = (_b2 = translationMap.products) == null ? void 0 : _b2[itemId];
3418
+ if (productData) {
3419
+ if (typeof productData === "string") {
3420
+ return key === "products.name" ? productData : defaultText;
3421
+ }
3422
+ return key === "products.name" ? productData.name || defaultText : productData.description || defaultText;
3423
+ }
3424
+ return defaultText;
3425
+ }
3168
3426
  if (key.startsWith("data_elements.")) {
3169
- return ((_b2 = translationMap.data_elements) == null ? void 0 : _b2[itemId]) || defaultText;
3427
+ return ((_c2 = translationMap.data_elements) == null ? void 0 : _c2[itemId]) || defaultText;
3170
3428
  }
3171
3429
  }
3172
3430
  const value = translationMap[key];
@@ -3224,63 +3482,76 @@ var RedactoNoticeConsent = ({
3224
3482
  defaultLang === "en" || defaultLang === "EN" ? "English" : defaultLang
3225
3483
  );
3226
3484
  }
3227
- const initialCollapsedState = activeConfig.purposes.reduce(
3228
- (acc, purpose) => ({
3485
+ const initialCollapsedState = noticePurposeRows(
3486
+ activeConfig.products,
3487
+ activeConfig.purposes,
3488
+ activeConfig.product_purpose_order
3489
+ ).reduce(
3490
+ (acc, { purpose, productUuid }) => ({
3229
3491
  ...acc,
3230
- [purpose.uuid]: true
3492
+ [productPurposeKey(productUuid, purpose.uuid)]: true
3231
3493
  }),
3232
3494
  {}
3233
3495
  );
3234
3496
  setCollapsedPurposes(initialCollapsedState);
3497
+ setCollapsedProducts({});
3235
3498
  const isReconsentRequired = consentContentData.detail.reconsent_required;
3236
3499
  const purposeSelections = consentContentData.detail.purpose_selections;
3237
3500
  const newCategorizedPurposes = isReconsentRequired ? {
3238
- alreadyConsented: activeConfig.purposes.filter(
3239
- (purpose) => {
3240
- const selection = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3241
- return selection && (selection.status === "ACTIVE" || selection.status === "EXPIRED") && !selection.needs_reconsent;
3242
- }
3243
- ),
3244
- needsConsent: activeConfig.purposes.filter(
3245
- (purpose) => {
3246
- const selection = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3247
- return !selection || selection.status !== "ACTIVE" || selection.needs_reconsent;
3248
- }
3249
- )
3501
+ alreadyConsented: activeConfig.purposes.filter((purpose) => {
3502
+ const selection = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3503
+ return selection && (selection.status === "ACTIVE" || selection.status === "EXPIRED") && !selection.needs_reconsent;
3504
+ }),
3505
+ needsConsent: activeConfig.purposes.filter((purpose) => {
3506
+ const selection = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3507
+ return !selection || selection.status !== "ACTIVE" || selection.needs_reconsent;
3508
+ })
3250
3509
  } : null;
3251
3510
  setCategorizedPurposes(newCategorizedPurposes);
3252
3511
  const preselection = buildPreselection(
3253
3512
  activeConfig.purposes,
3254
3513
  resolvePurposePreselection(activeConfig.purpose_preselection)
3255
3514
  );
3256
- const initialPurposeState = activeConfig.purposes.reduce(
3257
- (acc, purpose) => {
3515
+ const seedRows = noticePurposeRows(
3516
+ activeConfig.products,
3517
+ activeConfig.purposes,
3518
+ activeConfig.product_purpose_order
3519
+ );
3520
+ const recordedFor = (purposeUuid, productUuid) => {
3521
+ var _a3, _b2;
3522
+ const perProduct = productUuid ? (_b2 = (_a3 = activeConfig.product_purpose_selections) == null ? void 0 : _a3[productUuid]) == null ? void 0 : _b2[purposeUuid] : void 0;
3523
+ return perProduct != null ? perProduct : purposeSelections == null ? void 0 : purposeSelections[purposeUuid];
3524
+ };
3525
+ const initialPurposeState = seedRows.reduce(
3526
+ (acc, { purpose, productUuid }) => {
3258
3527
  var _a3;
3259
- const recorded = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid];
3528
+ const key = productPurposeKey(productUuid, purpose.uuid);
3529
+ const recorded = recordedFor(purpose.uuid, productUuid);
3260
3530
  if (hasRecordedPurposeState(recorded)) {
3261
- acc[purpose.uuid] = recorded.selected;
3531
+ acc[key] = recorded.selected;
3262
3532
  } else {
3263
- acc[purpose.uuid] = (_a3 = preselection.selectedPurposes[purpose.uuid]) != null ? _a3 : false;
3533
+ acc[key] = (_a3 = preselection.selectedPurposes[purpose.uuid]) != null ? _a3 : false;
3264
3534
  }
3265
3535
  return acc;
3266
3536
  },
3267
3537
  {}
3268
3538
  );
3269
3539
  setSelectedPurposes(initialPurposeState);
3270
- const initialDataElementState = activeConfig.purposes.reduce(
3271
- (acc, purpose) => {
3272
- const hasRecordedSelection = hasRecordedPurposeState(
3273
- purposeSelections == null ? void 0 : purposeSelections[purpose.uuid]
3274
- );
3540
+ const initialDataElementState = seedRows.reduce(
3541
+ (acc, { purpose, productUuid }) => {
3542
+ const key = productPurposeKey(productUuid, purpose.uuid);
3543
+ const recorded = recordedFor(purpose.uuid, productUuid);
3544
+ const hasRecordedSelection = hasRecordedPurposeState(recorded);
3275
3545
  purpose.data_elements.forEach((element) => {
3276
- var _a3, _b2, _c2;
3277
- const combinedId = `${purpose.uuid}-${element.uuid}`;
3278
- if ((_b2 = (_a3 = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid]) == null ? void 0 : _a3.data_elements) == null ? void 0 : _b2[element.uuid]) {
3279
- acc[combinedId] = purposeSelections[purpose.uuid].data_elements[element.uuid].selected;
3546
+ var _a3, _b2;
3547
+ const combinedId = `${key}-${element.uuid}`;
3548
+ const recordedElement = (_a3 = recorded == null ? void 0 : recorded.data_elements) == null ? void 0 : _a3[element.uuid];
3549
+ if (recordedElement) {
3550
+ acc[combinedId] = recordedElement.selected;
3280
3551
  } else if (hasRecordedSelection) {
3281
3552
  acc[combinedId] = false;
3282
3553
  } else {
3283
- acc[combinedId] = (_c2 = preselection.selectedDataElements[combinedId]) != null ? _c2 : false;
3554
+ acc[combinedId] = (_b2 = preselection.selectedDataElements[`${purpose.uuid}-${element.uuid}`]) != null ? _b2 : false;
3284
3555
  }
3285
3556
  });
3286
3557
  return acc;
@@ -3291,10 +3562,14 @@ var RedactoNoticeConsent = ({
3291
3562
  if (isReconsentRequired && newCategorizedPurposes) {
3292
3563
  setIsFullyConsentedReviewMode(false);
3293
3564
  const initialSelections = {};
3294
- newCategorizedPurposes.alreadyConsented.forEach((purpose) => {
3565
+ const alreadyConsentedUuids = new Set(
3566
+ newCategorizedPurposes.alreadyConsented.map((p) => p.uuid)
3567
+ );
3568
+ seedRows.filter(({ purpose }) => alreadyConsentedUuids.has(purpose.uuid)).forEach(({ purpose, productUuid }) => {
3569
+ const rowKey = productPurposeKey(productUuid, purpose.uuid);
3295
3570
  purpose.data_elements.forEach((element) => {
3296
3571
  var _a3, _b2;
3297
- const combinedId = `${purpose.uuid}-${element.uuid}`;
3572
+ const combinedId = `${rowKey}-${element.uuid}`;
3298
3573
  if ((_b2 = (_a3 = purposeSelections == null ? void 0 : purposeSelections[purpose.uuid]) == null ? void 0 : _a3.data_elements) == null ? void 0 : _b2[element.uuid]) {
3299
3574
  initialSelections[combinedId] = purposeSelections[purpose.uuid].data_elements[element.uuid].selected;
3300
3575
  } else {
@@ -3304,28 +3579,27 @@ var RedactoNoticeConsent = ({
3304
3579
  });
3305
3580
  setInitialDataElementSelections(initialSelections);
3306
3581
  } else if (includeFullyConsentedData && purposeSelections) {
3307
- const allPurposesSelected = activeConfig.purposes.length > 0 && activeConfig.purposes.every(
3308
- (p) => {
3582
+ const allPurposesSelected = seedRows.length > 0 && seedRows.every(
3583
+ ({ purpose, productUuid }) => {
3309
3584
  var _a3;
3310
- return ((_a3 = purposeSelections[p.uuid]) == null ? void 0 : _a3.selected) === true;
3585
+ return ((_a3 = recordedFor(purpose.uuid, productUuid)) == null ? void 0 : _a3.selected) === true;
3311
3586
  }
3312
3587
  );
3313
3588
  setIsFullyConsentedReviewMode(allPurposesSelected);
3314
3589
  if (allPurposesSelected) {
3315
3590
  const initialSelections = {};
3316
- activeConfig.purposes.forEach(
3317
- (purpose) => {
3318
- purpose.data_elements.forEach((element) => {
3319
- var _a3, _b2;
3320
- const combinedId = `${purpose.uuid}-${element.uuid}`;
3321
- if ((_b2 = (_a3 = purposeSelections[purpose.uuid]) == null ? void 0 : _a3.data_elements) == null ? void 0 : _b2[element.uuid]) {
3322
- initialSelections[combinedId] = purposeSelections[purpose.uuid].data_elements[element.uuid].selected;
3323
- } else {
3324
- initialSelections[combinedId] = false;
3325
- }
3326
- });
3327
- }
3328
- );
3591
+ seedRows.forEach(({ purpose, productUuid }) => {
3592
+ const rowKey = productPurposeKey(productUuid, purpose.uuid);
3593
+ purpose.data_elements.forEach((element) => {
3594
+ var _a3, _b2;
3595
+ const combinedId = `${rowKey}-${element.uuid}`;
3596
+ if ((_b2 = (_a3 = purposeSelections[purpose.uuid]) == null ? void 0 : _a3.data_elements) == null ? void 0 : _b2[element.uuid]) {
3597
+ initialSelections[combinedId] = purposeSelections[purpose.uuid].data_elements[element.uuid].selected;
3598
+ } else {
3599
+ initialSelections[combinedId] = false;
3600
+ }
3601
+ });
3602
+ });
3329
3603
  setInitialDataElementSelections(initialSelections);
3330
3604
  } else {
3331
3605
  setIsFullyConsentedReviewMode(false);
@@ -3375,10 +3649,11 @@ var RedactoNoticeConsent = ({
3375
3649
  // Removed onError and isRefreshingToken from dependencies to prevent duplicate API calls
3376
3650
  // onError is only called on errors, not needed for initial data fetching
3377
3651
  ]);
3378
- const togglePurposeCollapse = (purposeUuid) => {
3652
+ const togglePurposeCollapse = (purposeUuid, productUuid) => {
3653
+ const key = productPurposeKey(productUuid, purposeUuid);
3379
3654
  setCollapsedPurposes((prev) => ({
3380
3655
  ...prev,
3381
- [purposeUuid]: !prev[purposeUuid]
3656
+ [key]: !prev[key]
3382
3657
  }));
3383
3658
  };
3384
3659
  const toggleLanguageDropdown = () => {
@@ -3388,28 +3663,70 @@ var RedactoNoticeConsent = ({
3388
3663
  setSelectedLanguage(lang);
3389
3664
  setIsLanguageDropdownOpen(false);
3390
3665
  };
3391
- const handlePurposeCheckboxChange = (purposeUuid) => {
3666
+ const handlePurposeCheckboxChange = (purposeUuid, productUuid) => {
3392
3667
  if (content) {
3393
3668
  const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
3394
3669
  if (purpose) {
3395
- const newPurposeState = !selectedPurposes[purposeUuid];
3670
+ const key = productPurposeKey(productUuid, purposeUuid);
3671
+ const newPurposeState = !selectedPurposes[key];
3396
3672
  setSelectedPurposes((prev) => ({
3397
3673
  ...prev,
3398
- [purposeUuid]: newPurposeState
3674
+ [key]: newPurposeState
3399
3675
  }));
3400
3676
  setSelectedDataElements((prev) => {
3401
3677
  const updated = { ...prev };
3402
3678
  purpose.data_elements.forEach((el) => {
3403
- updated[`${purposeUuid}-${el.uuid}`] = newPurposeState;
3679
+ updated[`${key}-${el.uuid}`] = newPurposeState;
3404
3680
  });
3405
3681
  return updated;
3406
3682
  });
3407
3683
  }
3408
3684
  }
3409
3685
  };
3410
- const handleDataElementCheckboxChange = (elementUuid, purposeUuid) => {
3686
+ const toggleProductCollapse = (productUuid) => {
3687
+ setCollapsedProducts((prev) => ({
3688
+ ...prev,
3689
+ [productUuid]: !prev[productUuid]
3690
+ }));
3691
+ };
3692
+ const handleProductCheckboxChange = (productUuid) => {
3693
+ if (!content) {
3694
+ return;
3695
+ }
3696
+ const group = productGroups.find(
3697
+ (candidate) => candidate.product.uuid === productUuid
3698
+ );
3699
+ if (!group) {
3700
+ return;
3701
+ }
3702
+ const nextState = !group.purposes.every(
3703
+ (purpose) => selectedPurposes[productPurposeKey(productUuid, purpose.uuid)]
3704
+ );
3705
+ setSelectedPurposes((prev) => {
3706
+ const updated = { ...prev };
3707
+ group.purposes.forEach((purpose) => {
3708
+ updated[productPurposeKey(productUuid, purpose.uuid)] = nextState;
3709
+ });
3710
+ return updated;
3711
+ });
3712
+ setSelectedDataElements((prev) => {
3713
+ const updated = { ...prev };
3714
+ group.purposes.forEach((purpose) => {
3715
+ const key = productPurposeKey(productUuid, purpose.uuid);
3716
+ purpose.data_elements.forEach((element) => {
3717
+ updated[`${key}-${element.uuid}`] = nextState;
3718
+ });
3719
+ });
3720
+ return updated;
3721
+ });
3722
+ if (!nextState) {
3723
+ setCollapsedProducts((prev) => ({ ...prev, [productUuid]: false }));
3724
+ }
3725
+ };
3726
+ const handleDataElementCheckboxChange = (elementUuid, purposeUuid, productUuid) => {
3411
3727
  if (!content) return;
3412
- const combinedId = `${purposeUuid}-${elementUuid}`;
3728
+ const key = productPurposeKey(productUuid, purposeUuid);
3729
+ const combinedId = `${key}-${elementUuid}`;
3413
3730
  setSelectedDataElements((prev) => {
3414
3731
  const newState = {
3415
3732
  ...prev,
@@ -3423,16 +3740,16 @@ var RedactoNoticeConsent = ({
3423
3740
  let shouldCheckPurpose;
3424
3741
  if (requiredElements.length > 0) {
3425
3742
  shouldCheckPurpose = requiredElements.every(
3426
- (el) => newState[`${purposeUuid}-${el.uuid}`]
3743
+ (el) => newState[`${key}-${el.uuid}`]
3427
3744
  );
3428
3745
  } else {
3429
3746
  shouldCheckPurpose = purpose.data_elements.some(
3430
- (el) => newState[`${purposeUuid}-${el.uuid}`]
3747
+ (el) => newState[`${key}-${el.uuid}`]
3431
3748
  );
3432
3749
  }
3433
3750
  setSelectedPurposes((prevPurposes) => ({
3434
3751
  ...prevPurposes,
3435
- [purposeUuid]: shouldCheckPurpose
3752
+ [key]: shouldCheckPurpose
3436
3753
  }));
3437
3754
  }
3438
3755
  return newState;
@@ -3442,14 +3759,16 @@ var RedactoNoticeConsent = ({
3442
3759
  const submitController = new AbortController();
3443
3760
  try {
3444
3761
  if (isMinorFlow && !verificationReference) {
3445
- setErrorMessage("Guardian verification is required before consent can be submitted.");
3762
+ setErrorMessage(
3763
+ "Guardian verification is required before consent can be submitted."
3764
+ );
3446
3765
  return;
3447
3766
  }
3448
3767
  setIsSubmittingAccept(true);
3449
3768
  setIsSubmitting(true);
3450
3769
  setErrorMessage(null);
3451
3770
  if (content) {
3452
- const selection = buildSelectionForMode(content.purposes, mode, {
3771
+ const selection = buildSelectionForMode(purposeRows, mode, {
3453
3772
  selectedPurposes,
3454
3773
  selectedDataElements
3455
3774
  });
@@ -3462,14 +3781,20 @@ var RedactoNoticeConsent = ({
3462
3781
  baseUrl,
3463
3782
  ledgerBaseUrl,
3464
3783
  noticeUuid: content.notice_uuid,
3465
- purposes: content.purposes.map((purpose) => {
3784
+ // One entry per (product, purpose) once a notice covers several. The
3785
+ // ledger keys consent on the pair, and repeating a purpose under two
3786
+ // products is how one submission accepts it for one and declines it
3787
+ // for the other. Collapses to content.purposes on a single-product
3788
+ // notice, so that payload is byte-identical to before.
3789
+ purposes: purposeRows.map(({ purpose, productUuid }) => {
3790
+ const stateKey = productPurposeKey(productUuid, purpose.uuid);
3466
3791
  const isAlreadyConsented = (categorizedPurposes == null ? void 0 : categorizedPurposes.alreadyConsented.some(
3467
3792
  (p) => p.uuid === purpose.uuid
3468
3793
  )) || false;
3469
- let purposeSelected = selection.selectedPurposes[purpose.uuid] || false;
3794
+ let purposeSelected = selection.selectedPurposes[stateKey] || false;
3470
3795
  if (mode === "selected" && isAlreadyConsented) {
3471
3796
  const wasModified = purpose.data_elements.some((element) => {
3472
- const combinedId = `${purpose.uuid}-${element.uuid}`;
3797
+ const combinedId = `${stateKey}-${element.uuid}`;
3473
3798
  const currentSelection = selection.selectedDataElements[combinedId] || false;
3474
3799
  const initialSelection = initialDataElementSelections[combinedId] || false;
3475
3800
  return currentSelection !== initialSelection;
@@ -3480,10 +3805,11 @@ var RedactoNoticeConsent = ({
3480
3805
  }
3481
3806
  return {
3482
3807
  ...purpose,
3808
+ ...productUuid ? { product_uuid: productUuid } : {},
3483
3809
  selected: purposeSelected,
3484
3810
  data_elements: purpose.data_elements.map((element) => ({
3485
3811
  ...element,
3486
- selected: element.required ? true : selection.selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false
3812
+ selected: element.required ? true : selection.selectedDataElements[`${stateKey}-${element.uuid}`] || false
3487
3813
  }))
3488
3814
  };
3489
3815
  }),
@@ -3586,7 +3912,9 @@ var RedactoNoticeConsent = ({
3586
3912
  return;
3587
3913
  }
3588
3914
  if (!response.session_token || !response.digilocker_redirect_url) {
3589
- throw new Error("Invalid response from server: missing required fields");
3915
+ throw new Error(
3916
+ "Invalid response from server: missing required fields"
3917
+ );
3590
3918
  }
3591
3919
  setShowGuardianForm(false);
3592
3920
  setShowVerificationScreen(true);
@@ -3676,7 +4004,18 @@ var RedactoNoticeConsent = ({
3676
4004
  } finally {
3677
4005
  setIsSubmittingGuardian(false);
3678
4006
  }
3679
- }, [accessToken, sandboxParams, baseUrl, guardianFormData, digilockerMode, digilockerCallbackUrl, selectedPurposes, selectedDataElements, noticeId, onError]);
4007
+ }, [
4008
+ accessToken,
4009
+ sandboxParams,
4010
+ baseUrl,
4011
+ guardianFormData,
4012
+ digilockerMode,
4013
+ digilockerCallbackUrl,
4014
+ selectedPurposes,
4015
+ selectedDataElements,
4016
+ noticeId,
4017
+ onError
4018
+ ]);
3680
4019
  const handleAgeVerificationYes = useCallback(() => {
3681
4020
  setShowAgeVerification(false);
3682
4021
  setIsMinorFlow(false);
@@ -3727,20 +4066,21 @@ var RedactoNoticeConsent = ({
3727
4066
  } catch {
3728
4067
  }
3729
4068
  }
3730
- setVerificationError(
3731
- "Verification window was closed. Please try again."
3732
- );
4069
+ setVerificationError("Verification window was closed. Please try again.");
3733
4070
  setIsPollingStatus(false);
3734
4071
  isPollingStatusRef.current = false;
3735
4072
  setIsInitiatingVerification(false);
3736
4073
  }, [accessToken, sandboxParams, baseUrl]);
3737
- const startPopupMonitoring = useCallback((popup) => {
3738
- popupCheckIntervalRef.current = window.setInterval(() => {
3739
- if (popup.closed) {
3740
- handlePopupClosed();
3741
- }
3742
- }, 500);
3743
- }, [handlePopupClosed]);
4074
+ const startPopupMonitoring = useCallback(
4075
+ (popup) => {
4076
+ popupCheckIntervalRef.current = window.setInterval(() => {
4077
+ if (popup.closed) {
4078
+ handlePopupClosed();
4079
+ }
4080
+ }, 500);
4081
+ },
4082
+ [handlePopupClosed]
4083
+ );
3744
4084
  const getVerificationErrorMessage = useCallback(
3745
4085
  (errorCode, fallbackError) => {
3746
4086
  switch (errorCode) {
@@ -3842,7 +4182,10 @@ var RedactoNoticeConsent = ({
3842
4182
  return;
3843
4183
  }
3844
4184
  if (!paused) {
3845
- statusPollIntervalRef.current = window.setTimeout(poll, POLL_INTERVAL);
4185
+ statusPollIntervalRef.current = window.setTimeout(
4186
+ poll,
4187
+ POLL_INTERVAL
4188
+ );
3846
4189
  }
3847
4190
  } catch (error) {
3848
4191
  const err = error;
@@ -3863,7 +4206,10 @@ var RedactoNoticeConsent = ({
3863
4206
  );
3864
4207
  return;
3865
4208
  }
3866
- statusPollIntervalRef.current = window.setTimeout(poll, POLL_INTERVAL);
4209
+ statusPollIntervalRef.current = window.setTimeout(
4210
+ poll,
4211
+ POLL_INTERVAL
4212
+ );
3867
4213
  }
3868
4214
  };
3869
4215
  const onVisibilityChange = () => {
@@ -4164,12 +4510,19 @@ var RedactoNoticeConsent = ({
4164
4510
  const dataElementsAudioCount = ((_a2 = purpose.data_elements) == null ? void 0 : _a2.length) || 0;
4165
4511
  const totalPurposeAudioCount = purposeAudioCount + dataElementsAudioCount;
4166
4512
  if (currentIndex >= audioOffset && currentIndex < audioOffset + totalPurposeAudioCount) {
4167
- if (collapsedPurposes[purpose.uuid]) {
4168
- setCollapsedPurposes((prev) => ({
4169
- ...prev,
4170
- [purpose.uuid]: false
4171
- }));
4172
- }
4513
+ setCollapsedPurposes((prev) => {
4514
+ const next = { ...prev };
4515
+ let changed = false;
4516
+ for (const row of purposeRows) {
4517
+ if (row.purpose.uuid !== purpose.uuid) continue;
4518
+ const rowKey = productPurposeKey(row.productUuid, purpose.uuid);
4519
+ if (next[rowKey]) {
4520
+ next[rowKey] = false;
4521
+ changed = true;
4522
+ }
4523
+ }
4524
+ return changed ? next : prev;
4525
+ });
4173
4526
  break;
4174
4527
  }
4175
4528
  audioOffset += totalPurposeAudioCount;
@@ -4247,7 +4600,15 @@ var RedactoNoticeConsent = ({
4247
4600
  return () => {
4248
4601
  ttsAbortController.abort();
4249
4602
  };
4250
- }, [content, selectedLanguage, accessToken, sandboxParams, baseUrl, ledgerBaseUrl, getLanguageCode]);
4603
+ }, [
4604
+ content,
4605
+ selectedLanguage,
4606
+ accessToken,
4607
+ sandboxParams,
4608
+ baseUrl,
4609
+ ledgerBaseUrl,
4610
+ getLanguageCode
4611
+ ]);
4251
4612
  const modalStyle = {
4252
4613
  borderRadius: (settings == null ? void 0 : settings.borderRadius) || "8px",
4253
4614
  backgroundColor: (settings == null ? void 0 : settings.backgroundColor) || "#ffffff",
@@ -4946,7 +5307,7 @@ var RedactoNoticeConsent = ({
4946
5307
  },
4947
5308
  `purpose-section-heading-${selectedLanguage}`
4948
5309
  ),
4949
- categorizedPurposes ? /* @__PURE__ */ jsxs2("div", { style: styles.optionsContainer, children: [
5310
+ categorizedPurposes && !isMultiProduct(content == null ? void 0 : content.products) ? /* @__PURE__ */ jsxs2("div", { style: styles.optionsContainer, children: [
4950
5311
  categorizedPurposes.alreadyConsented.map((purpose) => /* @__PURE__ */ jsx3(
4951
5312
  PurposeItem,
4952
5313
  {
@@ -4988,7 +5349,61 @@ var RedactoNoticeConsent = ({
4988
5349
  ))
4989
5350
  ] }) : (
4990
5351
  /* Regular consent UI */
4991
- /* @__PURE__ */ jsx3("div", { style: styles.optionsContainer, children: (_y = content == null ? void 0 : content.purposes) == null ? void 0 : _y.map((purpose) => /* @__PURE__ */ jsx3(
5352
+ /* @__PURE__ */ jsx3("div", { style: styles.optionsContainer, children: isMultiProduct(content == null ? void 0 : content.products) ? productGroups.map((group) => {
5353
+ const selectedInGroup = group.purposes.filter(
5354
+ (purpose) => selectedPurposes[productPurposeKey(
5355
+ group.product.uuid,
5356
+ purpose.uuid
5357
+ )]
5358
+ ).length;
5359
+ return /* @__PURE__ */ jsxs2("div", { children: [
5360
+ /* @__PURE__ */ jsx3(
5361
+ ProductSectionHeader,
5362
+ {
5363
+ product: group.product,
5364
+ purposeCount: group.purposes.length,
5365
+ allSelected: selectedInGroup === group.purposes.length,
5366
+ someSelected: selectedInGroup > 0,
5367
+ collapsed: collapsedProducts[group.product.uuid] || false,
5368
+ locked: isFullyConsentedReviewMode,
5369
+ settings,
5370
+ styles,
5371
+ responsiveStyles,
5372
+ onToggleCollapse: toggleProductCollapse,
5373
+ onToggleSelect: handleProductCheckboxChange,
5374
+ getTranslatedText
5375
+ }
5376
+ ),
5377
+ /* @__PURE__ */ jsx3(
5378
+ "div",
5379
+ {
5380
+ id: `product-${group.product.uuid}`,
5381
+ hidden: collapsedProducts[group.product.uuid] || false,
5382
+ children: group.purposes.map((purpose) => /* @__PURE__ */ jsx3(
5383
+ PurposeItem,
5384
+ {
5385
+ purpose,
5386
+ productUuid: group.product.uuid,
5387
+ selectedPurposes,
5388
+ collapsedPurposes,
5389
+ selectedDataElements,
5390
+ settings,
5391
+ styles,
5392
+ responsiveStyles,
5393
+ onPurposeToggle: handlePurposeCheckboxChange,
5394
+ onPurposeCollapse: togglePurposeCollapse,
5395
+ onDataElementToggle: handleDataElementCheckboxChange,
5396
+ getTranslatedText,
5397
+ isAlreadyConsented: isFullyConsentedReviewMode,
5398
+ selectedLanguage,
5399
+ initialDataElementSelections
5400
+ },
5401
+ `${group.product.uuid}-${purpose.uuid}-${selectedLanguage}`
5402
+ ))
5403
+ }
5404
+ )
5405
+ ] }, group.product.uuid);
5406
+ }) : (_y = content == null ? void 0 : content.purposes) == null ? void 0 : _y.map((purpose) => /* @__PURE__ */ jsx3(
4992
5407
  PurposeItem,
4993
5408
  {
4994
5409
  purpose,
@@ -5699,7 +6114,6 @@ var RedactoConsentInlineComponent = ({
5699
6114
  settings,
5700
6115
  applicationId
5701
6116
  }) => {
5702
- var _a;
5703
6117
  const [content, setContent] = useState3(null);
5704
6118
  const [isLoading, setIsLoading] = useState3(true);
5705
6119
  const [isSubmitting, setIsSubmitting] = useState3(false);
@@ -5756,7 +6170,7 @@ var RedactoConsentInlineComponent = ({
5756
6170
  }
5757
6171
  }, [hasAlreadyConsented, onAccept]);
5758
6172
  const getTranslatedText = (key, defaultText, itemId) => {
5759
- var _a2, _b;
6173
+ var _a, _b, _c;
5760
6174
  if (!content) return defaultText;
5761
6175
  if (selectedLanguage === "English" || selectedLanguage === "en" || selectedLanguage === "EN") {
5762
6176
  if (key === "privacy_policy_anchor_text" && content.privacy_policy_anchor_text) {
@@ -5773,7 +6187,7 @@ var RedactoConsentInlineComponent = ({
5773
6187
  }
5774
6188
  if (itemId) {
5775
6189
  if (key === "purposes.name" || key === "purposes.description") {
5776
- const purposeData = (_a2 = translationMap.purposes) == null ? void 0 : _a2[itemId];
6190
+ const purposeData = (_a = translationMap.purposes) == null ? void 0 : _a[itemId];
5777
6191
  if (purposeData) {
5778
6192
  if (typeof purposeData === "string") {
5779
6193
  return key === "purposes.name" ? purposeData : defaultText;
@@ -5782,8 +6196,18 @@ var RedactoConsentInlineComponent = ({
5782
6196
  }
5783
6197
  return defaultText;
5784
6198
  }
6199
+ if (key === "products.name" || key === "products.description") {
6200
+ const productData = (_b = translationMap.products) == null ? void 0 : _b[itemId];
6201
+ if (productData) {
6202
+ if (typeof productData === "string") {
6203
+ return key === "products.name" ? productData : defaultText;
6204
+ }
6205
+ return key === "products.name" ? productData.name || defaultText : productData.description || defaultText;
6206
+ }
6207
+ return defaultText;
6208
+ }
5785
6209
  if (key.startsWith("data_elements.")) {
5786
- return ((_b = translationMap.data_elements) == null ? void 0 : _b[itemId]) || defaultText;
6210
+ return ((_c = translationMap.data_elements) == null ? void 0 : _c[itemId]) || defaultText;
5787
6211
  }
5788
6212
  }
5789
6213
  const value = translationMap[key];
@@ -5892,25 +6316,27 @@ var RedactoConsentInlineComponent = ({
5892
6316
  controller.abort();
5893
6317
  };
5894
6318
  }, [ledgerBaseUrl, applicationId, accessToken, content, notice_uuid]);
5895
- const togglePurposeCollapse = (purposeUuid) => {
6319
+ const togglePurposeCollapse = (purposeUuid, productUuid) => {
6320
+ const key = productPurposeKey(productUuid, purposeUuid);
5896
6321
  setCollapsedPurposes((prev) => ({
5897
6322
  ...prev,
5898
- [purposeUuid]: !prev[purposeUuid]
6323
+ [key]: !prev[key]
5899
6324
  }));
5900
6325
  };
5901
- const handlePurposeCheckboxChange = (purposeUuid) => {
6326
+ const handlePurposeCheckboxChange = (purposeUuid, productUuid) => {
5902
6327
  if (content) {
5903
6328
  const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
5904
6329
  if (purpose) {
5905
- const newPurposeState = !selectedPurposes[purposeUuid];
6330
+ const key = productPurposeKey(productUuid, purposeUuid);
6331
+ const newPurposeState = !selectedPurposes[key];
5906
6332
  setSelectedPurposes((prev) => ({
5907
6333
  ...prev,
5908
- [purposeUuid]: newPurposeState
6334
+ [key]: newPurposeState
5909
6335
  }));
5910
6336
  setSelectedDataElements((prev) => {
5911
6337
  const updated = { ...prev };
5912
6338
  purpose.data_elements.forEach((el) => {
5913
- const combinedId = `${purposeUuid}-${el.uuid}`;
6339
+ const combinedId = `${key}-${el.uuid}`;
5914
6340
  updated[combinedId] = newPurposeState;
5915
6341
  });
5916
6342
  return updated;
@@ -5918,7 +6344,7 @@ var RedactoConsentInlineComponent = ({
5918
6344
  }
5919
6345
  }
5920
6346
  };
5921
- const handleDataElementCheckboxChange = (elementUuid, purposeUuid) => {
6347
+ const handleDataElementCheckboxChange = (elementUuid, purposeUuid, productUuid) => {
5922
6348
  if (!content) return;
5923
6349
  const purpose = content.purposes.find((p) => p.uuid === purposeUuid);
5924
6350
  if (!purpose) return;
@@ -5926,7 +6352,8 @@ var RedactoConsentInlineComponent = ({
5926
6352
  (el) => el.uuid === elementUuid
5927
6353
  );
5928
6354
  if (!dataElement) return;
5929
- const combinedId = `${purposeUuid}-${elementUuid}`;
6355
+ const key = productPurposeKey(productUuid, purposeUuid);
6356
+ const combinedId = `${key}-${elementUuid}`;
5930
6357
  const currentState = selectedDataElements[combinedId] || false;
5931
6358
  const newElementState = !currentState;
5932
6359
  setSelectedDataElements((prev) => {
@@ -5940,16 +6367,16 @@ var RedactoConsentInlineComponent = ({
5940
6367
  let shouldCheckPurpose;
5941
6368
  if (requiredElements.length > 0) {
5942
6369
  shouldCheckPurpose = requiredElements.every(
5943
- (el) => newState[`${purposeUuid}-${el.uuid}`]
6370
+ (el) => newState[`${key}-${el.uuid}`]
5944
6371
  );
5945
6372
  } else {
5946
6373
  shouldCheckPurpose = purpose.data_elements.some(
5947
- (el) => newState[`${purposeUuid}-${el.uuid}`]
6374
+ (el) => newState[`${key}-${el.uuid}`]
5948
6375
  );
5949
6376
  }
5950
6377
  setSelectedPurposes((prevPurposes) => ({
5951
6378
  ...prevPurposes,
5952
- [purposeUuid]: shouldCheckPurpose
6379
+ [key]: shouldCheckPurpose
5953
6380
  }));
5954
6381
  return newState;
5955
6382
  });
@@ -5970,20 +6397,36 @@ var RedactoConsentInlineComponent = ({
5970
6397
  baseUrl,
5971
6398
  ledgerBaseUrl,
5972
6399
  noticeUuid: content.notice_uuid,
5973
- purposes: content.purposes.map((purpose) => {
6400
+ // One entry per (product, purpose) once a notice covers several, so a
6401
+ // purpose can be accepted for one product and declined for another.
6402
+ // Collapses to content.purposes below two products, leaving that
6403
+ // payload byte-identical to before.
6404
+ purposes: (isMultiProduct(content.products) ? expandPurposesByProduct(
6405
+ content.products,
6406
+ content.purposes,
6407
+ content.product_purpose_order
6408
+ ).map((row) => ({
6409
+ purpose: row.purpose,
6410
+ productUuid: row.product.uuid
6411
+ })) : content.purposes.map((purpose) => ({
6412
+ purpose,
6413
+ productUuid: void 0
6414
+ }))).map(({ purpose, productUuid }) => {
6415
+ const key = productPurposeKey(productUuid, purpose.uuid);
5974
6416
  const hasSelectedRequiredElement = purpose.data_elements.some(
5975
- (element) => element.required && (selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false)
6417
+ (element) => element.required && (selectedDataElements[`${key}-${element.uuid}`] || false)
5976
6418
  );
5977
6419
  const hasSelectedDataElement = purpose.data_elements.some(
5978
- (element) => selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false
6420
+ (element) => selectedDataElements[`${key}-${element.uuid}`] || false
5979
6421
  );
5980
- const purposeSelected = selectedPurposes[purpose.uuid] || hasSelectedRequiredElement || hasSelectedDataElement;
6422
+ const purposeSelected = selectedPurposes[key] || hasSelectedRequiredElement || hasSelectedDataElement;
5981
6423
  return {
5982
6424
  ...purpose,
6425
+ ...productUuid ? { product_uuid: productUuid } : {},
5983
6426
  selected: purposeSelected,
5984
6427
  data_elements: purpose.data_elements.map((element) => ({
5985
6428
  ...element,
5986
- selected: selectedDataElements[`${purpose.uuid}-${element.uuid}`] || false
6429
+ selected: selectedDataElements[`${key}-${element.uuid}`] || false
5987
6430
  }))
5988
6431
  };
5989
6432
  }),
@@ -6084,133 +6527,99 @@ var RedactoConsentInlineComponent = ({
6084
6527
  children: errorMessage
6085
6528
  }
6086
6529
  ),
6087
- (_a = content == null ? void 0 : content.purposes) == null ? void 0 : _a.map((purpose) => /* @__PURE__ */ jsxs3("div", { children: [
6088
- /* @__PURE__ */ jsxs3("div", { style: styles2.optionItem, children: [
6089
- /* @__PURE__ */ jsxs3(
6090
- "div",
6091
- {
6092
- style: styles2.optionLeft,
6093
- onClick: () => togglePurposeCollapse(purpose.uuid),
6094
- role: "button",
6095
- tabIndex: 0,
6096
- "aria-expanded": !collapsedPurposes[purpose.uuid],
6097
- "aria-controls": `purpose-${purpose.uuid}`,
6098
- "aria-label": `${collapsedPurposes[purpose.uuid] ? "Expand" : "Collapse"} ${getTranslatedText(
6099
- `purposes.name`,
6100
- purpose.name,
6101
- purpose.uuid
6102
- )} details`,
6103
- onKeyDown: (e) => {
6104
- if (e.key === "Enter" || e.key === " ") {
6105
- e.preventDefault();
6106
- togglePurposeCollapse(purpose.uuid);
6107
- }
6108
- },
6109
- children: [
6110
- /* @__PURE__ */ jsx4(
6111
- "div",
6112
- {
6113
- style: {
6114
- display: "flex",
6115
- alignItems: "center",
6116
- justifyContent: "center",
6117
- marginLeft: "5px"
6118
- },
6119
- children: /* @__PURE__ */ jsx4(
6120
- "svg",
6121
- {
6122
- width: "7",
6123
- height: "12",
6124
- viewBox: "0 0 7 12",
6125
- fill: "none",
6126
- xmlns: "http://www.w3.org/2000/svg",
6127
- stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
6128
- strokeWidth: "2",
6129
- strokeLinecap: "round",
6130
- strokeLinejoin: "round",
6131
- style: {
6132
- transform: !collapsedPurposes[purpose.uuid] ? "rotate(90deg)" : "rotate(0deg)",
6133
- transition: "transform 0.3s ease"
6134
- },
6135
- "aria-hidden": "true",
6136
- children: /* @__PURE__ */ jsx4("path", { d: "M1 1L6 6L1 11" })
6137
- }
6138
- )
6139
- }
6140
- ),
6141
- /* @__PURE__ */ jsxs3("div", { style: styles2.optionTextContainer, children: [
6142
- /* @__PURE__ */ jsxs3(
6143
- "h2",
6144
- {
6145
- style: {
6146
- ...styles2.optionTitle,
6147
- ...responsiveStyles.optionTitle,
6148
- ...headingStyle
6149
- },
6150
- children: [
6151
- getTranslatedText(
6152
- `purposes.name`,
6153
- purpose.name,
6154
- purpose.uuid
6155
- ),
6156
- hasRequiredDataElement(purpose) && /* @__PURE__ */ jsx4(RequiredMarker, {})
6157
- ]
6158
- }
6159
- ),
6160
- /* @__PURE__ */ jsx4(
6161
- "h3",
6162
- {
6163
- style: {
6164
- ...styles2.optionDescription,
6165
- ...responsiveStyles.optionDescription,
6166
- ...textStyle
6167
- },
6168
- children: getTranslatedText(
6169
- `purposes.description`,
6170
- purpose.description,
6171
- purpose.uuid
6172
- )
6173
- }
6174
- )
6175
- ] })
6176
- ]
6177
- }
6178
- ),
6179
- /* @__PURE__ */ jsx4(
6180
- "input",
6181
- {
6182
- type: "checkbox",
6183
- checked: selectedPurposes[purpose.uuid] || false,
6184
- onChange: () => handlePurposeCheckboxChange(purpose.uuid),
6185
- "aria-label": `Select all data elements for ${getTranslatedText(
6186
- `purposes.name`,
6187
- purpose.name,
6188
- purpose.uuid
6189
- )}`
6190
- }
6191
- )
6192
- ] }),
6193
- !collapsedPurposes[purpose.uuid] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ jsx4(
6194
- "div",
6195
- {
6196
- id: `purpose-${purpose.uuid}`,
6197
- style: styles2.dataElementsContainer,
6198
- children: purpose.data_elements.map((dataElement) => /* @__PURE__ */ jsxs3("div", { style: styles2.dataElementItem, children: [
6530
+ (() => {
6531
+ var _a, _b;
6532
+ const renderPurpose = (purpose, productUuid) => {
6533
+ const stateKey = productPurposeKey(productUuid, purpose.uuid);
6534
+ return /* @__PURE__ */ jsxs3("div", { children: [
6535
+ /* @__PURE__ */ jsxs3("div", { style: styles2.optionItem, children: [
6199
6536
  /* @__PURE__ */ jsxs3(
6200
- "span",
6537
+ "div",
6201
6538
  {
6202
- style: {
6203
- ...styles2.dataElementText,
6204
- ...responsiveStyles.dataElementText,
6205
- ...textStyle
6539
+ style: styles2.optionLeft,
6540
+ onClick: () => togglePurposeCollapse(purpose.uuid, productUuid),
6541
+ role: "button",
6542
+ tabIndex: 0,
6543
+ "aria-expanded": !collapsedPurposes[stateKey],
6544
+ "aria-controls": `purpose-${stateKey}`,
6545
+ "aria-label": `${collapsedPurposes[stateKey] ? "Expand" : "Collapse"} ${getTranslatedText(
6546
+ `purposes.name`,
6547
+ purpose.name,
6548
+ purpose.uuid
6549
+ )} details`,
6550
+ onKeyDown: (e) => {
6551
+ if (e.key === "Enter" || e.key === " ") {
6552
+ e.preventDefault();
6553
+ togglePurposeCollapse(purpose.uuid, productUuid);
6554
+ }
6206
6555
  },
6207
6556
  children: [
6208
- getTranslatedText(
6209
- `data_elements.name`,
6210
- dataElement.name,
6211
- dataElement.uuid
6557
+ /* @__PURE__ */ jsx4(
6558
+ "div",
6559
+ {
6560
+ style: {
6561
+ display: "flex",
6562
+ alignItems: "center",
6563
+ justifyContent: "center",
6564
+ marginLeft: "5px"
6565
+ },
6566
+ children: /* @__PURE__ */ jsx4(
6567
+ "svg",
6568
+ {
6569
+ width: "7",
6570
+ height: "12",
6571
+ viewBox: "0 0 7 12",
6572
+ fill: "none",
6573
+ xmlns: "http://www.w3.org/2000/svg",
6574
+ stroke: (settings == null ? void 0 : settings.headingColor) || "#323B4B",
6575
+ strokeWidth: "2",
6576
+ strokeLinecap: "round",
6577
+ strokeLinejoin: "round",
6578
+ style: {
6579
+ transform: !collapsedPurposes[stateKey] ? "rotate(90deg)" : "rotate(0deg)",
6580
+ transition: "transform 0.3s ease"
6581
+ },
6582
+ "aria-hidden": "true",
6583
+ children: /* @__PURE__ */ jsx4("path", { d: "M1 1L6 6L1 11" })
6584
+ }
6585
+ )
6586
+ }
6212
6587
  ),
6213
- dataElement.required && /* @__PURE__ */ jsx4(RequiredMarker, {})
6588
+ /* @__PURE__ */ jsxs3("div", { style: styles2.optionTextContainer, children: [
6589
+ /* @__PURE__ */ jsxs3(
6590
+ "h2",
6591
+ {
6592
+ style: {
6593
+ ...styles2.optionTitle,
6594
+ ...responsiveStyles.optionTitle,
6595
+ ...headingStyle
6596
+ },
6597
+ children: [
6598
+ getTranslatedText(
6599
+ `purposes.name`,
6600
+ purpose.name,
6601
+ purpose.uuid
6602
+ ),
6603
+ hasRequiredDataElement(purpose) && /* @__PURE__ */ jsx4(RequiredMarker, {})
6604
+ ]
6605
+ }
6606
+ ),
6607
+ /* @__PURE__ */ jsx4(
6608
+ "h3",
6609
+ {
6610
+ style: {
6611
+ ...styles2.optionDescription,
6612
+ ...responsiveStyles.optionDescription,
6613
+ ...textStyle
6614
+ },
6615
+ children: getTranslatedText(
6616
+ `purposes.description`,
6617
+ purpose.description,
6618
+ purpose.uuid
6619
+ )
6620
+ }
6621
+ )
6622
+ ] })
6214
6623
  ]
6215
6624
  }
6216
6625
  ),
@@ -6218,22 +6627,87 @@ var RedactoConsentInlineComponent = ({
6218
6627
  "input",
6219
6628
  {
6220
6629
  type: "checkbox",
6221
- checked: selectedDataElements[`${purpose.uuid}-${dataElement.uuid}`] || false,
6222
- onChange: () => handleDataElementCheckboxChange(
6223
- dataElement.uuid,
6630
+ checked: selectedPurposes[stateKey] || false,
6631
+ onChange: () => handlePurposeCheckboxChange(purpose.uuid, productUuid),
6632
+ "aria-label": `Select all data elements for ${getTranslatedText(
6633
+ `purposes.name`,
6634
+ purpose.name,
6224
6635
  purpose.uuid
6225
- ),
6226
- "aria-label": `Select ${getTranslatedText(
6227
- `data_elements.name`,
6228
- dataElement.name,
6229
- dataElement.uuid
6230
- )}${dataElement.required ? " (required)" : ""}`
6636
+ )}`
6231
6637
  }
6232
6638
  )
6233
- ] }, dataElement.uuid))
6234
- }
6235
- )
6236
- ] }, purpose.uuid))
6639
+ ] }),
6640
+ !collapsedPurposes[stateKey] && purpose.data_elements && purpose.data_elements.length > 0 && /* @__PURE__ */ jsx4(
6641
+ "div",
6642
+ {
6643
+ id: `purpose-${stateKey}`,
6644
+ style: styles2.dataElementsContainer,
6645
+ children: purpose.data_elements.map((dataElement) => /* @__PURE__ */ jsxs3(
6646
+ "div",
6647
+ {
6648
+ style: styles2.dataElementItem,
6649
+ children: [
6650
+ /* @__PURE__ */ jsxs3(
6651
+ "span",
6652
+ {
6653
+ style: {
6654
+ ...styles2.dataElementText,
6655
+ ...responsiveStyles.dataElementText,
6656
+ ...textStyle
6657
+ },
6658
+ children: [
6659
+ getTranslatedText(
6660
+ `data_elements.name`,
6661
+ dataElement.name,
6662
+ dataElement.uuid
6663
+ ),
6664
+ dataElement.required && /* @__PURE__ */ jsx4(RequiredMarker, {})
6665
+ ]
6666
+ }
6667
+ ),
6668
+ /* @__PURE__ */ jsx4(
6669
+ "input",
6670
+ {
6671
+ type: "checkbox",
6672
+ checked: selectedDataElements[`${stateKey}-${dataElement.uuid}`] || false,
6673
+ onChange: () => handleDataElementCheckboxChange(
6674
+ dataElement.uuid,
6675
+ purpose.uuid,
6676
+ productUuid
6677
+ ),
6678
+ "aria-label": `Select ${getTranslatedText(
6679
+ `data_elements.name`,
6680
+ dataElement.name,
6681
+ dataElement.uuid
6682
+ )}${dataElement.required ? " (required)" : ""}`
6683
+ }
6684
+ )
6685
+ ]
6686
+ },
6687
+ dataElement.uuid
6688
+ ))
6689
+ }
6690
+ )
6691
+ ] }, stateKey);
6692
+ };
6693
+ if (!isMultiProduct(content == null ? void 0 : content.products)) {
6694
+ return (_a = content == null ? void 0 : content.purposes) == null ? void 0 : _a.map((purpose) => renderPurpose(purpose));
6695
+ }
6696
+ return groupPurposesByProduct(
6697
+ content == null ? void 0 : content.products,
6698
+ (_b = content == null ? void 0 : content.purposes) != null ? _b : [],
6699
+ content == null ? void 0 : content.product_purpose_order
6700
+ ).map((group) => /* @__PURE__ */ jsxs3("div", { children: [
6701
+ /* @__PURE__ */ jsx4("div", { style: { ...styles2.optionTitle, margin: "0 0 8px" }, children: getTranslatedText(
6702
+ "products.name",
6703
+ group.product.name,
6704
+ group.product.uuid
6705
+ ) }),
6706
+ group.purposes.map(
6707
+ (purpose) => renderPurpose(purpose, group.product.uuid)
6708
+ )
6709
+ ] }, group.product.uuid));
6710
+ })()
6237
6711
  ] });
6238
6712
  };
6239
6713
  RedactoConsentInlineComponent.displayName = "RedactoConsentInline";
@@ -9375,10 +9849,14 @@ var RedactoNoticeAssisted = ({
9375
9849
  const handleConfirmStep = useCallback3(
9376
9850
  (mode = "selected") => {
9377
9851
  if (mode !== "selected" && content) {
9378
- const selection = buildSelectionForMode(content.purposes, mode, {
9379
- selectedPurposes,
9380
- selectedDataElements
9381
- });
9852
+ const selection = buildSelectionForMode(
9853
+ noticePurposeRows(void 0, content.purposes),
9854
+ mode,
9855
+ {
9856
+ selectedPurposes,
9857
+ selectedDataElements
9858
+ }
9859
+ );
9382
9860
  setSelectedPurposes(selection.selectedPurposes);
9383
9861
  setSelectedDataElements(selection.selectedDataElements);
9384
9862
  }
@@ -9579,10 +10057,14 @@ var RedactoNoticeAssisted = ({
9579
10057
  return;
9580
10058
  }
9581
10059
  }
9582
- const selection = buildSelectionForMode(content.purposes, acceptMode, {
9583
- selectedPurposes,
9584
- selectedDataElements
9585
- });
10060
+ const selection = buildSelectionForMode(
10061
+ noticePurposeRows(void 0, content.purposes),
10062
+ acceptMode,
10063
+ {
10064
+ selectedPurposes,
10065
+ selectedDataElements
10066
+ }
10067
+ );
9586
10068
  const submitPurposes = content.purposes.map((purpose) => ({
9587
10069
  ...purpose,
9588
10070
  selected: selection.selectedPurposes[purpose.uuid] || false,