@redacto.io/consent-sdk-react 5.2.0 → 6.0.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.
Files changed (84) hide show
  1. package/.turbo/turbo-build.log +24 -0
  2. package/CHANGELOG.md +12 -0
  3. package/dist/index.d.mts +24 -1
  4. package/dist/index.d.ts +24 -1
  5. package/dist/index.js +149 -206
  6. package/dist/index.mjs +148 -206
  7. package/dist/privacy-center.d.mts +178 -39
  8. package/dist/privacy-center.d.ts +178 -39
  9. package/dist/privacy-center.js +10534 -6795
  10. package/dist/privacy-center.mjs +7922 -4181
  11. package/package.json +1 -1
  12. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +16 -20
  13. package/src/RedactoNoticeConsent/api/index.ts +21 -103
  14. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +5 -4
  15. package/src/RedactoNoticeConsentInline/api/index.ts +64 -117
  16. package/src/RedactoPrivacyCenter/NotificationsPage.tsx +174 -0
  17. package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +3 -0
  18. package/src/RedactoPrivacyCenter/api/actions.ts +136 -8
  19. package/src/RedactoPrivacyCenter/api/client.ts +176 -15
  20. package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +722 -273
  21. package/src/RedactoPrivacyCenter/components/ConsentManagerCells.tsx +187 -0
  22. package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +65 -165
  23. package/src/RedactoPrivacyCenter/components/ConsentManagerExpandedRow.tsx +179 -0
  24. package/src/RedactoPrivacyCenter/components/ConsentManagerGroupedColumns.tsx +318 -0
  25. package/src/RedactoPrivacyCenter/components/ConsentManagerProductCards.tsx +333 -0
  26. package/src/RedactoPrivacyCenter/components/ConsentManagerPurposeCards.tsx +102 -0
  27. package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +6 -2
  28. package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +15 -0
  29. package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +8 -5
  30. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationBell.tsx +160 -0
  31. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationCTAButton.tsx +18 -0
  32. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdown.tsx +54 -0
  33. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdownItem.tsx +57 -0
  34. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationIcon.tsx +19 -0
  35. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationListPanel.tsx +92 -0
  36. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRefreshButton.tsx +20 -0
  37. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRow.tsx +58 -0
  38. package/src/RedactoPrivacyCenter/components/NotificationHub/StatCard.tsx +10 -0
  39. package/src/RedactoPrivacyCenter/components/NotificationHub/useNotificationActions.ts +112 -0
  40. package/src/RedactoPrivacyCenter/components/NotificationHub/utils.ts +27 -0
  41. package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +105 -55
  42. package/src/RedactoPrivacyCenter/components/types.ts +2 -5
  43. package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +2 -2
  44. package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +21 -1
  45. package/src/RedactoPrivacyCenter/lib/api.ts +147 -28
  46. package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +4 -2
  47. package/src/RedactoPrivacyCenter/lib/constants.ts +78 -1
  48. package/src/RedactoPrivacyCenter/lib/types.ts +205 -36
  49. package/src/RedactoPrivacyCenter/lib/utils.ts +3 -0
  50. package/src/RedactoPrivacyCenter/locales/as/translation.json +12 -1
  51. package/src/RedactoPrivacyCenter/locales/bho/translation.json +12 -1
  52. package/src/RedactoPrivacyCenter/locales/bn/translation.json +12 -1
  53. package/src/RedactoPrivacyCenter/locales/brx/translation.json +12 -1
  54. package/src/RedactoPrivacyCenter/locales/doi/translation.json +12 -1
  55. package/src/RedactoPrivacyCenter/locales/en/translation.json +42 -1
  56. package/src/RedactoPrivacyCenter/locales/gom/translation.json +12 -1
  57. package/src/RedactoPrivacyCenter/locales/gu/translation.json +12 -1
  58. package/src/RedactoPrivacyCenter/locales/hi/translation.json +15 -1
  59. package/src/RedactoPrivacyCenter/locales/kn/translation.json +12 -1
  60. package/src/RedactoPrivacyCenter/locales/ks/translation.json +12 -1
  61. package/src/RedactoPrivacyCenter/locales/mai/translation.json +12 -1
  62. package/src/RedactoPrivacyCenter/locales/ml/translation.json +12 -1
  63. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +12 -1
  64. package/src/RedactoPrivacyCenter/locales/mr/translation.json +12 -1
  65. package/src/RedactoPrivacyCenter/locales/ne/translation.json +12 -1
  66. package/src/RedactoPrivacyCenter/locales/or/translation.json +12 -1
  67. package/src/RedactoPrivacyCenter/locales/pa/translation.json +12 -1
  68. package/src/RedactoPrivacyCenter/locales/sa/translation.json +12 -1
  69. package/src/RedactoPrivacyCenter/locales/sat/translation.json +12 -1
  70. package/src/RedactoPrivacyCenter/locales/sd/translation.json +12 -1
  71. package/src/RedactoPrivacyCenter/locales/ta/translation.json +12 -1
  72. package/src/RedactoPrivacyCenter/locales/te/translation.json +12 -1
  73. package/src/RedactoPrivacyCenter/locales/ur/translation.json +12 -1
  74. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +1027 -0
  75. package/src/RedactoPrivacyCenter/styles/variables.css +5 -0
  76. package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +36 -16
  77. package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +4 -12
  78. package/src/RedactoPrivacyCenter/ui/PCInput.tsx +1 -1
  79. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +23 -1
  80. package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +51 -5
  81. package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +2 -4
  82. package/src/index.ts +5 -0
  83. package/src/privacy-center.ts +8 -3
  84. package/src/shared/api-errors.ts +171 -0
