@redacto.io/consent-sdk-react 3.6.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.d.mts +4 -4
  3. package/dist/index.d.ts +4 -4
  4. package/dist/index.js +36 -7
  5. package/dist/index.mjs +36 -7
  6. package/dist/privacy-center.d.mts +134 -51
  7. package/dist/privacy-center.d.ts +134 -51
  8. package/dist/privacy-center.js +6391 -2741
  9. package/dist/privacy-center.mjs +6135 -2482
  10. package/package.json +1 -1
  11. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +5 -5
  12. package/src/RedactoNoticeConsent/language-codes.ts +37 -1
  13. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +0 -3
  14. package/src/RedactoNoticeConsentInline/api/index.ts +0 -1
  15. package/src/RedactoPrivacyCenter/PrivacyCenterCaseDetailsPage.tsx +154 -0
  16. package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +3 -0
  17. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +81 -50
  18. package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +4 -0
  19. package/src/RedactoPrivacyCenter/api/actions.ts +68 -36
  20. package/src/RedactoPrivacyCenter/api/client.ts +125 -14
  21. package/src/RedactoPrivacyCenter/components/CaseDetails/AttachmentCard.tsx +38 -0
  22. package/src/RedactoPrivacyCenter/components/CaseDetails/ChatBubble.tsx +31 -0
  23. package/src/RedactoPrivacyCenter/components/CaseDetails/DocumentRequestCard.tsx +73 -0
  24. package/src/RedactoPrivacyCenter/components/CaseDetails/MessageComposer.tsx +87 -0
  25. package/src/RedactoPrivacyCenter/components/CaseDetails/MessageItem.tsx +95 -0
  26. package/src/RedactoPrivacyCenter/components/CaseDetails/MessagesTab.tsx +147 -0
  27. package/src/RedactoPrivacyCenter/components/CaseDetails/RequestDetailsTab.tsx +171 -0
  28. package/src/RedactoPrivacyCenter/components/CaseDetails/StatusEvent.tsx +23 -0
  29. package/src/RedactoPrivacyCenter/components/CaseDetails/useFileUpload.ts +116 -0
  30. package/src/RedactoPrivacyCenter/components/CaseDetails/useMessagePolling.ts +36 -0
  31. package/src/RedactoPrivacyCenter/components/CaseDetails/utils.ts +56 -0
  32. package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +1 -0
  33. package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +6 -13
  34. package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +6 -0
  35. package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +0 -1
  36. package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +2 -2
  37. package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +2 -3
  38. package/src/RedactoPrivacyCenter/components/Form.tsx +152 -11
  39. package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +4 -6
  40. package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +9 -2
  41. package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +1 -0
  42. package/src/RedactoPrivacyCenter/components/Pagination.tsx +12 -12
  43. package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +3 -1
  44. package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +15 -1
  45. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +8 -8
  46. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideMenuPC.tsx +7 -11
  47. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +1 -1
  48. package/src/RedactoPrivacyCenter/components/activitiesColumns.tsx +1 -1
  49. package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +4 -24
  50. package/src/RedactoPrivacyCenter/components/constant.ts +3 -7
  51. package/src/RedactoPrivacyCenter/components/types.ts +12 -4
  52. package/src/RedactoPrivacyCenter/context/AuthContext.tsx +30 -16
  53. package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +3 -6
  54. package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +35 -3
  55. package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +1 -1
  56. package/src/RedactoPrivacyCenter/context/types.ts +14 -0
  57. package/src/RedactoPrivacyCenter/lib/api.ts +101 -3
  58. package/src/RedactoPrivacyCenter/lib/constants.ts +46 -1
  59. package/src/RedactoPrivacyCenter/lib/i18n.ts +1 -1
  60. package/src/RedactoPrivacyCenter/lib/queries.ts +106 -2
  61. package/src/RedactoPrivacyCenter/lib/types.ts +186 -1
  62. package/src/RedactoPrivacyCenter/lib/utils.ts +1 -0
  63. package/src/RedactoPrivacyCenter/locales/as/translation.json +93 -1
  64. package/src/RedactoPrivacyCenter/locales/bho/translation.json +93 -1
  65. package/src/RedactoPrivacyCenter/locales/bn/translation.json +93 -1
  66. package/src/RedactoPrivacyCenter/locales/brx/translation.json +93 -1
  67. package/src/RedactoPrivacyCenter/locales/doi/translation.json +93 -1
  68. package/src/RedactoPrivacyCenter/locales/en/translation.json +69 -2
  69. package/src/RedactoPrivacyCenter/locales/gom/translation.json +93 -1
  70. package/src/RedactoPrivacyCenter/locales/gu/translation.json +93 -1
  71. package/src/RedactoPrivacyCenter/locales/hi/translation.json +93 -1
  72. package/src/RedactoPrivacyCenter/locales/kn/translation.json +93 -1
  73. package/src/RedactoPrivacyCenter/locales/ks/translation.json +93 -1
  74. package/src/RedactoPrivacyCenter/locales/mai/translation.json +93 -1
  75. package/src/RedactoPrivacyCenter/locales/ml/translation.json +93 -1
  76. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +93 -1
  77. package/src/RedactoPrivacyCenter/locales/mr/translation.json +93 -1
  78. package/src/RedactoPrivacyCenter/locales/ne/translation.json +93 -1
  79. package/src/RedactoPrivacyCenter/locales/or/translation.json +93 -1
  80. package/src/RedactoPrivacyCenter/locales/pa/translation.json +93 -1
  81. package/src/RedactoPrivacyCenter/locales/sa/translation.json +93 -1
  82. package/src/RedactoPrivacyCenter/locales/sat/translation.json +93 -1
  83. package/src/RedactoPrivacyCenter/locales/sd/translation.json +93 -1
  84. package/src/RedactoPrivacyCenter/locales/ta/translation.json +93 -1
  85. package/src/RedactoPrivacyCenter/locales/te/translation.json +93 -1
  86. package/src/RedactoPrivacyCenter/locales/ur/translation.json +93 -1
  87. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +725 -0
  88. package/src/RedactoPrivacyCenter/styles/pcStyles.ts +0 -6
  89. package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +5 -3
  90. package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +9 -12
  91. package/src/RedactoPrivacyCenter/ui/Footer.tsx +4 -2
  92. package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +24 -18
  93. package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +30 -20
  94. package/src/RedactoPrivacyCenter/ui/PCInput.tsx +17 -13
  95. package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +3 -2
  96. package/src/RedactoPrivacyCenter/ui/PCModal.tsx +3 -1
  97. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +0 -2
  98. package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +10 -4
  99. package/src/RedactoPrivacyCenter/ui/PCTable.tsx +44 -32
  100. package/src/RedactoPrivacyCenter/ui/PCText.tsx +5 -3
  101. package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +0 -2
  102. package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +4 -2
  103. package/src/privacy-center.ts +1 -0
