@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
|
@@ -4,18 +4,17 @@ import type {
|
|
|
4
4
|
ConsentEventPayload,
|
|
5
5
|
FetchConsentContentParams,
|
|
6
6
|
FetchTTSAudioUrlsParams,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
InitiateGuardianVerificationParams,
|
|
8
|
+
InitiateGuardianVerificationResponse,
|
|
9
9
|
RedactoJwtPayload,
|
|
10
10
|
SubmitConsentEventParams,
|
|
11
|
-
SubmitGuardianInfoParams,
|
|
12
11
|
TTSAudioUrlsResponse,
|
|
12
|
+
VerifyGuardianStatusParams,
|
|
13
|
+
VerifyGuardianStatusResponse,
|
|
13
14
|
} from "./types";
|
|
14
|
-
import { isLegacyApi } from "../../utils/isLegacyApi";
|
|
15
15
|
|
|
16
16
|
const BASE_URL = "https://api.redacto.io/consent";
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
// Simple in-memory cache for API responses to reduce calls
|
|
20
19
|
const apiCache = new Map<string, { data: any; timestamp: number; ttl: number }>();
|
|
21
20
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
@@ -60,90 +59,82 @@ export const fetchConsentContent = async ({
|
|
|
60
59
|
specific_uuid,
|
|
61
60
|
validate_against = "all",
|
|
62
61
|
include_fully_consented_data = false,
|
|
63
|
-
signal,
|
|
62
|
+
signal,
|
|
64
63
|
}: FetchConsentContentParams & { signal?: AbortSignal }): Promise<ConsentContent> => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
throw new Error("noticeId and accessToken are required");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const decodedToken = decodeTokenSafely(accessToken);
|
|
72
|
-
if (!decodedToken) {
|
|
73
|
-
throw new Error("Invalid access token");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
|
|
77
|
-
if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
|
|
78
|
-
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
79
|
-
}
|
|
64
|
+
if (!noticeId || !accessToken || !validate_against) {
|
|
65
|
+
throw new Error("noticeId and accessToken are required");
|
|
66
|
+
}
|
|
80
67
|
|
|
81
|
-
|
|
68
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
69
|
+
if (!decodedToken) {
|
|
70
|
+
throw new Error("Invalid access token");
|
|
71
|
+
}
|
|
82
72
|
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
|
|
74
|
+
if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
|
|
75
|
+
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
76
|
+
}
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
if (validate_against === "all" && !specific_uuid) {
|
|
88
|
-
const cachedData = getCachedData(cacheKey);
|
|
89
|
-
if (cachedData) {
|
|
90
|
-
return cachedData;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
78
|
+
const apiBaseUrl = baseUrl || BASE_URL;
|
|
93
79
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
80
|
+
// Create cache key for this request - include accessToken to ensure different users get different cache
|
|
81
|
+
const cacheKey = `${accessToken}-${noticeId}-${validate_against}-${language}-${specific_uuid || ''}-${include_fully_consented_data}`;
|
|
97
82
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (include_fully_consented_data) {
|
|
103
|
-
url.searchParams.append("include_fully_consented_data", "true");
|
|
83
|
+
if (validate_against === "all" && !specific_uuid) {
|
|
84
|
+
const cachedData = getCachedData(cacheKey);
|
|
85
|
+
if (cachedData) {
|
|
86
|
+
return cachedData;
|
|
104
87
|
}
|
|
88
|
+
}
|
|
105
89
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
Authorization: `Bearer ${accessToken}`,
|
|
110
|
-
"Accept-Language": language,
|
|
111
|
-
"Content-Type": "application/json",
|
|
112
|
-
},
|
|
113
|
-
signal, // Add abort signal for request cancellation
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
if (!response.ok) {
|
|
117
|
-
let errorMessage = `Failed to fetch consent content: ${response.status} ${response.statusText}`;
|
|
90
|
+
const url = new URL(
|
|
91
|
+
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/notices/${noticeId}`
|
|
92
|
+
);
|
|
118
93
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
errorMessage = "User has already provided consent";
|
|
127
|
-
} else if (response.status >= 500) {
|
|
128
|
-
errorMessage = "Server error: Please try again later";
|
|
129
|
-
}
|
|
94
|
+
if (specific_uuid) {
|
|
95
|
+
url.searchParams.append("specific_uuid", specific_uuid);
|
|
96
|
+
}
|
|
97
|
+
url.searchParams.append("validate_against", validate_against);
|
|
98
|
+
if (include_fully_consented_data) {
|
|
99
|
+
url.searchParams.append("include_fully_consented_data", "true");
|
|
100
|
+
}
|
|
130
101
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
102
|
+
const response = await fetch(url.toString(), {
|
|
103
|
+
method: "GET",
|
|
104
|
+
headers: {
|
|
105
|
+
Authorization: `Bearer ${accessToken}`,
|
|
106
|
+
"Accept-Language": language,
|
|
107
|
+
},
|
|
108
|
+
signal,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
let errorMessage = `Failed to fetch consent content: ${response.status} ${response.statusText}`;
|
|
113
|
+
|
|
114
|
+
if (response.status === 401) {
|
|
115
|
+
errorMessage = "Unauthorized: Invalid or expired token";
|
|
116
|
+
} else if (response.status === 403) {
|
|
117
|
+
errorMessage = "Forbidden: Access denied";
|
|
118
|
+
} else if (response.status === 404) {
|
|
119
|
+
errorMessage = "Notice not found";
|
|
120
|
+
} else if (response.status === 409) {
|
|
121
|
+
errorMessage = "User has already provided consent";
|
|
122
|
+
} else if (response.status >= 500) {
|
|
123
|
+
errorMessage = "Server error: Please try again later";
|
|
134
124
|
}
|
|
135
125
|
|
|
136
|
-
const
|
|
126
|
+
const error = new Error(errorMessage) as Error & { status?: number };
|
|
127
|
+
error.status = response.status;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
137
130
|
|
|
138
|
-
|
|
139
|
-
if (validate_against === "all" && !specific_uuid) {
|
|
140
|
-
setCachedData(cacheKey, data);
|
|
141
|
-
}
|
|
131
|
+
const data: ConsentContent = await response.json();
|
|
142
132
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
throw error;
|
|
133
|
+
if (validate_against === "all" && !specific_uuid) {
|
|
134
|
+
setCachedData(cacheKey, data);
|
|
146
135
|
}
|
|
136
|
+
|
|
137
|
+
return data;
|
|
147
138
|
};
|
|
148
139
|
|
|
149
140
|
export const submitConsentEvent = async ({
|
|
@@ -152,157 +143,193 @@ export const submitConsentEvent = async ({
|
|
|
152
143
|
noticeUuid,
|
|
153
144
|
purposes,
|
|
154
145
|
declined,
|
|
146
|
+
language,
|
|
155
147
|
meta_data,
|
|
156
|
-
|
|
148
|
+
guardianVerificationReference,
|
|
149
|
+
selfDeclaredAdult,
|
|
150
|
+
signal,
|
|
157
151
|
}: SubmitConsentEventParams & { signal?: AbortSignal }): Promise<void> => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
throw new Error("noticeUuid, accessToken, and purposes array are required");
|
|
162
|
-
}
|
|
152
|
+
if (!noticeUuid || !accessToken || !Array.isArray(purposes)) {
|
|
153
|
+
throw new Error("noticeUuid, accessToken, and purposes array are required");
|
|
154
|
+
}
|
|
163
155
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
156
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
157
|
+
if (!decodedToken) {
|
|
158
|
+
throw new Error("Invalid access token");
|
|
159
|
+
}
|
|
168
160
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
161
|
+
const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
|
|
162
|
+
if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
|
|
163
|
+
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
164
|
+
}
|
|
173
165
|
|
|
174
|
-
|
|
166
|
+
const apiBaseUrl = baseUrl || BASE_URL;
|
|
175
167
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// @deprecated Legacy payload for production (.io) backend
|
|
180
|
-
const primaryEmail = typeof window !== "undefined" ? localStorage.getItem("userEmail") : null;
|
|
181
|
-
payload = {
|
|
182
|
-
primary_email: primaryEmail || undefined,
|
|
183
|
-
notice_uuid: noticeUuid,
|
|
184
|
-
source: "WEB",
|
|
185
|
-
declined: Boolean(declined),
|
|
186
|
-
consents: {
|
|
187
|
-
purposes: purposes.map((purpose) => {
|
|
188
|
-
if (!purpose.uuid) {
|
|
189
|
-
throw new Error(`Invalid purpose data: missing uuid`);
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
uuid: purpose.uuid,
|
|
193
|
-
name: purpose.name,
|
|
194
|
-
description: purpose.description || "",
|
|
195
|
-
industries: purpose.industries || "",
|
|
196
|
-
selected: Boolean(purpose.selected),
|
|
197
|
-
data_elements: (purpose.data_elements || []).map((element) => ({
|
|
198
|
-
uuid: element.uuid,
|
|
199
|
-
name: element.name,
|
|
200
|
-
enabled: Boolean(element.enabled),
|
|
201
|
-
required: Boolean(element.required),
|
|
202
|
-
selected: element.required ? true : Boolean(element.selected),
|
|
203
|
-
})),
|
|
204
|
-
};
|
|
205
|
-
}),
|
|
206
|
-
},
|
|
207
|
-
meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid } : undefined,
|
|
208
|
-
};
|
|
209
|
-
} else {
|
|
210
|
-
// New payload for staging (.tech) / localhost
|
|
211
|
-
const validatedPurposes = purposes.map((purpose) => {
|
|
212
|
-
if (!purpose.uuid) {
|
|
213
|
-
throw new Error(`Invalid purpose data: missing uuid`);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Only include enabled data elements (disabled elements are not selectable)
|
|
217
|
-
const dataElements = (purpose.data_elements || [])
|
|
218
|
-
.filter((element) => element.enabled)
|
|
219
|
-
.map((element) => ({
|
|
220
|
-
uuid: element.uuid,
|
|
221
|
-
selected: element.required ? true : Boolean(element.selected),
|
|
222
|
-
}));
|
|
223
|
-
|
|
224
|
-
return {
|
|
225
|
-
purpose_uuid: purpose.uuid,
|
|
226
|
-
selected: Boolean(purpose.selected),
|
|
227
|
-
data_elements: dataElements.length > 0 ? dataElements : undefined,
|
|
228
|
-
};
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
payload = {
|
|
232
|
-
notice_uuid: noticeUuid,
|
|
233
|
-
purposes: validatedPurposes,
|
|
234
|
-
select_all_mandatory: false,
|
|
235
|
-
source: "WEB",
|
|
236
|
-
declined: Boolean(declined),
|
|
237
|
-
meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid } : undefined,
|
|
238
|
-
};
|
|
168
|
+
const validatedPurposes = purposes.map((purpose) => {
|
|
169
|
+
if (!purpose.uuid) {
|
|
170
|
+
throw new Error(`Invalid purpose data: missing uuid`);
|
|
239
171
|
}
|
|
240
172
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
173
|
+
// Only include enabled data elements (disabled elements are not selectable)
|
|
174
|
+
const dataElements = (purpose.data_elements || [])
|
|
175
|
+
.filter((element) => element.enabled)
|
|
176
|
+
.map((element) => ({
|
|
177
|
+
uuid: element.uuid,
|
|
178
|
+
selected: element.required ? true : Boolean(element.selected),
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
purpose_uuid: purpose.uuid,
|
|
183
|
+
selected: Boolean(purpose.selected),
|
|
184
|
+
data_elements: dataElements.length > 0 ? dataElements : undefined,
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const payload: ConsentEventPayload = {
|
|
189
|
+
notice_uuid: noticeUuid,
|
|
190
|
+
purposes: validatedPurposes,
|
|
191
|
+
select_all_mandatory: false,
|
|
192
|
+
source: "WEB",
|
|
193
|
+
declined: Boolean(declined),
|
|
194
|
+
language: language || undefined,
|
|
195
|
+
meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid, minor_age: meta_data.minor_age } : undefined,
|
|
196
|
+
guardian_verification_reference: guardianVerificationReference,
|
|
197
|
+
self_declared_adult: selfDeclaredAdult,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const response = await fetch(
|
|
201
|
+
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/submit-consent`,
|
|
202
|
+
{
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: {
|
|
205
|
+
Authorization: `Bearer ${accessToken}`,
|
|
206
|
+
"Content-Type": "application/json",
|
|
207
|
+
},
|
|
208
|
+
body: JSON.stringify(payload),
|
|
209
|
+
signal,
|
|
210
|
+
}
|
|
211
|
+
);
|
|
253
212
|
|
|
254
|
-
|
|
255
|
-
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
let errorMessage = `Failed to submit consent: ${response.status} ${response.statusText}`;
|
|
256
215
|
|
|
257
|
-
|
|
216
|
+
if (response.status === 400) {
|
|
217
|
+
try {
|
|
218
|
+
const errorData = await response.json();
|
|
219
|
+
errorMessage = errorData.message || "Invalid request data";
|
|
220
|
+
} catch {
|
|
258
221
|
errorMessage = "Invalid request data";
|
|
259
|
-
} else if (response.status === 401) {
|
|
260
|
-
errorMessage = "Unauthorized: Invalid or expired token";
|
|
261
|
-
} else if (response.status === 403) {
|
|
262
|
-
errorMessage = "Forbidden: Access denied";
|
|
263
|
-
} else if (response.status === 404) {
|
|
264
|
-
errorMessage = "Notice not found";
|
|
265
|
-
} else if (response.status >= 500) {
|
|
266
|
-
errorMessage = "Server error: Please try again later";
|
|
267
222
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
223
|
+
} else if (response.status === 401) {
|
|
224
|
+
errorMessage = "Unauthorized: Invalid or expired token";
|
|
225
|
+
} else if (response.status === 403) {
|
|
226
|
+
errorMessage = "Forbidden: Access denied";
|
|
227
|
+
} else if (response.status === 404) {
|
|
228
|
+
errorMessage = "Notice not found";
|
|
229
|
+
} else if (response.status >= 500) {
|
|
230
|
+
errorMessage = "Server error: Please try again later";
|
|
272
231
|
}
|
|
273
232
|
|
|
274
|
-
|
|
275
|
-
|
|
233
|
+
const error = new Error(errorMessage) as Error & { status?: number };
|
|
234
|
+
error.status = response.status;
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
276
237
|
|
|
277
|
-
|
|
238
|
+
// Clear any cached consent content since user preferences have changed
|
|
239
|
+
apiCache.clear();
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const fetchTTSAudioUrls = async ({
|
|
243
|
+
accessToken,
|
|
244
|
+
baseUrl,
|
|
245
|
+
noticeUuid,
|
|
246
|
+
language,
|
|
247
|
+
signal,
|
|
248
|
+
}: FetchTTSAudioUrlsParams & { signal?: AbortSignal }): Promise<TTSAudioUrlsResponse> => {
|
|
249
|
+
if (!accessToken || !noticeUuid || !language) {
|
|
250
|
+
throw new Error("accessToken, noticeUuid, and language are required");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
254
|
+
if (!decodedToken) {
|
|
255
|
+
throw new Error("Invalid access token");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
|
|
259
|
+
if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
|
|
260
|
+
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const apiBaseUrl = baseUrl || BASE_URL;
|
|
264
|
+
|
|
265
|
+
const url = `${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/notices/${noticeUuid}/audio/${language}`;
|
|
266
|
+
|
|
267
|
+
const response = await fetch(url, {
|
|
268
|
+
method: "GET",
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${accessToken}`,
|
|
271
|
+
Accept: "application/json",
|
|
272
|
+
},
|
|
273
|
+
signal,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
let errorMessage = `Failed to fetch TTS audio URLs: ${response.status} ${response.statusText}`;
|
|
278
|
+
|
|
279
|
+
if (response.status === 401) {
|
|
280
|
+
errorMessage = "Unauthorized: Invalid or expired token";
|
|
281
|
+
} else if (response.status === 403) {
|
|
282
|
+
errorMessage = "Forbidden: Access denied";
|
|
283
|
+
} else if (response.status === 404) {
|
|
284
|
+
errorMessage = "Notice or audio not found";
|
|
285
|
+
} else if (response.status >= 500) {
|
|
286
|
+
errorMessage = "Server error: Please try again later";
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const error = new Error(errorMessage) as Error & { status?: number };
|
|
290
|
+
error.status = response.status;
|
|
278
291
|
throw error;
|
|
279
292
|
}
|
|
293
|
+
|
|
294
|
+
const data: TTSAudioUrlsResponse = await response.json();
|
|
295
|
+
return data;
|
|
280
296
|
};
|
|
281
297
|
|
|
282
|
-
|
|
298
|
+
// =============================================================================
|
|
299
|
+
// Guardian Verification API Functions
|
|
300
|
+
// =============================================================================
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Initiate guardian verification via DigiLocker
|
|
304
|
+
* POST /guardian/initiate-verification
|
|
305
|
+
*
|
|
306
|
+
* This combines guardian details submission and DigiLocker initiation in one call.
|
|
307
|
+
*/
|
|
308
|
+
export const initiateGuardianVerification = async ({
|
|
283
309
|
accessToken,
|
|
284
310
|
baseUrl,
|
|
285
311
|
guardianName,
|
|
286
312
|
guardianContact,
|
|
287
313
|
guardianRelationship,
|
|
288
|
-
|
|
289
|
-
|
|
314
|
+
frontendCallbackUrl,
|
|
315
|
+
metadata,
|
|
316
|
+
signal,
|
|
317
|
+
}: InitiateGuardianVerificationParams): Promise<InitiateGuardianVerificationResponse> => {
|
|
290
318
|
try {
|
|
291
|
-
// Validate required parameters
|
|
292
319
|
if (!accessToken) {
|
|
293
320
|
throw new Error("accessToken is required");
|
|
294
321
|
}
|
|
295
322
|
|
|
296
323
|
if (!guardianName?.trim()) {
|
|
297
|
-
throw new Error("
|
|
324
|
+
throw new Error("Guardian name is required");
|
|
298
325
|
}
|
|
299
326
|
|
|
300
327
|
if (!guardianContact?.trim()) {
|
|
301
|
-
throw new Error("
|
|
328
|
+
throw new Error("Guardian contact is required");
|
|
302
329
|
}
|
|
303
330
|
|
|
304
331
|
if (!guardianRelationship?.trim()) {
|
|
305
|
-
throw new Error("
|
|
332
|
+
throw new Error("Guardian relationship is required");
|
|
306
333
|
}
|
|
307
334
|
|
|
308
335
|
const decodedToken = decodeTokenSafely(accessToken);
|
|
@@ -315,16 +342,30 @@ export const submitGuardianInfo = async ({
|
|
|
315
342
|
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
316
343
|
}
|
|
317
344
|
|
|
318
|
-
const
|
|
345
|
+
const apiBaseUrl = baseUrl || BASE_URL;
|
|
346
|
+
|
|
347
|
+
const payload: {
|
|
348
|
+
guardian_name: string;
|
|
349
|
+
guardian_contact: string;
|
|
350
|
+
guardian_relationship: string;
|
|
351
|
+
frontend_callback_url?: string;
|
|
352
|
+
metadata?: Record<string, unknown>;
|
|
353
|
+
} = {
|
|
319
354
|
guardian_name: guardianName.trim(),
|
|
320
355
|
guardian_contact: guardianContact.trim(),
|
|
321
356
|
guardian_relationship: guardianRelationship.trim(),
|
|
322
357
|
};
|
|
323
358
|
|
|
324
|
-
|
|
359
|
+
if (frontendCallbackUrl) {
|
|
360
|
+
payload.frontend_callback_url = frontendCallbackUrl;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (metadata) {
|
|
364
|
+
payload.metadata = metadata;
|
|
365
|
+
}
|
|
325
366
|
|
|
326
367
|
const response = await fetch(
|
|
327
|
-
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/guardian-
|
|
368
|
+
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/guardian/initiate-verification`,
|
|
328
369
|
{
|
|
329
370
|
method: "POST",
|
|
330
371
|
headers: {
|
|
@@ -332,27 +373,29 @@ export const submitGuardianInfo = async ({
|
|
|
332
373
|
"Content-Type": "application/json",
|
|
333
374
|
},
|
|
334
375
|
body: JSON.stringify(payload),
|
|
335
|
-
signal,
|
|
376
|
+
signal,
|
|
336
377
|
}
|
|
337
378
|
);
|
|
338
379
|
|
|
339
380
|
if (!response.ok) {
|
|
340
|
-
let errorMessage = `Failed to
|
|
381
|
+
let errorMessage = `Failed to initiate verification: ${response.status} ${response.statusText}`;
|
|
341
382
|
|
|
342
383
|
if (response.status === 400) {
|
|
343
|
-
|
|
384
|
+
try {
|
|
385
|
+
const errorData = await response.json();
|
|
386
|
+
errorMessage = errorData.message || "Invalid request data";
|
|
387
|
+
} catch {
|
|
388
|
+
errorMessage = "Invalid request data";
|
|
389
|
+
}
|
|
344
390
|
} else if (response.status === 401) {
|
|
345
391
|
errorMessage = "Unauthorized: Invalid or expired token";
|
|
346
|
-
} else if (response.status === 403) {
|
|
347
|
-
errorMessage = "Forbidden: Access denied";
|
|
348
392
|
} else if (response.status === 404) {
|
|
349
393
|
errorMessage = "Organization or workspace not found";
|
|
350
394
|
} else if (response.status === 422) {
|
|
351
|
-
// Handle validation errors from the server
|
|
352
395
|
try {
|
|
353
396
|
const errorData = await response.json();
|
|
354
|
-
errorMessage = errorData.message || "Validation error";
|
|
355
|
-
} catch
|
|
397
|
+
errorMessage = errorData.message || "Validation error: Please check your input";
|
|
398
|
+
} catch {
|
|
356
399
|
errorMessage = "Validation error: Please check your input";
|
|
357
400
|
}
|
|
358
401
|
} else if (response.status >= 500) {
|
|
@@ -364,72 +407,120 @@ export const submitGuardianInfo = async ({
|
|
|
364
407
|
throw error;
|
|
365
408
|
}
|
|
366
409
|
|
|
410
|
+
const responseData = await response.json();
|
|
411
|
+
|
|
412
|
+
// Handle response with detail wrapper (backend format: { code, status, detail: {...} })
|
|
413
|
+
const detail = responseData.detail || responseData;
|
|
414
|
+
|
|
415
|
+
// Already verified — skip DigiLocker entirely
|
|
416
|
+
if (detail.already_verified) {
|
|
417
|
+
return {
|
|
418
|
+
success: true,
|
|
419
|
+
guardian_info_uuid: detail.guardian_info_uuid,
|
|
420
|
+
already_verified: true,
|
|
421
|
+
verification_reference: detail.verification_reference,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// New verification flow — needs DigiLocker
|
|
426
|
+
if (!detail.session_token || !detail.digilocker_redirect_url) {
|
|
427
|
+
throw new Error("Invalid response: missing session_token or digilocker_redirect_url");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
success: detail.success ?? true,
|
|
432
|
+
guardian_info_uuid: detail.guardian_info_uuid,
|
|
433
|
+
already_verified: false,
|
|
434
|
+
session_token: detail.session_token,
|
|
435
|
+
digilocker_redirect_url: detail.digilocker_redirect_url,
|
|
436
|
+
expires_at: detail.expires_at,
|
|
437
|
+
};
|
|
367
438
|
} catch (error) {
|
|
368
|
-
|
|
369
|
-
console.error("Error submitting guardian info:", error instanceof Error ? error.message : error);
|
|
439
|
+
console.error("Error initiating guardian verification:", error instanceof Error ? error.message : error);
|
|
370
440
|
throw error;
|
|
371
441
|
}
|
|
372
442
|
};
|
|
373
443
|
|
|
374
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Check guardian verification status
|
|
446
|
+
* POST /guardian/verify-status
|
|
447
|
+
*
|
|
448
|
+
* Poll this endpoint to check if DigiLocker verification is complete.
|
|
449
|
+
* Returns verification_reference when status is "verified".
|
|
450
|
+
*/
|
|
451
|
+
export const verifyGuardianStatus = async ({
|
|
375
452
|
accessToken,
|
|
376
453
|
baseUrl,
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if (!accessToken || !noticeUuid || !language) {
|
|
384
|
-
throw new Error("accessToken, noticeUuid, and language are required");
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const decodedToken = decodeTokenSafely(accessToken);
|
|
388
|
-
if (!decodedToken) {
|
|
389
|
-
throw new Error("Invalid access token");
|
|
390
|
-
}
|
|
454
|
+
sessionToken,
|
|
455
|
+
signal,
|
|
456
|
+
}: VerifyGuardianStatusParams): Promise<VerifyGuardianStatusResponse> => {
|
|
457
|
+
if (!accessToken || !sessionToken) {
|
|
458
|
+
throw new Error("accessToken and sessionToken are required");
|
|
459
|
+
}
|
|
391
460
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
461
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
462
|
+
if (!decodedToken) {
|
|
463
|
+
throw new Error("Invalid access token");
|
|
464
|
+
}
|
|
396
465
|
|
|
397
|
-
|
|
466
|
+
const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
|
|
467
|
+
if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
|
|
468
|
+
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
469
|
+
}
|
|
398
470
|
|
|
399
|
-
|
|
471
|
+
const apiBaseUrl = baseUrl || BASE_URL;
|
|
400
472
|
|
|
401
|
-
|
|
402
|
-
|
|
473
|
+
const response = await fetch(
|
|
474
|
+
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/guardian/verify-status`,
|
|
475
|
+
{
|
|
476
|
+
method: "POST",
|
|
403
477
|
headers: {
|
|
404
478
|
Authorization: `Bearer ${accessToken}`,
|
|
405
|
-
Accept: "application/json",
|
|
406
479
|
"Content-Type": "application/json",
|
|
407
480
|
},
|
|
408
|
-
|
|
409
|
-
|
|
481
|
+
body: JSON.stringify({
|
|
482
|
+
session_token: sessionToken,
|
|
483
|
+
}),
|
|
484
|
+
signal,
|
|
485
|
+
}
|
|
486
|
+
);
|
|
410
487
|
|
|
411
|
-
|
|
412
|
-
|
|
488
|
+
if (!response.ok) {
|
|
489
|
+
let errorMessage = `Failed to check verification status: ${response.status} ${response.statusText}`;
|
|
413
490
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
errorMessage = "
|
|
418
|
-
}
|
|
419
|
-
errorMessage = "
|
|
420
|
-
} else if (response.status >= 500) {
|
|
421
|
-
errorMessage = "Server error: Please try again later";
|
|
491
|
+
if (response.status === 400) {
|
|
492
|
+
try {
|
|
493
|
+
const errorData = await response.json();
|
|
494
|
+
errorMessage = errorData.message || errorData.detail?.message || "Invalid session token";
|
|
495
|
+
} catch {
|
|
496
|
+
errorMessage = "Invalid session token";
|
|
422
497
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
498
|
+
} else if (response.status === 401) {
|
|
499
|
+
errorMessage = "Unauthorized: Invalid or expired token";
|
|
500
|
+
} else if (response.status === 404) {
|
|
501
|
+
errorMessage = "Verification session not found or expired. Please restart the verification process.";
|
|
502
|
+
} else if (response.status === 410) {
|
|
503
|
+
errorMessage = "Verification session has expired. Please restart the verification process.";
|
|
504
|
+
} else if (response.status >= 500) {
|
|
505
|
+
errorMessage = "Server error: Please try again later";
|
|
427
506
|
}
|
|
428
507
|
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
return data;
|
|
432
|
-
} catch (error) {
|
|
508
|
+
const error = new Error(errorMessage) as Error & { status?: number };
|
|
509
|
+
error.status = response.status;
|
|
433
510
|
throw error;
|
|
434
511
|
}
|
|
512
|
+
|
|
513
|
+
const responseData = await response.json();
|
|
514
|
+
const detail = responseData.detail || responseData;
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
status: detail.status ?? "in_progress",
|
|
518
|
+
guardian_info_uuid: detail.guardian_info_uuid ?? null,
|
|
519
|
+
verification_reference: detail.verification_reference ?? null,
|
|
520
|
+
guardian_details: detail.guardian_details ?? null,
|
|
521
|
+
error: detail.error ?? null,
|
|
522
|
+
error_code: detail.error_code ?? null,
|
|
523
|
+
can_retry: detail.can_retry ?? true,
|
|
524
|
+
};
|
|
435
525
|
};
|
|
526
|
+
|