@redacto.io/consent-sdk-react 4.0.0 → 5.1.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 (161) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/LICENSE +176 -0
  3. package/README.md +98 -1
  4. package/dist/index.js +231 -96
  5. package/dist/index.mjs +231 -96
  6. package/dist/privacy-center.d.mts +649 -0
  7. package/dist/privacy-center.d.ts +649 -0
  8. package/dist/privacy-center.js +20824 -0
  9. package/dist/privacy-center.mjs +20789 -0
  10. package/package.json +47 -3
  11. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +6 -4
  12. package/src/RedactoNoticeConsent/api/index.ts +2 -0
  13. package/src/RedactoNoticeConsent/api/types.ts +2 -0
  14. package/src/RedactoNoticeConsent/components/DigiLockerCallback.tsx +128 -62
  15. package/src/RedactoNoticeConsent/language-codes.ts +97 -0
  16. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +0 -3
  17. package/src/RedactoNoticeConsentInline/api/index.ts +0 -1
  18. package/src/RedactoPrivacyCenter/PrivacyCenterActivityPage.tsx +19 -0
  19. package/src/RedactoPrivacyCenter/PrivacyCenterCaseDetailsPage.tsx +154 -0
  20. package/src/RedactoPrivacyCenter/PrivacyCenterConsentManagerPage.tsx +17 -0
  21. package/src/RedactoPrivacyCenter/PrivacyCenterFormPage.tsx +79 -0
  22. package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +30 -0
  23. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +173 -0
  24. package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +55 -0
  25. package/src/RedactoPrivacyCenter/api/actions.ts +476 -0
  26. package/src/RedactoPrivacyCenter/api/client.ts +524 -0
  27. package/src/RedactoPrivacyCenter/api/config.ts +33 -0
  28. package/src/RedactoPrivacyCenter/api/fetcher.ts +181 -0
  29. package/src/RedactoPrivacyCenter/api/upload.ts +93 -0
  30. package/src/RedactoPrivacyCenter/assets/character.svg +31 -0
  31. package/src/RedactoPrivacyCenter/assets/fileAttach.svg +9 -0
  32. package/src/RedactoPrivacyCenter/assets/greenTick.svg +4 -0
  33. package/src/RedactoPrivacyCenter/assets/mail.svg +3 -0
  34. package/src/RedactoPrivacyCenter/assets/poweredByRedacto.svg +9 -0
  35. package/src/RedactoPrivacyCenter/assets/sentImg.svg +9 -0
  36. package/src/RedactoPrivacyCenter/assets/uploadFileIcon.svg +9 -0
  37. package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +272 -0
  38. package/src/RedactoPrivacyCenter/components/CaseDetails/AttachmentCard.tsx +38 -0
  39. package/src/RedactoPrivacyCenter/components/CaseDetails/ChatBubble.tsx +31 -0
  40. package/src/RedactoPrivacyCenter/components/CaseDetails/DocumentRequestCard.tsx +73 -0
  41. package/src/RedactoPrivacyCenter/components/CaseDetails/MessageComposer.tsx +87 -0
  42. package/src/RedactoPrivacyCenter/components/CaseDetails/MessageItem.tsx +95 -0
  43. package/src/RedactoPrivacyCenter/components/CaseDetails/MessagesTab.tsx +147 -0
  44. package/src/RedactoPrivacyCenter/components/CaseDetails/RequestDetailsTab.tsx +171 -0
  45. package/src/RedactoPrivacyCenter/components/CaseDetails/StatusEvent.tsx +23 -0
  46. package/src/RedactoPrivacyCenter/components/CaseDetails/useFileUpload.ts +116 -0
  47. package/src/RedactoPrivacyCenter/components/CaseDetails/useMessagePolling.ts +36 -0
  48. package/src/RedactoPrivacyCenter/components/CaseDetails/utils.ts +56 -0
  49. package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +469 -0
  50. package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +426 -0
  51. package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +732 -0
  52. package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +238 -0
  53. package/src/RedactoPrivacyCenter/components/ConsentManagerConstants.ts +7 -0
  54. package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +155 -0
  55. package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +189 -0
  56. package/src/RedactoPrivacyCenter/components/DataRequest/GrievanceDataSection.tsx +118 -0
  57. package/src/RedactoPrivacyCenter/components/DataRequest/NominationDataSection.tsx +66 -0
  58. package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +304 -0
  59. package/src/RedactoPrivacyCenter/components/Form.tsx +1596 -0
  60. package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +174 -0
  61. package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +337 -0
  62. package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +330 -0
  63. package/src/RedactoPrivacyCenter/components/Pagination.tsx +140 -0
  64. package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +304 -0
  65. package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +232 -0
  66. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +475 -0
  67. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideMenuPC.tsx +408 -0
  68. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +42 -0
  69. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/menuItemsPC.tsx +1 -0
  70. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +40 -0
  71. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/utils.ts +38 -0
  72. package/src/RedactoPrivacyCenter/components/ThemeTransitionOverlay.tsx +85 -0
  73. package/src/RedactoPrivacyCenter/components/activitiesColumns.tsx +141 -0
  74. package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +159 -0
  75. package/src/RedactoPrivacyCenter/components/constant.ts +16 -0
  76. package/src/RedactoPrivacyCenter/components/types.ts +76 -0
  77. package/src/RedactoPrivacyCenter/components/utils.tsx +10 -0
  78. package/src/RedactoPrivacyCenter/config/routes.ts +9 -0
  79. package/src/RedactoPrivacyCenter/config/table.ts +25 -0
  80. package/src/RedactoPrivacyCenter/context/AuthContext.tsx +137 -0
  81. package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +41 -0
  82. package/src/RedactoPrivacyCenter/context/LocaleContext.tsx +11 -0
  83. package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +96 -0
  84. package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +30 -0
  85. package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +60 -0
  86. package/src/RedactoPrivacyCenter/context/theme.tsx +127 -0
  87. package/src/RedactoPrivacyCenter/context/types.ts +14 -0
  88. package/src/RedactoPrivacyCenter/lib/api.ts +566 -0
  89. package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +63 -0
  90. package/src/RedactoPrivacyCenter/lib/constants.ts +161 -0
  91. package/src/RedactoPrivacyCenter/lib/i18n.ts +81 -0
  92. package/src/RedactoPrivacyCenter/lib/locales.ts +105 -0
  93. package/src/RedactoPrivacyCenter/lib/queries.ts +134 -0
  94. package/src/RedactoPrivacyCenter/lib/types.ts +531 -0
  95. package/src/RedactoPrivacyCenter/lib/useCurrentLanguage.ts +37 -0
  96. package/src/RedactoPrivacyCenter/lib/utils.ts +626 -0
  97. package/src/RedactoPrivacyCenter/locales/as/translation.json +280 -0
  98. package/src/RedactoPrivacyCenter/locales/bho/translation.json +280 -0
  99. package/src/RedactoPrivacyCenter/locales/bn/translation.json +280 -0
  100. package/src/RedactoPrivacyCenter/locales/brx/translation.json +280 -0
  101. package/src/RedactoPrivacyCenter/locales/doi/translation.json +280 -0
  102. package/src/RedactoPrivacyCenter/locales/en/translation.json +255 -0
  103. package/src/RedactoPrivacyCenter/locales/gom/translation.json +280 -0
  104. package/src/RedactoPrivacyCenter/locales/gu/translation.json +280 -0
  105. package/src/RedactoPrivacyCenter/locales/hi/translation.json +280 -0
  106. package/src/RedactoPrivacyCenter/locales/kn/translation.json +280 -0
  107. package/src/RedactoPrivacyCenter/locales/ks/translation.json +280 -0
  108. package/src/RedactoPrivacyCenter/locales/mai/translation.json +280 -0
  109. package/src/RedactoPrivacyCenter/locales/ml/translation.json +280 -0
  110. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +280 -0
  111. package/src/RedactoPrivacyCenter/locales/mr/translation.json +280 -0
  112. package/src/RedactoPrivacyCenter/locales/ne/translation.json +280 -0
  113. package/src/RedactoPrivacyCenter/locales/or/translation.json +280 -0
  114. package/src/RedactoPrivacyCenter/locales/pa/translation.json +280 -0
  115. package/src/RedactoPrivacyCenter/locales/sa/translation.json +280 -0
  116. package/src/RedactoPrivacyCenter/locales/sat/translation.json +280 -0
  117. package/src/RedactoPrivacyCenter/locales/sd/translation.json +280 -0
  118. package/src/RedactoPrivacyCenter/locales/ta/translation.json +280 -0
  119. package/src/RedactoPrivacyCenter/locales/te/translation.json +280 -0
  120. package/src/RedactoPrivacyCenter/locales/ur/translation.json +280 -0
  121. package/src/RedactoPrivacyCenter/styles/base.css +117 -0
  122. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +1725 -0
  123. package/src/RedactoPrivacyCenter/styles/pcStyles.ts +813 -0
  124. package/src/RedactoPrivacyCenter/styles/responsive.ts +138 -0
  125. package/src/RedactoPrivacyCenter/styles/variables.css +133 -0
  126. package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +367 -0
  127. package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +476 -0
  128. package/src/RedactoPrivacyCenter/table/index.ts +2 -0
  129. package/src/RedactoPrivacyCenter/types/images.d.ts +29 -0
  130. package/src/RedactoPrivacyCenter/ui/Footer.tsx +75 -0
  131. package/src/RedactoPrivacyCenter/ui/FormBlockWrapper.tsx +54 -0
  132. package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +93 -0
  133. package/src/RedactoPrivacyCenter/ui/PCActionIcon.tsx +47 -0
  134. package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +69 -0
  135. package/src/RedactoPrivacyCenter/ui/PCBadge.tsx +48 -0
  136. package/src/RedactoPrivacyCenter/ui/PCButton.tsx +77 -0
  137. package/src/RedactoPrivacyCenter/ui/PCCheckbox.tsx +55 -0
  138. package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +107 -0
  139. package/src/RedactoPrivacyCenter/ui/PCEmpty.tsx +35 -0
  140. package/src/RedactoPrivacyCenter/ui/PCInput.tsx +132 -0
  141. package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +31 -0
  142. package/src/RedactoPrivacyCenter/ui/PCModal.tsx +106 -0
  143. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +173 -0
  144. package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +387 -0
  145. package/src/RedactoPrivacyCenter/ui/PCTable.tsx +149 -0
  146. package/src/RedactoPrivacyCenter/ui/PCText.tsx +41 -0
  147. package/src/RedactoPrivacyCenter/ui/PCTextarea.tsx +43 -0
  148. package/src/RedactoPrivacyCenter/ui/PCTitle.tsx +37 -0
  149. package/src/RedactoPrivacyCenter/ui/PCTooltip.tsx +119 -0
  150. package/src/RedactoPrivacyCenter/ui/SimpleBar.tsx +25 -0
  151. package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +53 -0
  152. package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +72 -0
  153. package/src/RedactoPrivacyCenter/ui/index.ts +18 -0
  154. package/src/RedactoPrivacyCenter/utils/cn.ts +28 -0
  155. package/src/RedactoPrivacyCenter/utils/date.ts +48 -0
  156. package/src/RedactoPrivacyCenter/utils/status.ts +111 -0
  157. package/src/RedactoPrivacyCenter/utils/toast.ts +130 -0
  158. package/src/privacy-center.ts +108 -0
  159. package/tsconfig.json +2 -0
  160. package/tsup.config.ts +16 -1
  161. package/.turbo/turbo-build.log +0 -21
