@dashadmin/dash-app-common 1.3.25 → 1.3.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +35 -0
  2. package/dist/DashApp.js +176 -1
  3. package/dist/DashAppLoader.js +348 -3
  4. package/dist/DashDefaultResources.js +10 -1
  5. package/dist/components/DashDefaultPrivateApp.js +150 -1
  6. package/dist/components/DashDefaultPrivateResources.js +10 -1
  7. package/dist/components/DashDefaultPublicApp.js +20 -1
  8. package/dist/components/DashResourceLoader.js +245 -1
  9. package/dist/defaults/extensions/bootstrap/DashDefaultErrorBoundary.js +78 -1
  10. package/dist/defaults/extensions/bootstrap/dashDefaultInitializeApp.js +59 -1
  11. package/dist/defaults/extensions/bootstrap/index.js +24 -1
  12. package/dist/defaults/extensions/components/DashDefaultLoader.js +43 -2
  13. package/dist/defaults/extensions/components/dashDefaultAutoAdminComponents.js +67 -1
  14. package/dist/defaults/extensions/components/dashDefaultNotification.js +28 -1
  15. package/dist/defaults/extensions/components/dashDefaultStaticComponents.js +15 -1
  16. package/dist/defaults/extensions/components/index.js +13 -1
  17. package/dist/defaults/extensions/electron/dashDefaultIpcListeners.js +38 -1
  18. package/dist/defaults/extensions/electron/index.js +5 -1
  19. package/dist/defaults/extensions/hooks/index.js +12 -1
  20. package/dist/defaults/extensions/hooks/useDashDefaultLazyAutoAdminComponents.js +17 -1
  21. package/dist/defaults/extensions/hooks/useDashDefaultLazyI18nProvider.js +51 -1
  22. package/dist/defaults/extensions/hooks/useDashDefaultLazyResources.js +19 -1
  23. package/dist/defaults/extensions/hooks/useDashDefaultMountTracker.js +22 -1
  24. package/dist/defaults/extensions/hooks/useDashDefaultRoutePath.js +17 -1
  25. package/dist/defaults/extensions/i18n/dashDefaultI18nProvider.js +44 -1
  26. package/dist/defaults/extensions/i18n/dashDefaultTranslations.js +87 -1
  27. package/dist/defaults/extensions/i18n/index.js +24 -1
  28. package/dist/defaults/extensions/index.js +190 -1
  29. package/dist/defaults/extensions/layout/dashDefaultDomainAppLayout.js +58 -1
  30. package/dist/defaults/extensions/layout/index.js +12 -1
  31. package/dist/defaults/extensions/managers/DashDefaultWSMessagesManager.js +84 -1
  32. package/dist/defaults/extensions/managers/index.js +5 -1
  33. package/dist/defaults/extensions/providers/dashDefaultAuthProvider.js +220 -1
  34. package/dist/defaults/extensions/providers/dashDefaultDataProvider.js +395 -1
  35. package/dist/defaults/extensions/providers/index.js +24 -1
  36. package/dist/defaults/extensions/router/DashDefaultLanding.js +46 -1
  37. package/dist/defaults/extensions/router/dashDefaultPrivateRoutes.js +38 -1
  38. package/dist/defaults/extensions/router/dashDefaultPublicRoutes.js +65 -1
  39. package/dist/defaults/extensions/router/dashDefaultRouter.js +39 -1
  40. package/dist/defaults/extensions/router/dashDefaultSharedRoutes.js +40 -1
  41. package/dist/defaults/extensions/router/index.js +12 -1
  42. package/dist/defaults/extensions/state/dashDefaultAppCommon.js +57 -1
  43. package/dist/defaults/extensions/state/dashDefaultAppSettings.js +38 -1
  44. package/dist/defaults/extensions/state/dashDefaultInitialAppState.js +31 -1
  45. package/dist/defaults/extensions/state/dashDefaultLayoutSettings.js +48 -1
  46. package/dist/defaults/extensions/state/dashDefaultQueryClient.js +58 -1
  47. package/dist/defaults/extensions/state/dashDefaultResourcesIcons.js +24 -1
  48. package/dist/defaults/extensions/state/index.js +29 -1
  49. package/dist/defaults/extensions/styles/index.js +2 -1
  50. package/dist/defaults/extensions/theme/dashDefaultThemeOptions.js +74 -1
  51. package/dist/defaults/extensions/theme/index.js +6 -1
  52. package/dist/defaults/extensions/utils/IPCMessageBrokerContext.js +12 -1
  53. package/dist/defaults/extensions/utils/dashDefaultAuthProviderUtils.js +29 -1
  54. package/dist/defaults/extensions/utils/dashDefaultDataProviderUtils.js +37 -1
  55. package/dist/defaults/extensions/utils/dashDefaultNotificationsProcessor.js +149 -1
  56. package/dist/defaults/extensions/utils/index.js +44 -1
  57. package/dist/defaults/filters/demo/dashDefaultDemoFilters.js +36 -1
  58. package/dist/defaults/filters/demo/index.js +4 -1
  59. package/dist/defaults/filters/index.js +5 -1
  60. package/dist/defaults/index.js +14 -1
  61. package/dist/defaults/pages/Account/DashDefaultRegister.js +282 -1
  62. package/dist/defaults/pages/Account/DashDefaultSignUp.js +420 -1
  63. package/dist/defaults/pages/Account/DashDefaultSignUpSuccess.js +101 -1
  64. package/dist/defaults/pages/Account/index.js +8 -1
  65. package/dist/defaults/pages/DashDefaultLogin.js +218 -1
  66. package/dist/defaults/pages/Misc/DashDefaultNotFound.js +74 -1
  67. package/dist/defaults/pages/Misc/DashDefaultServerError.js +79 -1
  68. package/dist/defaults/pages/Misc/DashDefaultUnauthorized.js +90 -1
  69. package/dist/defaults/pages/Misc/index.js +8 -1
  70. package/dist/defaults/pages/Static/DashDefaultPrivacyPage.js +48 -1
  71. package/dist/defaults/pages/Static/DashDefaultStaticPage.js +55 -1
  72. package/dist/defaults/pages/Static/DashDefaultTermsPage.js +52 -1
  73. package/dist/defaults/pages/Static/index.js +8 -1
  74. package/dist/defaults/pages/index.js +25 -1
  75. package/dist/defaults/schemas/dashDefaultSchema.js +14 -1
  76. package/dist/defaults/schemas/demo/dashDefaultTodoSchema.js +24 -1
  77. package/dist/defaults/schemas/demo/index.js +4 -1
  78. package/dist/defaults/schemas/index.js +7 -1
  79. package/dist/filters/demo/todoFilters.js +36 -1
  80. package/dist/index.js +56 -5975
  81. package/dist/resources/demo/todoResource.js +58 -1
  82. package/dist/schemas/defaultSchema.js +14 -1
  83. package/dist/schemas/demo/todoSchema.js +24 -1
  84. package/package.json +132 -77
  85. package/src/DashApp.tsx +294 -0
  86. package/src/DashAppLoader.tsx +717 -0
  87. package/src/DashDefaultResources.tsx +10 -0
  88. package/src/components/DashDefaultPrivateApp.tsx +358 -0
  89. package/src/components/DashDefaultPrivateResources.tsx +10 -0
  90. package/src/components/DashDefaultPublicApp.tsx +40 -0
  91. package/src/components/DashResourceLoader.tsx +564 -0
  92. package/src/defaults/extensions/bootstrap/DashDefaultErrorBoundary.tsx +113 -0
  93. package/src/defaults/extensions/bootstrap/dashDefaultInitializeApp.ts +81 -0
  94. package/src/defaults/extensions/bootstrap/index.ts +32 -0
  95. package/src/defaults/extensions/components/DashDefaultLoader.tsx +59 -0
  96. package/src/defaults/extensions/components/dashDefaultAutoAdminComponents.tsx +49 -0
  97. package/src/defaults/extensions/components/dashDefaultNotification.tsx +37 -0
  98. package/src/defaults/extensions/components/dashDefaultStaticComponents.tsx +34 -0
  99. package/src/defaults/extensions/components/index.ts +17 -0
  100. package/src/defaults/extensions/electron/dashDefaultIpcListeners.ts +62 -0
  101. package/src/defaults/extensions/electron/index.ts +7 -0
  102. package/src/defaults/extensions/hooks/index.ts +11 -0
  103. package/src/defaults/extensions/hooks/useDashDefaultLazyAutoAdminComponents.ts +25 -0
  104. package/src/defaults/extensions/hooks/useDashDefaultLazyI18nProvider.ts +38 -0
  105. package/src/defaults/extensions/hooks/useDashDefaultLazyResources.ts +29 -0
  106. package/src/defaults/extensions/hooks/useDashDefaultMountTracker.ts +28 -0
  107. package/src/defaults/extensions/hooks/useDashDefaultRoutePath.ts +27 -0
  108. package/src/defaults/extensions/i18n/dashDefaultI18nProvider.ts +66 -0
  109. package/src/defaults/extensions/i18n/dashDefaultTranslations.ts +82 -0
  110. package/src/defaults/extensions/i18n/index.ts +22 -0
  111. package/src/defaults/extensions/index.ts +177 -0
  112. package/src/defaults/extensions/layout/dashDefaultDomainAppLayout.tsx +126 -0
  113. package/src/defaults/extensions/layout/index.ts +12 -0
  114. package/src/defaults/extensions/managers/DashDefaultWSMessagesManager.tsx +99 -0
  115. package/src/defaults/extensions/managers/index.ts +7 -0
  116. package/src/defaults/extensions/providers/dashDefaultAuthProvider.ts +256 -0
  117. package/src/defaults/extensions/providers/dashDefaultDataProvider.ts +513 -0
  118. package/src/defaults/extensions/providers/index.ts +25 -0
  119. package/src/defaults/extensions/router/DashDefaultLanding.tsx +51 -0
  120. package/src/defaults/extensions/router/dashDefaultPrivateRoutes.tsx +50 -0
  121. package/src/defaults/extensions/router/dashDefaultPublicRoutes.tsx +86 -0
  122. package/src/defaults/extensions/router/dashDefaultRouter.tsx +54 -0
  123. package/src/defaults/extensions/router/dashDefaultSharedRoutes.tsx +56 -0
  124. package/src/defaults/extensions/router/index.ts +13 -0
  125. package/src/defaults/extensions/state/dashDefaultAppCommon.ts +54 -0
  126. package/src/defaults/extensions/state/dashDefaultAppSettings.ts +29 -0
  127. package/src/defaults/extensions/state/dashDefaultInitialAppState.ts +75 -0
  128. package/src/defaults/extensions/state/dashDefaultLayoutSettings.ts +59 -0
  129. package/src/defaults/extensions/state/dashDefaultQueryClient.ts +59 -0
  130. package/src/defaults/extensions/state/dashDefaultResourcesIcons.tsx +23 -0
  131. package/src/defaults/extensions/state/index.ts +29 -0
  132. package/src/defaults/extensions/styles/index.ts +17 -0
  133. package/src/defaults/extensions/theme/dashDefaultThemeOptions.ts +89 -0
  134. package/src/defaults/extensions/theme/index.ts +7 -0
  135. package/src/defaults/extensions/utils/IPCMessageBrokerContext.tsx +22 -0
  136. package/src/defaults/extensions/utils/dashDefaultAuthProviderUtils.ts +36 -0
  137. package/src/defaults/extensions/utils/dashDefaultDataProviderUtils.ts +46 -0
  138. package/src/defaults/extensions/utils/dashDefaultNotificationsProcessor.tsx +208 -0
  139. package/src/defaults/extensions/utils/index.ts +42 -0
  140. package/src/defaults/filters/demo/dashDefaultDemoFilters.tsx +50 -0
  141. package/src/defaults/filters/demo/index.ts +6 -0
  142. package/src/defaults/filters/index.ts +11 -0
  143. package/src/defaults/index.ts +27 -0
  144. package/src/defaults/pages/Account/DashDefaultRegister.tsx +281 -0
  145. package/src/defaults/pages/Account/DashDefaultSignUp.tsx +492 -0
  146. package/src/defaults/pages/Account/DashDefaultSignUpSuccess.tsx +113 -0
  147. package/src/defaults/pages/Account/index.ts +8 -0
  148. package/src/defaults/pages/DashDefaultLogin.tsx +224 -0
  149. package/src/defaults/pages/Misc/DashDefaultNotFound.tsx +96 -0
  150. package/src/defaults/pages/Misc/DashDefaultServerError.tsx +102 -0
  151. package/src/defaults/pages/Misc/DashDefaultUnauthorized.tsx +116 -0
  152. package/src/defaults/pages/Misc/index.ts +8 -0
  153. package/src/defaults/pages/Static/DashDefaultPrivacyPage.tsx +85 -0
  154. package/src/defaults/pages/Static/DashDefaultStaticPage.tsx +75 -0
  155. package/src/defaults/pages/Static/DashDefaultTermsPage.tsx +69 -0
  156. package/src/defaults/pages/Static/index.ts +8 -0
  157. package/src/defaults/pages/index.ts +28 -0
  158. package/src/defaults/schemas/dashDefaultSchema.tsx +25 -0
  159. package/src/defaults/schemas/demo/dashDefaultTodoSchema.tsx +39 -0
  160. package/src/defaults/schemas/demo/index.ts +6 -0
  161. package/src/defaults/schemas/index.ts +14 -0
  162. package/src/filters/demo/todoFilters.tsx +39 -0
  163. package/src/index.ts +131 -0
  164. package/src/resources/demo/todoResource.tsx +68 -0
  165. package/src/schemas/defaultSchema.tsx +14 -0
  166. package/src/schemas/demo/todoSchema.tsx +27 -0
