@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.
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +36 -7
- package/dist/index.mjs +36 -7
- package/dist/privacy-center.d.mts +134 -51
- package/dist/privacy-center.d.ts +134 -51
- package/dist/privacy-center.js +6391 -2741
- package/dist/privacy-center.mjs +6135 -2482
- package/package.json +1 -1
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +5 -5
- package/src/RedactoNoticeConsent/language-codes.ts +37 -1
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +0 -3
- package/src/RedactoNoticeConsentInline/api/index.ts +0 -1
- package/src/RedactoPrivacyCenter/PrivacyCenterCaseDetailsPage.tsx +154 -0
- package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +3 -0
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +81 -50
- package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +4 -0
- package/src/RedactoPrivacyCenter/api/actions.ts +68 -36
- package/src/RedactoPrivacyCenter/api/client.ts +125 -14
- package/src/RedactoPrivacyCenter/components/CaseDetails/AttachmentCard.tsx +38 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/ChatBubble.tsx +31 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/DocumentRequestCard.tsx +73 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessageComposer.tsx +87 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessageItem.tsx +95 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessagesTab.tsx +147 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/RequestDetailsTab.tsx +171 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/StatusEvent.tsx +23 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/useFileUpload.ts +116 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/useMessagePolling.ts +36 -0
- package/src/RedactoPrivacyCenter/components/CaseDetails/utils.ts +56 -0
- package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +1 -0
- package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +6 -13
- package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +6 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +0 -1
- package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +2 -2
- package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +2 -3
- package/src/RedactoPrivacyCenter/components/Form.tsx +152 -11
- package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +4 -6
- package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +9 -2
- package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +1 -0
- package/src/RedactoPrivacyCenter/components/Pagination.tsx +12 -12
- package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +3 -1
- package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +15 -1
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +8 -8
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideMenuPC.tsx +7 -11
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +1 -1
- package/src/RedactoPrivacyCenter/components/activitiesColumns.tsx +1 -1
- package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +4 -24
- package/src/RedactoPrivacyCenter/components/constant.ts +3 -7
- package/src/RedactoPrivacyCenter/components/types.ts +12 -4
- package/src/RedactoPrivacyCenter/context/AuthContext.tsx +30 -16
- package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +3 -6
- package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +35 -3
- package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +1 -1
- package/src/RedactoPrivacyCenter/context/types.ts +14 -0
- package/src/RedactoPrivacyCenter/lib/api.ts +101 -3
- package/src/RedactoPrivacyCenter/lib/constants.ts +46 -1
- package/src/RedactoPrivacyCenter/lib/i18n.ts +1 -1
- package/src/RedactoPrivacyCenter/lib/queries.ts +106 -2
- package/src/RedactoPrivacyCenter/lib/types.ts +186 -1
- package/src/RedactoPrivacyCenter/lib/utils.ts +1 -0
- package/src/RedactoPrivacyCenter/locales/as/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/bho/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/bn/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/brx/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/doi/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/en/translation.json +69 -2
- package/src/RedactoPrivacyCenter/locales/gom/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/gu/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/hi/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/kn/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/ks/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/mai/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/ml/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/mr/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/ne/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/or/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/pa/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/sa/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/sat/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/sd/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/ta/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/te/translation.json +93 -1
- package/src/RedactoPrivacyCenter/locales/ur/translation.json +93 -1
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +725 -0
- package/src/RedactoPrivacyCenter/styles/pcStyles.ts +0 -6
- package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +5 -3
- package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +9 -12
- package/src/RedactoPrivacyCenter/ui/Footer.tsx +4 -2
- package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +24 -18
- package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +30 -20
- package/src/RedactoPrivacyCenter/ui/PCInput.tsx +17 -13
- package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +3 -2
- package/src/RedactoPrivacyCenter/ui/PCModal.tsx +3 -1
- package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +0 -2
- package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +10 -4
- package/src/RedactoPrivacyCenter/ui/PCTable.tsx +44 -32
- package/src/RedactoPrivacyCenter/ui/PCText.tsx +5 -3
- package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +0 -2
- package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +4 -2
- package/src/privacy-center.ts +1 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
1
|
import { PCTitle, PCCollapse, PCTooltip } from '../../ui';
|
|
3
2
|
import { pcStyles } from '../../styles/pcStyles';
|
|
4
|
-
import { cn } from '../../utils/cn';
|
|
5
3
|
import { PiCaretDownBold } from 'react-icons/pi';
|
|
6
4
|
import { MdLineWeight } from 'react-icons/md';
|
|
7
5
|
|
|
@@ -9,11 +7,9 @@ const getVar = (varName: string): string => `var(${varName})`;
|
|
|
9
7
|
import StatusBadge from '../../ui/StatusBadge';
|
|
10
8
|
import { useInternalNavigation, type InternalPage } from '../../context/NavigationContext';
|
|
11
9
|
import { useTranslation } from 'react-i18next';
|
|
12
|
-
import type {
|
|
10
|
+
import type { MenuBadgeProps, SideMenuPCProps } from './types';
|
|
13
11
|
import { badges } from './constants';
|
|
14
12
|
|
|
15
|
-
export type { DropdownItem, MenuItem, SideMenuPCProps } from './types';
|
|
16
|
-
|
|
17
13
|
const MenuBadge = ({ type, isDisabled }: MenuBadgeProps) => {
|
|
18
14
|
const { t } = useTranslation();
|
|
19
15
|
const badge = badges[type];
|
|
@@ -25,7 +21,7 @@ const MenuBadge = ({ type, isDisabled }: MenuBadgeProps) => {
|
|
|
25
21
|
return (
|
|
26
22
|
<span
|
|
27
23
|
style={{
|
|
28
|
-
marginLeft:
|
|
24
|
+
marginLeft: getVar('--pc-spacing-sm'),
|
|
29
25
|
borderRadius: '9999px',
|
|
30
26
|
paddingLeft: '0.5rem',
|
|
31
27
|
paddingRight: '0.5rem',
|
|
@@ -150,7 +146,7 @@ export function SideMenuPC({ menuItems, isCollapsed, onItemSelect }: SideMenuPCP
|
|
|
150
146
|
{item?.icon && (
|
|
151
147
|
<span
|
|
152
148
|
style={{
|
|
153
|
-
marginRight: isCollapsed ? 0 :
|
|
149
|
+
marginRight: isCollapsed ? 0 : getVar('--pc-spacing-sm'),
|
|
154
150
|
display: 'inline-flex',
|
|
155
151
|
height: '1.25rem',
|
|
156
152
|
width: '1.25rem',
|
|
@@ -242,7 +238,7 @@ export function SideMenuPC({ menuItems, isCollapsed, onItemSelect }: SideMenuPCP
|
|
|
242
238
|
{dropdownItem?.icon && (
|
|
243
239
|
<span
|
|
244
240
|
style={{
|
|
245
|
-
marginRight:
|
|
241
|
+
marginRight: getVar('--pc-spacing-sm'),
|
|
246
242
|
display: 'inline-flex',
|
|
247
243
|
height: '1.125rem',
|
|
248
244
|
width: '1.125rem',
|
|
@@ -265,7 +261,7 @@ export function SideMenuPC({ menuItems, isCollapsed, onItemSelect }: SideMenuPCP
|
|
|
265
261
|
/>
|
|
266
262
|
)}
|
|
267
263
|
</div>
|
|
268
|
-
<div style={{ marginLeft:
|
|
264
|
+
<div style={{ marginLeft: getVar('--pc-spacing-md'), ...pcStyles.flex, ...pcStyles.itemsCenter }}>
|
|
269
265
|
{dropdownItem?.badge?.length ? (
|
|
270
266
|
<StatusBadge status={dropdownItem?.badge} className="pc-status-badge" />
|
|
271
267
|
) : (
|
|
@@ -284,7 +280,7 @@ export function SideMenuPC({ menuItems, isCollapsed, onItemSelect }: SideMenuPCP
|
|
|
284
280
|
renderItemWithTooltip(
|
|
285
281
|
<div
|
|
286
282
|
key={item.name + '-' + index}
|
|
287
|
-
onClick={() => handleNavigate(item.href)}
|
|
283
|
+
onClick={() => item.href && handleNavigate(item.href)}
|
|
288
284
|
style={{
|
|
289
285
|
position: 'relative',
|
|
290
286
|
marginTop: isCollapsed ? 0 : index === 0 ? 0 : '0.1875rem',
|
|
@@ -317,7 +313,7 @@ export function SideMenuPC({ menuItems, isCollapsed, onItemSelect }: SideMenuPCP
|
|
|
317
313
|
{item?.icon && (
|
|
318
314
|
<span
|
|
319
315
|
style={{
|
|
320
|
-
marginRight: isCollapsed ? 0 :
|
|
316
|
+
marginRight: isCollapsed ? 0 : getVar('--pc-spacing-sm'),
|
|
321
317
|
display: 'inline-flex',
|
|
322
318
|
height: '1.25rem',
|
|
323
319
|
width: '1.25rem',
|
|
@@ -52,7 +52,7 @@ export const useActivitiesColumns = () => {
|
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
54
|
<div style={{ paddingTop: pcStyles.pySm.paddingTop, paddingBottom: pcStyles.pySm.paddingBottom }}>
|
|
55
|
-
<StatusBadge status={status}
|
|
55
|
+
<StatusBadge status={status} className="pc-status-badge">
|
|
56
56
|
{displayType}
|
|
57
57
|
</StatusBadge>
|
|
58
58
|
</div>
|
|
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { PCText, PCActionIcon, PCTooltip, PCBadge } from '../ui';
|
|
4
4
|
import { pcStyles } from '../styles/pcStyles';
|
|
5
|
-
import {
|
|
5
|
+
import { FiDownload } from 'react-icons/fi';
|
|
6
6
|
import { createColumnHelper } from '@tanstack/react-table';
|
|
7
7
|
import type { CaseRequest } from '../lib/types';
|
|
8
8
|
import { formatDate } from './utils';
|
|
@@ -21,13 +21,7 @@ const getCaseStatusVariant = (status: string): 'success' | 'error' | 'warning' |
|
|
|
21
21
|
const formatCaseStatus = (s: string) =>
|
|
22
22
|
s.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
23
23
|
|
|
24
|
-
export
|
|
25
|
-
onViewDetails: (caseRequest: CaseRequest) => void;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const useCaseHistoryColumns = ({
|
|
29
|
-
onViewDetails,
|
|
30
|
-
}: UseCaseHistoryColumnsProps) => {
|
|
24
|
+
export const useCaseHistoryColumns = () => {
|
|
31
25
|
const { t } = useTranslation();
|
|
32
26
|
return useMemo(
|
|
33
27
|
() => [
|
|
@@ -133,7 +127,7 @@ export const useCaseHistoryColumns = ({
|
|
|
133
127
|
meta: { align: 'right' },
|
|
134
128
|
enableSorting: false,
|
|
135
129
|
enableColumnFilter: false,
|
|
136
|
-
cell: (
|
|
130
|
+
cell: () => {
|
|
137
131
|
return (
|
|
138
132
|
<div style={{
|
|
139
133
|
...pcStyles.flex,
|
|
@@ -141,20 +135,6 @@ export const useCaseHistoryColumns = ({
|
|
|
141
135
|
...pcStyles.gapXs,
|
|
142
136
|
width: '100%',
|
|
143
137
|
}}>
|
|
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
138
|
<PCTooltip content={t('downloadResponse')}>
|
|
159
139
|
<PCActionIcon
|
|
160
140
|
variant="outline"
|
|
@@ -174,6 +154,6 @@ export const useCaseHistoryColumns = ({
|
|
|
174
154
|
},
|
|
175
155
|
}),
|
|
176
156
|
],
|
|
177
|
-
[
|
|
157
|
+
[t]
|
|
178
158
|
);
|
|
179
159
|
};
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
export const TABS = {
|
|
2
|
-
ALL: 'All Requests',
|
|
3
|
-
PROCESSING: 'Processing',
|
|
4
|
-
COMPLETED: 'Completed',
|
|
5
|
-
REJECTED: 'Rejected',
|
|
6
|
-
};
|
|
7
|
-
|
|
8
1
|
export const GRIEVANCE_TAB_IDS = {
|
|
9
2
|
ALL: 'all',
|
|
10
3
|
PROCESSING: 'processing',
|
|
@@ -18,3 +11,6 @@ export {
|
|
|
18
11
|
INITIAL_PAGE,
|
|
19
12
|
INITIAL_PAGE_SIZE,
|
|
20
13
|
} from '../config/table';
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export const MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL = 3;
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
2
|
import type { CaseRequest, UserConsent } from '../lib/types';
|
|
3
3
|
|
|
4
|
+
export type TableColumnAlign = 'left' | 'center' | 'right';
|
|
5
|
+
|
|
6
|
+
export type TableColumnMeta = {
|
|
7
|
+
align?: TableColumnAlign;
|
|
8
|
+
};
|
|
9
|
+
|
|
4
10
|
export type PrivacyCenterLayoutProps = {
|
|
5
11
|
children: ReactNode;
|
|
6
12
|
sidebarClassName?: string;
|
|
@@ -20,11 +26,13 @@ export type CaseDetailsModalProps = {
|
|
|
20
26
|
selectedCase: CaseRequest | null;
|
|
21
27
|
};
|
|
22
28
|
|
|
23
|
-
export type RequestSubmitProps = {
|
|
29
|
+
export type RequestSubmitProps = Readonly<{
|
|
24
30
|
caseId: string;
|
|
25
31
|
contact: string;
|
|
26
32
|
onCreateRequest: () => void;
|
|
27
|
-
|
|
33
|
+
revokeConsentOnFulfilment?: boolean;
|
|
34
|
+
revokedPurposeNames?: string;
|
|
35
|
+
}>;
|
|
28
36
|
|
|
29
37
|
export type PaginationProps = {
|
|
30
38
|
currentPage: number;
|
|
@@ -47,7 +55,7 @@ export type ProductFilterOption = {
|
|
|
47
55
|
};
|
|
48
56
|
|
|
49
57
|
export type ConsentManagerProps = {
|
|
50
|
-
variant?:
|
|
58
|
+
variant?: 'modern' | 'minimal';
|
|
51
59
|
};
|
|
52
60
|
|
|
53
61
|
export type ModifyConsentModalProps = {
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { setRefreshTokenCookie,
|
|
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';
|
|
3
4
|
|
|
4
|
-
export
|
|
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
|
-
}
|
|
5
|
+
export type { AuthContextValue } from './types';
|
|
11
6
|
|
|
12
7
|
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
13
8
|
|
|
@@ -30,8 +25,10 @@ const syncJwtDataToSessionStorage = (accessToken: string): void => {
|
|
|
30
25
|
return;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
// Extract email from JWT (contact or primary_email)
|
|
34
|
-
const email = decoded.contact || decoded.primary_email
|
|
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);
|
|
35
32
|
if (email && typeof email === 'string') {
|
|
36
33
|
sessionStorage.setItem('email', email);
|
|
37
34
|
}
|
|
@@ -61,17 +58,34 @@ export const AuthProvider = ({
|
|
|
61
58
|
refreshToken: initialRefreshToken,
|
|
62
59
|
onError,
|
|
63
60
|
}: AuthProviderProps) => {
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
});
|
|
66
82
|
|
|
67
|
-
//
|
|
68
|
-
// Only sync if tokens are valid non-empty strings
|
|
83
|
+
// Re-sync when props change (for token refresh scenarios)
|
|
69
84
|
useEffect(() => {
|
|
70
85
|
if (initialAccessToken && typeof initialAccessToken === 'string' && initialAccessToken.trim() !== '') {
|
|
71
86
|
setAccessToken(initialAccessToken);
|
|
72
87
|
if (typeof window !== 'undefined') {
|
|
73
88
|
sessionStorage.setItem('access_token', initialAccessToken);
|
|
74
|
-
// Extract and store org/auth data from JWT
|
|
75
89
|
syncJwtDataToSessionStorage(initialAccessToken);
|
|
76
90
|
}
|
|
77
91
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from 'react';
|
|
2
2
|
import { createConsentClient } from '../api/client';
|
|
3
|
-
import type {
|
|
3
|
+
import type { BaseUrlContextValue } from './types';
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
baseUrl: string;
|
|
7
|
-
consentClient: ReturnType<typeof createConsentClient>;
|
|
8
|
-
}
|
|
5
|
+
export type { BaseUrlContextValue } from './types';
|
|
9
6
|
|
|
10
7
|
export const BaseUrlContext = createContext<BaseUrlContextValue | null>(null);
|
|
11
8
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
|
|
2
|
+
import type { CaseRequest } from '../lib/types';
|
|
2
3
|
|
|
3
|
-
export type InternalPage = 'consent-manager' | 'form' | 'activity';
|
|
4
|
+
export type InternalPage = 'consent-manager' | 'form' | 'activity' | 'case-details';
|
|
4
5
|
|
|
5
6
|
const NAV_STORAGE_KEY = 'pc_current_page';
|
|
7
|
+
const CASE_UUID_STORAGE_KEY = 'pc_selected_case_uuid';
|
|
6
8
|
|
|
7
9
|
const VALID_PAGES: ReadonlySet<string> = new Set<InternalPage>([
|
|
8
|
-
'consent-manager', 'form', 'activity',
|
|
10
|
+
'consent-manager', 'form', 'activity', 'case-details',
|
|
9
11
|
]);
|
|
10
12
|
|
|
11
13
|
function getStoredPage(fallback: InternalPage): InternalPage {
|
|
@@ -21,9 +23,21 @@ function getStoredPage(fallback: InternalPage): InternalPage {
|
|
|
21
23
|
return fallback;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
function getStoredCaseUuid(): string | null {
|
|
27
|
+
if (typeof window === 'undefined') return null;
|
|
28
|
+
try {
|
|
29
|
+
return sessionStorage.getItem(CASE_UUID_STORAGE_KEY);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
24
35
|
interface NavigationContextType {
|
|
25
36
|
currentPage: InternalPage;
|
|
26
37
|
navigateTo: (page: InternalPage) => void;
|
|
38
|
+
selectedCase: CaseRequest | null;
|
|
39
|
+
setSelectedCase: (c: CaseRequest | null) => void;
|
|
40
|
+
pendingCaseUuid: string | null;
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
const NavigationContext = createContext<NavigationContextType | null>(null);
|
|
@@ -38,9 +52,27 @@ export const NavigationProvider = ({
|
|
|
38
52
|
const [currentPage, setCurrentPage] = useState<InternalPage>(
|
|
39
53
|
() => getStoredPage(initialPage)
|
|
40
54
|
);
|
|
55
|
+
const [selectedCase, setSelectedCaseState] = useState<CaseRequest | null>(null);
|
|
56
|
+
const [pendingCaseUuid] = useState<string | null>(() => getStoredCaseUuid());
|
|
57
|
+
|
|
58
|
+
const setSelectedCase = useCallback((c: CaseRequest | null) => {
|
|
59
|
+
setSelectedCaseState(c);
|
|
60
|
+
try {
|
|
61
|
+
if (c?.uuid) {
|
|
62
|
+
sessionStorage.setItem(CASE_UUID_STORAGE_KEY, c.uuid);
|
|
63
|
+
} else {
|
|
64
|
+
sessionStorage.removeItem(CASE_UUID_STORAGE_KEY);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// sessionStorage unavailable
|
|
68
|
+
}
|
|
69
|
+
}, []);
|
|
41
70
|
|
|
42
71
|
const navigateTo = useCallback((page: InternalPage) => {
|
|
43
72
|
setCurrentPage(page);
|
|
73
|
+
if (page !== 'case-details') {
|
|
74
|
+
setSelectedCaseState(null);
|
|
75
|
+
}
|
|
44
76
|
try {
|
|
45
77
|
sessionStorage.setItem(NAV_STORAGE_KEY, page);
|
|
46
78
|
} catch {
|
|
@@ -49,7 +81,7 @@ export const NavigationProvider = ({
|
|
|
49
81
|
}, []);
|
|
50
82
|
|
|
51
83
|
return (
|
|
52
|
-
<NavigationContext.Provider value={{ currentPage, navigateTo }}>
|
|
84
|
+
<NavigationContext.Provider value={{ currentPage, navigateTo, selectedCase, setSelectedCase, pendingCaseUuid }}>
|
|
53
85
|
{children}
|
|
54
86
|
</NavigationContext.Provider>
|
|
55
87
|
);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { createConsentClient } from '../api/client';
|
|
2
|
+
|
|
3
|
+
export type AuthContextValue = Readonly<{
|
|
4
|
+
accessToken: string;
|
|
5
|
+
refreshToken: string;
|
|
6
|
+
onError: (error: Error) => boolean;
|
|
7
|
+
// Internal: updates tokens after refresh
|
|
8
|
+
updateTokens: (accessToken: string, refreshToken?: string) => void;
|
|
9
|
+
}>;
|
|
10
|
+
|
|
11
|
+
export type BaseUrlContextValue = Readonly<{
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
consentClient: ReturnType<typeof createConsentClient>;
|
|
14
|
+
}>;
|
|
@@ -6,16 +6,21 @@ import {
|
|
|
6
6
|
fetchUserConsentsServer,
|
|
7
7
|
getFormDataServer,
|
|
8
8
|
manageConsentServer,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
getCaseMessagesServer,
|
|
10
|
+
sendCaseMessageServer,
|
|
11
|
+
uploadCaseDocumentServer,
|
|
12
|
+
submitDocumentForRequestServer,
|
|
13
|
+
getCaseDocumentServer,
|
|
11
14
|
} from '../api/actions';
|
|
12
15
|
import type { BaseUrlContextValue } from '../context/BaseUrlContext';
|
|
13
16
|
import type {
|
|
14
17
|
ActivityDetail,
|
|
15
18
|
CaseHistoryDetail,
|
|
19
|
+
CaseMessage,
|
|
16
20
|
CreateCaseResponse,
|
|
17
21
|
FormData,
|
|
18
|
-
|
|
22
|
+
MessageDocument,
|
|
23
|
+
UploadDocumentResponse,
|
|
19
24
|
UserConsentDetail,
|
|
20
25
|
UserDataRequest,
|
|
21
26
|
} from './types';
|
|
@@ -466,3 +471,96 @@ export const fetchCaseHistory = async (
|
|
|
466
471
|
return null;
|
|
467
472
|
}
|
|
468
473
|
};
|
|
474
|
+
|
|
475
|
+
// ─── Auth-retry helper ──────────────────────────────────────────────────────
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Wraps an API call with token acquisition and single-retry-on-auth-error logic.
|
|
479
|
+
* `fn` receives a valid access token and returns the API result.
|
|
480
|
+
*/
|
|
481
|
+
async function withAuthRetry<T>(
|
|
482
|
+
organizationId: string,
|
|
483
|
+
workspaceId: string,
|
|
484
|
+
authContext: AuthContext | undefined,
|
|
485
|
+
consentClient: BaseUrlContextValue['consentClient'] | undefined,
|
|
486
|
+
errorLabel: string,
|
|
487
|
+
fn: (token: string) => Promise<T>
|
|
488
|
+
): Promise<T | null> {
|
|
489
|
+
try {
|
|
490
|
+
const accessToken = await ensureValidToken(organizationId, workspaceId, undefined, authContext, consentClient);
|
|
491
|
+
if (!accessToken) return null;
|
|
492
|
+
return await fn(accessToken);
|
|
493
|
+
} catch (error: any) {
|
|
494
|
+
const shouldRetry = await handleClientAuthError(error, organizationId, workspaceId, authContext, consentClient);
|
|
495
|
+
if (shouldRetry) {
|
|
496
|
+
const token = sessionStorage.getItem('access_token') || authContext?.accessToken || '';
|
|
497
|
+
return await fn(token);
|
|
498
|
+
}
|
|
499
|
+
console.error(errorLabel, error);
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ─── Case Communication ──────────────────────────────────────────────────────
|
|
505
|
+
|
|
506
|
+
export const getCaseMessages = async (
|
|
507
|
+
organizationId: string,
|
|
508
|
+
workspaceId: string,
|
|
509
|
+
caseUuid: string,
|
|
510
|
+
authContext?: AuthContext,
|
|
511
|
+
consentClient?: BaseUrlContextValue['consentClient']
|
|
512
|
+
): Promise<CaseMessage[] | null> =>
|
|
513
|
+
withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching case messages:', (token) =>
|
|
514
|
+
getCaseMessagesServer({ organizationId, workspaceId, caseUuid, authToken: token, consentClient })
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
export const sendCaseMessage = async (
|
|
518
|
+
organizationId: string,
|
|
519
|
+
workspaceId: string,
|
|
520
|
+
caseUuid: string,
|
|
521
|
+
body: string,
|
|
522
|
+
documentUuids: string[],
|
|
523
|
+
authContext?: AuthContext,
|
|
524
|
+
consentClient?: BaseUrlContextValue['consentClient']
|
|
525
|
+
): Promise<CaseMessage | null> =>
|
|
526
|
+
withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error sending case message:', (token) =>
|
|
527
|
+
sendCaseMessageServer({ organizationId, workspaceId, caseUuid, body, documentUuids, authToken: token, consentClient })
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
export const uploadCaseDocument = async (
|
|
531
|
+
organizationId: string,
|
|
532
|
+
workspaceId: string,
|
|
533
|
+
caseUuid: string,
|
|
534
|
+
file: File,
|
|
535
|
+
authContext?: AuthContext,
|
|
536
|
+
consentClient?: BaseUrlContextValue['consentClient']
|
|
537
|
+
): Promise<UploadDocumentResponse | null> =>
|
|
538
|
+
withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error uploading case document:', (token) =>
|
|
539
|
+
uploadCaseDocumentServer({ organizationId, workspaceId, caseUuid, file, authToken: token, consentClient })
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
export const submitDocumentForRequest = async (
|
|
543
|
+
organizationId: string,
|
|
544
|
+
workspaceId: string,
|
|
545
|
+
caseUuid: string,
|
|
546
|
+
requestEventUuid: string,
|
|
547
|
+
documentUuid: string,
|
|
548
|
+
body: string,
|
|
549
|
+
authContext?: AuthContext,
|
|
550
|
+
consentClient?: BaseUrlContextValue['consentClient']
|
|
551
|
+
): Promise<CaseMessage | null> =>
|
|
552
|
+
withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error submitting document for request:', (token) =>
|
|
553
|
+
submitDocumentForRequestServer({ organizationId, workspaceId, caseUuid, requestEventUuid, documentUuid, body, authToken: token, consentClient })
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
export const getCaseDocument = async (
|
|
557
|
+
organizationId: string,
|
|
558
|
+
workspaceId: string,
|
|
559
|
+
caseUuid: string,
|
|
560
|
+
documentUuid: string,
|
|
561
|
+
authContext?: AuthContext,
|
|
562
|
+
consentClient?: BaseUrlContextValue['consentClient']
|
|
563
|
+
): Promise<MessageDocument | null> =>
|
|
564
|
+
withAuthRetry(organizationId, workspaceId, authContext, consentClient, 'Error fetching case document:', (token) =>
|
|
565
|
+
getCaseDocumentServer({ organizationId, workspaceId, caseUuid, documentUuid, authToken: token, consentClient })
|
|
566
|
+
);
|
|
@@ -4,6 +4,7 @@ import { GRIEVANCE_TAB_IDS } from '../components/constant';
|
|
|
4
4
|
import { menuItemsPC } from '../components/SideMenuBarPC/constants';
|
|
5
5
|
import type { MenuItem } from '../components/SideMenuBarPC/types';
|
|
6
6
|
import type { InternalPage } from '../context/NavigationContext';
|
|
7
|
+
import type { DocumentRequestStatus, DocumentRequestStatusInfo } from './types';
|
|
7
8
|
|
|
8
9
|
export const useRequestTypeLabels = () => {
|
|
9
10
|
const { t } = useTranslation();
|
|
@@ -36,7 +37,7 @@ export const languageOptions = [
|
|
|
36
37
|
{ value: 'sd', label: 'سنڌي (Sindhi)' },
|
|
37
38
|
{ value: 'doi', label: 'डोगरी (Dogri)' },
|
|
38
39
|
{ value: 'mni-Mtei', label: 'ꯃꯩꯇꯩꯂꯣꯟ (Manipuri)' },
|
|
39
|
-
{ value: 'gom', label: 'कोंकणी (Konkani)' },
|
|
40
|
+
{ value: 'gom', label: 'कोंकणी (Goan Konkani)' },
|
|
40
41
|
{ value: 'sa', label: 'संस्कृतम् (Sanskrit)' },
|
|
41
42
|
{ value: 'bho', label: 'भोजपुरी (Bhojpuri)' },
|
|
42
43
|
{ value: 'brx', label: 'बड़ो (Bodo)' },
|
|
@@ -84,6 +85,10 @@ export const PAGE_CHROME_BY_PAGE: Record<InternalPage, PageChromeConfig> = {
|
|
|
84
85
|
titleKey: 'activities',
|
|
85
86
|
descriptionKey: 'activitiesSubtitle',
|
|
86
87
|
},
|
|
88
|
+
'case-details': {
|
|
89
|
+
titleKey: 'caseDetails',
|
|
90
|
+
descriptionKey: 'yourPrivacyCenter',
|
|
91
|
+
},
|
|
87
92
|
};
|
|
88
93
|
|
|
89
94
|
const HREF_TO_NAME_KEY: Record<string, string> = {
|
|
@@ -100,6 +105,46 @@ export const useMenuItemsPC = (): MenuItem[] => {
|
|
|
100
105
|
}));
|
|
101
106
|
};
|
|
102
107
|
|
|
108
|
+
// ─── Upload validation ────────────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
111
|
+
|
|
112
|
+
export const ALLOWED_MIME_TYPES = [
|
|
113
|
+
'application/pdf',
|
|
114
|
+
'image/jpeg',
|
|
115
|
+
'image/png',
|
|
116
|
+
'image/webp',
|
|
117
|
+
'application/msword',
|
|
118
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
119
|
+
'application/vnd.ms-excel',
|
|
120
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
121
|
+
'text/plain',
|
|
122
|
+
'text/csv',
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// ─── Status colors ───────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
export const STATUS_COLORS = {
|
|
128
|
+
success: { color: '#16a34a', bg: '#f0fdf4', border: '#bbf7d0' },
|
|
129
|
+
error: { color: '#dc2626', bg: '#fef2f2', border: '#fecaca' },
|
|
130
|
+
warning: { color: '#d97706', bg: '#fffbeb', border: '#fde68a' },
|
|
131
|
+
info: { color: '#2563eb', bg: '#f8fbff', border: '#93c5fd' },
|
|
132
|
+
infoDark: '#1d4ed8',
|
|
133
|
+
infoBanner: { color: '#1d4ed8', bg: '#eff6ff', border: '#bfdbfe' },
|
|
134
|
+
} as const;
|
|
135
|
+
|
|
136
|
+
// ─── Document request ─────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
export const UPLOAD_ACTIVE_STATUSES: DocumentRequestStatus[] = ['pending', 'replacement_requested'];
|
|
139
|
+
|
|
140
|
+
export const DOCUMENT_REQUEST_STATUS_LABEL: Record<DocumentRequestStatus, DocumentRequestStatusInfo> = {
|
|
141
|
+
pending: { labelKey: 'docStatusPending', color: STATUS_COLORS.warning.color },
|
|
142
|
+
under_review: { labelKey: 'docStatusUnderReview', color: STATUS_COLORS.info.color },
|
|
143
|
+
approved: { labelKey: 'docStatusApproved', color: STATUS_COLORS.success.color },
|
|
144
|
+
rejected: { labelKey: 'docStatusRejected', color: STATUS_COLORS.error.color },
|
|
145
|
+
replacement_requested: { labelKey: 'docStatusReplacementRequested', color: STATUS_COLORS.warning.color },
|
|
146
|
+
};
|
|
147
|
+
|
|
103
148
|
// Activity columns constants
|
|
104
149
|
export const ACTIVITY_COLUMN_SIZES = {
|
|
105
150
|
TITLE: 220,
|
|
@@ -62,7 +62,7 @@ i18n
|
|
|
62
62
|
order: ['sessionStorage', 'navigator', 'htmlTag'],
|
|
63
63
|
caches: ['sessionStorage'],
|
|
64
64
|
lookupSessionStorage: 'i18nextLng',
|
|
65
|
-
convertDetectedLanguage: (lng) => {
|
|
65
|
+
convertDetectedLanguage: (lng: string) => {
|
|
66
66
|
// Extract base language code (e.g. 'en-US' -> 'en') so it matches
|
|
67
67
|
// our supported language keys, but preserve script subtags like 'mni-Mtei'.
|
|
68
68
|
const base = lng.split('-')[0];
|