@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
@@ -4,6 +4,7 @@ import {
4
4
  fetchCaseHistoryServer,
5
5
  fetchProductsServer,
6
6
  fetchUserConsentsServer,
7
+ fetchGroupPurposesServer,
7
8
  getFormDataServer,
8
9
  manageConsentServer,
9
10
  getCaseMessagesServer,
@@ -11,6 +12,12 @@ import {
11
12
  uploadCaseDocumentServer,
12
13
  submitDocumentForRequestServer,
13
14
  getCaseDocumentServer,
15
+ getNotificationsServer,
16
+ getNotificationSummaryServer,
17
+ getUnreadCountServer,
18
+ markNotificationReadServer,
19
+ markAllNotificationsReadServer,
20
+ acknowledgeNotificationServer,
14
21
  } from '../api/actions';
15
22
  import type { BaseUrlContextValue } from '../context/BaseUrlContext';
16
23
  import type {
@@ -20,9 +27,15 @@ import type {
20
27
  CreateCaseResponse,
21
28
  FormData,
22
29
  MessageDocument,
30
+ NotificationActionResponse,
31
+ NotificationListResponse,
32
+ NotificationQueryParams,
33
+ NotificationSummary,
34
+ UnreadCountResponse,
23
35
  UploadDocumentResponse,
24
36
  UserConsentDetail,
25
37
  UserDataRequest,
38
+ GroupPurposesOut,
26
39
  } from './types';
27
40
  import { ensureValidToken } from './utils';
28
41
 
@@ -249,37 +262,35 @@ export const fetchProducts = async (
249
262
  }
250
263
  };
251
264
 
265
+ export type FetchUserConsentsParams = {
266
+ organizationId: string;
267
+ workspaceId: string;
268
+ offset?: number;
269
+ limit?: number;
270
+ nominatedOffset?: number;
271
+ nominatedLimit?: number;
272
+ search?: string;
273
+ status?: string;
274
+ product_uuid?: string;
275
+ language?: string;
276
+ authContext?: AuthContext;
277
+ consentClient?: BaseUrlContextValue['consentClient'];
278
+ };
279
+
252
280
  export const fetchUserConsents = async (
253
- organizationId: string,
254
- workspaceId: string,
255
- contact: string,
256
- offset?: number,
257
- limit?: number,
258
- search?: string,
259
- status?: string,
260
- product_uuid?: string,
261
- authContext?: AuthContext,
262
- consentClient?: BaseUrlContextValue['consentClient'],
263
- language?: string
281
+ params: FetchUserConsentsParams
264
282
  ): Promise<UserConsentDetail | null> => {
283
+ const { organizationId, workspaceId, authContext, consentClient, ...rest } = params;
265
284
  try {
266
- // Ensure we have a valid token before making the API call
267
285
  const accessToken = await ensureValidToken(organizationId, workspaceId, undefined, authContext, consentClient);
268
286
  if (!accessToken) {
269
- // ensureValidToken already handled sign out and redirect
270
287
  return null;
271
288
  }
272
289
 
273
290
  const result = await fetchUserConsentsServer({
274
291
  organizationId,
275
292
  workspaceId,
276
- contact,
277
- offset,
278
- limit,
279
- search,
280
- status,
281
- product_uuid,
282
- language,
293
+ ...rest,
283
294
  authToken: accessToken,
284
295
  consentClient,
285
296
  });
@@ -287,18 +298,11 @@ export const fetchUserConsents = async (
287
298
  } catch (error: any) {
288
299
  const shouldRetry = await handleClientAuthError(error, organizationId, workspaceId, authContext, consentClient);
289
300
  if (shouldRetry) {
290
- // Read from sessionStorage first (synchronously updated) then fall back to context
291
301
  const newAccessToken = sessionStorage.getItem('access_token') || authContext?.accessToken || '';
292
302
  return await fetchUserConsentsServer({
293
303
  organizationId,
294
304
  workspaceId,
295
- contact,
296
- offset,
297
- limit,
298
- search,
299
- status,
300
- product_uuid,
301
- language,
305
+ ...rest,
302
306
  authToken: newAccessToken || undefined,
303
307
  consentClient,
304
308
  });
@@ -308,6 +312,55 @@ export const fetchUserConsents = async (
308
312
  }
309
313
  };
310
314
 
315
+ export type FetchGroupPurposesParams = {
316
+ organizationId: string;
317
+ workspaceId: string;
318
+ productUuid: string;
319
+ offset?: number;
320
+ limit?: number;
321
+ status?: string;
322
+ search?: string;
323
+ language?: string;
324
+ nominatorUuid?: string;
325
+ authContext?: AuthContext;
326
+ consentClient?: BaseUrlContextValue['consentClient'];
327
+ };
328
+
329
+ export const fetchGroupPurposes = async (
330
+ params: FetchGroupPurposesParams
331
+ ): Promise<GroupPurposesOut | null> => {
332
+ const { organizationId, workspaceId, authContext, consentClient, ...rest } = params;
333
+ try {
334
+ const accessToken = await ensureValidToken(organizationId, workspaceId, undefined, authContext, consentClient);
335
+ if (!accessToken) {
336
+ return null;
337
+ }
338
+
339
+ const result = await fetchGroupPurposesServer({
340
+ organizationId,
341
+ workspaceId,
342
+ ...rest,
343
+ authToken: accessToken,
344
+ consentClient,
345
+ });
346
+ return result;
347
+ } catch (error: any) {
348
+ const shouldRetry = await handleClientAuthError(error, organizationId, workspaceId, authContext, consentClient);
349
+ if (shouldRetry) {
350
+ const newAccessToken = sessionStorage.getItem('access_token') || authContext?.accessToken || '';
351
+ return await fetchGroupPurposesServer({
352
+ organizationId,
353
+ workspaceId,
354
+ ...rest,
355
+ authToken: newAccessToken || undefined,
356
+ consentClient,
357
+ });
358
+ }
359
+ console.error('Error fetching group purposes:', error);
360
+ return null;
361
+ }
362
+ };
363
+
311
364
  export const manageConsent = async ({
312
365
  organizationId,
313
366
  workspaceId,
@@ -564,3 +617,69 @@ export const getCaseDocument = async (
564
617
  withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching case document:', (token) =>
565
618
  getCaseDocumentServer({ organizationId, workspaceId, caseUuid, documentUuid, authToken: token, consentClient })
566
619
  );
620
+
621
+ // ─── Notification Hub ───────────────────────────────────────────────────────
622
+
623
+ export const fetchNotifications = async (
624
+ organizationId: string,
625
+ workspaceId: string,
626
+ params?: NotificationQueryParams,
627
+ authContext?: AuthContext,
628
+ consentClient?: BaseUrlContextValue['consentClient'],
629
+ language?: string,
630
+ ): Promise<NotificationListResponse | null> =>
631
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching notifications:', (token) =>
632
+ getNotificationsServer({ organizationId, workspaceId, ...params, language, authToken: token, consentClient })
633
+ );
634
+
635
+ export const fetchNotificationSummary = async (
636
+ organizationId: string,
637
+ workspaceId: string,
638
+ authContext?: AuthContext,
639
+ consentClient?: BaseUrlContextValue['consentClient']
640
+ ): Promise<NotificationSummary | null> =>
641
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching notification summary:', (token) =>
642
+ getNotificationSummaryServer({ organizationId, workspaceId, authToken: token, consentClient })
643
+ );
644
+
645
+ export const fetchUnreadCount = async (
646
+ organizationId: string,
647
+ workspaceId: string,
648
+ authContext?: AuthContext,
649
+ consentClient?: BaseUrlContextValue['consentClient']
650
+ ): Promise<UnreadCountResponse | null> =>
651
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching unread count:', (token) =>
652
+ getUnreadCountServer({ organizationId, workspaceId, authToken: token, consentClient })
653
+ );
654
+
655
+ export const markNotificationRead = async (
656
+ organizationId: string,
657
+ workspaceId: string,
658
+ notificationUuid: string,
659
+ authContext?: AuthContext,
660
+ consentClient?: BaseUrlContextValue['consentClient']
661
+ ): Promise<NotificationActionResponse | null> =>
662
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error marking notification as read:', (token) =>
663
+ markNotificationReadServer({ organizationId, workspaceId, notificationUuid, authToken: token, consentClient })
664
+ );
665
+
666
+ export const markAllRead = async (
667
+ organizationId: string,
668
+ workspaceId: string,
669
+ authContext?: AuthContext,
670
+ consentClient?: BaseUrlContextValue['consentClient']
671
+ ): Promise<NotificationActionResponse | null> =>
672
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error marking all notifications as read:', (token) =>
673
+ markAllNotificationsReadServer({ organizationId, workspaceId, authToken: token, consentClient })
674
+ );
675
+
676
+ export const acknowledgeNotification = async (
677
+ organizationId: string,
678
+ workspaceId: string,
679
+ notificationUuid: string,
680
+ authContext?: AuthContext,
681
+ consentClient?: BaseUrlContextValue['consentClient']
682
+ ): Promise<NotificationActionResponse | null> =>
683
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error acknowledging notification:', (token) =>
684
+ acknowledgeNotificationServer({ organizationId, workspaceId, notificationUuid, authToken: token, consentClient })
685
+ );
@@ -1,7 +1,9 @@
1
1
  import { useMemo } from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
