@redacto.io/consent-sdk-react 3.5.0 → 3.6.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 (152) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +176 -0
  3. package/dist/index.d.mts +51 -4
  4. package/dist/index.d.ts +51 -4
  5. package/dist/index.js +1891 -243
  6. package/dist/index.mjs +1890 -243
  7. package/dist/privacy-center.d.mts +566 -0
  8. package/dist/privacy-center.d.ts +566 -0
  9. package/dist/privacy-center.js +17174 -0
  10. package/dist/privacy-center.mjs +17136 -0
  11. package/package.json +47 -3
  12. package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +201 -118
  13. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +1194 -93
  14. package/src/RedactoNoticeConsent/api/index.ts +335 -206
  15. package/src/RedactoNoticeConsent/api/types.ts +140 -16
  16. package/src/RedactoNoticeConsent/components/DigiLockerCallback.tsx +602 -0
  17. package/src/RedactoNoticeConsent/components/index.ts +7 -0
  18. package/src/RedactoNoticeConsent/injectStyles.ts +11 -0
  19. package/src/RedactoNoticeConsent/language-codes.ts +61 -0
  20. package/src/RedactoNoticeConsent/services/storage.ts +122 -0
  21. package/src/RedactoNoticeConsent/types.ts +2 -0
  22. package/src/RedactoPrivacyCenter/PrivacyCenterActivityPage.tsx +19 -0
  23. package/src/RedactoPrivacyCenter/PrivacyCenterConsentManagerPage.tsx +17 -0
  24. package/src/RedactoPrivacyCenter/PrivacyCenterFormPage.tsx +79 -0
  25. package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +27 -0
  26. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +142 -0
  27. package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +51 -0
  28. package/src/RedactoPrivacyCenter/api/actions.ts +444 -0
  29. package/src/RedactoPrivacyCenter/api/client.ts +413 -0
  30. package/src/RedactoPrivacyCenter/api/config.ts +33 -0
  31. package/src/RedactoPrivacyCenter/api/fetcher.ts +181 -0
  32. package/src/RedactoPrivacyCenter/api/upload.ts +93 -0
  33. package/src/RedactoPrivacyCenter/assets/character.svg +31 -0
  34. package/src/RedactoPrivacyCenter/assets/fileAttach.svg +9 -0
  35. package/src/RedactoPrivacyCenter/assets/greenTick.svg +4 -0
  36. package/src/RedactoPrivacyCenter/assets/mail.svg +3 -0
  37. package/src/RedactoPrivacyCenter/assets/poweredByRedacto.svg +9 -0
  38. package/src/RedactoPrivacyCenter/assets/sentImg.svg +9 -0
  39. package/src/RedactoPrivacyCenter/assets/uploadFileIcon.svg +9 -0
  40. package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +272 -0
  41. package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +468 -0
  42. package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +433 -0
  43. package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +726 -0
  44. package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +239 -0
  45. package/src/RedactoPrivacyCenter/components/ConsentManagerConstants.ts +7 -0
  46. package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +155 -0
  47. package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +189 -0
  48. package/src/RedactoPrivacyCenter/components/DataRequest/GrievanceDataSection.tsx +118 -0
  49. package/src/RedactoPrivacyCenter/components/DataRequest/NominationDataSection.tsx +66 -0
  50. package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +305 -0
  51. package/src/RedactoPrivacyCenter/components/Form.tsx +1455 -0
  52. package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +176 -0
  53. package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +330 -0
  54. package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +329 -0
  55. package/src/RedactoPrivacyCenter/components/Pagination.tsx +140 -0
  56. package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +302 -0
  57. package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +218 -0
  58. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +475 -0
  59. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideMenuPC.tsx +412 -0
  60. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +42 -0
  61. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/menuItemsPC.tsx +1 -0
  62. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +40 -0
  63. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/utils.ts +38 -0
  64. package/src/RedactoPrivacyCenter/components/ThemeTransitionOverlay.tsx +85 -0
  65. package/src/RedactoPrivacyCenter/components/activitiesColumns.tsx +141 -0
  66. package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +179 -0
  67. package/src/RedactoPrivacyCenter/components/constant.ts +20 -0
  68. package/src/RedactoPrivacyCenter/components/types.ts +68 -0
  69. package/src/RedactoPrivacyCenter/components/utils.tsx +10 -0
  70. package/src/RedactoPrivacyCenter/config/routes.ts +9 -0
  71. package/src/RedactoPrivacyCenter/config/table.ts +25 -0
  72. package/src/RedactoPrivacyCenter/context/AuthContext.tsx +123 -0
  73. package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +44 -0
  74. package/src/RedactoPrivacyCenter/context/LocaleContext.tsx +11 -0
  75. package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +64 -0
  76. package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +30 -0
  77. package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +60 -0
  78. package/src/RedactoPrivacyCenter/context/theme.tsx +127 -0
  79. package/src/RedactoPrivacyCenter/lib/api.ts +468 -0
  80. package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +63 -0
  81. package/src/RedactoPrivacyCenter/lib/constants.ts +116 -0
  82. package/src/RedactoPrivacyCenter/lib/i18n.ts +81 -0
  83. package/src/RedactoPrivacyCenter/lib/locales.ts +105 -0
  84. package/src/RedactoPrivacyCenter/lib/queries.ts +30 -0
  85. package/src/RedactoPrivacyCenter/lib/types.ts +346 -0
  86. package/src/RedactoPrivacyCenter/lib/useCurrentLanguage.ts +37 -0
  87. package/src/RedactoPrivacyCenter/lib/utils.ts +625 -0
  88. package/src/RedactoPrivacyCenter/locales/as/translation.json +188 -0
  89. package/src/RedactoPrivacyCenter/locales/bho/translation.json +188 -0
  90. package/src/RedactoPrivacyCenter/locales/bn/translation.json +188 -0
  91. package/src/RedactoPrivacyCenter/locales/brx/translation.json +188 -0
  92. package/src/RedactoPrivacyCenter/locales/doi/translation.json +188 -0
  93. package/src/RedactoPrivacyCenter/locales/en/translation.json +188 -0
  94. package/src/RedactoPrivacyCenter/locales/gom/translation.json +188 -0
  95. package/src/RedactoPrivacyCenter/locales/gu/translation.json +188 -0
  96. package/src/RedactoPrivacyCenter/locales/hi/translation.json +188 -0
  97. package/src/RedactoPrivacyCenter/locales/kn/translation.json +188 -0
  98. package/src/RedactoPrivacyCenter/locales/ks/translation.json +188 -0
  99. package/src/RedactoPrivacyCenter/locales/mai/translation.json +188 -0
  100. package/src/RedactoPrivacyCenter/locales/ml/translation.json +188 -0
  101. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +188 -0
  102. package/src/RedactoPrivacyCenter/locales/mr/translation.json +188 -0
  103. package/src/RedactoPrivacyCenter/locales/ne/translation.json +188 -0
  104. package/src/RedactoPrivacyCenter/locales/or/translation.json +188 -0
  105. package/src/RedactoPrivacyCenter/locales/pa/translation.json +188 -0
  106. package/src/RedactoPrivacyCenter/locales/sa/translation.json +188 -0
  107. package/src/RedactoPrivacyCenter/locales/sat/translation.json +188 -0
  108. package/src/RedactoPrivacyCenter/locales/sd/translation.json +188 -0
  109. package/src/RedactoPrivacyCenter/locales/ta/translation.json +188 -0
  110. package/src/RedactoPrivacyCenter/locales/te/translation.json +188 -0
  111. package/src/RedactoPrivacyCenter/locales/ur/translation.json +188 -0
  112. package/src/RedactoPrivacyCenter/styles/base.css +117 -0
  113. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +1000 -0
  114. package/src/RedactoPrivacyCenter/styles/pcStyles.ts +819 -0
  115. package/src/RedactoPrivacyCenter/styles/responsive.ts +138 -0
  116. package/src/RedactoPrivacyCenter/styles/variables.css +133 -0
  117. package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +365 -0
  118. package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +479 -0
  119. package/src/RedactoPrivacyCenter/table/index.ts +2 -0
  120. package/src/RedactoPrivacyCenter/types/images.d.ts +29 -0
  121. package/src/RedactoPrivacyCenter/ui/Footer.tsx +73 -0
  122. package/src/RedactoPrivacyCenter/ui/FormBlockWrapper.tsx +54 -0
  123. package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +87 -0
  124. package/src/RedactoPrivacyCenter/ui/PCActionIcon.tsx +47 -0
  125. package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +69 -0
  126. package/src/RedactoPrivacyCenter/ui/PCBadge.tsx +48 -0
  127. package/src/RedactoPrivacyCenter/ui/PCButton.tsx +77 -0
  128. package/src/RedactoPrivacyCenter/ui/PCCheckbox.tsx +55 -0
  129. package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +97 -0
  130. package/src/RedactoPrivacyCenter/ui/PCEmpty.tsx +35 -0
  131. package/src/RedactoPrivacyCenter/ui/PCInput.tsx +128 -0
  132. package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +30 -0
  133. package/src/RedactoPrivacyCenter/ui/PCModal.tsx +104 -0
  134. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +175 -0
  135. package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +381 -0
  136. package/src/RedactoPrivacyCenter/ui/PCTable.tsx +137 -0
  137. package/src/RedactoPrivacyCenter/ui/PCText.tsx +39 -0
  138. package/src/RedactoPrivacyCenter/ui/PCTextarea.tsx +43 -0
  139. package/src/RedactoPrivacyCenter/ui/PCTitle.tsx +37 -0
  140. package/src/RedactoPrivacyCenter/ui/PCTooltip.tsx +119 -0
  141. package/src/RedactoPrivacyCenter/ui/SimpleBar.tsx +25 -0
  142. package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +55 -0
  143. package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +70 -0
  144. package/src/RedactoPrivacyCenter/ui/index.ts +18 -0
  145. package/src/RedactoPrivacyCenter/utils/cn.ts +28 -0
  146. package/src/RedactoPrivacyCenter/utils/date.ts +48 -0
  147. package/src/RedactoPrivacyCenter/utils/status.ts +111 -0
  148. package/src/RedactoPrivacyCenter/utils/toast.ts +130 -0
  149. package/src/index.ts +10 -0
  150. package/src/privacy-center.ts +107 -0
  151. package/tsconfig.json +2 -0
  152. package/tsup.config.ts +16 -1
