@fluid-app/rep-sdk 0.1.8 → 0.1.10

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 (86) hide show
  1. package/README.md +3 -1
  2. package/dist/ContactsScreen-CB6l0Lf1.mjs +24 -0
  3. package/dist/ContactsScreen-CB6l0Lf1.mjs.map +1 -0
  4. package/dist/ContactsScreen-UfrdOORn.cjs +41 -0
  5. package/dist/ContactsScreen-UfrdOORn.cjs.map +1 -0
  6. package/dist/CoreScreenPlaceholder-D93ZYKt2.mjs +32 -0
  7. package/dist/CoreScreenPlaceholder-D93ZYKt2.mjs.map +1 -0
  8. package/dist/CoreScreenPlaceholder-DBZqxDsK.cjs +38 -0
  9. package/dist/CoreScreenPlaceholder-DBZqxDsK.cjs.map +1 -0
  10. package/dist/CustomersScreen-BEar6Leg.mjs +24 -0
  11. package/dist/CustomersScreen-BEar6Leg.mjs.map +1 -0
  12. package/dist/CustomersScreen-DXXPpWZW.cjs +41 -0
  13. package/dist/CustomersScreen-DXXPpWZW.cjs.map +1 -0
  14. package/dist/MessagingScreen-CsDvKkLC.cjs +1458 -0
  15. package/dist/MessagingScreen-CsDvKkLC.cjs.map +1 -0
  16. package/dist/MessagingScreen-xO9YudMx.mjs +1285 -0
  17. package/dist/MessagingScreen-xO9YudMx.mjs.map +1 -0
  18. package/dist/OrdersScreen-DB1v051q.mjs +24 -0
  19. package/dist/OrdersScreen-DB1v051q.mjs.map +1 -0
  20. package/dist/OrdersScreen-fcxcnpNU.cjs +41 -0
  21. package/dist/OrdersScreen-fcxcnpNU.cjs.map +1 -0
  22. package/dist/ProductsScreen-BaEng3LB.cjs +41 -0
  23. package/dist/ProductsScreen-BaEng3LB.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 +3397 -2850
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +1026 -955
  30. package/dist/index.d.cts.map +1 -0
  31. package/dist/index.d.mts +2207 -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 +28 -28
  36. package/dist/ContactsScreen-33AJ5XUB.js +0 -5
  37. package/dist/ContactsScreen-33AJ5XUB.js.map +0 -1
  38. package/dist/ContactsScreen-ATASCZO2.cjs +0 -18
  39. package/dist/ContactsScreen-ATASCZO2.cjs.map +0 -1
  40. package/dist/CustomersScreen-E4HXBKV7.js +0 -5
  41. package/dist/CustomersScreen-E4HXBKV7.js.map +0 -1
  42. package/dist/CustomersScreen-XMPMKSQA.cjs +0 -18
  43. package/dist/CustomersScreen-XMPMKSQA.cjs.map +0 -1
  44. package/dist/MessagingScreen-GHBLPYZZ.cjs +0 -17
  45. package/dist/MessagingScreen-GHBLPYZZ.cjs.map +0 -1
  46. package/dist/MessagingScreen-MBCIJWVN.js +0 -4
  47. package/dist/MessagingScreen-MBCIJWVN.js.map +0 -1
  48. package/dist/OrdersScreen-IPPZLEYF.js +0 -5
  49. package/dist/OrdersScreen-IPPZLEYF.js.map +0 -1
  50. package/dist/OrdersScreen-X7FYUROL.cjs +0 -18
  51. package/dist/OrdersScreen-X7FYUROL.cjs.map +0 -1
  52. package/dist/ProductsScreen-4ZIUQNUU.cjs +0 -18
  53. package/dist/ProductsScreen-4ZIUQNUU.cjs.map +0 -1
  54. package/dist/ProductsScreen-YTSOZW7B.js +0 -5
  55. package/dist/ProductsScreen-YTSOZW7B.js.map +0 -1
  56. package/dist/chunk-424PT5DM.js +0 -21
  57. package/dist/chunk-424PT5DM.js.map +0 -1
  58. package/dist/chunk-5UBEM3AX.cjs +0 -19
  59. package/dist/chunk-5UBEM3AX.cjs.map +0 -1
  60. package/dist/chunk-7MWTW3ZU.js +0 -1538
  61. package/dist/chunk-7MWTW3ZU.js.map +0 -1
  62. package/dist/chunk-B6S6BEPL.cjs +0 -16
  63. package/dist/chunk-B6S6BEPL.cjs.map +0 -1
  64. package/dist/chunk-CMF2FYTD.js +0 -16
  65. package/dist/chunk-CMF2FYTD.js.map +0 -1
  66. package/dist/chunk-EOXYOOWS.cjs +0 -19
  67. package/dist/chunk-EOXYOOWS.cjs.map +0 -1
  68. package/dist/chunk-FG2CI6HA.cjs +0 -19
  69. package/dist/chunk-FG2CI6HA.cjs.map +0 -1
  70. package/dist/chunk-HDQ2JUQT.cjs +0 -24
  71. package/dist/chunk-HDQ2JUQT.cjs.map +0 -1
  72. package/dist/chunk-MHPK7YQ2.cjs +0 -19
  73. package/dist/chunk-MHPK7YQ2.cjs.map +0 -1
  74. package/dist/chunk-QH2W373Y.cjs +0 -1597
  75. package/dist/chunk-QH2W373Y.cjs.map +0 -1
  76. package/dist/chunk-QZMWG7EM.js +0 -16
  77. package/dist/chunk-QZMWG7EM.js.map +0 -1
  78. package/dist/chunk-RS4OSTES.js +0 -14
  79. package/dist/chunk-RS4OSTES.js.map +0 -1
  80. package/dist/chunk-WFPYEYC7.js +0 -16
  81. package/dist/chunk-WFPYEYC7.js.map +0 -1
  82. package/dist/chunk-WMBD65GH.js +0 -16
  83. package/dist/chunk-WMBD65GH.js.map +0 -1
  84. package/dist/index.d.ts +0 -2138
  85. package/dist/index.js +0 -2536
  86. package/dist/index.js.map +0 -1
