@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,330 @@
1
+ import { useState, useMemo, useEffect } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { PCButton, PCText, PCModal, PCAccordion, PCBadge, PCCheckbox } from '../ui';
4
+ import { pcStyles } from '../styles/pcStyles';
5
+ import { ChevronDownIcon } from 'lucide-react';
6
+ import { PiWarningCircleBold } from 'react-icons/pi';
7
+ import type { ModifyConsentModalProps } from './types';
8
+ import { ConsentStatusEnum } from '../lib/types';
9
+ import type { ConsentAction } from '../lib/types';
10
+
11
+ export const ModifyConsentModal = ({
12
+ isOpen,
13
+ onClose,
14
+ selectedRecord,
15
+ isLoading,
16
+ onManageConsent,
17
+ }: ModifyConsentModalProps) => {
18
+ const { t } = useTranslation();
19
+ const [isAccordionOpen, setIsAccordionOpen] = useState(false);
20
+ const [selectedDataElementUuids, setSelectedDataElementUuids] = useState<string[]>([]);
21
+
22
+ const nominatorInfo = selectedRecord?.nominator_info;
23
+ const nominatorLabel = nominatorInfo?.name || nominatorInfo?.email || nominatorInfo?.org_user_id || null;
24
+
25
+ const actionConfig = useMemo(() => {
26
+ if (!selectedRecord) return null;
27
+
28
+ const status = selectedRecord.status;
29
+
30
+ if (status === ConsentStatusEnum.ACTIVE) {
31
+ return {
32
+ action: 'revoke' as ConsentAction,
33
+ buttonLabel: t('revokeConsent'),
34
+ buttonVariant: 'danger' as const,
35
+ warningMessage: t('revokeConsentWarning'),
36
+ description: nominatorLabel
37
+ ? t('aboutToModifyConsentOnBehalf', { nominator: nominatorLabel })
38
+ : t('aboutToModifyConsent'),
39
+ };
40
+ }
41
+
42
+ if (status === ConsentStatusEnum.WITHDRAWN) {
43
+ return {
44
+ action: 'regrant' as ConsentAction,
45
+ buttonLabel: t('regrantConsent'),
46
+ buttonVariant: 'primary' as const,
47
+ warningMessage: t('regrantConsentWarning'),
48
+ description: nominatorLabel
49
+ ? t('aboutToRegrantConsentOnBehalf', { nominator: nominatorLabel })
50
+ : t('aboutToRegrantConsent'),
51
+ };
52
+ }
53
+
54
+ if (status === ConsentStatusEnum.EXPIRED) {
55
+ return {
56
+ action: 'renew' as ConsentAction,
57
+ buttonLabel: t('renewConsent'),
58
+ buttonVariant: 'primary' as const,
59
+ warningMessage: t('renewConsentWarning'),
60
+ description: nominatorLabel
61
+ ? t('aboutToRenewConsentOnBehalf', { nominator: nominatorLabel })
62
+ : t('aboutToRenewConsent'),
63
+ };
64
+ }
65
+
66
+ // DECLINED or unknown statuses — no action available
67
+ return null;
68
+ }, [selectedRecord, t, nominatorLabel]);
69
+
70
+ const isRegrantAction = actionConfig?.action === 'regrant';
71
+
72
+ useEffect(() => {
73
+ setIsAccordionOpen(isRegrantAction);
74
+ }, [selectedRecord, isRegrantAction]);
75
+
76
+ const regrantEligibleDataElements = useMemo(
77
+ () =>
78
+ (selectedRecord?.data_elements || []).filter(
79
+ (element) => Boolean(element?.uuid) && element.enabled
80
+ ),
81
+ [selectedRecord]
82
+ );
83
+
84
+ const requiredDataElementUuids = useMemo(
85
+ () =>
86
+ regrantEligibleDataElements
87
+ .filter((element) => element.required)
88
+ .map((element) => element.uuid),
89
+ [regrantEligibleDataElements]
90
+ );
91
+
92
+ const requiredDataElementUuidSet = useMemo(
93
+ () => new Set(requiredDataElementUuids),
94
+ [requiredDataElementUuids]
95
+ );
96
+
97
+ useEffect(() => {
98
+ if (isRegrantAction) {
99
+ setSelectedDataElementUuids(regrantEligibleDataElements.map((element) => element.uuid));
100
+ return;
101
+ }
102
+ setSelectedDataElementUuids([]);
103
+ }, [isRegrantAction, regrantEligibleDataElements]);
104
+
105
+ const toggleDataElementSelection = (uuid: string, checked: boolean) => {
106
+ if (requiredDataElementUuidSet.has(uuid)) {
107
+ return;
108
+ }
109
+ setSelectedDataElementUuids((prev) => {
110
+ if (checked) {
111
+ return prev.includes(uuid) ? prev : [...prev, uuid];
112
+ }
113
+ return prev.filter((item) => item !== uuid);
114
+ });
115
+ };
116
+
117
+ return (
118
+ <PCModal
119
+ isOpen={isOpen}
120
+ onClose={onClose}
121
+ closeAriaLabel={t('closeModal')}
122
+ title={
123
+ <PCText size="base" weight="semibold">
124
+ {t('modifyConsent')}
125
+ </PCText>
126
+ }
127
+ >
128
+ <div style={{ padding: pcStyles.pMd.padding }}>
129
+ {selectedRecord && !actionConfig && (
130
+ <PCText size="sm" weight="medium" color="tertiary" style={{ textAlign: 'center', padding: pcStyles.pMd.padding }}>
131
+ {t('noActionsAvailable', 'No actions available for this record.')}
132
+ </PCText>
133
+ )}
134
+ {selectedRecord && actionConfig && (
135
+ <>
136
+ <PCText size="sm" weight="medium" color="secondary" style={{ marginBottom: pcStyles.mbMd.marginBottom }}>
137
+ {actionConfig.description}
138
+ </PCText>
139
+ <div style={{
140
+ marginTop: pcStyles.mtMd.marginTop,
141
+ borderRadius: pcStyles.borderRadiusMd,
142
+ ...pcStyles.bgSecondary,
143
+ border: `1px solid ${pcStyles.borderColor}`,
144
+ padding: pcStyles.pMd.padding,
145
+ ...pcStyles.overflowHidden,
146
+ }}>
147
+ <PCAccordion style={{ border: 'none', borderRadius: 0 }}>
148
+ <PCAccordion.Header
149
+ open={isAccordionOpen}
150
+ onToggle={() => setIsAccordionOpen(!isAccordionOpen)}
151
+ >
152
+ {({ open }) => (
153
+ <>
154
+ <div style={{ flex: 1 }}>
155
+ <p style={{
156
+ marginBottom: pcStyles.mbXs.marginBottom,
157
+ fontSize: pcStyles.textBase.fontSize,
158
+ fontWeight: pcStyles.fontWeightSemibold.fontWeight,
159
+ color: pcStyles.textPrimary.color,
160
+ }}>
161
+ {selectedRecord.purpose}
162
+ </p>
163
+ <p style={{
164
+ textAlign: 'left',
165
+ fontSize: pcStyles.textSm.fontSize,
166
+ fontWeight: pcStyles.fontWeightMedium.fontWeight,
167
+ color: pcStyles.textSecondary.color,
168
+ margin: 0,
169
+ }}>
170
+ {selectedRecord.purpose_description}
171
+ </p>
172
+ </div>
173
+ <ChevronDownIcon
174
+ size={20}
175
+ style={{
176
+ transform: open ? 'rotate(0deg)' : 'rotate(-90deg)',
177
+ transition: 'transform 0.3s',
178
+ color: pcStyles.textSecondary.color,
179
+ flexShrink: 0,
180
+ marginLeft: pcStyles.mlSm.marginLeft,
181
+ }}
182
+ />
183
+ </>
184
+ )}
185
+ </PCAccordion.Header>
186
+
187
+ <PCAccordion.Body open={isAccordionOpen}>
188
+ <div style={{
189
+ marginTop: pcStyles.mtSm.marginTop,
190
+ paddingTop: pcStyles.ptSm.paddingTop,
191
+ ...(!isRegrantAction && { borderTop: `1px solid ${pcStyles.borderColor}` }),
192
+ }}>
193
+ {isRegrantAction && regrantEligibleDataElements.length > 0 ? (
194
+ <div>
195
+ <PCText
196
+ size="sm"
197
+ color="secondary"
198
+ style={{ marginTop: 0, marginBottom: pcStyles.mbSm.marginBottom }}
199
+ >
200
+ {t(
201
+ 'selectDataElementsForRegrant',
202
+ 'Select the data elements you want to regrant consent for.'
203
+ )}
204
+ </PCText>
205
+ <div style={{ display: 'grid', gap: pcStyles.gapSm.gap }}>
206
+ {regrantEligibleDataElements.map((element) => {
207
+ const isRequired = requiredDataElementUuidSet.has(element.uuid);
208
+ const isChecked = isRequired || selectedDataElementUuids.includes(element.uuid);
209
+
210
+ return (
211
+ <PCCheckbox
212
+ key={element.uuid}
213
+ size="md"
214
+ checked={isChecked}
215
+ disabled={isRequired}
216
+ onChange={(event) =>
217
+ toggleDataElementSelection(element.uuid, event.target.checked)
218
+ }
219
+ label={
220
+ isRequired
221
+ ? `${element.name} (${t('mandatory', 'Mandatory')})`
222
+ : element.name
223
+ }
224
+ labelClassName="text-sm"
225
+ style={{ accentColor: pcStyles.primary }}
226
+ />
227
+ );
228
+ })}
229
+ </div>
230
+ </div>
231
+ ) : Array.isArray(selectedRecord.data_elements) &&
232
+ selectedRecord.data_elements.filter((el) => el.selected).length > 0 ? (
233
+ selectedRecord.data_elements
234
+ .filter((el) => el.selected)
235
+ .map((i, index, filteredArray) => (
236
+ <div
237
+ key={index}
238
+ style={{
239
+ ...pcStyles.flex,
240
+ ...pcStyles.itemsCenter,
241
+ paddingBottom: index < filteredArray.length - 1 ? pcStyles.pbXs.paddingBottom : 0,
242
+ marginBottom: index < filteredArray.length - 1 ? pcStyles.mbXs.marginBottom : 0,
243
+ }}
244
+ >
245
+ <PCBadge renderAsDot size="sm">{''}</PCBadge>
246
+ <span style={{
247
+ marginLeft: pcStyles.mlSm.marginLeft,
248
+ fontSize: pcStyles.textSm.fontSize,
249
+ color: pcStyles.textPrimary.color,
250
+ fontWeight: pcStyles.fontWeightMedium.fontWeight,
251
+ }}>
252
+ {i.name}
253
+ </span>
254
+ </div>
255
+ ))
256
+ ) : (
257
+ <PCText size="sm" color="tertiary">
258
+ {t('noDataElementsAvailable')}
259
+ </PCText>
260
+ )}
261
+ </div>
262
+ </PCAccordion.Body>
263
+ </PCAccordion>
264
+ </div>
265
+ <div style={{
266
+ marginTop: pcStyles.mtLg.marginTop,
267
+ ...pcStyles.flex,
268
+ ...pcStyles.itemsStart,
269
+ ...pcStyles.gapSm,
270
+ borderRadius: pcStyles.borderRadiusMd,
271
+ border: `1px solid ${pcStyles.warningBorder}`,
272
+ backgroundColor: pcStyles.warningBg,
273
+ padding: pcStyles.pMd.padding,
274
+ lineHeight: 1.5,
275
+ }}>
276
+ <PiWarningCircleBold
277
+ size={20}
278
+ style={{
279
+ flexShrink: 0,
280
+ color: pcStyles.warningText,
281
+ marginTop: '2px',
282
+ }}
283
+ />
284
+ <span style={{
285
+ fontWeight: pcStyles.fontWeightMedium.fontWeight,
286
+ fontSize: pcStyles.textSm.fontSize,
287
+ color: pcStyles.warningText,
288
+ }}>
289
+ {actionConfig.warningMessage}
290
+ </span>
291
+ </div>
292
+
293
+ <div style={{
294
+ marginTop: pcStyles.mtLg.marginTop,
295
+ ...pcStyles.flex,
296
+ ...pcStyles.flexRow,
297
+ ...pcStyles.gapSm,
298
+ ...pcStyles.justifyEnd,
299
+ }}>
300
+ <PCButton variant="outline" onClick={onClose}>
301
+ {t('cancel')}
302
+ </PCButton>
303
+ <PCButton
304
+ isLoading={isLoading}
305
+ disabled={
306
+ !selectedRecord.purpose_uuid ||
307
+ (isRegrantAction && selectedDataElementUuids.length === 0)
308
+ }
309
+ variant={actionConfig.buttonVariant}
310
+ onClick={() => {
311
+ if (selectedRecord.purpose_uuid) {
312
+ const nomContact = selectedRecord.nominator_info?.email || selectedRecord.nominator_info?.org_user_id || undefined;
313
+ onManageConsent(
314
+ selectedRecord.purpose_uuid,
315
+ actionConfig.action,
316
+ nomContact,
317
+ isRegrantAction ? selectedDataElementUuids : undefined
318
+ );
319
+ }
320
+ }}
321
+ >
322
+ {actionConfig.buttonLabel}
323
+ </PCButton>
324
+ </div>
325
+ </>
326
+ )}
327
+ </div>
328
+ </PCModal>
329
+ );
330
+ };
@@ -0,0 +1,140 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import { PCActionIcon, PCSelect, PCText } from '../ui';
3
+ import {
4
+ ChevronLeft,
5
+ ChevronRight,
6
+ ChevronsLeft,
7
+ ChevronsRight,
8
+ } from 'lucide-react';
9
+ import { cn } from '../utils/cn';
10
+ import { pcStyles } from '../styles/pcStyles';
11
+ import { PAGE_SIZE_OPTIONS } from './constant';
12
+ import type { PaginationProps } from './types';
13
+
14
+ export default function Pagination({
15
+ currentPage,
16
+ totalPages,
17
+ totalRecords,
18
+ pageSize,
19
+ onPageChange,
20
+ onPageSizeChange,
21
+ className,
22
+ }: PaginationProps) {
23
+ const { t } = useTranslation();
24
+ const handlePageChange = (newPageIndex: number) => {
25
+ onPageChange(newPageIndex + 1);
26
+ };
27
+
28
+ const currentPageSizeOption = PAGE_SIZE_OPTIONS.find(opt => opt.value === pageSize) || PAGE_SIZE_OPTIONS[1];
29
+
30
+ return (
31
+ <div className={cn("pc-flex-responsive", className)} style={{
32
+ ...pcStyles.flex,
33
+ ...pcStyles.gapMd,
34
+ ...pcStyles.itemsCenter,
35
+ ...pcStyles.justifyBetween,
36
+ }}>
37
+ <div style={{
38
+ ...pcStyles.flex,
39
+ ...pcStyles.itemsCenter,
40
+ ...pcStyles.gapMd,
41
+ width: '100%',
42
+ flexShrink: 0,
43
+ }} className="pc-flex-responsive">
44
+ <div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.gapSm }}>
45
+ <PCText size="xs" color="secondary" className="pc-hidden-xs pc-block-sm">
46
+ {t('rowsPerPage')}
47
+ </PCText>
48
+ <PCSelect
49
+ size="sm"
50
+ options={PAGE_SIZE_OPTIONS}
51
+ value={currentPageSizeOption}
52
+ onChange={(v) => {
53
+ if (v) {
54
+ const newSize = Number(v.value);
55
+ onPageSizeChange(newSize);
56
+ }
57
+ }}
58
+ style={{ width: '48px' }}
59
+ />
60
+ </div>
61
+ <div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.gapSm }}>
62
+ <PCText size="xs" color="secondary">
63
+ {t('totalRecords', { count: totalRecords })}
64
+ </PCText>
65
+ </div>
66
+ </div>
67
+ <div style={{
68
+ ...pcStyles.flex,
69
+ ...pcStyles.justifyEnd,
70
+ ...pcStyles.itemsCenter,
71
+ ...pcStyles.gapSm,
72
+ width: '100%',
73
+ }} className="pc-flex-responsive">
74
+ <div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.gapSm }}>
75
+ <PCText size="xs" color="secondary">
76
+ {t('pageOf', { current: totalPages === 0 ? 0 : currentPage, total: totalPages })}
77
+ </PCText>
78
+ </div>
79
+ <div style={{
80
+ display: 'grid',
81
+ gridTemplateColumns: 'repeat(4, 1fr)',
82
+ gap: '0.375rem',
83
+ }}>
84
+ <PCActionIcon
85
+ size="sm"
86
+ variant="outline"
87
+ aria-label={t('goToFirstPage')}
88
+ onClick={() => handlePageChange(0)}
89
+ disabled={currentPage === 1}
90
+ style={{
91
+ borderRadius: '0.5rem',
92
+ boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
93
+ }}
94
+ >
95
+ <ChevronsLeft size={14} />
96
+ </PCActionIcon>
97
+ <PCActionIcon
98
+ size="sm"
99
+ variant="outline"
100
+ aria-label={t('goToPreviousPage')}
101
+ onClick={() => handlePageChange(currentPage - 2)}
102
+ disabled={currentPage === 1}
103
+ style={{
104
+ borderRadius: '0.5rem',
105
+ boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
106
+ }}
107
+ >
108
+ <ChevronLeft size={14} />
109
+ </PCActionIcon>
110
+ <PCActionIcon
111
+ size="sm"
112
+ variant="outline"
113
+ aria-label={t('goToNextPage')}
114
+ onClick={() => handlePageChange(currentPage)}
115
+ disabled={totalRecords === 0 || currentPage === totalPages}
116
+ style={{
117
+ borderRadius: '0.5rem',
118
+ boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
119
+ }}
120
+ >
121
+ <ChevronRight size={14} />
122
+ </PCActionIcon>
123
+ <PCActionIcon
124
+ size="sm"
125
+ variant="outline"
126
+ aria-label={t('goToLastPage')}
127
+ onClick={() => handlePageChange(totalPages - 1)}
128
+ disabled={totalRecords === 0 || currentPage === totalPages}
129
+ style={{
130
+ borderRadius: '0.5rem',
131
+ boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
132
+ }}
133
+ >
134
+ <ChevronsRight size={14} />
135
+ </PCActionIcon>
136
+ </div>
137
+ </div>
138
+ </div>
139
+ );
140
+ }