@@ -0,0 +1,58 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import type { NotificationRowProps } from '../../lib/types';
3
+ import { NotificationIcon } from './NotificationIcon';
4
+ import { NotificationCTAButton } from './NotificationCTAButton';
5
+ import { formatTimeAgo } from './utils';
6
+
7
+ export const NotificationRow = ({ notification, onClick, onCTAClick }: NotificationRowProps) => {
8
+ const { t } = useTranslation();
9
+ const statusLabel = notification.status_label_display || notification.status_label;
10
+
11
+ return (
12
+ <div
13
+ onClick={onClick}
14
+ onKeyDown={(e) => {
15
+ if (e.currentTarget !== e.target) {
16
+ return;
17
+ }
18
+ if (e.key === 'Enter' || e.key === ' ') {
19
+ e.preventDefault();
20
+ onClick();
21
+ }
22
+ }}
23
+ role="button"
24
+ tabIndex={0}
25
+ className="pc-notification-row"
26
+ data-read={notification.is_read ? 'true' : 'false'}
27
+ >
28
+ <NotificationIcon notificationType={notification.notification_type} size="lg" />
29
+
30
+ <div className="pc-notification-row-content">
31
+ <div className="pc-notification-row-title-line">
32
+ <span className="pc-notification-row-title">{notification.title}</span>
33
+ {!notification.is_read && <span className="pc-notification-unread-dot-lg" />}
34
+ {statusLabel && (
35
+ <span className="pc-notification-status-badge">{statusLabel}</span>
36
+ )}
37
+ </div>
38
+
39
+ <p className="pc-notification-row-description">{notification.description}</p>
40
+
41
+ {notification.ctas && notification.ctas.length > 0 && (
42
+ <div className="pc-notification-row-ctas">
43
+ {notification.ctas.map((cta) => (
44
+ <NotificationCTAButton
45
+ key={`${cta.action_type}-${cta.label}`}
46
+ cta={cta}
47
+ size="lg"
48
+ onClick={(e) => { e.stopPropagation(); onCTAClick(cta); }}
49
+ />
50
+ ))}
51
+ </div>
52
+ )}
53
+ </div>
54
+
55
+ <span className="pc-notification-row-time">{formatTimeAgo(notification.created_at, t)}</span>
56
+ </div>
57
+ );
58
+ }
@@ -0,0 +1,10 @@
1
+ import type { StatCardProps } from '../../lib/types';
2
+
3
+ export const StatCard = ({ value, label }: StatCardProps) => {
4
+ return (
5
+ <div className="pc-notification-stat-card">
6
+ <span className="pc-notification-stat-value">{value}</span>
7
+ <span className="pc-notification-stat-label">{label}</span>
8
+ </div>
9
+ );
10
+ }
@@ -0,0 +1,112 @@
1
+ import { useCallback, useMemo } from 'react';
2
+ import { useInternalNavigation } from '../../context/NavigationContext';
3
+ import { useOrganization } from '../../context/OrganizationContext';
4
+ import { useAuth } from '../../context/AuthContext';
5
+ import { useBaseUrl } from '../../context/BaseUrlContext';
6
+ import { markNotificationRead, markAllRead, acknowledgeNotification, fetchCaseHistory } from '../../lib/api';
7
+ import type { AuthContext } from '../../lib/api';
8
+ import { useCurrentLanguage } from '../../lib/useCurrentLanguage';
9
+ import type { Notification, NotificationCTA } from '../../lib/types';
10
+ import { CASE_DETAILS_PREFIX } from '../../lib/constants';
11
+
12
+ export const useNotificationActions = () => {
13
+ const { navigateTo, setSelectedCase } = useInternalNavigation();
14
+ const { organization } = useOrganization();
15
+ const { accessToken, refreshToken, onError, updateTokens } = useAuth();
16
+ const { consentClient } = useBaseUrl();
17
+ const { langParam } = useCurrentLanguage();
18
+
19
+ const organizationId = organization?.uuid;
20
+ const workspaceId = organization?.workspaces[0]?.uuid;
21
+
22
+ const authContext: AuthContext = useMemo(() => ({
23
+ accessToken, refreshToken, onError, updateTokens,
24
+ }), [accessToken, refreshToken, onError, updateTokens]);
25
+
26
+ const hasAcknowledgeCTA = useCallback((notification: Notification) => {
27
+ return notification.ctas?.some((cta) => cta.action_type === 'acknowledge') ?? false;
28
+ }, []);
29
+
30
+ const handleMarkRead = useCallback(async (notificationUuid: string): Promise<boolean> => {
31
+ if (!organizationId || !workspaceId) {
32
+ return false;
33
+ }
34
+ const result = await markNotificationRead(organizationId, workspaceId, notificationUuid, authContext, consentClient);
35
+ return result !== null;
36
+ }, [organizationId, workspaceId, authContext, consentClient]);
37
+
38
+ const handleMarkAllRead = useCallback(async (): Promise<boolean> => {
39
+ if (!organizationId || !workspaceId) {
40
+ return false;
41
+ }
42
+ const result = await markAllRead(organizationId, workspaceId, authContext, consentClient);
43
+ return result !== null;
44
+ }, [organizationId, workspaceId, authContext, consentClient]);
45
+
46
+ const handleAcknowledge = useCallback(async (notificationUuid: string): Promise<boolean> => {
47
+ if (!organizationId || !workspaceId) {
48
+ return false;
49
+ }
50
+ const result = await acknowledgeNotification(organizationId, workspaceId, notificationUuid, authContext, consentClient);
51
+ return result !== null;
52
+ }, [organizationId, workspaceId, authContext, consentClient]);
53
+
54
+ const handleCTAClick = useCallback(async (notification: Notification, cta: NotificationCTA) => {
55
+ if (cta.action_type === 'acknowledge') {
56
+ const ok = await handleAcknowledge(notification.uuid);
57
+ if (!ok) {
58
+ return null;
59
+ }
60
+ return 'acknowledged' as const;
61
+ }
62
+
63
+ if (cta.action_type !== 'link' || !cta.url) {
64
+ return null;
65
+ }
66
+
67
+ if (!notification.is_read) {
68
+ await handleMarkRead(notification.uuid);
69
+ }
70
+
71
+
72
+ const caseUuid = cta.url.startsWith(CASE_DETAILS_PREFIX)
73
+ ? cta.url.slice(CASE_DETAILS_PREFIX.length)
74
+ : '';
75
+ if (caseUuid && organizationId && workspaceId) {
76
+ const contact = typeof window !== 'undefined' ? sessionStorage.getItem('email') || '' : '';
77
+ const caseHistory = await fetchCaseHistory(organizationId, workspaceId, contact, undefined, undefined, authContext, consentClient, langParam);
78
+ const fullCase = caseHistory?.data?.find((c) => c.uuid === caseUuid);
79
+ if (fullCase) {
80
+ setSelectedCase(fullCase);
81
+ navigateTo('case-details');
82
+ return 'navigated' as const;
83
+ }
84
+ return 'not_found' as const;
85
+ }
86
+
87
+ let parsed: URL;
88
+ try {
89
+ parsed = new URL(cta.url);
90
+ } catch {
91
+ return null;
92
+ }
93
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
94
+ return null;
95
+ }
96
+ window.open(parsed.href, '_blank', 'noopener,noreferrer');
97
+ return 'opened' as const;
98
+ }, [handleAcknowledge, handleMarkRead, navigateTo, setSelectedCase, organizationId, workspaceId, authContext, consentClient, langParam]);
99
+
100
+ return {
101
+ organizationId,
102
+ workspaceId,
103
+ authContext,
104
+ consentClient,
105
+ navigateTo,
106
+ hasAcknowledgeCTA,
107
+ handleMarkRead,
108
+ handleMarkAllRead,
109
+ handleAcknowledge,
110
+ handleCTAClick,
111
+ };
112
+ }
@@ -0,0 +1,27 @@
1
+ import type { TFunction } from 'i18next';
2
+
3
+ export const formatTimeAgo = (dateString: string, t: TFunction): string => {
4
+ const now = new Date();
5
+ const date = new Date(dateString);
6
+ if (Number.isNaN(date.getTime())) {
7
+ return t('justNow')
8
+ };
9
+ const diffMs = now.getTime() - date.getTime();
10
+ const diffMin = Math.floor(diffMs / 60_000);
11
+ if (diffMin < 1) {
12
+ return t('justNow')
13
+ };
14
+ if (diffMin < 60) {
15
+ return t('minutesAgo', { count: diffMin })
16
+ };
17
+ const diffHrs = Math.floor(diffMin / 60);
18
+ if (diffHrs < 24) {
19
+ return t('hoursAgo', { count: diffHrs })
20
+ };
21
+ const diffDays = Math.floor(diffHrs / 24);
22
+ if (diffDays < 7) {
23
+ return t('daysAgo', { count: diffDays })
24
+ };
25
+ const diffWeeks = Math.floor(diffDays / 7);
26
+ return t('weeksAgo', { count: diffWeeks });
27
+ };
@@ -12,8 +12,9 @@ import { usePageChrome } from '../context/PageChromeContext';
12
12
  import i18n from '../lib/i18n';
