@fluid-app/portal-sdk 0.1.11

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 (35) hide show
  1. package/README.md +415 -0
  2. package/dist/ContactsScreen-CB6l0Lf1.mjs +24 -0
  3. package/dist/ContactsScreen-CB6l0Lf1.mjs.map +1 -0
  4. package/dist/ContactsScreen-Cqyrl4AQ.cjs +41 -0
  5. package/dist/ContactsScreen-Cqyrl4AQ.cjs.map +1 -0
  6. package/dist/CoreScreenPlaceholder-CA2-2V5o.cjs +38 -0
  7. package/dist/CoreScreenPlaceholder-CA2-2V5o.cjs.map +1 -0
  8. package/dist/CoreScreenPlaceholder-D93ZYKt2.mjs +32 -0
  9. package/dist/CoreScreenPlaceholder-D93ZYKt2.mjs.map +1 -0
  10. package/dist/CustomersScreen-BEar6Leg.mjs +24 -0
  11. package/dist/CustomersScreen-BEar6Leg.mjs.map +1 -0
  12. package/dist/CustomersScreen-gFgOzBox.cjs +41 -0
  13. package/dist/CustomersScreen-gFgOzBox.cjs.map +1 -0
  14. package/dist/MessagingScreen-BklF48Fn.mjs +1281 -0
  15. package/dist/MessagingScreen-BklF48Fn.mjs.map +1 -0
  16. package/dist/MessagingScreen-CHb-scHD.cjs +1454 -0
  17. package/dist/MessagingScreen-CHb-scHD.cjs.map +1 -0
  18. package/dist/OrdersScreen-CPGDYvEd.cjs +41 -0
  19. package/dist/OrdersScreen-CPGDYvEd.cjs.map +1 -0
  20. package/dist/OrdersScreen-DB1v051q.mjs +24 -0
  21. package/dist/OrdersScreen-DB1v051q.mjs.map +1 -0
  22. package/dist/ProductsScreen-B1tlIth6.cjs +41 -0
  23. package/dist/ProductsScreen-B1tlIth6.cjs.map +1 -0
  24. package/dist/ProductsScreen-nVDsY6kf.mjs +24 -0
  25. package/dist/ProductsScreen-nVDsY6kf.mjs.map +1 -0
  26. package/dist/chunk-D1SwGrFN.mjs +27 -0
  27. package/dist/index.cjs +3610 -0
  28. package/dist/index.cjs.map +1 -0
  29. package/dist/index.d.cts +2500 -0
  30. package/dist/index.d.cts.map +1 -0
  31. package/dist/index.d.mts +2498 -0
  32. package/dist/index.d.mts.map +1 -0
  33. package/dist/index.mjs +2921 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/package.json +76 -0
