@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.
- package/CHANGELOG.md +45 -0
- package/README.md +22 -2
- package/dist/chrome/CommandPalette.d.ts +16 -0
- package/dist/chrome/CommandPalette.js +88 -0
- package/dist/chrome/ConditionalAuthWrapper.d.ts +23 -0
- package/dist/chrome/ConditionalAuthWrapper.js +135 -0
- package/dist/chrome/ConsoleToaster.d.ts +11 -0
- package/dist/chrome/ConsoleToaster.js +28 -0
- package/dist/chrome/ErrorBoundary.d.ts +39 -0
- package/dist/chrome/ErrorBoundary.js +64 -0
- package/dist/chrome/KeyboardShortcutsDialog.d.ts +7 -0
- package/dist/chrome/KeyboardShortcutsDialog.js +63 -0
- package/dist/chrome/LoadingScreen.d.ts +12 -0
- package/dist/chrome/LoadingScreen.js +44 -0
- package/dist/chrome/OnboardingWalkthrough.d.ts +9 -0
- package/dist/chrome/OnboardingWalkthrough.js +11 -0
- package/dist/chrome/ThemeProvider.d.ts +13 -0
- package/dist/chrome/ThemeProvider.js +37 -0
- package/dist/chrome/index.d.ts +8 -0
- package/dist/chrome/index.js +8 -0
- package/dist/console/AppContent.d.ts +26 -0
- package/dist/console/AppContent.js +266 -0
- package/dist/console/ConsoleShell.d.ts +66 -0
- package/dist/console/ConsoleShell.js +97 -0
- package/dist/console/auth/AuthPageLayout.d.ts +9 -0
- package/dist/console/auth/AuthPageLayout.js +4 -0
- package/dist/console/auth/ForgotPasswordPage.d.ts +4 -0
- package/dist/console/auth/ForgotPasswordPage.js +23 -0
- package/dist/console/auth/LoginPage.d.ts +4 -0
- package/dist/console/auth/LoginPage.js +24 -0
- package/dist/console/auth/RegisterPage.d.ts +4 -0
- package/dist/console/auth/RegisterPage.js +29 -0
- package/dist/console/auth/index.d.ts +4 -0
- package/dist/console/auth/index.js +4 -0
- package/dist/console/home/AppCard.d.ts +14 -0
- package/dist/console/home/AppCard.js +36 -0
- package/dist/console/home/HomeLayout.d.ts +15 -0
- package/dist/console/home/HomeLayout.js +20 -0
- package/dist/console/home/HomePage.d.ts +18 -0
- package/dist/console/home/HomePage.js +54 -0
- package/dist/console/home/QuickActions.d.ts +9 -0
- package/dist/console/home/QuickActions.js +53 -0
- package/dist/console/home/RecentApps.d.ts +13 -0
- package/dist/console/home/RecentApps.js +32 -0
- package/dist/console/home/StarredApps.d.ts +13 -0
- package/dist/console/home/StarredApps.js +29 -0
- package/dist/console/home/index.d.ts +11 -0
- package/dist/console/home/index.js +11 -0
- package/dist/console/organizations/CreateWorkspaceDialog.d.ts +16 -0
- package/dist/console/organizations/CreateWorkspaceDialog.js +71 -0
- package/dist/console/organizations/OrganizationsLayout.d.ts +15 -0
- package/dist/console/organizations/OrganizationsLayout.js +20 -0
- package/dist/console/organizations/OrganizationsPage.d.ts +9 -0
- package/dist/console/organizations/OrganizationsPage.js +76 -0
- package/dist/console/organizations/index.d.ts +8 -0
- package/dist/console/organizations/index.js +8 -0
- package/dist/console/organizations/manage/AcceptInvitationPage.d.ts +7 -0
- package/dist/console/organizations/manage/AcceptInvitationPage.js +100 -0
- package/dist/console/organizations/manage/InvitationsPage.d.ts +6 -0
- package/dist/console/organizations/manage/InvitationsPage.js +107 -0
- package/dist/console/organizations/manage/InviteMemberDialog.d.ts +16 -0
- package/dist/console/organizations/manage/InviteMemberDialog.js +83 -0
- package/dist/console/organizations/manage/MembersPage.d.ts +6 -0
- package/dist/console/organizations/manage/MembersPage.js +89 -0
- package/dist/console/organizations/manage/OrganizationLayout.d.ts +8 -0
- package/dist/console/organizations/manage/OrganizationLayout.js +51 -0
- package/dist/console/organizations/manage/SettingsPage.d.ts +6 -0
- package/dist/console/organizations/manage/SettingsPage.js +141 -0
- package/dist/console/organizations/manage/orgContext.d.ts +5 -0
- package/dist/console/organizations/manage/orgContext.js +4 -0
- package/dist/context/FavoritesProvider.d.ts +50 -0
- package/dist/context/FavoritesProvider.js +129 -0
- package/dist/context/NavigationContext.d.ts +46 -0
- package/dist/context/NavigationContext.js +59 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.js +2 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/useFavorites.d.ts +13 -0
- package/dist/hooks/useFavorites.js +13 -0
- package/dist/hooks/useMetadataService.d.ts +13 -0
- package/dist/hooks/useMetadataService.js +18 -0
- package/dist/hooks/useNavPins.d.ts +16 -0
- package/dist/hooks/useNavPins.js +72 -0
- package/dist/hooks/useNavigationSync.d.ts +82 -0
- package/dist/hooks/useNavigationSync.js +495 -0
- package/dist/hooks/useObjectActions.d.ts +38 -0
- package/dist/hooks/useObjectActions.js +92 -0
- package/dist/hooks/useRecentItems.d.ts +21 -0
- package/dist/hooks/useRecentItems.js +50 -0
- package/dist/hooks/useResponsiveSidebar.d.ts +8 -0
- package/dist/hooks/useResponsiveSidebar.js +28 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +35 -0
- package/dist/layout/ActivityFeed.d.ts +22 -0
- package/dist/layout/ActivityFeed.js +57 -0
- package/dist/layout/AppHeader.d.ts +33 -0
- package/dist/layout/AppHeader.js +182 -0
- package/dist/layout/AppSidebar.d.ts +14 -0
- package/dist/layout/AppSidebar.js +217 -0
- package/dist/layout/AppSwitcher.d.ts +12 -0
- package/dist/layout/AppSwitcher.js +39 -0
- package/dist/layout/AuthPageLayout.d.ts +9 -0
- package/dist/layout/AuthPageLayout.js +4 -0
- package/dist/layout/ConnectionStatus.d.ts +14 -0
- package/dist/layout/ConnectionStatus.js +36 -0
- package/dist/layout/ConsoleLayout.d.ts +21 -0
- package/dist/layout/ConsoleLayout.js +71 -0
- package/dist/layout/LocaleSwitcher.d.ts +7 -0
- package/dist/layout/LocaleSwitcher.js +27 -0
- package/dist/layout/ModeToggle.d.ts +1 -0
- package/dist/layout/ModeToggle.js +10 -0
- package/dist/layout/UnifiedSidebar.d.ts +22 -0
- package/dist/layout/UnifiedSidebar.js +210 -0
- package/dist/layout/index.d.ts +11 -0
- package/dist/layout/index.js +10 -0
- package/dist/providers/AdapterProvider.d.ts +21 -0
- package/dist/providers/AdapterProvider.js +52 -0
- package/dist/providers/ExpressionProvider.d.ts +50 -0
- package/dist/providers/ExpressionProvider.js +67 -0
- package/dist/providers/MetadataProvider.d.ts +18 -0
- package/dist/providers/MetadataProvider.js +324 -0
- package/dist/services/MetadataService.d.ts +119 -0
- package/dist/services/MetadataService.js +211 -0
- package/dist/skeletons/SkeletonDashboard.d.ts +13 -0
- package/dist/skeletons/SkeletonDashboard.js +12 -0
- package/dist/skeletons/SkeletonDetail.d.ts +15 -0
- package/dist/skeletons/SkeletonDetail.js +12 -0
- package/dist/skeletons/SkeletonGrid.d.ts +15 -0
- package/dist/skeletons/SkeletonGrid.js +12 -0
- package/dist/skeletons/index.d.ts +3 -0
- package/dist/skeletons/index.js +3 -0
- package/dist/utils/getIcon.d.ts +10 -0
- package/dist/utils/getIcon.js +24 -0
- package/dist/utils/index.d.ts +33 -0
- package/dist/utils/index.js +62 -0
- package/dist/utils/metadataConverters.d.ts +78 -0
- package/dist/utils/metadataConverters.js +74 -0
- package/dist/views/ActionConfirmDialog.d.ts +22 -0
- package/dist/views/ActionConfirmDialog.js +22 -0
- package/dist/views/ActionParamDialog.d.ts +23 -0
- package/dist/views/ActionParamDialog.js +67 -0
- package/dist/views/DashboardView.d.ts +9 -0
- package/dist/views/DashboardView.js +357 -0
- package/dist/views/DesignDrawer.d.ts +28 -0
- package/dist/views/DesignDrawer.js +51 -0
- package/dist/views/MetadataInspector.d.ts +40 -0
- package/dist/views/MetadataInspector.js +69 -0
- package/dist/views/ObjectView.d.ts +14 -0
- package/dist/views/ObjectView.js +770 -0
- package/dist/views/PageView.d.ts +6 -0
- package/dist/views/PageView.js +49 -0
- package/dist/views/RecordDetailView.d.ts +14 -0
- package/dist/views/RecordDetailView.js +508 -0
- package/dist/views/ReportView.d.ts +4 -0
- package/dist/views/ReportView.js +284 -0
- package/dist/views/SearchResultsPage.d.ts +9 -0
- package/dist/views/SearchResultsPage.js +107 -0
- package/dist/views/ViewConfigPanel.d.ts +56 -0
- package/dist/views/ViewConfigPanel.js +91 -0
- package/dist/views/index.d.ts +11 -0
- package/dist/views/index.js +11 -0
- package/package.json +59 -11
- package/.turbo/turbo-build.log +0 -4
- package/src/components/AppShell.tsx +0 -31
- package/src/components/DashboardRenderer.tsx +0 -36
- package/src/components/FormRenderer.tsx +0 -71
- package/src/components/ObjectRenderer.tsx +0 -122
- package/src/components/PageRenderer.tsx +0 -28
- package/src/index.ts +0 -20
- package/src/types.ts +0 -78
- package/tsconfig.json +0 -20
- 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,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,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
|
+
}
|