@redacto.io/consent-sdk-react 3.5.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 +6 -0
- package/LICENSE +176 -0
- package/dist/index.d.mts +51 -4
- package/dist/index.d.ts +51 -4
- package/dist/index.js +1891 -243
- package/dist/index.mjs +1890 -243
- 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 +335 -206
- package/src/RedactoNoticeConsent/api/types.ts +140 -16
- 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/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
|
@@ -4,16 +4,17 @@ import type {
|
|
|
4
4
|
ConsentEventPayload,
|
|
5
5
|
FetchConsentContentParams,
|
|
6
6
|
FetchTTSAudioUrlsParams,
|
|
7
|
-
|
|
7
|
+
InitiateGuardianVerificationParams,
|
|
8
|
+
InitiateGuardianVerificationResponse,
|
|
8
9
|
RedactoJwtPayload,
|
|
9
10
|
SubmitConsentEventParams,
|
|
10
|
-
SubmitGuardianInfoParams,
|
|
11
11
|
TTSAudioUrlsResponse,
|
|
12
|
+
VerifyGuardianStatusParams,
|
|
13
|
+
VerifyGuardianStatusResponse,
|
|
12
14
|
} from "./types";
|
|
13
15
|
|
|
14
16
|
const BASE_URL = "https://api.redacto.io/consent";
|
|
15
17
|
|
|
16
|
-
|
|
17
18
|
// Simple in-memory cache for API responses to reduce calls
|
|
18
19
|
const apiCache = new Map<string, { data: any; timestamp: number; ttl: number }>();
|
|
19
20
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
@@ -58,90 +59,82 @@ export const fetchConsentContent = async ({
|
|
|
58
59
|
specific_uuid,
|
|
59
60
|
validate_against = "all",
|
|
60
61
|
include_fully_consented_data = false,
|
|
61
|
-
signal,
|
|
62
|
+
signal,
|
|
62
63
|
}: FetchConsentContentParams & { signal?: AbortSignal }): Promise<ConsentContent> => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
throw new Error("noticeId and accessToken are required");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const decodedToken = decodeTokenSafely(accessToken);
|
|
70
|
-
if (!decodedToken) {
|
|
71
|
-
throw new Error("Invalid access token");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const { organisation_uuid: ORGANISATION_UUID, workspace_uuid: WORKSPACE_UUID } = decodedToken;
|
|
75
|
-
if (!ORGANISATION_UUID || !WORKSPACE_UUID) {
|
|
76
|
-
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
77
|
-
}
|
|
64
|
+
if (!noticeId || !accessToken || !validate_against) {
|
|
65
|
+
throw new Error("noticeId and accessToken are required");
|
|
66
|
+
}
|
|
78
67
|
|
|
79
|
-
|
|
68
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
69
|
+
if (!decodedToken) {
|
|
70
|
+
throw new Error("Invalid access token");
|
|
71
|
+
}
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
}
|
|
83
77
|
|
|
84
|
-
|
|
85
|
-
if (validate_against === "all" && !specific_uuid) {
|
|
86
|
-
const cachedData = getCachedData(cacheKey);
|
|
87
|
-
if (cachedData) {
|
|
88
|
-
return cachedData;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
78
|
+
const apiBaseUrl = baseUrl || BASE_URL;
|
|
91
79
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
);
|
|
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}`;
|
|
95
82
|
|
|
96
|
-
|
|
97
|
-
|
|
83
|
+
if (validate_against === "all" && !specific_uuid) {
|
|
84
|
+
const cachedData = getCachedData(cacheKey);
|
|
85
|
+
if (cachedData) {
|
|
86
|
+
return cachedData;
|
|
98
87
|
}
|
|
99
|
-
|
|
100
|
-
if (include_fully_consented_data) {
|
|
101
|
-
url.searchParams.append("include_fully_consented_data", "true");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const response = await fetch(url.toString(), {
|
|
105
|
-
method: "GET",
|
|
106
|
-
headers: {
|
|
107
|
-
Authorization: `Bearer ${accessToken}`,
|
|
108
|
-
"Accept-Language": language,
|
|
109
|
-
"Content-Type": "application/json",
|
|
110
|
-
},
|
|
111
|
-
signal, // Add abort signal for request cancellation
|
|
112
|
-
});
|
|
88
|
+
}
|
|
113
89
|
|
|
114
|
-
|
|
115
|
-
|
|
90
|
+
const url = new URL(
|
|
91
|
+
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/notices/${noticeId}`
|
|
92
|
+
);
|
|
116
93
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
errorMessage = "User has already provided consent";
|
|
125
|
-
} else if (response.status >= 500) {
|
|
126
|
-
errorMessage = "Server error: Please try again later";
|
|
127
|
-
}
|
|
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
|
+
}
|
|
128
101
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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";
|
|
132
124
|
}
|
|
133
125
|
|
|
134
|
-
const
|
|
126
|
+
const error = new Error(errorMessage) as Error & { status?: number };
|
|
127
|
+
error.status = response.status;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
135
130
|
|
|
136
|
-
|
|
137
|
-
if (validate_against === "all" && !specific_uuid) {
|
|
138
|
-
setCachedData(cacheKey, data);
|
|
139
|
-
}
|
|
131
|
+
const data: ConsentContent = await response.json();
|
|
140
132
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
throw error;
|
|
133
|
+
if (validate_against === "all" && !specific_uuid) {
|
|
134
|
+
setCachedData(cacheKey, data);
|
|
144
135
|
}
|
|
136
|
+
|
|
137
|
+
return data;
|
|
145
138
|
};
|
|
146
139
|
|
|
147
140
|
export const submitConsentEvent = async ({
|
|
@@ -150,121 +143,193 @@ export const submitConsentEvent = async ({
|
|
|
150
143
|
noticeUuid,
|
|
151
144
|
purposes,
|
|
152
145
|
declined,
|
|
146
|
+
language,
|
|
153
147
|
meta_data,
|
|
154
|
-
|
|
148
|
+
guardianVerificationReference,
|
|
149
|
+
selfDeclaredAdult,
|
|
150
|
+
signal,
|
|
155
151
|
}: SubmitConsentEventParams & { signal?: AbortSignal }): Promise<void> => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
throw new Error("noticeUuid, accessToken, and purposes array are required");
|
|
160
|
-
}
|
|
152
|
+
if (!noticeUuid || !accessToken || !Array.isArray(purposes)) {
|
|
153
|
+
throw new Error("noticeUuid, accessToken, and purposes array are required");
|
|
154
|
+
}
|
|
161
155
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
156
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
157
|
+
if (!decodedToken) {
|
|
158
|
+
throw new Error("Invalid access token");
|
|
159
|
+
}
|
|
160
|
+
|
|
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
|
+
}
|
|
165
|
+
|
|
166
|
+
const apiBaseUrl = baseUrl || BASE_URL;
|
|
167
|
+
|
|
168
|
+
const validatedPurposes = purposes.map((purpose) => {
|
|
169
|
+
if (!purpose.uuid) {
|
|
170
|
+
throw new Error(`Invalid purpose data: missing uuid`);
|
|
165
171
|
}
|
|
166
172
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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,
|
|
170
210
|
}
|
|
211
|
+
);
|
|
171
212
|
|
|
172
|
-
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
let errorMessage = `Failed to submit consent: ${response.status} ${response.statusText}`;
|
|
173
215
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
216
|
+
if (response.status === 400) {
|
|
217
|
+
try {
|
|
218
|
+
const errorData = await response.json();
|
|
219
|
+
errorMessage = errorData.message || "Invalid request data";
|
|
220
|
+
} catch {
|
|
221
|
+
errorMessage = "Invalid request data";
|
|
177
222
|
}
|
|
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";
|
|
231
|
+
}
|
|
178
232
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
uuid: element.uuid,
|
|
184
|
-
selected: element.required ? true : Boolean(element.selected),
|
|
185
|
-
}));
|
|
233
|
+
const error = new Error(errorMessage) as Error & { status?: number };
|
|
234
|
+
error.status = response.status;
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
186
237
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
data_elements: dataElements.length > 0 ? dataElements : undefined,
|
|
191
|
-
};
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
const payload: ConsentEventPayload = {
|
|
195
|
-
notice_uuid: noticeUuid,
|
|
196
|
-
purposes: validatedPurposes,
|
|
197
|
-
select_all_mandatory: false,
|
|
198
|
-
source: "WEB",
|
|
199
|
-
declined: Boolean(declined),
|
|
200
|
-
meta_data: meta_data ? { specific_uuid: meta_data.specific_uuid } : undefined,
|
|
201
|
-
};
|
|
238
|
+
// Clear any cached consent content since user preferences have changed
|
|
239
|
+
apiCache.clear();
|
|
240
|
+
};
|
|
202
241
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
);
|
|
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
|
+
}
|
|
215
252
|
|
|
216
|
-
|
|
217
|
-
|
|
253
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
254
|
+
if (!decodedToken) {
|
|
255
|
+
throw new Error("Invalid access token");
|
|
256
|
+
}
|
|
218
257
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
} else if (response.status === 403) {
|
|
224
|
-
errorMessage = "Forbidden: Access denied";
|
|
225
|
-
} else if (response.status === 404) {
|
|
226
|
-
errorMessage = "Notice not found";
|
|
227
|
-
} else if (response.status >= 500) {
|
|
228
|
-
errorMessage = "Server error: Please try again later";
|
|
229
|
-
}
|
|
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
|
+
}
|
|
230
262
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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";
|
|
234
287
|
}
|
|
235
288
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
} catch (error) {
|
|
289
|
+
const error = new Error(errorMessage) as Error & { status?: number };
|
|
290
|
+
error.status = response.status;
|
|
240
291
|
throw error;
|
|
241
292
|
}
|
|
293
|
+
|
|
294
|
+
const data: TTSAudioUrlsResponse = await response.json();
|
|
295
|
+
return data;
|
|
242
296
|
};
|
|
243
297
|
|
|
244
|
-
|
|
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 ({
|
|
245
309
|
accessToken,
|
|
246
310
|
baseUrl,
|
|
247
311
|
guardianName,
|
|
248
312
|
guardianContact,
|
|
249
313
|
guardianRelationship,
|
|
250
|
-
|
|
251
|
-
|
|
314
|
+
frontendCallbackUrl,
|
|
315
|
+
metadata,
|
|
316
|
+
signal,
|
|
317
|
+
}: InitiateGuardianVerificationParams): Promise<InitiateGuardianVerificationResponse> => {
|
|
252
318
|
try {
|
|
253
|
-
// Validate required parameters
|
|
254
319
|
if (!accessToken) {
|
|
255
320
|
throw new Error("accessToken is required");
|
|
256
321
|
}
|
|
257
322
|
|
|
258
323
|
if (!guardianName?.trim()) {
|
|
259
|
-
throw new Error("
|
|
324
|
+
throw new Error("Guardian name is required");
|
|
260
325
|
}
|
|
261
326
|
|
|
262
327
|
if (!guardianContact?.trim()) {
|
|
263
|
-
throw new Error("
|
|
328
|
+
throw new Error("Guardian contact is required");
|
|
264
329
|
}
|
|
265
330
|
|
|
266
331
|
if (!guardianRelationship?.trim()) {
|
|
267
|
-
throw new Error("
|
|
332
|
+
throw new Error("Guardian relationship is required");
|
|
268
333
|
}
|
|
269
334
|
|
|
270
335
|
const decodedToken = decodeTokenSafely(accessToken);
|
|
@@ -277,16 +342,30 @@ export const submitGuardianInfo = async ({
|
|
|
277
342
|
throw new Error("Invalid token: missing organization or workspace UUID");
|
|
278
343
|
}
|
|
279
344
|
|
|
280
|
-
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
|
+
} = {
|
|
281
354
|
guardian_name: guardianName.trim(),
|
|
282
355
|
guardian_contact: guardianContact.trim(),
|
|
283
356
|
guardian_relationship: guardianRelationship.trim(),
|
|
284
357
|
};
|
|
285
358
|
|
|
286
|
-
|
|
359
|
+
if (frontendCallbackUrl) {
|
|
360
|
+
payload.frontend_callback_url = frontendCallbackUrl;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (metadata) {
|
|
364
|
+
payload.metadata = metadata;
|
|
365
|
+
}
|
|
287
366
|
|
|
288
367
|
const response = await fetch(
|
|
289
|
-
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/guardian-
|
|
368
|
+
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/guardian/initiate-verification`,
|
|
290
369
|
{
|
|
291
370
|
method: "POST",
|
|
292
371
|
headers: {
|
|
@@ -294,27 +373,29 @@ export const submitGuardianInfo = async ({
|
|
|
294
373
|
"Content-Type": "application/json",
|
|
295
374
|
},
|
|
296
375
|
body: JSON.stringify(payload),
|
|
297
|
-
signal,
|
|
376
|
+
signal,
|
|
298
377
|
}
|
|
299
378
|
);
|
|
300
379
|
|
|
301
380
|
if (!response.ok) {
|
|
302
|
-
let errorMessage = `Failed to
|
|
381
|
+
let errorMessage = `Failed to initiate verification: ${response.status} ${response.statusText}`;
|
|
303
382
|
|
|
304
383
|
if (response.status === 400) {
|
|
305
|
-
|
|
384
|
+
try {
|
|
385
|
+
const errorData = await response.json();
|
|
386
|
+
errorMessage = errorData.message || "Invalid request data";
|
|
387
|
+
} catch {
|
|
388
|
+
errorMessage = "Invalid request data";
|
|
389
|
+
}
|
|
306
390
|
} else if (response.status === 401) {
|
|
307
391
|
errorMessage = "Unauthorized: Invalid or expired token";
|
|
308
|
-
} else if (response.status === 403) {
|
|
309
|
-
errorMessage = "Forbidden: Access denied";
|
|
310
392
|
} else if (response.status === 404) {
|
|
311
393
|
errorMessage = "Organization or workspace not found";
|
|
312
394
|
} else if (response.status === 422) {
|
|
313
|
-
// Handle validation errors from the server
|
|
314
395
|
try {
|
|
315
396
|
const errorData = await response.json();
|
|
316
|
-
errorMessage = errorData.message || "Validation error";
|
|
317
|
-
} catch
|
|
397
|
+
errorMessage = errorData.message || "Validation error: Please check your input";
|
|
398
|
+
} catch {
|
|
318
399
|
errorMessage = "Validation error: Please check your input";
|
|
319
400
|
}
|
|
320
401
|
} else if (response.status >= 500) {
|
|
@@ -326,72 +407,120 @@ export const submitGuardianInfo = async ({
|
|
|
326
407
|
throw error;
|
|
327
408
|
}
|
|
328
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
|
+
};
|
|
329
438
|
} catch (error) {
|
|
330
|
-
|
|
331
|
-
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);
|
|
332
440
|
throw error;
|
|
333
441
|
}
|
|
334
442
|
};
|
|
335
443
|
|
|
336
|
-
|
|
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 ({
|
|
337
452
|
accessToken,
|
|
338
453
|
baseUrl,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (!accessToken || !noticeUuid || !language) {
|
|
346
|
-
throw new Error("accessToken, noticeUuid, and language are required");
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const decodedToken = decodeTokenSafely(accessToken);
|
|
350
|
-
if (!decodedToken) {
|
|
351
|
-
throw new Error("Invalid access token");
|
|
352
|
-
}
|
|
454
|
+
sessionToken,
|
|
455
|
+
signal,
|
|
456
|
+
}: VerifyGuardianStatusParams): Promise<VerifyGuardianStatusResponse> => {
|
|
457
|
+
if (!accessToken || !sessionToken) {
|
|
458
|
+
throw new Error("accessToken and sessionToken are required");
|
|
459
|
+
}
|
|
353
460
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
461
|
+
const decodedToken = decodeTokenSafely(accessToken);
|
|
462
|
+
if (!decodedToken) {
|
|
463
|
+
throw new Error("Invalid access token");
|
|
464
|
+
}
|
|
358
465
|
|
|
359
|
-
|
|
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
|
+
}
|
|
360
470
|
|
|
361
|
-
|
|
471
|
+
const apiBaseUrl = baseUrl || BASE_URL;
|
|
362
472
|
|
|
363
|
-
|
|
364
|
-
|
|
473
|
+
const response = await fetch(
|
|
474
|
+
`${apiBaseUrl}/public/organisations/${ORGANISATION_UUID}/workspaces/${WORKSPACE_UUID}/guardian/verify-status`,
|
|
475
|
+
{
|
|
476
|
+
method: "POST",
|
|
365
477
|
headers: {
|
|
366
478
|
Authorization: `Bearer ${accessToken}`,
|
|
367
|
-
Accept: "application/json",
|
|
368
479
|
"Content-Type": "application/json",
|
|
369
480
|
},
|
|
370
|
-
|
|
371
|
-
|
|
481
|
+
body: JSON.stringify({
|
|
482
|
+
session_token: sessionToken,
|
|
483
|
+
}),
|
|
484
|
+
signal,
|
|
485
|
+
}
|
|
486
|
+
);
|
|
372
487
|
|
|
373
|
-
|
|
374
|
-
|
|
488
|
+
if (!response.ok) {
|
|
489
|
+
let errorMessage = `Failed to check verification status: ${response.status} ${response.statusText}`;
|
|
375
490
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
errorMessage = "
|
|
380
|
-
}
|
|
381
|
-
errorMessage = "
|
|
382
|
-
} else if (response.status >= 500) {
|
|
383
|
-
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";
|
|
384
497
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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";
|
|
389
506
|
}
|
|
390
507
|
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
return data;
|
|
394
|
-
} catch (error) {
|
|
508
|
+
const error = new Error(errorMessage) as Error & { status?: number };
|
|
509
|
+
error.status = response.status;
|
|
395
510
|
throw error;
|
|
396
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
|
+
};
|
|
397
525
|
};
|
|
526
|
+
|