3
  import { ConsentStatusEnum } from '../types';
4
- import type { UserConsent } from '../types';
4
+ import type { PurposeItem } from '../types';
5
+
6
+ type CsvRow = Partial<PurposeItem> & Record<string, unknown>;
5
7
  import { formatDateTime as formatDateTimeShared } from '../../utils/date';
6
8
 
7
9
  // Re-export shared date formatting
@@ -20,7 +22,7 @@ export const useStatusOptions = () => {
20
22
  );
21
23
  };
22
24
 
23
- export const exportTableToCSV = (rows: UserConsent[]) => {
25
+ export const exportTableToCSV = (rows: CsvRow[]) => {
24
26
  if (!rows || rows.length === 0) {
25
27
  console.warn('No data to export');
26
28
  return;
@@ -4,7 +4,8 @@ import { GRIEVANCE_TAB_IDS } from '../components/constant';
4
4
  import { menuItemsPC } from '../components/SideMenuBarPC/constants';
5
5
  import type { MenuItem } from '../components/SideMenuBarPC/types';
6
6
  import type { InternalPage } from '../context/NavigationContext';
7
- import type { DocumentRequestStatus, DocumentRequestStatusInfo } from './types';
7
+ import type { DocumentRequestStatus, DocumentRequestStatusInfo, NotificationCTA, NotificationType, NotificationCategory, IconCategory, FilterTab } from './types';
8
+ import { IoPersonOutline, IoTimeOutline, IoDocumentOutline, IoShieldCheckmarkOutline, IoAlertCircleOutline, IoSettingsOutline } from 'react-icons/io5';
8
9
 
9
10
  export const useRequestTypeLabels = () => {
10
11
  const { t } = useTranslation();
@@ -89,6 +90,10 @@ export const PAGE_CHROME_BY_PAGE: Record<InternalPage, PageChromeConfig> = {
89
90
  titleKey: 'caseDetails',
90
91
  descriptionKey: 'yourPrivacyCenter',
91
92
  },
93
+ notifications: {
94
+ titleKey: 'notificationHub',
95
+ descriptionKey: 'notificationHubSubtitle',
96
+ },
92
97
  };
93
98
 
94
99
  const HREF_TO_NAME_KEY: Record<string, string> = {
@@ -159,3 +164,75 @@ export const ACTIVITY_DATE_FORMATS = {
159
164
  DISPLAY: 'MMM D, YYYY [at] h:mm A',
160
165
  TOOLTIP: 'MMMM D, YYYY [at] h:mm:ss A',
161
166
  } as const;
167
+
168
+
169
+ // ─── Notification Hub ──────────────────────────────────────────────────────
170
+
171
+ export const POLL_INTERVAL = 30_000;
172
+ export const NOTIFICATION_PAGE_SIZE = 10;
173
+ export const NOTIFICATION_DROPDOWN_LIMIT = 5;
174
+
175
+ export const TAB_TO_CATEGORY: Record<FilterTab, NotificationCategory | undefined> = {
176
+ all: undefined,
177
+ consent: 'consent',
178
+ request: 'request',
179
+ system: 'system',
180
+ };
181
+
182
+ export const TYPE_TO_ICON_CATEGORY: Record<NotificationType, IconCategory> = {
183
+ guardian_verification_request: 'guardian',
184
+ consent_expiry_warning: 'consent',
185
+ new_consent_notice: 'system',
186
+ consent_withdrawal_confirmation: 'withdrawal',
187
+ data_request_status_update: 'access',
188
+ case_message: 'access',
189
+ grievance_status_update: 'grievance',
190
+ consent_regrant_prompt: 'consent',
191
+ };
192
+
193
+ export const ICON_MAP: Record<IconCategory, typeof IoPersonOutline> = {
194
+ guardian: IoPersonOutline,
195
+ consent: IoTimeOutline,
196
+ access: IoDocumentOutline,
197
+ withdrawal: IoShieldCheckmarkOutline,
198
+ grievance: IoAlertCircleOutline,
199
+ system: IoSettingsOutline,
200
+ };
201
+
202
+ export const ICON_COLORS: Record<IconCategory, string> = {
203
+ guardian: '#7c3aed',
204
+ consent: '#ea580c',
205
+ access: '#16a34a',
206
+ withdrawal: '#2563eb',
207
+ grievance: '#dc2626',
208
+ system: '#6b7280',
209
+ };
210
+
211
+ export const CTA_VARIANT_CLASS: Record<NotificationCTA['style'], string> = {
212
+ primary: 'pc-notification-cta--primary',
213
+ danger: 'pc-notification-cta--danger',
214
+ secondary: 'pc-notification-cta--secondary',
215
+ };
216
+
217
+ export const useNotificationFilterTabs = () => {
218
+ const { t } = useTranslation();
219
+ return [
220
+ { key: 'all' as FilterTab, label: t('all') },
221
+ { key: 'consent' as FilterTab, label: t('consent') },
222
+ { key: 'request' as FilterTab, label: t('requests') },
223
+ { key: 'system' as FilterTab, label: t('system') },
224
+ ];
225
+ };
226
+
227
+ export const useNotificationStatLabels = () => {
228
+ const { t } = useTranslation();
229
+ return {
230
+ total: t('total'),
231
+ unread: t('unread'),
232
+ action_needed: t('actionNeeded'),
233
+ acknowledged: t('acknowledged'),
234
+ };
235
+ };
236
+
237
+
238
+ export const CASE_DETAILS_PREFIX = '/case-details/';
@@ -180,21 +180,6 @@ export type ConsentDataElement = {
180
180
  selected: boolean;
181
181
  };
182
182
 
183
- export type UserConsent = {
184
- purpose_uuid?: string;
185
- purpose: string;
186
- purpose_description: string;
187
- status: ConsentStatusEnum;
188
- given_date: string;
189
- valid_till: string;
190
- method: string;
191
- data_elements: ConsentDataElement[];
192
- product_uuid?: string;
193
- product_name?: string;
194
- product_description?: string | null;
195
- nominator_info?: NominatorInfo;
196
- };
197
-
198
183
  export type NominatorInfo = {
199
184
  org_user_id: string;
200
185
  name: string | null;
@@ -202,44 +187,79 @@ export type NominatorInfo = {
202
187
  email: string | null;
203
188
  };
204
189
 
190
+ export type PurposeItem = {
191
+ purpose_uuid: string;
192
+ name: string;
193
+ description: string | null;
194
+ status: ConsentStatusEnum | string;
195
+ given_date: string | null;
196
+ valid_till: string | null;
197
+ method: string;
198
+ data_elements: ConsentDataElement[];
199
+ link_reason: string | null;
200
+ };
201
+
202
+ export type ConsentGroup = {
203
+ product_uuid: string;
204
+ product_name: string;
205
+ product_description: string | null;
206
+ nominator: NominatorInfo | null;
207
+ total_purposes: number;
208
+ active_purposes: number;
209
+ purposes: PurposeItem[];
210
+ has_more_purposes: boolean;
211
+ };
212
+
213
+ export type PaginationMeta = {
214
+ total_count: number;
215
+ offset: number;
216
+ limit: number;
217
+ };
218
+
219
+ export type Pagination = PaginationMeta;
220
+
205
221
  export type ConsentHistoryStatusSummary = {
206
222
  total_purposes: number;
207
223
  direct_purposes: number;
208
224
  nominated_purposes: number;
209
- nominee_purposes?: number;
210
225
  active_purposes: number;
211
226
  inactive_purposes: number;
212
227
  };
213
228
 
214
- export type ProductConsentHistoryGroup = {
229
+ export type DisplayProductGroup = ConsentGroup & {
230
+ withdrawn_purposes: number;
231
+ expired_purposes: number;
232
+ declined_purposes: number;
233
+ latest_given_date: string | null;
234
+ };
235
+
236
+ export type DisplayPurposeRow = PurposeItem & {
215
237
  product_uuid: string;
216
238
  product_name: string;
217
239
  product_description: string | null;
218
- purposes: UserConsent[];
219
- total_purposes: number;
220
- active_purposes: number;
240
+ nominator: NominatorInfo | null;
221
241
  };
222
242
 
223
- export type NominatedPurposesGroup = {
224
- nominator_info: NominatorInfo;
225
- product_groups: ProductConsentHistoryGroup[];
243
+ export type ConsentModalRecord = PurposeItem & {
244
+ product_uuid: string;
245
+ product_name: string;
246
+ product_description: string | null;
247
+ nominator: NominatorInfo | null;
226
248
  };
227
249
 
228
- export type Pagination = {
229
- total_count: number;
230
- offset: number;
231
- limit: number;
250
+ export type UserConsentDetail = {
251
+ direct: ConsentGroup[];
252
+ direct_pagination: PaginationMeta;
253
+ nominated: ConsentGroup[];
254
+ nominated_pagination: PaginationMeta;
255
+ user_status: 'ACTIVE' | 'TRANSFERRED' | null;
256
+ nominators: NominatorInfo[];
257
+ status_summary: ConsentHistoryStatusSummary;
232
258
  };
233
259
 
234
- export type UserConsentDetail = {
235
- data?: UserConsent[];
236
- product_purpose_groups?: ProductConsentHistoryGroup[];
237
- pagination: Pagination;
238
- direct_product_groups?: ProductConsentHistoryGroup[];
239
- user_status?: 'ACTIVE' | 'TRANSFERRED' | null;
240
- nominated_purposes?: NominatedPurposesGroup[];
241
- nominators?: NominatorInfo[];
242
- status_summary?: ConsentHistoryStatusSummary | null;
260
+ export type GroupPurposesOut = {
261
+ purposes: PurposeItem[];
262
+ pagination: PaginationMeta;
243
263
  };
244
264
 
245
265
  export type UserConsentResponse = {
@@ -251,6 +271,7 @@ export type UserConsentResponse = {
251
271
  export enum ConsentStatusEnum {
252
272
  ACTIVE = 'ACTIVE',
253
273
  WITHDRAWN = 'WITHDRAW',
274
+ REVOKED = 'REVOKED',
254
275
  EXPIRED = 'EXPIRED',
255
276
  DECLINED = 'DECLINED',
256
277
  }
@@ -529,3 +550,151 @@ export type SubmitDocumentForRequestInput = Readonly<{
529
550
  export type DocumentRequestStatusInfo = Readonly<{ labelKey: string; color: string }>;
530
551
 
531
552
  export type CaseDetailsTab = Readonly<{ id: TabId; label: string }>;
553
+
554
+ // ─── Notification Hub ──────────────────────────────────────────────────────
555
+
556
+ export type NotificationType =
557
+ | 'guardian_verification_request'
558
+ | 'consent_expiry_warning'
559
+ | 'new_consent_notice'
560
+ | 'consent_withdrawal_confirmation'
561
+ | 'data_request_status_update'
562
+ | 'case_message'
563
+ | 'grievance_status_update'
564
+ | 'consent_regrant_prompt';
565
+
566
+ export type NotificationCategory = 'consent' | 'request' | 'system';
567
+
568
+ export type NotificationCTA = {
569
+ label: string;
570
+ style: 'primary' | 'secondary' | 'danger';
571
+ action_type: 'link' | 'acknowledge';
572
+ url?: string;
573
+ };
574
+
575
+ export type Notification = {
576
+ uuid: string;
577
+ notification_type: NotificationType;
578
+ category: NotificationCategory;
579
+ title: string;
580
+ description: string;
581
+ is_read: boolean;
582
+ is_acknowledged: boolean;
583
+ source_type: string;
584
+ source_uuid: string | null;
585
+ request_type: string;
586
+ request_type_display?: string | null;
587
+ status_label: string;
588
+ status_label_display?: string | null;
589
+ ctas: NotificationCTA[];
590
+ created_at: string;
591
+ };
592
+
593
+ export type NotificationSummary = {
594
+ total: number;
595
+ unread: number;
596
+ action_needed: number;
597
+ acknowledged: number;
598
+ };
599
+
600
+ export type NotificationListResponse = {
601
+ data: Notification[];
602
+ total: number;
603
+ skip: number;
604
+ limit: number;
605
+ summary: NotificationSummary | null;
606
+ };
607
+
608
+ export type UnreadCountResponse = {
609
+ count: number;
610
+ };
611
+
612
+ export type NotificationActionResponse = {
613
+ message: string;
614
+ };
615
+
616
+ export type NotificationActionBaseParams = {
617
+ organizationId: string;
618
+ workspaceId: string;
619
+ authToken?: string;
620
+ consentClient?: ReturnType<typeof import('../api/client').createConsentClient>;
621
+ };
622
+
623
+ export type StatCardProps = Readonly<{
624
+ value: number; label: string;
625
+ }>
626
+
627
+ export type NotificationDropdownProps = Readonly<{
628
+ notifications: Notification[];
629
+ onMarkAllRead: () => void;
630
+ onRefresh: () => void;
631
+ isRefreshing: boolean;
632
+ onViewAll: () => void;
633
+ onNotificationClick: (notification: Notification) => void;
634
+ onCTAClick: (notification: Notification, cta: NotificationCTA) => void;
635
+ }>;
636
+
637
+ export type NotificationListPanelProps = Readonly<{
638
+ notifications: Notification[];
639
+ totalCount: number;
640
+ activeTab: FilterTab;
641
+ onTabChange: (tab: FilterTab) => void;
642
+ skip: number;
643
+ onSkipChange: (skip: number) => void;
644
+ isLoading: boolean;
645
+ isRefreshing: boolean;
646
+ onRefresh: () => void;
647
+ onMarkAllRead: () => void;
648
+ onNotificationClick: (notification: Notification) => void;
649
+ onCTAClick: (notification: Notification, cta: NotificationCTA) => void;
650
+ }>;
651
+
652
+
653
+ export type NotificationRowProps = Readonly<{
654
+ notification: Notification;
655
+ onClick: () => void;
656
+ onCTAClick: (cta: NotificationCTA) => void;
657
+ }>;
658
+
659
+ export type NotificationIconProps = Readonly<{
660
+ notificationType: NotificationType;
661
+ size?: 'sm' | 'lg';
662
+ }>;
663
+
664
+ export type NotificationCTAButtonProps = Readonly<{
665
+ cta: NotificationCTA;
666
+ onClick: (e: React.MouseEvent) => void;
667
+ size?: 'sm' | 'lg';
668
+ }>;
669
+
670
+ export type FilterTab = 'all' | 'consent' | 'request' | 'system';
671
+
672
+ export type IconCategory = 'guardian' | 'consent' | 'access' | 'withdrawal' | 'grievance' | 'system';
673
+
674
+ export type NotificationDropdownItemProps = Readonly<{
675
+ notification: Notification;
676
+ onNotificationClick: (notification: Notification) => void;
677
+ onCTAClick: (notification: Notification, cta: NotificationCTA) => void;
678
+ }>
679
+
680
+ export type NotificationQueryParams = {
681
+ category?: NotificationCategory;
682
+ notification_type?: NotificationType;
683
+ is_read?: boolean;
684
+ skip?: number;
685
+ limit?: number;
686
+ include_summary?: boolean;
687
+ language?: string;
688
+ };
689
+
690
+ export type NotificationClient = NonNullable<NotificationActionBaseParams['consentClient']>;
691
+
692
+ export type GetNotificationsServerParams = NotificationActionBaseParams & NotificationQueryParams;
693
+
694
+ export type NotificationByUuidServerParams = NotificationActionBaseParams & { notificationUuid: string };
695
+
696
+ export type NotificationRefreshButtonProps = Readonly<{
697
+ isRefreshing: boolean;
698
+ onRefresh: () => void;
699
+ iconSize?: number;
700
+ }>;
@@ -624,3 +624,6 @@ export const formatActivityType = (activityType: string): string => {
624
624
  .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
625
625
  .join(' ');
626
626
  };
627
+
628
+
629
+ export const getVar = (varName: string): string => `var(${varName})`;
@@ -276,5 +276,16 @@
276
276
  "removeAttachment": "সংলগ্ন আঁতৰাওক",
277
277
  "document": "নথি",
278
278
  "downloadAttachment": "{{fileName}} ডাউনলোড কৰক",
279
- "sortByColumn": "স্তম্ভ অনুসৰি সজাওক"
279
+ "sortByColumn": "স্তম্ভ অনুসৰি সজাওক",
280
+ "manage": "Manage",
281
+ "consents": "Consents",
282
+ "consentCount_one": "{{count}} consent",
283
+ "consentCount_other": "{{count}} consents",
284
+ "purposes": "Purposes",
285
+ "latestActivity": "Latest activity",
286
+ "activeWithCount": "{{count}} active",
287
+ "withdrawnWithCount": "{{count}} withdrawn",
288
+ "expiredWithCount": "{{count}} expired",
289
+ "declinedWithCount": "{{count}} declined",
290
+ "productsAndConsentsSummary": "{{products}} products, {{consents}} consents"
280
291
  }
@@ -276,5 +276,16 @@
276
276
  "removeAttachment": "संलग्नक हटाईं",
277
277
  "document": "दस्तावेज",
278
278
  "downloadAttachment": "{{fileName}} डाउनलोड करीं",
279
- "sortByColumn": "कॉलम अनुसार क्रमबद्ध करीं"
279
+ "sortByColumn": "कॉलम अनुसार क्रमबद्ध करीं",
280
+ "manage": "Manage",
281
+ "consents": "Consents",
282
+ "consentCount_one": "{{count}} consent",
283
+ "consentCount_other": "{{count}} consents",
284
+ "purposes": "Purposes",
285
+ "latestActivity": "Latest activity",
286
+ "activeWithCount": "{{count}} active",
287
+ "withdrawnWithCount": "{{count}} withdrawn",
288
+ "expiredWithCount": "{{count}} expired",
289
+ "declinedWithCount": "{{count}} declined",
290
+ "productsAndConsentsSummary": "{{products}} products, {{consents}} consents"
280
291
  }
@@ -276,5 +276,16 @@
276
276
  "removeAttachment": "সংযুক্তি সরান",
277
277
  "document": "নথি",
278
278
  "downloadAttachment": "{{fileName}} ডাউনলোড করুন",
279
- "sortByColumn": "কলাম অনুসারে সাজান"
279
+ "sortByColumn": "কলাম অনুসারে সাজান",
280
+ "manage": "Manage",
281
+ "consents": "Consents",
282
+ "consentCount_one": "{{count}} consent",
283
+ "consentCount_other": "{{count}} consents",
284
+ "purposes": "Purposes",
285
+ "latestActivity": "Latest activity",
286
+ "activeWithCount": "{{count}} active",
287
+ "withdrawnWithCount": "{{count}} withdrawn",
288
+ "expiredWithCount": "{{count}} expired",
289
+ "declinedWithCount": "{{count}} declined",
290
+ "productsAndConsentsSummary": "{{products}} products, {{consents}} consents"
280
291
  }