package/dist/index.mjs ADDED
@@ -0,0 +1,2921 @@
1
+ import { C as toNavigationItem, D as createDefaultAuthRedirect, E as DEFAULT_AUTH_URL, S as transformThemes, T as toScreenDefinition, _ as createFluidClient, a as useMessagingConfig, b as getActiveThemeId, c as FluidProvider, d as FluidAuthProvider, f as useFluidAuthContext, g as ApiError, h as themes_exports, i as createFluidFileUploader, l as useFluidContext, m as useThemeContext, o as useMessagingAuth, p as FluidThemeProvider, r as messagingScreenPropertySchema, s as useFluidApi, t as MessagingScreen, u as DEFAULT_SDK_WIDGET_REGISTRY, v as isApiError, w as normalizeComponentTree, x as transformManifestToRepAppData, y as buildThemeDefinition } from "./MessagingScreen-xO9YudMx.mjs";
2
+ import { t as CoreScreenPlaceholder } from "./CoreScreenPlaceholder-D93ZYKt2.mjs";
3
+ import { r as contactsScreenPropertySchema, t as ContactsScreen } from "./ContactsScreen-CB6l0Lf1.mjs";
4
+ import { r as ordersScreenPropertySchema, t as OrdersScreen } from "./OrdersScreen-DB1v051q.mjs";
5
+ import { r as customersScreenPropertySchema, t as CustomersScreen } from "./CustomersScreen-BEar6Leg.mjs";
6
+ import { r as productsScreenPropertySchema, t as ProductsScreen } from "./ProductsScreen-nVDsY6kf.mjs";
7
+ import * as React$1 from "react";
8
+ import { createContext, forwardRef, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
9
+ import { useQuery } from "@tanstack/react-query";
10
+ import { AUTH_CONSTANTS, STORAGE_KEYS, URL_PARAMS, USER_TYPES, cleanTokenFromUrl, clearTokens, decodeToken, extractAllTokensFromUrl, extractCompanyTokenFromUrl, extractTokenFromUrl, getStoredToken, getTokenExpiration, getTokenTimeRemaining, hasStoredToken, hasTokenInUrl, isTokenExpired, isUserType, isValidToken, storeToken, validateToken } from "@fluid-app/auth";
11
+ import { applyTheme as applyTheme$1, removeAllThemes as removeAllThemes$1, resolveTheme as resolveTheme$1 } from "@fluid-app/rep-core/theme";
12
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
+ import { useRegistry } from "@fluid-app/rep-widgets/contexts";
14
+ import { createScreen, createWidgetFromShareable, createWidgetRegistry, groupChildrenByColumn } from "@fluid-app/rep-core/widget-utils";
15
+ import { AlertWidget, CalendarWidget, CarouselWidget, CatchUpWidget, ChartWidget, ContainerWidget, EmbedWidget, ImageWidget, LayoutWidget, ListWidget, MySiteWidget, NestedWidget, QuickShareWidget, RecentActivityWidget, SpacerWidget, TableWidget, TextWidget, ToDoWidget, VideoWidget, alertWidgetPropertySchema, calendarWidgetPropertySchema, carouselWidgetPropertySchema, catchUpWidgetPropertySchema, chartWidgetPropertySchema, containerWidgetPropertySchema, embedWidgetPropertySchema, imageWidgetPropertySchema, layoutWidgetPropertySchema, listWidgetPropertySchema, mySiteWidgetPropertySchema, nestedWidgetPropertySchema, quickShareWidgetPropertySchema, recentActivityWidgetPropertySchema, spacerWidgetPropertySchema, tableWidgetPropertySchema, textWidgetPropertySchema, toDoWidgetPropertySchema, videoWidgetPropertySchema, widgetPropertySchemas } from "@fluid-app/rep-widgets/widgets";
16
+ import { WIDGET_TYPE_NAMES, assertDefined, assertNever, isWidgetType, isWidgetTypeName, sectionLayoutConfig } from "@fluid-app/rep-core/types";
17
+ import { PROPERTY_FIELD_TYPES, gapValues, isPropertyFieldType } from "@fluid-app/rep-core/registries";
18
+ import { AppShellLayout } from "@fluid-app/rep-core/shell/AppShellLayout";
19
+ import { ThemeModeProvider, ThemeModeProvider as ThemeModeProvider$1, getThemeModeAttribute, useThemeMode, useThemeMode as useThemeMode$1 } from "@fluid-app/rep-core/shell/ThemeModeContext";
20
+ import { getSystemNavigationBySection, isSystemNavigationItem, normalizeSlug } from "@fluid-app/rep-core/navigation/system-navigation-items";
21
+ import { ScreenHeader } from "@fluid-app/rep-core/shell/ScreenHeader";
22
+ import { ScreenHeaderProvider } from "@fluid-app/rep-core/shell/ScreenHeaderContext";
23
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
24
+ import { faEllipsis } from "@fortawesome/pro-regular-svg-icons/faEllipsis";
25
+ import { faXmark } from "@fortawesome/pro-regular-svg-icons/faXmark";
26
+ import { RepIcon } from "@fluid-app/rep-core/components/RepIcon";
27
+ import { SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from "@fluid-app/rep-core/shell/sidebar";
28
+ import { faBars } from "@fortawesome/pro-regular-svg-icons/faBars";
29
+ import { faSun } from "@fortawesome/pro-regular-svg-icons/faSun";
30
+ import { faMoon } from "@fortawesome/pro-regular-svg-icons/faMoon";
31
+ import { faTableCellsLarge } from "@fortawesome/pro-regular-svg-icons/faTableCellsLarge";
32
+ import { DataAwareWidget } from "@fluid-app/rep-core/data-sources/DataAwareWidget";
33
+ //#region src/types/page-template.ts
34
+ /**
35
+ * Built-in page category IDs
36
+ */
37
+ const PAGE_CATEGORIES = {
38
+ CORE: "core",
39
+ COMMERCE: "commerce",
40
+ COMMUNICATION: "communication",
41
+ DATA: "data",
42
+ CUSTOM: "custom"
43
+ };
44
+ //#endregion
45
+ //#region src/registries/page-template-registry.ts
46
+ /**
47
+ * Registry for managing reusable page templates.
48
+ *
49
+ * The registry provides a central store for page templates that can be
50
+ * shared across multiple navigations. Core pages (like Messaging, Contacts)
51
+ * are pre-registered and cannot be removed.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // Register a custom page template
56
+ * PageTemplateRegistry.register({
57
+ * id: 'custom-dashboard',
58
+ * slug: 'dashboard',
59
+ * name: 'Custom Dashboard',
60
+ * category: 'custom',
61
+ * version: '1.0.0',
62
+ * component_tree: [{ type: 'TextWidget', props: { text: 'Hello' } }],
63
+ * });
64
+ *
65
+ * // Get a template by ID
66
+ * const template = PageTemplateRegistry.get('custom-dashboard');
67
+ *
68
+ * // List all templates in a category
69
+ * const corePages = PageTemplateRegistry.getByCategory('core');
70
+ * ```
71
+ */
72
+ var PageTemplateRegistryImpl = class {
73
+ templates = /* @__PURE__ */ new Map();
74
+ categories = [];
75
+ constructor() {
76
+ this.categories = [
77
+ {
78
+ id: PAGE_CATEGORIES.CORE,
79
+ label: "Core Features",
80
+ icon: "star"
81
+ },
82
+ {
83
+ id: PAGE_CATEGORIES.COMMERCE,
84
+ label: "Commerce",
85
+ icon: "shopping-cart"
86
+ },
87
+ {
88
+ id: PAGE_CATEGORIES.COMMUNICATION,
89
+ label: "Communication",
90
+ icon: "message-circle"
91
+ },
92
+ {
93
+ id: PAGE_CATEGORIES.DATA,
94
+ label: "Data & Analytics",
95
+ icon: "bar-chart"
96
+ },
97
+ {
98
+ id: PAGE_CATEGORIES.CUSTOM,
99
+ label: "Custom",
100
+ icon: "puzzle"
101
+ }
102
+ ];
103
+ }
104
+ /**
105
+ * Register a new page template.
106
+ * @throws Error if a template with the same ID already exists
107
+ */
108
+ register(template) {
109
+ if (this.templates.has(template.id)) throw new Error(`Page template with ID "${template.id}" is already registered`);
110
+ this.templates.set(template.id, template);
111
+ }
112
+ /**
113
+ * Unregister a page template by ID.
114
+ * Core templates cannot be unregistered.
115
+ * @returns true if the template was removed, false if it didn't exist or is a core template
116
+ */
117
+ unregister(id) {
118
+ const template = this.templates.get(id);
119
+ if (!template) return false;
120
+ if (template.isCore) {
121
+ console.warn(`Cannot unregister core page template "${id}". Core templates are required.`);
122
+ return false;
123
+ }
124
+ return this.templates.delete(id);
125
+ }
126
+ /**
127
+ * Get a page template by ID.
128
+ */
129
+ get(id) {
130
+ return this.templates.get(id);
131
+ }
132
+ /**
133
+ * Get all page templates in a specific category.
134
+ */
135
+ getByCategory(category) {
136
+ return Array.from(this.templates.values()).filter((t) => t.category === category);
137
+ }
138
+ /**
139
+ * List all registered page templates.
140
+ */
141
+ listAll() {
142
+ return Array.from(this.templates.values());
143
+ }
144
+ /**
145
+ * List all core page templates (isCore: true).
146
+ */
147
+ listCore() {
148
+ return Array.from(this.templates.values()).filter((t) => t.isCore);
149
+ }
150
+ /**
151
+ * List all non-core page templates.
152
+ */
153
+ listOptional() {
154
+ return Array.from(this.templates.values()).filter((t) => !t.isCore);
155
+ }
156
+ /**
157
+ * List all registered categories.
158
+ */
159
+ listCategories() {
160
+ return [...this.categories];
161
+ }
162
+ /**
163
+ * Add a custom category.
164
+ */
165
+ addCategory(category) {
166
+ if (this.categories.some((c) => c.id === category.id)) {
167
+ console.warn(`Category with ID "${category.id}" already exists`);
168
+ return;
169
+ }
170
+ this.categories.push(category);
171
+ }
172
+ /**
173
+ * Check if a template exists by ID.
174
+ */
175
+ has(id) {
176
+ return this.templates.has(id);
177
+ }
178
+ /**
179
+ * Get the count of registered templates.
180
+ */
181
+ get size() {
182
+ return this.templates.size;
183
+ }
184
+ /**
185
+ * Clear all non-core templates.
186
+ * Useful for testing or resetting the registry.
187
+ */
188
+ clearNonCore() {
189
+ for (const [id, template] of this.templates) if (!template.isCore) this.templates.delete(id);
190
+ }
191
+ };
192
+ /**
193
+ * Global page template registry singleton.
194
+ *
195
+ * This registry is automatically populated with core page templates
196
+ * (Messaging, Contacts) when the SDK is imported.
197
+ */
198
+ const PageTemplateRegistry = new PageTemplateRegistryImpl();
199
+ //#endregion
200
+ //#region src/core/resolve-pages.ts
201
+ /**
202
+ * Apply prop overrides to widgets in a component tree.
203
+ * Recursively walks widget.props.children so overrides targeting
204
+ * nested widgets (inside LayoutWidget, ContainerWidget, etc.) are applied.
205
+ *
206
+ * @param componentTree - The original component tree from the page template
207
+ * @param overrides - Array of widget-specific prop overrides
208
+ * @returns A new component tree with overrides applied
209
+ */
210
+ function applyOverrides(componentTree, overrides) {
211
+ if (!overrides.length) return [...componentTree];
212
+ const overrideMap = new Map(overrides.map((o) => [o.widget_id, o.props]));
213
+ return componentTree.map((widget) => applyWidgetOverrides(widget, overrideMap));
214
+ }
215
+ /**
216
+ * Recursively apply overrides to a widget and its children.
217
+ * Only clones widgets that actually change (have an override or contain children that might).
218
+ */
219
+ function applyWidgetOverrides(widget, overrideMap) {
220
+ const override = widget.id ? overrideMap.get(widget.id) : void 0;
221
+ const children = widget.props.children;
222
+ const hasChildren = Array.isArray(children) && children.length > 0;
223
+ if (!override && !hasChildren) return widget;
224
+ const newProps = override ? {
225
+ ...widget.props,
226
+ ...override
227
+ } : { ...widget.props };
228
+ if (hasChildren) newProps.children = children.map((child) => applyWidgetOverrides(child, overrideMap));
229
+ return {
230
+ ...widget,
231
+ props: newProps
232
+ };
233
+ }
234
+ /**
235
+ * Resolve a page reference to a screen definition.
236
+ *
237
+ * @param ref - The page reference from navigation
238
+ * @returns A ScreenDefinition or undefined if the template doesn't exist
239
+ */
240
+ function resolvePageReference(ref) {
241
+ const template = PageTemplateRegistry.get(ref.page_template_id);
242
+ if (!template) {
243
+ console.warn(`Page template "${ref.page_template_id}" not found in registry`);
244
+ return;
245
+ }
246
+ const componentTree = ref.overrides ? applyOverrides(template.component_tree, ref.overrides) : [...template.component_tree];
247
+ return {
248
+ id: ref.screen_id,
249
+ slug: template.slug,
250
+ name: template.name,
251
+ component_tree: componentTree
252
+ };
253
+ }
254
+ /**
255
+ * Resolve all page references and local screens into a unified screen list.
256
+ *
257
+ * This function merges:
258
+ * 1. Screen definitions from page_refs (shared templates)
259
+ * 2. Local screen definitions (for backwards compatibility and custom screens)
260
+ *
261
+ * When a screen_id appears in both page_refs and screens, the local screen
262
+ * takes precedence (allows local overrides of template pages).
263
+ *
264
+ * @param navigation - The navigation configuration
265
+ * @returns A unified array of ScreenDefinition objects
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * const navigation: Navigation = {
270
+ * definition_id: 1,
271
+ * id: 1,
272
+ * name: "Main Nav",
273
+ * navigation_items: [...],
274
+ * screens: [
275
+ * // Local custom screen
276
+ * { id: 1, slug: "home", name: "Home", component_tree: [...] }
277
+ * ],
278
+ * page_refs: [
279
+ * // Reference to shared messaging template
280
+ * { page_template_id: "core-messaging", screen_id: 2 }
281
+ * ],
282
+ * };
283
+ *
284
+ * const allScreens = resolveNavigationPages(navigation);
285
+ * // Returns: [home screen, messaging screen from template]
286
+ * ```
287
+ */
288
+ function resolveNavigationPages(navigation) {
289
+ const localScreenIds = new Set(navigation.screens.map((s) => s.id));
290
+ const result = [...navigation.screens];
291
+ if (navigation.page_refs) for (const ref of navigation.page_refs) {
292
+ if (localScreenIds.has(ref.screen_id)) continue;
293
+ const screen = resolvePageReference(ref);
294
+ if (screen) result.push(screen);
295
+ }
296
+ return result;
297
+ }
298
+ /**
299
+ * Get all available page templates for use in navigation.
300
+ *
301
+ * @returns Array of page templates from the registry
302
+ */
303
+ function getAvailablePageTemplates() {
304
+ return PageTemplateRegistry.listAll();
305
+ }
306
+ /**
307
+ * Get core page templates that are required for basic functionality.
308
+ *
309
+ * @returns Array of core page templates
310
+ */
311
+ function getCorePageTemplates() {
312
+ return PageTemplateRegistry.listCore();
313
+ }
314
+ /**
315
+ * Get optional page templates that can be added to navigation.
316
+ *
317
+ * @returns Array of optional (non-core) page templates
318
+ */
319
+ function getOptionalPageTemplates() {
320
+ return PageTemplateRegistry.listOptional();
321
+ }
322
+ /**
323
+ * Check if a navigation has all required core pages.
324
+ *
325
+ * @param navigation - The navigation to check
326
+ * @returns Object with validation result and missing page IDs
327
+ */
328
+ function validateNavigationPages(navigation) {
329
+ const corePages = PageTemplateRegistry.listCore();
330
+ const referencedTemplateIds = new Set(navigation.page_refs?.map((ref) => ref.page_template_id) ?? []);
331
+ const missingCorePages = corePages.filter((page) => !referencedTemplateIds.has(page.id)).map((page) => page.id);
332
+ return {
333
+ valid: missingCorePages.length === 0,
334
+ missingCorePages
335
+ };
336
+ }
337
+ //#endregion
338
+ //#region src/providers/PageTemplateProvider.tsx
339
+ /** Stable empty array for the default `templates` prop (avoids new reference each render) */
340
+ const EMPTY_TEMPLATES = [];
341
+ const PageTemplateContext = createContext(null);
342
+ /**
343
+ * Provider for page template resolution.
344
+ *
345
+ * This provider:
346
+ * 1. Registers any custom templates passed via props
347
+ * 2. Provides methods for resolving navigation pages
348
+ * 3. Cleans up custom templates on unmount
349
+ *
350
+ * @example
351
+ * ```tsx
352
+ * // With custom templates
353
+ * const customTemplates: PageTemplate[] = [
354
+ * {
355
+ * id: 'custom-dashboard',
356
+ * slug: 'dashboard',
357
+ * name: 'Dashboard',
358
+ * category: 'custom',
359
+ * version: '1.0.0',
360
+ * component_tree: [{ type: 'TextWidget', props: { text: 'Custom Dashboard' } }],
361
+ * },
362
+ * ];
363
+ *
364
+ * <PageTemplateProvider templates={customTemplates}>
365
+ * <App />
366
+ * </PageTemplateProvider>
367
+ *
368
+ * // Without custom templates (uses only core templates)
369
+ * <PageTemplateProvider>
370
+ * <App />
371
+ * </PageTemplateProvider>
372
+ * ```
373
+ */
374
+ function PageTemplateProvider({ children, templates = EMPTY_TEMPLATES }) {
375
+ const registeredIds = useRef([]);
376
+ useEffect(() => {
377
+ const registered = [];
378
+ for (const template of templates) if (!PageTemplateRegistry.has(template.id)) try {
379
+ PageTemplateRegistry.register(template);
380
+ registered.push(template.id);
381
+ } catch (error) {
382
+ console.warn(`Failed to register page template "${template.id}":`, error);
383
+ }
384
+ registeredIds.current = registered;
385
+ return () => {
386
+ for (const id of registeredIds.current) PageTemplateRegistry.unregister(id);
387
+ registeredIds.current = [];
388
+ };
389
+ }, [templates.map((t) => t.id).join(",")]);
390
+ const contextValue = useMemo(() => ({
391
+ resolvePages: resolveNavigationPages,
392
+ listTemplates: () => PageTemplateRegistry.listAll(),
393
+ getTemplate: (id) => PageTemplateRegistry.get(id),
394
+ hasTemplate: (id) => PageTemplateRegistry.has(id)
395
+ }), []);
396
+ return /* @__PURE__ */ jsx(PageTemplateContext.Provider, {
397
+ value: contextValue,
398
+ children
399
+ });
400
+ }
401
+ /**
402
+ * Hook to access page template functionality.
403
+ *
404
+ * @throws Error if used outside of PageTemplateProvider
405
+ *
406
+ * @example
407
+ * ```tsx
408
+ * function NavigationRenderer({ navigation }: { navigation: Navigation }) {
409
+ * const { resolvePages } = usePageTemplates();
410
+ * const screens = resolvePages(navigation);
411
+ *
412
+ * return (
413
+ * <div>
414
+ * {screens.map((screen) => (
415
+ * <Screen key={screen.id} definition={screen} />
416
+ * ))}
417
+ * </div>
418
+ * );
419
+ * }
420
+ * ```
421
+ */
422
+ function usePageTemplates() {
423
+ const context = useContext(PageTemplateContext);
424
+ if (!context) throw new Error("usePageTemplates must be used within a PageTemplateProvider");
425
+ return context;
426
+ }
427
+ /**
428
+ * Hook to resolve navigation pages directly.
429
+ * Convenience wrapper around usePageTemplates().resolvePages.
430
+ *
431
+ * @param navigation - The navigation to resolve
432
+ * @returns Array of resolved screen definitions
433
+ */
434
+ function useResolvedPages(navigation) {
435
+ const { resolvePages } = usePageTemplates();
436
+ return useMemo(() => resolvePages(navigation), [resolvePages, navigation]);
437
+ }
438
+ //#endregion
439
+ //#region src/hooks/use-fluid-profile.ts
440
+ /**
441
+ * Query key for profile data
442
+ */
443
+ const PROFILE_QUERY_KEY = ["fluid", "profile"];
444
+ /**
445
+ * Hook to fetch the rep portal profile (themes, navigation, screens)
446
+ *
447
+ * @example
448
+ * ```tsx
449
+ * function Navigation() {
450
+ * const { data: profile, isLoading } = useFluidProfile();
451
+ *
452
+ * if (isLoading) return <Spinner />;
453
+ *
454
+ * return (
455
+ * <nav>
456
+ * {profile?.navigation.navigation_items.map(item => (
457
+ * <NavItem key={item.id} item={item} />
458
+ * ))}
459
+ * </nav>
460
+ * );
461
+ * }
462
+ * ```
463
+ */
464
+ function useFluidProfile() {
465
+ const api = useFluidApi();
466
+ return useQuery({
467
+ queryKey: PROFILE_QUERY_KEY,
468
+ queryFn: () => api.profile.get()
469
+ });
470
+ }
471
+ //#endregion
472
+ //#region ../../../node_modules/.pnpm/@tanstack+query-core@5.90.12/node_modules/@tanstack/query-core/build/modern/timeoutManager.js
473
+ var defaultTimeoutProvider = {
474
+ setTimeout: (callback, delay) => setTimeout(callback, delay),
475
+ clearTimeout: (timeoutId) => clearTimeout(timeoutId),
476
+ setInterval: (callback, delay) => setInterval(callback, delay),
477
+ clearInterval: (intervalId) => clearInterval(intervalId)
478
+ };
479
+ var TimeoutManager = class {
480
+ #provider = defaultTimeoutProvider;
481
+ #providerCalled = false;
482
+ setTimeoutProvider(provider) {
483
+ if (process.env.NODE_ENV !== "production") {
484
+ if (this.#providerCalled && provider !== this.#provider) console.error(`[timeoutManager]: Switching provider after calls to previous provider might result in unexpected behavior.`, {
485
+ previous: this.#provider,
486
+ provider
487
+ });
488
+ }
489
+ this.#provider = provider;
490
+ if (process.env.NODE_ENV !== "production") this.#providerCalled = false;
491
+ }
492
+ setTimeout(callback, delay) {
493
+ if (process.env.NODE_ENV !== "production") this.#providerCalled = true;
494
+ return this.#provider.setTimeout(callback, delay);
495
+ }
496
+ clearTimeout(timeoutId) {
497
+ this.#provider.clearTimeout(timeoutId);
498
+ }
499
+ setInterval(callback, delay) {
500
+ if (process.env.NODE_ENV !== "production") this.#providerCalled = true;
501
+ return this.#provider.setInterval(callback, delay);
502
+ }
503
+ clearInterval(intervalId) {
504
+ this.#provider.clearInterval(intervalId);
505
+ }
506
+ };
507
+ new TimeoutManager();
508
+ function systemSetTimeoutZero(callback) {
509
+ setTimeout(callback, 0);
510
+ }
511
+ typeof window === "undefined" || "Deno" in globalThis;
512
+ function matchQuery(filters, query) {
513
+ const { type = "all", exact, fetchStatus, predicate, queryKey, stale } = filters;
514
+ if (queryKey) {
515
+ if (exact) {
516
+ if (query.queryHash !== hashQueryKeyByOptions(queryKey, query.options)) return false;
517
+ } else if (!partialMatchKey(query.queryKey, queryKey)) return false;
518
+ }
519
+ if (type !== "all") {
520
+ const isActive = query.isActive();
521
+ if (type === "active" && !isActive) return false;
522
+ if (type === "inactive" && isActive) return false;
523
+ }
524
+ if (typeof stale === "boolean" && query.isStale() !== stale) return false;
525
+ if (fetchStatus && fetchStatus !== query.state.fetchStatus) return false;
526
+ if (predicate && !predicate(query)) return false;
527
+ return true;
528
+ }
529
+ function hashQueryKeyByOptions(queryKey, options) {
530
+ return (options?.queryKeyHashFn || hashKey)(queryKey);
531
+ }
532
+ function hashKey(queryKey) {
533
+ return JSON.stringify(queryKey, (_, val) => isPlainObject(val) ? Object.keys(val).sort().reduce((result, key) => {
534
+ result[key] = val[key];
535
+ return result;
536
+ }, {}) : val);
537
+ }
538
+ function partialMatchKey(a, b) {
539
+ if (a === b) return true;
540
+ if (typeof a !== typeof b) return false;
541
+ if (a && b && typeof a === "object" && typeof b === "object") return Object.keys(b).every((key) => partialMatchKey(a[key], b[key]));
542
+ return false;
543
+ }
544
+ function isPlainObject(o) {
545
+ if (!hasObjectPrototype(o)) return false;
546
+ const ctor = o.constructor;
547
+ if (ctor === void 0) return true;
548
+ const prot = ctor.prototype;
549
+ if (!hasObjectPrototype(prot)) return false;
550
+ if (!prot.hasOwnProperty("isPrototypeOf")) return false;
551
+ if (Object.getPrototypeOf(o) !== Object.prototype) return false;
552
+ return true;
553
+ }
554
+ function hasObjectPrototype(o) {
555
+ return Object.prototype.toString.call(o) === "[object Object]";
556
+ }
557
+ //#endregion
558
+ //#region ../../../node_modules/.pnpm/@tanstack+query-core@5.90.12/node_modules/@tanstack/query-core/build/modern/notifyManager.js
559
+ var defaultScheduler = systemSetTimeoutZero;
560
+ function createNotifyManager() {
561
+ let queue = [];
562
+ let transactions = 0;
563
+ let notifyFn = (callback) => {
564
+ callback();
565
+ };
566
+ let batchNotifyFn = (callback) => {
567
+ callback();
568
+ };
569
+ let scheduleFn = defaultScheduler;
570
+ const schedule = (callback) => {
571
+ if (transactions) queue.push(callback);
572
+ else scheduleFn(() => {
573
+ notifyFn(callback);
574
+ });
575
+ };
576
+ const flush = () => {
577
+ const originalQueue = queue;
578
+ queue = [];
579
+ if (originalQueue.length) scheduleFn(() => {
580
+ batchNotifyFn(() => {
581
+ originalQueue.forEach((callback) => {
582
+ notifyFn(callback);
583
+ });
584
+ });
585
+ });
586
+ };
587
+ return {
588
+ batch: (callback) => {
589
+ let result;
590
+ transactions++;
591
+ try {
592
+ result = callback();
593
+ } finally {
594
+ transactions--;
595
+ if (!transactions) flush();
596
+ }
597
+ return result;
598
+ },
599
+ batchCalls: (callback) => {
600
+ return (...args) => {
601
+ schedule(() => {
602
+ callback(...args);
603
+ });
604
+ };
605
+ },
606
+ schedule,
607
+ setNotifyFunction: (fn) => {
608
+ notifyFn = fn;
609
+ },
610
+ setBatchNotifyFunction: (fn) => {
611
+ batchNotifyFn = fn;
612
+ },
613
+ setScheduler: (fn) => {
614
+ scheduleFn = fn;
615
+ }
616
+ };
617
+ }
618
+ var notifyManager = createNotifyManager();
619
+ //#endregion
620
+ //#region ../../../node_modules/.pnpm/@tanstack+query-persist-client-core@5.91.11/node_modules/@tanstack/query-persist-client-core/build/modern/createPersister.js
621
+ var PERSISTER_KEY_PREFIX = "tanstack-query";
622
+ function experimental_createQueryPersister({ storage, buster = "", maxAge = 1e3 * 60 * 60 * 24, serialize = JSON.stringify, deserialize = JSON.parse, prefix = PERSISTER_KEY_PREFIX, refetchOnRestore = true, filters }) {
623
+ function isExpiredOrBusted(persistedQuery) {
624
+ if (persistedQuery.state.dataUpdatedAt) {
625
+ const expired = Date.now() - persistedQuery.state.dataUpdatedAt > maxAge;
626
+ const busted = persistedQuery.buster !== buster;
627
+ if (expired || busted) return true;
628
+ return false;
629
+ }
630
+ return true;
631
+ }
632
+ async function retrieveQuery(queryHash, afterRestoreMacroTask) {
633
+ if (storage != null) {
634
+ const storageKey = `${prefix}-${queryHash}`;
635
+ try {
636
+ const storedData = await storage.getItem(storageKey);
637
+ if (storedData) {
638
+ const persistedQuery = await deserialize(storedData);
639
+ if (isExpiredOrBusted(persistedQuery)) await storage.removeItem(storageKey);
640
+ else {
641
+ if (afterRestoreMacroTask) notifyManager.schedule(() => afterRestoreMacroTask(persistedQuery));
642
+ return persistedQuery.state.data;
643
+ }
644
+ }
645
+ } catch (err) {
646
+ if (process.env.NODE_ENV === "development") {
647
+ console.error(err);
648
+ console.warn("Encountered an error attempting to restore query cache from persisted location.");
649
+ }
650
+ await storage.removeItem(storageKey);
651
+ }
652
+ }
653
+ }
654
+ async function persistQueryByKey(queryKey, queryClient) {
655
+ if (storage != null) {
656
+ const query = queryClient.getQueryCache().find({ queryKey });
657
+ if (query) await persistQuery(query);
658
+ else if (process.env.NODE_ENV === "development") console.warn("Could not find query to be persisted. QueryKey:", JSON.stringify(queryKey));
659
+ }
660
+ }
661
+ async function persistQuery(query) {
662
+ if (storage != null) {
663
+ const storageKey = `${prefix}-${query.queryHash}`;
664
+ storage.setItem(storageKey, await serialize({
665
+ state: query.state,
666
+ queryKey: query.queryKey,
667
+ queryHash: query.queryHash,
668
+ buster
669
+ }));
670
+ }
671
+ }
672
+ async function persisterFn(queryFn, ctx, query) {
673
+ const matchesFilter = filters ? matchQuery(filters, query) : true;
674
+ if (matchesFilter && query.state.data === void 0 && storage != null) {
675
+ const restoredData = await retrieveQuery(query.queryHash, (persistedQuery) => {
676
+ query.setState({
677
+ dataUpdatedAt: persistedQuery.state.dataUpdatedAt,
678
+ errorUpdatedAt: persistedQuery.state.errorUpdatedAt
679
+ });
680
+ if (refetchOnRestore === "always" || refetchOnRestore === true && query.isStale()) query.fetch();
681
+ });
682
+ if (restoredData !== void 0) return Promise.resolve(restoredData);
683
+ }
684
+ const queryFnResult = await queryFn(ctx);
685
+ if (matchesFilter && storage != null) notifyManager.schedule(() => {
686
+ persistQuery(query);
687
+ });
688
+ return Promise.resolve(queryFnResult);
689
+ }
690
+ async function persisterGc() {
691
+ if (storage?.entries) {
692
+ const entries = await storage.entries();
693
+ for (const [key, value] of entries) if (key.startsWith(prefix)) {
694
+ if (isExpiredOrBusted(await deserialize(value))) await storage.removeItem(key);
695
+ }
696
+ } else if (process.env.NODE_ENV === "development") throw new Error("Provided storage does not implement `entries` method. Garbage collection is not possible without ability to iterate over storage items.");
697
+ }
698
+ async function restoreQueries(queryClient, filters2 = {}) {
699
+ const { exact, queryKey } = filters2;
700
+ if (storage?.entries) {
701
+ const entries = await storage.entries();
702
+ for (const [key, value] of entries) if (key.startsWith(prefix)) {
703
+ const persistedQuery = await deserialize(value);
704
+ if (isExpiredOrBusted(persistedQuery)) {
705
+ await storage.removeItem(key);
706
+ continue;
707
+ }
708
+ if (queryKey) {
709
+ if (exact) {
710
+ if (persistedQuery.queryHash !== hashKey(queryKey)) continue;
711
+ } else if (!partialMatchKey(persistedQuery.queryKey, queryKey)) continue;
712
+ }
713
+ queryClient.setQueryData(persistedQuery.queryKey, persistedQuery.state.data, { updatedAt: persistedQuery.state.dataUpdatedAt });
714
+ }
715
+ } else if (process.env.NODE_ENV === "development") throw new Error("Provided storage does not implement `entries` method. Restoration of all stored entries is not possible without ability to iterate over storage items.");
716
+ }
717
+ return {
718
+ persisterFn,
719
+ persistQuery,
720
+ persistQueryByKey,
721
+ retrieveQuery,
722
+ persisterGc,
723
+ restoreQueries
724
+ };
725
+ }
726
+ //#endregion
727
+ //#region ../../platform/query-persister/src/persister.ts
728
+ const DB_NAME = "fluid_tanstack_query_cache";
729
+ const STORE_NAME = "fluid_queries";
730
+ const VERSION = 1;
731
+ let dbPromise = null;
732
+ async function deleteDatabase() {
733
+ return new Promise((resolve, reject) => {
734
+ console.warn("[IDB] Deleting database due to error");
735
+ const req = indexedDB.deleteDatabase(DB_NAME);
736
+ req.onsuccess = () => {
737
+ console.log("[IDB] Database deleted successfully");
738
+ resolve();
739
+ };
740
+ req.onerror = () => {
741
+ console.error("[IDB] Failed to delete database:", req.error);
742
+ reject(req.error ?? /* @__PURE__ */ new Error("deleteDatabase failed"));
743
+ };
744
+ req.onblocked = () => {
745
+ console.warn("[IDB] Delete blocked: close all tabs using this database");
746
+ };
747
+ });
748
+ }
749
+ function openDatabase() {
750
+ return new Promise((resolve, reject) => {
751
+ const req = indexedDB.open(DB_NAME, VERSION);
752
+ req.onupgradeneeded = () => {
753
+ const upgradeDb = req.result;
754
+ if (!upgradeDb.objectStoreNames.contains(STORE_NAME)) upgradeDb.createObjectStore(STORE_NAME);
755
+ };
756
+ req.onsuccess = () => {
757
+ const conn = req.result;
758
+ conn.onversionchange = () => {
759
+ console.trace("[IDB] version change – closing connection");
760
+ conn.close();
761
+ dbPromise = null;
762
+ };
763
+ resolve(conn);
764
+ };
765
+ req.onblocked = () => {
766
+ console.warn("[IDB] open blocked: another connection is holding the DB");
767
+ };
768
+ req.onerror = () => {
769
+ reject(req.error instanceof Error ? req.error : /* @__PURE__ */ new Error(`IndexedDB open failed: ${String(req.error)}`));
770
+ };
771
+ });
772
+ }
773
+ async function getDbWithRecovery() {
774
+ try {
775
+ return await openDatabase();
776
+ } catch (err) {
777
+ console.warn("[IDB] Initial open failed, attempting recovery:", err);
778
+ try {
779
+ await deleteDatabase();
780
+ console.log("[IDB] Retrying database open after deletion");
781
+ return await openDatabase();
782
+ } catch (retryErr) {
783
+ console.error("[IDB] Recovery failed:", retryErr);
784
+ throw retryErr;
785
+ }
786
+ }
787
+ }
788
+ function getDb() {
789
+ if (dbPromise) return dbPromise;
790
+ dbPromise = getDbWithRecovery().catch((err) => {
791
+ dbPromise = null;
792
+ throw err;
793
+ });
794
+ return dbPromise;
795
+ }
796
+ const storage = {
797
+ async getItem(key) {
798
+ try {
799
+ const db = await getDb();
800
+ return new Promise((res) => {
801
+ try {
802
+ const r = db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).get(key);
803
+ r.onsuccess = () => res(r.result);
804
+ r.onerror = () => {
805
+ console.trace("[IDB] getItem error:", r.error);
806
+ res(void 0);
807
+ };
808
+ } catch (txErr) {
809
+ console.trace("[IDB] getItem transaction error:", txErr);
810
+ res(void 0);
811
+ }
812
+ });
813
+ } catch (err) {
814
+ console.trace("[IDB] getItem getDb error:", err);
815
+ try {
816
+ const db = await getDb();
817
+ return new Promise((res) => {
818
+ try {
819
+ const r = db.transaction(STORE_NAME, "readonly").objectStore(STORE_NAME).get(key);
820
+ r.onsuccess = () => res(r.result);
821
+ r.onerror = () => {
822
+ console.trace("[IDB] getItem retry error:", r.error);
823
+ res(void 0);
824
+ };
825
+ } catch (txErr) {
826
+ console.trace("[IDB] getItem retry transaction error:", txErr);
827
+ res(void 0);
828
+ }
829
+ });
830
+ } catch (recoveryErr) {
831
+ console.trace("[IDB] getItem recovery failed:", recoveryErr);
832
+ }
833
+ return;
834
+ }
835
+ },
836
+ async setItem(key, value) {
837
+ const cloneableValue = JSON.parse(JSON.stringify(value));
838
+ try {
839
+ const db = await getDb();
840
+ if (!db) return;
841
+ await new Promise((resolve) => {
842
+ try {
843
+ const req = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(cloneableValue, key);
844
+ req.onsuccess = () => resolve();
845
+ req.onerror = () => {
846
+ console.trace("[IDB] setItem error:", req.error);
847
+ resolve();
848
+ };
849
+ } catch (txErr) {
850
+ console.trace("[IDB] setItem transaction error:", txErr);
851
+ resolve();
852
+ }
853
+ });
854
+ } catch (err) {
855
+ console.trace("[IDB] setItem getDb error:", err);
856
+ try {
857
+ const db = await getDb();
858
+ await new Promise((resolve) => {
859
+ try {
860
+ const req = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).put(cloneableValue, key);
861
+ req.onsuccess = () => resolve();
862
+ req.onerror = () => {
863
+ console.trace("[IDB] setItem retry error:", req.error);
864
+ resolve();
865
+ };
866
+ } catch (txErr) {
867
+ console.trace("[IDB] setItem retry transaction error:", txErr);
868
+ resolve();
869
+ }
870
+ });
871
+ } catch (recoveryErr) {
872
+ console.trace("[IDB] setItem recovery failed:", recoveryErr);
873
+ }
874
+ }
875
+ },
876
+ async removeItem(key) {
877
+ try {
878
+ const db = await getDb();
879
+ if (!db) return;
880
+ await new Promise((resolve) => {
881
+ try {
882
+ const req = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).delete(key);
883
+ req.onsuccess = () => resolve();
884
+ req.onerror = () => {
885
+ console.trace("[IDB] removeItem error:", req.error);
886
+ resolve();
887
+ };
888
+ } catch (txErr) {
889
+ console.trace("[IDB] removeItem transaction error:", txErr);
890
+ resolve();
891
+ }
892
+ });
893
+ } catch (err) {
894
+ console.trace("[IDB] removeItem getDb error:", err);
895
+ try {
896
+ const db = await getDb();
897
+ await new Promise((resolve) => {
898
+ try {
899
+ const req = db.transaction(STORE_NAME, "readwrite").objectStore(STORE_NAME).delete(key);
900
+ req.onsuccess = () => resolve();
901
+ req.onerror = () => {
902
+ console.trace("[IDB] removeItem retry error:", req.error);
903
+ resolve();
904
+ };
905
+ } catch (txErr) {
906
+ console.trace("[IDB] removeItem retry transaction error:", txErr);
907
+ resolve();
908
+ }
909
+ });
910
+ } catch (recoveryErr) {
911
+ console.trace("[IDB] removeItem recovery failed:", recoveryErr);
912
+ }
913
+ }
914
+ }
915
+ };
916
+ function createPersister() {
917
+ return experimental_createQueryPersister({
918
+ storage,
919
+ serialize: (persistedQuery) => persistedQuery,
920
+ deserialize: (cached) => cached
921
+ });
922
+ }
923
+ //#endregion
924
+ //#region src/hooks/use-fluid-app.ts
925
+ /**
926
+ * Query key for full app data (fluidos endpoint)
927
+ */
928
+ const APP_DATA_QUERY_KEY = ["fluid", "app"];
929
+ /**
930
+ * Module-level persister instance (browser only).
931
+ * Created once when the module loads so all calls to useFluidApp share
932
+ * the same IndexedDB connection. Applied at the query level so it works
933
+ * regardless of which QueryClient the app provides.
934
+ */
935
+ const appDataPersister = typeof window !== "undefined" ? createPersister() : void 0;
936
+ /**
937
+ * Hook to fetch the full rep app data from the fluidos API.
938
+ *
939
+ * Returns a `RepAppData` object containing:
940
+ * - `screens` — all screen definitions with normalized component trees
941
+ * - `profile.themes` — fully-transformed ThemeDefinition[] (handles legacy + new formats)
942
+ * - `profile.activeThemeId` — the currently active theme ID
943
+ * - `profile.navigation.navigation_items` — sorted, recursive navigation tree
944
+ *
945
+ * Uses IndexedDB persistence so subsequent page loads hydrate instantly
946
+ * from cache while revalidating in the background. The raw API response
947
+ * (plain JSON) is cached; Color objects are recreated from cache via
948
+ * `select` on every restore — this is fast (CPU only, no network).
949
+ *
950
+ * @example
951
+ * ```tsx
952
+ * function App() {
953
+ * const { data: appData, isLoading } = useFluidApp();
954
+ *
955
+ * if (isLoading) return <Spinner />;
956
+ *
957
+ * return (
958
+ * <AppShell
959
+ * appData={appData}
960
+ * navigation={appData.profile.navigation.navigation_items}
961
+ * />
962
+ * );
963
+ * }
964
+ * ```
965
+ */
966
+ function useFluidApp(options) {
967
+ const api = useFluidApi();
968
+ return useQuery({
969
+ queryKey: APP_DATA_QUERY_KEY,
970
+ queryFn: () => api.app.getRaw(),
971
+ select: transformManifestToRepAppData,
972
+ ...appDataPersister && { persister: appDataPersister.persisterFn },
973
+ ...options?.enabled !== void 0 && { enabled: options.enabled }
974
+ });
975
+ }
976
+ //#endregion
977
+ //#region src/hooks/use-fluid-permissions.ts
978
+ /**
979
+ * Query key for permissions data
980
+ */
981
+ const PERMISSIONS_QUERY_KEY = ["fluid", "permissions"];
982
+ /**
983
+ * Hook to fetch and check user permissions
984
+ *
985
+ * @example
986
+ * ```tsx
987
+ * function TeamSettings() {
988
+ * const { can, isSuperAdmin } = useFluidPermissions();
989
+ *
990
+ * if (!can("team", "manage")) {
991
+ * return <AccessDenied />;
992
+ * }
993
+ *
994
+ * return <TeamSettingsForm canDelete={can("team", "delete")} />;
995
+ * }
996
+ * ```
997
+ */
998
+ function useFluidPermissions() {
999
+ const api = useFluidApi();
1000
+ const query = useQuery({
1001
+ queryKey: PERMISSIONS_QUERY_KEY,
1002
+ queryFn: () => api.permissions.get()
1003
+ });
1004
+ const permissions = query.data;
1005
+ return {
1006
+ query,
1007
+ permissions,
1008
+ can: useMemo(() => {
1009
+ return (resource, action = "view") => {
1010
+ if (!permissions) return false;
1011
+ if (permissions.is_super_admin) return true;
1012
+ const resourcePermissions = permissions.permissions[resource];
1013
+ if (!resourcePermissions) return false;
1014
+ return resourcePermissions[action] ?? false;
1015
+ };
1016
+ }, [permissions]),
1017
+ isSuperAdmin: permissions?.is_super_admin ?? false
1018
+ };
1019
+ }
1020
+ //#endregion
1021
+ //#region src/hooks/use-fluid-theme.ts
1022
+ /**
1023
+ * Hook to access and control theme settings
1024
+ *
1025
+ * @example
1026
+ * ```tsx
1027
+ * function ThemeSwitcher({ themes }: { themes: ThemeDefinition[] }) {
1028
+ * const { currentTheme, setTheme, setThemeMode, mode } = useFluidTheme();
1029
+ *
1030
+ * return (
1031
+ * <div>
1032
+ * <select
1033
+ * value={currentTheme?.name}
1034
+ * onChange={(e) => {
1035
+ * const theme = themes.find(t => t.name === e.target.value);
1036
+ * if (theme) setTheme(theme);
1037
+ * }}
1038
+ * >
1039
+ * {themes.map(theme => (
1040
+ * <option key={theme.name} value={theme.name}>
1041
+ * {theme.name}
1042
+ * </option>
1043
+ * ))}
1044
+ * </select>
1045
+ *
1046
+ * <button onClick={() => setThemeMode(mode === "dark" ? "light" : "dark")}>
1047
+ * Toggle {mode === "dark" ? "Light" : "Dark"} Mode
1048
+ * </button>
1049
+ * </div>
1050
+ * );
1051
+ * }
1052
+ * ```
1053
+ */
1054
+ function useFluidTheme() {
1055
+ const { currentTheme, setTheme, setThemeMode, mode } = useThemeContext();
1056
+ return {
1057
+ currentTheme,
1058
+ setTheme,
1059
+ setThemeMode,
1060
+ mode
1061
+ };
1062
+ }
1063
+ //#endregion
1064
+ //#region src/hooks/use-current-rep.ts
1065
+ /**
1066
+ * Query key for current rep data
1067
+ */
1068
+ const CURRENT_REP_QUERY_KEY = ["fluid", "currentRep"];
1069
+ /**
1070
+ * Hook to fetch the currently authenticated rep's profile
1071
+ *
1072
+ * @example
1073
+ * ```tsx
1074
+ * function RepHeader() {
1075
+ * const { data: rep, isLoading } = useCurrentRep();
1076
+ *
1077
+ * if (isLoading) return <Skeleton />;
1078
+ *
1079
+ * return (
1080
+ * <div>
1081
+ * <Avatar src={rep?.avatar_url} />
1082
+ * <span>{rep?.first_name} {rep?.last_name}</span>
1083
+ * </div>
1084
+ * );
1085
+ * }
1086
+ * ```
1087
+ */
1088
+ function useCurrentRep() {
1089
+ const api = useFluidApi();
1090
+ return useQuery({
1091
+ queryKey: CURRENT_REP_QUERY_KEY,
1092
+ queryFn: () => api.reps.current()
1093
+ });
1094
+ }
1095
+ //#endregion
1096
+ //#region src/hooks/use-fluid-auth.ts
1097
+ /**
1098
+ * useFluidAuth Hook
1099
+ *
1100
+ * Provides access to authentication state and utilities.
1101
+ * This is the primary hook for interacting with auth in components.
1102
+ */
1103
+ /**
1104
+ * Hook to access authentication state and utilities.
1105
+ *
1106
+ * Must be used within a `FluidAuthProvider`.
1107
+ *
1108
+ * @returns Authentication context with user info, loading state, and utilities
1109
+ * @throws Error if used outside FluidAuthProvider
1110
+ *
1111
+ * @example
1112
+ * ```tsx
1113
+ * function UserProfile() {
1114
+ * const { isAuthenticated, isLoading, user, clearAuth } = useFluidAuth();
1115
+ *
1116
+ * if (isLoading) {
1117
+ * return <Spinner />;
1118
+ * }
1119
+ *
1120
+ * if (!isAuthenticated) {
1121
+ * return <p>Please log in</p>;
1122
+ * }
1123
+ *
1124
+ * return (
1125
+ * <div>
1126
+ * <p>Welcome, {user.full_name}!</p>
1127
+ * <button onClick={clearAuth}>Log out</button>
1128
+ * </div>
1129
+ * );
1130
+ * }
1131
+ * ```
1132
+ */
1133
+ function useFluidAuth() {
1134
+ return useFluidAuthContext();
1135
+ }
1136
+ //#endregion
1137
+ //#region src/hooks/demo-data/calendar-events.ts
1138
+ const now$4 = /* @__PURE__ */ new Date();
1139
+ function daysFromNow$1(days, hour, minute = 0) {
1140
+ const d = new Date(now$4);
1141
+ d.setDate(d.getDate() + days);
1142
+ d.setHours(hour, minute, 0, 0);
1143
+ return d;
1144
+ }
1145
+ const DEMO_CALENDAR_EVENTS = [
1146
+ {
1147
+ id: 1,
1148
+ title: "Team Standup",
1149
+ description: { body: "Daily sync with the sales team" },
1150
+ color: "#4f46e5",
1151
+ start: daysFromNow$1(0, 9, 0).toISOString(),
1152
+ end: daysFromNow$1(0, 9, 30).toISOString(),
1153
+ active: true,
1154
+ status: "confirmed"
1155
+ },
1156
+ {
1157
+ id: 2,
1158
+ title: "Client Review - Acme Corp",
1159
+ description: { body: "Quarterly business review with key accounts" },
1160
+ color: "#059669",
1161
+ start: daysFromNow$1(2, 14, 0).toISOString(),
1162
+ end: daysFromNow$1(2, 15, 0).toISOString(),
1163
+ active: true,
1164
+ status: "confirmed",
1165
+ venue: "Conference Room B"
1166
+ },
1167
+ {
1168
+ id: 3,
1169
+ title: "Product Training Webinar",
1170
+ description: { body: "New product line training for the field" },
1171
+ color: "#d97706",
1172
+ start: daysFromNow$1(4, 11, 0).toISOString(),
1173
+ end: daysFromNow$1(4, 12, 30).toISOString(),
1174
+ active: true,
1175
+ status: "confirmed",
1176
+ url: "https://example.com/webinar"
1177
+ },
1178
+ {
1179
+ id: 4,
1180
+ title: "Regional Conference",
1181
+ description: { body: "Annual West Coast regional conference" },
1182
+ color: "#dc2626",
1183
+ start: daysFromNow$1(10, 8, 0).toISOString(),
1184
+ end: daysFromNow$1(12, 17, 0).toISOString(),
1185
+ active: true,
1186
+ status: "confirmed",
1187
+ venue: "San Diego Convention Center",
1188
+ isAllDay: true
1189
+ }
1190
+ ];
1191
+ //#endregion
1192
+ //#region src/hooks/use-calendar-events.ts
1193
+ /**
1194
+ * Hook to fetch calendar events.
1195
+ * This is a stub implementation - override with your own data fetching logic.
1196
+ */
1197
+ function useCalendarEvents() {
1198
+ return {
1199
+ data: [...DEMO_CALENDAR_EVENTS],
1200
+ isLoading: false,
1201
+ isError: false
1202
+ };
1203
+ }
1204
+ //#endregion
1205
+ //#region src/hooks/demo-data/todos.ts
1206
+ const now$3 = /* @__PURE__ */ new Date();
1207
+ function daysFromNow(days) {
1208
+ const d = new Date(now$3);
1209
+ d.setDate(d.getDate() + days);
1210
+ return d.toISOString();
1211
+ }
1212
+ const DEMO_TODOS = [
1213
+ {
1214
+ id: 1,
1215
+ body: "Follow up with Sarah about Q2 order",
1216
+ dueAt: daysFromNow(1),
1217
+ completedAt: null,
1218
+ createdAt: daysFromNow(-2),
1219
+ contactName: "Sarah Johnson"
1220
+ },
1221
+ {
1222
+ id: 2,
1223
+ body: "Send pricing proposal to Acme Corp",
1224
+ dueAt: daysFromNow(7),
1225
+ completedAt: null,
1226
+ createdAt: daysFromNow(-1),
1227
+ contactName: "Mike Chen"
1228
+ },
1229
+ {
1230
+ id: 3,
1231
+ body: "Schedule onboarding call with new team member",
1232
+ dueAt: daysFromNow(3),
1233
+ completedAt: null,
1234
+ createdAt: daysFromNow(0),
1235
+ contactName: "Emily Rodriguez"
1236
+ },
1237
+ {
1238
+ id: 4,
1239
+ body: "Review and approve Q1 expense report",
1240
+ dueAt: daysFromNow(-1),
1241
+ completedAt: daysFromNow(-1),
1242
+ createdAt: daysFromNow(-5),
1243
+ contactName: null
1244
+ },
1245
+ {
1246
+ id: 5,
1247
+ body: "Prepare presentation for Friday team meeting",
1248
+ dueAt: daysFromNow(4),
1249
+ completedAt: daysFromNow(0),
1250
+ createdAt: daysFromNow(-3),
1251
+ contactName: null
1252
+ }
1253
+ ];
1254
+ //#endregion
1255
+ //#region src/hooks/use-todos.ts
1256
+ /**
1257
+ * Hook to fetch todo items.
1258
+ * This is a stub implementation - override with your own data fetching logic.
1259
+ */
1260
+ function useTodos() {
1261
+ return {
1262
+ data: [...DEMO_TODOS],
1263
+ isLoading: false,
1264
+ isError: false
1265
+ };
1266
+ }
1267
+ //#endregion
1268
+ //#region src/hooks/hook-types.ts
1269
+ /**
1270
+ * Type predicate to check if a query result has successfully loaded data.
1271
+ * Narrows the data type from T | null | undefined to T.
1272
+ *
1273
+ * @example
1274
+ * const result = useContact(id);
1275
+ * if (hasData(result)) {
1276
+ * // TypeScript knows result.data is Contact, not Contact | null
1277
+ * console.log(result.data.name);
1278
+ * }
1279
+ */
1280
+ function hasData(result) {
1281
+ return result.data != null && !result.isLoading && !result.isError;
1282
+ }
1283
+ /**
1284
+ * Type predicate to check if a query result is in loading state.
1285
+ * Useful for conditional rendering.
1286
+ *
1287
+ * @example
1288
+ * if (isLoading(result)) {
1289
+ * return <Spinner />;
1290
+ * }
1291
+ */
1292
+ function isLoading(result) {
1293
+ return result.isLoading;
1294
+ }
1295
+ /**
1296
+ * Type predicate to check if a query result has an error.
1297
+ * Narrows to include the error property.
1298
+ *
1299
+ * @example
1300
+ * if (isErrorResult(result)) {
1301
+ * console.error(result.error); // error is E, not undefined
1302
+ * }
1303
+ */
1304
+ function isErrorResult(result) {
1305
+ return result.isError && result.error !== void 0;
1306
+ }
1307
+ /**
1308
+ * Type predicate to check if a query result is in idle state (not loading, no error, has data).
1309
+ *
1310
+ * @example
1311
+ * if (isIdle(result)) {
1312
+ * // Safe to access and render data
1313
+ * }
1314
+ */
1315
+ function isIdle(result) {
1316
+ return !result.isLoading && !result.isError;
1317
+ }
1318
+ /**
1319
+ * Type-safe property selector for hook results.
1320
+ * Uses K extends keyof T constraint (generics-function-constraints rule).
1321
+ *
1322
+ * @typeParam T - The data object type
1323
+ * @typeParam K - Key of T (constrained to actual keys)
1324
+ *
1325
+ * @example
1326
+ * const users = [{ name: "Alice", age: 30 }];
1327
+ * const names = selectProperty(users, "name"); // string[]
1328
+ * const ages = selectProperty(users, "age"); // number[]
1329
+ * selectProperty(users, "invalid"); // Error: "invalid" not in "name" | "age"
1330
+ */
1331
+ function selectProperty(items, key) {
1332
+ return items.map((item) => item[key]);
1333
+ }
1334
+ /**
1335
+ * Type-safe property getter for a single item.
1336
+ * Returns undefined if item is null/undefined.
1337
+ *
1338
+ * @typeParam T - The data object type
1339
+ * @typeParam K - Key of T (constrained to actual keys)
1340
+ */
1341
+ function getProperty(item, key) {
1342
+ return item?.[key];
1343
+ }
1344
+ /**
1345
+ * Activity slug constants as a const object.
1346
+ * Derive the ActivitySlug type from this single source of truth.
1347
+ */
1348
+ const ACTIVITY_SLUGS = {
1349
+ abandonedCart: "abandoned_cart",
1350
+ announcements: "announcements",
1351
+ cartItemsAdded: "cart_items_added",
1352
+ commentReply: "comment_reply",
1353
+ directMessage: "direct_message",
1354
+ fantasyPoint: "fantasy_point",
1355
+ newLead: "new_lead",
1356
+ orderPlaced: "order_placed",
1357
+ pageViews: "page_views",
1358
+ pageViewsContact: "page_views_contact",
1359
+ tasks: "tasks",
1360
+ upcomingEvent: "upcoming_event",
1361
+ video: "video",
1362
+ videoComplete: "video_complete",
1363
+ videoCompleteContact: "video_complete_contact",
1364
+ videoContact: "video_contact",
1365
+ messageReceived: "message_received",
1366
+ messageSent: "message_sent",
1367
+ newCartItemsAdded: "new_cart_items_added",
1368
+ smartLinkClicked: "smart_link_clicked",
1369
+ reviewLeft: "review_left"
1370
+ };
1371
+ /** Type predicate to check if a string is a valid ActivitySlug. */
1372
+ function isActivitySlug(value) {
1373
+ return Object.values(ACTIVITY_SLUGS).includes(value);
1374
+ }
1375
+ //#endregion
1376
+ //#region src/hooks/demo-data/activities.ts
1377
+ const now$2 = /* @__PURE__ */ new Date();
1378
+ function hoursAgo$1(hours) {
1379
+ const d = new Date(now$2);
1380
+ d.setHours(d.getHours() - hours);
1381
+ return d.toISOString();
1382
+ }
1383
+ const DEMO_ACTIVITIES = [
1384
+ {
1385
+ id: 1,
1386
+ userName: "Sarah Johnson",
1387
+ avatarUrl: null,
1388
+ activityType: "New Order",
1389
+ targetName: "Wellness Starter Kit",
1390
+ timestamp: hoursAgo$1(1),
1391
+ slug: "order_placed"
1392
+ },
1393
+ {
1394
+ id: 2,
1395
+ userName: "Mike Chen",
1396
+ avatarUrl: null,
1397
+ activityType: "New Lead",
1398
+ targetName: "Referral from LinkedIn campaign",
1399
+ timestamp: hoursAgo$1(3),
1400
+ slug: "new_lead"
1401
+ },
1402
+ {
1403
+ id: 3,
1404
+ userName: "Emily Rodriguez",
1405
+ avatarUrl: null,
1406
+ activityType: "Page View",
1407
+ targetName: "Product Catalog",
1408
+ timestamp: hoursAgo$1(5),
1409
+ slug: "page_views"
1410
+ },
1411
+ {
1412
+ id: 4,
1413
+ userName: "David Park",
1414
+ avatarUrl: null,
1415
+ activityType: "Cart Items Added",
1416
+ targetName: "Premium Bundle (3 items)",
1417
+ timestamp: hoursAgo$1(8),
1418
+ slug: "cart_items_added"
1419
+ },
1420
+ {
1421
+ id: 5,
1422
+ userName: "Lisa Thompson",
1423
+ avatarUrl: null,
1424
+ activityType: "Video Watched",
1425
+ targetName: "Getting Started Tutorial",
1426
+ timestamp: hoursAgo$1(12),
1427
+ slug: "video_complete"
1428
+ },
1429
+ {
1430
+ id: 6,
1431
+ userName: "James Wilson",
1432
+ avatarUrl: null,
1433
+ activityType: "Message Received",
1434
+ targetName: "Question about shipping",
1435
+ timestamp: hoursAgo$1(18),
1436
+ slug: "message_received"
1437
+ },
1438
+ {
1439
+ id: 7,
1440
+ userName: "Rachel Kim",
1441
+ avatarUrl: null,
1442
+ activityType: "Smart Link Clicked",
1443
+ targetName: "Holiday Promo Link",
1444
+ timestamp: hoursAgo$1(24),
1445
+ slug: "smart_link_clicked"
1446
+ },
1447
+ {
1448
+ id: 8,
1449
+ userName: "Tom Martinez",
1450
+ avatarUrl: null,
1451
+ activityType: "Review Left",
1452
+ targetName: "Essential Oil Set - 5 stars",
1453
+ timestamp: hoursAgo$1(36),
1454
+ slug: "review_left"
1455
+ }
1456
+ ];
1457
+ //#endregion
1458
+ //#region src/hooks/use-activities.ts
1459
+ /**
1460
+ * Hook to fetch recent activities.
1461
+ * This is a stub implementation - override with your own data fetching logic.
1462
+ */
1463
+ function useActivities() {
1464
+ return {
1465
+ data: [...DEMO_ACTIVITIES],
1466
+ isLoading: false,
1467
+ isError: false
1468
+ };
1469
+ }
1470
+ //#endregion
1471
+ //#region src/hooks/demo-data/catchups.ts
1472
+ const DEMO_CATCHUPS = [
1473
+ {
1474
+ id: 1,
1475
+ suggestion_title: "Sarah Johnson hasn't ordered in 30 days"
1476
+ },
1477
+ {
1478
+ id: 2,
1479
+ suggestion_title: "Mike Chen's subscription is expiring soon"
1480
+ },
1481
+ {
1482
+ id: 3,
1483
+ suggestion_title: "Emily Rodriguez left a cart with 3 items"
1484
+ }
1485
+ ];
1486
+ //#endregion
1487
+ //#region src/hooks/use-catchups.ts
1488
+ /**
1489
+ * Hook to fetch catch up items.
1490
+ * This is a stub implementation - override with your own data fetching logic.
1491
+ */
1492
+ function useCatchUps() {
1493
+ return {
1494
+ data: [...DEMO_CATCHUPS],
1495
+ isLoading: false,
1496
+ isError: false
1497
+ };
1498
+ }
1499
+ //#endregion
1500
+ //#region src/hooks/demo-data/mysite.ts
1501
+ const DEMO_MYSITE = {
1502
+ url: "https://my-portal.example.com",
1503
+ views: 1243,
1504
+ leads: 37,
1505
+ userName: "Demo User"
1506
+ };
1507
+ //#endregion
1508
+ //#region src/hooks/use-mysite.ts
1509
+ /**
1510
+ * Hook to fetch MySite data.
1511
+ * This is a stub implementation - override with your own data fetching logic.
1512
+ */
1513
+ function useMySite() {
1514
+ return {
1515
+ data: DEMO_MYSITE,
1516
+ isLoading: false,
1517
+ isError: false
1518
+ };
1519
+ }
1520
+ //#endregion
1521
+ //#region src/hooks/demo-data/conversations.ts
1522
+ const now$1 = /* @__PURE__ */ new Date();
1523
+ function hoursAgo(hours) {
1524
+ const d = new Date(now$1);
1525
+ d.setHours(d.getHours() - hours);
1526
+ return d.toISOString();
1527
+ }
1528
+ function daysAgo$1(days) {
1529
+ const d = new Date(now$1);
1530
+ d.setDate(d.getDate() - days);
1531
+ return d.toISOString();
1532
+ }
1533
+ const DEMO_CONVERSATIONS = [
1534
+ {
1535
+ id: "conv-1",
1536
+ title: "Sarah Johnson",
1537
+ participants: [{
1538
+ id: "user-1",
1539
+ name: "You",
1540
+ email: "me@example.com",
1541
+ isOnline: true
1542
+ }, {
1543
+ id: "user-2",
1544
+ name: "Sarah Johnson",
1545
+ email: "sarah.johnson@example.com",
1546
+ isOnline: true
1547
+ }],
1548
+ lastMessage: {
1549
+ id: "msg-1-3",
1550
+ conversationId: "conv-1",
1551
+ senderId: "user-2",
1552
+ senderName: "Sarah Johnson",
1553
+ type: "text",
1554
+ content: "Sounds great! I'll place the order by end of day.",
1555
+ timestamp: hoursAgo(1),
1556
+ isRead: false
1557
+ },
1558
+ unreadCount: 1,
1559
+ status: "active",
1560
+ createdAt: daysAgo$1(30),
1561
+ updatedAt: hoursAgo(1)
1562
+ },
1563
+ {
1564
+ id: "conv-2",
1565
+ title: "Mike Chen",
1566
+ participants: [{
1567
+ id: "user-1",
1568
+ name: "You",
1569
+ email: "me@example.com",
1570
+ isOnline: true
1571
+ }, {
1572
+ id: "user-3",
1573
+ name: "Mike Chen",
1574
+ email: "mike.chen@acmecorp.com",
1575
+ isOnline: false
1576
+ }],
1577
+ lastMessage: {
1578
+ id: "msg-2-2",
1579
+ conversationId: "conv-2",
1580
+ senderId: "user-1",
1581
+ senderName: "You",
1582
+ type: "text",
1583
+ content: "I've attached the updated pricing sheet. Let me know if you have any questions!",
1584
+ timestamp: hoursAgo(5),
1585
+ isRead: true
1586
+ },
1587
+ unreadCount: 0,
1588
+ status: "active",
1589
+ createdAt: daysAgo$1(14),
1590
+ updatedAt: hoursAgo(5)
1591
+ },
1592
+ {
1593
+ id: "conv-3",
1594
+ title: "Team Updates",
1595
+ participants: [
1596
+ {
1597
+ id: "user-1",
1598
+ name: "You",
1599
+ email: "me@example.com",
1600
+ isOnline: true
1601
+ },
1602
+ {
1603
+ id: "user-4",
1604
+ name: "Emily Rodriguez",
1605
+ email: "emily.r@healthfirst.io"
1606
+ },
1607
+ {
1608
+ id: "user-5",
1609
+ name: "David Park",
1610
+ email: "david.park@email.com"
1611
+ }
1612
+ ],
1613
+ lastMessage: {
1614
+ id: "msg-3-4",
1615
+ conversationId: "conv-3",
1616
+ senderId: "user-4",
1617
+ senderName: "Emily Rodriguez",
1618
+ type: "text",
1619
+ content: "The new product samples arrived today. Will distribute to the team tomorrow.",
1620
+ timestamp: daysAgo$1(1),
1621
+ isRead: true
1622
+ },
1623
+ unreadCount: 0,
1624
+ status: "active",
1625
+ createdAt: daysAgo$1(60),
1626
+ updatedAt: daysAgo$1(1)
1627
+ }
1628
+ ];
1629
+ const DEMO_MESSAGES = [
1630
+ {
1631
+ id: "msg-1-1",
1632
+ conversationId: "conv-1",
1633
+ senderId: "user-1",
1634
+ senderName: "You",
1635
+ type: "text",
1636
+ content: "Hi Sarah! I wanted to follow up on the Wellness Starter Kit. We have a special promotion running this month.",
1637
+ timestamp: hoursAgo(3),
1638
+ isRead: true
1639
+ },
1640
+ {
1641
+ id: "msg-1-2",
1642
+ conversationId: "conv-1",
1643
+ senderId: "user-2",
1644
+ senderName: "Sarah Johnson",
1645
+ type: "text",
1646
+ content: "That's perfect timing! I was just thinking about reordering. What's the promo?",
1647
+ timestamp: hoursAgo(2),
1648
+ isRead: true
1649
+ },
1650
+ {
1651
+ id: "msg-1-3",
1652
+ conversationId: "conv-1",
1653
+ senderId: "user-2",
1654
+ senderName: "Sarah Johnson",
1655
+ type: "text",
1656
+ content: "Sounds great! I'll place the order by end of day.",
1657
+ timestamp: hoursAgo(1),
1658
+ isRead: false
1659
+ }
1660
+ ];
1661
+ //#endregion
1662
+ //#region src/hooks/use-conversations.ts
1663
+ /**
1664
+ * Hook to fetch all conversations.
1665
+ * This is a stub implementation - override with your own data fetching logic.
1666
+ *
1667
+ * @returns UseConversationsResult with empty data array
1668
+ *
1669
+ * @example
1670
+ * ```tsx
1671
+ * const { data: conversations, isLoading, isError } = useConversations();
1672
+ *
1673
+ * if (isLoading) return <Loading />;
1674
+ * if (isError) return <Error />;
1675
+ *
1676
+ * return conversations.map(conv => <ConversationItem key={conv.id} {...conv} />);
1677
+ * ```
1678
+ */
1679
+ function useConversations() {
1680
+ return {
1681
+ data: [...DEMO_CONVERSATIONS],
1682
+ isLoading: false,
1683
+ isError: false
1684
+ };
1685
+ }
1686
+ /**
1687
+ * Hook to fetch messages for a specific conversation.
1688
+ * This is a stub implementation - override with your own data fetching logic.
1689
+ *
1690
+ * @param conversationId - The ID of the conversation to fetch messages for
1691
+ * @returns UseConversationMessagesResult with empty data array
1692
+ *
1693
+ * @example
1694
+ * ```tsx
1695
+ * const { data: messages, isLoading, isError } = useConversationMessages(conversationId);
1696
+ *
1697
+ * if (isLoading) return <Loading />;
1698
+ * if (isError) return <Error />;
1699
+ *
1700
+ * return messages.map(msg => <MessageBubble key={msg.id} {...msg} />);
1701
+ * ```
1702
+ */
1703
+ function useConversationMessages(_conversationId) {
1704
+ return {
1705
+ data: [...DEMO_MESSAGES],
1706
+ isLoading: false,
1707
+ isError: false
1708
+ };
1709
+ }
1710
+ //#endregion
1711
+ //#region src/types/screen-types.ts
1712
+ /**
1713
+ * Contact status constant - single source of truth.
1714
+ */
1715
+ const CONTACT_STATUSES = {
1716
+ active: "active",
1717
+ inactive: "inactive",
1718
+ lead: "lead",
1719
+ prospect: "prospect"
1720
+ };
1721
+ //#endregion
1722
+ //#region src/hooks/demo-data/contacts.ts
1723
+ const now = /* @__PURE__ */ new Date();
1724
+ function daysAgo(days) {
1725
+ const d = new Date(now);
1726
+ d.setDate(d.getDate() - days);
1727
+ return d.toISOString();
1728
+ }
1729
+ const DEMO_CONTACTS = [
1730
+ {
1731
+ id: "contact-1",
1732
+ firstName: "Sarah",
1733
+ lastName: "Johnson",
1734
+ email: "sarah.johnson@example.com",
1735
+ phone: "+1 (555) 123-4567",
1736
+ company: "Wellness Works Inc.",
1737
+ jobTitle: "Purchasing Manager",
1738
+ status: "active",
1739
+ type: "individual",
1740
+ tags: ["VIP", "Repeat Buyer"],
1741
+ createdAt: daysAgo(90),
1742
+ updatedAt: daysAgo(2)
1743
+ },
1744
+ {
1745
+ id: "contact-2",
1746
+ firstName: "Mike",
1747
+ lastName: "Chen",
1748
+ email: "mike.chen@acmecorp.com",
1749
+ phone: "+1 (555) 234-5678",
1750
+ company: "Acme Corp",
1751
+ jobTitle: "Director of Operations",
1752
+ status: "active",
1753
+ type: "individual",
1754
+ tags: ["Enterprise"],
1755
+ createdAt: daysAgo(60),
1756
+ updatedAt: daysAgo(5)
1757
+ },
1758
+ {
1759
+ id: "contact-3",
1760
+ firstName: "Emily",
1761
+ lastName: "Rodriguez",
1762
+ email: "emily.r@healthfirst.io",
1763
+ company: "HealthFirst",
1764
+ status: "lead",
1765
+ type: "individual",
1766
+ notes: "Interested in bulk pricing for team orders",
1767
+ createdAt: daysAgo(7),
1768
+ updatedAt: daysAgo(1)
1769
+ },
1770
+ {
1771
+ id: "contact-4",
1772
+ firstName: "David",
1773
+ lastName: "Park",
1774
+ email: "david.park@email.com",
1775
+ phone: "+1 (555) 345-6789",
1776
+ status: "prospect",
1777
+ type: "individual",
1778
+ tags: ["Referral"],
1779
+ createdAt: daysAgo(14),
1780
+ updatedAt: daysAgo(10)
1781
+ },
1782
+ {
1783
+ id: "contact-5",
1784
+ firstName: "Lisa",
1785
+ lastName: "Thompson",
1786
+ email: "lisa.t@globalfit.com",
1787
+ company: "Global Fitness",
1788
+ jobTitle: "CEO",
1789
+ status: "active",
1790
+ type: "individual",
1791
+ address: {
1792
+ city: "Los Angeles",
1793
+ state: "CA",
1794
+ country: "US"
1795
+ },
1796
+ createdAt: daysAgo(120),
1797
+ updatedAt: daysAgo(15)
1798
+ },
1799
+ {
1800
+ id: "contact-6",
1801
+ firstName: "James",
1802
+ lastName: "Wilson",
1803
+ email: "jwilson@retired.net",
1804
+ status: "inactive",
1805
+ type: "individual",
1806
+ notes: "Moved out of area, no longer purchasing",
1807
+ createdAt: daysAgo(200),
1808
+ updatedAt: daysAgo(45)
1809
+ }
1810
+ ];
1811
+ const DEMO_CONTACT = DEMO_CONTACTS[0];
1812
+ //#endregion
1813
+ //#region src/hooks/use-contacts.ts
1814
+ /**
1815
+ * Type predicate to check if a status string is a valid ContactStatus.
1816
+ * Enables runtime validation with type narrowing.
1817
+ */
1818
+ function isContactStatus(value) {
1819
+ return Object.values(CONTACT_STATUSES).includes(value);
1820
+ }
1821
+ /**
1822
+ * Hook to fetch a list of contacts with optional filtering and pagination.
1823
+ * This is a stub implementation - override with your own data fetching logic.
1824
+ *
1825
+ * @param params - Optional parameters for filtering and pagination
1826
+ * @returns Object containing contacts data, loading state, error state, and total count
1827
+ *
1828
+ * @example
1829
+ * ```tsx
1830
+ * const { data: contacts, isLoading, totalCount } = useContacts({
1831
+ * search: 'john',
1832
+ * status: 'active',
1833
+ * limit: 20
1834
+ * });
1835
+ * ```
1836
+ */
1837
+ function useContacts(_params) {
1838
+ return {
1839
+ data: [...DEMO_CONTACTS],
1840
+ isLoading: false,
1841
+ isError: false,
1842
+ totalCount: DEMO_CONTACTS.length
1843
+ };
1844
+ }
1845
+ /**
1846
+ * Hook to fetch a single contact by ID.
1847
+ * This is a stub implementation - override with your own data fetching logic.
1848
+ *
1849
+ * @param contactId - The unique identifier of the contact to fetch
1850
+ * @returns Object containing contact data, loading state, and error state
1851
+ *
1852
+ * @example
1853
+ * ```tsx
1854
+ * const { data: contact, isLoading, isError } = useContact('contact-123');
1855
+ * ```
1856
+ */
1857
+ function useContact(_contactId) {
1858
+ return {
1859
+ data: DEMO_CONTACT,
1860
+ isLoading: false,
1861
+ isError: false
1862
+ };
1863
+ }
1864
+ //#endregion
1865
+ //#region src/components/AuthError.tsx
1866
+ /**
1867
+ * AuthError - Default Authentication Error Component
1868
+ *
1869
+ * Displayed when authentication fails. Can be customized or replaced
1870
+ * via the RequireAuth component's errorComponent prop.
1871
+ */
1872
+ /**
1873
+ * Default authentication error component.
1874
+ *
1875
+ * Displays a simple error message when authentication fails.
1876
+ * Can be customized via props or replaced entirely in RequireAuth.
1877
+ *
1878
+ * @example
1879
+ * ```tsx
1880
+ * // Use with default message
1881
+ * <AuthError />
1882
+ *
1883
+ * // Use with custom message
1884
+ * <AuthError message="Session expired. Please log in again." />
1885
+ *
1886
+ * // Use with custom content
1887
+ * <AuthError>
1888
+ * <CustomLoginButton />
1889
+ * </AuthError>
1890
+ * ```
1891
+ */
1892
+ function AuthError({ message = "You need to be authenticated to view this content.", title = "Authentication Required", children }) {
1893
+ return /* @__PURE__ */ jsx("div", {
1894
+ style: {
1895
+ display: "flex",
1896
+ flexDirection: "column",
1897
+ alignItems: "center",
1898
+ justifyContent: "center",
1899
+ minHeight: "100vh",
1900
+ padding: "2rem",
1901
+ fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
1902
+ backgroundColor: "#f9fafb",
1903
+ color: "#111827"
1904
+ },
1905
+ children: /* @__PURE__ */ jsxs("div", {
1906
+ style: {
1907
+ maxWidth: "400px",
1908
+ textAlign: "center",
1909
+ padding: "2rem",
1910
+ backgroundColor: "#ffffff",
1911
+ borderRadius: "0.75rem",
1912
+ boxShadow: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)"
1913
+ },
1914
+ children: [
1915
+ /* @__PURE__ */ jsx("div", {
1916
+ style: {
1917
+ width: "64px",
1918
+ height: "64px",
1919
+ margin: "0 auto 1.5rem",
1920
+ backgroundColor: "#fee2e2",
1921
+ borderRadius: "50%",
1922
+ display: "flex",
1923
+ alignItems: "center",
1924
+ justifyContent: "center"
1925
+ },
1926
+ children: /* @__PURE__ */ jsxs("svg", {
1927
+ width: "32",
1928
+ height: "32",
1929
+ viewBox: "0 0 24 24",
1930
+ fill: "none",
1931
+ stroke: "#dc2626",
1932
+ strokeWidth: "2",
1933
+ strokeLinecap: "round",
1934
+ strokeLinejoin: "round",
1935
+ children: [/* @__PURE__ */ jsx("rect", {
1936
+ x: "3",
1937
+ y: "11",
1938
+ width: "18",
1939
+ height: "11",
1940
+ rx: "2",
1941
+ ry: "2"
1942
+ }), /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })]
1943
+ })
1944
+ }),
1945
+ /* @__PURE__ */ jsx("h1", {
1946
+ style: {
1947
+ fontSize: "1.5rem",
1948
+ fontWeight: "600",
1949
+ marginBottom: "0.75rem",
1950
+ color: "#111827"
1951
+ },
1952
+ children: title
1953
+ }),
1954
+ /* @__PURE__ */ jsx("p", {
1955
+ style: {
1956
+ fontSize: "1rem",
1957
+ color: "#6b7280",
1958
+ marginBottom: children ? "1.5rem" : "0",
1959
+ lineHeight: "1.5"
1960
+ },
1961
+ children: message
1962
+ }),
1963
+ children
1964
+ ]
1965
+ })
1966
+ });
1967
+ }
1968
+ const SPIN_STYLE_ID$1 = "fluid-auth-loading-spin";
1969
+ /**
1970
+ * Inject the spin keyframes style once into the document head.
1971
+ */
1972
+ function ensureSpinStyle$1() {
1973
+ if (typeof document === "undefined") return;
1974
+ if (document.getElementById(SPIN_STYLE_ID$1)) return;
1975
+ const style = document.createElement("style");
1976
+ style.id = SPIN_STYLE_ID$1;
1977
+ style.textContent = `@keyframes spin { to { transform: rotate(360deg); } }`;
1978
+ document.head.appendChild(style);
1979
+ }
1980
+ /**
1981
+ * Simple loading spinner component for auth loading state.
1982
+ */
1983
+ function AuthLoading() {
1984
+ useEffect(() => {
1985
+ ensureSpinStyle$1();
1986
+ }, []);
1987
+ return /* @__PURE__ */ jsxs("div", {
1988
+ style: {
1989
+ display: "flex",
1990
+ flexDirection: "column",
1991
+ alignItems: "center",
1992
+ justifyContent: "center",
1993
+ minHeight: "100vh",
1994
+ fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
1995
+ backgroundColor: "#f9fafb"
1996
+ },
1997
+ children: [/* @__PURE__ */ jsx("div", { style: {
1998
+ width: "40px",
1999
+ height: "40px",
2000
+ border: "3px solid #e5e7eb",
2001
+ borderTopColor: "#3b82f6",
2002
+ borderRadius: "50%",
2003
+ animation: "spin 1s linear infinite"
2004
+ } }), /* @__PURE__ */ jsx("p", {
2005
+ style: {
2006
+ marginTop: "1rem",
2007
+ color: "#6b7280",
2008
+ fontSize: "0.875rem"
2009
+ },
2010
+ children: "Authenticating..."
2011
+ })]
2012
+ });
2013
+ }
2014
+ //#endregion
2015
+ //#region src/components/RequireAuth.tsx
2016
+ /**
2017
+ * Component that protects content requiring authentication.
2018
+ *
2019
+ * **Important:** This provides UX-level protection only. It prevents
2020
+ * unauthenticated users from seeing the UI, but the real security
2021
+ * boundary is the server-side API. Client-side auth can always be
2022
+ * bypassed by modifying browser state.
2023
+ *
2024
+ * For defense-in-depth, configure `jwksUrl` in `FluidAuthConfig`
2025
+ * to enable client-side JWT signature verification.
2026
+ *
2027
+ * Shows different states based on auth status:
2028
+ * - Loading: Shows fallback (spinner by default)
2029
+ * - Not authenticated: Shows errorComponent (AuthError by default)
2030
+ * - Authenticated: Shows children
2031
+ *
2032
+ * @example
2033
+ * ```tsx
2034
+ * // Basic usage - shows default loading/error states
2035
+ * <RequireAuth>
2036
+ * <ProtectedContent />
2037
+ * </RequireAuth>
2038
+ *
2039
+ * // Custom loading state
2040
+ * <RequireAuth fallback={<CustomSpinner />}>
2041
+ * <ProtectedContent />
2042
+ * </RequireAuth>
2043
+ *
2044
+ * // Custom error component
2045
+ * <RequireAuth errorComponent={<LoginPrompt />}>
2046
+ * <ProtectedContent />
2047
+ * </RequireAuth>
2048
+ *
2049
+ * // Both custom
2050
+ * <RequireAuth
2051
+ * fallback={<SkeletonLoader />}
2052
+ * errorComponent={<RedirectToLogin />}
2053
+ * >
2054
+ * <Dashboard />
2055
+ * </RequireAuth>
2056
+ * ```
2057
+ */
2058
+ function RequireAuth({ children, fallback = /* @__PURE__ */ jsx(AuthLoading, {}), errorComponent = /* @__PURE__ */ jsx(AuthError, {}) }) {
2059
+ const { isAuthenticated, isLoading, error } = useFluidAuth();
2060
+ if (isLoading) return /* @__PURE__ */ jsx(Fragment, { children: fallback });
2061
+ if (!isAuthenticated || error) return /* @__PURE__ */ jsx(Fragment, { children: errorComponent });
2062
+ return /* @__PURE__ */ jsx(Fragment, { children });
2063
+ }
2064
+ //#endregion
2065
+ //#region src/screens/index.ts
2066
+ const screenPropertySchemas = {
2067
+ MessagingScreen: () => import("./MessagingScreen-xO9YudMx.mjs").then((n) => n.n).then((m) => m.messagingScreenPropertySchema),
2068
+ ContactsScreen: () => import("./ContactsScreen-CB6l0Lf1.mjs").then((n) => n.n).then((m) => m.contactsScreenPropertySchema),
2069
+ OrdersScreen: () => import("./OrdersScreen-DB1v051q.mjs").then((n) => n.n).then((m) => m.ordersScreenPropertySchema),
2070
+ CustomersScreen: () => import("./CustomersScreen-BEar6Leg.mjs").then((n) => n.n).then((m) => m.customersScreenPropertySchema),
2071
+ ProductsScreen: () => import("./ProductsScreen-nVDsY6kf.mjs").then((n) => n.n).then((m) => m.productsScreenPropertySchema)
2072
+ };
2073
+ /**
2074
+ * Core page template IDs
2075
+ */
2076
+ const CORE_PAGE_IDS = {
2077
+ MESSAGING: "core-messaging",
2078
+ CONTACTS: "core-contacts",
2079
+ ORDERS: "core-orders",
2080
+ CUSTOMERS: "core-customers",
2081
+ PRODUCTS: "core-products"
2082
+ };
2083
+ /**
2084
+ * Register core page templates.
2085
+ * These are automatically registered when the SDK is imported.
2086
+ */
2087
+ function registerCorePageTemplates() {
2088
+ PageTemplateRegistry.register({
2089
+ id: CORE_PAGE_IDS.MESSAGING,
2090
+ slug: "messaging",
2091
+ name: "Messaging",
2092
+ description: "Messaging interface provided by Fluid Commerce",
2093
+ category: PAGE_CATEGORIES.COMMUNICATION,
2094
+ tags: [
2095
+ "messaging",
2096
+ "chat",
2097
+ "conversations",
2098
+ "communication"
2099
+ ],
2100
+ version: "1.0.0",
2101
+ isCore: true,
2102
+ component_tree: [{
2103
+ type: "MessagingScreen",
2104
+ id: "messaging-screen-root",
2105
+ props: {}
2106
+ }],
2107
+ defaultProps: {}
2108
+ });
2109
+ PageTemplateRegistry.register({
2110
+ id: CORE_PAGE_IDS.CONTACTS,
2111
+ slug: "contacts",
2112
+ name: "Contacts",
2113
+ description: "Contact management provided by Fluid Commerce",
2114
+ category: PAGE_CATEGORIES.CORE,
2115
+ tags: [
2116
+ "contacts",
2117
+ "people",
2118
+ "address-book"
2119
+ ],
2120
+ version: "1.0.0",
2121
+ isCore: true,
2122
+ component_tree: [{
2123
+ type: "ContactsScreen",
2124
+ id: "contacts-screen-root",
2125
+ props: {}
2126
+ }],
2127
+ defaultProps: {}
2128
+ });
2129
+ PageTemplateRegistry.register({
2130
+ id: CORE_PAGE_IDS.ORDERS,
2131
+ slug: "orders",
2132
+ name: "Orders",
2133
+ description: "Order management provided by Fluid Commerce",
2134
+ category: PAGE_CATEGORIES.CORE,
2135
+ tags: [
2136
+ "orders",
2137
+ "commerce",
2138
+ "sales"
2139
+ ],
2140
+ version: "1.0.0",
2141
+ isCore: true,
2142
+ component_tree: [{
2143
+ type: "OrdersScreen",
2144
+ id: "orders-screen-root",
2145
+ props: {}
2146
+ }],
2147
+ defaultProps: {}
2148
+ });
2149
+ PageTemplateRegistry.register({
2150
+ id: CORE_PAGE_IDS.CUSTOMERS,
2151
+ slug: "customers",
2152
+ name: "Customers",
2153
+ description: "Customer management provided by Fluid Commerce",
2154
+ category: PAGE_CATEGORIES.CORE,
2155
+ tags: [
2156
+ "customers",
2157
+ "people",
2158
+ "commerce"
2159
+ ],
2160
+ version: "1.0.0",
2161
+ isCore: true,
2162
+ component_tree: [{
2163
+ type: "CustomersScreen",
2164
+ id: "customers-screen-root",
2165
+ props: {}
2166
+ }],
2167
+ defaultProps: {}
2168
+ });
2169
+ PageTemplateRegistry.register({
2170
+ id: CORE_PAGE_IDS.PRODUCTS,
2171
+ slug: "products",
2172
+ name: "Products",
2173
+ description: "Product catalog provided by Fluid Commerce",
2174
+ category: PAGE_CATEGORIES.CORE,
2175
+ tags: [
2176
+ "products",
2177
+ "catalog",
2178
+ "commerce"
2179
+ ],
2180
+ version: "1.0.0",
2181
+ isCore: true,
2182
+ component_tree: [{
2183
+ type: "ProductsScreen",
2184
+ id: "products-screen-root",
2185
+ props: {}
2186
+ }],
2187
+ defaultProps: {}
2188
+ });
2189
+ }
2190
+ registerCorePageTemplates();
2191
+ //#endregion
2192
+ //#region src/shell/SdkBottomNav.tsx
2193
+ function isInSection(item, currentSlug) {
2194
+ const normalized = normalizeSlug(currentSlug);
2195
+ if (normalizeSlug(item.slug) === normalized) return true;
2196
+ return item.children?.some((child) => normalizeSlug(child.slug) === normalized) ?? false;
2197
+ }
2198
+ function getNavTarget(item) {
2199
+ if (item.slug && (!item.children || item.children.length === 0)) return normalizeSlug(item.slug);
2200
+ if (item.children && item.children.length > 0) {
2201
+ const firstChild = item.children[0];
2202
+ if (firstChild?.slug) return normalizeSlug(firstChild.slug);
2203
+ }
2204
+ return null;
2205
+ }
2206
+ function SdkBottomNav({ navItems, currentSlug, onNavigate, maxVisibleItems = 5 }) {
2207
+ const { isMobile, useBottomNav } = useSidebar();
2208
+ const [moreOpen, setMoreOpen] = React$1.useState(false);
2209
+ if (!isMobile || !useBottomNav) return null;
2210
+ const navigableItems = navItems.filter((item) => !item.parent_id).filter((item) => item.slug || item.children && item.children.length > 0);
2211
+ const hasOverflow = navigableItems.length > maxVisibleItems;
2212
+ const visibleItems = hasOverflow ? navigableItems.slice(0, maxVisibleItems - 1) : navigableItems;
2213
+ const overflowItems = hasOverflow ? navigableItems.slice(maxVisibleItems - 1) : [];
2214
+ const handleItemClick = (item) => {
2215
+ const target = getNavTarget(item);
2216
+ if (target) onNavigate(target);
2217
+ setMoreOpen(false);
2218
+ };
2219
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("nav", {
2220
+ className: "border-border bg-sidebar fixed right-0 bottom-0 left-0 z-40 flex h-16 items-end justify-around border-t pb-[env(safe-area-inset-bottom)]",
2221
+ children: [visibleItems.map((item) => {
2222
+ return /* @__PURE__ */ jsxs("button", {
2223
+ type: "button",
2224
+ onClick: () => handleItemClick(item),
2225
+ className: `flex flex-1 flex-col items-center justify-center gap-0.5 py-2 text-[10px] font-medium transition-colors ${isInSection(item, currentSlug) ? "text-primary" : "text-muted-foreground"}`,
2226
+ children: [item.icon ? /* @__PURE__ */ jsx(RepIcon, {
2227
+ name: item.icon,
2228
+ className: "size-5"
2229
+ }) : /* @__PURE__ */ jsx("span", { className: "size-5" }), /* @__PURE__ */ jsx("span", {
2230
+ className: "max-w-[72px] truncate",
2231
+ children: item.label
2232
+ })]
2233
+ }, item.id ?? item.slug ?? item.label);
2234
+ }), hasOverflow && /* @__PURE__ */ jsxs("button", {
2235
+ type: "button",
2236
+ onClick: () => setMoreOpen(true),
2237
+ className: `flex flex-1 flex-col items-center justify-center gap-0.5 py-2 text-[10px] font-medium transition-colors ${overflowItems.some((item) => isInSection(item, currentSlug)) ? "text-primary" : "text-muted-foreground"}`,
2238
+ children: [/* @__PURE__ */ jsx(FontAwesomeIcon, {
2239
+ icon: faEllipsis,
2240
+ className: "size-5"
2241
+ }), /* @__PURE__ */ jsx("span", { children: "More" })]
2242
+ })]
2243
+ }), moreOpen && /* @__PURE__ */ jsxs("div", {
2244
+ className: "fixed inset-0 z-50 flex flex-col",
2245
+ children: [/* @__PURE__ */ jsx("div", {
2246
+ className: "flex-1 bg-black/50",
2247
+ onClick: () => setMoreOpen(false),
2248
+ "aria-hidden": "true"
2249
+ }), /* @__PURE__ */ jsxs("div", {
2250
+ className: "bg-sidebar rounded-t-2xl pb-[env(safe-area-inset-bottom)]",
2251
+ children: [/* @__PURE__ */ jsxs("div", {
2252
+ className: "flex items-center justify-between px-4 pt-4 pb-2",
2253
+ children: [/* @__PURE__ */ jsx("span", {
2254
+ className: "text-foreground text-sm font-semibold",
2255
+ children: "More"
2256
+ }), /* @__PURE__ */ jsx("button", {
2257
+ type: "button",
2258
+ onClick: () => setMoreOpen(false),
2259
+ className: "text-muted-foreground hover:bg-sidebar-accent flex items-center justify-center rounded-full p-1",
2260
+ "aria-label": "Close",
2261
+ children: /* @__PURE__ */ jsx(FontAwesomeIcon, {
2262
+ icon: faXmark,
2263
+ className: "size-5"
2264
+ })
2265
+ })]
2266
+ }), /* @__PURE__ */ jsx("div", {
2267
+ className: "px-2 pb-4",
2268
+ children: overflowItems.map((item) => {
2269
+ return /* @__PURE__ */ jsxs("button", {
2270
+ type: "button",
2271
+ onClick: () => handleItemClick(item),
2272
+ className: `flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors ${isInSection(item, currentSlug) ? "bg-sidebar-primary text-sidebar-primary-foreground" : "text-sidebar-foreground hover:bg-sidebar-accent"}`,
2273
+ children: [item.icon && /* @__PURE__ */ jsx(RepIcon, {
2274
+ name: item.icon,
2275
+ className: "size-5"
2276
+ }), /* @__PURE__ */ jsx("span", { children: item.label })]
2277
+ }, item.id ?? item.slug ?? item.label);
2278
+ })
2279
+ })]
2280
+ })]
2281
+ })] });
2282
+ }
2283
+ //#endregion
2284
+ //#region src/shell/AppShellLoading.tsx
2285
+ const SPIN_STYLE_ID = "fluid-app-shell-loading-spin";
2286
+ /**
2287
+ * Inject the spin keyframes style once into the document head.
2288
+ */
2289
+ function ensureSpinStyle() {
2290
+ if (typeof document === "undefined") return;
2291
+ if (document.getElementById(SPIN_STYLE_ID)) return;
2292
+ const style = document.createElement("style");
2293
+ style.id = SPIN_STYLE_ID;
2294
+ style.textContent = `@keyframes spin { to { transform: rotate(360deg); } }`;
2295
+ document.head.appendChild(style);
2296
+ }
2297
+ /**
2298
+ * Full-page loading spinner shown while AppShell is fetching app data
2299
+ * for the first time. Uses inline styles (not Tailwind) since the theme
2300
+ * hasn't loaded yet.
2301
+ */
2302
+ function AppShellLoading() {
2303
+ useEffect(() => {
2304
+ ensureSpinStyle();
2305
+ }, []);
2306
+ return /* @__PURE__ */ jsxs("div", {
2307
+ style: {
2308
+ display: "flex",
2309
+ flexDirection: "column",
2310
+ alignItems: "center",
2311
+ justifyContent: "center",
2312
+ minHeight: "100vh",
2313
+ fontFamily: "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
2314
+ backgroundColor: "#f9fafb"
2315
+ },
2316
+ children: [/* @__PURE__ */ jsx("div", { style: {
2317
+ width: "40px",
2318
+ height: "40px",
2319
+ border: "3px solid #e5e7eb",
2320
+ borderTopColor: "#3b82f6",
2321
+ borderRadius: "50%",
2322
+ animation: "spin 1s linear infinite"
2323
+ } }), /* @__PURE__ */ jsx("p", {
2324
+ style: {
2325
+ marginTop: "1rem",
2326
+ color: "#6b7280",
2327
+ fontSize: "0.875rem"
2328
+ },
2329
+ children: "Loading..."
2330
+ })]
2331
+ });
2332
+ }
2333
+ //#endregion
2334
+ //#region src/shell/slug-utils.ts
2335
+ /**
2336
+ * Extract all slugs from a navigation tree, sorted by segment count descending.
2337
+ * Longest slugs first enables greedy prefix matching (e.g. "share/playlists"
2338
+ * is checked before "share").
2339
+ */
2340
+ function collectNavSlugs(items) {
2341
+ const slugs = [];
2342
+ for (const item of items) {
2343
+ if (item.slug) slugs.push(normalizeSlug(item.slug));
2344
+ for (const child of item.children ?? []) if (child.slug) slugs.push(normalizeSlug(child.slug));
2345
+ }
2346
+ return [...slugs].sort((a, b) => b.split("/").length - a.split("/").length);
2347
+ }
2348
+ /**
2349
+ * Find the longest registered nav slug that is a prefix of `fullSlug`.
2350
+ * Uses segment-boundary checking to prevent "shop" from matching "shopping".
2351
+ */
2352
+ function matchSlugPrefix(fullSlug, navSlugs) {
2353
+ const normalized = normalizeSlug(fullSlug);
2354
+ if (!normalized) return void 0;
2355
+ for (const navSlug of navSlugs) {
2356
+ if (normalized === navSlug) return {
2357
+ matchedSlug: navSlug,
2358
+ rest: ""
2359
+ };
2360
+ if (normalized.startsWith(navSlug + "/")) return {
2361
+ matchedSlug: navSlug,
2362
+ rest: normalized.slice(navSlug.length + 1)
2363
+ };
2364
+ }
2365
+ }
2366
+ /**
2367
+ * Extract the slug portion from a full pathname by stripping the basePath prefix.
2368
+ * Returns an empty string when the pathname equals the basePath exactly.
2369
+ *
2370
+ * Examples:
2371
+ * extractSlugFromPathname("/contacts/123", "/") → "contacts/123"
2372
+ * extractSlugFromPathname("/portal/contacts", "/portal") → "contacts"
2373
+ * extractSlugFromPathname("/portal", "/portal") → ""
2374
+ * extractSlugFromPathname("/", "/") → ""
2375
+ */
2376
+ function extractSlugFromPathname(pathname, basePath) {
2377
+ if (basePath === "/") return pathname.replace(/^\//, "");
2378
+ if (pathname.startsWith(basePath)) return pathname.slice(basePath.length).replace(/^\//, "");
2379
+ return pathname.replace(/^\//, "");
2380
+ }
2381
+ function isSlugInSection(item, currentSlug, navSlugs) {
2382
+ const baseSlug = matchSlugPrefix(currentSlug, navSlugs)?.matchedSlug ?? normalizeSlug(currentSlug);
2383
+ if (normalizeSlug(item.slug) === baseSlug) return true;
2384
+ return item.children?.some((child) => normalizeSlug(child.slug) === baseSlug) ?? false;
2385
+ }
2386
+ //#endregion
2387
+ //#region src/shell/SdkNavigation.tsx
2388
+ function SdkNavigation({ navItems, currentSlug, onNavigate, navSlugs }) {
2389
+ return /* @__PURE__ */ jsx(SidebarMenu, { children: navItems.map((item, index) => {
2390
+ if (!item.slug && !item.label) return null;
2391
+ if (!item.slug) return /* @__PURE__ */ jsx(SidebarGroupLabel, { children: item.label }, item.id ?? `section-${index}`);
2392
+ const itemSlug = normalizeSlug(item.slug);
2393
+ return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
2394
+ isActive: isSlugInSection(item, currentSlug, navSlugs),
2395
+ onClick: () => onNavigate(itemSlug),
2396
+ children: [item.icon && /* @__PURE__ */ jsx(RepIcon, { name: item.icon }), /* @__PURE__ */ jsx("span", {
2397
+ className: "truncate",
2398
+ children: item.label
2399
+ })]
2400
+ }) }, item.id ?? itemSlug);
2401
+ }) });
2402
+ }
2403
+ //#endregion
2404
+ //#region src/shell/QuickLinksDropdown.tsx
2405
+ function QuickLinksDropdown({ onNavigate }) {
2406
+ const [open, setOpen] = useState(false);
2407
+ const panelRef = useRef(null);
2408
+ const buttonRef = useRef(null);
2409
+ const close = useCallback(() => setOpen(false), []);
2410
+ useEffect(() => {
2411
+ if (!open) return;
2412
+ const handleMouseDown = (e) => {
2413
+ if (panelRef.current && !panelRef.current.contains(e.target) && buttonRef.current && !buttonRef.current.contains(e.target)) close();
2414
+ };
2415
+ const handleKeyDown = (e) => {
2416
+ if (e.key === "Escape") close();
2417
+ };
2418
+ document.addEventListener("mousedown", handleMouseDown);
2419
+ document.addEventListener("keydown", handleKeyDown);
2420
+ return () => {
2421
+ document.removeEventListener("mousedown", handleMouseDown);
2422
+ document.removeEventListener("keydown", handleKeyDown);
2423
+ };
2424
+ }, [open, close]);
2425
+ const sections = getSystemNavigationBySection();
2426
+ const handleItemClick = (slug) => {
2427
+ onNavigate(slug);
2428
+ close();
2429
+ };
2430
+ return /* @__PURE__ */ jsxs("div", {
2431
+ className: "relative",
2432
+ children: [/* @__PURE__ */ jsx("button", {
2433
+ ref: buttonRef,
2434
+ type: "button",
2435
+ onClick: () => setOpen((prev) => !prev),
2436
+ className: "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex shrink-0 items-center justify-center rounded-md p-2",
2437
+ "aria-label": "Quick links",
2438
+ "aria-expanded": open,
2439
+ children: /* @__PURE__ */ jsx(FontAwesomeIcon, {
2440
+ icon: faTableCellsLarge,
2441
+ className: "h-4 w-4"
2442
+ })
2443
+ }), open && /* @__PURE__ */ jsx("div", {
2444
+ ref: panelRef,
2445
+ className: "border-border bg-background absolute top-full right-0 z-50 mt-1 w-[700px] max-w-[90vw] overflow-hidden rounded-lg border shadow-lg",
2446
+ children: /* @__PURE__ */ jsxs("div", {
2447
+ className: "flex flex-col",
2448
+ children: [/* @__PURE__ */ jsxs("div", {
2449
+ className: "bg-background flex items-center gap-2 border-b p-4",
2450
+ children: [/* @__PURE__ */ jsx(FontAwesomeIcon, {
2451
+ icon: faTableCellsLarge,
2452
+ className: "text-muted-foreground h-5 w-5"
2453
+ }), /* @__PURE__ */ jsx("h3", {
2454
+ className: "text-foreground text-sm font-semibold",
2455
+ children: "Shortcuts"
2456
+ })]
2457
+ }), /* @__PURE__ */ jsx("div", {
2458
+ className: "bg-muted space-y-6 p-6",
2459
+ children: Object.entries(sections).map(([sectionName, items]) => /* @__PURE__ */ jsxs("div", {
2460
+ className: "space-y-3",
2461
+ children: [/* @__PURE__ */ jsx("h3", {
2462
+ className: "text-foreground text-sm font-semibold",
2463
+ children: sectionName
2464
+ }), /* @__PURE__ */ jsx("div", {
2465
+ className: "grid grid-cols-4 gap-2",
2466
+ children: items.map((item) => /* @__PURE__ */ jsxs("button", {
2467
+ type: "button",
2468
+ onClick: () => handleItemClick(item.slug),
2469
+ className: "text-sidebar-foreground hover:bg-sidebar-primary hover:text-sidebar-primary-foreground flex items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors",
2470
+ children: [item.icon && /* @__PURE__ */ jsx(RepIcon, {
2471
+ name: item.icon,
2472
+ className: "text-muted-foreground h-4 w-4 shrink-0"
2473
+ }), /* @__PURE__ */ jsx("span", {
2474
+ className: "truncate",
2475
+ children: item.label
2476
+ })]
2477
+ }, item.slug))
2478
+ })]
2479
+ }, sectionName))
2480
+ })]
2481
+ })
2482
+ })]
2483
+ });
2484
+ }
2485
+ //#endregion
2486
+ //#region src/shell/SdkHeader.tsx
2487
+ function SdkHeader({ tabs, mobileTabs, currentSlug, onNavigate, navSlugs }) {
2488
+ const sidebar = useSidebar();
2489
+ const themeMode = useThemeMode$1();
2490
+ const baseSlug = matchSlugPrefix(currentSlug, navSlugs)?.matchedSlug ?? normalizeSlug(currentSlug);
2491
+ const themeIcon = themeMode.mode === "dark" ? faMoon : faSun;
2492
+ const activeTabs = sidebar.isMobile && sidebar.useBottomNav && mobileTabs ? mobileTabs : tabs;
2493
+ return /* @__PURE__ */ jsxs("header", {
2494
+ className: "bg-sidebar flex h-[52px] shrink-0 items-center gap-2 px-6",
2495
+ children: [
2496
+ sidebar.isMobile && !sidebar.useBottomNav && /* @__PURE__ */ jsx("button", {
2497
+ type: "button",
2498
+ onClick: sidebar.toggleSidebar,
2499
+ className: "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground mr-2 flex shrink-0 items-center justify-center rounded-md p-2",
2500
+ "aria-label": "Toggle sidebar",
2501
+ children: /* @__PURE__ */ jsx(FontAwesomeIcon, {
2502
+ icon: faBars,
2503
+ className: "size-5"
2504
+ })
2505
+ }),
2506
+ /* @__PURE__ */ jsx("nav", {
2507
+ className: "scrollbar-none flex flex-1 items-center gap-1 overflow-x-auto",
2508
+ children: activeTabs.map((tab) => {
2509
+ const tabSlug = normalizeSlug(tab.slug);
2510
+ if (!tabSlug) return null;
2511
+ return /* @__PURE__ */ jsx("button", {
2512
+ type: "button",
2513
+ onClick: () => onNavigate(tabSlug),
2514
+ className: `border-b-2 px-4 py-3 text-sm font-medium whitespace-nowrap transition-colors ${tabSlug === baseSlug ? "border-primary text-foreground" : "text-muted-foreground hover:border-border hover:text-foreground border-transparent"}`,
2515
+ children: tab.label
2516
+ }, tab.id ?? tabSlug);
2517
+ })
2518
+ }),
2519
+ /* @__PURE__ */ jsx(QuickLinksDropdown, { onNavigate }),
2520
+ /* @__PURE__ */ jsx("button", {
2521
+ type: "button",
2522
+ onClick: themeMode.cycleMode,
2523
+ className: "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground ml-2 flex shrink-0 items-center justify-center rounded-md p-2",
2524
+ "aria-label": `Switch theme (current: ${themeMode.mode})`,
2525
+ children: /* @__PURE__ */ jsx(FontAwesomeIcon, {
2526
+ icon: themeIcon,
2527
+ className: "size-3"
2528
+ })
2529
+ })
2530
+ ]
2531
+ });
2532
+ }
2533
+ //#endregion
2534
+ //#region src/shell/BuilderScreenView.tsx
2535
+ /**
2536
+ * BuilderScreenView — renders a builder screen's component_tree with
2537
+ * DataAwareWidget support for data-bound widgets.
2538
+ *
2539
+ * Uses the shared ScreenRenderer from rep-widgets under the hood,
2540
+ * but wraps data-bound widgets with DataAwareWidget from rep-core.
2541
+ */
2542
+ /**
2543
+ * Renders a single widget, wrapping with DataAwareWidget if it has a dataSource.
2544
+ */
2545
+ function WidgetRenderer({ widget, index }) {
2546
+ const Component = useRegistry()[widget.type];
2547
+ if (!Component) {
2548
+ console.warn(`[BuilderScreenView] Widget type "${String(widget.type)}" not found in registry`);
2549
+ return null;
2550
+ }
2551
+ if (widget.dataSource) return /* @__PURE__ */ jsx("div", {
2552
+ className: "h-full w-full overflow-x-hidden",
2553
+ children: /* @__PURE__ */ jsx(DataAwareWidget, {
2554
+ widget,
2555
+ Component
2556
+ })
2557
+ }, widget.id ?? index);
2558
+ return /* @__PURE__ */ jsx("div", {
2559
+ className: "h-full w-full overflow-x-hidden",
2560
+ children: /* @__PURE__ */ jsx(Component, { ...widget.props })
2561
+ }, widget.id ?? index);
2562
+ }
2563
+ /**
2564
+ * Renders a builder screen's component_tree with full data source support.
2565
+ * Widgets with `dataSource` config are automatically wrapped with `DataAwareWidget`
2566
+ * which fetches data and merges it with static props before rendering.
2567
+ */
2568
+ function BuilderScreenViewImpl({ screen, className }) {
2569
+ const widgets = screen.component_tree;
2570
+ if (!widgets || widgets.length === 0) return /* @__PURE__ */ jsx("div", {
2571
+ className: className ?? "h-full w-full",
2572
+ children: /* @__PURE__ */ jsx("div", {
2573
+ className: "text-muted-foreground flex h-full items-center justify-center",
2574
+ children: /* @__PURE__ */ jsx("p", { children: "This screen has no content yet." })
2575
+ })
2576
+ });
2577
+ return /* @__PURE__ */ jsx("div", {
2578
+ className: className ?? "h-full w-full",
2579
+ children: widgets.map((widget, index) => {
2580
+ if (!widget) return null;
2581
+ return /* @__PURE__ */ jsx(WidgetRenderer, {
2582
+ widget,
2583
+ index
2584
+ }, widget.id ?? index);
2585
+ })
2586
+ });
2587
+ }
2588
+ const BuilderScreenView = memo(BuilderScreenViewImpl);
2589
+ //#endregion
2590
+ //#region src/shell/PageRouter.tsx
2591
+ const SYSTEM_SLUG_SCREEN_MAP = {
2592
+ messages: MessagingScreen,
2593
+ contacts: ContactsScreen,
2594
+ shop: OrdersScreen,
2595
+ customers: CustomersScreen,
2596
+ products: ProductsScreen
2597
+ };
2598
+ function PageRouter({ currentSlug, currentNavItem, customPages, screens, baseSlug, restParams }) {
2599
+ const screenById = useMemo(() => {
2600
+ if (!screens || screens.length === 0) return void 0;
2601
+ return new Map(screens.map((s) => [s.id, s]));
2602
+ }, [screens]);
2603
+ const screenBySlug = useMemo(() => {
2604
+ if (!screens || screens.length === 0) return void 0;
2605
+ return new Map(screens.filter((s) => s.slug).map((s) => [s.slug, s]));
2606
+ }, [screens]);
2607
+ const builderScreen = useMemo(() => {
2608
+ if (currentNavItem?.screen_id && screenById) {
2609
+ const byId = screenById.get(currentNavItem.screen_id);
2610
+ if (byId) return byId;
2611
+ }
2612
+ if (screenBySlug) return screenBySlug.get(currentSlug) ?? screenBySlug.get(baseSlug) ?? void 0;
2613
+ }, [
2614
+ currentNavItem?.screen_id,
2615
+ screenById,
2616
+ screenBySlug,
2617
+ currentSlug,
2618
+ baseSlug
2619
+ ]);
2620
+ const SystemScreen = SYSTEM_SLUG_SCREEN_MAP[baseSlug];
2621
+ if (SystemScreen) return /* @__PURE__ */ jsx(SystemScreen, {});
2622
+ if (isSystemNavigationItem(baseSlug)) return /* @__PURE__ */ jsx(CoreScreenPlaceholder, { name: currentNavItem?.label ?? baseSlug });
2623
+ const ExactCustomPage = customPages?.[currentSlug];
2624
+ if (ExactCustomPage) return /* @__PURE__ */ jsx(ExactCustomPage, {
2625
+ slug: currentSlug,
2626
+ params: restParams
2627
+ });
2628
+ if (baseSlug !== currentSlug) {
2629
+ const PrefixCustomPage = customPages?.[baseSlug];
2630
+ if (PrefixCustomPage) return /* @__PURE__ */ jsx(PrefixCustomPage, {
2631
+ slug: currentSlug,
2632
+ params: restParams
2633
+ });
2634
+ }
2635
+ if (builderScreen) return /* @__PURE__ */ jsx(BuilderScreenView, { screen: builderScreen });
2636
+ return /* @__PURE__ */ jsx(CoreScreenPlaceholder, { name: currentNavItem?.label ?? currentSlug });
2637
+ }
2638
+ //#endregion
2639
+ //#region src/shell/AppNavigationContext.tsx
2640
+ const AppNavigationContext = createContext(null);
2641
+ function AppNavigationProvider({ currentSlug, basePath, navigate, children }) {
2642
+ const buildHref = useMemo(() => {
2643
+ return (slug) => {
2644
+ if (basePath === "/") return `/${slug}`;
2645
+ return `${basePath}/${slug}`;
2646
+ };
2647
+ }, [basePath]);
2648
+ const value = useMemo(() => ({
2649
+ currentSlug,
2650
+ basePath,
2651
+ navigate,
2652
+ buildHref
2653
+ }), [
2654
+ currentSlug,
2655
+ basePath,
2656
+ navigate,
2657
+ buildHref
2658
+ ]);
2659
+ return /* @__PURE__ */ jsx(AppNavigationContext.Provider, {
2660
+ value,
2661
+ children
2662
+ });
2663
+ }
2664
+ function useAppNavigation() {
2665
+ const ctx = useContext(AppNavigationContext);
2666
+ if (!ctx) throw new Error("useAppNavigation must be used within an <AppShell> or <AppNavigationProvider>");
2667
+ return ctx;
2668
+ }
2669
+ //#endregion
2670
+ //#region src/shell/AppShell.tsx
2671
+ const THEME_STORAGE_KEY = "rep-theme-mode";
2672
+ const DEFAULT_NAVIGATION = [
2673
+ {
2674
+ label: "We Commerce",
2675
+ children: []
2676
+ },
2677
+ {
2678
+ slug: "shop",
2679
+ label: "Shop",
2680
+ icon: "store",
2681
+ children: []
2682
+ },
2683
+ {
2684
+ slug: "account",
2685
+ label: "Account",
2686
+ icon: "user-gear",
2687
+ children: []
2688
+ },
2689
+ {
2690
+ slug: "messages",
2691
+ label: "Messaging",
2692
+ icon: "comment",
2693
+ children: []
2694
+ },
2695
+ {
2696
+ label: "Marketing Assets",
2697
+ children: []
2698
+ },
2699
+ {
2700
+ slug: "share/products",
2701
+ label: "Products",
2702
+ icon: "boxes-stacked",
2703
+ children: []
2704
+ }
2705
+ ];
2706
+ function getInitialThemeMode() {
2707
+ if (typeof window === "undefined") return "light";
2708
+ const stored = localStorage.getItem(THEME_STORAGE_KEY);
2709
+ if (stored === "light" || stored === "dark") return stored;
2710
+ return "light";
2711
+ }
2712
+ function findFirstNavigableSlug(items) {
2713
+ for (const item of items) {
2714
+ if (item.slug) return normalizeSlug(item.slug);
2715
+ for (const child of item.children ?? []) if (child.slug) return normalizeSlug(child.slug);
2716
+ }
2717
+ return "";
2718
+ }
2719
+ function findCurrentSection(items, slug) {
2720
+ const normalized = normalizeSlug(slug);
2721
+ for (const item of items) {
2722
+ if (normalizeSlug(item.slug) === normalized) return item;
2723
+ if (item.children?.some((child) => normalizeSlug(child.slug) === normalized)) return item;
2724
+ }
2725
+ }
2726
+ function findNavItem(items, slug) {
2727
+ const normalized = normalizeSlug(slug);
2728
+ for (const item of items) {
2729
+ if (normalizeSlug(item.slug) === normalized) return item;
2730
+ for (const child of item.children ?? []) if (normalizeSlug(child.slug) === normalized) return child;
2731
+ }
2732
+ }
2733
+ function AppShell({ appData: appDataProp, navigation: navigationProp, customPages, basePath = "/", currentSlug: controlledSlug, onNavigate: onNavigateProp, sidebarHeader, sidebarFooter, children }) {
2734
+ const normalizedBasePath = useMemo(() => {
2735
+ const stripped = basePath.replace(/^\/|\/$/g, "");
2736
+ return stripped ? `/${stripped}` : "/";
2737
+ }, [basePath]);
2738
+ const { data: fetchedAppData, isLoading } = useFluidApp({ enabled: !appDataProp });
2739
+ const appData = appDataProp ?? fetchedAppData;
2740
+ const activeTheme = useMemo(() => {
2741
+ if (!appData?.profile?.themes?.length) return void 0;
2742
+ const themes = appData.profile.themes;
2743
+ const activeId = appData.profile.activeThemeId;
2744
+ return activeId ? themes.find((t) => t.id === activeId) ?? themes[0] : themes[0];
2745
+ }, [appData?.profile?.themes, appData?.profile?.activeThemeId]);
2746
+ useEffect(() => {
2747
+ if (!activeTheme) return;
2748
+ applyTheme$1(resolveTheme$1(activeTheme));
2749
+ return () => {
2750
+ removeAllThemes$1();
2751
+ };
2752
+ }, [activeTheme]);
2753
+ const navItems = useMemo(() => {
2754
+ if (navigationProp) return navigationProp;
2755
+ const profileNav = appData?.profile?.navigation?.navigation_items;
2756
+ if (profileNav && profileNav.length > 0) return profileNav;
2757
+ return DEFAULT_NAVIGATION;
2758
+ }, [navigationProp, appData?.profile?.navigation?.navigation_items]);
2759
+ const mobileNavItems = useMemo(() => {
2760
+ const mobileNav = appData?.profile?.mobile_navigation?.navigation_items;
2761
+ if (mobileNav && mobileNav.length > 0) return mobileNav;
2762
+ return navItems;
2763
+ }, [appData?.profile?.mobile_navigation?.navigation_items, navItems]);
2764
+ const screens = appData?.screens;
2765
+ const navSlugs = useMemo(() => collectNavSlugs(navItems), [navItems]);
2766
+ const [themeMode, setThemeMode] = useState(getInitialThemeMode);
2767
+ const handleThemeModeChange = useCallback((mode) => {
2768
+ setThemeMode(mode);
2769
+ if (typeof window !== "undefined") localStorage.setItem(THEME_STORAGE_KEY, mode);
2770
+ }, []);
2771
+ const [pathSlug, setPathSlug] = useState(() => {
2772
+ if (typeof window === "undefined") return "";
2773
+ return extractSlugFromPathname(window.location.pathname, normalizedBasePath);
2774
+ });
2775
+ useEffect(() => {
2776
+ if (controlledSlug !== void 0) return;
2777
+ if (typeof window === "undefined") return;
2778
+ if (!extractSlugFromPathname(window.location.pathname, normalizedBasePath)) {
2779
+ const firstSlug = findFirstNavigableSlug(navItems);
2780
+ if (firstSlug) {
2781
+ const targetPath = normalizedBasePath === "/" ? `/${firstSlug}` : `${normalizedBasePath}/${firstSlug}`;
2782
+ history.replaceState(null, "", targetPath);
2783
+ setPathSlug(firstSlug);
2784
+ }
2785
+ }
2786
+ }, [
2787
+ navItems,
2788
+ controlledSlug,
2789
+ normalizedBasePath
2790
+ ]);
2791
+ useEffect(() => {
2792
+ if (controlledSlug !== void 0) return;
2793
+ const handlePopState = () => {
2794
+ setPathSlug(extractSlugFromPathname(window.location.pathname, normalizedBasePath));
2795
+ };
2796
+ window.addEventListener("popstate", handlePopState);
2797
+ return () => window.removeEventListener("popstate", handlePopState);
2798
+ }, [controlledSlug, normalizedBasePath]);
2799
+ const activeSlug = controlledSlug ?? pathSlug;
2800
+ const handleNavigate = useCallback((slug) => {
2801
+ if (onNavigateProp) {
2802
+ onNavigateProp(slug);
2803
+ return;
2804
+ }
2805
+ const targetPath = normalizedBasePath === "/" ? `/${slug}` : `${normalizedBasePath}/${slug}`;
2806
+ history.pushState(null, "", targetPath);
2807
+ setPathSlug(slug);
2808
+ }, [onNavigateProp, normalizedBasePath]);
2809
+ const slugMatch = useMemo(() => matchSlugPrefix(activeSlug, navSlugs), [activeSlug, navSlugs]);
2810
+ const baseSlug = slugMatch?.matchedSlug ?? normalizeSlug(activeSlug);
2811
+ const restParams = slugMatch?.rest ?? "";
2812
+ const secondLevelTabs = findCurrentSection(navItems, baseSlug)?.children ?? [];
2813
+ const mobileSecondLevelTabs = findCurrentSection(mobileNavItems, baseSlug)?.children ?? [];
2814
+ const currentNavItem = findNavItem(navItems, baseSlug);
2815
+ const screenTitle = currentNavItem?.label || screens?.find((s) => s.id === currentNavItem?.screen_id)?.name || void 0;
2816
+ const content = typeof children === "function" ? children({
2817
+ currentSlug: activeSlug,
2818
+ currentNavItem
2819
+ }) : children ?? /* @__PURE__ */ jsxs(ScreenHeaderProvider, { children: [/* @__PURE__ */ jsx(ScreenHeader, { title: screenTitle }), /* @__PURE__ */ jsx(PageRouter, {
2820
+ currentSlug: activeSlug,
2821
+ currentNavItem,
2822
+ customPages,
2823
+ screens,
2824
+ baseSlug,
2825
+ restParams
2826
+ })] });
2827
+ if (isLoading && !appData) return /* @__PURE__ */ jsx(AppShellLoading, {});
2828
+ return /* @__PURE__ */ jsx(ThemeModeProvider$1, {
2829
+ mode: themeMode,
2830
+ onModeChange: handleThemeModeChange,
2831
+ autoModeEnabled: false,
2832
+ children: /* @__PURE__ */ jsx(AppNavigationProvider, {
2833
+ currentSlug: activeSlug,
2834
+ basePath: normalizedBasePath,
2835
+ navigate: handleNavigate,
2836
+ children: /* @__PURE__ */ jsx("div", {
2837
+ "data-theme": activeTheme?.id,
2838
+ "data-theme-mode": themeMode === "auto" ? void 0 : themeMode,
2839
+ children: /* @__PURE__ */ jsx(AppShellLayout, {
2840
+ sidebarContent: /* @__PURE__ */ jsx(SdkNavigation, {
2841
+ navItems,
2842
+ currentSlug: activeSlug,
2843
+ onNavigate: handleNavigate,
2844
+ navSlugs
2845
+ }),
2846
+ headerContent: /* @__PURE__ */ jsx(SdkHeader, {
2847
+ tabs: secondLevelTabs,
2848
+ mobileTabs: mobileSecondLevelTabs,
2849
+ currentSlug: activeSlug,
2850
+ onNavigate: handleNavigate,
2851
+ navSlugs
2852
+ }),
2853
+ sidebarHeader,
2854
+ sidebarFooter,
2855
+ useBottomNav: true,
2856
+ afterContent: /* @__PURE__ */ jsx(SdkBottomNav, {
2857
+ navItems: mobileNavItems,
2858
+ currentSlug: activeSlug,
2859
+ onNavigate: handleNavigate
2860
+ }),
2861
+ children: content
2862
+ })
2863
+ })
2864
+ })
2865
+ });
2866
+ }
2867
+ //#endregion
2868
+ //#region src/shell/AppLink.tsx
2869
+ /**
2870
+ * SPA-aware link that renders a real `<a href>` for accessibility
2871
+ * (right-click, ctrl+click, screen readers) but intercepts normal
2872
+ * clicks for client-side navigation.
2873
+ */
2874
+ const AppLink = forwardRef(function AppLink({ to, onClick, children, ...rest }, ref) {
2875
+ const { navigate, buildHref } = useAppNavigation();
2876
+ const handleClick = (e) => {
2877
+ onClick?.(e);
2878
+ if (e.defaultPrevented) return;
2879
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
2880
+ if (e.button !== 0) return;
2881
+ if (rest.target && rest.target !== "_self") return;
2882
+ e.preventDefault();
2883
+ navigate(to);
2884
+ };
2885
+ return /* @__PURE__ */ jsx("a", {
2886
+ ref,
2887
+ href: buildHref(to),
2888
+ onClick: handleClick,
2889
+ ...rest,
2890
+ children
2891
+ });
2892
+ });
2893
+ //#endregion
2894
+ var DEFAULT_COLORS = themes_exports.DEFAULT_COLORS;
2895
+ var DEFAULT_FONT_FAMILIES = themes_exports.DEFAULT_FONT_FAMILIES;
2896
+ var DEFAULT_FONT_SIZES = themes_exports.DEFAULT_FONT_SIZES;
2897
+ var DEFAULT_RADII = themes_exports.DEFAULT_RADII;
2898
+ var DEFAULT_SPACING = themes_exports.DEFAULT_SPACING;
2899
+ var DEFAULT_THEME_ID = themes_exports.DEFAULT_THEME_ID;
2900
+ var DEFAULT_THEME_NAME = themes_exports.DEFAULT_THEME_NAME;
2901
+ var FONT_FAMILY_KEYS = themes_exports.FONT_FAMILY_KEYS;
2902
+ var FONT_SIZE_KEYS = themes_exports.FONT_SIZE_KEYS;
2903
+ var RADIUS_KEYS = themes_exports.RADIUS_KEYS;
2904
+ var SEMANTIC_COLOR_NAMES = themes_exports.SEMANTIC_COLOR_NAMES;
2905
+ var SHADE_STEPS = themes_exports.SHADE_STEPS;
2906
+ var applyTheme = themes_exports.applyTheme;
2907
+ var deriveDarkVariant = themes_exports.deriveDarkVariant;
2908
+ var deserialiseTheme = themes_exports.deserialiseTheme;
2909
+ var generateShades = themes_exports.generateShades;
2910
+ var generateThemeCSS = themes_exports.generateThemeCSS;
2911
+ var getDefaultThemeDefinition = themes_exports.getDefaultThemeDefinition;
2912
+ var getForegroundColor = themes_exports.getForegroundColor;
2913
+ var mergeDarkOverrides = themes_exports.mergeDarkOverrides;
2914
+ var parseColor = themes_exports.parseColor;
2915
+ var removeAllThemes = themes_exports.removeAllThemes;
2916
+ var removeTheme = themes_exports.removeTheme;
2917
+ var resolveTheme = themes_exports.resolveTheme;
2918
+ var serialiseTheme = themes_exports.serialiseTheme;
2919
+ export { ACTIVITY_SLUGS, APP_DATA_QUERY_KEY, AUTH_CONSTANTS, AlertWidget, ApiError, AppLink, AppNavigationProvider, AppShell, AuthError, AuthLoading, BuilderScreenView, CORE_PAGE_IDS, CURRENT_REP_QUERY_KEY, CalendarWidget, CarouselWidget, CatchUpWidget, ChartWidget, ContactsScreen, ContainerWidget, CustomersScreen, DEFAULT_AUTH_URL, DEFAULT_COLORS, DEFAULT_FONT_FAMILIES, DEFAULT_FONT_SIZES, DEFAULT_RADII, DEFAULT_SDK_WIDGET_REGISTRY, DEFAULT_SPACING, DEFAULT_THEME_ID, DEFAULT_THEME_NAME, EmbedWidget, FONT_FAMILY_KEYS, FONT_SIZE_KEYS, FluidAuthProvider, FluidProvider, FluidThemeProvider, ImageWidget, LayoutWidget, ListWidget, MessagingScreen, MySiteWidget, NestedWidget, OrdersScreen, PAGE_CATEGORIES, PERMISSIONS_QUERY_KEY, PROFILE_QUERY_KEY, PROPERTY_FIELD_TYPES, PageRouter, PageTemplateProvider, PageTemplateRegistry, ProductsScreen, QuickLinksDropdown, QuickShareWidget, RADIUS_KEYS, RecentActivityWidget, RequireAuth, SEMANTIC_COLOR_NAMES, SHADE_STEPS, STORAGE_KEYS, SdkHeader, SdkNavigation, SpacerWidget, TableWidget, TextWidget, ThemeModeProvider, ToDoWidget, URL_PARAMS, USER_TYPES, VideoWidget, WIDGET_TYPE_NAMES, alertWidgetPropertySchema, applyTheme, assertDefined, assertNever, buildThemeDefinition, calendarWidgetPropertySchema, carouselWidgetPropertySchema, catchUpWidgetPropertySchema, chartWidgetPropertySchema, cleanTokenFromUrl, clearTokens, collectNavSlugs, contactsScreenPropertySchema, containerWidgetPropertySchema, createDefaultAuthRedirect, createFluidClient, createFluidFileUploader, createScreen, createWidgetFromShareable, createWidgetRegistry, customersScreenPropertySchema, decodeToken, deriveDarkVariant, deserialiseTheme, embedWidgetPropertySchema, extractAllTokensFromUrl, extractCompanyTokenFromUrl, extractSlugFromPathname, extractTokenFromUrl, gapValues, generateShades, generateThemeCSS, getActiveThemeId, getAvailablePageTemplates, getCorePageTemplates, getDefaultThemeDefinition, getForegroundColor, getOptionalPageTemplates, getProperty, getStoredToken, getThemeModeAttribute, getTokenExpiration, getTokenTimeRemaining, groupChildrenByColumn, hasData, hasStoredToken, hasTokenInUrl, imageWidgetPropertySchema, isActivitySlug, isApiError, isContactStatus, isErrorResult, isIdle, isLoading, isPropertyFieldType, isSlugInSection, isTokenExpired, isUserType, isValidToken, isWidgetType, isWidgetTypeName, layoutWidgetPropertySchema, listWidgetPropertySchema, matchSlugPrefix, mergeDarkOverrides, messagingScreenPropertySchema, mySiteWidgetPropertySchema, nestedWidgetPropertySchema, normalizeComponentTree, ordersScreenPropertySchema, parseColor, productsScreenPropertySchema, quickShareWidgetPropertySchema, recentActivityWidgetPropertySchema, removeAllThemes, removeTheme, resolveNavigationPages, resolveTheme, screenPropertySchemas, sectionLayoutConfig, selectProperty, serialiseTheme, spacerWidgetPropertySchema, storeToken, tableWidgetPropertySchema, textWidgetPropertySchema, toDoWidgetPropertySchema, toNavigationItem, toScreenDefinition, transformManifestToRepAppData, transformThemes, useActivities, useAppNavigation, useCalendarEvents, useCatchUps, useContact, useContacts, useConversationMessages, useConversations, useCurrentRep, useFluidApi, useFluidApp, useFluidAuth, useFluidAuthContext, useFluidContext, useFluidPermissions, useFluidProfile, useFluidTheme, useMessagingAuth, useMessagingConfig, useMySite, usePageTemplates, useResolvedPages, useThemeContext, useThemeMode, useTodos, validateNavigationPages, validateToken, videoWidgetPropertySchema, widgetPropertySchemas };
2920
+
2921
+ //# sourceMappingURL=index.mjs.map