@@ -148,7 +148,7 @@ export default function CorrectionDataSection({
148
148
  gap: getVar('--pc-spacing-xs'),
149
149
  }}>
150
150
  <PCInput
151
- placeholder="Current value"
151
+ placeholder={t('currentValuePlaceholder')}
152
152
  size="sm"
153
153
  required
154
154
  value={correctionValues[name]?.current || ''}
@@ -163,7 +163,7 @@ export default function CorrectionDataSection({
163
163
  }
164
164
  />
165
165
  <PCInput
166
- placeholder="Updated value"
166
+ placeholder={t('updatedValuePlaceholder')}
167
167
  size="sm"
168
168
  required
169
169
  value={correctionValues[name]?.updated || ''}
@@ -3,7 +3,6 @@ import { pcStyles } from '../../styles/pcStyles';
3
3
  import { ChevronDown } from 'lucide-react';
4
4
  import { RequestTypeEnum } from '../../lib/types';
5
5
  import type { Purpose, DataElement } from '../../lib/types';
6
- import { cn } from '../../utils/cn';
7
6
  import { useTranslation } from 'react-i18next';
8
7
  import { useEffect, useState } from 'react';
9
8
 
@@ -206,7 +205,7 @@ export default function PurposeSelectionSection({
206
205
  type="button"
207
206
  onClick={() => toggleAccordion(index)}
208
207
  style={{
209
- marginLeft: pcStyles.mLg.marginLeft,
208
+ marginLeft: getVar('--pc-spacing-lg'),
210
209
  color: getVar('--pc-text-secondary'),
211
210
  background: 'none',
212
211
  border: 'none',
@@ -254,7 +253,7 @@ export default function PurposeSelectionSection({
254
253
  // The backend should handle consent validation
255
254
  return de !== null && de !== undefined;
256
255
  })
257
- .map((de: DataElement, originalIndex: number) => {
256
+ .map((de: DataElement) => {
258
257
  // Find the original index in the unfiltered array for proper state updates
259
258
  const actualIndex = item.data_elements.findIndex(
260
259
  (el) => el.uuid === de.uuid
@@ -9,6 +9,7 @@ import { AiOutlineClose } from 'react-icons/ai';
9
9
  import { PiUploadBold } from 'react-icons/pi';
10
10
  import { toast } from '../utils/toast';
11
11
  import { PCButton, PCCheckbox, PCInput, PCLoader, PCSelect, PCTextarea } from '../ui';
12
+ import { PCModal } from '../ui/PCModal';
12
13
  import { pcStyles } from '../styles/pcStyles';
13
14
  import { createCaseRequest, getFormData } from '../lib/api';
14
15
  import { ensureValidToken } from '../lib/utils';
@@ -40,8 +41,8 @@ import NominationDataSection from './DataRequest/NominationDataSection';
40
41
  import PurposeSelectionSection from './DataRequest/PurposeSelectionSection';
41
42
  import RequestSubmit from './RequestSubmit';
42
43
  import type { CaseHistoryProps } from './types';
43
- import { cn } from '../utils/cn';
44
- import { FiChevronDown, FiChevronUp, FiX } from 'react-icons/fi';
44
+ import { FiAlertCircle, FiChevronDown, FiChevronUp, FiX } from 'react-icons/fi';
45
+ import { MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL } from './constant';
45
46
 
46
47
  const getVar = (varName: string): string => `var(${varName})`;
47
48
 
@@ -133,6 +134,10 @@ export default function Form({
133
134
  const [isTranslating, setIsTranslating] = useState(false);
134
135
  const [isRequestDetailsExpanded, setIsRequestDetailsExpanded] =
135
136
  useState(true);
137
+ const [isErasureConsentModalOpen, setIsErasureConsentModalOpen] =
138
+ useState(false);
139
+ const [revokeConsentOnFulfilment, setRevokeConsentOnFulfilment] =
140
+ useState(false);
136
141
 
137
142
  const resetFileInput = () => {
138
143
  if (fileInputRef.current) {
@@ -472,7 +477,9 @@ export default function Form({
472
477
  * Maps translated correction element names back to English names using UUID lookup.
473
478
  */
474
479
  const getEnglishDataElementName = useCallback((translatedName: string): string => {
475
- if (!englishDataRef.current?.purposes) return translatedName;
480
+ if (!englishDataRef.current?.purposes){
481
+ return translatedName
482
+ }
476
483
 
477
484
  // Build a map from translated name -> english name via UUID
478
485
  const translatedToUuid = new Map<string, string>();
@@ -485,17 +492,64 @@ export default function Form({
485
492
  }
486
493
 
487
494
  const uuid = translatedToUuid.get(translatedName);
488
- if (!uuid) return translatedName;
495
+ if (!uuid){
496
+ return translatedName
497
+ }
489
498
 
490
499
  for (const pw of englishDataRef.current.purposes) {
491
500
  const englishDe = pw.purpose?.data_elements?.find((de) => de.uuid === uuid);
492
- if (englishDe) return englishDe.name;
501
+ if (englishDe){
502
+ return englishDe.name
503
+ }
493
504
  }
494
505
  return translatedName;
495
506
  }, [data]);
496
507
 
497
- const handleSubmit = async () => {
498
- if (!isFormValid) return;
508
+ const selectedErasurePurposeList = useMemo(
509
+ () =>
510
+ erasurePurposes
511
+ .filter((p) => p.selected || p.data_elements.some((de) => de.selected))
512
+ .map((p) => p.name)
513
+ .filter(Boolean),
514
+ [erasurePurposes]
515
+ );
516
+
517
+ const selectedErasurePurposeNames = useMemo(() => {
518
+ if (
519
+ selectedErasurePurposeList.length === 0 ||
520
+ selectedErasurePurposeList.length > MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL
521
+ ) {
522
+ return t('revokeConsentModalYourPurposes');
523
+ }
524
+ return selectedErasurePurposeList.join(', ');
525
+ }, [selectedErasurePurposeList, t]);
526
+
527
+ const handleSubmitClick = () => {
528
+ if (!isFormValid){
529
+ return
530
+ };
531
+ if (requestType === RequestTypeEnum.ERASURE) {
532
+ setIsErasureConsentModalOpen(true);
533
+ return;
534
+ }
535
+ setRevokeConsentOnFulfilment(false);
536
+ void handleSubmit(false);
537
+ };
538
+
539
+ const handleErasureModalChoice = (revoke: boolean) => {
540
+ setRevokeConsentOnFulfilment(revoke);
541
+ setIsErasureConsentModalOpen(false);
542
+ void handleSubmit(revoke);
543
+ };
544
+
545
+ const handleErasureModalClose = () => {
546
+ setIsErasureConsentModalOpen(false);
547
+ };
548
+
549
+ const handleSubmit = async (revokeConsent: boolean = false) => {
550
+ if (!isFormValid){
551
+ return
552
+ };
499
553
 
500
554
  const requestDetails: {
501
555
  purposes: UpdatePurpose[];
@@ -569,6 +623,9 @@ export default function Form({
569
623
  ? parseInt(timePeriod || '30', 10)
570
624
  : 0,
571
625
  requestor_note: formData.additionalNote || '',
626
+ ...(requestType === RequestTypeEnum.ERASURE
627
+ ? { revoke_consent_on_fulfilment: revokeConsent }
628
+ : {}),
572
629
  };
573
630
 
574
631
  try {
@@ -626,7 +683,9 @@ export default function Form({
626
683
  };
627
684
 
628
685
  const isRequestDetailsValid = useCallback(() => {
629
- if (!requestType) return false;
686
+ if (!requestType){
687
+ return false
688
+ };
630
689
  switch (requestType) {
631
690
  case RequestTypeEnum.ACCESS:
632
691
  return accessPurposes.some(
@@ -768,7 +827,9 @@ export default function Form({
768
827
  };
769
828
 
770
829
  const handleVerifyContact = async () => {
771
- if (!isPopup) return;
830
+ if (!isPopup) {
831
+ return
832
+ };
772
833
  if (!formData.contact) {
773
834
  toast.error(t('pleaseEnterContactToVerify'));
774
835
  return;
@@ -862,6 +923,8 @@ export default function Form({
862
923
  caseId={caseId || ''}
863
924
  contact={formData?.contact}
864
925
  onCreateRequest={onCreateRequest}
926
+ revokeConsentOnFulfilment={revokeConsentOnFulfilment}
927
+ revokedPurposeNames={selectedErasurePurposeNames}
865
928
  />
866
929
  ) : (
867
930
  <div>
@@ -962,7 +1025,9 @@ export default function Form({
962
1025
  value={formData.contact}
963
1026
  readOnly={!isPopup}
964
1027
  onChange={(e) => {
965
- if (!isPopup) return;
1028
+ if (!isPopup){
1029
+ return
1030
+ };
966
1031
  setFormData({
967
1032
  ...formData,
968
1033
  contact: e.target.value,
@@ -1428,7 +1493,7 @@ export default function Form({
1428
1493
  )}
1429
1494
  <PCButton
1430
1495
  variant="primary"
1431
- onClick={handleSubmit}
1496
+ onClick={handleSubmitClick}
1432
1497
  disabled={!isFormValid || isLoading}
1433
1498
  isLoading={isLoading}
1434
1499
  style={{
@@ -1450,6 +1515,82 @@ export default function Form({
1450
1515
  </div>
1451
1516
  </div>
1452
1517
  )}
1518
+ <PCModal
1519
+ isOpen={isErasureConsentModalOpen}
1520
+ onClose={handleErasureModalClose}
1521
+ size="md"
1522
+ showCloseButton={false}
1523
+ footer={
1524
+ <div className="pc-erasure-modal-footer">
1525
+ <PCButton
1526
+ variant="outline"
1527
+ onClick={() => handleErasureModalChoice(false)}
1528
+ disabled={isLoading}
1529
+ >
1530
+ {t('revokeConsentNo')}
1531
+ </PCButton>
1532
+ <PCButton
1533
+ variant="primary"
1534
+ onClick={() => handleErasureModalChoice(true)}
1535
+ disabled={isLoading}
1536
+ isLoading={isLoading}
1537
+ >
1538
+ {t('revokeConsentYes')}
1539
+ </PCButton>
1540
+ </div>
1541
+ }
1542
+ >
1543
+ <div className="pc-erasure-modal-content">
1544
+ <div className="pc-erasure-modal-header">
1545
+ <div className="pc-erasure-modal-icon">
1546
+ <FiAlertCircle size={24} />
1547
+ </div>
1548
+ <p className="pc-erasure-modal-title">
1549
+ {t('revokeConsentModalTitle')}
1550
+ </p>
1551
+ <p className="pc-erasure-modal-question">
1552
+ {t('revokeConsentModalQuestion')}
1553
+ </p>
1554
+ </div>
1555
+
1556
+ <div className="pc-erasure-modal-purposes">
1557
+ <p className="pc-erasure-modal-purposes-label">
1558
+ {t('revokeConsentModalPurposesLabel')}
1559
+ </p>
1560
+ {selectedErasurePurposeList.length > 0 ? (
1561
+ <>
1562
+ {selectedErasurePurposeList
1563
+ .slice(0, MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL)
1564
+ .map((name) => (
1565
+ <div key={name} className="pc-erasure-modal-purpose-item">
1566
+ {name}
1567
+ </div>
1568
+ ))}
1569
+ {selectedErasurePurposeList.length > MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL && (
1570
+ <span className="pc-erasure-modal-purposes-placeholder">
1571
+ {t('revokeConsentModalAndMore', {
1572
+ count: selectedErasurePurposeList.length - MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL,
1573
+ })}
1574
+ </span>
1575
+ )}
1576
+ </>
1577
+ ) : (
1578
+ <span className="pc-erasure-modal-purposes-placeholder">
1579
+ {t('revokeConsentModalYourPurposes')}
1580
+ </span>
1581
+ )}
1582
+ </div>
1583
+
1584
+ <div className="pc-erasure-modal-warning">
1585
+ <p className="pc-erasure-modal-warning-text">
1586
+ <span className="pc-erasure-modal-warning-label">
1587
+ {t('revokeConsentModalIfYesLabel')}
1588
+ </span>{' '}
1589
+ {t('revokeConsentModalIfYesBody')}
1590
+ </p>
1591
+ </div>
1592
+ </div>
1593
+ </PCModal>
1453
1594
  </>
1454
1595
  );
1455
1596
  }
@@ -3,10 +3,8 @@ import { useTranslation } from 'react-i18next';
3
3
  import { flexRender } from '@tanstack/react-table';
4
4
  import { PCTable, PCLoader, PCEmpty } from '../ui';
5
5
  import { pcStyles } from '../styles/pcStyles';
6
- import type {
7
- Table as ReactTableType,
8
- Row,
9
- } from '@tanstack/react-table';
6
+ import type { Table as ReactTableType } from '@tanstack/react-table';
7
+ import type { TableColumnMeta } from './types';
10
8
 
11
9
  const getVar = (varName: string): string => `var(${varName})`;
12
10
 
@@ -74,7 +72,7 @@ export default function GrievanceTable<TData extends Record<string, any>>({
74
72
  color: getVar('--pc-text-primary'),
75
73
  backgroundColor: getVar('--pc-bg-secondary'),
76
74
  borderBottom: `1px solid ${getVar('--pc-border-color')}`,
77
- textAlign: header.column.columnDef.meta?.align || 'left',
75
+ textAlign: (header.column.columnDef.meta as TableColumnMeta | undefined)?.align || 'left',
78
76
  whiteSpace: 'nowrap',
79
77
  }}
80
78
  >
@@ -156,7 +154,7 @@ export default function GrievanceTable<TData extends Record<string, any>>({
156
154
  fontSize: pcStyles.textSm.fontSize,
157
155
  color: getVar('--pc-text-primary'),
158
156
  borderBottom: `1px solid ${getVar('--pc-border-color')}`,
159
- textAlign: cell.column.columnDef.meta?.align || 'left',
157
+ textAlign: (cell.column.columnDef.meta as TableColumnMeta | undefined)?.align || 'left',
160
158
  }}
161
159
  >
162
160
  {flexRender(
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import { useTranslation } from 'react-i18next';
3
2
  import { type Table as ReactTableType } from '@tanstack/react-table';
4
3
  import {
@@ -7,7 +6,7 @@ import {
7
6
  PiCaretLeftBold,
8
7
  PiCaretRightBold,
9
8
  } from 'react-icons/pi';
10
- import { PCActionIcon, PCSelect, PCText, type SelectOption } from '../ui';
9
+ import { PCActionIcon, PCSelect, PCText } from '../ui';
11
10
  import { pcStyles } from '../styles/pcStyles';
12
11
 
13
12
  const getVar = (varName: string): string => `var(${varName})`;
@@ -161,6 +160,7 @@ export default function GrievanceTablePagination<
161
160
  <PCActionIcon
162
161
  size="sm"
163
162
  variant="outline"
163
+ aria-label={t('goToFirstPage')}
164
164
  onClick={() => handlePageChange(0)}
165
165
  disabled={!canPreviousPage}
166
166
  style={{
@@ -174,6 +174,7 @@ export default function GrievanceTablePagination<
174
174
  <PCActionIcon
175
175
  size="sm"
176
176
  variant="outline"
177
+ aria-label={t('goToPreviousPage')}
177
178
  onClick={() => handlePageChange(currentPageIndex - 1)}
178
179
  disabled={!canPreviousPage}
179
180
  style={{
@@ -187,6 +188,7 @@ export default function GrievanceTablePagination<
187
188
  <PCActionIcon
188
189
  size="sm"
189
190
  variant="outline"
191
+ aria-label={t('goToNextPage')}
190
192
  onClick={() => handlePageChange(currentPageIndex + 1)}
191
193
  disabled={!canNextPage}
192
194
  style={{
@@ -200,6 +202,7 @@ export default function GrievanceTablePagination<
200
202
  <PCActionIcon
201
203
  size="sm"
202
204
  variant="outline"
205
+ aria-label={t('goToLastPage')}
203
206
  onClick={() => handlePageChange(totalPages - 1)}
204
207
  disabled={!canNextPage}
205
208
  style={{
@@ -274,6 +277,7 @@ export default function GrievanceTablePagination<
274
277
  <PCActionIcon
275
278
  size="sm"
276
279
  variant="outline"
280
+ aria-label={t('goToFirstPage')}
277
281
  onClick={() => handlePageChange(0)}
278
282
  disabled={!canPreviousPage}
279
283
  style={{
@@ -287,6 +291,7 @@ export default function GrievanceTablePagination<
287
291
  <PCActionIcon
288
292
  size="sm"
289
293
  variant="outline"
294
+ aria-label={t('goToPreviousPage')}
290
295
  onClick={() => handlePageChange(currentPageIndex - 1)}
291
296
  disabled={!canPreviousPage}
292
297
  style={{
@@ -300,6 +305,7 @@ export default function GrievanceTablePagination<
300
305
  <PCActionIcon
301
306
  size="sm"
302
307
  variant="outline"
308
+ aria-label={t('goToNextPage')}
303
309
  onClick={() => handlePageChange(currentPageIndex + 1)}
304
310
  disabled={!canNextPage}
305
311
  style={{
@@ -313,6 +319,7 @@ export default function GrievanceTablePagination<
313
319
  <PCActionIcon
314
320
  size="sm"
315
321
  variant="outline"
322
+ aria-label={t('goToLastPage')}
316
323
  onClick={() => handlePageChange(totalPages - 1)}
317
324
  disabled={!canNextPage}
318
325
  style={{
@@ -118,6 +118,7 @@ export const ModifyConsentModal = ({
118
118
  <PCModal
119
119
  isOpen={isOpen}
120
120
  onClose={onClose}
121
+ closeAriaLabel={t('closeModal')}
121
122
  title={
122
123
  <PCText size="base" weight="semibold">
123
124
  {t('modifyConsent')}
@@ -1,4 +1,5 @@
1
- import { PCActionIcon, PCSelect, PCText, type SelectOption } from '../ui';
1
+ import { useTranslation } from 'react-i18next';
2
+ import { PCActionIcon, PCSelect, PCText } from '../ui';
2
3
  import {
3
4
  ChevronLeft,
4
5
  ChevronRight,
@@ -19,6 +20,7 @@ export default function Pagination({
19
20
  onPageSizeChange,
20
21
  className,
21
22
  }: PaginationProps) {
23
+ const { t } = useTranslation();
22
24
  const handlePageChange = (newPageIndex: number) => {
23
25
  onPageChange(newPageIndex + 1);
24
26
  };
@@ -41,11 +43,11 @@ export default function Pagination({
41
43
  }} className="pc-flex-responsive">
42
44
  <div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.gapSm }}>
43
45
  <PCText size="xs" color="secondary" className="pc-hidden-xs pc-block-sm">
44
- Rows per page
46
+ {t('rowsPerPage')}
45
47
  </PCText>
46
48
  <PCSelect
47
49
  size="sm"
48
- options={PAGE_SIZE_OPTIONS as SelectOption[]}
50
+ options={PAGE_SIZE_OPTIONS}
49
51
  value={currentPageSizeOption}
50
52
  onChange={(v) => {
51
53
  if (v) {
@@ -58,7 +60,7 @@ export default function Pagination({
58
60
  </div>
59
61
  <div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.gapSm }}>
60
62
  <PCText size="xs" color="secondary">
61
- Total: {totalRecords} records
63
+ {t('totalRecords', { count: totalRecords })}
62
64
  </PCText>
63
65
  </div>
64
66
  </div>
@@ -70,11 +72,9 @@ export default function Pagination({
70
72
  width: '100%',
71
73
  }} className="pc-flex-responsive">
72
74
  <div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.gapSm }}>
73
- <PCText size="xs" color="secondary" className="pc-hidden-xs pc-block-sm">Page</PCText>
74
- <PCText size="xs" weight="semibold" color="primary">
75
- {totalPages === 0 ? 0 : currentPage}
75
+ <PCText size="xs" color="secondary">
76
+ {t('pageOf', { current: totalPages === 0 ? 0 : currentPage, total: totalPages })}
76
77
  </PCText>
77
- <PCText size="xs" color="secondary">of {totalPages}</PCText>
78
78
  </div>
79
79
  <div style={{
80
80
  display: 'grid',
@@ -84,7 +84,7 @@ export default function Pagination({
84
84
  <PCActionIcon
85
85
  size="sm"
86
86
  variant="outline"
87
- aria-label="Go to first page"
87
+ aria-label={t('goToFirstPage')}
88
88
  onClick={() => handlePageChange(0)}
89
89
  disabled={currentPage === 1}
90
90
  style={{
@@ -97,7 +97,7 @@ export default function Pagination({
97
97
  <PCActionIcon
98
98
  size="sm"
99
99
  variant="outline"
100
- aria-label="Go to previous page"
100
+ aria-label={t('goToPreviousPage')}
101
101
  onClick={() => handlePageChange(currentPage - 2)}
102
102
  disabled={currentPage === 1}
103
103
  style={{
@@ -110,7 +110,7 @@ export default function Pagination({
110
110
  <PCActionIcon
111
111
  size="sm"
112
112
  variant="outline"
113
- aria-label="Go to next page"
113
+ aria-label={t('goToNextPage')}
114
114
  onClick={() => handlePageChange(currentPage)}
115
115
  disabled={totalRecords === 0 || currentPage === totalPages}
116
116
  style={{
@@ -123,7 +123,7 @@ export default function Pagination({
123
123
  <PCActionIcon
124
124
  size="sm"
125
125
  variant="outline"
126
- aria-label="Go to last page"
126
+ aria-label={t('goToLastPage')}
127
127
  onClick={() => handlePageChange(totalPages - 1)}
128
128
  disabled={totalRecords === 0 || currentPage === totalPages}
129
129
  style={{
@@ -40,7 +40,7 @@ export default function PrivacyCenterLayout({
40
40
 
41
41
  return (
42
42
  <LocaleContext.Provider value={{ locale, setLocale }}>
43
- <Suspense fallback={<div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.justifyCenter, minHeight: '100vh' }}>Loading...</div>}>
43
+ <Suspense fallback={<div style={{ ...pcStyles.flex, ...pcStyles.itemsCenter, ...pcStyles.justifyCenter, minHeight: '100vh' }}>{t('loadingEllipsis')}</div>}>
44
44
  <ThemeTransitionOverlay />
45
45
  <main
46
46
  className="pc-root"
@@ -129,6 +129,8 @@ export default function PrivacyCenterLayout({
129
129
  <PCSelect
130
130
  size="sm"
131
131
  searchable
132
+ searchPlaceholder={t('searchPlaceholder')}
133
+ noOptionsMessage={t('noOptionsFound')}
132
134
  style={{
133
135
  width: 'auto',
134
136
  minWidth: '92px',
@@ -1,6 +1,5 @@
1
1
  import character from '../assets/character.svg';
2
2
  import greenTick from '../assets/greenTick.svg';
3
- import mailIcon from '../assets/mail.svg';
4
3
  import sentImg from '../assets/sentImg.svg';
5
4
  import { useTranslation } from 'react-i18next';
6
5
  import { PCButton, PCText } from '../ui';
@@ -14,6 +13,8 @@ export default function RequestSubmit({
14
13
  caseId,
15
14
  contact,
16
15
  onCreateRequest,
16
+ revokeConsentOnFulfilment,
17
+ revokedPurposeNames,
17
18
  }: RequestSubmitProps) {
18
19
  const { t } = useTranslation();
19
20
  return (
@@ -158,6 +159,19 @@ export default function RequestSubmit({
158
159
  >
159
160
  {t('processRequest')}.
160
161
  </PCText>
162
+ {revokeConsentOnFulfilment && (
163
+ <div className="pc-request-submit-revoke-notice">
164
+ <PCText
165
+ size="xs"
166
+ weight="medium"
167
+ className="pc-request-submit-revoke-notice-text"
168
+ >
169
+ {t('erasureConsentRevokeNotice', {
170
+ purposes: revokedPurposeNames,
171
+ })}
172
+ </PCText>
173
+ </div>
174
+ )}
161
175
  </div>
162
176
  </div>
163
177
  <div className="pc-hidden-xs pc-block-md" style={{ width: '100%' }}>
@@ -3,7 +3,7 @@ import SimpleBar from '../../ui/SimpleBar';
3
3
  import { PCText, PCAvatar, PCButton, PCActionIcon } from '../../ui';
4
4
  import { PCTooltip } from '../../ui/PCTooltip';
5
5
  import { pcStyles } from '../../styles/pcStyles';
6
- import { useState, useEffect, useCallback, useMemo } from 'react';
6
+ import { useState, useEffect, useCallback, useMemo, type CSSProperties } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import {
9
9
  IoArrowBackOutline,
@@ -119,9 +119,9 @@ export default function SideBarPC({
119
119
  [isCollapsed, className]
120
120
  );
121
121
 
122
- const sidebarStyle = useMemo(
122
+ const sidebarStyle = useMemo<CSSProperties>(
123
123
  () => ({
124
- position: 'fixed' as const,
124
+ position: 'fixed',
125
125
  top: 0,
126
126
  bottom: 0,
127
127
  left: 0,
@@ -131,8 +131,8 @@ export default function SideBarPC({
131
131
  transition: 'width 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s ease, border-color 0.3s ease',
132
132
  display: 'flex',
133
133
  flexDirection: 'column',
134
- overflowY: 'hidden' as const,
135
- overflowX: 'visible' as const,
134
+ overflowY: 'hidden',
135
+ overflowX: 'visible',
136
136
  boxSizing: 'border-box',
137
137
  height: '100vh',
138
138
  willChange: 'width',
@@ -226,7 +226,7 @@ export default function SideBarPC({
226
226
  {!isCompact && !isMobileOpen && (
227
227
  <PCActionIcon
228
228
  variant="outline"
229
- aria-label={isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
229
+ aria-label={isCollapsed ? t('expandSidebar') : t('collapseSidebar')}
230
230
  className="pc-sidebar-toggle-desktop pc-hidden-xs pc-hidden-sm pc-flex-md"
231
231
  data-collapsed={isCollapsed ? 'true' : 'false'}
232
232
  data-visible={(!isCollapsed || isDesktopHovered) ? 'true' : 'false'}
@@ -258,7 +258,7 @@ export default function SideBarPC({
258
258
  {!isMobileOpen && (
259
259
  <PCActionIcon
260
260
  data-mobile-menu-button
261
- aria-label="Open Sidebar Menu"
261
+ aria-label={t('openSidebarMenu')}
262
262
  variant="default"
263
263
  style={{
264
264
  position: 'fixed',
@@ -357,7 +357,7 @@ export default function SideBarPC({
357
357
  style={{ color: getVar('--pc-text-primary') }}
358
358
  className="pc-hidden-md"
359
359
  onClick={handleMobileClose}
360
- aria-label="Close Sidebar Menu"
360
+ aria-label={t('closeSidebarMenu')}
361
361
  >
362
362
  <IoCloseOutline size={20} />
363
363
  </PCActionIcon>