@redacto.io/consent-sdk-react 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/LICENSE +176 -0
  3. package/dist/index.js +231 -96
  4. package/dist/index.mjs +231 -96
  5. package/dist/privacy-center.d.mts +649 -0
  6. package/dist/privacy-center.d.ts +649 -0
  7. package/dist/privacy-center.js +20824 -0
  8. package/dist/privacy-center.mjs +20789 -0
  9. package/package.json +47 -3
  10. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +6 -4
  11. package/src/RedactoNoticeConsent/api/index.ts +2 -0
  12. package/src/RedactoNoticeConsent/api/types.ts +2 -0
  13. package/src/RedactoNoticeConsent/components/DigiLockerCallback.tsx +128 -62
  14. package/src/RedactoNoticeConsent/language-codes.ts +97 -0
  15. package/src/RedactoNoticeConsentInline/RedactoNoticeConsentInline.tsx +0 -3
  16. package/src/RedactoNoticeConsentInline/api/index.ts +0 -1
  17. package/src/RedactoPrivacyCenter/PrivacyCenterActivityPage.tsx +19 -0
  18. package/src/RedactoPrivacyCenter/PrivacyCenterCaseDetailsPage.tsx +154 -0
  19. package/src/RedactoPrivacyCenter/PrivacyCenterConsentManagerPage.tsx +17 -0
  20. package/src/RedactoPrivacyCenter/PrivacyCenterFormPage.tsx +79 -0
  21. package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +30 -0
  22. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +173 -0
  23. package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +55 -0
  24. package/src/RedactoPrivacyCenter/api/actions.ts +476 -0
  25. package/src/RedactoPrivacyCenter/api/client.ts +524 -0
  26. package/src/RedactoPrivacyCenter/api/config.ts +33 -0
  27. package/src/RedactoPrivacyCenter/api/fetcher.ts +181 -0
  28. package/src/RedactoPrivacyCenter/api/upload.ts +93 -0
  29. package/src/RedactoPrivacyCenter/assets/character.svg +31 -0
  30. package/src/RedactoPrivacyCenter/assets/fileAttach.svg +9 -0
  31. package/src/RedactoPrivacyCenter/assets/greenTick.svg +4 -0
  32. package/src/RedactoPrivacyCenter/assets/mail.svg +3 -0
  33. package/src/RedactoPrivacyCenter/assets/poweredByRedacto.svg +9 -0
  34. package/src/RedactoPrivacyCenter/assets/sentImg.svg +9 -0
  35. package/src/RedactoPrivacyCenter/assets/uploadFileIcon.svg +9 -0
  36. package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +272 -0
  37. package/src/RedactoPrivacyCenter/components/CaseDetails/AttachmentCard.tsx +38 -0
  38. package/src/RedactoPrivacyCenter/components/CaseDetails/ChatBubble.tsx +31 -0
  39. package/src/RedactoPrivacyCenter/components/CaseDetails/DocumentRequestCard.tsx +73 -0
  40. package/src/RedactoPrivacyCenter/components/CaseDetails/MessageComposer.tsx +87 -0
  41. package/src/RedactoPrivacyCenter/components/CaseDetails/MessageItem.tsx +95 -0
  42. package/src/RedactoPrivacyCenter/components/CaseDetails/MessagesTab.tsx +147 -0
  43. package/src/RedactoPrivacyCenter/components/CaseDetails/RequestDetailsTab.tsx +171 -0
  44. package/src/RedactoPrivacyCenter/components/CaseDetails/StatusEvent.tsx +23 -0
  45. package/src/RedactoPrivacyCenter/components/CaseDetails/useFileUpload.ts +116 -0
  46. package/src/RedactoPrivacyCenter/components/CaseDetails/useMessagePolling.ts +36 -0
  47. package/src/RedactoPrivacyCenter/components/CaseDetails/utils.ts +56 -0
  48. package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +469 -0
  49. package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +426 -0
  50. package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +732 -0
  51. package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +238 -0
  52. package/src/RedactoPrivacyCenter/components/ConsentManagerConstants.ts +7 -0
  53. package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +155 -0
  54. package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +189 -0
  55. package/src/RedactoPrivacyCenter/components/DataRequest/GrievanceDataSection.tsx +118 -0
  56. package/src/RedactoPrivacyCenter/components/DataRequest/NominationDataSection.tsx +66 -0
  57. package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +304 -0
  58. package/src/RedactoPrivacyCenter/components/Form.tsx +1596 -0
  59. package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +174 -0
  60. package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +337 -0
  61. package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +330 -0
  62. package/src/RedactoPrivacyCenter/components/Pagination.tsx +140 -0
  63. package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +304 -0
  64. package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +232 -0
  65. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +475 -0
  66. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideMenuPC.tsx +408 -0
  67. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +42 -0
  68. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/menuItemsPC.tsx +1 -0
  69. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +40 -0
  70. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/utils.ts +38 -0
  71. package/src/RedactoPrivacyCenter/components/ThemeTransitionOverlay.tsx +85 -0
  72. package/src/RedactoPrivacyCenter/components/activitiesColumns.tsx +141 -0
  73. package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +159 -0
  74. package/src/RedactoPrivacyCenter/components/constant.ts +16 -0
  75. package/src/RedactoPrivacyCenter/components/types.ts +76 -0
  76. package/src/RedactoPrivacyCenter/components/utils.tsx +10 -0
  77. package/src/RedactoPrivacyCenter/config/routes.ts +9 -0
  78. package/src/RedactoPrivacyCenter/config/table.ts +25 -0
  79. package/src/RedactoPrivacyCenter/context/AuthContext.tsx +137 -0
  80. package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +41 -0
  81. package/src/RedactoPrivacyCenter/context/LocaleContext.tsx +11 -0
  82. package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +96 -0
  83. package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +30 -0
  84. package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +60 -0
  85. package/src/RedactoPrivacyCenter/context/theme.tsx +127 -0
  86. package/src/RedactoPrivacyCenter/context/types.ts +14 -0
  87. package/src/RedactoPrivacyCenter/lib/api.ts +566 -0
  88. package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +63 -0
  89. package/src/RedactoPrivacyCenter/lib/constants.ts +161 -0
  90. package/src/RedactoPrivacyCenter/lib/i18n.ts +81 -0
  91. package/src/RedactoPrivacyCenter/lib/locales.ts +105 -0
  92. package/src/RedactoPrivacyCenter/lib/queries.ts +134 -0
  93. package/src/RedactoPrivacyCenter/lib/types.ts +531 -0
  94. package/src/RedactoPrivacyCenter/lib/useCurrentLanguage.ts +37 -0
  95. package/src/RedactoPrivacyCenter/lib/utils.ts +626 -0
  96. package/src/RedactoPrivacyCenter/locales/as/translation.json +280 -0
  97. package/src/RedactoPrivacyCenter/locales/bho/translation.json +280 -0
  98. package/src/RedactoPrivacyCenter/locales/bn/translation.json +280 -0
  99. package/src/RedactoPrivacyCenter/locales/brx/translation.json +280 -0
  100. package/src/RedactoPrivacyCenter/locales/doi/translation.json +280 -0
  101. package/src/RedactoPrivacyCenter/locales/en/translation.json +255 -0
  102. package/src/RedactoPrivacyCenter/locales/gom/translation.json +280 -0
  103. package/src/RedactoPrivacyCenter/locales/gu/translation.json +280 -0
  104. package/src/RedactoPrivacyCenter/locales/hi/translation.json +280 -0
  105. package/src/RedactoPrivacyCenter/locales/kn/translation.json +280 -0
  106. package/src/RedactoPrivacyCenter/locales/ks/translation.json +280 -0
  107. package/src/RedactoPrivacyCenter/locales/mai/translation.json +280 -0
  108. package/src/RedactoPrivacyCenter/locales/ml/translation.json +280 -0
  109. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +280 -0
  110. package/src/RedactoPrivacyCenter/locales/mr/translation.json +280 -0
  111. package/src/RedactoPrivacyCenter/locales/ne/translation.json +280 -0
  112. package/src/RedactoPrivacyCenter/locales/or/translation.json +280 -0
  113. package/src/RedactoPrivacyCenter/locales/pa/translation.json +280 -0
  114. package/src/RedactoPrivacyCenter/locales/sa/translation.json +280 -0
  115. package/src/RedactoPrivacyCenter/locales/sat/translation.json +280 -0
  116. package/src/RedactoPrivacyCenter/locales/sd/translation.json +280 -0
  117. package/src/RedactoPrivacyCenter/locales/ta/translation.json +280 -0
  118. package/src/RedactoPrivacyCenter/locales/te/translation.json +280 -0
  119. package/src/RedactoPrivacyCenter/locales/ur/translation.json +280 -0
  120. package/src/RedactoPrivacyCenter/styles/base.css +117 -0
  121. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +1725 -0
  122. package/src/RedactoPrivacyCenter/styles/pcStyles.ts +813 -0
  123. package/src/RedactoPrivacyCenter/styles/responsive.ts +138 -0
  124. package/src/RedactoPrivacyCenter/styles/variables.css +133 -0
  125. package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +367 -0
  126. package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +476 -0
  127. package/src/RedactoPrivacyCenter/table/index.ts +2 -0
  128. package/src/RedactoPrivacyCenter/types/images.d.ts +29 -0
  129. package/src/RedactoPrivacyCenter/ui/Footer.tsx +75 -0
  130. package/src/RedactoPrivacyCenter/ui/FormBlockWrapper.tsx +54 -0
  131. package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +93 -0
  132. package/src/RedactoPrivacyCenter/ui/PCActionIcon.tsx +47 -0
  133. package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +69 -0
  134. package/src/RedactoPrivacyCenter/ui/PCBadge.tsx +48 -0
  135. package/src/RedactoPrivacyCenter/ui/PCButton.tsx +77 -0
  136. package/src/RedactoPrivacyCenter/ui/PCCheckbox.tsx +55 -0
  137. package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +107 -0
  138. package/src/RedactoPrivacyCenter/ui/PCEmpty.tsx +35 -0
  139. package/src/RedactoPrivacyCenter/ui/PCInput.tsx +132 -0
  140. package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +31 -0
  141. package/src/RedactoPrivacyCenter/ui/PCModal.tsx +106 -0
  142. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +173 -0
  143. package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +387 -0
  144. package/src/RedactoPrivacyCenter/ui/PCTable.tsx +149 -0
  145. package/src/RedactoPrivacyCenter/ui/PCText.tsx +41 -0
  146. package/src/RedactoPrivacyCenter/ui/PCTextarea.tsx +43 -0
  147. package/src/RedactoPrivacyCenter/ui/PCTitle.tsx +37 -0
  148. package/src/RedactoPrivacyCenter/ui/PCTooltip.tsx +119 -0
  149. package/src/RedactoPrivacyCenter/ui/SimpleBar.tsx +25 -0
  150. package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +53 -0
  151. package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +72 -0
  152. package/src/RedactoPrivacyCenter/ui/index.ts +18 -0
  153. package/src/RedactoPrivacyCenter/utils/cn.ts +28 -0
  154. package/src/RedactoPrivacyCenter/utils/date.ts +48 -0
  155. package/src/RedactoPrivacyCenter/utils/status.ts +111 -0
  156. package/src/RedactoPrivacyCenter/utils/toast.ts +130 -0
  157. package/src/privacy-center.ts +108 -0
  158. package/tsconfig.json +2 -0
  159. package/tsup.config.ts +16 -1
  160. package/.turbo/turbo-build.log +0 -21
