@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,413 @@
|
|
|
1
|
+
import { apiFetch } from "./fetcher";
|
|
2
|
+
import type { ApiFetchOptions, ApiResponse } from "./fetcher";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base request parameters for consent service calls
|
|
6
|
+
*/
|
|
7
|
+
export type BaseRequest = {
|
|
8
|
+
organisationUuid: string;
|
|
9
|
+
workspaceUuid: string;
|
|
10
|
+
authToken: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Consent service client factory.
|
|
15
|
+
* Creates a client instance with a specific base URL.
|
|
16
|
+
* @param baseUrl - Consent server base URL
|
|
17
|
+
*/
|
|
18
|
+
export function createConsentClient(baseUrl: string) {
|
|
19
|
+
/**
|
|
20
|
+
* Privacy Center API fetch wrapper that uses Authorization: Bearer header.
|
|
21
|
+
* Privacy center endpoints require JWT tokens in Authorization header, not X-Auth-Token.
|
|
22
|
+
*/
|
|
23
|
+
async function privacyCenterApiFetch<T>(
|
|
24
|
+
url: string,
|
|
25
|
+
options: ApiFetchOptions & { authToken?: string }
|
|
26
|
+
): Promise<T> {
|
|
27
|
+
const { authToken, getAuthToken, headers = {}, ...fetchOptions } = options;
|
|
28
|
+
|
|
29
|
+
// Build headers using Headers object
|
|
30
|
+
const requestHeaders = new Headers();
|
|
31
|
+
|
|
32
|
+
// Set default headers (skip Content-Type for FormData)
|
|
33
|
+
const isFormData = fetchOptions.body instanceof FormData;
|
|
34
|
+
if (!isFormData) {
|
|
35
|
+
requestHeaders.set("Content-Type", "application/json");
|
|
36
|
+
}
|
|
37
|
+
requestHeaders.set("Accept", "application/json");
|
|
38
|
+
|
|
39
|
+
// Add custom headers from options
|
|
40
|
+
if (headers && typeof headers === "object") {
|
|
41
|
+
if (headers instanceof Headers) {
|
|
42
|
+
headers.forEach((value, key) => {
|
|
43
|
+
requestHeaders.set(key, value);
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
Object.entries(headers as Record<string, string>).forEach(
|
|
47
|
+
([key, value]) => {
|
|
48
|
+
if (value) {
|
|
49
|
+
requestHeaders.set(key, value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add Authorization Bearer token for privacy center APIs
|
|
57
|
+
const token = authToken || (getAuthToken ? await getAuthToken() : null);
|
|
58
|
+
if (token) {
|
|
59
|
+
const trimmedToken = token.trim();
|
|
60
|
+
// Format token with Bearer prefix if not already present
|
|
61
|
+
const bearerToken = trimmedToken.startsWith('Bearer ')
|
|
62
|
+
? trimmedToken
|
|
63
|
+
: `Bearer ${trimmedToken}`;
|
|
64
|
+
requestHeaders.set("Authorization", bearerToken);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Build fetch options
|
|
68
|
+
const fetchOptionsFinal: RequestInit = {
|
|
69
|
+
...fetchOptions,
|
|
70
|
+
headers: requestHeaders,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(url, fetchOptionsFinal);
|
|
75
|
+
|
|
76
|
+
// Handle non-OK responses
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
let errorMessage = `Request failed with status ${response.status}`;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const errorData = (await response.json()) as {
|
|
82
|
+
detail?: unknown;
|
|
83
|
+
message?: string;
|
|
84
|
+
} | null;
|
|
85
|
+
if (errorData) {
|
|
86
|
+
if ("detail" in errorData && errorData.detail) {
|
|
87
|
+
errorMessage =
|
|
88
|
+
typeof errorData.detail === "string"
|
|
89
|
+
? errorData.detail
|
|
90
|
+
: JSON.stringify(errorData.detail, null, 2);
|
|
91
|
+
} else if ("message" in errorData && errorData.message) {
|
|
92
|
+
errorMessage = errorData.message;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// If response is not JSON, try to get text
|
|
97
|
+
try {
|
|
98
|
+
const errorText = await response.text();
|
|
99
|
+
if (errorText) {
|
|
100
|
+
errorMessage = errorText;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
// Ignore parsing errors
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
throw new Error(errorMessage);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Parse JSON response
|
|
111
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
112
|
+
if (!contentType.includes("application/json")) {
|
|
113
|
+
const textResponse = await response.text();
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Expected JSON response, got ${contentType}: ${textResponse}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const data = (await response.json()) as ApiResponse<T> | T;
|
|
120
|
+
|
|
121
|
+
// Handle wrapped API responses (code, status, detail format)
|
|
122
|
+
if (
|
|
123
|
+
typeof data === "object" &&
|
|
124
|
+
data !== null &&
|
|
125
|
+
"code" in data &&
|
|
126
|
+
"status" in data &&
|
|
127
|
+
"detail" in data
|
|
128
|
+
) {
|
|
129
|
+
const apiResponse = data as ApiResponse<T>;
|
|
130
|
+
return apiResponse.detail as T;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return data as T;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error instanceof Error) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
throw new Error(`Unknown error occurred: ${String(error)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
/**
|
|
144
|
+
* Get form data (privacy center)
|
|
145
|
+
*/
|
|
146
|
+
async getFormData(
|
|
147
|
+
baseRequest: BaseRequest,
|
|
148
|
+
contact: string,
|
|
149
|
+
authToken?: string,
|
|
150
|
+
options?: ApiFetchOptions & { useSessionAuth?: boolean },
|
|
151
|
+
language?: string
|
|
152
|
+
): Promise<unknown> {
|
|
153
|
+
const queryParams = new URLSearchParams();
|
|
154
|
+
if (language) queryParams.append("language", language);
|
|
155
|
+
const queryString = queryParams.toString();
|
|
156
|
+
const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/form/data${queryString ? `?${queryString}` : ""}`;
|
|
157
|
+
const { useSessionAuth = false, getAuthToken, ...fetchOptions } = options || {};
|
|
158
|
+
|
|
159
|
+
// Case management uses session-based auth (X-Auth-Token)
|
|
160
|
+
// Privacy center uses JWT Bearer tokens (Authorization: Bearer)
|
|
161
|
+
if (useSessionAuth) {
|
|
162
|
+
return apiFetch(url, {
|
|
163
|
+
...fetchOptions,
|
|
164
|
+
method: "POST",
|
|
165
|
+
body: JSON.stringify({ contact }),
|
|
166
|
+
getAuthToken,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Privacy center: use JWT Bearer token
|
|
171
|
+
return privacyCenterApiFetch(url, {
|
|
172
|
+
...fetchOptions,
|
|
173
|
+
method: "POST",
|
|
174
|
+
body: JSON.stringify({ contact }),
|
|
175
|
+
authToken: authToken || baseRequest.authToken,
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create case request (privacy center or case management)
|
|
181
|
+
*/
|
|
182
|
+
async createCaseRequest(
|
|
183
|
+
baseRequest: BaseRequest,
|
|
184
|
+
payload: unknown,
|
|
185
|
+
authToken?: string,
|
|
186
|
+
options?: ApiFetchOptions & { useSessionAuth?: boolean }
|
|
187
|
+
): Promise<unknown> {
|
|
188
|
+
const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/case`;
|
|
189
|
+
const { useSessionAuth = false, getAuthToken, ...fetchOptions } = options || {};
|
|
190
|
+
|
|
191
|
+
// Case management uses session-based auth (X-Auth-Token)
|
|
192
|
+
// Privacy center uses JWT Bearer tokens (Authorization: Bearer)
|
|
193
|
+
if (useSessionAuth) {
|
|
194
|
+
return apiFetch(url, {
|
|
195
|
+
...fetchOptions,
|
|
196
|
+
method: "POST",
|
|
197
|
+
body: JSON.stringify(payload),
|
|
198
|
+
getAuthToken,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Privacy center: use JWT Bearer token
|
|
203
|
+
return privacyCenterApiFetch(url, {
|
|
204
|
+
...fetchOptions,
|
|
205
|
+
method: "POST",
|
|
206
|
+
body: JSON.stringify(payload),
|
|
207
|
+
authToken: authToken || baseRequest.authToken,
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Create OTP (public)
|
|
213
|
+
*/
|
|
214
|
+
async createOtp(
|
|
215
|
+
organisationId: string,
|
|
216
|
+
workspaceId: string,
|
|
217
|
+
email: string,
|
|
218
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
219
|
+
): Promise<unknown> {
|
|
220
|
+
const url = `${baseUrl}/public/organisations/${organisationId}/workspaces/${workspaceId}/otp/create`;
|
|
221
|
+
return apiFetch(url, {
|
|
222
|
+
...options,
|
|
223
|
+
method: "POST",
|
|
224
|
+
body: JSON.stringify({ to: email, channel: "email" }),
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Verify OTP (public)
|
|
230
|
+
*/
|
|
231
|
+
async verifyOtp(
|
|
232
|
+
organisationId: string,
|
|
233
|
+
workspaceId: string,
|
|
234
|
+
otpId: string,
|
|
235
|
+
otp: string,
|
|
236
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
237
|
+
): Promise<unknown> {
|
|
238
|
+
const url = `${baseUrl}/public/organisations/${organisationId}/workspaces/${workspaceId}/otp/verify`;
|
|
239
|
+
return apiFetch(url, {
|
|
240
|
+
...options,
|
|
241
|
+
method: "POST",
|
|
242
|
+
body: JSON.stringify({ uuid: otpId, otp }),
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Exchange one-time token for JWT tokens (public)
|
|
248
|
+
*/
|
|
249
|
+
async exchangeToken(
|
|
250
|
+
token: string,
|
|
251
|
+
): Promise<unknown> {
|
|
252
|
+
const url = `${baseUrl}/public/tokens/token-exchange?token=${encodeURIComponent(token)}`;
|
|
253
|
+
const response = await fetch(url, {
|
|
254
|
+
method: "GET",
|
|
255
|
+
headers: { "Accept": "application/json" },
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
const errorData = await response.json().catch(() => null) as { detail?: string; message?: string } | null;
|
|
260
|
+
throw new Error(
|
|
261
|
+
errorData?.detail || errorData?.message || `Token exchange failed with status ${response.status}`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const data = await response.json() as { code?: number; status?: string; detail?: unknown };
|
|
266
|
+
if (data && typeof data === "object" && "code" in data && "status" in data && "detail" in data) {
|
|
267
|
+
return data.detail;
|
|
268
|
+
}
|
|
269
|
+
return data;
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Refresh OTP token (public)
|
|
274
|
+
*/
|
|
275
|
+
async refreshOtpToken(
|
|
276
|
+
organisationId: string,
|
|
277
|
+
workspaceId: string,
|
|
278
|
+
refreshToken: string,
|
|
279
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
280
|
+
): Promise<unknown> {
|
|
281
|
+
const url = `${baseUrl}/public/organisations/${organisationId}/workspaces/${workspaceId}/otp/refresh`;
|
|
282
|
+
return apiFetch(url, {
|
|
283
|
+
...options,
|
|
284
|
+
method: "POST",
|
|
285
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get products
|
|
291
|
+
*/
|
|
292
|
+
async getProducts(
|
|
293
|
+
baseRequest: BaseRequest,
|
|
294
|
+
skip: number = 0,
|
|
295
|
+
limit: number = 10,
|
|
296
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
297
|
+
): Promise<unknown> {
|
|
298
|
+
const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/notice-management/products/?skip=${skip}&limit=${limit}`;
|
|
299
|
+
return apiFetch<unknown>(url, {
|
|
300
|
+
...options,
|
|
301
|
+
getAuthToken: async () => baseRequest.authToken,
|
|
302
|
+
});
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get user consents (privacy center)
|
|
307
|
+
*/
|
|
308
|
+
async getUserConsents(
|
|
309
|
+
baseRequest: BaseRequest,
|
|
310
|
+
contact: string,
|
|
311
|
+
offset?: number,
|
|
312
|
+
limit?: number,
|
|
313
|
+
search?: string,
|
|
314
|
+
status?: string,
|
|
315
|
+
product_uuid?: string,
|
|
316
|
+
language?: string,
|
|
317
|
+
authToken?: string,
|
|
318
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
319
|
+
): Promise<unknown> {
|
|
320
|
+
const queryParams = new URLSearchParams();
|
|
321
|
+
if (offset !== undefined) queryParams.append("offset", offset.toString());
|
|
322
|
+
if (limit !== undefined) queryParams.append("limit", limit.toString());
|
|
323
|
+
if (search) queryParams.append("search", search);
|
|
324
|
+
if (status) queryParams.append("status", status);
|
|
325
|
+
if (product_uuid) queryParams.append("product_uuid", product_uuid);
|
|
326
|
+
if (language) queryParams.append("language", language);
|
|
327
|
+
const queryString = queryParams.toString();
|
|
328
|
+
const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/user-consents${queryString ? `?${queryString}` : ""}`;
|
|
329
|
+
return privacyCenterApiFetch(url, {
|
|
330
|
+
...options,
|
|
331
|
+
method: "POST",
|
|
332
|
+
body: JSON.stringify({ contact }),
|
|
333
|
+
authToken: authToken || baseRequest.authToken,
|
|
334
|
+
});
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Manage consent (privacy center) - revoke, regrant, or renew
|
|
339
|
+
*/
|
|
340
|
+
async manageConsent(
|
|
341
|
+
baseRequest: BaseRequest,
|
|
342
|
+
purposeId: string,
|
|
343
|
+
contact: string,
|
|
344
|
+
status: 'revoke' | 'regrant' | 'renew',
|
|
345
|
+
authToken?: string,
|
|
346
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">,
|
|
347
|
+
nominatorContact?: string,
|
|
348
|
+
dataElementUuids?: string[]
|
|
349
|
+
): Promise<unknown> {
|
|
350
|
+
const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/manage-consent?status=${status}`;
|
|
351
|
+
const body: Record<string, unknown> = { purpose_uuid: purposeId, contact };
|
|
352
|
+
if (nominatorContact) {
|
|
353
|
+
body.nominator_contact = nominatorContact;
|
|
354
|
+
}
|
|
355
|
+
if (Array.isArray(dataElementUuids) && dataElementUuids.length > 0) {
|
|
356
|
+
body.data_element_uuids = dataElementUuids;
|
|
357
|
+
}
|
|
358
|
+
return privacyCenterApiFetch(url, {
|
|
359
|
+
...options,
|
|
360
|
+
method: "POST",
|
|
361
|
+
body: JSON.stringify(body),
|
|
362
|
+
authToken: authToken || baseRequest.authToken,
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get activities (privacy center)
|
|
368
|
+
*/
|
|
369
|
+
async getActivities(
|
|
370
|
+
baseRequest: BaseRequest,
|
|
371
|
+
offset?: number,
|
|
372
|
+
limit?: number,
|
|
373
|
+
language?: string,
|
|
374
|
+
authToken?: string,
|
|
375
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
376
|
+
): Promise<unknown> {
|
|
377
|
+
const queryParams = new URLSearchParams();
|
|
378
|
+
if (offset !== undefined) queryParams.append("offset", offset.toString());
|
|
379
|
+
if (limit !== undefined) queryParams.append("limit", limit.toString());
|
|
380
|
+
if (language) queryParams.append("language", language);
|
|
381
|
+
const queryString = queryParams.toString();
|
|
382
|
+
const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/activities${queryString ? `?${queryString}` : ""}`;
|
|
383
|
+
return privacyCenterApiFetch<unknown>(url, {
|
|
384
|
+
...options,
|
|
385
|
+
authToken: authToken || baseRequest.authToken,
|
|
386
|
+
});
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get case history (privacy center)
|
|
391
|
+
*/
|
|
392
|
+
async getCaseHistory(
|
|
393
|
+
baseRequest: BaseRequest,
|
|
394
|
+
contact: string,
|
|
395
|
+
offset?: number,
|
|
396
|
+
limit?: number,
|
|
397
|
+
language?: string,
|
|
398
|
+
authToken?: string,
|
|
399
|
+
options?: Omit<ApiFetchOptions, "getAuthToken">
|
|
400
|
+
): Promise<unknown> {
|
|
401
|
+
const queryParams = new URLSearchParams();
|
|
402
|
+
if (offset !== undefined) queryParams.append("offset", offset.toString());
|
|
403
|
+
if (limit !== undefined) queryParams.append("limit", limit.toString());
|
|
404
|
+
if (language) queryParams.append("language", language);
|
|
405
|
+
const queryString = queryParams.toString();
|
|
406
|
+
const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center/cases?${queryString}`;
|
|
407
|
+
return privacyCenterApiFetch<unknown>(url, {
|
|
408
|
+
...options,
|
|
409
|
+
authToken: authToken || baseRequest.authToken,
|
|
410
|
+
});
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side configuration for backend service URLs.
|
|
3
|
+
* consent.baseUrl is set via BaseUrlProvider context.
|
|
4
|
+
*/
|
|
5
|
+
let baseUrl: string | undefined = undefined;
|
|
6
|
+
|
|
7
|
+
export const config = {
|
|
8
|
+
consent: {
|
|
9
|
+
get baseUrl() {
|
|
10
|
+
return baseUrl;
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Sets the base URL from the context provider.
|
|
17
|
+
* This should be called by BaseUrlProvider when it mounts.
|
|
18
|
+
*/
|
|
19
|
+
export function setUserBaseUrl(url: string): void {
|
|
20
|
+
baseUrl = url;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validates that required base URL is set.
|
|
25
|
+
* Throws an error if base URL is missing.
|
|
26
|
+
*/
|
|
27
|
+
export function validateConfig(): void {
|
|
28
|
+
if (!config.consent.baseUrl) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
"Base URL is not defined. Ensure BaseUrlProvider is set up correctly."
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for apiFetch function
|
|
3
|
+
*/
|
|
4
|
+
export type ApiFetchOptions = RequestInit & {
|
|
5
|
+
/**
|
|
6
|
+
* Auth token getter function
|
|
7
|
+
*/
|
|
8
|
+
getAuthToken?: () => Promise<string | null>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Standard API response wrapper
|
|
13
|
+
*/
|
|
14
|
+
export type ApiResponse<T> = {
|
|
15
|
+
code: number;
|
|
16
|
+
status: string;
|
|
17
|
+
detail: T;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Unified fetch wrapper for client-side API calls.
|
|
22
|
+
* Provides typed responses and automatic error handling.
|
|
23
|
+
*
|
|
24
|
+
* @param url - The URL to fetch
|
|
25
|
+
* @param options - Fetch options including auth token getter
|
|
26
|
+
* @returns Typed response data
|
|
27
|
+
* @throws Error if the request fails
|
|
28
|
+
*/
|
|
29
|
+
export async function apiFetch<T>(
|
|
30
|
+
url: string,
|
|
31
|
+
options: ApiFetchOptions = {}
|
|
32
|
+
): Promise<T> {
|
|
33
|
+
const { getAuthToken, headers = {}, ...fetchOptions } = options;
|
|
34
|
+
|
|
35
|
+
// Build headers using Headers object for better compatibility
|
|
36
|
+
const requestHeaders = new Headers();
|
|
37
|
+
|
|
38
|
+
// Set default headers (skip Content-Type for FormData - browser will set it with boundary)
|
|
39
|
+
const isFormData = fetchOptions.body instanceof FormData;
|
|
40
|
+
if (!isFormData) {
|
|
41
|
+
requestHeaders.set("Content-Type", "application/json");
|
|
42
|
+
}
|
|
43
|
+
requestHeaders.set("Accept", "application/json");
|
|
44
|
+
|
|
45
|
+
// Add custom headers from options
|
|
46
|
+
if (headers && typeof headers === "object") {
|
|
47
|
+
if (headers instanceof Headers) {
|
|
48
|
+
headers.forEach((value, key) => {
|
|
49
|
+
requestHeaders.set(key, value);
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
Object.entries(headers as Record<string, string>).forEach(
|
|
53
|
+
([key, value]) => {
|
|
54
|
+
if (value) {
|
|
55
|
+
requestHeaders.set(key, value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Add auth token AFTER custom headers to ensure it's never overridden
|
|
63
|
+
if (getAuthToken) {
|
|
64
|
+
const token = await getAuthToken();
|
|
65
|
+
if (token) {
|
|
66
|
+
// Use the exact header format that works in other parts of the codebase
|
|
67
|
+
// Other working API calls use 'X-Auth-Token' (capital A and T, rest lowercase)
|
|
68
|
+
const trimmedToken = token.trim();
|
|
69
|
+
requestHeaders.set("X-Auth-Token", trimmedToken);
|
|
70
|
+
// Also set lowercase for compatibility (some servers are case-sensitive)
|
|
71
|
+
requestHeaders.set("x-auth-token", trimmedToken);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build fetch options
|
|
76
|
+
const fetchOptionsFinal: RequestInit = {
|
|
77
|
+
...fetchOptions,
|
|
78
|
+
headers: requestHeaders,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch(url, fetchOptionsFinal);
|
|
83
|
+
|
|
84
|
+
// Handle non-OK responses
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
let errorMessage = `Request failed with status ${response.status}`;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const errorData = (await response.json()) as {
|
|
90
|
+
detail?: unknown;
|
|
91
|
+
message?: string;
|
|
92
|
+
} | null;
|
|
93
|
+
if (errorData) {
|
|
94
|
+
if ("detail" in errorData && errorData.detail) {
|
|
95
|
+
errorMessage =
|
|
96
|
+
typeof errorData.detail === "string"
|
|
97
|
+
? errorData.detail
|
|
98
|
+
: JSON.stringify(errorData.detail, null, 2);
|
|
99
|
+
} else if ("message" in errorData && errorData.message) {
|
|
100
|
+
errorMessage = errorData.message;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
// If response is not JSON, try to get text
|
|
105
|
+
try {
|
|
106
|
+
const errorText = await response.text();
|
|
107
|
+
if (errorText) {
|
|
108
|
+
errorMessage = errorText;
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
// Ignore parsing errors
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new Error(errorMessage);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle empty responses (common for DELETE requests with 204 No Content)
|
|
119
|
+
const contentLength = response.headers.get("Content-Length");
|
|
120
|
+
const contentType = response.headers.get("Content-Type") || "";
|
|
121
|
+
|
|
122
|
+
// If content length is 0, return undefined for void types
|
|
123
|
+
if (contentLength === "0") {
|
|
124
|
+
return undefined as T;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Read response text once (we can't read it twice)
|
|
128
|
+
let text: string;
|
|
129
|
+
try {
|
|
130
|
+
text = await response.text();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// If reading fails, assume empty response is acceptable for void return types
|
|
133
|
+
return undefined as T;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// If response is empty, return undefined for void types
|
|
137
|
+
if (!text || text.trim() === "") {
|
|
138
|
+
return undefined as T;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Parse JSON response
|
|
142
|
+
if (contentType && !contentType.includes("application/json")) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Expected JSON response, got ${contentType}: ${text.substring(0, 100)}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let data: ApiResponse<T> | T;
|
|
149
|
+
try {
|
|
150
|
+
data = JSON.parse(text) as ApiResponse<T> | T;
|
|
151
|
+
} catch (parseError) {
|
|
152
|
+
// If JSON parsing fails, check if it's just whitespace or "null"
|
|
153
|
+
if (text.trim() === "" || text.trim() === "null") {
|
|
154
|
+
return undefined as T;
|
|
155
|
+
}
|
|
156
|
+
// Otherwise, throw the parsing error
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Failed to parse JSON response: ${parseError instanceof Error ? parseError.message : String(parseError)}. Response: ${text.substring(0, 200)}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Handle wrapped API responses (code, status, detail format)
|
|
163
|
+
if (
|
|
164
|
+
typeof data === "object" &&
|
|
165
|
+
data !== null &&
|
|
166
|
+
"code" in data &&
|
|
167
|
+
"status" in data &&
|
|
168
|
+
"detail" in data
|
|
169
|
+
) {
|
|
170
|
+
const apiResponse = data as ApiResponse<T>;
|
|
171
|
+
return apiResponse.detail as T;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return data as T;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (error instanceof Error) {
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
throw new Error(`Unknown error occurred: ${String(error)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|