@@ -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} variant="filled" 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,179 @@
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 { FiEye, 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 type UseCaseHistoryColumnsProps = {
25
+ onViewDetails: (caseRequest: CaseRequest) => void;
26
+ };
27
+
28
+ export const useCaseHistoryColumns = ({
29
+ onViewDetails,
30
+ }: UseCaseHistoryColumnsProps) => {
31
+ const { t } = useTranslation();
32
+ return useMemo(
33
+ () => [
34
+ columnHelper.accessor('case_id', {
35
+ id: 'case_id',
36
+ size: 150,
37
+ minSize: 120,
38
+ header: t('caseID'),
39
+ meta: { align: 'left' },
40
+ cell: (info) => (
41
+ <div style={{ width: '100%', minWidth: '100px', textAlign: 'left' }}>
42
+ <PCText size="sm" weight="medium" color="primary">
43
+ {info.getValue()}
44
+ </PCText>
45
+ <PCText
46
+ size="sm"
47
+ color="secondary"
48
+ style={{
49
+ marginTop: pcStyles.mtXs.marginTop,
50
+ display: 'block',
51
+ fontSize: '0.75rem',
52
+ }}
53
+ >
54
+ {formatDate(info.row.original.created_at)}
55
+ </PCText>
56
+ </div>
57
+ ),
58
+ enableSorting: false,
59
+ enableColumnFilter: false,
60
+ }),
61
+ columnHelper.accessor('request_type', {
62
+ id: 'request_type',
63
+ size: 150,
64
+ minSize: 120,
65
+ header: t('requestType'),
66
+ meta: { align: 'left' },
67
+ cell: (info) => {
68
+ const row = info.row.original;
69
+ const displayValue = row.request_type_display || info.getValue().replace('_', ' ');
70
+ return (
71
+ <PCText
72
+ size="sm"
73
+ style={{
74
+ textAlign: 'left',
75
+ textTransform: row.request_type_display ? 'none' : 'capitalize',
76
+ width: '100%',
77
+ }}
78
+ >
79
+ {displayValue}
80
+ </PCText>
81
+ );
82
+ },
83
+ enableSorting: false,
84
+ enableColumnFilter: false,
85
+ }),
86
+ columnHelper.accessor('status', {
87
+ id: 'status',
88
+ size: 120,
89
+ minSize: 100,
90
+ header: t('status'),
91
+ meta: { align: 'left' },
92
+ cell: (info) => {
93
+ const row = info.row.original;
94
+ const displayStatus = row.status_display || formatCaseStatus(info.getValue());
95
+ return (
96
+ <div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.justifyStart, width: '100%' }}>
97
+ <PCBadge variant={getCaseStatusVariant(info.getValue())} size="sm" className="pc-status-badge">
98
+ {displayStatus}
99
+ </PCBadge>
100
+ </div>
101
+ );
102
+ },
103
+ enableSorting: false,
104
+ enableColumnFilter: false,
105
+ }),
106
+ columnHelper.accessor('created_at', {
107
+ id: 'created_at',
108
+ size: 180,
109
+ minSize: 140,
110
+ header: t('submittedOn'),
111
+ meta: { align: 'left' },
112
+ cell: (info) => (
113
+ <PCText
114
+ size="sm"
115
+ color="secondary"
116
+ style={{
117
+ textAlign: 'left',
118
+ fontSize: '0.875rem',
119
+ width: '100%',
120
+ }}
121
+ >
122
+ {formatDate(info.getValue())}
123
+ </PCText>
124
+ ),
125
+ enableSorting: false,
126
+ enableColumnFilter: false,
127
+ }),
128
+ columnHelper.display({
129
+ id: 'action',
130
+ size: 100,
131
+ minSize: 80,
132
+ header: t('action'),
133
+ meta: { align: 'right' },
134
+ enableSorting: false,
135
+ enableColumnFilter: false,
136
+ cell: ({ row }) => {
137
+ return (
138
+ <div style={{
139
+ ...pcStyles.flex,
140
+ ...pcStyles.justifyEnd,
141
+ ...pcStyles.gapXs,
142
+ width: '100%',
143
+ }}>
144
+ <PCTooltip content={t('viewDetails')}>
145
+ <PCActionIcon
146
+ variant="outline"
147
+ size="sm"
148
+ style={{
149
+ color: getVar('--pc-text-secondary'),
150
+ transition: 'color 0.2s ease',
151
+ }}
152
+ onClick={() => onViewDetails(row.original)}
153
+ aria-label={t('viewDetails')}
154
+ >
155
+ <FiEye size={16} />
156
+ </PCActionIcon>
157
+ </PCTooltip>
158
+ <PCTooltip content={t('downloadResponse')}>
159
+ <PCActionIcon
160
+ variant="outline"
161
+ size="sm"
162
+ disabled={true}
163
+ style={{
164
+ color: getVar('--pc-text-secondary'),
165
+ opacity: 0.5,
166
+ }}
167
+ aria-label={t('downloadResponse')}
168
+ >
169
+ <FiDownload size={16} />
170
+ </PCActionIcon>
171
+ </PCTooltip>
172
+ </div>
173
+ );
174
+ },
175
+ }),
176
+ ],
177
+ [onViewDetails, t]
178
+ );
179
+ };
@@ -0,0 +1,20 @@
1
+ export const TABS = {
2
+ ALL: 'All Requests',
3
+ PROCESSING: 'Processing',
4
+ COMPLETED: 'Completed',
5
+ REJECTED: 'Rejected',
6
+ };
7
+
8
+ export const GRIEVANCE_TAB_IDS = {
9
+ ALL: 'all',
10
+ PROCESSING: 'processing',
11
+ COMPLETED: 'completed',
12
+ REJECTED: 'rejected',
13
+ } as const;
14
+
15
+ // Re-export shared table configuration
16
+ export {
17
+ PAGE_SIZE_OPTIONS,
18
+ INITIAL_PAGE,
19
+ INITIAL_PAGE_SIZE,
20
+ } from '../config/table';
@@ -0,0 +1,68 @@
1
+ import { ReactNode } from 'react';
2
+ import type { CaseRequest, UserConsent } from '../lib/types';
3
+
4
+ export type PrivacyCenterLayoutProps = {
5
+ children: ReactNode;
6
+ sidebarClassName?: string;
7
+ contentClassName?: string;
8
+ onBack?: 'back' | 'signout';
9
+ };
10
+
11
+ export type CaseHistoryProps = {
12
+ onCreateRequest: () => void;
13
+ isPopup?: boolean;
14
+ onClose?: () => void;
15
+ };
16
+
17
+ export type CaseDetailsModalProps = {
18
+ isOpen: boolean;
19
+ onClose: () => void;
20
+ selectedCase: CaseRequest | null;
21
+ };
22
+
23
+ export type RequestSubmitProps = {
24
+ caseId: string;
25
+ contact: string;
26
+ onCreateRequest: () => void;
27
+ };
28
+
29
+ export type PaginationProps = {
30
+ currentPage: number;
31
+ totalPages: number;
32
+ totalRecords: number;
33
+ pageSize: number;
34
+ onPageChange: (page: number) => void;
35
+ onPageSizeChange: (size: number) => void;
36
+ className?: string;
37
+ };
38
+
39
+ export type StatusFilterOption = {
40
+ label: string;
41
+ value: string;
42
+ };
43
+
44
+ export type ProductFilterOption = {
45
+ label: string;
46
+ value: string;
47
+ };
48
+
49
+ export type ConsentManagerProps = {
50
+ variant?: import('rizzui').TableVariantProps;
51
+ };
52
+
53
+ export type ModifyConsentModalProps = {
54
+ isOpen: boolean;
55
+ onClose: () => void;
56
+ selectedRecord: UserConsent | null;
57
+ isLoading: boolean;
58
+ onManageConsent: (
59
+ purposeId: string,
60
+ action: import('../lib/types').ConsentAction,
61
+ nominatorContact?: string,
62
+ dataElementUuids?: string[]
63
+ ) => void;
64
+ };
65
+
66
+ export type UseConsentManagerColumnsProps = {
67
+ onOpenModal: (record: UserConsent) => void;
68
+ };
@@ -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,123 @@
1
+ import React, { createContext, useContext, ReactNode, useState, useEffect, useCallback, useMemo } from 'react';
2
+ import { setRefreshTokenCookie, getCookieValue, decodeJwtToken } from '../lib/utils';
3
+
4
+ export interface AuthContextValue {
5
+ accessToken: string;
6
+ refreshToken: string;
7
+ onError: (error: Error) => boolean;
8
+ // Internal: updates tokens after refresh
9
+ updateTokens: (accessToken: string, refreshToken?: string) => void;
10
+ }
11
+
12
+ const AuthContext = createContext<AuthContextValue | null>(null);
13
+
14
+ interface AuthProviderProps {
15
+ children: ReactNode;
16
+ accessToken: string;
17
+ refreshToken: string;
18
+ onError: (error: Error) => boolean;
19
+ }
20
+
21
+ /**
22
+ * Extract and store organization/auth data from JWT token to sessionStorage
23
+ * This makes the SDK self-sufficient - it doesn't depend on host app to set these values
24
+ */
25
+ const syncJwtDataToSessionStorage = (accessToken: string): void => {
26
+ if (typeof window === 'undefined') return;
27
+
28
+ const decoded = decodeJwtToken(accessToken);
29
+ if (!decoded) {
30
+ return;
31
+ }
32
+
33
+ // Extract email from JWT (contact or primary_email)
34
+ const email = decoded.contact || decoded.primary_email;
35
+ if (email && typeof email === 'string') {
36
+ sessionStorage.setItem('email', email);
37
+ }
38
+
39
+ // Extract organization UUID
40
+ const orgUuid = decoded.organisation_uuid;
41
+ if (orgUuid && typeof orgUuid === 'string') {
42
+ sessionStorage.setItem('pc_org_id', orgUuid);
43
+ }
44
+
45
+ // Extract workspace UUID
46
+ const workspaceUuid = decoded.workspace_uuid;
47
+ if (workspaceUuid && typeof workspaceUuid === 'string') {
48
+ sessionStorage.setItem('pc_workspace_id', workspaceUuid);
49
+ }
50
+
51
+ // Extract organization slug
52
+ const orgSlug = decoded.organisation_slug;
53
+ if (orgSlug && typeof orgSlug === 'string') {
54
+ sessionStorage.setItem('pc_org_slug', orgSlug);
55
+ }
56
+ };
57
+
58
+ export const AuthProvider = ({
59
+ children,
60
+ accessToken: initialAccessToken,
61
+ refreshToken: initialRefreshToken,
62
+ onError,
63
+ }: AuthProviderProps) => {
64
+ const [accessToken, setAccessToken] = useState(initialAccessToken);
65
+ const [refreshToken, setRefreshToken] = useState(initialRefreshToken);
66
+
67
+ // Sync props to state and storage when they change
68
+ // Only sync if tokens are valid non-empty strings
69
+ useEffect(() => {
70
+ if (initialAccessToken && typeof initialAccessToken === 'string' && initialAccessToken.trim() !== '') {
71
+ setAccessToken(initialAccessToken);
72
+ if (typeof window !== 'undefined') {
73
+ sessionStorage.setItem('access_token', initialAccessToken);
74
+ // Extract and store org/auth data from JWT
75
+ syncJwtDataToSessionStorage(initialAccessToken);
76
+ }
77
+ }
78
+ }, [initialAccessToken]);
79
+
80
+ useEffect(() => {
81
+ if (initialRefreshToken && typeof initialRefreshToken === 'string' && initialRefreshToken.trim() !== '') {
82
+ setRefreshToken(initialRefreshToken);
83
+ if (typeof window !== 'undefined') {
84
+ setRefreshTokenCookie(initialRefreshToken);
85
+ }
86
+ }
87
+ }, [initialRefreshToken]);
88
+
89
+ // Internal method to update tokens after refresh
90
+ const updateTokens = useCallback((newAccessToken: string, newRefreshToken?: string) => {
91
+ setAccessToken(newAccessToken);
92
+ if (newRefreshToken) {
93
+ setRefreshToken(newRefreshToken);
94
+ }
95
+ // Also update storage
96
+ if (typeof window !== 'undefined') {
97
+ sessionStorage.setItem('access_token', newAccessToken);
98
+ // Extract and store org/auth data from JWT
99
+ syncJwtDataToSessionStorage(newAccessToken);
100
+ if (newRefreshToken) {
101
+ setRefreshTokenCookie(newRefreshToken);
102
+ }
103
+ }
104
+ }, []);
105
+
106
+ // Memoize context value to prevent unnecessary re-renders
107
+ const value: AuthContextValue = useMemo(() => ({
108
+ accessToken,
109
+ refreshToken,
110
+ onError,
111
+ updateTokens,
112
+ }), [accessToken, refreshToken, onError, updateTokens]);
113
+
114
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
115
+ };
116
+
117
+ export const useAuth = (): AuthContextValue => {
118
+ const context = useContext(AuthContext);
119
+ if (!context) {
120
+ throw new Error('useAuth must be used within an AuthProvider');
121
+ }
122
+ return context;
123
+ };
@@ -0,0 +1,44 @@
1
+ import React, { createContext, useContext, ReactNode, useMemo } from 'react';
2
+ import { createConsentClient } from '../api/client';
3
+ import type { BaseRequest } from '../api/client';
4
+
5
+ export interface BaseUrlContextValue {
6
+ baseUrl: string;
7
+ consentClient: ReturnType<typeof createConsentClient>;
8
+ }
9
+
10
+ export const BaseUrlContext = createContext<BaseUrlContextValue | null>(null);
11
+
12
+ export const BaseUrlProvider = ({
13
+ children,
14
+ baseUrl,
15
+ }: {
16
+ children: ReactNode;
17
+ baseUrl: string; // Consent server base URL
18
+ }) => {
19
+ const consentClient = useMemo(() => createConsentClient(baseUrl), [baseUrl]);
20
+
21
+ const value: BaseUrlContextValue = useMemo(
22
+ () => ({
23
+ baseUrl,
24
+ consentClient,
25
+ }),
26
+ [baseUrl, consentClient]
27
+ );
28
+
29
+ return (
30
+ <BaseUrlContext.Provider value={value}>
31
+ {children}
32
+ </BaseUrlContext.Provider>
33
+ );
34
+ };
35
+
36
+ export const useBaseUrl = () => {
37
+ const context = useContext(BaseUrlContext);
38
+ if (!context) {
39
+ throw new Error(
40
+ 'useBaseUrl must be used within a BaseUrlProvider'
41
+ );
42
+ }
43
+ return context;
44
+ };
@@ -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
+ });