@redacto.io/consent-sdk-react 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.d.mts +24 -1
  3. package/dist/index.d.ts +24 -1
  4. package/dist/index.js +149 -206
  5. package/dist/index.mjs +148 -206
  6. package/dist/privacy-center.d.mts +81 -2
  7. package/dist/privacy-center.d.ts +81 -2
  8. package/dist/privacy-center.js +2823 -1272
  9. package/dist/privacy-center.mjs +2813 -1263
  10. package/package.json +1 -1
  11. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +16 -20
  12. package/src/RedactoNoticeConsent/api/index.ts +21 -103
  13. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +5 -4
  14. package/src/RedactoNoticeConsentInline/api/index.ts +64 -117
  15. package/src/RedactoPrivacyCenter/NotificationsPage.tsx +174 -0
  16. package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +3 -0
  17. package/src/RedactoPrivacyCenter/api/actions.ts +84 -0
  18. package/src/RedactoPrivacyCenter/api/client.ts +118 -0
  19. package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +15 -0
  20. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationBell.tsx +160 -0
  21. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationCTAButton.tsx +18 -0
  22. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdown.tsx +54 -0
  23. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdownItem.tsx +57 -0
  24. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationIcon.tsx +19 -0
  25. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationListPanel.tsx +92 -0
  26. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRefreshButton.tsx +20 -0
  27. package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRow.tsx +58 -0
  28. package/src/RedactoPrivacyCenter/components/NotificationHub/StatCard.tsx +10 -0
  29. package/src/RedactoPrivacyCenter/components/NotificationHub/useNotificationActions.ts +112 -0
  30. package/src/RedactoPrivacyCenter/components/NotificationHub/utils.ts +27 -0
  31. package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +3 -2
  32. package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +2 -2
  33. package/src/RedactoPrivacyCenter/lib/api.ts +77 -0
  34. package/src/RedactoPrivacyCenter/lib/constants.ts +78 -1
  35. package/src/RedactoPrivacyCenter/lib/types.ts +148 -0
  36. package/src/RedactoPrivacyCenter/lib/utils.ts +3 -0
  37. package/src/RedactoPrivacyCenter/locales/en/translation.json +27 -1
  38. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +505 -0
  39. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +23 -1
  40. package/src/index.ts +5 -0
  41. package/src/privacy-center.ts +1 -0
  42. 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,8 @@ 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
17
 
