@object-ui/app-shell 3.3.0 → 3.3.1

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 (173) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +22 -2
  3. package/dist/chrome/CommandPalette.d.ts +16 -0
  4. package/dist/chrome/CommandPalette.js +88 -0
  5. package/dist/chrome/ConditionalAuthWrapper.d.ts +23 -0
  6. package/dist/chrome/ConditionalAuthWrapper.js +135 -0
  7. package/dist/chrome/ConsoleToaster.d.ts +11 -0
  8. package/dist/chrome/ConsoleToaster.js +28 -0
  9. package/dist/chrome/ErrorBoundary.d.ts +39 -0
  10. package/dist/chrome/ErrorBoundary.js +64 -0
  11. package/dist/chrome/KeyboardShortcutsDialog.d.ts +7 -0
  12. package/dist/chrome/KeyboardShortcutsDialog.js +63 -0
  13. package/dist/chrome/LoadingScreen.d.ts +12 -0
  14. package/dist/chrome/LoadingScreen.js +44 -0
  15. package/dist/chrome/OnboardingWalkthrough.d.ts +9 -0
  16. package/dist/chrome/OnboardingWalkthrough.js +11 -0
  17. package/dist/chrome/ThemeProvider.d.ts +13 -0
  18. package/dist/chrome/ThemeProvider.js +37 -0
  19. package/dist/chrome/index.d.ts +8 -0
  20. package/dist/chrome/index.js +8 -0
  21. package/dist/console/AppContent.d.ts +26 -0
  22. package/dist/console/AppContent.js +266 -0
  23. package/dist/console/ConsoleShell.d.ts +66 -0
  24. package/dist/console/ConsoleShell.js +97 -0
  25. package/dist/console/auth/AuthPageLayout.d.ts +9 -0
  26. package/dist/console/auth/AuthPageLayout.js +4 -0
  27. package/dist/console/auth/ForgotPasswordPage.d.ts +4 -0
  28. package/dist/console/auth/ForgotPasswordPage.js +23 -0
  29. package/dist/console/auth/LoginPage.d.ts +4 -0
  30. package/dist/console/auth/LoginPage.js +24 -0
  31. package/dist/console/auth/RegisterPage.d.ts +4 -0
  32. package/dist/console/auth/RegisterPage.js +29 -0
  33. package/dist/console/auth/index.d.ts +4 -0
  34. package/dist/console/auth/index.js +4 -0
  35. package/dist/console/home/AppCard.d.ts +14 -0
  36. package/dist/console/home/AppCard.js +36 -0
  37. package/dist/console/home/HomeLayout.d.ts +15 -0
  38. package/dist/console/home/HomeLayout.js +20 -0
  39. package/dist/console/home/HomePage.d.ts +18 -0
  40. package/dist/console/home/HomePage.js +54 -0
  41. package/dist/console/home/QuickActions.d.ts +9 -0
  42. package/dist/console/home/QuickActions.js +53 -0
  43. package/dist/console/home/RecentApps.d.ts +13 -0
  44. package/dist/console/home/RecentApps.js +32 -0
  45. package/dist/console/home/StarredApps.d.ts +13 -0
  46. package/dist/console/home/StarredApps.js +29 -0
  47. package/dist/console/home/index.d.ts +11 -0
  48. package/dist/console/home/index.js +11 -0
  49. package/dist/console/organizations/CreateWorkspaceDialog.d.ts +16 -0
  50. package/dist/console/organizations/CreateWorkspaceDialog.js +71 -0
  51. package/dist/console/organizations/OrganizationsLayout.d.ts +15 -0
  52. package/dist/console/organizations/OrganizationsLayout.js +20 -0
  53. package/dist/console/organizations/OrganizationsPage.d.ts +9 -0
  54. package/dist/console/organizations/OrganizationsPage.js +76 -0
  55. package/dist/console/organizations/index.d.ts +8 -0
  56. package/dist/console/organizations/index.js +8 -0
  57. package/dist/console/organizations/manage/AcceptInvitationPage.d.ts +7 -0
  58. package/dist/console/organizations/manage/AcceptInvitationPage.js +100 -0
  59. package/dist/console/organizations/manage/InvitationsPage.d.ts +6 -0
  60. package/dist/console/organizations/manage/InvitationsPage.js +107 -0
  61. package/dist/console/organizations/manage/InviteMemberDialog.d.ts +16 -0
  62. package/dist/console/organizations/manage/InviteMemberDialog.js +83 -0
  63. package/dist/console/organizations/manage/MembersPage.d.ts +6 -0
  64. package/dist/console/organizations/manage/MembersPage.js +89 -0
  65. package/dist/console/organizations/manage/OrganizationLayout.d.ts +8 -0
  66. package/dist/console/organizations/manage/OrganizationLayout.js +51 -0
  67. package/dist/console/organizations/manage/SettingsPage.d.ts +6 -0
  68. package/dist/console/organizations/manage/SettingsPage.js +141 -0
  69. package/dist/console/organizations/manage/orgContext.d.ts +5 -0
  70. package/dist/console/organizations/manage/orgContext.js +4 -0
  71. package/dist/context/FavoritesProvider.d.ts +50 -0
  72. package/dist/context/FavoritesProvider.js +129 -0
  73. package/dist/context/NavigationContext.d.ts +46 -0
  74. package/dist/context/NavigationContext.js +59 -0
  75. package/dist/context/index.d.ts +3 -0
  76. package/dist/context/index.js +2 -0
  77. package/dist/hooks/index.d.ts +7 -0
  78. package/dist/hooks/index.js +7 -0
  79. package/dist/hooks/useFavorites.d.ts +13 -0
  80. package/dist/hooks/useFavorites.js +13 -0
  81. package/dist/hooks/useMetadataService.d.ts +13 -0
  82. package/dist/hooks/useMetadataService.js +18 -0
  83. package/dist/hooks/useNavPins.d.ts +16 -0
  84. package/dist/hooks/useNavPins.js +72 -0
  85. package/dist/hooks/useNavigationSync.d.ts +82 -0
  86. package/dist/hooks/useNavigationSync.js +495 -0
  87. package/dist/hooks/useObjectActions.d.ts +38 -0
  88. package/dist/hooks/useObjectActions.js +92 -0
  89. package/dist/hooks/useRecentItems.d.ts +21 -0
  90. package/dist/hooks/useRecentItems.js +50 -0
  91. package/dist/hooks/useResponsiveSidebar.d.ts +8 -0
  92. package/dist/hooks/useResponsiveSidebar.js +28 -0
  93. package/dist/index.d.ts +29 -0
  94. package/dist/index.js +35 -0
  95. package/dist/layout/ActivityFeed.d.ts +22 -0
  96. package/dist/layout/ActivityFeed.js +57 -0
  97. package/dist/layout/AppHeader.d.ts +33 -0
  98. package/dist/layout/AppHeader.js +182 -0
  99. package/dist/layout/AppSidebar.d.ts +14 -0
  100. package/dist/layout/AppSidebar.js +217 -0
  101. package/dist/layout/AppSwitcher.d.ts +12 -0
  102. package/dist/layout/AppSwitcher.js +39 -0
  103. package/dist/layout/AuthPageLayout.d.ts +9 -0
  104. package/dist/layout/AuthPageLayout.js +4 -0
  105. package/dist/layout/ConnectionStatus.d.ts +14 -0
  106. package/dist/layout/ConnectionStatus.js +36 -0
  107. package/dist/layout/ConsoleLayout.d.ts +21 -0
  108. package/dist/layout/ConsoleLayout.js +71 -0
  109. package/dist/layout/LocaleSwitcher.d.ts +7 -0
  110. package/dist/layout/LocaleSwitcher.js +27 -0
  111. package/dist/layout/ModeToggle.d.ts +1 -0
  112. package/dist/layout/ModeToggle.js +10 -0
  113. package/dist/layout/UnifiedSidebar.d.ts +22 -0
  114. package/dist/layout/UnifiedSidebar.js +210 -0
  115. package/dist/layout/index.d.ts +11 -0
  116. package/dist/layout/index.js +10 -0
  117. package/dist/providers/AdapterProvider.d.ts +21 -0
  118. package/dist/providers/AdapterProvider.js +52 -0
  119. package/dist/providers/ExpressionProvider.d.ts +50 -0
  120. package/dist/providers/ExpressionProvider.js +67 -0
  121. package/dist/providers/MetadataProvider.d.ts +18 -0
  122. package/dist/providers/MetadataProvider.js +324 -0
  123. package/dist/services/MetadataService.d.ts +119 -0
  124. package/dist/services/MetadataService.js +211 -0
  125. package/dist/skeletons/SkeletonDashboard.d.ts +13 -0
  126. package/dist/skeletons/SkeletonDashboard.js +12 -0
  127. package/dist/skeletons/SkeletonDetail.d.ts +15 -0
  128. package/dist/skeletons/SkeletonDetail.js +12 -0
  129. package/dist/skeletons/SkeletonGrid.d.ts +15 -0
  130. package/dist/skeletons/SkeletonGrid.js +12 -0
  131. package/dist/skeletons/index.d.ts +3 -0
  132. package/dist/skeletons/index.js +3 -0
  133. package/dist/utils/getIcon.d.ts +10 -0
  134. package/dist/utils/getIcon.js +24 -0
  135. package/dist/utils/index.d.ts +33 -0
  136. package/dist/utils/index.js +62 -0
  137. package/dist/utils/metadataConverters.d.ts +78 -0
  138. package/dist/utils/metadataConverters.js +74 -0
  139. package/dist/views/ActionConfirmDialog.d.ts +22 -0
  140. package/dist/views/ActionConfirmDialog.js +22 -0
  141. package/dist/views/ActionParamDialog.d.ts +23 -0
  142. package/dist/views/ActionParamDialog.js +67 -0
  143. package/dist/views/DashboardView.d.ts +9 -0
  144. package/dist/views/DashboardView.js +357 -0
  145. package/dist/views/DesignDrawer.d.ts +28 -0
  146. package/dist/views/DesignDrawer.js +51 -0
  147. package/dist/views/MetadataInspector.d.ts +40 -0
  148. package/dist/views/MetadataInspector.js +69 -0
  149. package/dist/views/ObjectView.d.ts +14 -0
  150. package/dist/views/ObjectView.js +770 -0
  151. package/dist/views/PageView.d.ts +6 -0
  152. package/dist/views/PageView.js +49 -0
  153. package/dist/views/RecordDetailView.d.ts +14 -0
  154. package/dist/views/RecordDetailView.js +508 -0
  155. package/dist/views/ReportView.d.ts +4 -0
  156. package/dist/views/ReportView.js +284 -0
  157. package/dist/views/SearchResultsPage.d.ts +9 -0
  158. package/dist/views/SearchResultsPage.js +107 -0
  159. package/dist/views/ViewConfigPanel.d.ts +56 -0
  160. package/dist/views/ViewConfigPanel.js +91 -0
  161. package/dist/views/index.d.ts +11 -0
  162. package/dist/views/index.js +11 -0
  163. package/package.json +59 -11
  164. package/.turbo/turbo-build.log +0 -4
  165. package/src/components/AppShell.tsx +0 -31
  166. package/src/components/DashboardRenderer.tsx +0 -36
  167. package/src/components/FormRenderer.tsx +0 -71
  168. package/src/components/ObjectRenderer.tsx +0 -122
  169. package/src/components/PageRenderer.tsx +0 -28
  170. package/src/index.ts +0 -20
  171. package/src/types.ts +0 -78
  172. package/tsconfig.json +0 -20
  173. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,11 @@
