@redacto.io/consent-sdk-react 10.0.0-beta.9 → 10.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/NOTICE.md +17 -0
- package/README.md +141 -3
- package/dist/chunk-FEGTW73Q.mjs +182 -0
- package/dist/fonts.d.mts +35 -0
- package/dist/fonts.d.ts +35 -0
- package/dist/fonts.js +183 -0
- package/dist/fonts.mjs +10 -0
- package/dist/index.d.mts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +724 -365
- package/dist/index.mjs +346 -201
- package/dist/montserrat.generated-NBTECYRW.mjs +22 -0
- package/dist/privacy-center.d.mts +0 -1
- package/dist/privacy-center.d.ts +0 -1
- package/dist/privacy-center.js +2 -66
- package/dist/privacy-center.mjs +2 -66
- package/dist/types-6Aj7faxL.d.mts +57 -0
- package/dist/types-6Aj7faxL.d.ts +57 -0
- package/package.json +10 -1
- package/.turbo/turbo-build.log +0 -22
- package/.turbo/turbo-test.log +0 -203
- package/.turbo/turbo-verify-pack.log +0 -6
- package/CHANGELOG.md +0 -353
- package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.test.tsx +0 -934
- package/src/RedactoNoticeAssisted/RedactoNoticeAssisted.tsx +0 -2160
- package/src/RedactoNoticeAssisted/api/index.ts +0 -275
- package/src/RedactoNoticeAssisted/api/types.ts +0 -182
- package/src/RedactoNoticeAssisted/assets/redacto-logo.png +0 -0
- package/src/RedactoNoticeAssisted/constants.ts +0 -51
- package/src/RedactoNoticeAssisted/i18n.ts +0 -1408
- package/src/RedactoNoticeAssisted/index.ts +0 -1
- package/src/RedactoNoticeAssisted/injectStyles.ts +0 -251
- package/src/RedactoNoticeAssisted/styles.ts +0 -730
- package/src/RedactoNoticeAssisted/types.ts +0 -26
- package/src/RedactoNoticeAssisted/useMediaQuery.ts +0 -36
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +0 -1152
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +0 -4308
- package/src/RedactoNoticeConsent/api/index.ts +0 -542
- package/src/RedactoNoticeConsent/api/sandbox.ts +0 -150
- package/src/RedactoNoticeConsent/api/types.ts +0 -515
- package/src/RedactoNoticeConsent/assets/redacto-logo.png +0 -0
- package/src/RedactoNoticeConsent/components/DigiLockerCallback.tsx +0 -602
- package/src/RedactoNoticeConsent/components/index.ts +0 -7
- package/src/RedactoNoticeConsent/constants.ts +0 -20
- package/src/RedactoNoticeConsent/index.ts +0 -1
- package/src/RedactoNoticeConsent/injectStyles.ts +0 -151
- package/src/RedactoNoticeConsent/language-codes.ts +0 -95
- package/src/RedactoNoticeConsent/services/storage.ts +0 -122
- package/src/RedactoNoticeConsent/styles.ts +0 -409
- package/src/RedactoNoticeConsent/types.ts +0 -80
- package/src/RedactoNoticeConsent/useMediaQuery.ts +0 -36
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.test.tsx +0 -369
- package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +0 -687
- package/src/RedactoNoticeConsentInline/api/index.ts +0 -106
- package/src/RedactoNoticeConsentInline/api/types.ts +0 -183
- package/src/RedactoNoticeConsentInline/assets/redacto-logo.png +0 -0
- package/src/RedactoNoticeConsentInline/index.ts +0 -1
- package/src/RedactoNoticeConsentInline/injectStyles.ts +0 -40
- package/src/RedactoNoticeConsentInline/styles.ts +0 -397
- package/src/RedactoNoticeConsentInline/types.ts +0 -51
- package/src/RedactoNoticeConsentInline/useMediaQuery.ts +0 -36
- package/src/RedactoPrivacyCenter/NotificationsPage.tsx +0 -182
- package/src/RedactoPrivacyCenter/PrivacyCenterActivityPage.tsx +0 -19
- package/src/RedactoPrivacyCenter/PrivacyCenterCaseDetailsPage.tsx +0 -156
- package/src/RedactoPrivacyCenter/PrivacyCenterConsentManagerPage.tsx +0 -17
- package/src/RedactoPrivacyCenter/PrivacyCenterFormPage.tsx +0 -79
- package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +0 -36
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +0 -177
- package/src/RedactoPrivacyCenter/PrivacyCenterMyReceiptPage.tsx +0 -26
- package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +0 -93
- package/src/RedactoPrivacyCenter/api/actions.ts +0 -695
- package/src/RedactoPrivacyCenter/api/client.ts +0 -1003
- package/src/RedactoPrivacyCenter/api/config.ts +0 -33
- package/src/RedactoPrivacyCenter/api/fetcher.ts +0 -275
- package/src/RedactoPrivacyCenter/api/orgBranding.ts +0 -16
- package/src/RedactoPrivacyCenter/api/upload.ts +0 -93
- package/src/RedactoPrivacyCenter/assets/character.svg +0 -31
- package/src/RedactoPrivacyCenter/assets/fileAttach.svg +0 -9
- package/src/RedactoPrivacyCenter/assets/greenTick.svg +0 -4
- package/src/RedactoPrivacyCenter/assets/mail.svg +0 -3
- package/src/RedactoPrivacyCenter/assets/poweredByRedacto.svg +0 -9
- package/src/RedactoPrivacyCenter/assets/sentImg.svg +0 -9
- package/src/RedactoPrivacyCenter/assets/uploadFileIcon.svg +0 -9
- package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +0 -252
- package/src/RedactoPrivacyCenter/components/ActivityTimeline.tsx +0 -239
- package/src/RedactoPrivacyCenter/components/AppShell/AppShell.tsx +0 -268
- package/src/RedactoPrivacyCenter/components/AppShell/MobileNavDrawer.tsx +0 -218
- package/src/RedactoPrivacyCenter/components/AppShell/NavItem.tsx +0 -205
- package/src/RedactoPrivacyCenter/components/AppShell/PageHeader.tsx +0 -107
- package/src/RedactoPrivacyCenter/components/AppShell/SidebarNav.tsx +0 -100
- package/src/RedactoPrivacyCenter/components/AppShell/TopAppBar.tsx +0 -339
- package/src/RedactoPrivacyCenter/components/AppShell/UserMenu.tsx +0 -245
- package/src/RedactoPrivacyCenter/components/AppShell/hooks/useIsTruncated.ts +0 -28
- package/src/RedactoPrivacyCenter/components/AppShell/hooks/useKeyboardShortcut.ts +0 -40
- package/src/RedactoPrivacyCenter/components/AppShell/hooks/useSidebarState.ts +0 -62
- package/src/RedactoPrivacyCenter/components/AppShell/usePrivacyCenterExitAction.ts +0 -36
- package/src/RedactoPrivacyCenter/components/CaseDetails/AttachmentCard.tsx +0 -38
- package/src/RedactoPrivacyCenter/components/CaseDetails/ChatBubble.tsx +0 -31
- package/src/RedactoPrivacyCenter/components/CaseDetails/DocumentRequestCard.tsx +0 -79
- package/src/RedactoPrivacyCenter/components/CaseDetails/FinalizedTooltipWrapper.tsx +0 -25
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessageComposer.tsx +0 -96
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessageItem.tsx +0 -97
- package/src/RedactoPrivacyCenter/components/CaseDetails/MessagesTab.tsx +0 -156
- package/src/RedactoPrivacyCenter/components/CaseDetails/RequestDetailsTab.tsx +0 -171
- package/src/RedactoPrivacyCenter/components/CaseDetails/StatusEvent.tsx +0 -23
- package/src/RedactoPrivacyCenter/components/CaseDetails/useFileUpload.ts +0 -116
- package/src/RedactoPrivacyCenter/components/CaseDetails/useMessagePolling.ts +0 -43
- package/src/RedactoPrivacyCenter/components/CaseDetails/utils.ts +0 -71
- package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +0 -469
- package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +0 -426
- package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +0 -1206
- package/src/RedactoPrivacyCenter/components/ConsentManagerCells.tsx +0 -218
- package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +0 -138
- package/src/RedactoPrivacyCenter/components/ConsentManagerConstants.ts +0 -7
- package/src/RedactoPrivacyCenter/components/ConsentManagerExpandedRow.tsx +0 -179
- package/src/RedactoPrivacyCenter/components/ConsentManagerGroupedColumns.tsx +0 -318
- package/src/RedactoPrivacyCenter/components/ConsentManagerProductCards.tsx +0 -333
- package/src/RedactoPrivacyCenter/components/ConsentManagerPurposeCards.tsx +0 -102
- package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +0 -159
- package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +0 -189
- package/src/RedactoPrivacyCenter/components/DataRequest/GrievanceDataSection.tsx +0 -118
- package/src/RedactoPrivacyCenter/components/DataRequest/NominationDataSection.tsx +0 -66
- package/src/RedactoPrivacyCenter/components/DataRequest/ProductSelectionSection.tsx +0 -222
- package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +0 -331
- package/src/RedactoPrivacyCenter/components/Form.tsx +0 -1821
- package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +0 -174
- package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +0 -337
- package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +0 -334
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationBell.tsx +0 -173
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationCTAButton.tsx +0 -18
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdown.tsx +0 -54
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationDropdownItem.tsx +0 -57
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationIcon.tsx +0 -19
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationListPanel.tsx +0 -92
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRefreshButton.tsx +0 -20
- package/src/RedactoPrivacyCenter/components/NotificationHub/NotificationRow.tsx +0 -58
- package/src/RedactoPrivacyCenter/components/NotificationHub/StatCard.tsx +0 -10
- package/src/RedactoPrivacyCenter/components/NotificationHub/useNotificationActions.ts +0 -112
- package/src/RedactoPrivacyCenter/components/NotificationHub/utils.ts +0 -27
- package/src/RedactoPrivacyCenter/components/Pagination.tsx +0 -140
- package/src/RedactoPrivacyCenter/components/PrivacyCenterEmptyState.tsx +0 -76
- package/src/RedactoPrivacyCenter/components/PrivacyCenterSessionExpired.tsx +0 -97
- package/src/RedactoPrivacyCenter/components/ReceiptsList.tsx +0 -454
- package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +0 -270
- package/src/RedactoPrivacyCenter/components/SelectUser.tsx +0 -95
- package/src/RedactoPrivacyCenter/components/SelectUserGate.tsx +0 -195
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +0 -25
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +0 -21
- package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +0 -159
- package/src/RedactoPrivacyCenter/components/constant.ts +0 -52
- package/src/RedactoPrivacyCenter/components/groupHelpers.ts +0 -155
- package/src/RedactoPrivacyCenter/components/receiptColumns.tsx +0 -203
- package/src/RedactoPrivacyCenter/components/types.ts +0 -123
- package/src/RedactoPrivacyCenter/components/utils.tsx +0 -10
- package/src/RedactoPrivacyCenter/config/routes.ts +0 -9
- package/src/RedactoPrivacyCenter/config/table.ts +0 -25
- package/src/RedactoPrivacyCenter/context/AuthContext.tsx +0 -252
- package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +0 -47
- package/src/RedactoPrivacyCenter/context/BrandingContext.tsx +0 -52
- package/src/RedactoPrivacyCenter/context/DataAvailabilityContext.session-expired.test.tsx +0 -92
- package/src/RedactoPrivacyCenter/context/DataAvailabilityContext.tsx +0 -181
- package/src/RedactoPrivacyCenter/context/LocaleContext.tsx +0 -11
- package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +0 -96
- package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +0 -30
- package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +0 -80
- package/src/RedactoPrivacyCenter/context/ProfileSwitchContext.tsx +0 -15
- package/src/RedactoPrivacyCenter/context/types.ts +0 -57
- package/src/RedactoPrivacyCenter/lib/api.ts +0 -759
- package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +0 -67
- package/src/RedactoPrivacyCenter/lib/constants.ts +0 -283
- package/src/RedactoPrivacyCenter/lib/i18n.ts +0 -81
- package/src/RedactoPrivacyCenter/lib/locales.ts +0 -102
- package/src/RedactoPrivacyCenter/lib/queries.ts +0 -141
- package/src/RedactoPrivacyCenter/lib/sandbox.ts +0 -104
- package/src/RedactoPrivacyCenter/lib/types.ts +0 -896
- package/src/RedactoPrivacyCenter/lib/useCurrentLanguage.ts +0 -37
- package/src/RedactoPrivacyCenter/lib/utils.refresh-circuit.test.ts +0 -127
- package/src/RedactoPrivacyCenter/lib/utils.refresh-storage.test.ts +0 -85
- package/src/RedactoPrivacyCenter/lib/utils.session-expired.test.ts +0 -109
- package/src/RedactoPrivacyCenter/lib/utils.ts +0 -798
- package/src/RedactoPrivacyCenter/locales/as/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/bn/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/brx/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/doi/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/en/translation.json +0 -357
- package/src/RedactoPrivacyCenter/locales/gom/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/gu/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/hi/translation.json +0 -345
- package/src/RedactoPrivacyCenter/locales/kn/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ks/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/mai/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ml/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/mr/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ne/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/or/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/pa/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/sa/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/sat/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/sd/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ta/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/te/translation.json +0 -342
- package/src/RedactoPrivacyCenter/locales/ur/translation.json +0 -342
- package/src/RedactoPrivacyCenter/styles/applyBrandingTheme.ts +0 -66
- package/src/RedactoPrivacyCenter/styles/base.css +0 -117
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +0 -3108
- package/src/RedactoPrivacyCenter/styles/pcStyles.ts +0 -831
- package/src/RedactoPrivacyCenter/styles/responsive.ts +0 -138
- package/src/RedactoPrivacyCenter/styles/variables.css +0 -96
- package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +0 -387
- package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +0 -468
- package/src/RedactoPrivacyCenter/table/index.ts +0 -2
- package/src/RedactoPrivacyCenter/types/images.d.ts +0 -29
- package/src/RedactoPrivacyCenter/ui/Footer.tsx +0 -69
- package/src/RedactoPrivacyCenter/ui/FormBlockWrapper.tsx +0 -54
- package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +0 -93
- package/src/RedactoPrivacyCenter/ui/PCActionIcon.tsx +0 -47
- package/src/RedactoPrivacyCenter/ui/PCAvatar.test.tsx +0 -83
- package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +0 -93
- package/src/RedactoPrivacyCenter/ui/PCBadge.tsx +0 -48
- package/src/RedactoPrivacyCenter/ui/PCButton.tsx +0 -77
- package/src/RedactoPrivacyCenter/ui/PCCheckbox.tsx +0 -55
- package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +0 -107
- package/src/RedactoPrivacyCenter/ui/PCEmpty.tsx +0 -35
- package/src/RedactoPrivacyCenter/ui/PCInput.tsx +0 -132
- package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +0 -31
- package/src/RedactoPrivacyCenter/ui/PCModal.tsx +0 -106
- package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +0 -206
- package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +0 -433
- package/src/RedactoPrivacyCenter/ui/PCTable.tsx +0 -149
- package/src/RedactoPrivacyCenter/ui/PCText.tsx +0 -41
- package/src/RedactoPrivacyCenter/ui/PCTextarea.tsx +0 -43
- package/src/RedactoPrivacyCenter/ui/PCTitle.tsx +0 -37
- package/src/RedactoPrivacyCenter/ui/PCTooltip.tsx +0 -121
- package/src/RedactoPrivacyCenter/ui/SimpleBar.tsx +0 -25
- package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +0 -53
- package/src/RedactoPrivacyCenter/ui/index.ts +0 -18
- package/src/RedactoPrivacyCenter/utils/cn.ts +0 -28
- package/src/RedactoPrivacyCenter/utils/date.ts +0 -48
- package/src/RedactoPrivacyCenter/utils/status.ts +0 -111
- package/src/RedactoPrivacyCenter/utils/toast.ts +0 -130
- package/src/index.ts +0 -19
- package/src/privacy-center.ts +0 -113
- package/src/shared/api-errors.ts +0 -180
- package/src/shared/notice-consent-status.ts +0 -190
- package/src/shared/sandbox.ts +0 -6
- package/src/types/css.d.ts +0 -4
- package/src/types/images.d.ts +0 -19
- package/src/types/react-jsx.d.ts +0 -26
- package/tests/DataAvailabilityContext.test.tsx +0 -123
- package/tests/Form.test.tsx +0 -1424
- package/tests/ManageConsentErrorMapping.test.tsx +0 -85
- package/tests/ProductSelectionSection.test.tsx +0 -328
- package/tests/RequestSubmit.test.tsx +0 -135
- package/tests/RevocablePurpose.test.tsx +0 -146
- package/tests/RevokeWarningMessage.test.tsx +0 -151
- package/tests/consentClient.test.ts +0 -217
- package/tests/manageConsentServer.test.ts +0 -38
- package/tests/mocks.ts +0 -257
- package/tests/resolveContactFromJwt.test.ts +0 -44
- package/tests/setup.ts +0 -16
- package/tsconfig.json +0 -16
- package/tsup.config.ts +0 -31
- package/vitest.config.ts +0 -16
|
@@ -1,10 +0,0 @@
|
|
|
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;
|
|
@@ -1,25 +0,0 @@
|
|
|
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;
|
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
-
import { setRefreshTokenCookie, decodeJwtToken, resolveContactFromJwt, setSessionExpiredListener } from '../lib/utils';
|
|
3
|
-
import { setSandboxConfig } from '../lib/sandbox';
|
|
4
|
-
import type {
|
|
5
|
-
AuthContextValue,
|
|
6
|
-
AuthProviderProps,
|
|
7
|
-
SeedSessionStorageData,
|
|
8
|
-
} from './types';
|
|
9
|
-
|
|
10
|
-
export type { AuthContextValue } from './types';
|
|
11
|
-
|
|
12
|
-
const AuthContext = createContext<AuthContextValue | null>(null);
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Raw sessionStorage writer for the five keys the Privacy Center reads
|
|
16
|
-
* (access_token / pc_org_id / pc_workspace_id / pc_org_slug / email). Single
|
|
17
|
-
* source of truth for those key names so the JWT and sandbox seeding paths can
|
|
18
|
-
* never drift. Each key is written only when its value is a non-empty string;
|
|
19
|
-
* `accessToken` is optional because the JWT path stores it separately. The
|
|
20
|
-
* `contact` value goes under the `email` key even when it is a mobile number
|
|
21
|
-
* (matching resolveContactFromJwt, which also stores mobile/org_user_id
|
|
22
|
-
* subjects there). No-ops when `window` is unavailable (SSR).
|
|
23
|
-
*/
|
|
24
|
-
const seedSessionStorage = (data: SeedSessionStorageData): void => {
|
|
25
|
-
if (typeof window === 'undefined') {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (data.accessToken) sessionStorage.setItem('access_token', data.accessToken);
|
|
29
|
-
if (data.orgId) sessionStorage.setItem('pc_org_id', data.orgId);
|
|
30
|
-
if (data.workspaceId) sessionStorage.setItem('pc_workspace_id', data.workspaceId);
|
|
31
|
-
if (data.slug) sessionStorage.setItem('pc_org_slug', data.slug);
|
|
32
|
-
if (data.contact) sessionStorage.setItem('email', data.contact);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Seed the sessionStorage keys the Privacy Center reads (org/workspace/slug/
|
|
37
|
-
* email/access_token) from the sandbox props and register the module-level
|
|
38
|
-
* sandbox config, so the fetch + auth layers switch to sandbox headers and skip
|
|
39
|
-
* token refresh. Returns true when sandbox mode was configured.
|
|
40
|
-
*/
|
|
41
|
-
const configureSandbox = (props: AuthProviderProps): boolean => {
|
|
42
|
-
const token = props.token?.trim();
|
|
43
|
-
if (!token) {
|
|
44
|
-
setSandboxConfig(null);
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const organisationUuid = props.organisationUuid?.trim() ?? '';
|
|
49
|
-
const workspaceUuid = props.workspaceUuid?.trim() ?? '';
|
|
50
|
-
const ucic = props.sandboxUcic?.trim() ?? '';
|
|
51
|
-
const subject = props.sandboxSubject?.trim() ?? '';
|
|
52
|
-
const slug = props.slug?.trim() ?? '';
|
|
53
|
-
|
|
54
|
-
setSandboxConfig({
|
|
55
|
-
token,
|
|
56
|
-
ucic: ucic || undefined,
|
|
57
|
-
subject,
|
|
58
|
-
organisationUuid,
|
|
59
|
-
workspaceUuid,
|
|
60
|
-
slug,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
seedSessionStorage({
|
|
64
|
-
accessToken: token,
|
|
65
|
-
orgId: organisationUuid,
|
|
66
|
-
workspaceId: workspaceUuid,
|
|
67
|
-
slug,
|
|
68
|
-
contact: ucic || subject,
|
|
69
|
-
});
|
|
70
|
-
return true;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Extract and store organization/auth data from JWT token to sessionStorage
|
|
75
|
-
* This makes the SDK self-sufficient - it doesn't depend on host app to set these values
|
|
76
|
-
*/
|
|
77
|
-
const syncJwtDataToSessionStorage = (accessToken: string): void => {
|
|
78
|
-
if (typeof window === 'undefined') return;
|
|
79
|
-
|
|
80
|
-
const decoded = decodeJwtToken(accessToken);
|
|
81
|
-
if (!decoded) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Resolve the user's contact identifier (email, or mobile/org_user_id for
|
|
86
|
-
// mobile-only logins) so case + consent-management actions have a subject.
|
|
87
|
-
// Falls back to previously stored values when the token omits identifiers.
|
|
88
|
-
const contact = resolveContactFromJwt(decoded)
|
|
89
|
-
|| sessionStorage.getItem('email')
|
|
90
|
-
|| (typeof localStorage !== 'undefined' ? localStorage.getItem('userEmail') : null);
|
|
91
|
-
|
|
92
|
-
// The access_token is stored separately by the caller (state initializer /
|
|
93
|
-
// updateTokens), so it is intentionally omitted here.
|
|
94
|
-
seedSessionStorage({
|
|
95
|
-
orgId: typeof decoded.organisation_uuid === 'string' ? decoded.organisation_uuid : undefined,
|
|
96
|
-
workspaceId: typeof decoded.workspace_uuid === 'string' ? decoded.workspace_uuid : undefined,
|
|
97
|
-
slug: typeof decoded.organisation_slug === 'string' ? decoded.organisation_slug : undefined,
|
|
98
|
-
contact: typeof contact === 'string' ? contact : undefined,
|
|
99
|
-
});
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export const AuthProvider = (props: AuthProviderProps) => {
|
|
103
|
-
const {
|
|
104
|
-
children,
|
|
105
|
-
accessToken: initialAccessToken,
|
|
106
|
-
refreshToken: initialRefreshToken,
|
|
107
|
-
token: sandboxToken,
|
|
108
|
-
sandboxUcic,
|
|
109
|
-
sandboxSubject,
|
|
110
|
-
organisationUuid,
|
|
111
|
-
workspaceUuid,
|
|
112
|
-
slug,
|
|
113
|
-
onError,
|
|
114
|
-
} = props;
|
|
115
|
-
|
|
116
|
-
// Sandbox PoC: configure the module singleton + seed sessionStorage BEFORE any
|
|
117
|
-
// child renders (same reason the JWT sync runs in the state initializer). This
|
|
118
|
-
// MUST run during render, not in a useEffect: child effects (which fire the
|
|
119
|
-
// first data fetch and read `sandboxAuthHeaders()` synchronously) run on commit
|
|
120
|
-
// BEFORE the parent's effects, so an effect here would configure the singleton
|
|
121
|
-
// too late. A useState lazy initializer runs once on the first render, ahead of
|
|
122
|
-
// any child, and is not re-run on subsequent parent re-renders — so it sets the
|
|
123
|
-
// config before the first fetch without the every-render side effect. When
|
|
124
|
-
// active, the JWT path below is skipped entirely.
|
|
125
|
-
const [sandboxActive] = useState(() => configureSandbox(props));
|
|
126
|
-
|
|
127
|
-
// Sync to sessionStorage synchronously during initialization so child
|
|
128
|
-
// components (ConsentManager, SideBarPC) find the data when they mount.
|
|
129
|
-
// useEffect runs AFTER children render — too late for components that
|
|
130
|
-
// read sessionStorage during their render phase.
|
|
131
|
-
const [accessToken, setAccessToken] = useState(() => {
|
|
132
|
-
if (sandboxActive) {
|
|
133
|
-
return sandboxToken ?? '';
|
|
134
|
-
}
|
|
135
|
-
if (initialAccessToken && typeof initialAccessToken === 'string' && initialAccessToken.trim() !== '') {
|
|
136
|
-
if (typeof window !== 'undefined') {
|
|
137
|
-
sessionStorage.setItem('access_token', initialAccessToken);
|
|
138
|
-
syncJwtDataToSessionStorage(initialAccessToken);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return initialAccessToken ?? '';
|
|
142
|
-
});
|
|
143
|
-
const [refreshToken, setRefreshToken] = useState(() => {
|
|
144
|
-
if (sandboxActive) {
|
|
145
|
-
return '';
|
|
146
|
-
}
|
|
147
|
-
if (initialRefreshToken && typeof initialRefreshToken === 'string' && initialRefreshToken.trim() !== '') {
|
|
148
|
-
if (typeof window !== 'undefined') {
|
|
149
|
-
setRefreshTokenCookie(initialRefreshToken);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return initialRefreshToken ?? '';
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Terminal "session expired" flag. Set by utils' refresh routines (via the
|
|
156
|
-
// module listener registered below) when the refresh token is dead; reset when
|
|
157
|
-
// a new refresh token appears (re-auth) so the breaker and the screen recover
|
|
158
|
-
// together. Drives the shared session-expired screen and gates all fetching.
|
|
159
|
-
const [sessionExpired, setSessionExpired] = useState(false);
|
|
160
|
-
const markSessionExpired = useCallback(() => setSessionExpired(true), []);
|
|
161
|
-
|
|
162
|
-
// Bridge the module-level refresh routines to React state. utils calls the
|
|
163
|
-
// registered listener on terminal failure; this is the same single-instance
|
|
164
|
-
// singleton model as the OTP refresh circuit breaker.
|
|
165
|
-
useEffect(() => {
|
|
166
|
-
setSessionExpiredListener(markSessionExpired);
|
|
167
|
-
return () => setSessionExpiredListener(null);
|
|
168
|
-
}, [markSessionExpired]);
|
|
169
|
-
|
|
170
|
-
// Re-sync when props change (for token refresh scenarios). Sandbox mode has no
|
|
171
|
-
// JWT and no refresh, so these are no-ops there.
|
|
172
|
-
useEffect(() => {
|
|
173
|
-
if (sandboxActive) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (initialAccessToken && typeof initialAccessToken === 'string' && initialAccessToken.trim() !== '') {
|
|
177
|
-
setAccessToken(initialAccessToken);
|
|
178
|
-
if (typeof window !== 'undefined') {
|
|
179
|
-
sessionStorage.setItem('access_token', initialAccessToken);
|
|
180
|
-
syncJwtDataToSessionStorage(initialAccessToken);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}, [initialAccessToken, sandboxActive]);
|
|
184
|
-
|
|
185
|
-
useEffect(() => {
|
|
186
|
-
if (sandboxActive) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (initialRefreshToken && typeof initialRefreshToken === 'string' && initialRefreshToken.trim() !== '') {
|
|
190
|
-
setRefreshToken(initialRefreshToken);
|
|
191
|
-
// A fresh refresh token means the user re-authenticated — clear the
|
|
192
|
-
// terminal state (the breaker auto-re-arms on the next ensureValidToken).
|
|
193
|
-
setSessionExpired(false);
|
|
194
|
-
if (typeof window !== 'undefined') {
|
|
195
|
-
setRefreshTokenCookie(initialRefreshToken);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}, [initialRefreshToken, sandboxActive]);
|
|
199
|
-
|
|
200
|
-
// Sandbox PoC: re-configure the module singleton when the sandbox inputs
|
|
201
|
-
// change after mount (the lazy initializer above only runs once, on first
|
|
202
|
-
// render — this mirrors the JWT re-sync effects for prop changes). Clears the
|
|
203
|
-
// singleton on unmount so a later JWT-mode mount never inherits a stale sandbox
|
|
204
|
-
// config. First-fetch ordering is unaffected: the initializer already set the
|
|
205
|
-
// config before any child effect ran.
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
if (!sandboxActive) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
configureSandbox(props);
|
|
211
|
-
return () => setSandboxConfig(null);
|
|
212
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
213
|
-
}, [sandboxActive, sandboxToken, sandboxUcic, sandboxSubject, organisationUuid, workspaceUuid, slug]);
|
|
214
|
-
|
|
215
|
-
// Internal method to update tokens after refresh
|
|
216
|
-
const updateTokens = useCallback((newAccessToken: string, newRefreshToken?: string) => {
|
|
217
|
-
setAccessToken(newAccessToken);
|
|
218
|
-
if (newRefreshToken) {
|
|
219
|
-
setRefreshToken(newRefreshToken);
|
|
220
|
-
}
|
|
221
|
-
// A successful refresh clears any prior terminal state.
|
|
222
|
-
setSessionExpired(false);
|
|
223
|
-
// Also update storage
|
|
224
|
-
if (typeof window !== 'undefined') {
|
|
225
|
-
sessionStorage.setItem('access_token', newAccessToken);
|
|
226
|
-
// Extract and store org/auth data from JWT
|
|
227
|
-
syncJwtDataToSessionStorage(newAccessToken);
|
|
228
|
-
if (newRefreshToken) {
|
|
229
|
-
setRefreshTokenCookie(newRefreshToken);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}, []);
|
|
233
|
-
|
|
234
|
-
// Memoize context value to prevent unnecessary re-renders
|
|
235
|
-
const value: AuthContextValue = useMemo(() => ({
|
|
236
|
-
accessToken,
|
|
237
|
-
refreshToken,
|
|
238
|
-
onError,
|
|
239
|
-
updateTokens,
|
|
240
|
-
sessionExpired,
|
|
241
|
-
}), [accessToken, refreshToken, onError, updateTokens, sessionExpired]);
|
|
242
|
-
|
|
243
|
-
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
export const useAuth = (): AuthContextValue => {
|
|
247
|
-
const context = useContext(AuthContext);
|
|
248
|
-
if (!context) {
|
|
249
|
-
throw new Error('useAuth must be used within an AuthProvider');
|
|
250
|
-
}
|
|
251
|
-
return context;
|
|
252
|
-
};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, type ReactNode } from 'react';
|
|
2
|
-
import { createConsentClient } from '../api/client';
|
|
3
|
-
import type { BaseUrlContextValue } from './types';
|
|
4
|
-
|
|
5
|
-
export type { BaseUrlContextValue } from './types';
|
|
6
|
-
|
|
7
|
-
export const BaseUrlContext = createContext<BaseUrlContextValue | null>(null);
|
|
8
|
-
|
|
9
|
-
export const BaseUrlProvider = ({
|
|
10
|
-
children,
|
|
11
|
-
baseUrl,
|
|
12
|
-
ledgerBaseUrl,
|
|
13
|
-
}: {
|
|
14
|
-
children: ReactNode;
|
|
15
|
-
baseUrl: string; // Consent server base URL
|
|
16
|
-
ledgerBaseUrl?: string; // Go ledger base URL — serves user-consents
|
|
17
|
-
}) => {
|
|
18
|
-
const consentClient = useMemo(
|
|
19
|
-
() => createConsentClient(baseUrl, ledgerBaseUrl),
|
|
20
|
-
[baseUrl, ledgerBaseUrl]
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const value: BaseUrlContextValue = useMemo(
|
|
24
|
-
() => ({
|
|
25
|
-
baseUrl,
|
|
26
|
-
ledgerBaseUrl,
|
|
27
|
-
consentClient,
|
|
28
|
-
}),
|
|
29
|
-
[baseUrl, ledgerBaseUrl, consentClient]
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<BaseUrlContext.Provider value={value}>
|
|
34
|
-
{children}
|
|
35
|
-
</BaseUrlContext.Provider>
|
|
36
|
-
);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const useBaseUrl = () => {
|
|
40
|
-
const context = useContext(BaseUrlContext);
|
|
41
|
-
if (!context) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
'useBaseUrl must be used within a BaseUrlProvider'
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
return context;
|
|
47
|
-
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
|
|
2
|
-
import { fetchOrgBranding } from '../api/orgBranding';
|
|
3
|
-
import { applyBrandingTheme, clearBrandingTheme } from '../styles/applyBrandingTheme';
|
|
4
|
-
import type { OrgBrandingDetail } from '../lib/types';
|
|
5
|
-
|
|
6
|
-
type BrandingContextValue = { branding: OrgBrandingDetail | null };
|
|
7
|
-
|
|
8
|
-
const BrandingContext = createContext<BrandingContextValue | null>(null);
|
|
9
|
-
|
|
10
|
-
export const BrandingProvider = ({
|
|
11
|
-
children,
|
|
12
|
-
slug,
|
|
13
|
-
baseUrl,
|
|
14
|
-
}: {
|
|
15
|
-
children: ReactNode;
|
|
16
|
-
slug: string;
|
|
17
|
-
baseUrl: string;
|
|
18
|
-
}) => {
|
|
19
|
-
const [branding, setBranding] = useState<OrgBrandingDetail | null>(null);
|
|
20
|
-
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (!slug) return;
|
|
23
|
-
let cancelled = false;
|
|
24
|
-
fetchOrgBranding(slug, baseUrl)
|
|
25
|
-
.then((data) => {
|
|
26
|
-
if (!cancelled) setBranding(data);
|
|
27
|
-
})
|
|
28
|
-
.catch((error) => {
|
|
29
|
-
console.warn('[RedactoPrivacyCenter] org branding fetch failed:', error);
|
|
30
|
-
});
|
|
31
|
-
return () => {
|
|
32
|
-
cancelled = true;
|
|
33
|
-
};
|
|
34
|
-
}, [slug, baseUrl]);
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (!branding) return;
|
|
38
|
-
applyBrandingTheme(branding.theme);
|
|
39
|
-
return clearBrandingTheme;
|
|
40
|
-
}, [branding]);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<BrandingContext.Provider value={{ branding }}>
|
|
44
|
-
{children}
|
|
45
|
-
</BrandingContext.Provider>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const useBranding = (): OrgBrandingDetail | null => {
|
|
50
|
-
const ctx = useContext(BrandingContext);
|
|
51
|
-
return ctx?.branding ?? null;
|
|
52
|
-
};
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { render, screen, waitFor, cleanup } from '@testing-library/react';
|
|
3
|
-
import {
|
|
4
|
-
DataAvailabilityProvider,
|
|
5
|
-
useDataAvailability,
|
|
6
|
-
} from './DataAvailabilityContext';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Regression: when the session is terminally expired, the DataAvailabilityProvider
|
|
10
|
-
* must NOT fire any data fetch on mount — it goes straight to the 'session-expired'
|
|
11
|
-
* status so the shell renders the terminal screen instead of (fail-open) content.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
// Mutable auth state shared with the hoisted mock so each test can flip it.
|
|
15
|
-
const auth = vi.hoisted(() => ({
|
|
16
|
-
current: {
|
|
17
|
-
accessToken: 'stale-access-token',
|
|
18
|
-
refreshToken: 'dead-refresh-token',
|
|
19
|
-
onError: () => false,
|
|
20
|
-
updateTokens: () => {},
|
|
21
|
-
sessionExpired: true,
|
|
22
|
-
},
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
vi.mock('../lib/api', () => ({
|
|
26
|
-
fetchUserConsents: vi.fn(() => Promise.resolve(null)),
|
|
27
|
-
fetchActivities: vi.fn(() => Promise.resolve(null)),
|
|
28
|
-
fetchCaseHistory: vi.fn(() => Promise.resolve(null)),
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
|
-
vi.mock('./AuthContext', () => ({ useAuth: () => auth.current }));
|
|
32
|
-
vi.mock('./OrganizationContext', () => ({
|
|
33
|
-
useOrganization: () => ({
|
|
34
|
-
organization: { uuid: 'org-uuid', workspaces: [{ uuid: 'ws-uuid' }] },
|
|
35
|
-
}),
|
|
36
|
-
}));
|
|
37
|
-
vi.mock('./BaseUrlContext', () => ({ useBaseUrl: () => ({ consentClient: {} }) }));
|
|
38
|
-
vi.mock('../lib/useCurrentLanguage', () => ({
|
|
39
|
-
useCurrentLanguage: () => ({ langParam: 'en' }),
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
import { fetchUserConsents, fetchActivities, fetchCaseHistory } from '../lib/api';
|
|
43
|
-
|
|
44
|
-
function StatusProbe() {
|
|
45
|
-
const { status } = useDataAvailability();
|
|
46
|
-
return <div data-testid="status">{status}</div>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
describe('DataAvailabilityProvider — session-expired gate', () => {
|
|
50
|
-
beforeEach(() => {
|
|
51
|
-
vi.clearAllMocks();
|
|
52
|
-
sessionStorage.clear();
|
|
53
|
-
sessionStorage.setItem('email', 'user@example.com');
|
|
54
|
-
auth.current = {
|
|
55
|
-
accessToken: 'stale-access-token',
|
|
56
|
-
refreshToken: 'dead-refresh-token',
|
|
57
|
-
onError: () => false,
|
|
58
|
-
updateTokens: () => {},
|
|
59
|
-
sessionExpired: true,
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
afterEach(() => cleanup());
|
|
64
|
-
|
|
65
|
-
it('does not fetch and reports session-expired when the session is expired', () => {
|
|
66
|
-
auth.current.sessionExpired = true;
|
|
67
|
-
|
|
68
|
-
render(
|
|
69
|
-
<DataAvailabilityProvider>
|
|
70
|
-
<StatusProbe />
|
|
71
|
-
</DataAvailabilityProvider>,
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
expect(screen.getByTestId('status').textContent).toBe('session-expired');
|
|
75
|
-
expect(fetchUserConsents).not.toHaveBeenCalled();
|
|
76
|
-
expect(fetchActivities).not.toHaveBeenCalled();
|
|
77
|
-
expect(fetchCaseHistory).not.toHaveBeenCalled();
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('does fetch when the session is NOT expired (gate is meaningful)', async () => {
|
|
81
|
-
auth.current.sessionExpired = false;
|
|
82
|
-
|
|
83
|
-
render(
|
|
84
|
-
<DataAvailabilityProvider>
|
|
85
|
-
<StatusProbe />
|
|
86
|
-
</DataAvailabilityProvider>,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
await waitFor(() => expect(fetchUserConsents).toHaveBeenCalled());
|
|
90
|
-
expect(fetchActivities).toHaveBeenCalled();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createContext,
|
|
3
|
-
useCallback,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
type ReactNode,
|
|
10
|
-
} from 'react';
|
|
11
|
-
import { fetchUserConsents, fetchActivities, fetchCaseHistory } from '../lib/api';
|
|
12
|
-
import { useCurrentLanguage } from '../lib/useCurrentLanguage';
|
|
13
|
-
import { useOrganization } from './OrganizationContext';
|
|
14
|
-
import { useAuth } from './AuthContext';
|
|
15
|
-
import { useBaseUrl } from './BaseUrlContext';
|
|
16
|
-
|
|
17
|
-
export type DataAvailabilityStatus =
|
|
18
|
-
| 'checking'
|
|
19
|
-
| 'has-data'
|
|
20
|
-
| 'empty'
|
|
21
|
-
| 'session-expired';
|
|
22
|
-
|
|
23
|
-
type DataAvailabilityContextValue = {
|
|
24
|
-
status: DataAvailabilityStatus;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const DataAvailabilityContext = createContext<DataAvailabilityContextValue | null>(null);
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Determines whether the authenticated user has ANY data in the Privacy Center
|
|
31
|
-
* (consents, data-request cases, or activity). When all three are empty we
|
|
32
|
-
* surface a dedicated empty state instead of dropping the user into a shell
|
|
33
|
-
* full of empty tables.
|
|
34
|
-
*
|
|
35
|
-
* Fail-open by design: the underlying fetchers return `null` on non-auth
|
|
36
|
-
* errors (and may reject if a post-refresh retry throws). A `null`/rejected
|
|
37
|
-
* signal is treated as "has data" so a transient or auth error never wrongly
|
|
38
|
-
* locks a user out — the empty state shows ONLY when all three signals
|
|
39
|
-
* conclusively resolve to empty.
|
|
40
|
-
*/
|
|
41
|
-
export const DataAvailabilityProvider = ({ children }: { children: ReactNode }) => {
|
|
42
|
-
const { organization } = useOrganization();
|
|
43
|
-
const { accessToken, refreshToken, onError, updateTokens, sessionExpired } = useAuth();
|
|
44
|
-
const { consentClient } = useBaseUrl();
|
|
45
|
-
const { langParam } = useCurrentLanguage();
|
|
46
|
-
|
|
47
|
-
const organizationId = organization?.uuid;
|
|
48
|
-
const workspaceId = organization?.workspaces[0]?.uuid;
|
|
49
|
-
|
|
50
|
-
// Memoized to keep the fetch identity stable across renders (mirrors the
|
|
51
|
-
// canonical pattern in ActivitiesList / AuthContext).
|
|
52
|
-
const authContext = useMemo(
|
|
53
|
-
() => ({ accessToken, refreshToken, onError, updateTokens }),
|
|
54
|
-
[accessToken, refreshToken, onError, updateTokens],
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const [status, setStatus] = useState<DataAvailabilityStatus>('checking');
|
|
58
|
-
|
|
59
|
-
// Monotonic run id guards against a stale in-flight check (org/token changed)
|
|
60
|
-
// overwriting a newer one; mountedRef guards against setState after unmount.
|
|
61
|
-
const runIdRef = useRef(0);
|
|
62
|
-
const mountedRef = useRef(true);
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
mountedRef.current = true;
|
|
65
|
-
return () => {
|
|
66
|
-
mountedRef.current = false;
|
|
67
|
-
};
|
|
68
|
-
}, []);
|
|
69
|
-
|
|
70
|
-
const runCheck = useCallback(async () => {
|
|
71
|
-
if (!organizationId || !workspaceId) return;
|
|
72
|
-
|
|
73
|
-
const runId = ++runIdRef.current;
|
|
74
|
-
setStatus('checking');
|
|
75
|
-
|
|
76
|
-
// Cases require the user's email (same source the request Form reads).
|
|
77
|
-
const contact =
|
|
78
|
-
typeof window !== 'undefined' ? sessionStorage.getItem('email') : null;
|
|
79
|
-
|
|
80
|
-
// Each signal resolves to a boolean "is empty". A `null` result (the
|
|
81
|
-
// fetchers' non-auth error path) or a rejection (rare post-refresh retry
|
|
82
|
-
// throw) resolves to `false` so the signal counts as "has data" — the
|
|
83
|
-
// empty state shows ONLY when all three conclusively resolve to empty.
|
|
84
|
-
const consentsEmptyP: Promise<boolean> = fetchUserConsents({
|
|
85
|
-
organizationId,
|
|
86
|
-
workspaceId,
|
|
87
|
-
limit: 1,
|
|
88
|
-
nominatedLimit: 1,
|
|
89
|
-
language: langParam,
|
|
90
|
-
authContext,
|
|
91
|
-
consentClient,
|
|
92
|
-
})
|
|
93
|
-
.then(
|
|
94
|
-
(v) =>
|
|
95
|
-
!!v &&
|
|
96
|
-
v.status_summary?.total_purposes === 0 &&
|
|
97
|
-
(v.direct?.length ?? 0) === 0 &&
|
|
98
|
-
(v.nominated?.length ?? 0) === 0 &&
|
|
99
|
-
(v.nominators?.length ?? 0) === 0,
|
|
100
|
-
)
|
|
101
|
-
.catch(() => false);
|
|
102
|
-
|
|
103
|
-
// No contact => we can't check cases, so treat as empty and rely on the
|
|
104
|
-
// other two signals' fail-open. With a contact, a null/rejected result
|
|
105
|
-
// fails open (not empty).
|
|
106
|
-
const casesEmptyP: Promise<boolean> = contact
|
|
107
|
-
? fetchCaseHistory(
|
|
108
|
-
organizationId,
|
|
109
|
-
workspaceId,
|
|
110
|
-
contact,
|
|
111
|
-
0,
|
|
112
|
-
1,
|
|
113
|
-
authContext,
|
|
114
|
-
consentClient,
|
|
115
|
-
langParam,
|
|
116
|
-
)
|
|
117
|
-
.then((v) => !!v && (v.data?.length ?? 0) === 0)
|
|
118
|
-
.catch(() => false)
|
|
119
|
-
: Promise.resolve(true);
|
|
120
|
-
|
|
121
|
-
const activitiesEmptyP: Promise<boolean> = fetchActivities(
|
|
122
|
-
organizationId,
|
|
123
|
-
workspaceId,
|
|
124
|
-
0,
|
|
125
|
-
1,
|
|
126
|
-
authContext,
|
|
127
|
-
consentClient,
|
|
128
|
-
langParam,
|
|
129
|
-
)
|
|
130
|
-
.then((v) => !!v && (v.pagination?.total_count ?? 0) === 0)
|
|
131
|
-
.catch(() => false);
|
|
132
|
-
|
|
133
|
-
const [consentsEmpty, casesEmpty, activitiesEmpty] = await Promise.all([
|
|
134
|
-
consentsEmptyP,
|
|
135
|
-
casesEmptyP,
|
|
136
|
-
activitiesEmptyP,
|
|
137
|
-
]);
|
|
138
|
-
|
|
139
|
-
// Ignore if a newer check superseded this one or we unmounted.
|
|
140
|
-
if (!mountedRef.current || runId !== runIdRef.current) return;
|
|
141
|
-
|
|
142
|
-
setStatus(
|
|
143
|
-
consentsEmpty && casesEmpty && activitiesEmpty ? 'empty' : 'has-data',
|
|
144
|
-
);
|
|
145
|
-
}, [organizationId, workspaceId, authContext, consentClient, langParam]);
|
|
146
|
-
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
// Terminal session: never fetch. Bump the run id so any in-flight runCheck
|
|
149
|
-
// resolves into a no-op (its fail-open path would otherwise flip the status
|
|
150
|
-
// back to 'has-data' and remount the content), then show the expired screen.
|
|
151
|
-
if (sessionExpired) {
|
|
152
|
-
runIdRef.current += 1;
|
|
153
|
-
setStatus('session-expired');
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (organizationId && workspaceId && accessToken) {
|
|
157
|
-
void runCheck();
|
|
158
|
-
}
|
|
159
|
-
}, [organizationId, workspaceId, accessToken, runCheck, sessionExpired]);
|
|
160
|
-
|
|
161
|
-
const value = useMemo<DataAvailabilityContextValue>(
|
|
162
|
-
() => ({ status }),
|
|
163
|
-
[status],
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<DataAvailabilityContext.Provider value={value}>
|
|
168
|
-
{children}
|
|
169
|
-
</DataAvailabilityContext.Provider>
|
|
170
|
-
);
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
export const useDataAvailability = (): DataAvailabilityContextValue => {
|
|
174
|
-
const ctx = useContext(DataAvailabilityContext);
|
|
175
|
-
if (!ctx) {
|
|
176
|
-
throw new Error(
|
|
177
|
-
'useDataAvailability must be used within a DataAvailabilityProvider',
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
return ctx;
|
|
181
|
-
};
|