18
18
  export default function PrivacyCenterLayout({
19
19
  children,
@@ -141,6 +141,7 @@ export default function PrivacyCenterLayout({
141
141
  value={languageOptions.find((opt) => opt.value === locale) || null}
142
142
  onChange={handleLanguageChange}
143
143
  />
144
+ <NotificationBell />
144
145
  <ThemeToggle />
145
146
  </div>
146
147
  </nav>
@@ -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 {
@@ -11,6 +11,12 @@ import {
11
11
  uploadCaseDocumentServer,
12
12
  submitDocumentForRequestServer,
13
13
  getCaseDocumentServer,
14
+ getNotificationsServer,
15
+ getNotificationSummaryServer,
16
+ getUnreadCountServer,
17
+ markNotificationReadServer,
18
+ markAllNotificationsReadServer,
19
+ acknowledgeNotificationServer,
14
20
  } from '../api/actions';
15
21
  import type { BaseUrlContextValue } from '../context/BaseUrlContext';
16
22
  import type {
@@ -20,6 +26,11 @@ import type {
20
26
  CreateCaseResponse,
21
27
  FormData,
22
28
  MessageDocument,
29
+ NotificationActionResponse,
30
+ NotificationListResponse,
31
+ NotificationQueryParams,
32
+ NotificationSummary,
33
+ UnreadCountResponse,
23
34
  UploadDocumentResponse,
24
35
  UserConsentDetail,
25
36
  UserDataRequest,
@@ -564,3 +575,69 @@ export const getCaseDocument = async (
564
575
  withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching case document:', (token) =>
565
576
  getCaseDocumentServer({ organizationId, workspaceId, caseUuid, documentUuid, authToken: token, consentClient })
566
577
  );
578
+
579
+ // ─── Notification Hub ───────────────────────────────────────────────────────
580
+
581
+ export const fetchNotifications = async (
582
+ organizationId: string,
583
+ workspaceId: string,
584
+ params?: NotificationQueryParams,
585
+ authContext?: AuthContext,
586
+ consentClient?: BaseUrlContextValue['consentClient'],
587
+ language?: string,
588
+ ): Promise<NotificationListResponse | null> =>
589
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching notifications:', (token) =>
590
+ getNotificationsServer({ organizationId, workspaceId, ...params, language, authToken: token, consentClient })
591
+ );
592
+
593
+ export const fetchNotificationSummary = async (
594
+ organizationId: string,
595
+ workspaceId: string,
596
+ authContext?: AuthContext,
597
+ consentClient?: BaseUrlContextValue['consentClient']
598
+ ): Promise<NotificationSummary | null> =>
599
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching notification summary:', (token) =>
600
+ getNotificationSummaryServer({ organizationId, workspaceId, authToken: token, consentClient })
601
+ );
602
+
603
+ export const fetchUnreadCount = async (
604
+ organizationId: string,
605
+ workspaceId: string,
606
+ authContext?: AuthContext,
607
+ consentClient?: BaseUrlContextValue['consentClient']
608
+ ): Promise<UnreadCountResponse | null> =>
609
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching unread count:', (token) =>
610
+ getUnreadCountServer({ organizationId, workspaceId, authToken: token, consentClient })
611
+ );
612
+
613
+ export const markNotificationRead = async (
614
+ organizationId: string,
615
+ workspaceId: string,
616
+ notificationUuid: string,
617
+ authContext?: AuthContext,
618
+ consentClient?: BaseUrlContextValue['consentClient']
619
+ ): Promise<NotificationActionResponse | null> =>
620
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error marking notification as read:', (token) =>
621
+ markNotificationReadServer({ organizationId, workspaceId, notificationUuid, authToken: token, consentClient })
622
+ );
623
+
624
+ export const markAllRead = async (
625
+ organizationId: string,
626
+ workspaceId: string,
627
+ authContext?: AuthContext,
628
+ consentClient?: BaseUrlContextValue['consentClient']
629
+ ): Promise<NotificationActionResponse | null> =>
630
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error marking all notifications as read:', (token) =>
631
+ markAllNotificationsReadServer({ organizationId, workspaceId, authToken: token, consentClient })
632
+ );
633
+
634
+ export const acknowledgeNotification = async (
635
+ organizationId: string,
636
+ workspaceId: string,
637
+ notificationUuid: string,
638
+ authContext?: AuthContext,
639
+ consentClient?: BaseUrlContextValue['consentClient']
640
+ ): Promise<NotificationActionResponse | null> =>
641
+ withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error acknowledging notification:', (token) =>
642
+ acknowledgeNotificationServer({ organizationId, workspaceId, notificationUuid, authToken: token, consentClient })
643
+ );
@@ -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/';
@@ -529,3 +529,151 @@ export type SubmitDocumentForRequestInput = Readonly<{
529
529
  export type DocumentRequestStatusInfo = Readonly<{ labelKey: string; color: string }>;
530
530
 
531
531
  export type CaseDetailsTab = Readonly<{ id: TabId; label: string }>;
532
+
533
+ // ─── Notification Hub ──────────────────────────────────────────────────────
534
+
535
+ export type NotificationType =
536
+ | 'guardian_verification_request'
537
+ | 'consent_expiry_warning'
538
+ | 'new_consent_notice'
539
+ | 'consent_withdrawal_confirmation'
540
+ | 'data_request_status_update'
541
+ | 'case_message'
542
+ | 'grievance_status_update'
543
+ | 'consent_regrant_prompt';
544
+
545
+ export type NotificationCategory = 'consent' | 'request' | 'system';
546
+
547
+ export type NotificationCTA = {
548
+ label: string;
549
+ style: 'primary' | 'secondary' | 'danger';
550
+ action_type: 'link' | 'acknowledge';
551
+ url?: string;
552
+ };
553
+
554
+ export type Notification = {
555
+ uuid: string;
556
+ notification_type: NotificationType;
557
+ category: NotificationCategory;
558
+ title: string;
559
+ description: string;
560
+ is_read: boolean;
561
+ is_acknowledged: boolean;
562
+ source_type: string;
563
+ source_uuid: string | null;
564
+ request_type: string;
565
+ request_type_display?: string | null;
566
+ status_label: string;
567
+ status_label_display?: string | null;
568
+ ctas: NotificationCTA[];
569
+ created_at: string;
570
+ };
571
+
572
+ export type NotificationSummary = {
573
+ total: number;
574
+ unread: number;
575
+ action_needed: number;
576
+ acknowledged: number;
577
+ };
578
+
579
+ export type NotificationListResponse = {
580
+ data: Notification[];
581
+ total: number;
582
+ skip: number;
583
+ limit: number;
584
+ summary: NotificationSummary | null;
585
+ };
586
+
587
+ export type UnreadCountResponse = {
588
+ count: number;
589
+ };
590
+
591
+ export type NotificationActionResponse = {
592
+ message: string;
593
+ };
594
+
595
+ export type NotificationActionBaseParams = {
596
+ organizationId: string;
597
+ workspaceId: string;
598
+ authToken?: string;
599
+ consentClient?: ReturnType<typeof import('../api/client').createConsentClient>;
600
+ };
601
+
602
+ export type StatCardProps = Readonly<{
603
+ value: number; label: string;
604
+ }>
605
+
606
+ export type NotificationDropdownProps = Readonly<{
607
+ notifications: Notification[];
608
+ onMarkAllRead: () => void;
609
+ onRefresh: () => void;
610
+ isRefreshing: boolean;
611
+ onViewAll: () => void;
612
+ onNotificationClick: (notification: Notification) => void;
613
+ onCTAClick: (notification: Notification, cta: NotificationCTA) => void;
614
+ }>;
615
+
616
+ export type NotificationListPanelProps = Readonly<{
617
+ notifications: Notification[];
618
+ totalCount: number;
619
+ activeTab: FilterTab;
620
+ onTabChange: (tab: FilterTab) => void;
621
+ skip: number;
622
+ onSkipChange: (skip: number) => void;
623
+ isLoading: boolean;
624
+ isRefreshing: boolean;
625
+ onRefresh: () => void;
626
+ onMarkAllRead: () => void;
627
+ onNotificationClick: (notification: Notification) => void;
628
+ onCTAClick: (notification: Notification, cta: NotificationCTA) => void;
629
+ }>;
630
+
631
+
632
+ export type NotificationRowProps = Readonly<{
633
+ notification: Notification;
634
+ onClick: () => void;
635
+ onCTAClick: (cta: NotificationCTA) => void;
636
+ }>;
637
+
638
+ export type NotificationIconProps = Readonly<{
639
+ notificationType: NotificationType;
640
+ size?: 'sm' | 'lg';
641
+ }>;
642
+
643
+ export type NotificationCTAButtonProps = Readonly<{
644
+ cta: NotificationCTA;
645
+ onClick: (e: React.MouseEvent) => void;
646
+ size?: 'sm' | 'lg';
647
+ }>;
648
+
649
+ export type FilterTab = 'all' | 'consent' | 'request' | 'system';
650
+
651
+ export type IconCategory = 'guardian' | 'consent' | 'access' | 'withdrawal' | 'grievance' | 'system';
652
+
653
+ export type NotificationDropdownItemProps = Readonly<{
654
+ notification: Notification;
655
+ onNotificationClick: (notification: Notification) => void;
656
+ onCTAClick: (notification: Notification, cta: NotificationCTA) => void;
657
+ }>
658
+
659
+ export type NotificationQueryParams = {
660
+ category?: NotificationCategory;
661
+ notification_type?: NotificationType;
662
+ is_read?: boolean;
663
+ skip?: number;
664
+ limit?: number;
665
+ include_summary?: boolean;
666
+ language?: string;
667
+ };
668
+
669
+ export type NotificationClient = NonNullable<NotificationActionBaseParams['consentClient']>;
670
+
671
+ export type GetNotificationsServerParams = NotificationActionBaseParams & NotificationQueryParams;
672
+
673
+ export type NotificationByUuidServerParams = NotificationActionBaseParams & { notificationUuid: string };
674
+
675
+ export type NotificationRefreshButtonProps = Readonly<{
676
+ isRefreshing: boolean;
677
+ onRefresh: () => void;
678
+ iconSize?: number;
679
+ }>;
@@ -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})`;
@@ -251,5 +251,31 @@
251
251
  "document": "Document",
252
252
  "downloadAttachment": "Download {{fileName}}",
253
253
  "sortByColumn": "Sort by column",
254
- "failedToFetchActivityData": "Failed to fetch activity data"
254
+ "failedToFetchActivityData": "Failed to fetch activity data",
255
+ "acknowledged": "Acknowledged",
256
+ "actionNeeded": "Action needed",
257
+ "all": "All",
258
+ "backToManageConsent": "Back to manage consent",
259
+ "consent": "Consent",
260
+ "daysAgo": "{{count}}d ago",
261
+ "hoursAgo": "{{count}}h ago",
262
+ "justNow": "Just now",
263
+ "loadingNotifications": "Loading notifications...",
264
+ "markAllAsRead": "Mark all as read",
265
+ "minutesAgo": "{{count}}m ago",
266
+ "next": "Next",
267
+ "noNotificationsInCategory": "No notifications in this category.",
268
+ "noNotificationsYet": "No notifications yet.",
269
+ "notificationHub": "Notification Hub",
270
+ "notificationHubSubtitle": "All your privacy-related notifications in one place",
271
+ "notifications": "Notifications",
272
+ "paginationRange": "{{start}}–{{end}} of {{total}}",
273
+ "previous": "Previous",
274
+ "refreshNotifications": "Refresh notifications",
275
+ "requests": "Requests",
276
+ "system": "System",
277
+ "total": "Total",
278
+ "unread": "Unread",
279
+ "viewAllNotifications": "View all notifications",
280
+ "weeksAgo": "{{count}}w ago"
255
281
  }