@@ -0,0 +1,524 @@
1
+ import { apiFetch } from "./fetcher";
2
+ import type { ApiFetchOptions, ApiResponse } from "./fetcher";
3
+ import type {
4
+ CaseMessage,
5
+ MessageDocument,
6
+ UploadDocumentResponse,
7
+ } from "../lib/types";
8
+
9
+ /**
10
+ * Base request parameters for consent service calls
11
+ */
12
+ export type BaseRequest = {
13
+ organisationUuid: string;
14
+ workspaceUuid: string;
15
+ authToken: string;
16
+ };
17
+
18
+ /**
19
+ * Consent service client factory.
20
+ * Creates a client instance with a specific base URL.
21
+ * @param baseUrl - Consent server base URL
22
+ */
23
+ export function createConsentClient(baseUrl: string) {
24
+ /**
25
+ * Privacy Center API fetch wrapper that uses Authorization: Bearer header.
26
+ * Privacy center endpoints require JWT tokens in Authorization header, not X-Auth-Token.
27
+ */
28
+ async function privacyCenterApiFetch<T>(
29
+ url: string,
30
+ options: ApiFetchOptions & { authToken?: string }
31
+ ): Promise<T> {
32
+ const { authToken, getAuthToken, headers = {}, ...fetchOptions } = options;
33
+
34
+ // Build headers using Headers object
35
+ const requestHeaders = new Headers();
36
+
37
+ // Set default headers (skip Content-Type for FormData)
38
+ const isFormData = fetchOptions.body instanceof FormData;
39
+ if (!isFormData) {
40
+ requestHeaders.set("Content-Type", "application/json");
41
+ }
42
+ requestHeaders.set("Accept", "application/json");
43
+
44
+ // Add custom headers from options
45
+ if (headers && typeof headers === "object") {
46
+ if (headers instanceof Headers) {
47
+ headers.forEach((value, key) => {
48
+ requestHeaders.set(key, value);
49
+ });
50
+ } else {
51
+ Object.entries(headers as Record<string, string>).forEach(
52
+ ([key, value]) => {
53
+ if (value) {
54
+ requestHeaders.set(key, value);
55
+ }
56
+ }
57
+ );
58
+ }
59
+ }
60
+
61
+ // Add Authorization Bearer token for privacy center APIs
62
+ const token = authToken || (getAuthToken ? await getAuthToken() : null);
63
+ if (token) {
64
+ const trimmedToken = token.trim();
65
+ // Format token with Bearer prefix if not already present
66
+ const bearerToken = trimmedToken.startsWith('Bearer ')
67
+ ? trimmedToken
68
+ : `Bearer ${trimmedToken}`;
69
+ requestHeaders.set("Authorization", bearerToken);
70
+ }
71
+
72
+ // Build fetch options
73
+ const fetchOptionsFinal: RequestInit = {
74
+ ...fetchOptions,
75
+ headers: requestHeaders,
76
+ };
77
+
78
+ try {
79
+ const response = await fetch(url, fetchOptionsFinal);
80
+
81
+ // Handle non-OK responses
82
+ if (!response.ok) {
83
+ let errorMessage = `Request failed with status ${response.status}`;
84
+
85
+ try {
86
+ const errorData = (await response.json()) as {
87
+ detail?: unknown;
88
+ message?: string;
89
+ code?: string;
90
+ } | null;
91
+ if (errorData) {
92
+ if ("detail" in errorData && errorData.detail) {
93
+ errorMessage =
94
+ typeof errorData.detail === "string"
95
+ ? errorData.detail
96
+ : JSON.stringify(errorData.detail, null, 2);
97
+ } else if ("message" in errorData && errorData.message) {
98
+ errorMessage = errorData.message;
99
+ } else if ("code" in errorData && errorData.code) {
100
+ // e.g. { code: "empty_file" } or { code: "document_already_exists" }
101
+ errorMessage = errorData.code;
102
+ }
103
+ }
104
+ } catch {
105
+ // If response is not JSON, try to get text
106
+ try {
107
+ const errorText = await response.text();
108
+ if (errorText) {
109
+ errorMessage = errorText;
110
+ }
111
+ } catch {
112
+ // Ignore parsing errors
113
+ }
114
+ }
115
+
116
+ // Prepend HTTP status so callers can branch on it without losing context
117
+ throw new Error(`[${response.status}] ${errorMessage}`);
118
+ }
119
+
120
+ // Parse JSON response
121
+ const contentType = response.headers.get("Content-Type") || "";
122
+ if (!contentType.includes("application/json")) {
123
+ const textResponse = await response.text();
124
+ throw new Error(
125
+ `Expected JSON response, got ${contentType}: ${textResponse}`
126
+ );
127
+ }
128
+
129
+ const data = (await response.json()) as ApiResponse<T> | T;
130
+
131
+ // Handle wrapped API responses (code, status, detail format)
132
+ if (
133
+ typeof data === "object" &&
134
+ data !== null &&
135
+ "code" in data &&
136
+ "status" in data &&
137
+ "detail" in data
138
+ ) {
139
+ const apiResponse = data as ApiResponse<T>;
140
+ return apiResponse.detail as T;
141
+ }
142
+
143
+ return data as T;
144
+ } catch (error) {
145
+ if (error instanceof Error) {
146
+ throw error;
147
+ }
148
+ throw new Error(`Unknown error occurred: ${String(error)}`);
149
+ }
150
+ }
151
+
152
+ function privacyCenterUrl(baseRequest: BaseRequest, path: string): string {
153
+ return `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/dsar/privacy-center${path}`;
154
+ }
155
+
156
+ function caseUrl(baseRequest: BaseRequest, caseUuid: string, path = ''): string {
157
+ return privacyCenterUrl(baseRequest, `/cases/${caseUuid}${path}`);
158
+ }
159
+
160
+ function resolveAuthToken(authToken: string | undefined, baseRequest: BaseRequest): string {
161
+ return authToken || baseRequest.authToken;
162
+ }
163
+
164
+ return {
165
+ /**
166
+ * Get form data (privacy center)
167
+ */
168
+ async getFormData(
169
+ baseRequest: BaseRequest,
170
+ contact: string,
171
+ authToken?: string,
172
+ options?: ApiFetchOptions & { useSessionAuth?: boolean },
173
+ language?: string
174
+ ): Promise<unknown> {
175
+ const queryParams = new URLSearchParams();
176
+ if (language) queryParams.append("language", language);
177
+ const queryString = queryParams.toString();
178
+ const url = privacyCenterUrl(baseRequest, `/form/data${queryString ? `?${queryString}` : ""}`);
179
+ const { useSessionAuth = false, getAuthToken, ...fetchOptions } = options || {};
180
+
181
+ // Case management uses session-based auth (X-Auth-Token)
182
+ // Privacy center uses JWT Bearer tokens (Authorization: Bearer)
183
+ if (useSessionAuth) {
184
+ return apiFetch(url, {
185
+ ...fetchOptions,
186
+ method: "POST",
187
+ body: JSON.stringify({ contact }),
188
+ getAuthToken,
189
+ });
190
+ }
191
+
192
+ // Privacy center: use JWT Bearer token
193
+ return privacyCenterApiFetch(url, {
194
+ ...fetchOptions,
195
+ method: "POST",
196
+ body: JSON.stringify({ contact }),
197
+ authToken: resolveAuthToken(authToken, baseRequest),
198
+ });
199
+ },
200
+
201
+ /**
202
+ * Create case request (privacy center or case management)
203
+ */
204
+ async createCaseRequest(
205
+ baseRequest: BaseRequest,
206
+ payload: unknown,
207
+ authToken?: string,
208
+ options?: ApiFetchOptions & { useSessionAuth?: boolean }
209
+ ): Promise<unknown> {
210
+ const url = privacyCenterUrl(baseRequest, '/case');
211
+ const { useSessionAuth = false, getAuthToken, ...fetchOptions } = options || {};
212
+
213
+ // Case management uses session-based auth (X-Auth-Token)
214
+ // Privacy center uses JWT Bearer tokens (Authorization: Bearer)
215
+ if (useSessionAuth) {
216
+ return apiFetch(url, {
217
+ ...fetchOptions,
218
+ method: "POST",
219
+ body: JSON.stringify(payload),
220
+ getAuthToken,
221
+ });
222
+ }
223
+
224
+ // Privacy center: use JWT Bearer token
225
+ return privacyCenterApiFetch(url, {
226
+ ...fetchOptions,
227
+ method: "POST",
228
+ body: JSON.stringify(payload),
229
+ authToken: resolveAuthToken(authToken, baseRequest),
230
+ });
231
+ },
232
+
233
+ /**
234
+ * Create OTP (public)
235
+ */
236
+ async createOtp(
237
+ organisationId: string,
238
+ workspaceId: string,
239
+ email: string,
240
+ options?: Omit<ApiFetchOptions, "getAuthToken">
241
+ ): Promise<unknown> {
242
+ const url = `${baseUrl}/public/organisations/${organisationId}/workspaces/${workspaceId}/otp/create`;
243
+ return apiFetch(url, {
244
+ ...options,
245
+ method: "POST",
246
+ body: JSON.stringify({ to: email, channel: "email" }),
247
+ });
248
+ },
249
+
250
+ /**
251
+ * Verify OTP (public)
252
+ */
253
+ async verifyOtp(
254
+ organisationId: string,
255
+ workspaceId: string,
256
+ otpId: string,
257
+ otp: string,
258
+ options?: Omit<ApiFetchOptions, "getAuthToken">
259
+ ): Promise<unknown> {
260
+ const url = `${baseUrl}/public/organisations/${organisationId}/workspaces/${workspaceId}/otp/verify`;
261
+ return apiFetch(url, {
262
+ ...options,
263
+ method: "POST",
264
+ body: JSON.stringify({ uuid: otpId, otp }),
265
+ });
266
+ },
267
+
268
+ /**
269
+ * Exchange one-time token for JWT tokens (public)
270
+ */
271
+ async exchangeToken(
272
+ token: string,
273
+ ): Promise<unknown> {
274
+ const url = `${baseUrl}/public/tokens/token-exchange?token=${encodeURIComponent(token)}`;
275
+ const response = await fetch(url, {
276
+ method: "GET",
277
+ headers: { "Accept": "application/json" },
278
+ });
279
+
280
+ if (!response.ok) {
281
+ const errorData = await response.json().catch(() => null) as { detail?: string; message?: string } | null;
282
+ throw new Error(
283
+ errorData?.detail || errorData?.message || `Token exchange failed with status ${response.status}`
284
+ );
285
+ }
286
+
287
+ const data = await response.json() as { code?: number; status?: string; detail?: unknown };
288
+ if (data && typeof data === "object" && "code" in data && "status" in data && "detail" in data) {
289
+ return data.detail;
290
+ }
291
+ return data;
292
+ },
293
+
294
+ /**
295
+ * Refresh OTP token (public)
296
+ */
297
+ async refreshOtpToken(
298
+ organisationId: string,
299
+ workspaceId: string,
300
+ refreshToken: string,
301
+ options?: Omit<ApiFetchOptions, "getAuthToken">
302
+ ): Promise<unknown> {
303
+ const url = `${baseUrl}/public/organisations/${organisationId}/workspaces/${workspaceId}/otp/refresh`;
304
+ return apiFetch(url, {
305
+ ...options,
306
+ method: "POST",
307
+ body: JSON.stringify({ refresh_token: refreshToken }),
308
+ });
309
+ },
310
+
311
+ /**
312
+ * Get products
313
+ */
314
+ async getProducts(
315
+ baseRequest: BaseRequest,
316
+ skip: number = 0,
317
+ limit: number = 10,
318
+ options?: Omit<ApiFetchOptions, "getAuthToken">
319
+ ): Promise<unknown> {
320
+ const url = `${baseUrl}/organisations/${baseRequest.organisationUuid}/workspaces/${baseRequest.workspaceUuid}/notice-management/products/?skip=${skip}&limit=${limit}`;
321
+ return apiFetch<unknown>(url, {
322
+ ...options,
323
+ getAuthToken: async () => baseRequest.authToken,
324
+ });
325
+ },
326
+
327
+ /**
328
+ * Get user consents (privacy center)
329
+ */
330
+ async getUserConsents(
331
+ baseRequest: BaseRequest,
332
+ contact: string,
333
+ offset?: number,
334
+ limit?: number,
335
+ search?: string,
336
+ status?: string,
337
+ product_uuid?: string,
338
+ language?: string,
339
+ authToken?: string,
340
+ options?: Omit<ApiFetchOptions, "getAuthToken">
341
+ ): Promise<unknown> {
342
+ const queryParams = new URLSearchParams();
343
+ if (offset !== undefined) queryParams.append("offset", offset.toString());
344
+ if (limit !== undefined) queryParams.append("limit", limit.toString());
345
+ if (search) queryParams.append("search", search);
346
+ if (status) queryParams.append("status", status);
347
+ if (product_uuid) queryParams.append("product_uuid", product_uuid);
348
+ if (language) queryParams.append("language", language);
349
+ const queryString = queryParams.toString();
350
+ const url = privacyCenterUrl(baseRequest, `/user-consents${queryString ? `?${queryString}` : ""}`);
351
+ return privacyCenterApiFetch(url, {
352
+ ...options,
353
+ method: "POST",
354
+ body: JSON.stringify({ contact }),
355
+ authToken: resolveAuthToken(authToken, baseRequest),
356
+ });
357
+ },
358
+
359
+ /**
360
+ * Manage consent (privacy center) - revoke, regrant, or renew
361
+ */
362
+ async manageConsent(
363
+ baseRequest: BaseRequest,
364
+ purposeId: string,
365
+ contact: string,
366
+ status: 'revoke' | 'regrant' | 'renew',
367
+ authToken?: string,
368
+ options?: Omit<ApiFetchOptions, "getAuthToken">,
369
+ nominatorContact?: string,
370
+ dataElementUuids?: string[]
371
+ ): Promise<unknown> {
372
+ const url = privacyCenterUrl(baseRequest, `/manage-consent?status=${status}`);
373
+ const body: Record<string, unknown> = { purpose_uuid: purposeId, contact };
374
+ if (nominatorContact) {
375
+ body.nominator_contact = nominatorContact;
376
+ }
377
+ if (Array.isArray(dataElementUuids) && dataElementUuids.length > 0) {
378
+ body.data_element_uuids = dataElementUuids;
379
+ }
380
+ return privacyCenterApiFetch(url, {
381
+ ...options,
382
+ method: "POST",
383
+ body: JSON.stringify(body),
384
+ authToken: resolveAuthToken(authToken, baseRequest),
385
+ });
386
+ },
387
+
388
+ /**
389
+ * Get activities (privacy center)
390
+ */
391
+ async getActivities(
392
+ baseRequest: BaseRequest,
393
+ offset?: number,
394
+ limit?: number,
395
+ language?: string,
396
+ authToken?: string,
397
+ options?: Omit<ApiFetchOptions, "getAuthToken">
398
+ ): Promise<unknown> {
399
+ const queryParams = new URLSearchParams();
400
+ if (offset !== undefined) queryParams.append("offset", offset.toString());
401
+ if (limit !== undefined) queryParams.append("limit", limit.toString());
402
+ if (language) queryParams.append("language", language);
403
+ const queryString = queryParams.toString();
404
+ const url = privacyCenterUrl(baseRequest, `/activities${queryString ? `?${queryString}` : ""}`);
405
+ return privacyCenterApiFetch<unknown>(url, {
406
+ ...options,
407
+ authToken: resolveAuthToken(authToken, baseRequest),
408
+ });
409
+ },
410
+
411
+ /**
412
+ * Get case history (privacy center)
413
+ */
414
+ async getCaseHistory(
415
+ baseRequest: BaseRequest,
416
+ _contact: string,
417
+ offset?: number,
418
+ limit?: number,
419
+ language?: string,
420
+ authToken?: string,
421
+ options?: Omit<ApiFetchOptions, "getAuthToken">
422
+ ): Promise<unknown> {
423
+ const queryParams = new URLSearchParams();
424
+ if (offset !== undefined) queryParams.append("offset", offset.toString());
425
+ if (limit !== undefined) queryParams.append("limit", limit.toString());
426
+ if (language) queryParams.append("language", language);
427
+ const queryString = queryParams.toString();
428
+ const url = privacyCenterUrl(baseRequest, `/cases?${queryString}`);
429
+ return privacyCenterApiFetch<unknown>(url, {
430
+ ...options,
431
+ authToken: resolveAuthToken(authToken, baseRequest),
432
+ });
433
+ },
434
+
435
+ // ─── Case Communication ─────────────────────────────────────────────────
436
+
437
+ /**
438
+ * Get messages for a case (privacy center)
439
+ */
440
+ async getCaseMessages(
441
+ baseRequest: BaseRequest,
442
+ caseUuid: string,
443
+ authToken?: string
444
+ ): Promise<CaseMessage[]> {
445
+ const url = caseUrl(baseRequest, caseUuid, '/messages');
446
+ return privacyCenterApiFetch<CaseMessage[]>(url, {
447
+ authToken: resolveAuthToken(authToken, baseRequest),
448
+ });
449
+ },
450
+
451
+ /**
452
+ * Send a message on a case (privacy center)
453
+ */
454
+ async sendCaseMessage(
455
+ baseRequest: BaseRequest,
456
+ caseUuid: string,
457
+ body: string,
458
+ documentUuids: string[],
459
+ authToken?: string
460
+ ): Promise<CaseMessage> {
461
+ const url = caseUrl(baseRequest, caseUuid, '/messages');
462
+ return privacyCenterApiFetch<CaseMessage>(url, {
463
+ method: 'POST',
464
+ body: JSON.stringify({ body, document_uuids: documentUuids }),
465
+ authToken: resolveAuthToken(authToken, baseRequest),
466
+ });
467
+ },
468
+
469
+ /**
470
+ * Upload a document on a case (multipart/form-data, privacy center)
471
+ */
472
+ async uploadCaseDocument(
473
+ baseRequest: BaseRequest,
474
+ caseUuid: string,
475
+ file: File,
476
+ authToken?: string
477
+ ): Promise<UploadDocumentResponse> {
478
+ const url = caseUrl(baseRequest, caseUuid, '/upload-document');
479
+ const formData = new FormData();
480
+ formData.append('file', file, file.name); // field name must be exactly "file"
481
+ // "payload" is required by the privacy-center endpoint (omitting it causes 422)
482
+ formData.append('payload', JSON.stringify({ metadata: null }));
483
+ return privacyCenterApiFetch<UploadDocumentResponse>(url, {
484
+ method: 'POST',
485
+ body: formData,
486
+ authToken: resolveAuthToken(authToken, baseRequest),
487
+ });
488
+ },
489
+
490
+ /**
491
+ * Submit a document against a handler document request (privacy center)
492
+ */
493
+ async submitDocumentForRequest(
494
+ baseRequest: BaseRequest,
495
+ caseUuid: string,
496
+ requestEventUuid: string,
497
+ documentUuid: string,
498
+ body: string,
499
+ authToken?: string
500
+ ): Promise<CaseMessage> {
501
+ const url = caseUrl(baseRequest, caseUuid, `/document-requests/${requestEventUuid}/submit`);
502
+ return privacyCenterApiFetch<CaseMessage>(url, {
503
+ method: 'POST',
504
+ body: JSON.stringify({ document_uuid: documentUuid, body }),
505
+ authToken: resolveAuthToken(authToken, baseRequest),
506
+ });
507
+ },
508
+
509
+ /**
510
+ * Get document info/download URL for a case document (privacy center)
511
+ */
512
+ async getCaseDocument(
513
+ baseRequest: BaseRequest,
514
+ caseUuid: string,
515
+ documentUuid: string,
516
+ authToken?: string
517
+ ): Promise<MessageDocument> {
518
+ const url = caseUrl(baseRequest, caseUuid, `/documents/${documentUuid}`);
519
+ return privacyCenterApiFetch<MessageDocument>(url, {
520
+ authToken: resolveAuthToken(authToken, baseRequest),
521
+ });
522
+ },
523
+ };
524
+ }
@@ -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
+ }