1
+ /**
2
+ * OnboardingWalkthrough
3
+ *
4
+ * Intentionally disabled. The first-time onboarding dialog has been
5
+ * suppressed per product decision. To re-enable, restore the full
6
+ * implementation from git history (see the commit that introduced this stub).
7
+ * @module
8
+ */
9
+ export function OnboardingWalkthrough() {
10
+ return null;
11
+ }
@@ -0,0 +1,13 @@
1
+ type Theme = "dark" | "light" | "system";
2
+ type ThemeProviderProps = {
3
+ children: React.ReactNode;
4
+ defaultTheme?: Theme;
5
+ storageKey?: string;
6
+ };
7
+ type ThemeProviderState = {
8
+ theme: Theme;
9
+ setTheme: (theme: Theme) => void;
10
+ };
11
+ export declare function ThemeProvider({ children, defaultTheme, storageKey, ...props }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare const useTheme: () => ThemeProviderState;
13
+ export {};
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
+ const initialState = {
4
+ theme: "system",
5
+ setTheme: () => null,
6
+ };
7
+ const ThemeProviderContext = createContext(initialState);
8
+ export function ThemeProvider({ children, defaultTheme = "system", storageKey = "vite-ui-theme", ...props }) {
9
+ const [theme, setTheme] = useState(() => localStorage.getItem(storageKey) || defaultTheme);
10
+ useEffect(() => {
11
+ const root = window.document.documentElement;
12
+ root.classList.remove("light", "dark");
13
+ if (theme === "system") {
14
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
15
+ .matches
16
+ ? "dark"
17
+ : "light";
18
+ root.classList.add(systemTheme);
19
+ return;
20
+ }
21
+ root.classList.add(theme);
22
+ }, [theme]);
23
+ const value = {
24
+ theme,
25
+ setTheme: (theme) => {
26
+ localStorage.setItem(storageKey, theme);
27
+ setTheme(theme);
28
+ },
29
+ };
30
+ return (_jsx(ThemeProviderContext.Provider, { ...props, value: value, children: children }));
31
+ }
32
+ export const useTheme = () => {
33
+ const context = useContext(ThemeProviderContext);
34
+ if (context === undefined)
35
+ throw new Error("useTheme must be used within a ThemeProvider");
36
+ return context;
37
+ };
@@ -0,0 +1,8 @@
1
+ export { CommandPalette } from './CommandPalette';
2
+ export { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
3
+ export { OnboardingWalkthrough } from './OnboardingWalkthrough';
4
+ export { ConditionalAuthWrapper } from './ConditionalAuthWrapper';
5
+ export { ConsoleToaster } from './ConsoleToaster';
6
+ export { ErrorBoundary } from './ErrorBoundary';
7
+ export { LoadingScreen } from './LoadingScreen';
8
+ export { ThemeProvider, useTheme } from './ThemeProvider';
@@ -0,0 +1,8 @@
1
+ export { CommandPalette } from './CommandPalette';
2
+ export { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
3
+ export { OnboardingWalkthrough } from './OnboardingWalkthrough';
4
+ export { ConditionalAuthWrapper } from './ConditionalAuthWrapper';
5
+ export { ConsoleToaster } from './ConsoleToaster';
6
+ export { ErrorBoundary } from './ErrorBoundary';
7
+ export { LoadingScreen } from './LoadingScreen';
8
+ export { ThemeProvider, useTheme } from './ThemeProvider';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * AppContent — inner SPA rendered under /apps/:appName/*.
3
+ *
4
+ * Owns the per-app shell: ConsoleLayout, CommandPalette, KeyboardShortcutsDialog,
5
+ * route table for object/dashboard/report/page views, and the global ModalForm
6
+ * used by ObjectView edit actions. The outer routing skeleton (BrowserRouter,
7
+ * AuthGuard, AdapterProvider, MetadataProvider, theme/toaster, /home, /login,
8
+ * /organizations) is provided by `createConsole` from @object-ui/app-shell.
9
+ */
10
+ import { type ReactNode } from 'react';
11
+ interface AppContentProps {
12
+ /**
13
+ * Extra <Route> elements appended to the inner /apps/:appName/* router.
14
+ * Hosts can use this to mount console-specific routes (e.g. /system, legacy
15
+ * metadata editor) without forking AppContent.
16
+ */
17
+ extraRoutes?: ReactNode;
18
+ /**
19
+ * Extra <Route> elements rendered when there is no active app but the URL
20
+ * matches a special path (create-app, system). Mirrors `extraRoutes` for
21
+ * the no-app branch.
22
+ */
23
+ extraRoutesNoApp?: ReactNode;
24
+ }
25
+ export declare function AppContent({ extraRoutes, extraRoutesNoApp }?: AppContentProps): import("react/jsx-runtime").JSX.Element;
26
+ export {};
@@ -0,0 +1,266 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * AppContent — inner SPA rendered under /apps/:appName/*.
4
+ *
5
+ * Owns the per-app shell: ConsoleLayout, CommandPalette, KeyboardShortcutsDialog,
6
+ * route table for object/dashboard/report/page views, and the global ModalForm
7
+ * used by ObjectView edit actions. The outer routing skeleton (BrowserRouter,
8
+ * AuthGuard, AdapterProvider, MetadataProvider, theme/toaster, /home, /login,
9
+ * /organizations) is provided by `createConsole` from @object-ui/app-shell.
10
+ */
11
+ import { Routes, Route, Navigate, useNavigate, useLocation, useParams } from 'react-router-dom';
12
+ import { useState, useEffect, useCallback, lazy, Suspense, useMemo } from 'react';
13
+ import { ModalForm } from '@object-ui/plugin-form';
14
+ import { Empty, EmptyTitle, EmptyDescription, Button } from '@object-ui/components';
15
+ import { toast } from 'sonner';
16
+ import { SchemaRendererProvider, useActionRunner, useGlobalUndo } from '@object-ui/react';
17
+ import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
18
+ import { useAuth } from '@object-ui/auth';
19
+ import { useMetadata } from '../providers/MetadataProvider';
20
+ import { useAdapter } from '../providers/AdapterProvider';
21
+ import { ExpressionProvider, evaluateVisibility } from '../providers/ExpressionProvider';
22
+ import { useRecentItems } from '../hooks/useRecentItems';
23
+ import { ExpressionEvaluator } from '@object-ui/core';
24
+ // Components (eagerly loaded — always needed)
25
+ import { ConsoleLayout } from '../layout/ConsoleLayout';
26
+ import { CommandPalette } from '../chrome/CommandPalette';
27
+ import { ErrorBoundary } from '../chrome/ErrorBoundary';
28
+ import { LoadingScreen } from '../chrome/LoadingScreen';
29
+ import { ObjectView } from '../views/ObjectView';
30
+ import { KeyboardShortcutsDialog } from '../chrome/KeyboardShortcutsDialog';
31
+ import { OnboardingWalkthrough } from '../chrome/OnboardingWalkthrough';
32
+ import { NavigationSyncEffect } from '../hooks/useNavigationSync';
33
+ // Route-based code splitting — lazy-load less-frequently-used routes
34
+ const RecordDetailView = lazy(() => import('../views/RecordDetailView').then(m => ({ default: m.RecordDetailView })));
35
+ const DashboardView = lazy(() => import('../views/DashboardView').then(m => ({ default: m.DashboardView })));
36
+ const PageView = lazy(() => import('../views/PageView').then(m => ({ default: m.PageView })));
37
+ const ReportView = lazy(() => import('../views/ReportView').then(m => ({ default: m.ReportView })));
38
+ const SearchResultsPage = lazy(() => import('../views/SearchResultsPage').then(m => ({ default: m.SearchResultsPage })));
39
+ // Designer pages — sourced from @object-ui/plugin-designer so third-party hosts
40
+ // can opt out by not registering these routes.
41
+ const CreateAppPage = lazy(() => import('@object-ui/plugin-designer').then(m => ({ default: m.CreateAppPage })));
42
+ const EditAppPage = lazy(() => import('@object-ui/plugin-designer').then(m => ({ default: m.EditAppPage })));
43
+ const PageDesignPage = lazy(() => import('@object-ui/plugin-designer').then(m => ({ default: m.PageDesignPage })));
44
+ const DashboardDesignPage = lazy(() => import('@object-ui/plugin-designer').then(m => ({ default: m.DashboardDesignPage })));
45
+ export function AppContent({ extraRoutes, extraRoutesNoApp } = {}) {
46
+ const [connectionState, setConnectionState] = useState('disconnected');
47
+ const { user } = useAuth();
48
+ const dataSource = useAdapter();
49
+ const navigate = useNavigate();
50
+ const location = useLocation();
51
+ const { appName } = useParams();
52
+ const { apps, objects: allObjects, loading: metadataLoading, ensureType } = useMetadata();
53
+ const { t } = useObjectTranslation();
54
+ const { objectLabel } = useObjectLabel();
55
+ // Preload the metadata buckets that the routes under /apps/:appName/* assume
56
+ // are fully loaded by render time (the lazy MetadataProvider only eagerly
57
+ // loads `app`).
58
+ const [scopeMetaReady, setScopeMetaReady] = useState(!ensureType);
59
+ useEffect(() => {
60
+ if (!ensureType) {
61
+ setScopeMetaReady(true);
62
+ return;
63
+ }
64
+ let cancelled = false;
65
+ Promise.all([
66
+ ensureType('object'),
67
+ ensureType('dashboard'),
68
+ ensureType('report'),
69
+ ensureType('page'),
70
+ ]).finally(() => {
71
+ if (!cancelled)
72
+ setScopeMetaReady(true);
73
+ });
74
+ return () => { cancelled = true; };
75
+ }, [ensureType]);
76
+ const activeApps = apps.filter((a) => a.active !== false);
77
+ const activeApp = apps.find((a) => a.name === appName) ||
78
+ activeApps.find((a) => a.isDefault === true) ||
79
+ activeApps[0];
80
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
81
+ const [editingRecord, setEditingRecord] = useState(null);
82
+ const [refreshKey, setRefreshKey] = useState(0);
83
+ const { addRecentItem } = useRecentItems();
84
+ const { execute: executeAction, runner } = useActionRunner();
85
+ useGlobalUndo({
86
+ dataSource: dataSource ?? undefined,
87
+ onUndo: (op) => {
88
+ toast.info(`Undo: ${op.description}`, { duration: 4000 });
89
+ setRefreshKey(k => k + 1);
90
+ },
91
+ onRedo: (op) => {
92
+ toast.info(`Redo: ${op.description}`, { duration: 3000 });
93
+ setRefreshKey(k => k + 1);
94
+ },
95
+ });
96
+ useEffect(() => {
97
+ runner.registerHandler('crud_success', async (action) => {
98
+ setIsDialogOpen(false);
99
+ setRefreshKey(k => k + 1);
100
+ toast.success(action.params?.message ?? 'Record saved successfully');
101
+ return { success: true, reload: true };
102
+ });
103
+ runner.registerHandler('dialog_cancel', async () => {
104
+ setIsDialogOpen(false);
105
+ return { success: true };
106
+ });
107
+ // NOTE: `flow` actions are handled at the per-view ActionProvider level
108
+ // (RecordDetailView / ObjectView) so they share the same ActionRunner that
109
+ // <action:button> renderers consume via useAction(). Do NOT register a
110
+ // `flow` handler on this top-level useActionRunner — it lives on a
111
+ // different ActionRunner instance and would never be invoked from the
112
+ // record/list action buttons.
113
+ }, [runner]);
114
+ useEffect(() => {
115
+ if (!dataSource)
116
+ return;
117
+ const unsub = dataSource.onConnectionStateChange((event) => {
118
+ setConnectionState(event.state);
119
+ if (event.error)
120
+ console.error('[Console] Connection error:', event.error);
121
+ });
122
+ setConnectionState(dataSource.getConnectionState());
123
+ return unsub;
124
+ }, [dataSource]);
125
+ const cleanParts = location.pathname.split('/').filter(Boolean);
126
+ let objectNameFromPath = cleanParts[2];
127
+ if (objectNameFromPath === 'view' ||
128
+ objectNameFromPath === 'record' ||
129
+ objectNameFromPath === 'page' ||
130
+ objectNameFromPath === 'dashboard' ||
131
+ objectNameFromPath === 'design') {
132
+ objectNameFromPath = '';
133
+ }
134
+ const currentObjectDef = allObjects.find((o) => o.name === objectNameFromPath);
135
+ const handleCrudSuccess = useCallback(() => {
136
+ const label = currentObjectDef ? objectLabel(currentObjectDef) : t('common.record', { defaultValue: 'Record' });
137
+ executeAction({
138
+ type: 'crud_success',
139
+ params: {
140
+ message: editingRecord
141
+ ? t('form.updateSuccess', { object: label, defaultValue: `${label} updated successfully` })
142
+ : t('form.createSuccess', { object: label, defaultValue: `${label} created successfully` }),
143
+ },
144
+ });
145
+ }, [executeAction, editingRecord, currentObjectDef, objectLabel, t]);
146
+ const handleDialogCancel = useCallback(() => {
147
+ executeAction({ type: 'dialog_cancel' });
148
+ }, [executeAction]);
149
+ // Track recent items on route change.
150
+ useEffect(() => {
151
+ if (!activeApp)
152
+ return;
153
+ const parts = location.pathname.split('/').filter(Boolean);
154
+ let objName = parts[2];
155
+ if (objName === 'view' || objName === 'record' || objName === 'page' || objName === 'dashboard' || objName === 'design') {
156
+ objName = '';
157
+ }
158
+ const basePath = `/apps/${activeApp.name}`;
159
+ if (objName) {
160
+ const obj = allObjects.find((o) => o.name === objName);
161
+ if (obj) {
162
+ addRecentItem({
163
+ id: `object:${obj.name}`,
164
+ label: obj.label || obj.name,
165
+ href: `${basePath}/${obj.name}`,
166
+ type: 'object',
167
+ });
168
+ }
169
+ }
170
+ else if (parts[2] === 'dashboard' && parts[3]) {
171
+ addRecentItem({
172
+ id: `dashboard:${parts[3]}`,
173
+ label: parts[3].replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
174
+ href: `${basePath}/dashboard/${parts[3]}`,
175
+ type: 'dashboard',
176
+ });
177
+ }
178
+ else if (parts[2] === 'report' && parts[3]) {
179
+ addRecentItem({
180
+ id: `report:${parts[3]}`,
181
+ label: parts[3].replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
182
+ href: `${basePath}/report/${parts[3]}`,
183
+ type: 'report',
184
+ });
185
+ }
186
+ }, [location.pathname, addRecentItem]); // eslint-disable-line react-hooks/exhaustive-deps
187
+ const handleEdit = (record) => {
188
+ setEditingRecord(record);
189
+ setIsDialogOpen(true);
190
+ };
191
+ const handleAppChange = (newAppName) => {
192
+ navigate(`/apps/${newAppName}`);
193
+ };
194
+ const expressionEvaluator = useMemo(() => new ExpressionEvaluator({
195
+ user: user ? { name: user.name, email: user.email, role: user.role ?? 'user' } : {},
196
+ app: activeApp || {},
197
+ data: editingRecord || {},
198
+ }), [user, activeApp, editingRecord]);
199
+ if (!dataSource || metadataLoading || !scopeMetaReady)
200
+ return _jsx(LoadingScreen, {});
201
+ const isCreateAppRoute = location.pathname.endsWith('/create-app');
202
+ const isSystemRoute = location.pathname.includes('/system');
203
+ if (!activeApp && !isCreateAppRoute && !isSystemRoute)
204
+ return (_jsx("div", { className: "h-screen flex items-center justify-center", children: _jsxs(Empty, { children: [_jsx(EmptyTitle, { children: "No Apps Configured" }), _jsx(EmptyDescription, { children: "No applications have been registered. Create your first app or visit System Settings to configure your environment." }), _jsxs("div", { className: "mt-4 flex flex-col sm:flex-row items-center gap-3", children: [_jsx(Button, { onClick: () => navigate('/create-app'), "data-testid": "create-first-app-btn", children: "Create Your First App" }), _jsx(Button, { variant: "outline", onClick: () => navigate('/apps/setup'), "data-testid": "go-to-settings-btn", children: "System Settings" })] })] }) }));
205
+ if (!activeApp && (isCreateAppRoute || isSystemRoute)) {
206
+ return (_jsx(Suspense, { fallback: _jsx(LoadingScreen, {}), children: _jsxs(Routes, { children: [_jsx(Route, { path: "create-app", element: _jsx(CreateAppPage, {}) }), extraRoutesNoApp] }) }));
207
+ }
208
+ const expressionUser = user
209
+ ? { name: user.name, email: user.email, role: user.role ?? 'user' }
210
+ : { name: 'Anonymous', email: '', role: 'guest' };
211
+ return (_jsxs(ExpressionProvider, { user: expressionUser, app: activeApp, data: {}, children: [_jsx(NavigationSyncEffect, {}), _jsxs(ConsoleLayout, { activeAppName: activeApp.name, activeApp: activeApp, onAppChange: handleAppChange, objects: allObjects, connectionState: connectionState, children: [_jsx(CommandPalette, { apps: apps, activeApp: activeApp, objects: allObjects, onAppChange: handleAppChange }), _jsx(KeyboardShortcutsDialog, {}), _jsx(OnboardingWalkthrough, {}), _jsxs(SchemaRendererProvider, { dataSource: dataSource || {}, children: [_jsx(ErrorBoundary, { children: _jsx(Suspense, { fallback: _jsx(LoadingScreen, {}), children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: findFirstRoute(activeApp.navigation || []), replace: true }) }), _jsx(Route, { path: ":objectName", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit }) }), _jsx(Route, { path: ":objectName/view/:viewId", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit }) }), _jsx(Route, { path: ":objectName/record/:recordId", element: _jsx(RecordDetailView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit }, refreshKey) }), _jsx(Route, { path: "dashboard/:dashboardName", element: _jsx(DashboardView, { dataSource: dataSource }) }), _jsx(Route, { path: "report/:reportName", element: _jsx(ReportView, { dataSource: dataSource }) }), _jsx(Route, { path: "page/:pageName", element: _jsx(PageView, {}) }), _jsx(Route, { path: "design/page/:pageName", element: _jsx(PageDesignPage, {}) }), _jsx(Route, { path: "design/dashboard/:dashboardName", element: _jsx(DashboardDesignPage, {}) }), _jsx(Route, { path: "search", element: _jsx(SearchResultsPage, {}) }), _jsx(Route, { path: "create-app", element: _jsx(CreateAppPage, {}) }), _jsx(Route, { path: "edit-app/:editAppName", element: _jsx(EditAppPage, {}) }), extraRoutes] }) }) }), currentObjectDef && (_jsx(ModalForm, { schema: {
212
+ type: 'object-form',
213
+ formType: 'modal',
214
+ objectName: currentObjectDef.name,
215
+ mode: editingRecord ? 'edit' : 'create',
216
+ recordId: editingRecord?.id,
217
+ title: editingRecord
218
+ ? t('form.editTitle', { object: objectLabel(currentObjectDef) })
219
+ : t('form.createTitle', { object: objectLabel(currentObjectDef) }),
220
+ description: editingRecord
221
+ ? t('form.editDescription', { object: objectLabel(currentObjectDef) })
222
+ : t('form.createDescription', { object: objectLabel(currentObjectDef) }),
223
+ open: isDialogOpen,
224
+ onOpenChange: setIsDialogOpen,
225
+ layout: 'vertical',
226
+ fields: currentObjectDef.fields
227
+ ? (Array.isArray(currentObjectDef.fields)
228
+ ? currentObjectDef.fields
229
+ .filter((f) => {
230
+ if (typeof f === 'string')
231
+ return true;
232
+ return evaluateVisibility(f.visible, expressionEvaluator);
233
+ })
234
+ .map((f) => typeof f === 'string' ? f : f.name)
235
+ : Object.entries(currentObjectDef.fields)
236
+ .filter(([_, f]) => evaluateVisibility(f.visible, expressionEvaluator))
237
+ .map(([key]) => key))
238
+ : [],
239
+ onSuccess: handleCrudSuccess,
240
+ onCancel: handleDialogCancel,
241
+ showSubmit: true,
242
+ showCancel: true,
243
+ submitText: t('form.saveRecord'),
244
+ cancelText: t('common.cancel'),
245
+ }, dataSource: dataSource }, editingRecord?.id || 'new'))] })] })] }));
246
+ }
247
+ function findFirstRoute(items) {
248
+ if (!items || items.length === 0)
249
+ return '';
250
+ for (const item of items) {
251
+ if (item.type === 'object')
252
+ return item.viewName ? `${item.objectName}/view/${item.viewName}` : `${item.objectName}`;
253
+ if (item.type === 'page')
254
+ return item.pageName ? `page/${item.pageName}` : '';
255
+ if (item.type === 'dashboard')
256
+ return item.dashboardName ? `dashboard/${item.dashboardName}` : '';
257
+ if (item.type === 'url')
258
+ continue;
259
+ if (item.type === 'group' && item.children) {
260
+ const childRoute = findFirstRoute(item.children);
261
+ if (childRoute !== '')
262
+ return childRoute;
263
+ }
264
+ }
265
+ return '';
266
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Console building blocks — composable JSX elements that consumers assemble in
3
+ * their own App.tsx to build the console routing tree.
4
+ *
5
+ * Previously this module exported a `createConsole(config)` factory that hid the
6
+ * routing tree behind a config object. In practice every real project wants to
7
+ * edit the routes directly (add /billing, tweak AuthGuard behaviour, reorder
8
+ * providers), so we now export the pieces and let consumers write ~40 lines of
9
+ * JSX in App.tsx. See examples/console-starter/src/App.tsx for a minimal example,
10
+ * apps/console/src/App.tsx for one with custom system routes + CreateApp.
11
+ */
12
+ import { type ReactNode } from 'react';
13
+ export declare function LoadingFallback(): import("react/jsx-runtime").JSX.Element;
14
+ /**
15
+ * ConsoleShell — top-level provider stack shared by every console route.
16
+ * Wraps children in ThemeProvider + NavigationProvider + FavoritesProvider +
17
+ * Suspense so lazy route components get a default loading fallback and
18
+ * dark/light/system theme switching works out of the box.
19
+ *
20
+ * Place this inside a <BrowserRouter> and around your <Routes>:
21
+ *
22
+ * <BrowserRouter>
23
+ * <ConsoleShell>
24
+ * <Routes>...</Routes>
25
+ * </ConsoleShell>
26
+ * </BrowserRouter>
27
+ */
28
+ export declare function ConsoleShell({ children }: {
29
+ children: ReactNode;
30
+ }): import("react/jsx-runtime").JSX.Element;
31
+ /**
32
+ * ConnectedShell — mounts the data layer (AdapterProvider + MetadataProvider).
33
+ * Use this around any route element that needs metadata access, i.e. anything
34
+ * rendering objects / dashboards / pages.
35
+ */
36
+ export declare function ConnectedShell({ children }: {
37
+ children: ReactNode;
38
+ }): import("react/jsx-runtime").JSX.Element;
39
+ /**
40
+ * RequireOrganization — redirects to /organizations when the multi-tenant
41
+ * feature is enabled (user has orgs but no active one). Single-tenant
42
+ * deployments (empty organizations list) render through.
43
+ */
44
+ export declare function RequireOrganization({ children }: {
45
+ children: ReactNode;
46
+ }): import("react/jsx-runtime").JSX.Element;
47
+ /**
48
+ * AuthenticatedRoute — convenience wrapper composing AuthGuard + ConnectedShell
49
+ * (+ optional RequireOrganization). Covers the common case for protected
50
+ * routes. For bespoke needs, compose the primitives directly.
51
+ */
52
+ export declare function AuthenticatedRoute({ children, requireOrganization, loginPath, }: {
53
+ children: ReactNode;
54
+ requireOrganization?: boolean;
55
+ loginPath?: string;
56
+ }): import("react/jsx-runtime").JSX.Element;
57
+ /**
58
+ * RootRedirect — element for <Route path="/" />. Waits for metadata to load
59
+ * then sends the user to /home.
60
+ */
61
+ export declare function RootRedirect(): import("react/jsx-runtime").JSX.Element;
62
+ /**
63
+ * SystemRedirect — forwards legacy /system/* URLs to the canonical
64
+ * /apps/setup/* location so bookmarks keep working. Suffix is preserved.
65
+ */
66
+ export declare function SystemRedirect(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * Console building blocks — composable JSX elements that consumers assemble in
4
+ * their own App.tsx to build the console routing tree.
5
+ *
6
+ * Previously this module exported a `createConsole(config)` factory that hid the
7
+ * routing tree behind a config object. In practice every real project wants to
8
+ * edit the routes directly (add /billing, tweak AuthGuard behaviour, reorder
9
+ * providers), so we now export the pieces and let consumers write ~40 lines of
10
+ * JSX in App.tsx. See examples/console-starter/src/App.tsx for a minimal example,
11
+ * apps/console/src/App.tsx for one with custom system routes + CreateApp.
12
+ */
13
+ import { Suspense } from 'react';
14
+ import { Navigate, useLocation } from 'react-router-dom';
15
+ import { AuthGuard, useAuth } from '@object-ui/auth';
16
+ import { AdapterProvider, useAdapter } from '../providers/AdapterProvider';
17
+ import { MetadataProvider, useMetadata } from '../providers/MetadataProvider';
18
+ import { NavigationProvider } from '../context/NavigationContext';
19
+ import { FavoritesProvider } from '../context/FavoritesProvider';
20
+ import { ThemeProvider } from '../chrome/ThemeProvider';
21
+ export function LoadingFallback() {
22
+ return (_jsx("div", { className: "h-screen flex items-center justify-center text-sm text-muted-foreground", children: "Loading\u2026" }));
23
+ }
24
+ /**
25
+ * ConsoleShell — top-level provider stack shared by every console route.
26
+ * Wraps children in ThemeProvider + NavigationProvider + FavoritesProvider +
27
+ * Suspense so lazy route components get a default loading fallback and
28
+ * dark/light/system theme switching works out of the box.
29
+ *
30
+ * Place this inside a <BrowserRouter> and around your <Routes>:
31
+ *
32
+ * <BrowserRouter>
33
+ * <ConsoleShell>
34
+ * <Routes>...</Routes>
35
+ * </ConsoleShell>
36
+ * </BrowserRouter>
37
+ */
38
+ export function ConsoleShell({ children }) {
39
+ return (_jsx(ThemeProvider, { defaultTheme: "system", storageKey: "object-ui-theme", children: _jsx(NavigationProvider, { children: _jsx(FavoritesProvider, { children: _jsx(Suspense, { fallback: _jsx(LoadingFallback, {}), children: children }) }) }) }));
40
+ }
41
+ /**
42
+ * ConnectedShell — mounts the data layer (AdapterProvider + MetadataProvider).
43
+ * Use this around any route element that needs metadata access, i.e. anything
44
+ * rendering objects / dashboards / pages.
45
+ */
46
+ export function ConnectedShell({ children }) {
47
+ return (_jsx(AdapterProvider, { children: _jsx(ConnectedShellInner, { children: children }) }));
48
+ }
49
+ function ConnectedShellInner({ children }) {
50
+ const adapter = useAdapter();
51
+ if (!adapter)
52
+ return _jsx(LoadingFallback, {});
53
+ return _jsx(MetadataProvider, { adapter: adapter, children: children });
54
+ }
55
+ /**
56
+ * RequireOrganization — redirects to /organizations when the multi-tenant
57
+ * feature is enabled (user has orgs but no active one). Single-tenant
58
+ * deployments (empty organizations list) render through.
59
+ */
60
+ export function RequireOrganization({ children }) {
61
+ const { activeOrganization, organizations, isOrganizationsLoading } = useAuth();
62
+ if (isOrganizationsLoading)
63
+ return _jsx(LoadingFallback, {});
64
+ const orgList = organizations ?? [];
65
+ const orgFeatureEnabled = orgList.length > 0 || !!activeOrganization;
66
+ if (orgFeatureEnabled && !activeOrganization)
67
+ return _jsx(Navigate, { to: "/organizations", replace: true });
68
+ return _jsx(_Fragment, { children: children });
69
+ }
70
+ /**
71
+ * AuthenticatedRoute — convenience wrapper composing AuthGuard + ConnectedShell
72
+ * (+ optional RequireOrganization). Covers the common case for protected
73
+ * routes. For bespoke needs, compose the primitives directly.
74
+ */
75
+ export function AuthenticatedRoute({ children, requireOrganization = true, loginPath = '/login', }) {
76
+ return (_jsx(AuthGuard, { fallback: _jsx(Navigate, { to: loginPath }), loadingFallback: _jsx(LoadingFallback, {}), children: _jsx(ConnectedShell, { children: requireOrganization ? _jsx(RequireOrganization, { children: children }) : children }) }));
77
+ }
78
+ /**
79
+ * RootRedirect — element for <Route path="/" />. Waits for metadata to load
80
+ * then sends the user to /home.
81
+ */
82
+ export function RootRedirect() {
83
+ const { loading } = useMetadata();
84
+ if (loading)
85
+ return _jsx(LoadingFallback, {});
86
+ return _jsx(Navigate, { to: "/home", replace: true });
87
+ }
88
+ /**
89
+ * SystemRedirect — forwards legacy /system/* URLs to the canonical
90
+ * /apps/setup/* location so bookmarks keep working. Suffix is preserved.
91
+ */
92
+ export function SystemRedirect() {
93
+ const location = useLocation();
94
+ const suffix = location.pathname.replace(/^\/system/, '');
95
+ const target = suffix ? `/apps/setup/system${suffix}` : '/apps/setup';
96
+ return _jsx(Navigate, { to: `${target}${location.search}${location.hash}`, replace: true });
97
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Shared layout for authentication pages (login, register, forgot password).
3
+ * Provides a widescreen-optimized split-panel design with branding on the left
4
+ * and form content on the right, inspired by enterprise platforms like Airtable and Salesforce.
5
+ */
6
+ import type React from 'react';
7
+ export declare function AuthPageLayout({ children }: {
8
+ children: React.ReactNode;
9
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function AuthPageLayout({ children }) {
3
+ return (_jsxs("div", { className: "flex min-h-screen", children: [_jsx("div", { className: "hidden lg:flex lg:w-1/2 items-center justify-center bg-primary p-12", children: _jsxs("div", { className: "max-w-md space-y-6 text-primary-foreground", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "h-10 w-10", role: "img", "aria-label": "ObjectStack logo", children: [_jsx("title", { children: "ObjectStack" }), _jsx("path", { d: "M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" })] }), _jsx("span", { className: "text-2xl font-bold", children: "ObjectStack" })] }), _jsx("h2", { className: "text-3xl font-bold leading-tight", children: "Build powerful business applications, faster." }), _jsx("p", { className: "text-lg opacity-90", children: "The universal platform for enterprise data management, workflows, and analytics." })] }) }), _jsx("div", { className: "flex w-full lg:w-1/2 items-center justify-center bg-background px-6 py-12", children: children })] }));
4
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Forgot Password Page for ObjectStack Console
3
+ */
4
+ export declare function ForgotPasswordPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Forgot Password Page for ObjectStack Console
4
+ */
5
+ import { Link } from 'react-router-dom';
6
+ import { ForgotPasswordForm } from '@object-ui/auth';
7
+ import { useObjectTranslation } from '@object-ui/i18n';
8
+ import { AuthPageLayout } from './AuthPageLayout';
9
+ const RouterLink = ({ href, className, children }) => (_jsx(Link, { to: href, className: className, children: children }));
10
+ export function ForgotPasswordPage() {
11
+ const { t } = useObjectTranslation();
12
+ return (_jsx(AuthPageLayout, { children: _jsx(ForgotPasswordForm, { loginUrl: "/login", title: t('auth.forgotPassword.title'), description: t('auth.forgotPassword.description'), linkComponent: RouterLink, labels: {
13
+ emailLabel: t('auth.forgotPassword.emailLabel'),
14
+ emailPlaceholder: t('auth.forgotPassword.emailPlaceholder'),
15
+ submitButton: t('auth.forgotPassword.submitButton'),
16
+ submittingButton: t('auth.forgotPassword.submittingButton'),
17
+ successTitle: t('auth.forgotPassword.successTitle'),
18
+ successDescription: t('auth.forgotPassword.successDescription'),
19
+ backToSignInText: t('auth.forgotPassword.backToSignInText'),
20
+ rememberPasswordText: t('auth.forgotPassword.rememberPasswordText'),
21
+ signInText: t('auth.forgotPassword.signInText'),
22
+ } }) }));
23
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Login Page for ObjectStack Console
3
+ */
4
+ export declare function LoginPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Login Page for ObjectStack Console
4
+ */
5
+ import { useNavigate, Link } from 'react-router-dom';
6
+ import { LoginForm } from '@object-ui/auth';
7
+ import { useObjectTranslation } from '@object-ui/i18n';
8
+ import { AuthPageLayout } from './AuthPageLayout';
9
+ const RouterLink = ({ href, className, children }) => (_jsx(Link, { to: href, className: className, children: children }));
10
+ export function LoginPage() {
11
+ const navigate = useNavigate();
12
+ const { t } = useObjectTranslation();
13
+ return (_jsx(AuthPageLayout, { children: _jsx(LoginForm, { onSuccess: () => navigate('/'), registerUrl: "/register", forgotPasswordUrl: "/forgot-password", title: t('auth.login.title'), description: t('auth.login.description'), linkComponent: RouterLink, labels: {
14
+ emailLabel: t('auth.login.emailLabel'),
15
+ emailPlaceholder: t('auth.login.emailPlaceholder'),
16
+ passwordLabel: t('auth.login.passwordLabel'),
17
+ passwordPlaceholder: t('auth.login.passwordPlaceholder'),
18
+ forgotPasswordText: t('auth.login.forgotPasswordText'),
19
+ submitButton: t('auth.login.submitButton'),
20
+ submittingButton: t('auth.login.submittingButton'),
21
+ noAccountText: t('auth.login.noAccountText'),
22
+ signUpText: t('auth.login.signUpText'),
23
+ } }) }));
24
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Register Page for ObjectStack Console
3
+ */
4
+ export declare function RegisterPage(): import("react/jsx-runtime").JSX.Element;