@@ -0,0 +1,1454 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __exportAll = (all, no_symbols) => {
9
+ let target = {};
10
+ for (var name in all) __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true
13
+ });
14
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
15
+ return target;
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
19
+ key = keys[i];
20
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
21
+ get: ((k) => from[k]).bind(null, key),
22
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
23
+ });
24
+ }
25
+ return to;
26
+ };
27
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
29
+ value: mod,
30
+ enumerable: true
31
+ }) : target, mod));
32
+ //#endregion
33
+ let react = require("react");
34
+ let _tanstack_react_query = require("@tanstack/react-query");
35
+ let _fluid_app_portal_core_theme = require("@fluid-app/portal-core/theme");
36
+ let react_jsx_runtime = require("react/jsx-runtime");
37
+ let _fluid_app_portal_core_data_sources_registry_context = require("@fluid-app/portal-core/data-sources/registry-context");
38
+ let _fluid_app_portal_widgets_contexts = require("@fluid-app/portal-widgets/contexts");
39
+ let _fluid_app_portal_core_widget_utils = require("@fluid-app/portal-core/widget-utils");
40
+ let _fluid_app_portal_widgets_widgets = require("@fluid-app/portal-widgets/widgets");
41
+ let _fluid_app_messaging_core = require("@fluid-app/messaging-core");
42
+ let _fluid_app_messaging_ui_app = require("@fluid-app/messaging-ui/app");
43
+ let _fluid_app_messaging_ui = require("@fluid-app/messaging-ui");
44
+ let _fluid_app_messaging_api_client = require("@fluid-app/messaging-api-client");
45
+ let _fluid_app_auth = require("@fluid-app/auth");
46
+ //#region ../../platform/api-client-core/src/fetch-client.ts
47
+ /**
48
+ * API Error class compatible with fluid-admin's ApiError
49
+ */
50
+ var ApiError$1 = class ApiError$1 extends Error {
51
+ status;
52
+ data;
53
+ constructor(message, status, data) {
54
+ super(message);
55
+ this.name = "ApiError";
56
+ this.status = status;
57
+ this.data = data;
58
+ if ("captureStackTrace" in Error) Error.captureStackTrace(this, ApiError$1);
59
+ }
60
+ toJSON() {
61
+ return {
62
+ name: this.name,
63
+ message: this.message,
64
+ status: this.status,
65
+ data: this.data
66
+ };
67
+ }
68
+ };
69
+ /**
70
+ * Creates a configured fetch client instance
71
+ */
72
+ function createFetchClient(config) {
73
+ const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {} } = config;
74
+ /**
75
+ * Build headers for a request
76
+ */
77
+ async function buildHeaders(customHeaders) {
78
+ const headers = {
79
+ Accept: "application/json",
80
+ "Content-Type": "application/json",
81
+ ...defaultHeaders,
82
+ ...customHeaders
83
+ };
84
+ if (getAuthToken) {
85
+ const token = await getAuthToken();
86
+ if (token) headers.Authorization = `Bearer ${token}`;
87
+ }
88
+ return headers;
89
+ }
90
+ /**
91
+ * Join baseUrl + endpoint via string concatenation (matches fetchApi).
92
+ * Using `new URL(endpoint, baseUrl)` would strip any path prefix from
93
+ * baseUrl (e.g. "/api") when the endpoint starts with "/".
94
+ */
95
+ function joinUrl(endpoint) {
96
+ return `${baseUrl}${endpoint}`;
97
+ }
98
+ /**
99
+ * Build URL with query parameters for GET requests
100
+ * Compatible with fluid-admin's query param handling
101
+ */
102
+ function buildUrl(endpoint, params) {
103
+ const fullUrl = joinUrl(endpoint);
104
+ if (!params || Object.keys(params).length === 0) return fullUrl;
105
+ const queryString = new URLSearchParams();
106
+ Object.entries(params).forEach(([key, value]) => {
107
+ if (value === void 0 || value === null) return;
108
+ if (Array.isArray(value)) value.forEach((item) => queryString.append(`${key}[]`, String(item)));
109
+ else if (typeof value === "object") Object.entries(value).forEach(([subKey, subValue]) => {
110
+ if (subValue === void 0 || subValue === null) return;
111
+ if (Array.isArray(subValue)) subValue.forEach((item) => queryString.append(`${key}[${subKey}][]`, String(item)));
112
+ else queryString.append(`${key}[${subKey}]`, String(subValue));
113
+ });
114
+ else queryString.append(key, String(value));
115
+ });
116
+ const qs = queryString.toString();
117
+ return qs ? `${fullUrl}?${qs}` : fullUrl;
118
+ }
119
+ /**
120
+ * Shared response handler for both JSON and FormData requests.
121
+ * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.
122
+ */
123
+ async function handleResponse(response, method, _url) {
124
+ if (response.status === 401 && onAuthError) onAuthError();
125
+ if (!response.ok) try {
126
+ if (response.headers.get("content-type")?.includes("application/json")) {
127
+ const data = await response.json();
128
+ throw new ApiError$1(data.message || data.error_message || `${method} request failed`, response.status, data.errors || data);
129
+ } else throw new ApiError$1(`${method} request failed with status ${response.status}`, response.status, null);
130
+ } catch (error) {
131
+ if (error instanceof ApiError$1) throw error;
132
+ throw new ApiError$1(`${method} request failed with status ${response.status}`, response.status, null);
133
+ }
134
+ if (response.status === 204 || response.headers.get("content-length") === "0") return null;
135
+ if (response.headers.get("content-type")?.includes("application/json")) try {
136
+ return await response.json();
137
+ } catch {
138
+ try {
139
+ return await response.text();
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+ return null;
145
+ }
146
+ /**
147
+ * Main request function
148
+ */
149
+ async function request(endpoint, options = {}) {
150
+ const { method = "GET", headers: customHeaders, params, body, signal } = options;
151
+ const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);
152
+ const headers = await buildHeaders(customHeaders);
153
+ let response;
154
+ try {
155
+ const fetchOptions = {
156
+ method,
157
+ headers
158
+ };
159
+ const serializedBody = body && method !== "GET" ? JSON.stringify(body) : null;
160
+ if (serializedBody) fetchOptions.body = serializedBody;
161
+ if (signal) fetchOptions.signal = signal;
162
+ response = await fetch(url, fetchOptions);
163
+ } catch (networkError) {
164
+ throw new ApiError$1(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
165
+ }
166
+ return handleResponse(response, method, url);
167
+ }
168
+ /**
169
+ * Request with FormData (for file uploads)
170
+ */
171
+ async function requestWithFormData(endpoint, formData, options = {}) {
172
+ const { method = "POST", headers: customHeaders, signal } = options;
173
+ const url = joinUrl(endpoint);
174
+ const headers = await buildHeaders(customHeaders);
175
+ delete headers["Content-Type"];
176
+ let response;
177
+ try {
178
+ const fetchOptions = {
179
+ method,
180
+ headers,
181
+ body: formData
182
+ };
183
+ if (signal) fetchOptions.signal = signal;
184
+ response = await fetch(url, fetchOptions);
185
+ } catch (networkError) {
186
+ throw new ApiError$1(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
187
+ }
188
+ return handleResponse(response, method, url);
189
+ }
190
+ return {
191
+ request,
192
+ requestWithFormData,
193
+ get: (endpoint, params, options) => request(endpoint, {
194
+ ...options,
195
+ method: "GET",
196
+ ...params && { params }
197
+ }),
198
+ post: (endpoint, body, options) => request(endpoint, {
199
+ ...options,
200
+ method: "POST",
201
+ body
202
+ }),
203
+ put: (endpoint, body, options) => request(endpoint, {
204
+ ...options,
205
+ method: "PUT",
206
+ body
207
+ }),
208
+ patch: (endpoint, body, options) => request(endpoint, {
209
+ ...options,
210
+ method: "PATCH",
211
+ body
212
+ }),
213
+ delete: (endpoint, options) => request(endpoint, {
214
+ ...options,
215
+ method: "DELETE"
216
+ })
217
+ };
218
+ }
219
+ //#endregion
220
+ //#region ../../fluidos/api-client/src/namespaces/fluid_os.ts
221
+ /**
222
+ * Get active Fluid OS definition
223
+ * Retrieve the active Fluid OS definition manifest for a specific platform
224
+ *
225
+ * @param client - Fetch client instance
226
+ * @param params - params
227
+ */
228
+ async function getFluidOSManifest(client, params) {
229
+ return client.get(`/api/fluid_os/definitions/active`, params);
230
+ }
231
+ //#endregion
232
+ //#region src/client/types.ts
233
+ /**
234
+ * HTTP methods supported by the API client.
235
+ * Use `as const` for literal type inference and type safety.
236
+ */
237
+ const HTTP_METHODS = {
238
+ GET: "GET",
239
+ POST: "POST",
240
+ PUT: "PUT",
241
+ PATCH: "PATCH",
242
+ DELETE: "DELETE"
243
+ };
244
+ //#endregion
245
+ //#region src/transforms/screen-transforms.ts
246
+ /**
247
+ * Normalize component_tree to always be an array.
248
+ * The API stores component_tree as a hash (object), but the frontend expects an array.
249
+ */
250
+ function normalizeComponentTree(componentTree) {
251
+ if (!componentTree) return [];
252
+ if (Array.isArray(componentTree)) return componentTree;
253
+ if (typeof componentTree === "object") return [componentTree];
254
+ return [];
255
+ }
256
+ /**
257
+ * Convert a raw FluidOS screen to ScreenDefinition.
258
+ * Normalizes component_tree and converts string IDs to numbers.
259
+ */
260
+ function toScreenDefinition(screen) {
261
+ return {
262
+ id: Number(screen.id),
263
+ slug: screen.slug ?? "",
264
+ name: screen.name ?? "",
265
+ component_tree: normalizeComponentTree(screen.component_tree)
266
+ };
267
+ }
268
+ //#endregion
269
+ //#region src/transforms/navigation-transforms.ts
270
+ /**
271
+ * Convert a raw FluidOS navigation item to NavigationItem.
272
+ * Recursively transforms children and sorts by position.
273
+ */
274
+ function toNavigationItem(item) {
275
+ const children = (item.children ?? []).map(toNavigationItem).sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
276
+ return {
277
+ id: Number(item.id),
278
+ label: item.label ?? "Untitled",
279
+ ...item.slug != null ? { slug: String(item.slug) } : {},
280
+ ...item.icon != null ? { icon: String(item.icon) } : {},
281
+ ...item.screen_id != null ? { screen_id: Number(item.screen_id) } : {},
282
+ ...item.parent_id != null ? { parent_id: Number(item.parent_id) } : {},
283
+ ...item.source != null ? { source: item.source } : {},
284
+ position: item.position ?? 0,
285
+ children
286
+ };
287
+ }
288
+ //#endregion
289
+ //#region src/transforms/index.ts
290
+ /**
291
+ * Transform a raw FluidOS manifest API response into RepAppData.
292
+ *
293
+ * This is the top-level transform used by `FluidClient.app.get()`.
294
+ * It handles:
295
+ * - Theme transformation (legacy and new formats)
296
+ * - Screen normalization (component_tree array wrapping)
297
+ * - Navigation item transformation (recursive with position sorting)
298
+ */
299
+ function transformManifestToRepAppData(response) {
300
+ const manifest = response.manifest;
301
+ const rawProfile = manifest.profile;
302
+ const rawThemes = Array.isArray(rawProfile?.themes) ? rawProfile.themes : [];
303
+ const screens = (manifest.screens ?? []).map((screen) => toScreenDefinition(screen));
304
+ const navigationItems = (rawProfile?.navigation?.navigation_items ?? []).map(toNavigationItem);
305
+ const nav = rawProfile?.navigation;
306
+ const mobileNav = rawProfile?.mobile_navigation;
307
+ const mobileNavigationItems = (mobileNav?.navigation_items ?? []).map(toNavigationItem);
308
+ const activeThemeId = (0, _fluid_app_portal_core_theme.getActiveThemeId)(rawThemes);
309
+ return {
310
+ definition_id: manifest.definition_id,
311
+ published_version: manifest.published_version ?? 0,
312
+ screens,
313
+ profile: {
314
+ name: rawProfile?.name ?? "Default",
315
+ definition_id: rawProfile?.definition_id ?? manifest.definition_id,
316
+ themes: (0, _fluid_app_portal_core_theme.transformThemes)(rawThemes),
317
+ ...activeThemeId !== void 0 ? { activeThemeId } : {},
318
+ navigation: {
319
+ definition_id: nav?.definition_id ?? manifest.definition_id,
320
+ id: nav?.id ?? 0,
321
+ name: nav?.name ?? "Main Navigation",
322
+ navigation_items: navigationItems,
323
+ screens
324
+ },
325
+ ...mobileNav ? { mobile_navigation: {
326
+ definition_id: mobileNav.definition_id ?? manifest.definition_id,
327
+ id: mobileNav.id ?? 0,
328
+ name: mobileNav.name ?? "Mobile Navigation",
329
+ navigation_items: mobileNavigationItems,
330
+ screens
331
+ } } : {}
332
+ }
333
+ };
334
+ }
335
+ //#endregion
336
+ //#region src/client/fluid-client.ts
337
+ /**
338
+ * API Error class for structured error handling
339
+ */
340
+ var ApiError = class ApiError extends Error {
341
+ status;
342
+ data;
343
+ constructor(message, status, data) {
344
+ super(message);
345
+ this.name = "ApiError";
346
+ this.status = status;
347
+ this.data = data;
348
+ const errorWithCapture = Error;
349
+ if (errorWithCapture.captureStackTrace) errorWithCapture.captureStackTrace(this, ApiError);
350
+ }
351
+ toJSON() {
352
+ return {
353
+ name: this.name,
354
+ message: this.message,
355
+ status: this.status,
356
+ data: this.data
357
+ };
358
+ }
359
+ };
360
+ /**
361
+ * Type guard for ApiError
362
+ */
363
+ function isApiError(error) {
364
+ return error instanceof ApiError;
365
+ }
366
+ /**
367
+ * Type guard to check if a value is a non-null string
368
+ */
369
+ function isString(value) {
370
+ return typeof value === "string";
371
+ }
372
+ /**
373
+ * Extract error message from API response data using `in` operator narrowing.
374
+ * Checks common error message field names in order of precedence.
375
+ */
376
+ function extractErrorMessage(data, fallback) {
377
+ if ("message" in data && isString(data.message)) return data.message;
378
+ if ("error_message" in data && isString(data.error_message)) return data.error_message;
379
+ if ("error" in data && isString(data.error)) return data.error;
380
+ return fallback;
381
+ }
382
+ /**
383
+ * Creates a configured Fluid API client instance
384
+ */
385
+ function createFluidClient(config) {
386
+ const { baseUrl, getAuthToken, onAuthError, defaultHeaders = {} } = config;
387
+ const effectiveOnAuthError = onAuthError ?? (0, _fluid_app_auth.createDefaultAuthRedirect)();
388
+ const fetchClient = createFetchClient({
389
+ baseUrl,
390
+ ...getAuthToken ? { getAuthToken } : {},
391
+ onAuthError: effectiveOnAuthError,
392
+ defaultHeaders
393
+ });
394
+ /**
395
+ * Build headers for a request
396
+ */
397
+ async function buildHeaders(customHeaders) {
398
+ const headers = {
399
+ "Content-Type": "application/json",
400
+ ...defaultHeaders,
401
+ ...customHeaders
402
+ };
403
+ if (getAuthToken) {
404
+ const token = await getAuthToken();
405
+ if (token) headers.Authorization = `Bearer ${token}`;
406
+ }
407
+ return headers;
408
+ }
409
+ /**
410
+ * Build URL with query parameters (Rails-compatible)
411
+ */
412
+ function buildUrl(endpoint, params) {
413
+ const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
414
+ const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
415
+ const url = new URL(normalizedBase + normalizedEndpoint);
416
+ if (params) for (const [key, value] of Object.entries(params)) {
417
+ if (value === void 0 || value === null) continue;
418
+ if (Array.isArray(value)) for (const item of value) url.searchParams.append(`${key}[]`, String(item));
419
+ else if (typeof value === "object") for (const [subKey, subValue] of Object.entries(value)) {
420
+ if (subValue === void 0 || subValue === null) continue;
421
+ if (Array.isArray(subValue)) for (const item of subValue) url.searchParams.append(`${key}[${subKey}][]`, String(item));
422
+ else url.searchParams.append(`${key}[${subKey}]`, String(subValue));
423
+ }
424
+ else url.searchParams.append(key, String(value));
425
+ }
426
+ return url.toString();
427
+ }
428
+ /**
429
+ * Default request options for type-safe defaults.
430
+ * Uses `satisfies` to validate against RequestOptions while preserving literal types.
431
+ */
432
+ const defaultRequestOptions = { method: HTTP_METHODS.GET };
433
+ /**
434
+ * Main request function
435
+ */
436
+ async function request(endpoint, options = {}) {
437
+ const { method = defaultRequestOptions.method, headers: customHeaders, params, body, signal } = options;
438
+ const url = buildUrl(endpoint, method === HTTP_METHODS.GET ? params : void 0);
439
+ const headers = await buildHeaders(customHeaders);
440
+ let response;
441
+ try {
442
+ const fetchOptions = {
443
+ method,
444
+ headers
445
+ };
446
+ if (signal !== void 0) fetchOptions.signal = signal;
447
+ if (body && method !== HTTP_METHODS.GET) fetchOptions.body = JSON.stringify(body);
448
+ response = await fetch(url, fetchOptions);
449
+ } catch (networkError) {
450
+ throw new ApiError(`Network error: ${networkError instanceof Error ? networkError.message : "Unknown network error"}`, 0, null);
451
+ }
452
+ if (response.status === 401) {
453
+ effectiveOnAuthError();
454
+ throw new ApiError("Authentication required", 401, null);
455
+ }
456
+ if (!response.ok) try {
457
+ if (response.headers.get("content-type")?.includes("application/json")) {
458
+ const data = await response.json();
459
+ throw new ApiError(extractErrorMessage(data, `${method} request failed`), response.status, "errors" in data ? data.errors : data);
460
+ } else throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
461
+ } catch (error) {
462
+ if (isApiError(error)) throw error;
463
+ throw new ApiError(`${method} request failed with status ${response.status}`, response.status, null);
464
+ }
465
+ if (response.status === 204 || response.headers.get("content-length") === "0") return null;
466
+ try {
467
+ const data = await response.json();
468
+ if (data === null || data === void 0) throw new ApiError("Unexpected null/undefined in JSON response", response.status, null);
469
+ return data;
470
+ } catch (parseError) {
471
+ if (isApiError(parseError)) throw parseError;
472
+ throw new ApiError("Failed to parse response as JSON", response.status, null);
473
+ }
474
+ }
475
+ /**
476
+ * Request function for endpoints that may return null (204 No Content).
477
+ * Properly types the return as T | null.
478
+ */
479
+ async function requestNullable(endpoint, options = {}) {
480
+ return request(endpoint, options);
481
+ }
482
+ /**
483
+ * Safe request wrapper that returns a discriminated union instead of throwing.
484
+ * Use `isApiSuccess` or `isApiFailure` to narrow the result.
485
+ */
486
+ async function safeRequest(endpoint, options = {}) {
487
+ try {
488
+ return {
489
+ success: true,
490
+ data: await request(endpoint, options)
491
+ };
492
+ } catch (error) {
493
+ if (isApiError(error)) return {
494
+ success: false,
495
+ error
496
+ };
497
+ return {
498
+ success: false,
499
+ error: new ApiError(error instanceof Error ? error.message : "Unknown error", 0, null)
500
+ };
501
+ }
502
+ }
503
+ /**
504
+ * Helper to safely convert typed params to Record<string, unknown>.
505
+ * Type assertion required: TypeScript's structural typing allows any object
506
+ * to be treated as Record<string, unknown> when we only need to iterate
507
+ * over its entries. This is safe because buildUrl only reads properties.
508
+ */
509
+ function toParams(params) {
510
+ return params;
511
+ }
512
+ const get = (endpoint, params, options) => {
513
+ const baseOptions = {
514
+ ...options,
515
+ method: HTTP_METHODS.GET
516
+ };
517
+ const convertedParams = toParams(params);
518
+ return request(endpoint, convertedParams !== void 0 ? {
519
+ ...baseOptions,
520
+ params: convertedParams
521
+ } : baseOptions);
522
+ };
523
+ const post = (endpoint, body, options) => request(endpoint, {
524
+ ...options,
525
+ method: HTTP_METHODS.POST,
526
+ body
527
+ });
528
+ const put = (endpoint, body, options) => request(endpoint, {
529
+ ...options,
530
+ method: HTTP_METHODS.PUT,
531
+ body
532
+ });
533
+ const patch = (endpoint, body, options) => request(endpoint, {
534
+ ...options,
535
+ method: HTTP_METHODS.PATCH,
536
+ body
537
+ });
538
+ const del = (endpoint, options) => request(endpoint, {
539
+ ...options,
540
+ method: HTTP_METHODS.DELETE
541
+ });
542
+ return {
543
+ fetchClient,
544
+ request,
545
+ requestNullable,
546
+ safeRequest,
547
+ get,
548
+ post,
549
+ put,
550
+ patch,
551
+ delete: del,
552
+ products: {
553
+ list: (params) => get("/company/v1/products", params),
554
+ get: (id) => get(`/company/v1/products/${id}`),
555
+ search: (query, params) => get("/company/v1/products", {
556
+ search_query: query,
557
+ ...params
558
+ })
559
+ },
560
+ orders: {
561
+ list: (params) => get("/orders", params),
562
+ get: (id) => get(`/orders/${id}`),
563
+ create: (data) => post("/orders", data)
564
+ },
565
+ users: { me: () => get("/api/me") },
566
+ reps: {
567
+ current: () => get("/reps/me"),
568
+ updateProfile: (data) => patch("/reps/me", data)
569
+ },
570
+ profile: { get: () => get("/rep_app/manifest") },
571
+ app: {
572
+ getRaw: async () => {
573
+ return await getFluidOSManifest(fetchClient, { platform: "browser" });
574
+ },
575
+ get: async () => {
576
+ return transformManifestToRepAppData(await getFluidOSManifest(fetchClient, { platform: "browser" }));
577
+ }
578
+ },
579
+ permissions: { get: () => get("/company/roles/my_permissions") },
580
+ analytics: {
581
+ dashboard: () => get("/analytics/dashboard"),
582
+ sales: (params) => get("/analytics/sales", params)
583
+ }
584
+ };
585
+ }
586
+ //#endregion
587
+ //#region src/themes/index.ts
588
+ var themes_exports = /* @__PURE__ */ __exportAll({});
589
+ __reExport(themes_exports, require("@fluid-app/portal-core/theme"));
590
+ //#endregion
591
+ //#region src/providers/FluidThemeProvider.tsx
592
+ /**
593
+ * Theme Provider for Fluid SDK
594
+ * Handles CSS variable injection using portal-core's theme engine.
595
+ * Accepts ThemeDefinition objects and resolves them into CSS via generateThemeCSS.
596
+ */
597
+ const ThemeContext = (0, react.createContext)(null);
598
+ /**
599
+ * Apply a theme to the DOM using the shared portal-core pipeline.
600
+ * Also sets data attributes on the container for CSS selector targeting.
601
+ */
602
+ function applyThemeToDOM(theme, mode, container) {
603
+ const target = container ?? document.documentElement;
604
+ (0, themes_exports.applyTheme)((0, themes_exports.resolveTheme)(theme));
605
+ target.dataset.theme = theme.id;
606
+ if (mode) target.dataset.themeMode = mode;
607
+ else delete target.dataset.themeMode;
608
+ }
609
+ function FluidThemeProvider({ children, initialTheme, container }) {
610
+ const [currentTheme, setCurrentTheme] = (0, react.useState)(initialTheme ?? null);
611
+ const [mode, setMode] = (0, react.useState)(void 0);
612
+ (0, react.useEffect)(() => {
613
+ if (currentTheme) applyThemeToDOM(currentTheme, mode, container ?? null);
614
+ return () => {
615
+ (0, themes_exports.removeAllThemes)();
616
+ };
617
+ }, [
618
+ currentTheme,
619
+ mode,
620
+ container
621
+ ]);
622
+ const setTheme = (0, react.useCallback)((theme) => {
623
+ setCurrentTheme(theme);
624
+ }, []);
625
+ const setThemeMode = (0, react.useCallback)((newMode) => {
626
+ setMode(newMode);
627
+ }, []);
628
+ const value = (0, react.useMemo)(() => ({
629
+ currentTheme,
630
+ setTheme,
631
+ setThemeMode,
632
+ mode
633
+ }), [
634
+ currentTheme,
635
+ setTheme,
636
+ setThemeMode,
637
+ mode
638
+ ]);
639
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeContext.Provider, {
640
+ value,
641
+ children
642
+ });
643
+ }
644
+ /**
645
+ * Hook to access theme context
646
+ * Must be used within a FluidThemeProvider
647
+ */
648
+ function useThemeContext() {
649
+ const context = (0, react.useContext)(ThemeContext);
650
+ if (!context) throw new Error("useThemeContext must be used within a FluidThemeProvider");
651
+ return context;
652
+ }
653
+ //#endregion
654
+ //#region src/providers/FluidAuthProvider.tsx
655
+ /**
656
+ * FluidAuthProvider - Authentication Provider for Fluid Portal SDK
657
+ *
658
+ * Handles JWT token extraction from URL, validation, storage, and
659
+ * provides authentication context to child components.
660
+ */
661
+ /**
662
+ * Auth context - null when outside provider
663
+ */
664
+ const FluidAuthContext = (0, react.createContext)(null);
665
+ function authReducer(state, action) {
666
+ switch (action.type) {
667
+ case "SET_AUTH": return {
668
+ isLoading: false,
669
+ token: action.token,
670
+ user: action.user,
671
+ error: action.error
672
+ };
673
+ case "CLEAR_AUTH": return {
674
+ ...state,
675
+ token: null,
676
+ user: null,
677
+ error: null
678
+ };
679
+ case "DONE_LOADING": return {
680
+ ...state,
681
+ isLoading: false
682
+ };
683
+ }
684
+ }
685
+ const initialAuthState = {
686
+ isLoading: true,
687
+ token: null,
688
+ user: null,
689
+ error: null
690
+ };
691
+ /**
692
+ * Authentication provider for Fluid portal applications.
693
+ *
694
+ * On mount, this provider:
695
+ * 1. Checks for a token in the URL (passed from parent app)
696
+ * 2. Cleans token from URL immediately (security)
697
+ * 3. Falls back to stored token (cookie/localStorage)
698
+ * 4. Validates the token (checks expiration)
699
+ * 5. Stores valid tokens for future use
700
+ * 6. Calls onAuthFailure if no valid token found
701
+ *
702
+ * @example
703
+ * ```tsx
704
+ * import { FluidAuthProvider } from "@fluid-app/portal-sdk";
705
+ *
706
+ * function App() {
707
+ * return (
708
+ * <FluidAuthProvider
709
+ * config={{
710
+ * onAuthFailure: () => {
711
+ * window.location.href = "/login";
712
+ * },
713
+ * }}
714
+ * >
715
+ * <YourApp />
716
+ * </FluidAuthProvider>
717
+ * );
718
+ * }
719
+ * ```
720
+ */
721
+ function FluidAuthProvider({ children, config }) {
722
+ const configRef = (0, react.useRef)(config);
723
+ configRef.current = config;
724
+ const [state, dispatch] = (0, react.useReducer)(authReducer, initialAuthState);
725
+ const { isLoading, token, user, error } = state;
726
+ (0, react.useEffect)(() => {
727
+ const initializeAuth = async () => {
728
+ const handleAuthFailure = () => {
729
+ const current = configRef.current;
730
+ (0, _fluid_app_auth.resolveAuthFailureHandler)(current?.onAuthFailure, current?.authUrl)();
731
+ };
732
+ try {
733
+ if ((0, _fluid_app_auth.isDevBypassActive)(config?.devBypass)) {
734
+ const envToken = {}.env.VITE_DEV_TOKEN;
735
+ if (envToken) {
736
+ const validation = (0, _fluid_app_auth.validateToken)(envToken, config?.gracePeriodMs);
737
+ if (validation.isValid && validation.payload) {
738
+ (0, _fluid_app_auth.storeToken)(envToken, config);
739
+ dispatch({
740
+ type: "SET_AUTH",
741
+ token: envToken,
742
+ user: validation.payload,
743
+ error: null
744
+ });
745
+ return;
746
+ }
747
+ console.warn("[FluidAuth] VITE_DEV_TOKEN is invalid or expired, falling back to mock user");
748
+ }
749
+ console.warn("[FluidAuth] Dev bypass active - using mock user. API calls will fail without a real token.");
750
+ dispatch({
751
+ type: "SET_AUTH",
752
+ token: null,
753
+ user: (0, _fluid_app_auth.createDevUser)(),
754
+ error: null
755
+ });
756
+ return;
757
+ }
758
+ const tokenKey = config?.tokenKey ?? "fluidUserToken";
759
+ let candidateToken = (0, _fluid_app_auth.extractTokenFromUrl)(tokenKey);
760
+ if (!candidateToken && tokenKey !== _fluid_app_auth.AUTH_REDIRECT_TOKEN_KEY) candidateToken = (0, _fluid_app_auth.extractTokenFromUrl)(_fluid_app_auth.AUTH_REDIRECT_TOKEN_KEY);
761
+ (0, _fluid_app_auth.cleanTokenFromUrl)(tokenKey);
762
+ (0, _fluid_app_auth.cleanTokenFromUrl)(_fluid_app_auth.AUTH_REDIRECT_TOKEN_KEY);
763
+ if (!candidateToken) candidateToken = (0, _fluid_app_auth.getStoredToken)(config);
764
+ if (candidateToken) {
765
+ let payload = null;
766
+ if (config?.jwksUrl) {
767
+ payload = await (0, _fluid_app_auth.verifyToken)(candidateToken, config.jwksUrl);
768
+ if (!payload) {
769
+ (0, _fluid_app_auth.clearTokens)(config);
770
+ dispatch({
771
+ type: "SET_AUTH",
772
+ token: null,
773
+ user: null,
774
+ error: /* @__PURE__ */ new Error("JWT signature verification failed")
775
+ });
776
+ handleAuthFailure();
777
+ return;
778
+ }
779
+ if ((0, _fluid_app_auth.isTokenExpired)(candidateToken, config?.gracePeriodMs)) {
780
+ (0, _fluid_app_auth.clearTokens)(config);
781
+ dispatch({
782
+ type: "SET_AUTH",
783
+ token: null,
784
+ user: null,
785
+ error: /* @__PURE__ */ new Error("Token has expired")
786
+ });
787
+ handleAuthFailure();
788
+ return;
789
+ }
790
+ } else {
791
+ const validation = (0, _fluid_app_auth.validateToken)(candidateToken, config?.gracePeriodMs);
792
+ if (validation.isValid && validation.payload) payload = validation.payload;
793
+ else {
794
+ (0, _fluid_app_auth.clearTokens)(config);
795
+ dispatch({
796
+ type: "SET_AUTH",
797
+ token: null,
798
+ user: null,
799
+ error: new Error(validation.error ?? "Invalid token")
800
+ });
801
+ handleAuthFailure();
802
+ return;
803
+ }
804
+ }
805
+ (0, _fluid_app_auth.storeToken)(candidateToken, config);
806
+ dispatch({
807
+ type: "SET_AUTH",
808
+ token: candidateToken,
809
+ user: payload,
810
+ error: null
811
+ });
812
+ } else {
813
+ dispatch({
814
+ type: "SET_AUTH",
815
+ token: null,
816
+ user: null,
817
+ error: /* @__PURE__ */ new Error("No authentication token found")
818
+ });
819
+ handleAuthFailure();
820
+ }
821
+ } catch (err) {
822
+ dispatch({
823
+ type: "SET_AUTH",
824
+ token: null,
825
+ user: null,
826
+ error: err instanceof Error ? err : /* @__PURE__ */ new Error("Authentication error")
827
+ });
828
+ handleAuthFailure();
829
+ } finally {
830
+ dispatch({ type: "DONE_LOADING" });
831
+ }
832
+ };
833
+ initializeAuth();
834
+ }, []);
835
+ const clearAuth = (0, react.useCallback)(() => {
836
+ (0, _fluid_app_auth.clearTokens)(configRef.current);
837
+ dispatch({ type: "CLEAR_AUTH" });
838
+ }, []);
839
+ const contextValue = (0, react.useMemo)(() => ({
840
+ isAuthenticated: user !== null,
841
+ isLoading,
842
+ user,
843
+ token,
844
+ clearAuth,
845
+ error
846
+ }), [
847
+ token,
848
+ isLoading,
849
+ user,
850
+ clearAuth,
851
+ error
852
+ ]);
853
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FluidAuthContext.Provider, {
854
+ value: contextValue,
855
+ children
856
+ });
857
+ }
858
+ /**
859
+ * Hook to access the auth context directly.
860
+ * Prefer using `useFluidAuth` for most use cases.
861
+ *
862
+ * @throws Error if used outside FluidAuthProvider
863
+ */
864
+ function useFluidAuthContext() {
865
+ const context = (0, react.useContext)(FluidAuthContext);
866
+ if (!context) throw new Error("useFluidAuthContext must be used within a FluidAuthProvider. Wrap your app with <FluidAuthProvider> to use authentication features.");
867
+ return context;
868
+ }
869
+ /** Safe accessor — returns null when outside FluidAuthProvider (no throw) */
870
+ function useFluidAuthOptional() {
871
+ return (0, react.useContext)(FluidAuthContext);
872
+ }
873
+ //#endregion
874
+ //#region src/core/default-widget-registry.ts
875
+ const DEFAULT_SDK_WIDGET_REGISTRY = (0, _fluid_app_portal_core_widget_utils.createWidgetRegistry)({
876
+ AlertWidget: _fluid_app_portal_widgets_widgets.AlertWidget,
877
+ CalendarWidget: _fluid_app_portal_widgets_widgets.CalendarWidget,
878
+ CarouselWidget: _fluid_app_portal_widgets_widgets.CarouselWidget,
879
+ CatchUpWidget: _fluid_app_portal_widgets_widgets.CatchUpWidget,
880
+ ChartWidget: _fluid_app_portal_widgets_widgets.ChartWidget,
881
+ ContainerWidget: _fluid_app_portal_widgets_widgets.ContainerWidget,
882
+ EmbedWidget: _fluid_app_portal_widgets_widgets.EmbedWidget,
883
+ ImageWidget: _fluid_app_portal_widgets_widgets.ImageWidget,
884
+ LayoutWidget: _fluid_app_portal_widgets_widgets.LayoutWidget,
885
+ ListWidget: _fluid_app_portal_widgets_widgets.ListWidget,
886
+ MySiteWidget: _fluid_app_portal_widgets_widgets.MySiteWidget,
887
+ NestedWidget: _fluid_app_portal_widgets_widgets.NestedWidget,
888
+ QuickShareWidget: _fluid_app_portal_widgets_widgets.QuickShareWidget,
889
+ RecentActivityWidget: _fluid_app_portal_widgets_widgets.RecentActivityWidget,
890
+ SpacerWidget: _fluid_app_portal_widgets_widgets.SpacerWidget,
891
+ TableWidget: _fluid_app_portal_widgets_widgets.TableWidget,
892
+ TextWidget: _fluid_app_portal_widgets_widgets.TextWidget,
893
+ ToDoWidget: _fluid_app_portal_widgets_widgets.ToDoWidget,
894
+ VideoWidget: _fluid_app_portal_widgets_widgets.VideoWidget
895
+ });
896
+ //#endregion
897
+ //#region src/providers/FluidProvider.tsx
898
+ /**
899
+ * Main Fluid SDK Provider
900
+ * Wraps QueryClientProvider and FluidThemeProvider
901
+ */
902
+ const FluidContext = (0, react.createContext)(null);
903
+ /**
904
+ * Main provider for the Fluid Portal SDK
905
+ *
906
+ * @example
907
+ * ```tsx
908
+ * import { FluidProvider } from "@fluid-app/portal-sdk";
909
+ *
910
+ * function App() {
911
+ * return (
912
+ * <FluidProvider
913
+ * config={{
914
+ * baseUrl: "https://api.fluid.app/api",
915
+ * getAuthToken: () => localStorage.getItem("token"),
916
+ * }}
917
+ * >
918
+ * <YourApp />
919
+ * </FluidProvider>
920
+ * );
921
+ * }
922
+ * ```
923
+ */
924
+ function FluidProvider({ config, children, queryClient, initialTheme, themeContainer, widgetRegistry, variables }) {
925
+ const defaultQueryClient = (0, react.useMemo)(() => new _tanstack_react_query.QueryClient({ defaultOptions: { queries: {
926
+ staleTime: 1e3 * 60,
927
+ retry: 1
928
+ } } }), []);
929
+ const configRef = (0, react.useRef)(config);
930
+ configRef.current = config;
931
+ const client = (0, react.useMemo)(() => createFluidClient({
932
+ ...configRef.current,
933
+ getAuthToken: () => configRef.current.getAuthToken?.() ?? null,
934
+ onAuthError: () => configRef.current.onAuthError?.()
935
+ }), [config.baseUrl]);
936
+ const contextValue = (0, react.useMemo)(() => ({
937
+ client,
938
+ config: configRef.current
939
+ }), [client]);
940
+ const getApiHeaders = (0, react.useCallback)(() => {
941
+ const headers = { "Content-Type": "application/json" };
942
+ const getAuthToken = configRef.current.getAuthToken;
943
+ if (typeof getAuthToken === "function") {
944
+ const tokenOrPromise = getAuthToken();
945
+ if (typeof tokenOrPromise === "string") headers.Authorization = `Bearer ${tokenOrPromise}`;
946
+ }
947
+ return headers;
948
+ }, []);
949
+ const dataSourceBaseUrl = (0, react.useMemo)(() => {
950
+ const base = config.baseUrl.replace(/\/+$/, "");
951
+ return base.endsWith("/api") ? base : `${base}/api`;
952
+ }, [config.baseUrl]);
953
+ const authContext = useFluidAuthOptional();
954
+ const autoVariables = (0, react.useMemo)(() => {
955
+ if (authContext?.user?.id != null) return { rep_id: String(authContext.user.id) };
956
+ }, [authContext?.user?.id]);
957
+ const effectiveVariables = variables ?? autoVariables;
958
+ const themeProviderProps = {
959
+ ...initialTheme !== void 0 && { initialTheme },
960
+ ...themeContainer !== void 0 && { container: themeContainer }
961
+ };
962
+ const registry = widgetRegistry ?? DEFAULT_SDK_WIDGET_REGISTRY;
963
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_query.QueryClientProvider, {
964
+ client: queryClient ?? defaultQueryClient,
965
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FluidContext.Provider, {
966
+ value: contextValue,
967
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_fluid_app_portal_core_data_sources_registry_context.DataSourceRegistryProvider, {
968
+ baseUrl: dataSourceBaseUrl,
969
+ getApiHeaders,
970
+ variables: effectiveVariables,
971
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_fluid_app_portal_widgets_contexts.RegistryProvider, {
972
+ registry,
973
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FluidThemeProvider, {
974
+ ...themeProviderProps,
975
+ children
976
+ })
977
+ })
978
+ })
979
+ })
980
+ });
981
+ }
982
+ /**
983
+ * Hook to access the Fluid context
984
+ * Must be used within a FluidProvider
985
+ */
986
+ function useFluidContext() {
987
+ const context = (0, react.useContext)(FluidContext);
988
+ if (!context) throw new Error("useFluidContext must be used within a FluidProvider");
989
+ return context;
990
+ }
991
+ //#endregion
992
+ //#region src/hooks/use-fluid-api.ts
993
+ /**
994
+ * Hook to access the Fluid API client
995
+ *
996
+ * @example
997
+ * ```tsx
998
+ * function ProductList() {
999
+ * const api = useFluidApi();
1000
+ *
1001
+ * const { data: products } = useQuery({
1002
+ * queryKey: ["products"],
1003
+ * queryFn: () => api.products.list(),
1004
+ * });
1005
+ *
1006
+ * return <ul>{products?.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
1007
+ * }
1008
+ * ```
1009
+ */
1010
+ function useFluidApi() {
1011
+ const { client } = useFluidContext();
1012
+ return client;
1013
+ }
1014
+ //#endregion
1015
+ //#region src/messaging/use-messaging-auth.ts
1016
+ /**
1017
+ * Bridge hook: maps portal SDK auth context to MessagingAuthContext.
1018
+ *
1019
+ * The messaging system identifies users by `recipient_id`, which is NOT in the
1020
+ * JWT payload or the /reps/me endpoint. It IS returned by GET /api/me.
1021
+ * This hook fetches that data and maps it into the shape MessagingApp expects.
1022
+ */
1023
+ const USERS_ME_QUERY_KEY = [
1024
+ "fluid",
1025
+ "users",
1026
+ "me"
1027
+ ];
1028
+ function useMessagingAuth() {
1029
+ const auth = useFluidAuthContext();
1030
+ const api = useFluidApi();
1031
+ const { data: userMe, isLoading: isUserMeLoading, isError } = (0, _tanstack_react_query.useQuery)({
1032
+ queryKey: USERS_ME_QUERY_KEY,
1033
+ queryFn: () => api.users.me(),
1034
+ enabled: auth.isAuthenticated,
1035
+ staleTime: Infinity,
1036
+ retry: 1
1037
+ });
1038
+ if (auth.isLoading || auth.isAuthenticated && isUserMeLoading) return {
1039
+ recipientId: null,
1040
+ companyId: null,
1041
+ currentUser: null,
1042
+ isLoading: true
1043
+ };
1044
+ if (!auth.isAuthenticated || isError || !userMe) return {
1045
+ recipientId: null,
1046
+ companyId: null,
1047
+ currentUser: null,
1048
+ isLoading: false
1049
+ };
1050
+ const currentUser = userMe.recipient_id ? {
1051
+ id: userMe.id,
1052
+ recipientId: userMe.recipient_id,
1053
+ firstName: userMe.first_name ?? "",
1054
+ lastName: userMe.last_name ?? "",
1055
+ email: userMe.email,
1056
+ ...userMe.image_url != null && { imageUrl: userMe.image_url },
1057
+ ...userMe.affiliate_id != null && { affiliateId: userMe.affiliate_id }
1058
+ } : null;
1059
+ return {
1060
+ recipientId: userMe.recipient_id,
1061
+ companyId: userMe.company_id ?? null,
1062
+ currentUser,
1063
+ isLoading: false
1064
+ };
1065
+ }
1066
+ //#endregion
1067
+ //#region src/messaging/use-messaging-config.ts
1068
+ /**
1069
+ * Hook that derives MessagingApiConfig from the portal SDK's FluidProvider context.
1070
+ *
1071
+ * Maps FluidSDKConfig fields to the shape expected by MessagingApp:
1072
+ * - baseUrl -> from config.baseUrl
1073
+ * - getHeaders -> builds Authorization header from config.getAuthToken()
1074
+ * - onAuthError -> from config.onAuthError
1075
+ * - websocketUrl -> config.websocketUrl or derived from baseUrl
1076
+ * - token -> from auth context
1077
+ */
1078
+ function deriveWebsocketUrl(baseUrl) {
1079
+ return `${baseUrl.replace(/\/+$/, "").replace(/\/api$/, "")}/cable`;
1080
+ }
1081
+ function useMessagingConfig() {
1082
+ const { config } = useFluidContext();
1083
+ const auth = useFluidAuthContext();
1084
+ const getHeaders = (0, react.useCallback)(async () => {
1085
+ const headers = {
1086
+ "Content-Type": "application/json",
1087
+ ...config.defaultHeaders
1088
+ };
1089
+ if (config.getAuthToken) {
1090
+ const token = await config.getAuthToken();
1091
+ if (token) headers.Authorization = `Bearer ${token}`;
1092
+ }
1093
+ return headers;
1094
+ }, [config]);
1095
+ const apiBaseUrl = (0, react.useMemo)(() => {
1096
+ const base = config.baseUrl.replace(/\/+$/, "");
1097
+ return base.endsWith("/api") ? base : `${base}/api`;
1098
+ }, [config.baseUrl]);
1099
+ return {
1100
+ apiConfig: (0, react.useMemo)(() => ({
1101
+ baseUrl: apiBaseUrl,
1102
+ getHeaders,
1103
+ ...config.onAuthError != null && { onAuthError: config.onAuthError }
1104
+ }), [
1105
+ apiBaseUrl,
1106
+ config.onAuthError,
1107
+ getHeaders
1108
+ ]),
1109
+ websocketUrl: (0, react.useMemo)(() => config.websocketUrl ?? deriveWebsocketUrl(config.baseUrl), [config.websocketUrl, config.baseUrl]),
1110
+ token: auth.token
1111
+ };
1112
+ }
1113
+ //#endregion
1114
+ //#region src/messaging/fluid-file-uploader.ts
1115
+ const FILESTACK_UPLOAD_URL = "https://www.filestackapi.com/api/store/S3";
1116
+ function getImageDimensions(file) {
1117
+ if (!file.type.startsWith("image/")) return Promise.resolve(null);
1118
+ return new Promise((resolve) => {
1119
+ const img = new Image();
1120
+ const url = URL.createObjectURL(file);
1121
+ img.onload = () => {
1122
+ URL.revokeObjectURL(url);
1123
+ resolve({
1124
+ width: img.naturalWidth,
1125
+ height: img.naturalHeight
1126
+ });
1127
+ };
1128
+ img.onerror = () => {
1129
+ URL.revokeObjectURL(url);
1130
+ resolve(null);
1131
+ };
1132
+ img.src = url;
1133
+ });
1134
+ }
1135
+ function uploadToFilestack(file, apiKey, callbacks) {
1136
+ const xhr = new XMLHttpRequest();
1137
+ let aborted = false;
1138
+ getImageDimensions(file).then((metadata) => {
1139
+ if (aborted) return;
1140
+ const url = `${FILESTACK_UPLOAD_URL}?key=${encodeURIComponent(apiKey)}`;
1141
+ xhr.open("POST", url);
1142
+ xhr.setRequestHeader("Content-Type", file.type);
1143
+ xhr.upload.onprogress = (event) => {
1144
+ if (event.lengthComputable) {
1145
+ const progress = Math.round(event.loaded / event.total * 100);
1146
+ callbacks.onProgress(progress);
1147
+ }
1148
+ };
1149
+ xhr.onload = () => {
1150
+ if (xhr.status >= 200 && xhr.status < 300) try {
1151
+ const result = {
1152
+ url: JSON.parse(xhr.responseText).url,
1153
+ size: file.size,
1154
+ mimetype: file.type,
1155
+ kind: (0, _fluid_app_messaging_core.getFileTypeFromMimetype)(file.type),
1156
+ metadata
1157
+ };
1158
+ callbacks.onSuccess(result);
1159
+ } catch {
1160
+ callbacks.onError(/* @__PURE__ */ new Error("Failed to parse upload response"));
1161
+ }
1162
+ else callbacks.onError(/* @__PURE__ */ new Error(`Upload failed with status ${xhr.status}`));
1163
+ };
1164
+ xhr.onerror = () => {
1165
+ if (!aborted) callbacks.onError(/* @__PURE__ */ new Error("Upload failed due to a network error"));
1166
+ };
1167
+ xhr.onabort = () => {};
1168
+ xhr.send(file);
1169
+ }).catch((error) => {
1170
+ if (!aborted) callbacks.onError(error instanceof Error ? error : /* @__PURE__ */ new Error("Upload preparation failed"));
1171
+ });
1172
+ return { abort: () => {
1173
+ aborted = true;
1174
+ xhr.abort();
1175
+ } };
1176
+ }
1177
+ /**
1178
+ * Creates a FileUploader that uploads to Filestack using the REST API.
1179
+ *
1180
+ * @param apiKey - Filestack API key. If falsy, returns a noop uploader
1181
+ * that rejects uploads with a helpful error message.
1182
+ */
1183
+ function createFluidFileUploader(apiKey) {
1184
+ if (!apiKey) return { uploadFile: (_file, callbacks) => {
1185
+ callbacks.onError(/* @__PURE__ */ new Error("File uploads are not configured. Set filestackApiKey in your SDK config to enable attachments."));
1186
+ return { abort: () => {} };
1187
+ } };
1188
+ return { uploadFile: (file, callbacks) => uploadToFilestack(file, apiKey, callbacks) };
1189
+ }
1190
+ //#endregion
1191
+ //#region src/screens/MessagingScreen.tsx
1192
+ var MessagingScreen_exports = /* @__PURE__ */ __exportAll({
1193
+ MessagingScreen: () => MessagingScreen,
1194
+ messagingScreenPropertySchema: () => messagingScreenPropertySchema
1195
+ });
1196
+ function renderImage(props) {
1197
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
1198
+ src: props.src,
1199
+ alt: props.alt,
1200
+ width: props.width,
1201
+ height: props.height,
1202
+ className: props.className
1203
+ });
1204
+ }
1205
+ function defaultToast(message, type) {
1206
+ if (type === "error") console.warn("[Messaging]", message);
1207
+ }
1208
+ function MessagingScreen({ onToast, filestackApiKey, websocketUrl: websocketUrlOverride, background, textColor, accentColor, padding, borderRadius, ...divProps }) {
1209
+ const { config } = useFluidContext();
1210
+ const { apiConfig, websocketUrl, token } = useMessagingConfig();
1211
+ const messagingAuth = useMessagingAuth();
1212
+ const effectiveApiKey = filestackApiKey ?? config.filestackApiKey;
1213
+ const uploader = (0, react.useMemo)(() => createFluidFileUploader(effectiveApiKey), [effectiveApiKey]);
1214
+ const effectiveWsUrl = websocketUrlOverride ?? websocketUrl;
1215
+ const effectiveToast = onToast ?? defaultToast;
1216
+ const searchUsers = (0, react.useCallback)(async (query) => {
1217
+ return ((await (0, _fluid_app_messaging_api_client.listConnectedRecipients)(apiConfig, {
1218
+ filterrific: { search_query: query },
1219
+ per_page: 10,
1220
+ page: 1
1221
+ }))?.[1]?.items ?? []).map((user) => {
1222
+ const name = [user.first_name, user.last_name].filter(Boolean).length > 0 ? [user.first_name, user.last_name].filter(Boolean).join(" ") : `User ${user.id}`;
1223
+ return {
1224
+ id: String(user.id),
1225
+ label: name,
1226
+ imageType: "user",
1227
+ userData: {
1228
+ first_name: user.first_name,
1229
+ last_name: user.last_name,
1230
+ image_url: user.avatar_url,
1231
+ phone: user.phone || void 0,
1232
+ email: user.email || void 0
1233
+ },
1234
+ conversationName: name
1235
+ };
1236
+ });
1237
+ }, [apiConfig]);
1238
+ const searchChannels = (0, react.useCallback)(async (query) => {
1239
+ return (await (0, _fluid_app_messaging_api_client.searchConversations)(apiConfig, { filterrific: { search_query: query } }) ?? []).map((channel) => {
1240
+ const { text: nameWithoutEmoji } = (0, _fluid_app_messaging_ui.extractEmoji)(channel.name);
1241
+ return {
1242
+ id: `channel-${channel.id}`,
1243
+ label: (0, _fluid_app_messaging_ui.formatMessageChannelName)(nameWithoutEmoji),
1244
+ imageType: "channel",
1245
+ userData: {
1246
+ first_name: channel.name,
1247
+ last_name: null,
1248
+ image_url: channel.avatar_url
1249
+ },
1250
+ conversationName: channel.name
1251
+ };
1252
+ });
1253
+ }, [apiConfig]);
1254
+ if (messagingAuth.isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1255
+ ...divProps,
1256
+ className: `flex h-full items-center justify-center ${divProps.className ?? ""}`,
1257
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1258
+ className: "text-muted-foreground",
1259
+ children: "Loading messaging..."
1260
+ })
1261
+ });
1262
+ if (!messagingAuth.recipientId) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1263
+ ...divProps,
1264
+ className: `flex h-full items-center justify-center ${divProps.className ?? ""}`,
1265
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1266
+ className: "border-border max-w-sm rounded-lg border border-dashed p-8 text-center",
1267
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
1268
+ className: "text-foreground text-xl font-semibold",
1269
+ children: "Messaging"
1270
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1271
+ className: "text-muted-foreground mt-2",
1272
+ children: "Messaging is not available for your account"
1273
+ })]
1274
+ })
1275
+ });
1276
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1277
+ ...divProps,
1278
+ className: `h-full ${divProps.className ?? ""}`,
1279
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_fluid_app_messaging_ui_app.MessagingApp, {
1280
+ config: apiConfig,
1281
+ auth: messagingAuth,
1282
+ websocketUrl: effectiveWsUrl,
1283
+ token,
1284
+ renderImage,
1285
+ showAdminFeatures: false,
1286
+ onToast: effectiveToast,
1287
+ messagesViewProps: {
1288
+ uploader,
1289
+ saveDrafts: true,
1290
+ onToast: effectiveToast
1291
+ },
1292
+ newMessageViewProps: {
1293
+ searchUsers,
1294
+ searchChannels
1295
+ }
1296
+ })
1297
+ });
1298
+ }
1299
+ const messagingScreenPropertySchema = {
1300
+ widgetType: "MessagingScreen",
1301
+ displayName: "Messaging Screen",
1302
+ tabsConfig: [{
1303
+ id: "styling",
1304
+ label: "Styling"
1305
+ }],
1306
+ fields: []
1307
+ };
1308
+ //#endregion
1309
+ Object.defineProperty(exports, "ApiError", {
1310
+ enumerable: true,
1311
+ get: function() {
1312
+ return ApiError;
1313
+ }
1314
+ });
1315
+ Object.defineProperty(exports, "DEFAULT_SDK_WIDGET_REGISTRY", {
1316
+ enumerable: true,
1317
+ get: function() {
1318
+ return DEFAULT_SDK_WIDGET_REGISTRY;
1319
+ }
1320
+ });
1321
+ Object.defineProperty(exports, "FluidAuthProvider", {
1322
+ enumerable: true,
1323
+ get: function() {
1324
+ return FluidAuthProvider;
1325
+ }
1326
+ });
1327
+ Object.defineProperty(exports, "FluidProvider", {
1328
+ enumerable: true,
1329
+ get: function() {
1330
+ return FluidProvider;
1331
+ }
1332
+ });
1333
+ Object.defineProperty(exports, "FluidThemeProvider", {
1334
+ enumerable: true,
1335
+ get: function() {
1336
+ return FluidThemeProvider;
1337
+ }
1338
+ });
1339
+ Object.defineProperty(exports, "MessagingScreen", {
1340
+ enumerable: true,
1341
+ get: function() {
1342
+ return MessagingScreen;
1343
+ }
1344
+ });
1345
+ Object.defineProperty(exports, "MessagingScreen_exports", {
1346
+ enumerable: true,
1347
+ get: function() {
1348
+ return MessagingScreen_exports;
1349
+ }
1350
+ });
1351
+ Object.defineProperty(exports, "__exportAll", {
1352
+ enumerable: true,
1353
+ get: function() {
1354
+ return __exportAll;
1355
+ }
1356
+ });
1357
+ Object.defineProperty(exports, "__toESM", {
1358
+ enumerable: true,
1359
+ get: function() {
1360
+ return __toESM;
1361
+ }
1362
+ });
1363
+ Object.defineProperty(exports, "createFluidClient", {
1364
+ enumerable: true,
1365
+ get: function() {
1366
+ return createFluidClient;
1367
+ }
1368
+ });
1369
+ Object.defineProperty(exports, "createFluidFileUploader", {
1370
+ enumerable: true,
1371
+ get: function() {
1372
+ return createFluidFileUploader;
1373
+ }
1374
+ });
1375
+ Object.defineProperty(exports, "isApiError", {
1376
+ enumerable: true,
1377
+ get: function() {
1378
+ return isApiError;
1379
+ }
1380
+ });
1381
+ Object.defineProperty(exports, "messagingScreenPropertySchema", {
1382
+ enumerable: true,
1383
+ get: function() {
1384
+ return messagingScreenPropertySchema;
1385
+ }
1386
+ });
1387
+ Object.defineProperty(exports, "normalizeComponentTree", {
1388
+ enumerable: true,
1389
+ get: function() {
1390
+ return normalizeComponentTree;
1391
+ }
1392
+ });
1393
+ Object.defineProperty(exports, "themes_exports", {
1394
+ enumerable: true,
1395
+ get: function() {
1396
+ return themes_exports;
1397
+ }
1398
+ });
1399
+ Object.defineProperty(exports, "toNavigationItem", {
1400
+ enumerable: true,
1401
+ get: function() {
1402
+ return toNavigationItem;
1403
+ }
1404
+ });
1405
+ Object.defineProperty(exports, "toScreenDefinition", {
1406
+ enumerable: true,
1407
+ get: function() {
1408
+ return toScreenDefinition;
1409
+ }
1410
+ });
1411
+ Object.defineProperty(exports, "transformManifestToRepAppData", {
1412
+ enumerable: true,
1413
+ get: function() {
1414
+ return transformManifestToRepAppData;
1415
+ }
1416
+ });
1417
+ Object.defineProperty(exports, "useFluidApi", {
1418
+ enumerable: true,
1419
+ get: function() {
1420
+ return useFluidApi;
1421
+ }
1422
+ });
1423
+ Object.defineProperty(exports, "useFluidAuthContext", {
1424
+ enumerable: true,
1425
+ get: function() {
1426
+ return useFluidAuthContext;
1427
+ }
1428
+ });
1429
+ Object.defineProperty(exports, "useFluidContext", {
1430
+ enumerable: true,
1431
+ get: function() {
1432
+ return useFluidContext;
1433
+ }
1434
+ });
1435
+ Object.defineProperty(exports, "useMessagingAuth", {
1436
+ enumerable: true,
1437
+ get: function() {
1438
+ return useMessagingAuth;
1439
+ }
1440
+ });
1441
+ Object.defineProperty(exports, "useMessagingConfig", {
1442
+ enumerable: true,
1443
+ get: function() {
1444
+ return useMessagingConfig;
1445
+ }
1446
+ });
1447
+ Object.defineProperty(exports, "useThemeContext", {
1448
+ enumerable: true,
1449
+ get: function() {
1450
+ return useThemeContext;
1451
+ }
1452
+ });
1453
+
1454
+ //# sourceMappingURL=MessagingScreen-CHb-scHD.cjs.map