@@ -0,0 +1,564 @@
1
+ /**
2
+ * DashResourceLoader
3
+ *
4
+ * A component and utilities for loading resources dynamically from a manifest.
5
+ * This allows apps to code-split their resource configurations and load them on demand.
6
+ *
7
+ * ============================================================================
8
+ * USAGE
9
+ * ============================================================================
10
+ *
11
+ * // 1. Create a resourceManifest.ts in your app that exports import functions:
12
+ * // export const resourceManifest = {
13
+ * // systemResources: () => import('dash-admin/src/systemResources'),
14
+ * // todoResource: () => import('./resources/demo/todoResource'),
15
+ * // };
16
+ *
17
+ * // 2. Use DashPrivateAppWithManifest in your DASHApp.tsx:
18
+ * import { DashPrivateAppWithManifest } from 'dash-app-common';
19
+ * import { resourceManifest } from './resourceManifest';
20
+ *
21
+ * const DASHApp = () => (
22
+ * <DashPrivateAppWithManifest manifest={resourceManifest} />
23
+ * );
24
+ *
25
+ * // 3. Or use the hook directly:
26
+ * import { useDashResourceManifest } from 'dash-app-common';
27
+ *
28
+ * const MyApp = () => {
29
+ * const { resources, loading, error } = useDashResourceManifest(manifest);
30
+ * if (loading) return <Loading />;
31
+ * if (error) return <Error error={error} />;
32
+ * return <DashDefaultPrivateApp resources={resources} />;
33
+ * };
34
+ */
35
+ import React, { useState, useEffect, PropsWithChildren, ReactElement } from 'react';
36
+ import { IDashAutoAdminResourceConfig } from 'dash-auto-admin';
37
+ import AppLoadingFallback from 'dash-components/src/components/theme/AppLoadingFallback';
38
+ import DashDefaultPrivateApp from './DashDefaultPrivateApp';
39
+
40
+ // ============================================================================
41
+ // TYPES
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Import function type for lazy loading a resource module
46
+ */
47
+ export type ResourceImportFn = () => Promise<{ default: IDashAutoAdminResourceConfig | IDashAutoAdminResourceConfig[] }>;
48
+
49
+ /**
50
+ * Resource manifest - object mapping names to import functions
51
+ * This allows Vite/Webpack to properly resolve and bundle the imports
52
+ */
53
+ export type ResourceManifest = Record<string, ResourceImportFn>;
54
+
55
+ /**
56
+ * Result of the useDashResourceManifest hook
57
+ */
58
+ export interface UseDashResourceManifestResult {
59
+ /** Loaded resources (null if still loading) */
60
+ resources: IDashAutoAdminResourceConfig[] | null;
61
+ /** Whether resources are currently loading */
62
+ loading: boolean;
63
+ /** Error message if loading failed */
64
+ error: string | null;
65
+ /** Reload resources from manifest */
66
+ reload: () => void;
67
+ }
68
+
69
+ /**
70
+ * Props for DashPrivateAppWithManifest
71
+ *
72
+ * Includes all DASHAdmin props plus manifest-specific configuration.
73
+ * Props are passed through the hierarchy: DashPrivateAppWithManifest → DashDefaultPrivateApp → DASHAdmin
74
+ */
75
+ export interface DashPrivateAppWithManifestProps extends PropsWithChildren {
76
+ // ========================================================================
77
+ // MANIFEST-SPECIFIC PROPS
78
+ // ========================================================================
79
+ /** Resource manifest object with import functions */
80
+ manifest: ResourceManifest;
81
+ /** Custom loading component for manifest loading */
82
+ LoadingComponent?: React.ComponentType<{ message?: string }>;
83
+ /** Custom error component for manifest loading failures */
84
+ ErrorComponent?: React.ComponentType<{ error: string; onRetry: () => void }>;
85
+
86
+ // ========================================================================
87
+ // APP CONFIGURATION PROPS
88
+ // ========================================================================
89
+ /** Base path for the admin app (passed to DASHAdmin as basePath) */
90
+ appPath?: string;
91
+ /** Whether to use the internal router */
92
+ useOwnRouter?: boolean;
93
+ /** Global hook component (for global side effects) */
94
+ GlobalHook?: React.ComponentType<any>;
95
+
96
+ // ========================================================================
97
+ // DASHADMIN PROPS (passed through to DASHAdmin)
98
+ // ========================================================================
99
+ /** Custom layout component for the admin */
100
+ customLayout?: React.FC<any>;
101
+ /** Custom login page component */
102
+ customLoginPage?: React.FC<any>;
103
+ /** Custom notification component */
104
+ customNotification?: () => React.JSX.Element;
105
+ /** Custom error/catch-all page component */
106
+ customErrorPage?: any;
107
+ /** Custom data provider */
108
+ customDataProvider?: any;
109
+ /** Custom auth provider */
110
+ customAuthProvider?: any;
111
+ /** Custom i18n provider */
112
+ customI18nProvider?: any;
113
+ /** Whether to use core resources (default: false for manifest apps) */
114
+ useCoreResources?: boolean;
115
+ /** Custom profile page component (false to disable) */
116
+ customProfilePage?: React.JSX.Element | false;
117
+ /** Custom recover password page (false to disable) */
118
+ customRecoverPassword?: React.JSX.Element | false;
119
+ /** Custom change password page (false to disable) */
120
+ customChangePassword?: React.JSX.Element | false;
121
+ /** Custom verify account page (false to disable) */
122
+ customVerifyAccount?: React.JSX.Element | false;
123
+ /** Custom theme configuration */
124
+ customThemeConfig?: any;
125
+ /** Custom authenticated routes */
126
+ customAuthRoutes?: React.ReactElement[];
127
+ /** Custom public routes */
128
+ customRoutes?: React.ReactElement[];
129
+ /** Custom public routes (alias for customRoutes, used by DashDefaultPrivateApp) */
130
+ customPublicRoutes?: () => ReactElement[];
131
+ /** Custom private/auth routes (alias for customAuthRoutes, used by DashDefaultPrivateApp) */
132
+ customPrivateRoutes?: () => ReactElement[];
133
+ /** Custom React Query client */
134
+ customQueryClient?: any;
135
+ /** Custom dictionary for translations */
136
+ customDict?: { [x: string]: string };
137
+ /** Custom string replacements */
138
+ customReplacements?: { [x: string]: string };
139
+ /** Custom history object */
140
+ history?: any;
141
+ /** Initial app constants */
142
+ initialAppConstants?: any;
143
+ /** Admin hook component (wraps admin content) */
144
+ AdminHook?: React.ComponentType<any>;
145
+ /** WebSocket messages manager for real-time notifications */
146
+ wsMessagesManager?: any;
147
+ }
148
+
149
+ /**
150
+ * Props for DashPrivateApp (unified component)
151
+ *
152
+ * Supports both manifest-based and direct resource loading via a single `resources` prop.
153
+ * The component automatically detects which format is provided:
154
+ * - ResourceManifest: Object with import functions (for code-splitting)
155
+ * - IDashAutoAdminResourceConfig[]: Direct array of resources (immediate use)
156
+ */
157
+ export interface DashPrivateAppProps extends PropsWithChildren {
158
+ // ========================================================================
159
+ // RESOURCE PROPS (accepts either format)
160
+ // ========================================================================
161
+ /**
162
+ * Resources can be either:
163
+ * - ResourceManifest: Object with import functions for code-splitting
164
+ * - IDashAutoAdminResourceConfig[]: Direct array for immediate use
165
+ */
166
+ resources?: ResourceManifest | IDashAutoAdminResourceConfig[];
167
+
168
+ /** Custom loading component for manifest loading */
169
+ LoadingComponent?: React.ComponentType<{ message?: string }>;
170
+ /** Custom error component for manifest loading failures */
171
+ ErrorComponent?: React.ComponentType<{ error: string; onRetry: () => void }>;
172
+
173
+ // ========================================================================
174
+ // APP CONFIGURATION PROPS
175
+ // ========================================================================
176
+ /** Base path for the admin app (passed to DASHAdmin as basePath) */
177
+ appPath?: string;
178
+ /** Whether to use the internal router */
179
+ useOwnRouter?: boolean;
180
+ /** Global hook component (for global side effects) */
181
+ GlobalHook?: React.ComponentType<any>;
182
+
183
+ // ========================================================================
184
+ // DASHADMIN PROPS (passed through to DASHAdmin)
185
+ // ========================================================================
186
+ /** Custom layout component for the admin */
187
+ customLayout?: React.FC<any>;
188
+ /** Custom login page component */
189
+ customLoginPage?: React.FC<any>;
190
+ /** Custom notification component */
191
+ customNotification?: () => React.JSX.Element;
192
+ /** Custom error/catch-all page component */
193
+ customErrorPage?: any;
194
+ /** Custom data provider */
195
+ customDataProvider?: any;
196
+ /** Custom auth provider */
197
+ customAuthProvider?: any;
198
+ /** Custom i18n provider */
199
+ customI18nProvider?: any;
200
+ /** Whether to use core resources (default: false for manifest apps) */
201
+ useCoreResources?: boolean;
202
+ /** Custom profile page component (false to disable) */
203
+ customProfilePage?: React.JSX.Element | false;
204
+ /** Custom recover password page (false to disable) */
205
+ customRecoverPassword?: React.JSX.Element | false;
206
+ /** Custom change password page (false to disable) */
207
+ customChangePassword?: React.JSX.Element | false;
208
+ /** Custom verify account page (false to disable) */
209
+ customVerifyAccount?: React.JSX.Element | false;
210
+ /** Custom theme configuration */
211
+ customThemeConfig?: any;
212
+ /** Custom authenticated routes */
213
+ customAuthRoutes?: React.ReactElement[];
214
+ /** Custom public routes */
215
+ customRoutes?: React.ReactElement[];
216
+ /** Custom public routes (alias for customRoutes, used by DashDefaultPrivateApp) */
217
+ customPublicRoutes?: () => ReactElement[];
218
+ /** Custom private/auth routes (alias for customAuthRoutes, used by DashDefaultPrivateApp) */
219
+ customPrivateRoutes?: () => ReactElement[];
220
+ /** Custom React Query client */
221
+ customQueryClient?: any;
222
+ /** Custom dictionary for translations */
223
+ customDict?: { [x: string]: string };
224
+ /** Custom string replacements */
225
+ customReplacements?: { [x: string]: string };
226
+ /** Custom history object */
227
+ history?: any;
228
+ /** Initial app constants */
229
+ initialAppConstants?: any;
230
+ /** Admin hook component (wraps admin content) */
231
+ AdminHook?: React.ComponentType<any>;
232
+ /** WebSocket messages manager for real-time notifications */
233
+ wsMessagesManager?: any;
234
+ }
235
+
236
+ // ============================================================================
237
+ // TYPE GUARDS
238
+ // ============================================================================
239
+
240
+ /**
241
+ * Check if resources is a ResourceManifest (object with import functions)
242
+ */
243
+ export const isResourceManifest = (
244
+ resources: ResourceManifest | IDashAutoAdminResourceConfig[] | undefined
245
+ ): resources is ResourceManifest => {
246
+ if (!resources) return false;
247
+ if (Array.isArray(resources)) return false;
248
+ // Check if it's an object with function values (import functions)
249
+ return typeof resources === 'object' &&
250
+ Object.values(resources).every(val => typeof val === 'function');
251
+ };
252
+
253
+ /**
254
+ * Check if resources is a direct array of resource configs
255
+ */
256
+ export const isResourceArray = (
257
+ resources: ResourceManifest | IDashAutoAdminResourceConfig[] | undefined
258
+ ): resources is IDashAutoAdminResourceConfig[] => {
259
+ return Array.isArray(resources);
260
+ };
261
+
262
+ // ============================================================================
263
+ // RESOURCE LOADING UTILITIES
264
+ // ============================================================================
265
+
266
+ // Cache for loaded resources (keyed by manifest object reference)
267
+ const resourceCache = new WeakMap<ResourceManifest, IDashAutoAdminResourceConfig[]>();
268
+
269
+ /**
270
+ * Load resources from a manifest object
271
+ * @param manifest - Object mapping names to import functions
272
+ * @returns Promise resolving to array of resource configs
273
+ */
274
+ export const loadResourcesFromManifest = async (
275
+ manifest: ResourceManifest
276
+ ): Promise<IDashAutoAdminResourceConfig[]> => {
277
+ // Return cached resources if available
278
+ if (resourceCache.has(manifest)) {
279
+ return resourceCache.get(manifest)!;
280
+ }
281
+
282
+ const entries = Object.entries(manifest);
283
+
284
+ // Load all resource modules in parallel
285
+ const imports = await Promise.all(
286
+ entries.map(async ([name, importFn]) => {
287
+ try {
288
+ const module = await importFn();
289
+ return module.default;
290
+ } catch (error) {
291
+ console.error(`❌ Failed to load resource "${name}":`, error);
292
+ return null;
293
+ }
294
+ })
295
+ );
296
+
297
+ // Flatten and filter out failed imports
298
+ const allResources = imports
299
+ .filter((mod): mod is IDashAutoAdminResourceConfig | IDashAutoAdminResourceConfig[] => mod !== null)
300
+ .flatMap(mod => Array.isArray(mod) ? mod : [mod]);
301
+
302
+ // Deduplicate by model, keeping the LAST occurrence (so overrides win)
303
+ // Resources loaded later in the manifest override earlier ones with the same model
304
+ const resourceMap = new Map<string, IDashAutoAdminResourceConfig>();
305
+ for (const resource of allResources) {
306
+ if (resource.model) {
307
+ resourceMap.set(resource.model, resource);
308
+ }
309
+ }
310
+ const resources = Array.from(resourceMap.values());
311
+
312
+ // Cache the results
313
+ resourceCache.set(manifest, resources);
314
+
315
+ return resources;
316
+ };
317
+
318
+ /**
319
+ * Clear a specific manifest from cache
320
+ */
321
+ export const clearResourceCache = (manifest: ResourceManifest): void => {
322
+ resourceCache.delete(manifest);
323
+ };
324
+
325
+ // ============================================================================
326
+ // HOOK: useDashResourceManifest
327
+ // ============================================================================
328
+
329
+ /**
330
+ * Hook to load resources from a manifest
331
+ *
332
+ * @param manifest - Object mapping names to import functions
333
+ * @returns Object with resources, loading state, error, and reload function
334
+ *
335
+ * @example
336
+ * ```tsx
337
+ * const manifest = {
338
+ * systemResources: () => import('dash-admin/src/systemResources'),
339
+ * myResource: () => import('./resources/myResource'),
340
+ * };
341
+ *
342
+ * const { resources, loading, error, reload } = useDashResourceManifest(manifest);
343
+ * ```
344
+ */
345
+ export const useDashResourceManifest = (
346
+ manifest: ResourceManifest
347
+ ): UseDashResourceManifestResult => {
348
+ const [resources, setResources] = useState<IDashAutoAdminResourceConfig[] | null>(null);
349
+ const [loading, setLoading] = useState(true);
350
+ const [error, setError] = useState<string | null>(null);
351
+ const [reloadTrigger, setReloadTrigger] = useState(0);
352
+
353
+ useEffect(() => {
354
+ let mounted = true;
355
+
356
+ const load = async () => {
357
+ setLoading(true);
358
+ setError(null);
359
+
360
+ try {
361
+ const loadedResources = await loadResourcesFromManifest(manifest);
362
+
363
+ if (mounted) {
364
+ console.log('📦 useDashResourceManifest: Loaded', loadedResources.length, 'resources from manifest');
365
+ setResources(loadedResources);
366
+ setLoading(false);
367
+ }
368
+ } catch (err) {
369
+ if (mounted) {
370
+ const errorMessage = err instanceof Error ? err.message : 'Failed to load resources';
371
+ console.error('❌ useDashResourceManifest:', errorMessage);
372
+ setError(errorMessage);
373
+ setLoading(false);
374
+ }
375
+ }
376
+ };
377
+
378
+ load();
379
+
380
+ return () => {
381
+ mounted = false;
382
+ };
383
+ }, [manifest, reloadTrigger]);
384
+
385
+ const reload = () => {
386
+ // Clear cache for fresh load
387
+ clearResourceCache(manifest);
388
+ setReloadTrigger(prev => prev + 1);
389
+ };
390
+
391
+ return { resources, loading, error, reload };
392
+ };
393
+
394
+ // ============================================================================
395
+ // COMPONENT: DashPrivateAppWithManifest
396
+ // ============================================================================
397
+
398
+ /**
399
+ * Default loading component for manifest loading
400
+ */
401
+ const DefaultManifestLoader: React.FC<{ message?: string }> = ({ message }) => (
402
+ <AppLoadingFallback message={message || "Loading resources..."} />
403
+ );
404
+
405
+ /**
406
+ * Default error component for manifest loading failures
407
+ */
408
+ const DefaultManifestError: React.FC<{ error: string; onRetry: () => void }> = ({ error, onRetry }) => (
409
+ <div style={{
410
+ display: 'flex',
411
+ flexDirection: 'column',
412
+ justifyContent: 'center',
413
+ alignItems: 'center',
414
+ height: '100vh',
415
+ backgroundColor: 'var(--dash-background, #121212)',
416
+ color: 'var(--dash-text, #ffffff)',
417
+ padding: '20px'
418
+ }}>
419
+ <h2 style={{ color: '#f44336', marginBottom: '16px' }}>Failed to load resources</h2>
420
+ <p style={{ marginBottom: '24px', opacity: 0.8, textAlign: 'center', maxWidth: '500px' }}>
421
+ {error}
422
+ </p>
423
+ <button
424
+ onClick={onRetry}
425
+ style={{
426
+ backgroundColor: 'var(--dash-primary, #90caf9)',
427
+ color: '#000',
428
+ border: 'none',
429
+ padding: '12px 24px',
430
+ borderRadius: '4px',
431
+ cursor: 'pointer',
432
+ fontSize: '14px',
433
+ fontWeight: 500
434
+ }}
435
+ >
436
+ Retry
437
+ </button>
438
+ </div>
439
+ );
440
+
441
+ /**
442
+ * DashPrivateAppWithManifest
443
+ *
444
+ * A wrapper around DashDefaultPrivateApp that loads resources from a manifest.
445
+ * This component handles the async loading of resources and passes them to the app.
446
+ *
447
+ * @example
448
+ * ```tsx
449
+ * import { DashPrivateAppWithManifest } from 'dash-app-common';
450
+ *
451
+ * // Define manifest with import functions (allows proper bundling)
452
+ * const resourceManifest = {
453
+ * systemResources: () => import('dash-admin/src/systemResources'),
454
+ * todoResource: () => import('./resources/todoResource'),
455
+ * };
456
+ *
457
+ * const DASHApp = () => (
458
+ * <DashPrivateAppWithManifest
459
+ * manifest={resourceManifest}
460
+ * appPath="/admin"
461
+ * />
462
+ * );
463
+ * ```
464
+ */
465
+ export const DashPrivateAppWithManifest: React.FC<DashPrivateAppWithManifestProps> = ({
466
+ manifest,
467
+ LoadingComponent = DefaultManifestLoader,
468
+ ErrorComponent = DefaultManifestError,
469
+ children,
470
+ ...privateAppProps
471
+ }) => {
472
+ const { resources, loading, error, reload } = useDashResourceManifest(manifest);
473
+
474
+ // Show loading state
475
+ if (loading) {
476
+ return <LoadingComponent message="Loading resources..." />;
477
+ }
478
+
479
+ // Show error state
480
+ if (error) {
481
+ return <ErrorComponent error={error} onRetry={reload} />;
482
+ }
483
+
484
+ // Render the private app with loaded resources
485
+ return (
486
+ <DashDefaultPrivateApp
487
+ resources={resources || []}
488
+ {...privateAppProps}
489
+ >
490
+ {children}
491
+ </DashDefaultPrivateApp>
492
+ );
493
+ };
494
+
495
+ // ============================================================================
496
+ // COMPONENT: DashPrivateApp (Unified)
497
+ // ============================================================================
498
+
499
+ /**
500
+ * DashPrivateApp
501
+ *
502
+ * A unified component that supports both manifest-based and direct resource loading.
503
+ *
504
+ * Use this when you need flexibility:
505
+ * - Pass `resources` as ResourceManifest for code-splitting with dynamic imports (main app)
506
+ * - Pass `resources` as IDashAutoAdminResourceConfig[] directly for immediate use (sub-apps like MallClient)
507
+ *
508
+ * The component automatically detects the format and handles loading accordingly.
509
+ *
510
+ * @example
511
+ * ```tsx
512
+ * // With manifest (code-splitting) - loads resources async
513
+ * <DashPrivateApp resources={DASHResourceManifest} />
514
+ *
515
+ * // With direct resources (no loading needed) - immediate render
516
+ * <DashPrivateApp resources={MallClientAppResources} />
517
+ * ```
518
+ */
519
+ export const DashPrivateApp: React.FC<DashPrivateAppProps> = ({
520
+ resources,
521
+ LoadingComponent = DefaultManifestLoader,
522
+ ErrorComponent = DefaultManifestError,
523
+ children,
524
+ ...privateAppProps
525
+ }) => {
526
+ // Case 1: Direct resource array - use immediately without loading
527
+ if (isResourceArray(resources)) {
528
+ return (
529
+ <DashDefaultPrivateApp
530
+ resources={resources}
531
+ {...privateAppProps}
532
+ >
533
+ {children}
534
+ </DashDefaultPrivateApp>
535
+ );
536
+ }
537
+
538
+ // Case 2: Resource manifest - load resources asynchronously
539
+ if (isResourceManifest(resources)) {
540
+ return (
541
+ <DashPrivateAppWithManifest
542
+ manifest={resources}
543
+ LoadingComponent={LoadingComponent}
544
+ ErrorComponent={ErrorComponent}
545
+ {...privateAppProps}
546
+ >
547
+ {children}
548
+ </DashPrivateAppWithManifest>
549
+ );
550
+ }
551
+
552
+ // Case 3: No resources provided - render with empty resources (will use defaults from DashDefaultPrivateApp)
553
+ console.warn('DashPrivateApp: No resources provided. Using empty resources array.');
554
+ return (
555
+ <DashDefaultPrivateApp
556
+ resources={[]}
557
+ {...privateAppProps}
558
+ >
559
+ {children}
560
+ </DashDefaultPrivateApp>
561
+ );
562
+ };
563
+
564
+ export default DashPrivateAppWithManifest;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Default Error Boundary Component
3
+ *
4
+ * Catches JavaScript errors anywhere in the child component tree,
5
+ * logs those errors, and displays a fallback UI.
6
+ */
7
+ import React from 'react';
8
+
9
+ export interface DashDefaultErrorBoundaryProps {
10
+ children: React.ReactNode;
11
+ /** Custom fallback component to render on error */
12
+ fallback?: React.ReactNode;
13
+ /** Callback when an error is caught */
14
+ onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
15
+ }
16
+
17
+ export interface DashDefaultErrorBoundaryState {
18
+ hasError: boolean;
19
+ error?: Error;
20
+ }
21
+
22
+ /**
23
+ * Default error display component
24
+ */
25
+ const DefaultErrorDisplay: React.FC<{ error?: Error; onReload: () => void }> = ({
26
+ error,
27
+ onReload
28
+ }) => (
29
+ <div style={{
30
+ display: 'flex',
31
+ justifyContent: 'center',
32
+ alignItems: 'center',
33
+ height: '100vh',
34
+ backgroundColor: '#121212',
35
+ color: '#ffffff',
36
+ flexDirection: 'column',
37
+ gap: '20px'
38
+ }}>
39
+ <h2>Something went wrong</h2>
40
+ {error && (
41
+ <p style={{
42
+ color: '#ff6b6b',
43
+ maxWidth: '600px',
44
+ textAlign: 'center',
45
+ padding: '0 20px'
46
+ }}>
47
+ {error.message}
48
+ </p>
49
+ )}
50
+ <button
51
+ onClick={onReload}
52
+ style={{
53
+ padding: '10px 20px',
54
+ backgroundColor: '#007bff',
55
+ color: 'white',
56
+ border: 'none',
57
+ borderRadius: '4px',
58
+ cursor: 'pointer'
59
+ }}
60
+ >
61
+ Reload Page
62
+ </button>
63
+ </div>
64
+ );
65
+
66
+ /**
67
+ * Dash Default Error boundary for catching and handling React component errors
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * <DashDefaultErrorBoundary onError={(error) => logError(error)}>
72
+ * <App />
73
+ * </DashDefaultErrorBoundary>
74
+ * ```
75
+ */
76
+ export class DashDefaultErrorBoundary extends React.Component<DashDefaultErrorBoundaryProps, DashDefaultErrorBoundaryState> {
77
+ constructor(props: DashDefaultErrorBoundaryProps) {
78
+ super(props);
79
+ this.state = { hasError: false };
80
+ }
81
+
82
+ static getDerivedStateFromError(error: Error): DashDefaultErrorBoundaryState {
83
+ return { hasError: true, error };
84
+ }
85
+
86
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
87
+ console.error('DashDefaultErrorBoundary caught an error:', error, errorInfo);
88
+ this.props.onError?.(error, errorInfo);
89
+ }
90
+
91
+ handleReload = (): void => {
92
+ window.location.reload();
93
+ };
94
+
95
+ render(): React.ReactNode {
96
+ if (this.state.hasError) {
97
+ if (this.props.fallback) {
98
+ return this.props.fallback;
99
+ }
100
+
101
+ return (
102
+ <DefaultErrorDisplay
103
+ error={this.state.error}
104
+ onReload={this.handleReload}
105
+ />
106
+ );
107
+ }
108
+
109
+ return this.props.children;
110
+ }
111
+ }
112
+
113
+ export default DashDefaultErrorBoundary;