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