@fluid-app/rep-sdk 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ContactsScreen-CB6l0Lf1.mjs +24 -0
- package/dist/ContactsScreen-CB6l0Lf1.mjs.map +1 -0
- package/dist/ContactsScreen-UfrdOORn.cjs +41 -0
- package/dist/ContactsScreen-UfrdOORn.cjs.map +1 -0
- package/dist/CoreScreenPlaceholder-D93ZYKt2.mjs +32 -0
- package/dist/CoreScreenPlaceholder-D93ZYKt2.mjs.map +1 -0
- package/dist/CoreScreenPlaceholder-DBZqxDsK.cjs +38 -0
- package/dist/CoreScreenPlaceholder-DBZqxDsK.cjs.map +1 -0
- package/dist/CustomersScreen-BEar6Leg.mjs +24 -0
- package/dist/CustomersScreen-BEar6Leg.mjs.map +1 -0
- package/dist/CustomersScreen-DXXPpWZW.cjs +41 -0
- package/dist/CustomersScreen-DXXPpWZW.cjs.map +1 -0
- package/dist/MessagingScreen-CsDvKkLC.cjs +1458 -0
- package/dist/MessagingScreen-CsDvKkLC.cjs.map +1 -0
- package/dist/MessagingScreen-xO9YudMx.mjs +1285 -0
- package/dist/MessagingScreen-xO9YudMx.mjs.map +1 -0
- package/dist/OrdersScreen-DB1v051q.mjs +24 -0
- package/dist/OrdersScreen-DB1v051q.mjs.map +1 -0
- package/dist/OrdersScreen-fcxcnpNU.cjs +41 -0
- package/dist/OrdersScreen-fcxcnpNU.cjs.map +1 -0
- package/dist/ProductsScreen-BaEng3LB.cjs +41 -0
- package/dist/ProductsScreen-BaEng3LB.cjs.map +1 -0
- package/dist/ProductsScreen-nVDsY6kf.mjs +24 -0
- package/dist/ProductsScreen-nVDsY6kf.mjs.map +1 -0
- package/dist/chunk-D1SwGrFN.mjs +27 -0
- package/dist/index.cjs +3397 -2850
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1026 -955
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +2207 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2921 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +8 -8
- package/dist/ContactsScreen-7RGAMMGN.cjs +0 -18
- package/dist/ContactsScreen-7RGAMMGN.cjs.map +0 -1
- package/dist/ContactsScreen-XKZE5HTP.js +0 -5
- package/dist/ContactsScreen-XKZE5HTP.js.map +0 -1
- package/dist/CustomersScreen-BBQ3YHI4.js +0 -5
- package/dist/CustomersScreen-BBQ3YHI4.js.map +0 -1
- package/dist/CustomersScreen-EMGCQ2PV.cjs +0 -18
- package/dist/CustomersScreen-EMGCQ2PV.cjs.map +0 -1
- package/dist/MessagingScreen-I3GVOGKY.js +0 -4
- package/dist/MessagingScreen-I3GVOGKY.js.map +0 -1
- package/dist/MessagingScreen-ZRD4WEOE.cjs +0 -17
- package/dist/MessagingScreen-ZRD4WEOE.cjs.map +0 -1
- package/dist/OrdersScreen-ANDGND6F.js +0 -5
- package/dist/OrdersScreen-ANDGND6F.js.map +0 -1
- package/dist/OrdersScreen-NNE6OSRS.cjs +0 -18
- package/dist/OrdersScreen-NNE6OSRS.cjs.map +0 -1
- package/dist/ProductsScreen-5PMFLPWS.js +0 -5
- package/dist/ProductsScreen-5PMFLPWS.js.map +0 -1
- package/dist/ProductsScreen-ALQYD7ID.cjs +0 -18
- package/dist/ProductsScreen-ALQYD7ID.cjs.map +0 -1
- package/dist/chunk-2RIDFKS3.cjs +0 -19
- package/dist/chunk-2RIDFKS3.cjs.map +0 -1
- package/dist/chunk-424PT5DM.js +0 -21
- package/dist/chunk-424PT5DM.js.map +0 -1
- package/dist/chunk-4LTH3NNT.js +0 -16
- package/dist/chunk-4LTH3NNT.js.map +0 -1
- package/dist/chunk-4PHR2M6A.js +0 -14
- package/dist/chunk-4PHR2M6A.js.map +0 -1
- package/dist/chunk-EKMZ4NIA.cjs +0 -19
- package/dist/chunk-EKMZ4NIA.cjs.map +0 -1
- package/dist/chunk-HDQ2JUQT.cjs +0 -24
- package/dist/chunk-HDQ2JUQT.cjs.map +0 -1
- package/dist/chunk-HFPPHLOO.cjs +0 -19
- package/dist/chunk-HFPPHLOO.cjs.map +0 -1
- package/dist/chunk-HMOWQQDX.cjs +0 -19
- package/dist/chunk-HMOWQQDX.cjs.map +0 -1
- package/dist/chunk-JXEED3FK.js +0 -16
- package/dist/chunk-JXEED3FK.js.map +0 -1
- package/dist/chunk-LU42Y4H3.cjs +0 -1597
- package/dist/chunk-LU42Y4H3.cjs.map +0 -1
- package/dist/chunk-NGOEPTWZ.js +0 -16
- package/dist/chunk-NGOEPTWZ.js.map +0 -1
- package/dist/chunk-NNZKBXNB.cjs +0 -16
- package/dist/chunk-NNZKBXNB.cjs.map +0 -1
- package/dist/chunk-SYZUEL6B.js +0 -1538
- package/dist/chunk-SYZUEL6B.js.map +0 -1
- package/dist/chunk-TB6OK3WH.js +0 -16
- package/dist/chunk-TB6OK3WH.js.map +0 -1
- package/dist/index.d.ts +0 -2138
- package/dist/index.js +0 -2536
- 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 ? "text-foreground border-primary" : "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
|