@@ -0,0 +1,141 @@
1
+ import { useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { createColumnHelper } from '@tanstack/react-table';
4
+ import { PCText, PCTooltip } from '../ui';
5
+ import { pcStyles } from '../styles/pcStyles';
6
+ import dayjs from 'dayjs';
7
+ import relativeTime from 'dayjs/plugin/relativeTime';
8
+ import type { Activity } from '../lib/types';
9
+ import StatusBadge from '../ui/StatusBadge';
10
+ import { getActivityTypeStatus, formatActivityType } from '../lib/utils';
11
+ import {
12
+ ACTIVITY_COLUMN_SIZES,
13
+ ACTIVITY_DESCRIPTION_TRUNCATE_LENGTH,
14
+ ACTIVITY_DATE_FORMATS,
15
+ } from '../lib/constants';
16
+
17
+ dayjs.extend(relativeTime);
18
+
19
+ const columnHelper = createColumnHelper<Activity>();
20
+
21
+ export const useActivitiesColumns = () => {
22
+ const { t } = useTranslation();
23
+ return useMemo(
24
+ () => [
25
+ columnHelper.accessor('title', {
26
+ id: 'title',
27
+ minSize: ACTIVITY_COLUMN_SIZES.TITLE,
28
+ header: t('activity'),
29
+ cell: (info) => {
30
+ const row = info.row.original;
31
+ const title = row.title_display || info.getValue();
32
+ return (
33
+ <div style={{ paddingTop: pcStyles.pySm.paddingTop, paddingBottom: pcStyles.pySm.paddingBottom }}>
34
+ <PCText size="sm" weight="medium" color="primary">
35
+ {title}
36
+ </PCText>
37
+ </div>
38
+ );
39
+ },
40
+ enableSorting: false,
41
+ enableColumnFilter: false,
42
+ }),
43
+ columnHelper.accessor('activity_type', {
44
+ id: 'activity_type',
45
+ minSize: ACTIVITY_COLUMN_SIZES.ACTIVITY_TYPE,
46
+ header: t('activityType'),
47
+ cell: (info) => {
48
+ const activityType = info.getValue();
49
+ const row = info.row.original;
50
+ const displayType = row.activity_type_display || formatActivityType(activityType);
51
+ const status = getActivityTypeStatus(activityType);
52
+
53
+ return (
54
+ <div style={{ paddingTop: pcStyles.pySm.paddingTop, paddingBottom: pcStyles.pySm.paddingBottom }}>
55
+ <StatusBadge status={status} className="pc-status-badge">
56
+ {displayType}
57
+ </StatusBadge>
58
+ </div>
59
+ );
60
+ },
61
+ enableSorting: false,
62
+ enableColumnFilter: false,
63
+ }),
64
+ columnHelper.accessor('description', {
65
+ id: 'description',
66
+ minSize: ACTIVITY_COLUMN_SIZES.DESCRIPTION,
67
+ header: t('activityDescription'),
68
+ cell: (info) => {
69
+ const row = info.row.original;
70
+ const description = row.description_display || info.getValue();
71
+ const truncatedDescription =
72
+ description.length > ACTIVITY_DESCRIPTION_TRUNCATE_LENGTH
73
+ ? `${description.substring(0, ACTIVITY_DESCRIPTION_TRUNCATE_LENGTH)}...`
74
+ : description;
75
+
76
+ return (
77
+ <div style={{ paddingTop: pcStyles.pySm.paddingTop, paddingBottom: pcStyles.pySm.paddingBottom }}>
78
+ {description.length > ACTIVITY_DESCRIPTION_TRUNCATE_LENGTH ? (
79
+ <PCTooltip content={description} placement="top">
80
+ <span style={{ display: 'block', cursor: 'help' }}>
81
+ <PCText size="sm" color="secondary" style={{
82
+ display: '-webkit-box',
83
+ WebkitLineClamp: 2,
84
+ WebkitBoxOrient: 'vertical',
85
+ overflow: 'hidden',
86
+ }}>
87
+ {truncatedDescription}
88
+ </PCText>
89
+ </span>
90
+ </PCTooltip>
91
+ ) : (
92
+ <PCText size="sm" color="secondary">
93
+ {description}
94
+ </PCText>
95
+ )}
96
+ </div>
97
+ );
98
+ },
99
+ enableSorting: false,
100
+ enableColumnFilter: false,
101
+ }),
102
+ columnHelper.accessor('timestamp', {
103
+ id: 'timestamp',
104
+ minSize: ACTIVITY_COLUMN_SIZES.TIMESTAMP,
105
+ header: t('timestamp'),
106
+ cell: (info) => {
107
+ const timestamp = info.getValue();
108
+ const date = dayjs(timestamp);
109
+ const formattedDateTime = date.format(ACTIVITY_DATE_FORMATS.DISPLAY);
110
+ const relativeTime = date.fromNow();
111
+
112
+ return (
113
+ <div style={{ paddingTop: pcStyles.pySm.paddingTop, paddingBottom: pcStyles.pySm.paddingBottom }}>
114
+ <PCTooltip
115
+ content={date.format(ACTIVITY_DATE_FORMATS.TOOLTIP)}
116
+ placement="top"
117
+ >
118
+ <div style={{
119
+ ...pcStyles.flex,
120
+ ...pcStyles.flexCol,
121
+ minWidth: '120px',
122
+ gap: '0.125rem',
123
+ }}>
124
+ <PCText size="sm" weight="medium" color="primary">
125
+ {formattedDateTime}
126
+ </PCText>
127
+ <PCText size="sm" color="secondary" style={{ fontSize: '0.75rem' }}>
128
+ {relativeTime}
129
+ </PCText>
130
+ </div>
131
+ </PCTooltip>
132
+ </div>
133
+ );
134
+ },
135
+ enableSorting: false,
136
+ enableColumnFilter: false,
137
+ }),
138
+ ],
139
+ [t]
140
+ );
141
+ };
@@ -0,0 +1,159 @@
1
+ import { useMemo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { PCText, PCActionIcon, PCTooltip, PCBadge } from '../ui';
4
+ import { pcStyles } from '../styles/pcStyles';
5
+ import { FiDownload } from 'react-icons/fi';
6
+ import { createColumnHelper } from '@tanstack/react-table';
7
+ import type { CaseRequest } from '../lib/types';
8
+ import { formatDate } from './utils';
9
+
10
+ const getVar = (varName: string): string => `var(${varName})`;
11
+ const columnHelper = createColumnHelper<CaseRequest>();
12
+
13
+ const getCaseStatusVariant = (status: string): 'success' | 'error' | 'warning' | 'info' | 'secondary' => {
14
+ const n = status.toLowerCase().replace(/_/g, '');
15
+ if (n === 'completed') return 'success';
16
+ if (n === 'rejected' || n === 'declined') return 'error';
17
+ if (n === 'processing' || n === 'inprogress' || n === 'expired') return 'warning';
18
+ if (n === 'initiated') return 'info';
19
+ return 'secondary';
20
+ };
21
+ const formatCaseStatus = (s: string) =>
22
+ s.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
23
+
24
+ export const useCaseHistoryColumns = () => {
25
+ const { t } = useTranslation();
26
+ return useMemo(
27
+ () => [
28
+ columnHelper.accessor('case_id', {
29
+ id: 'case_id',
30
+ size: 150,
31
+ minSize: 120,
32
+ header: t('caseID'),
33
+ meta: { align: 'left' },
34
+ cell: (info) => (
35
+ <div style={{ width: '100%', minWidth: '100px', textAlign: 'left' }}>
36
+ <PCText size="sm" weight="medium" color="primary">
37
+ {info.getValue()}
38
+ </PCText>
39
+ <PCText
40
+ size="sm"
41
+ color="secondary"
42
+ style={{
43
+ marginTop: pcStyles.mtXs.marginTop,
44
+ display: 'block',
45
+ fontSize: '0.75rem',
46
+ }}
47
+ >
48
+ {formatDate(info.row.original.created_at)}
49
+ </PCText>
50
+ </div>
51
+ ),
52
+ enableSorting: false,
53
+ enableColumnFilter: false,
54
+ }),
55
+ columnHelper.accessor('request_type', {
56
+ id: 'request_type',
57
+ size: 150,
58
+ minSize: 120,
59
+ header: t('requestType'),
60
+ meta: { align: 'left' },
61
+ cell: (info) => {
62
+ const row = info.row.original;
63
+ const displayValue = row.request_type_display || info.getValue().replace('_', ' ');
64
+ return (
65
+ <PCText
66
+ size="sm"
67
+ style={{
68
+ textAlign: 'left',
69
+ textTransform: row.request_type_display ? 'none' : 'capitalize',
70
+ width: '100%',
71
+ }}
72
+ >
73
+ {displayValue}
74
+ </PCText>
75
+ );
76
+ },
77
+ enableSorting: false,
78
+ enableColumnFilter: false,
79
+ }),
80
+ columnHelper.accessor('status', {
81
+ id: 'status',
82
+ size: 120,
83
+ minSize: 100,
84
+ header: t('status'),
85
+ meta: { align: 'left' },
86
+ cell: (info) => {
87
+ const row = info.row.original;
88
+ const displayStatus = row.status_display || formatCaseStatus(info.getValue());
89
+ return (
90
+ <div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.justifyStart, width: '100%' }}>
91
+ <PCBadge variant={getCaseStatusVariant(info.getValue())} size="sm" className="pc-status-badge">
92
+ {displayStatus}
93
+ </PCBadge>
94
+ </div>
95
+ );
96
+ },
97
+ enableSorting: false,
98
+ enableColumnFilter: false,
99
+ }),
100
+ columnHelper.accessor('created_at', {
101
+ id: 'created_at',
102
+ size: 180,
103
+ minSize: 140,
104
+ header: t('submittedOn'),
105
+ meta: { align: 'left' },
106
+ cell: (info) => (
107
+ <PCText
108
+ size="sm"
109
+ color="secondary"
110
+ style={{
111
+ textAlign: 'left',
112
+ fontSize: '0.875rem',
113
+ width: '100%',
114
+ }}
115
+ >
116
+ {formatDate(info.getValue())}
117
+ </PCText>
118
+ ),
119
+ enableSorting: false,
120
+ enableColumnFilter: false,
121
+ }),
122
+ columnHelper.display({
123
+ id: 'action',
124
+ size: 100,
125
+ minSize: 80,
126
+ header: t('action'),
127
+ meta: { align: 'right' },
128
+ enableSorting: false,
129
+ enableColumnFilter: false,
130
+ cell: () => {
131
+ return (
132
+ <div style={{
133
+ ...pcStyles.flex,
134
+ ...pcStyles.justifyEnd,
135
+ ...pcStyles.gapXs,
136
+ width: '100%',
137
+ }}>
138
+ <PCTooltip content={t('downloadResponse')}>
139
+ <PCActionIcon
140
+ variant="outline"
141
+ size="sm"
142
+ disabled={true}
143
+ style={{
144
+ color: getVar('--pc-text-secondary'),
145
+ opacity: 0.5,
146
+ }}
147
+ aria-label={t('downloadResponse')}
148
+ >
149
+ <FiDownload size={16} />
150
+ </PCActionIcon>
151
+ </PCTooltip>
152
+ </div>
153
+ );
154
+ },
155
+ }),
156
+ ],
157
+ [t]
158
+ );
159
+ };
@@ -0,0 +1,16 @@
1
+ export const GRIEVANCE_TAB_IDS = {
2
+ ALL: 'all',
3
+ PROCESSING: 'processing',
4
+ COMPLETED: 'completed',
5
+ REJECTED: 'rejected',
6
+ } as const;
7
+
8
+ // Re-export shared table configuration
9
+ export {
10
+ PAGE_SIZE_OPTIONS,
11
+ INITIAL_PAGE,
12
+ INITIAL_PAGE_SIZE,
13
+ } from '../config/table';
14
+
15
+
16
+ export const MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL = 3;
@@ -0,0 +1,76 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { CaseRequest, UserConsent } from '../lib/types';
3
+
4
+ export type TableColumnAlign = 'left' | 'center' | 'right';
5
+
6
+ export type TableColumnMeta = {
7
+ align?: TableColumnAlign;
8
+ };
9
+
10
+ export type PrivacyCenterLayoutProps = {
11
+ children: ReactNode;
12
+ sidebarClassName?: string;
13
+ contentClassName?: string;
14
+ onBack?: 'back' | 'signout';
15
+ };
16
+
17
+ export type CaseHistoryProps = {
18
+ onCreateRequest: () => void;
19
+ isPopup?: boolean;
20
+ onClose?: () => void;
21
+ };
22
+
23
+ export type CaseDetailsModalProps = {
24
+ isOpen: boolean;
25
+ onClose: () => void;
26
+ selectedCase: CaseRequest | null;
27
+ };
28
+
29
+ export type RequestSubmitProps = Readonly<{
30
+ caseId: string;
31
+ contact: string;
32
+ onCreateRequest: () => void;
33
+ revokeConsentOnFulfilment?: boolean;
34
+ revokedPurposeNames?: string;
35
+ }>;
36
+
37
+ export type PaginationProps = {
38
+ currentPage: number;
39
+ totalPages: number;
40
+ totalRecords: number;
41
+ pageSize: number;
42
+ onPageChange: (page: number) => void;
43
+ onPageSizeChange: (size: number) => void;
44
+ className?: string;
45
+ };
46
+
47
+ export type StatusFilterOption = {
48
+ label: string;
49
+ value: string;
50
+ };
51
+
52
+ export type ProductFilterOption = {
53
+ label: string;
54
+ value: string;
55
+ };
56
+
57
+ export type ConsentManagerProps = {
58
+ variant?: 'modern' | 'minimal';
59
+ };
60
+
61
+ export type ModifyConsentModalProps = {
62
+ isOpen: boolean;
63
+ onClose: () => void;
64
+ selectedRecord: UserConsent | null;
65
+ isLoading: boolean;
66
+ onManageConsent: (
67
+ purposeId: string,
68
+ action: import('../lib/types').ConsentAction,
69
+ nominatorContact?: string,
70
+ dataElementUuids?: string[]
71
+ ) => void;
72
+ };
73
+
74
+ export type UseConsentManagerColumnsProps = {
75
+ onOpenModal: (record: UserConsent) => void;
76
+ };
@@ -0,0 +1,10 @@
1
+ import { getStatusColor as getSharedStatusColor } from '../utils/status';
2
+ import { formatDateShort } from '../utils/date';
3
+
4
+ // Re-export shared utilities with case-specific naming
5
+ export const getStatusColor = (status: string) => {
6
+ return getSharedStatusColor({ status });
7
+ };
8
+
9
+ // Re-export shared date formatting
10
+ export const formatDate = formatDateShort;
@@ -0,0 +1,9 @@
1
+ export const routes = {
2
+ cmp: {
3
+ privacyCenter: {
4
+ home: '/privacy-center',
5
+ signIn: (slug: string) => `/privacy-center/${slug}/sign-in`,
6
+ dashboard: (slug: string) => `/privacy-center/${slug}/home`,
7
+ },
8
+ },
9
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Unified table and pagination configuration constants
3
+ */
4
+
5
+ export const DEFAULT_PAGE = 1;
6
+ export const DEFAULT_PAGE_SIZE = 10;
7
+ export const INITIAL_PAGE = 1;
8
+ export const INITIAL_PAGE_SIZE = 10;
9
+
10
+ export const PAGE_SIZE_OPTIONS = [
11
+ { value: 10, label: '10' },
12
+ { value: 15, label: '15' },
13
+ { value: 20, label: '20' },
14
+ { value: 25, label: '25' },
15
+ { value: 50, label: '50' },
16
+ { value: 100, label: '100' },
17
+ ] as const;
18
+
19
+ export const TABLE_CONFIG = {
20
+ pageSize: DEFAULT_PAGE_SIZE,
21
+ maxDisplayPurposes: 3,
22
+ } as const;
23
+
24
+ export const SEARCH_DEBOUNCE_DELAY = 500;
25
+ export const SKELETON_ROW_COUNT = 5;
@@ -0,0 +1,137 @@
1
+ import { createContext, useContext, useState, useEffect, useCallback, useMemo, type ReactNode } from 'react';
2
+ import { setRefreshTokenCookie, decodeJwtToken } from '../lib/utils';
3
+ import type { AuthContextValue } from './types';
4
+
5
+ export type { AuthContextValue } from './types';
6
+
7
+ const AuthContext = createContext<AuthContextValue | null>(null);
8
+
9
+ interface AuthProviderProps {
10
+ children: ReactNode;
11
+ accessToken: string;
12
+ refreshToken: string;
13
+ onError: (error: Error) => boolean;
14
+ }
15
+
16
+ /**
17
+ * Extract and store organization/auth data from JWT token to sessionStorage
18
+ * This makes the SDK self-sufficient - it doesn't depend on host app to set these values
19
+ */
20
+ const syncJwtDataToSessionStorage = (accessToken: string): void => {
21
+ if (typeof window === 'undefined') return;
22
+
23
+ const decoded = decodeJwtToken(accessToken);
24
+ if (!decoded) {
25
+ return;
26
+ }
27
+
28
+ // Extract email from JWT (contact or primary_email), fall back to existing sources
29
+ const email = decoded.contact || decoded.primary_email
30
+ || sessionStorage.getItem('email')
31
+ || (typeof localStorage !== 'undefined' ? localStorage.getItem('userEmail') : null);
32
+ if (email && typeof email === 'string') {
33
+ sessionStorage.setItem('email', email);
34
+ }
35
+
36
+ // Extract organization UUID
37
+ const orgUuid = decoded.organisation_uuid;
38
+ if (orgUuid && typeof orgUuid === 'string') {
39
+ sessionStorage.setItem('pc_org_id', orgUuid);
40
+ }
41
+
42
+ // Extract workspace UUID
43
+ const workspaceUuid = decoded.workspace_uuid;
44
+ if (workspaceUuid && typeof workspaceUuid === 'string') {
45
+ sessionStorage.setItem('pc_workspace_id', workspaceUuid);
46
+ }
47
+
48
+ // Extract organization slug
49
+ const orgSlug = decoded.organisation_slug;
50
+ if (orgSlug && typeof orgSlug === 'string') {
51
+ sessionStorage.setItem('pc_org_slug', orgSlug);
52
+ }
53
+ };
54
+
55
+ export const AuthProvider = ({
56
+ children,
57
+ accessToken: initialAccessToken,
58
+ refreshToken: initialRefreshToken,
59
+ onError,
60
+ }: AuthProviderProps) => {
61
+ // Sync to sessionStorage synchronously during initialization so child
62
+ // components (ConsentManager, SideBarPC) find the data when they mount.
63
+ // useEffect runs AFTER children render — too late for components that
64
+ // read sessionStorage during their render phase.
65
+ const [accessToken, setAccessToken] = useState(() => {
66
+ if (initialAccessToken && typeof initialAccessToken === 'string' && initialAccessToken.trim() !== '') {
67
+ if (typeof window !== 'undefined') {
68
+ sessionStorage.setItem('access_token', initialAccessToken);
69
+ syncJwtDataToSessionStorage(initialAccessToken);
70
+ }
71
+ }
72
+ return initialAccessToken;
73
+ });
74
+ const [refreshToken, setRefreshToken] = useState(() => {
75
+ if (initialRefreshToken && typeof initialRefreshToken === 'string' && initialRefreshToken.trim() !== '') {
76
+ if (typeof window !== 'undefined') {
77
+ setRefreshTokenCookie(initialRefreshToken);
78
+ }
79
+ }
80
+ return initialRefreshToken;
81
+ });
82
+
83
+ // Re-sync when props change (for token refresh scenarios)
84
+ useEffect(() => {
85
+ if (initialAccessToken && typeof initialAccessToken === 'string' && initialAccessToken.trim() !== '') {
86
+ setAccessToken(initialAccessToken);
87
+ if (typeof window !== 'undefined') {
88
+ sessionStorage.setItem('access_token', initialAccessToken);
89
+ syncJwtDataToSessionStorage(initialAccessToken);
90
+ }
91
+ }
92
+ }, [initialAccessToken]);
93
+
94
+ useEffect(() => {
95
+ if (initialRefreshToken && typeof initialRefreshToken === 'string' && initialRefreshToken.trim() !== '') {
96
+ setRefreshToken(initialRefreshToken);
97
+ if (typeof window !== 'undefined') {
98
+ setRefreshTokenCookie(initialRefreshToken);
99
+ }
100
+ }
101
+ }, [initialRefreshToken]);
102
+
103
+ // Internal method to update tokens after refresh
104
+ const updateTokens = useCallback((newAccessToken: string, newRefreshToken?: string) => {
105
+ setAccessToken(newAccessToken);
106
+ if (newRefreshToken) {
107
+ setRefreshToken(newRefreshToken);
108
+ }
109
+ // Also update storage
110
+ if (typeof window !== 'undefined') {
111
+ sessionStorage.setItem('access_token', newAccessToken);
112
+ // Extract and store org/auth data from JWT
113
+ syncJwtDataToSessionStorage(newAccessToken);
114
+ if (newRefreshToken) {
115
+ setRefreshTokenCookie(newRefreshToken);
116
+ }
117
+ }
118
+ }, []);
119
+
120
+ // Memoize context value to prevent unnecessary re-renders
121
+ const value: AuthContextValue = useMemo(() => ({
122
+ accessToken,
123
+ refreshToken,
124
+ onError,
125
+ updateTokens,
126
+ }), [accessToken, refreshToken, onError, updateTokens]);
127
+
128
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
129
+ };
130
+
131
+ export const useAuth = (): AuthContextValue => {
132
+ const context = useContext(AuthContext);
133
+ if (!context) {
134
+ throw new Error('useAuth must be used within an AuthProvider');
135
+ }
136
+ return context;
137
+ };
@@ -0,0 +1,41 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from 'react';
2
+ import { createConsentClient } from '../api/client';
3
+ import type { BaseUrlContextValue } from './types';
4
+
5
+ export type { BaseUrlContextValue } from './types';
6
+
7
+ export const BaseUrlContext = createContext<BaseUrlContextValue | null>(null);
8
+
9
+ export const BaseUrlProvider = ({
10
+ children,
11
+ baseUrl,
12
+ }: {
13
+ children: ReactNode;
14
+ baseUrl: string; // Consent server base URL
15
+ }) => {
16
+ const consentClient = useMemo(() => createConsentClient(baseUrl), [baseUrl]);
17
+
18
+ const value: BaseUrlContextValue = useMemo(
19
+ () => ({
20
+ baseUrl,
21
+ consentClient,
22
+ }),
23
+ [baseUrl, consentClient]
24
+ );
25
+
26
+ return (
27
+ <BaseUrlContext.Provider value={value}>
28
+ {children}
29
+ </BaseUrlContext.Provider>
30
+ );
31
+ };
32
+
33
+ export const useBaseUrl = () => {
34
+ const context = useContext(BaseUrlContext);
35
+ if (!context) {
36
+ throw new Error(
37
+ 'useBaseUrl must be used within a BaseUrlProvider'
38
+ );
39
+ }
40
+ return context;
41
+ };
@@ -0,0 +1,11 @@
1
+ import { createContext } from 'react';
2
+
3
+ type LocaleContextType = {
4
+ locale: string;
5
+ setLocale: (locale: string) => void;
6
+ };
7
+
8
+ export const LocaleContext = createContext<LocaleContextType>({
9
+ locale: 'en',
10
+ setLocale: () => {},
11
+ });