-
Notifications
You must be signed in to change notification settings - Fork 1
feat: promote system routes to top-level and improve no-app empty state UX #898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a07d323
58a4892
313be35
e704b11
2166a10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,7 +1,7 @@ | ||||||
| import { BrowserRouter, Routes, Route, Navigate, useNavigate, useLocation, useSearchParams } from 'react-router-dom'; | ||||||
| import { useState, useEffect, useCallback, lazy, Suspense, useMemo, type ReactNode } from 'react'; | ||||||
| import { ModalForm } from '@object-ui/plugin-form'; | ||||||
| import { Empty, EmptyTitle, EmptyDescription } from '@object-ui/components'; | ||||||
| import { Empty, EmptyTitle, EmptyDescription, Button } from '@object-ui/components'; | ||||||
| import { toast } from 'sonner'; | ||||||
| import { SchemaRendererProvider, useActionRunner, useGlobalUndo } from '@object-ui/react'; | ||||||
| import type { ConnectionState } from './dataSource'; | ||||||
|
|
@@ -261,28 +261,49 @@ export function AppContent() { | |||||
| // Allow create-app route even when no active app exists | ||||||
| const isCreateAppRoute = location.pathname.endsWith('/create-app'); | ||||||
|
|
||||||
| if (!activeApp && !isCreateAppRoute) return ( | ||||||
| // Check if we're on a system route (accessible without an active app) | ||||||
| const isSystemRoute = location.pathname.includes('/system'); | ||||||
|
|
||||||
| if (!activeApp && !isCreateAppRoute && !isSystemRoute) return ( | ||||||
| <div className="h-screen flex items-center justify-center"> | ||||||
| <Empty> | ||||||
| <EmptyTitle>No Apps Configured</EmptyTitle> | ||||||
| <EmptyDescription>No applications have been registered.</EmptyDescription> | ||||||
| <button | ||||||
| className="mt-4 inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90" | ||||||
| onClick={() => navigate('create-app')} | ||||||
| data-testid="create-first-app-btn" | ||||||
| > | ||||||
| Create Your First App | ||||||
| </button> | ||||||
| <EmptyDescription> | ||||||
| No applications have been registered. Create your first app or visit System Settings to configure your environment. | ||||||
| </EmptyDescription> | ||||||
| <div className="mt-4 flex flex-col sm:flex-row items-center gap-3"> | ||||||
| <Button | ||||||
| onClick={() => navigate('/create-app')} | ||||||
| data-testid="create-first-app-btn" | ||||||
| > | ||||||
| Create Your First App | ||||||
| </Button> | ||||||
| <Button | ||||||
| variant="outline" | ||||||
| onClick={() => navigate('/system')} | ||||||
| data-testid="go-to-settings-btn" | ||||||
| > | ||||||
| System Settings | ||||||
| </Button> | ||||||
| </div> | ||||||
| </Empty> | ||||||
| </div> | ||||||
| ); | ||||||
|
|
||||||
| // When on create-app without an active app, render a minimal layout with just the wizard | ||||||
| if (!activeApp && isCreateAppRoute) { | ||||||
| if (!activeApp && (isCreateAppRoute || isSystemRoute)) { | ||||||
| return ( | ||||||
| <Suspense fallback={<LoadingScreen />}> | ||||||
| <Routes> | ||||||
| <Route path="create-app" element={<CreateAppPage />} /> | ||||||
| <Route path="system" element={<SystemHubPage />} /> | ||||||
| <Route path="system/apps" element={<AppManagementPage />} /> | ||||||
| <Route path="system/users" element={<UserManagementPage />} /> | ||||||
| <Route path="system/organizations" element={<OrgManagementPage />} /> | ||||||
| <Route path="system/roles" element={<RoleManagementPage />} /> | ||||||
| <Route path="system/permissions" element={<PermissionManagementPage />} /> | ||||||
| <Route path="system/audit-log" element={<AuditLogPage />} /> | ||||||
| <Route path="system/profile" element={<ProfilePage />} /> | ||||||
| </Routes> | ||||||
|
Comment on lines
293
to
307
|
||||||
| </Suspense> | ||||||
| ); | ||||||
|
|
@@ -455,22 +476,51 @@ function RootRedirect() { | |||||
| <Empty> | ||||||
| <EmptyTitle>{error ? 'Failed to Load Configuration' : 'No Apps Configured'}</EmptyTitle> | ||||||
| <EmptyDescription> | ||||||
| {error ? error.message : 'No applications have been registered. Check your ObjectStack configuration.'} | ||||||
| {error | ||||||
| ? 'There was an error loading the configuration. You can still create an app or access System Settings.' | ||||||
| : 'No applications have been registered. Create your first app or configure your system.'} | ||||||
| </EmptyDescription> | ||||||
| {!error && ( | ||||||
| <button | ||||||
| className="mt-4 inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90" | ||||||
| onClick={() => navigate('/apps/_new/create-app')} | ||||||
| <div className="mt-4 flex flex-col sm:flex-row items-center gap-3"> | ||||||
| <Button | ||||||
| onClick={() => navigate('/create-app')} | ||||||
| data-testid="create-first-app-btn" | ||||||
| > | ||||||
| Create Your First App | ||||||
| </button> | ||||||
| )} | ||||||
| </Button> | ||||||
| <Button | ||||||
| variant="outline" | ||||||
| onClick={() => navigate('/system')} | ||||||
| data-testid="go-to-settings-btn" | ||||||
| > | ||||||
| System Settings | ||||||
| </Button> | ||||||
| </div> | ||||||
| </Empty> | ||||||
| </div> | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * SystemRoutes — Top-level system admin routes accessible without any app context. | ||||||
| * Provides a minimal layout with system navigation sidebar. | ||||||
| */ | ||||||
| function SystemRoutes() { | ||||||
| return ( | ||||||
| <Suspense fallback={<LoadingScreen />}> | ||||||
| <Routes> | ||||||
| <Route path="/" element={<SystemHubPage />} /> | ||||||
|
||||||
| <Route path="/" element={<SystemHubPage />} /> | |
| <Route index element={<SystemHubPage />} /> |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New top-level /system/* and /create-app routes are introduced here, but the added test file only mounts AppContent under /apps/:appName/*, so it doesn’t exercise these top-level routes. Add a test that mounts the route config used by App() (or refactor route definitions for testability) to verify /system and /create-app work without an app context and remain guarded by auth as intended.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,209 @@ | ||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||
| * Empty State & System Routes Tests | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * Validates the empty state behavior when no apps are configured | ||||||||||||||||||||||||
| * and the availability of system routes and create-app entry points. | ||||||||||||||||||||||||
| * | ||||||||||||||||||||||||
| * Requirements: | ||||||||||||||||||||||||
| * - "Create App" button always visible in empty state (even on error) | ||||||||||||||||||||||||
| * - "System Settings" link always visible in empty state | ||||||||||||||||||||||||
| * - System routes accessible without app context | ||||||||||||||||||||||||
| * - Login/Register/Forgot password always accessible | ||||||||||||||||||||||||
|
Comment on lines
+5
to
+11
|
||||||||||||||||||||||||
| * and the availability of system routes and create-app entry points. | |
| * | |
| * Requirements: | |
| * - "Create App" button always visible in empty state (even on error) | |
| * - "System Settings" link always visible in empty state | |
| * - System routes accessible without app context | |
| * - Login/Register/Forgot password always accessible | |
| * and the availability of core system navigation and create-app entry points. | |
| * | |
| * This suite focuses on the empty state UI and key system navigation links | |
| * that should remain available even when no applications are configured. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isSystemRouteuseslocation.pathname.includes('/system'), which can match unrelated paths (e.g./apps/system/...whereappNameissystem, or/apps/_new/systematic). Since this logic gates whether we bypass the empty-state screen when!activeApp, it should match a full path segment (or useuseParams().appNameand checkpathname.startsWith(/apps/${appName}/system)) rather than a substring match.