13
13
  import { pcStyles } from '../styles/pcStyles';
14
14
  import type { PrivacyCenterLayoutProps } from './types';
15
-
16
- const getVar = (varName: string): string => `var(${varName})`;
15
+ import { NotificationBell } from './NotificationHub/NotificationBell';
16
+ import { getVar } from '../lib/utils';
17
+ import { Globe2 } from 'lucide-react';
17
18
 
18
19
  export default function PrivacyCenterLayout({
19
20
  children,
@@ -23,7 +24,7 @@ export default function PrivacyCenterLayout({
23
24
  }: PrivacyCenterLayoutProps) {
24
25
  const { t } = useTranslation();
25
26
  const { currentPage } = useInternalNavigation();
26
- const { pageChromeOverride } = usePageChrome();
27
+ const { pageChromeOverride, headerStatus } = usePageChrome();
27
28
  const [locale, setLocale] = useState(i18n.language);
28
29
  const pageChrome = pageChromeOverride ?? PAGE_CHROME_BY_PAGE[currentPage];
29
30
 
@@ -59,12 +60,11 @@ export default function PrivacyCenterLayout({
59
60
  flex: 1,
60
61
  minWidth: 0,
61
62
  backgroundColor: getVar('--pc-bg-shell'),
63
+ minHeight: 0,
62
64
  }} className="pc-content-wrapper">
63
65
  {/* Top Navbar with Theme Toggle */}
64
66
  <nav style={{
65
- position: 'fixed',
66
- top: 0,
67
- zIndex: 50,
67
+ position: 'relative',
68
68
  ...pcStyles.flex,
69
69
  ...pcStyles.itemsCenter,
70
70
  ...pcStyles.justifyBetween,
@@ -74,8 +74,8 @@ export default function PrivacyCenterLayout({
74
74
  paddingRight: 'var(--pc-navbar-padding-right, 0.75rem)',
75
75
  paddingTop: 'var(--pc-navbar-padding-top, 0.625rem)',
76
76
  paddingBottom: 'var(--pc-navbar-padding-bottom, 0.625rem)',
77
- transition: 'background-color 0.3s ease, border-color 0.3s ease, width 0.3s ease, left 0.3s ease',
78
- willChange: 'background-color, border-color, width, left',
77
+ transition: 'background-color 0.3s ease, border-color 0.3s ease',
78
+ willChange: 'background-color, border-color',
79
79
  }} className="pc-navbar">
80
80
  <div
81
81
  className="pc-navbar-page-meta"
@@ -85,32 +85,76 @@ export default function PrivacyCenterLayout({
85
85
  justifyContent: 'center',
86
86
  minWidth: 0,
87
87
  flex: 1,
88
- gap: '0.125rem',
88
+ gap: '0.1875rem',
89
89
  }}
90
90
  >
91
- <p
92
- className="pc-navbar-title"
93
- style={{
94
- margin: 0,
95
- color: getVar('--pc-text-primary'),
96
- fontSize: '1rem',
97
- fontWeight: 600,
98
- lineHeight: 1.3,
99
- whiteSpace: 'nowrap',
100
- overflow: 'hidden',
101
- textOverflow: 'ellipsis',
102
- }}
103
- >
104
- {t(pageChrome.titleKey)}
105
- </p>
91
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.625rem', minWidth: 0 }}>
92
+ <p
93
+ className="pc-navbar-title"
94
+ style={{
95
+ margin: 0,
96
+ color: getVar('--pc-text-primary'),
97
+ fontSize: '1.0625rem',
98
+ fontWeight: 700,
99
+ lineHeight: 1.2,
100
+ whiteSpace: 'nowrap',
101
+ overflow: 'hidden',
102
+ textOverflow: 'ellipsis',
103
+ }}
104
+ >
105
+ {t(pageChrome.titleKey)}
106
+ </p>
107
+ {headerStatus && (
108
+ <span
109
+ style={{
110
+ display: 'inline-flex',
111
+ alignItems: 'center',
112
+ flexShrink: 0,
113
+ borderRadius: '9999px',
114
+ gap: '0.375rem',
115
+ padding: '0.25rem 0.75rem',
116
+ fontSize: '0.625rem',
117
+ fontWeight: 600,
118
+ lineHeight: 1.2,
119
+ border: `1px solid ${
120
+ headerStatus.variant === 'success'
121
+ ? 'rgba(16, 185, 129, 0.35)'
122
+ : 'rgba(245, 158, 11, 0.35)'
123
+ }`,
124
+ backgroundColor:
125
+ headerStatus.variant === 'success'
126
+ ? 'rgba(16, 185, 129, 0.1)'
127
+ : '#fff7ed',
128
+ color:
129
+ headerStatus.variant === 'success'
130
+ ? getVar('--pc-success')
131
+ : '#d97706',
132
+ }}
133
+ >
134
+ <span
135
+ aria-hidden="true"
136
+ style={{
137
+ width: '0.4375rem',
138
+ height: '0.4375rem',
139
+ borderRadius: '9999px',
140
+ backgroundColor:
141
+ headerStatus.variant === 'success'
142
+ ? getVar('--pc-success')
143
+ : '#d97706',
144
+ }}
145
+ />
146
+ {headerStatus.label}
147
+ </span>
148
+ )}
149
+ </div>
106
150
  {pageChrome.descriptionKey && (
107
151
  <p
108
152
  className="pc-navbar-description"
109
153
  style={{
110
154
  margin: 0,
111
155
  color: getVar('--pc-text-secondary'),
112
- fontSize: '0.8125rem',
113
- lineHeight: 1.35,
156
+ fontSize: '0.75rem',
157
+ lineHeight: 1.2,
114
158
  whiteSpace: 'nowrap',
115
159
  overflow: 'hidden',
116
160
  textOverflow: 'ellipsis',
@@ -123,52 +167,57 @@ export default function PrivacyCenterLayout({
123
167
  <div style={{
124
168
  display: 'flex',
125
169
  alignItems: 'center',
126
- gap: '0.5rem',
170
+ gap: '0.375rem',
127
171
  flexShrink: 0,
128
172
  }} className="pc-navbar-actions">
129
173
  <PCSelect
174
+ className="pc-navbar-language-select"
130
175
  size="sm"
131
176
  searchable
177
+ prefix={<Globe2 size={12} strokeWidth={1.8} />}
132
178
  searchPlaceholder={t('searchPlaceholder')}
133
179
  noOptionsMessage={t('noOptionsFound')}
134
180
  style={{
135
181
  width: 'auto',
136
- minWidth: '92px',
137
- maxWidth: '136px',
182
+ minWidth: '120px',
183
+ maxWidth: 'min(65vw, 320px)',
138
184
  flexShrink: 0,
139
185
  }}
140
186
  options={languageOptions}
141
187
  value={languageOptions.find((opt) => opt.value === locale) || null}
142
188
  onChange={handleLanguageChange}
143
189
  />
144
- <ThemeToggle />
190
+ <NotificationBell />
191
+ <ThemeToggle className="pc-navbar-icon-btn" />
145
192
  </div>
146
193
  </nav>
147
194
  <div style={{
148
195
  ...pcStyles.flex,
149
196
  ...pcStyles.flexCol,
150
- flexGrow: 1,
197
+ flex: 1,
198
+ minHeight: 0,
151
199
  paddingLeft: 'var(--pc-content-padding-left, 0.75rem)',
152
200
  paddingRight: 'var(--pc-content-padding-right, 0.75rem)',
153
- paddingTop: 'var(--pc-content-padding-top, 4rem)',
201
+ paddingTop: 'var(--pc-content-padding-top, 0)',
154
202
  minWidth: 0,
155
203
  backgroundColor: getVar('--pc-bg-shell'),
156
204
  }} className="pc-content-container">
157
205
  <div style={{
158
- minHeight: '100vh',
206
+ display: 'flex',
207
+ flexDirection: 'column',
208
+ flex: 1,
209
+ minHeight: 0,
159
210
  width: '100%',
160
211
  }}>
161
212
  <div style={{
162
213
  ...pcStyles.flex,
163
214
  ...pcStyles.flexCol,
164
- height: '100%',
165
- minHeight: '100vh',
215
+ flex: 1,
216
+ minHeight: 0,
166
217
  ...pcStyles.justifyBetween,
167
- ...pcStyles.gapMd,
168
- paddingLeft: '0.5rem',
169
- paddingRight: '0.5rem',
170
- paddingTop: '1rem',
171
- paddingBottom: '1rem',
218
+ ...pcStyles.gapSm,
219
+ paddingTop: '0.625rem',
220
+ paddingBottom: '0.75rem',
172
221
  }}>
173
222
  <div className={contentClassName} style={{ width: '100%' }}>{children}</div>
174
223
  <div style={{
@@ -198,17 +247,14 @@ export default function PrivacyCenterLayout({
198
247
  .pc-navbar {
199
248
  --pc-navbar-padding-left: 0.75rem;
200
249
  --pc-navbar-padding-right: 0.75rem;
201
- --pc-navbar-padding-top: 0.625rem;
202
- --pc-navbar-padding-bottom: 0.625rem;
203
- left: var(--pc-sidebar-offset) !important;
204
- width: calc(100% - var(--pc-sidebar-offset)) !important;
205
- transition: left 0.3s ease, width 0.3s ease !important;
250
+ --pc-navbar-padding-top: 0.875rem;
251
+ --pc-navbar-padding-bottom: 0.75rem;
206
252
  }
207
253
 
208
254
  .pc-content-container {
209
255
  --pc-content-padding-left: 0.75rem;
210
256
  --pc-content-padding-right: 0.75rem;
211
- --pc-content-padding-top: 4rem;
257
+ --pc-content-padding-top: 0;
212
258
  }
213
259
 
214
260
  .pc-navbar-page-meta {
@@ -246,7 +292,7 @@ export default function PrivacyCenterLayout({
246
292
  .pc-navbar {
247
293
  --pc-navbar-padding-left: 3.75rem;
248
294
  --pc-navbar-padding-right: 0.75rem;
249
- --pc-navbar-padding-top: 0.625rem;
295
+ --pc-navbar-padding-top: 0.75rem;
250
296
  --pc-navbar-padding-bottom: 0.625rem;
251
297
  }
252
298
  .pc-navbar-description {
@@ -260,21 +306,21 @@ export default function PrivacyCenterLayout({
260
306
  gap: 0.375rem;
261
307
  }
262
308
  .pc-content-container {
263
- --pc-content-padding-top: 4rem;
309
+ --pc-content-padding-top: 0;
264
310
  }
265
311
  }
266
312
  @media (min-width: 768px) {
267
313
  .pc-navbar {
268
- --pc-navbar-padding-left: 1rem;
269
- --pc-navbar-padding-right: 1rem;
270
- --pc-navbar-padding-top: 0.75rem;
314
+ --pc-navbar-padding-left: 1.25rem;
315
+ --pc-navbar-padding-right: 1.25rem;
316
+ --pc-navbar-padding-top: 0.875rem;
271
317
  --pc-navbar-padding-bottom: 0.75rem;
272
318
  }
273
319
  .pc-navbar-description {
274
320
  display: block;
275
321
  }
276
322
  .pc-content-container {
277
- --pc-content-padding-top: 4.5rem;
323
+ --pc-content-padding-top: 0;
278
324
  }
279
325
  }
280
326
  @media (min-width: 768px) {
@@ -282,19 +328,23 @@ export default function PrivacyCenterLayout({
282
328
  font-size: 1.0625rem;
283
329
  }
284
330
  .pc-navbar-description {
285
- font-size: 0.8125rem;
331
+ font-size: 0.6875rem;
286
332
  }
287
333
  .pc-content-container {
288
334
  --pc-content-padding-left: 1.25rem;
289
335
  --pc-content-padding-right: 1.25rem;
290
- --pc-content-padding-top: 4.5rem;
336
+ --pc-content-padding-top: 0;
291
337
  }
292
338
  }
293
339
  @media (min-width: 1024px) {
340
+ .pc-navbar {
341
+ --pc-navbar-padding-left: 1.5rem;
342
+ --pc-navbar-padding-right: 1.5rem;
343
+ }
294
344
  .pc-content-container {
295
345
  --pc-content-padding-left: 1.5rem;
296
346
  --pc-content-padding-right: 1.5rem;
297
- --pc-content-padding-top: 4.75rem;
347
+ --pc-content-padding-top: 0;
298
348
  }
299
349
  }
300
350
  `}</style>
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
- import type { CaseRequest, UserConsent } from '../lib/types';
2
+ import type { CaseRequest, ConsentModalRecord } from '../lib/types';
3
3
 
4
4
  export type TableColumnAlign = 'left' | 'center' | 'right';
5
5
 
@@ -61,7 +61,7 @@ export type ConsentManagerProps = {
61
61
  export type ModifyConsentModalProps = {
62
62
  isOpen: boolean;
63
63
  onClose: () => void;
64
- selectedRecord: UserConsent | null;
64
+ selectedRecord: ConsentModalRecord | null;
65
65
  isLoading: boolean;
66
66
  onManageConsent: (
67
67
  purposeId: string,
@@ -71,6 +71,3 @@ export type ModifyConsentModalProps = {
71
71
  ) => void;
72
72
  };
73
73
 
74
- export type UseConsentManagerColumnsProps = {
75
- onOpenModal: (record: UserConsent) => void;
76
- };
@@ -1,13 +1,13 @@
1
1
  import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
2
2
  import type { CaseRequest } from '../lib/types';
3
3
 
4
- export type InternalPage = 'consent-manager' | 'form' | 'activity' | 'case-details';
4
+ export type InternalPage = 'consent-manager' | 'form' | 'activity' | 'case-details' | 'notifications';
5
5
 
6
6
  const NAV_STORAGE_KEY = 'pc_current_page';
7
7
  const CASE_UUID_STORAGE_KEY = 'pc_selected_case_uuid';
8
8
 
9
9
  const VALID_PAGES: ReadonlySet<string> = new Set<InternalPage>([
10
- 'consent-manager', 'form', 'activity', 'case-details',
10
+ 'consent-manager', 'form', 'activity', 'case-details', 'notifications',
11
11
  ]);
12
12
 
13
13
  function getStoredPage(fallback: InternalPage): InternalPage {
@@ -12,10 +12,17 @@ export type PageChromeOverride = {
12
12
  descriptionKey?: string;
13
13
  };
14
14
 
15
+ export type PageHeaderStatus = {
16
+ label: string;
17
+ variant: 'success' | 'warning';
18
+ };
19
+
15
20
  type PageChromeContextValue = {
16
21
  pageChromeOverride: PageChromeOverride | null;
17
22
  setPageChromeOverride: (override: PageChromeOverride | null) => void;
18
23
  clearPageChromeOverride: () => void;
24
+ headerStatus: PageHeaderStatus | null;
25
+ setHeaderStatus: (status: PageHeaderStatus | null) => void;
19
26
  };
20
27
 
21
28
  const PageChromeContext = createContext<PageChromeContextValue | null>(null);
@@ -23,6 +30,7 @@ const PageChromeContext = createContext<PageChromeContextValue | null>(null);
23
30
  export function PageChromeProvider({ children }: { children: ReactNode }) {
24
31
  const [pageChromeOverride, setPageChromeOverrideState] =
25
32
  useState<PageChromeOverride | null>(null);
33
+ const [headerStatus, setHeaderStatusState] = useState<PageHeaderStatus | null>(null);
26
34
 
27
35
  const setPageChromeOverride = useCallback(
28
36
  (override: PageChromeOverride | null) => {
@@ -35,13 +43,25 @@ export function PageChromeProvider({ children }: { children: ReactNode }) {
35
43
  setPageChromeOverrideState(null);
36
44
  }, []);
37
45
 
46
+ const setHeaderStatus = useCallback((status: PageHeaderStatus | null) => {
47
+ setHeaderStatusState(status);
48
+ }, []);
49
+
38
50
  const value = useMemo(
39
51
  () => ({
40
52
  pageChromeOverride,
41
53
  setPageChromeOverride,
42
54
  clearPageChromeOverride,
55
+ headerStatus,
56
+ setHeaderStatus,
43
57
  }),
44
- [pageChromeOverride, setPageChromeOverride, clearPageChromeOverride]
58
+ [
59
+ pageChromeOverride,
60
+ setPageChromeOverride,
61
+ clearPageChromeOverride,
62
+ headerStatus,
63
+ setHeaderStatus,
64
+ ]
45
65
  );
46
66
 
47
67
  return (