@redacto.io/consent-sdk-react 3.4.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +176 -0
- package/dist/index.d.mts +51 -4
- package/dist/index.d.ts +51 -4
- package/dist/index.js +1912 -345
- package/dist/index.mjs +1911 -345
- package/dist/privacy-center.d.mts +566 -0
- package/dist/privacy-center.d.ts +566 -0
- package/dist/privacy-center.js +17174 -0
- package/dist/privacy-center.mjs +17136 -0
- package/package.json +47 -3
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +201 -118
- package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +1194 -93
- package/src/RedactoNoticeConsent/api/index.ts +337 -246
- package/src/RedactoNoticeConsent/api/types.ts +140 -41
- package/src/RedactoNoticeConsent/components/DigiLockerCallback.tsx +602 -0
- package/src/RedactoNoticeConsent/components/index.ts +7 -0
- package/src/RedactoNoticeConsent/injectStyles.ts +11 -0
- package/src/RedactoNoticeConsent/language-codes.ts +61 -0
- package/src/RedactoNoticeConsent/services/storage.ts +122 -0
- package/src/RedactoNoticeConsent/types.ts +2 -0
- package/src/RedactoNoticeConsentInline/api/index.ts +26 -64
- package/src/RedactoNoticeConsentInline/api/types.ts +0 -25
- package/src/RedactoPrivacyCenter/PrivacyCenterActivityPage.tsx +19 -0
- package/src/RedactoPrivacyCenter/PrivacyCenterConsentManagerPage.tsx +17 -0
- package/src/RedactoPrivacyCenter/PrivacyCenterFormPage.tsx +79 -0
- package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +27 -0
- package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +142 -0
- package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +51 -0
- package/src/RedactoPrivacyCenter/api/actions.ts +444 -0
- package/src/RedactoPrivacyCenter/api/client.ts +413 -0
- package/src/RedactoPrivacyCenter/api/config.ts +33 -0
- package/src/RedactoPrivacyCenter/api/fetcher.ts +181 -0
- package/src/RedactoPrivacyCenter/api/upload.ts +93 -0
- package/src/RedactoPrivacyCenter/assets/character.svg +31 -0
- package/src/RedactoPrivacyCenter/assets/fileAttach.svg +9 -0
- package/src/RedactoPrivacyCenter/assets/greenTick.svg +4 -0
- package/src/RedactoPrivacyCenter/assets/mail.svg +3 -0
- package/src/RedactoPrivacyCenter/assets/poweredByRedacto.svg +9 -0
- package/src/RedactoPrivacyCenter/assets/sentImg.svg +9 -0
- package/src/RedactoPrivacyCenter/assets/uploadFileIcon.svg +9 -0
- package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +272 -0
- package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +468 -0
- package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +433 -0
- package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +726 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +239 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerConstants.ts +7 -0
- package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +155 -0
- package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +189 -0
- package/src/RedactoPrivacyCenter/components/DataRequest/GrievanceDataSection.tsx +118 -0
- package/src/RedactoPrivacyCenter/components/DataRequest/NominationDataSection.tsx +66 -0
- package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +305 -0
- package/src/RedactoPrivacyCenter/components/Form.tsx +1455 -0
- package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +176 -0
- package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +330 -0
- package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +329 -0
- package/src/RedactoPrivacyCenter/components/Pagination.tsx +140 -0
- package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +302 -0
- package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +218 -0
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +475 -0
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideMenuPC.tsx +412 -0
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +42 -0
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/menuItemsPC.tsx +1 -0
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +40 -0
- package/src/RedactoPrivacyCenter/components/SideMenuBarPC/utils.ts +38 -0
- package/src/RedactoPrivacyCenter/components/ThemeTransitionOverlay.tsx +85 -0
- package/src/RedactoPrivacyCenter/components/activitiesColumns.tsx +141 -0
- package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +179 -0
- package/src/RedactoPrivacyCenter/components/constant.ts +20 -0
- package/src/RedactoPrivacyCenter/components/types.ts +68 -0
- package/src/RedactoPrivacyCenter/components/utils.tsx +10 -0
- package/src/RedactoPrivacyCenter/config/routes.ts +9 -0
- package/src/RedactoPrivacyCenter/config/table.ts +25 -0
- package/src/RedactoPrivacyCenter/context/AuthContext.tsx +123 -0
- package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +44 -0
- package/src/RedactoPrivacyCenter/context/LocaleContext.tsx +11 -0
- package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +64 -0
- package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +30 -0
- package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +60 -0
- package/src/RedactoPrivacyCenter/context/theme.tsx +127 -0
- package/src/RedactoPrivacyCenter/lib/api.ts +468 -0
- package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +63 -0
- package/src/RedactoPrivacyCenter/lib/constants.ts +116 -0
- package/src/RedactoPrivacyCenter/lib/i18n.ts +81 -0
- package/src/RedactoPrivacyCenter/lib/locales.ts +105 -0
- package/src/RedactoPrivacyCenter/lib/queries.ts +30 -0
- package/src/RedactoPrivacyCenter/lib/types.ts +346 -0
- package/src/RedactoPrivacyCenter/lib/useCurrentLanguage.ts +37 -0
- package/src/RedactoPrivacyCenter/lib/utils.ts +625 -0
- package/src/RedactoPrivacyCenter/locales/as/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/bho/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/bn/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/brx/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/doi/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/en/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/gom/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/gu/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/hi/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/kn/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/ks/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/mai/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/ml/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/mr/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/ne/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/or/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/pa/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/sa/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/sat/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/sd/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/ta/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/te/translation.json +188 -0
- package/src/RedactoPrivacyCenter/locales/ur/translation.json +188 -0
- package/src/RedactoPrivacyCenter/styles/base.css +117 -0
- package/src/RedactoPrivacyCenter/styles/injectStyles.ts +1000 -0
- package/src/RedactoPrivacyCenter/styles/pcStyles.ts +819 -0
- package/src/RedactoPrivacyCenter/styles/responsive.ts +138 -0
- package/src/RedactoPrivacyCenter/styles/variables.css +133 -0
- package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +365 -0
- package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +479 -0
- package/src/RedactoPrivacyCenter/table/index.ts +2 -0
- package/src/RedactoPrivacyCenter/types/images.d.ts +29 -0
- package/src/RedactoPrivacyCenter/ui/Footer.tsx +73 -0
- package/src/RedactoPrivacyCenter/ui/FormBlockWrapper.tsx +54 -0
- package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +87 -0
- package/src/RedactoPrivacyCenter/ui/PCActionIcon.tsx +47 -0
- package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +69 -0
- package/src/RedactoPrivacyCenter/ui/PCBadge.tsx +48 -0
- package/src/RedactoPrivacyCenter/ui/PCButton.tsx +77 -0
- package/src/RedactoPrivacyCenter/ui/PCCheckbox.tsx +55 -0
- package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +97 -0
- package/src/RedactoPrivacyCenter/ui/PCEmpty.tsx +35 -0
- package/src/RedactoPrivacyCenter/ui/PCInput.tsx +128 -0
- package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +30 -0
- package/src/RedactoPrivacyCenter/ui/PCModal.tsx +104 -0
- package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +175 -0
- package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +381 -0
- package/src/RedactoPrivacyCenter/ui/PCTable.tsx +137 -0
- package/src/RedactoPrivacyCenter/ui/PCText.tsx +39 -0
- package/src/RedactoPrivacyCenter/ui/PCTextarea.tsx +43 -0
- package/src/RedactoPrivacyCenter/ui/PCTitle.tsx +37 -0
- package/src/RedactoPrivacyCenter/ui/PCTooltip.tsx +119 -0
- package/src/RedactoPrivacyCenter/ui/SimpleBar.tsx +25 -0
- package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +55 -0
- package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +70 -0
- package/src/RedactoPrivacyCenter/ui/index.ts +18 -0
- package/src/RedactoPrivacyCenter/utils/cn.ts +28 -0
- package/src/RedactoPrivacyCenter/utils/date.ts +48 -0
- package/src/RedactoPrivacyCenter/utils/status.ts +111 -0
- package/src/RedactoPrivacyCenter/utils/toast.ts +130 -0
- package/src/index.ts +10 -0
- package/src/privacy-center.ts +107 -0
- package/tsconfig.json +2 -0
- package/tsup.config.ts +16 -1
- package/src/utils/isLegacyApi.ts +0 -12
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Storage Service
|
|
3
|
+
*
|
|
4
|
+
* Handles state persistence for consent flow during DigiLocker redirects.
|
|
5
|
+
* Uses sessionStorage to maintain state across page navigations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PersistedConsentState } from "../api/types";
|
|
9
|
+
|
|
10
|
+
const STORAGE_KEY = "redacto_consent_sdk_state";
|
|
11
|
+
const STATE_EXPIRY_MS = 30 * 60 * 1000; // 30 minutes
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if code is running in browser environment
|
|
15
|
+
*/
|
|
16
|
+
const isBrowser = (): boolean => {
|
|
17
|
+
return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Save consent flow state to sessionStorage
|
|
22
|
+
*/
|
|
23
|
+
export const saveConsentState = (state: PersistedConsentState): void => {
|
|
24
|
+
if (!isBrowser()) {
|
|
25
|
+
console.warn("SessionStorage not available - state not persisted");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const stateWithTimestamp = {
|
|
31
|
+
...state,
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
};
|
|
34
|
+
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(stateWithTimestamp));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error("Failed to save consent state:", error);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Retrieve consent flow state from sessionStorage
|
|
42
|
+
* Returns null if state doesn't exist or is expired
|
|
43
|
+
*/
|
|
44
|
+
export const getConsentState = (): PersistedConsentState | null => {
|
|
45
|
+
if (!isBrowser()) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const savedState = sessionStorage.getItem(STORAGE_KEY);
|
|
51
|
+
if (!savedState) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parsed: PersistedConsentState = JSON.parse(savedState);
|
|
56
|
+
|
|
57
|
+
// Check if state is expired
|
|
58
|
+
const stateAge = Date.now() - new Date(parsed.timestamp).getTime();
|
|
59
|
+
if (stateAge > STATE_EXPIRY_MS) {
|
|
60
|
+
clearConsentState();
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return parsed;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("Failed to get consent state:", error);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Clear consent flow state from sessionStorage
|
|
73
|
+
*/
|
|
74
|
+
export const clearConsentState = (): void => {
|
|
75
|
+
if (!isBrowser()) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
sessionStorage.removeItem(STORAGE_KEY);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error("Failed to clear consent state:", error);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Update specific fields in the persisted consent state
|
|
88
|
+
*/
|
|
89
|
+
export const updateConsentState = (updates: Partial<PersistedConsentState>): void => {
|
|
90
|
+
const currentState = getConsentState();
|
|
91
|
+
if (currentState) {
|
|
92
|
+
saveConsentState({
|
|
93
|
+
...currentState,
|
|
94
|
+
...updates,
|
|
95
|
+
timestamp: new Date().toISOString(),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if there's a pending verification to resume
|
|
102
|
+
*/
|
|
103
|
+
export const hasPendingVerification = (): boolean => {
|
|
104
|
+
const state = getConsentState();
|
|
105
|
+
return state !== null && state.flowState === "digilocker-pending";
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get verification result from persisted state
|
|
110
|
+
*/
|
|
111
|
+
export const getVerificationResult = (): "pending" | "verified" | "failed" | "in_progress" | "expired" | null => {
|
|
112
|
+
const state = getConsentState();
|
|
113
|
+
return state?.verificationResult ?? null;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get verification reference from persisted state (after successful verification)
|
|
118
|
+
*/
|
|
119
|
+
export const getVerificationReference = (): string | null => {
|
|
120
|
+
const state = getConsentState();
|
|
121
|
+
return state?.verificationReference ?? null;
|
|
122
|
+
};
|
|
@@ -13,6 +13,8 @@ export type Props = Readonly<{
|
|
|
13
13
|
onError?: (error: Error) => void;
|
|
14
14
|
applicationId?: string;
|
|
15
15
|
validateAgainst?: "all" | "required";
|
|
16
|
+
digilockerMode?: "popup" | "redirect"; // Default: "redirect" (full page redirect - required for DigiLocker)
|
|
17
|
+
digilockerCallbackUrl?: string; // Default: "/digilocker-callback"
|
|
16
18
|
includeFullyConsentedData?: boolean;
|
|
17
19
|
reviewModeButtonText?: string;
|
|
18
20
|
}>;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ConsentContent,
|
|
3
3
|
ConsentEventPayload,
|
|
4
|
-
LegacyConsentEventPayload,
|
|
5
4
|
FetchConsentContentParams,
|
|
6
5
|
SubmitConsentEventParams,
|
|
7
6
|
} from "./types";
|
|
8
|
-
import { isLegacyApi } from "../../utils/isLegacyApi";
|
|
9
7
|
|
|
10
8
|
const BASE_URL = "https://api.redacto.tech/consent";
|
|
11
9
|
|
|
@@ -77,68 +75,32 @@ export const submitConsentEvent = async ({
|
|
|
77
75
|
try {
|
|
78
76
|
const apiBaseUrl = baseUrl || BASE_URL;
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
selected: element.required ? true : Boolean(element.selected),
|
|
107
|
-
})),
|
|
108
|
-
};
|
|
109
|
-
}),
|
|
110
|
-
},
|
|
111
|
-
meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid } : undefined,
|
|
112
|
-
};
|
|
113
|
-
} else {
|
|
114
|
-
// New payload for staging (.tech) / localhost
|
|
115
|
-
payload = {
|
|
116
|
-
notice_uuid: noticeUuid,
|
|
117
|
-
purposes: purposes.map((purpose) => {
|
|
118
|
-
if (!purpose.uuid) {
|
|
119
|
-
throw new Error(`Invalid purpose data: missing uuid`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Only include enabled data elements (disabled elements are not selectable)
|
|
123
|
-
const dataElements = purpose.data_elements
|
|
124
|
-
.filter((element) => element.enabled)
|
|
125
|
-
.map((element) => ({
|
|
126
|
-
uuid: element.uuid,
|
|
127
|
-
selected: element.required ? true : Boolean(element.selected),
|
|
128
|
-
}));
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
purpose_uuid: purpose.uuid,
|
|
132
|
-
selected: Boolean(purpose.selected),
|
|
133
|
-
data_elements: dataElements.length > 0 ? dataElements : undefined,
|
|
134
|
-
};
|
|
135
|
-
}),
|
|
136
|
-
select_all_mandatory: false,
|
|
137
|
-
source: "WEB",
|
|
138
|
-
declined,
|
|
139
|
-
meta_data,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
78
|
+
const payload: ConsentEventPayload = {
|
|
79
|
+
notice_uuid: noticeUuid,
|
|
80
|
+
purposes: purposes.map((purpose) => {
|
|
81
|
+
if (!purpose.uuid) {
|
|
82
|
+
throw new Error(`Invalid purpose data: missing uuid`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Only include enabled data elements (disabled elements are not selectable)
|
|
86
|
+
const dataElements = purpose.data_elements
|
|
87
|
+
.filter((element) => element.enabled)
|
|
88
|
+
.map((element) => ({
|
|
89
|
+
uuid: element.uuid,
|
|
90
|
+
selected: element.required ? true : Boolean(element.selected),
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
purpose_uuid: purpose.uuid,
|
|
95
|
+
selected: Boolean(purpose.selected),
|
|
96
|
+
data_elements: dataElements.length > 0 ? dataElements : undefined,
|
|
97
|
+
};
|
|
98
|
+
}),
|
|
99
|
+
select_all_mandatory: false,
|
|
100
|
+
source: "WEB",
|
|
101
|
+
declined,
|
|
102
|
+
meta_data,
|
|
103
|
+
};
|
|
142
104
|
|
|
143
105
|
const response = await fetch(
|
|
144
106
|
`${apiBaseUrl}/public/organisations/${org_uuid}/workspaces/${workspace_uuid}/submit-consent`,
|
|
@@ -151,31 +151,6 @@ export type ConsentEventPayload = {
|
|
|
151
151
|
};
|
|
152
152
|
};
|
|
153
153
|
|
|
154
|
-
// @deprecated Remove when production backend supports the new payload format.
|
|
155
|
-
export type LegacyConsentEventPayload = {
|
|
156
|
-
primary_email?: string;
|
|
157
|
-
notice_uuid: string;
|
|
158
|
-
source: string;
|
|
159
|
-
declined: boolean;
|
|
160
|
-
consents: {
|
|
161
|
-
purposes: Array<{
|
|
162
|
-
uuid: string;
|
|
163
|
-
name: string;
|
|
164
|
-
description: string;
|
|
165
|
-
industries?: string;
|
|
166
|
-
selected: boolean;
|
|
167
|
-
data_elements: Array<{
|
|
168
|
-
uuid: string;
|
|
169
|
-
name: string;
|
|
170
|
-
enabled: boolean;
|
|
171
|
-
required: boolean;
|
|
172
|
-
selected: boolean;
|
|
173
|
-
}>;
|
|
174
|
-
}>;
|
|
175
|
-
};
|
|
176
|
-
meta_data?: { specific_uuid: string };
|
|
177
|
-
};
|
|
178
|
-
|
|
179
154
|
export type DataElement = {
|
|
180
155
|
uuid: string;
|
|
181
156
|
name: string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import i18n from './lib/i18n';
|
|
3
|
+
import ActivitiesList from './components/ActivitiesList';
|
|
4
|
+
|
|
5
|
+
export function PrivacyCenterActivityPage() {
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!i18n.language) {
|
|
8
|
+
i18n.changeLanguage('en');
|
|
9
|
+
}
|
|
10
|
+
}, []);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div style={{ width: '100%' }}>
|
|
14
|
+
<div style={{ paddingTop: '0.5rem' }}>
|
|
15
|
+
<ActivitiesList />
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import i18n from './lib/i18n';
|
|
3
|
+
import ConsentManager from './components/ConsentManager';
|
|
4
|
+
|
|
5
|
+
export function PrivacyCenterConsentManagerPage() {
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!i18n.language) {
|
|
8
|
+
i18n.changeLanguage('en');
|
|
9
|
+
}
|
|
10
|
+
}, []);
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div style={{ width: '100%' }}>
|
|
14
|
+
<ConsentManager />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { PCButton } from './ui';
|
|
4
|
+
import { pcStyles } from './styles/pcStyles';
|
|
5
|
+
import { IoArrowBack } from 'react-icons/io5';
|
|
6
|
+
import i18n from './lib/i18n';
|
|
7
|
+
import Form from './components/Form';
|
|
8
|
+
import CaseHistory from './components/CaseHistory';
|
|
9
|
+
import { usePageChrome } from './context/PageChromeContext';
|
|
10
|
+
|
|
11
|
+
const getVar = (varName: string): string => `var(${varName})`;
|
|
12
|
+
|
|
13
|
+
export function PrivacyCenterFormPage() {
|
|
14
|
+
const { t } = useTranslation();
|
|
15
|
+
const [showForm, setShowForm] = useState(() => {
|
|
16
|
+
try {
|
|
17
|
+
return sessionStorage.getItem('pc_grievance_view') === 'form';
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const { setPageChromeOverride, clearPageChromeOverride } = usePageChrome();
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!i18n.language) {
|
|
26
|
+
i18n.changeLanguage('en');
|
|
27
|
+
}
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
try {
|
|
32
|
+
sessionStorage.setItem('pc_grievance_view', showForm ? 'form' : 'list');
|
|
33
|
+
} catch {
|
|
34
|
+
// sessionStorage unavailable
|
|
35
|
+
}
|
|
36
|
+
}, [showForm]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (showForm) {
|
|
40
|
+
setPageChromeOverride({
|
|
41
|
+
titleKey: 'yourPrivacyCenter',
|
|
42
|
+
descriptionKey: 'yourRightsOverDataSimplified',
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
clearPageChromeOverride();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
clearPageChromeOverride();
|
|
50
|
+
};
|
|
51
|
+
}, [showForm, setPageChromeOverride, clearPageChromeOverride]);
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div style={{ ...pcStyles.wFull, ...pcStyles.flex, ...pcStyles.flexCol, gap: pcStyles.gapLg.gap }}>
|
|
55
|
+
{showForm ? (
|
|
56
|
+
<>
|
|
57
|
+
<div style={pcStyles.mbMd}>
|
|
58
|
+
<PCButton
|
|
59
|
+
variant="text"
|
|
60
|
+
size="sm"
|
|
61
|
+
onClick={() => setShowForm(false)}
|
|
62
|
+
style={{ paddingLeft: 0, color: getVar('--pc-text-secondary') }}
|
|
63
|
+
>
|
|
64
|
+
<IoArrowBack size={16} style={{ marginRight: '0.5rem' }} />
|
|
65
|
+
{t('backToHome')}
|
|
66
|
+
</PCButton>
|
|
67
|
+
</div>
|
|
68
|
+
<div style={{ width: '100%' }}>
|
|
69
|
+
<Form onCreateRequest={() => setShowForm(false)} />
|
|
70
|
+
</div>
|
|
71
|
+
</>
|
|
72
|
+
) : (
|
|
73
|
+
<div style={{ width: '100%' }}>
|
|
74
|
+
<CaseHistory onCreateRequest={() => setShowForm(true)} />
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import i18n from './lib/i18n';
|
|
3
|
+
import { useInternalNavigation } from './context/NavigationContext';
|
|
4
|
+
import { PrivacyCenterConsentManagerPage } from './PrivacyCenterConsentManagerPage';
|
|
5
|
+
import { PrivacyCenterFormPage } from './PrivacyCenterFormPage';
|
|
6
|
+
import { PrivacyCenterActivityPage } from './PrivacyCenterActivityPage';
|
|
7
|
+
|
|
8
|
+
export function PrivacyCenterHomePage() {
|
|
9
|
+
const { currentPage } = useInternalNavigation();
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!i18n.language) {
|
|
13
|
+
i18n.changeLanguage('en');
|
|
14
|
+
}
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
switch (currentPage) {
|
|
18
|
+
case 'consent-manager':
|
|
19
|
+
return <PrivacyCenterConsentManagerPage />;
|
|
20
|
+
case 'form':
|
|
21
|
+
return <PrivacyCenterFormPage />;
|
|
22
|
+
case 'activity':
|
|
23
|
+
return <PrivacyCenterActivityPage />;
|
|
24
|
+
default:
|
|
25
|
+
return <PrivacyCenterConsentManagerPage />;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, ReactNode } from 'react';
|
|
2
|
+
import { OrganizationProvider } from './context/OrganizationContext';
|
|
3
|
+
import { ThemeProvider } from './context/theme';
|
|
4
|
+
import type { OrganizationDetail } from './lib/types';
|
|
5
|
+
import {
|
|
6
|
+
checkAndRefreshAuth,
|
|
7
|
+
decodeJwtToken,
|
|
8
|
+
} from './lib/utils';
|
|
9
|
+
import { useBaseUrl } from './context/BaseUrlContext';
|
|
10
|
+
import { PCLoader } from './ui';
|
|
11
|
+
import { pcStyles } from './styles/pcStyles';
|
|
12
|
+
import PrivacyCenterLayoutComponent from './components/PrivacyCenterLayout';
|
|
13
|
+
|
|
14
|
+
interface PrivacyCenterLayoutProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
defaultTheme?: 'light' | 'dark';
|
|
17
|
+
slug: string;
|
|
18
|
+
onBack?: 'back' | 'signout';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function PrivacyCenterLayout({ children, defaultTheme, slug, onBack }: PrivacyCenterLayoutProps) {
|
|
22
|
+
const { consentClient } = useBaseUrl();
|
|
23
|
+
const [organization, setOrganization] = useState<OrganizationDetail | null>(null);
|
|
24
|
+
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
|
25
|
+
const [isLoadingOrg, setIsLoadingOrg] = useState(true);
|
|
26
|
+
const hasRedirectedRef = useRef(false);
|
|
27
|
+
const authCheckInProgressRef = useRef(false);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
// Load organization info from sessionStorage
|
|
31
|
+
if (typeof window !== 'undefined' && slug) {
|
|
32
|
+
const accessToken = sessionStorage.getItem('access_token');
|
|
33
|
+
const orgId = sessionStorage.getItem('pc_org_id');
|
|
34
|
+
const workspaceId = sessionStorage.getItem('pc_workspace_id');
|
|
35
|
+
const orgSlug = sessionStorage.getItem('pc_org_slug');
|
|
36
|
+
|
|
37
|
+
// Create organization object from sessionStorage
|
|
38
|
+
if (accessToken && orgId && workspaceId && orgSlug === slug) {
|
|
39
|
+
// Prefer organisation_name from JWT; fall back to slug-derived name
|
|
40
|
+
const decoded = decodeJwtToken(accessToken);
|
|
41
|
+
const orgNameFromJwt = decoded?.organisation_name;
|
|
42
|
+
const orgName =
|
|
43
|
+
typeof orgNameFromJwt === 'string' && orgNameFromJwt.trim() !== ''
|
|
44
|
+
? orgNameFromJwt
|
|
45
|
+
: slug
|
|
46
|
+
.split('-')
|
|
47
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
48
|
+
.join(' ');
|
|
49
|
+
|
|
50
|
+
setOrganization({
|
|
51
|
+
uuid: orgId,
|
|
52
|
+
suid: '',
|
|
53
|
+
name: orgName,
|
|
54
|
+
slug: slug,
|
|
55
|
+
description: '',
|
|
56
|
+
domain: '',
|
|
57
|
+
workspaces: [{
|
|
58
|
+
uuid: workspaceId,
|
|
59
|
+
suid: '',
|
|
60
|
+
name: '',
|
|
61
|
+
description: '',
|
|
62
|
+
workspace_type: '',
|
|
63
|
+
status: '',
|
|
64
|
+
}],
|
|
65
|
+
ai_summary: {},
|
|
66
|
+
icon_url: null,
|
|
67
|
+
activated: true,
|
|
68
|
+
allowed_domains: [],
|
|
69
|
+
});
|
|
70
|
+
setIsLoadingOrg(false);
|
|
71
|
+
} else {
|
|
72
|
+
// If organization info is not available, set loading to false
|
|
73
|
+
// User will need to authenticate first
|
|
74
|
+
setIsLoadingOrg(false);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}, [slug]);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
// Don't check auth while still loading organization
|
|
81
|
+
if (isLoadingOrg || !slug || !organization) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Prevent multiple simultaneous auth checks
|
|
86
|
+
if (authCheckInProgressRef.current) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check authentication status with token refresh capability
|
|
91
|
+
const checkAuth = async () => {
|
|
92
|
+
authCheckInProgressRef.current = true;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
// Check if user is authenticated, attempt refresh if needed
|
|
96
|
+
const isAuthenticated = await checkAndRefreshAuth(
|
|
97
|
+
slug,
|
|
98
|
+
organization?.uuid,
|
|
99
|
+
organization?.workspaces[0]?.uuid,
|
|
100
|
+
consentClient
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// User is authenticated, allow access
|
|
104
|
+
hasRedirectedRef.current = false;
|
|
105
|
+
setIsCheckingAuth(false);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Auth check failed - user will need to provide tokens via props
|
|
108
|
+
setIsCheckingAuth(false);
|
|
109
|
+
} finally {
|
|
110
|
+
authCheckInProgressRef.current = false;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Check auth after organization is loaded
|
|
115
|
+
checkAuth();
|
|
116
|
+
}, [slug, organization, isLoadingOrg, consentClient]);
|
|
117
|
+
|
|
118
|
+
// Show loading state while checking authentication or loading organization
|
|
119
|
+
if (isLoadingOrg || !organization || isCheckingAuth) {
|
|
120
|
+
return (
|
|
121
|
+
<div style={{
|
|
122
|
+
...pcStyles.flex,
|
|
123
|
+
...pcStyles.hScreen,
|
|
124
|
+
...pcStyles.wScreen,
|
|
125
|
+
...pcStyles.itemsCenter,
|
|
126
|
+
...pcStyles.justifyCenter,
|
|
127
|
+
}}>
|
|
128
|
+
<PCLoader variant="spinner" color="info" size="lg" />
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<ThemeProvider defaultTheme={defaultTheme}>
|
|
135
|
+
<OrganizationProvider organization={organization}>
|
|
136
|
+
<PrivacyCenterLayoutComponent onBack={onBack}>
|
|
137
|
+
{children}
|
|
138
|
+
</PrivacyCenterLayoutComponent>
|
|
139
|
+
</OrganizationProvider>
|
|
140
|
+
</ThemeProvider>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { I18nextProvider } from 'react-i18next';
|
|
2
|
+
import i18n from './lib/i18n';
|
|
3
|
+
import { PrivacyCenterLayout } from './PrivacyCenterLayout';
|
|
4
|
+
import { PrivacyCenterHomePage } from './PrivacyCenterHomePage';
|
|
5
|
+
import { BaseUrlProvider } from './context/BaseUrlContext';
|
|
6
|
+
import { AuthProvider } from './context/AuthContext';
|
|
7
|
+
import { NavigationProvider } from './context/NavigationContext';
|
|
8
|
+
import type { InternalPage } from './context/NavigationContext';
|
|
9
|
+
import { PageChromeProvider } from './context/PageChromeContext';
|
|
10
|
+
|
|
11
|
+
interface RedactoPrivacyCenterProps {
|
|
12
|
+
baseUrl: string; // Consent server base URL
|
|
13
|
+
slug: string;
|
|
14
|
+
theme?: 'light' | 'dark';
|
|
15
|
+
accessToken: string;
|
|
16
|
+
refreshToken: string;
|
|
17
|
+
onError: (error: Error) => boolean;
|
|
18
|
+
initialPage?: InternalPage;
|
|
19
|
+
onBack?: 'back' | 'signout';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function RedactoPrivacyCenter({
|
|
23
|
+
baseUrl,
|
|
24
|
+
slug,
|
|
25
|
+
theme = 'light',
|
|
26
|
+
accessToken,
|
|
27
|
+
refreshToken,
|
|
28
|
+
onError,
|
|
29
|
+
initialPage = 'consent-manager',
|
|
30
|
+
onBack,
|
|
31
|
+
}: RedactoPrivacyCenterProps) {
|
|
32
|
+
return (
|
|
33
|
+
<I18nextProvider i18n={i18n}>
|
|
34
|
+
<BaseUrlProvider baseUrl={baseUrl}>
|
|
35
|
+
<AuthProvider
|
|
36
|
+
accessToken={accessToken}
|
|
37
|
+
refreshToken={refreshToken}
|
|
38
|
+
onError={onError}
|
|
39
|
+
>
|
|
40
|
+
<NavigationProvider initialPage={initialPage}>
|
|
41
|
+
<PageChromeProvider>
|
|
42
|
+
<PrivacyCenterLayout defaultTheme={theme} slug={slug} onBack={onBack}>
|
|
43
|
+
<PrivacyCenterHomePage />
|
|
44
|
+
</PrivacyCenterLayout>
|
|
45
|
+
</PageChromeProvider>
|
|
46
|
+
</NavigationProvider>
|
|
47
|
+
</AuthProvider>
|
|
48
|
+
</BaseUrlProvider>
|
|
49
|
+
</I18nextProvider>
|
|
50
|
+
);
|
|
51
|
+
}
|