-
Notifications
You must be signed in to change notification settings - Fork 2
Implement unified Home Dashboard for multi-app workspace overview #1169
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
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 |
|---|---|---|
|
|
@@ -55,6 +55,9 @@ const PermissionManagementPage = lazy(() => import('./pages/system/PermissionMan | |
| const AuditLogPage = lazy(() => import('./pages/system/AuditLogPage').then(m => ({ default: m.AuditLogPage }))); | ||
| const ProfilePage = lazy(() => import('./pages/system/ProfilePage').then(m => ({ default: m.ProfilePage }))); | ||
|
|
||
| // Home Page (lazy — landing page) | ||
| const HomePage = lazy(() => import('./pages/home/HomePage').then(m => ({ default: m.HomePage }))); | ||
|
|
||
| import { useParams } from 'react-router-dom'; | ||
| import { ThemeProvider } from './components/theme-provider'; | ||
| import { ConsoleToaster } from './components/ConsoleToaster'; | ||
|
|
@@ -456,44 +459,14 @@ function findFirstRoute(items: any[]): string { | |
| return ''; | ||
| } | ||
|
|
||
| // Redirect root to default app | ||
| // Redirect root to home page | ||
| function RootRedirect() { | ||
| const { apps, loading, error } = useMetadata(); | ||
| const navigate = useNavigate(); | ||
| const activeApps = apps.filter((a: any) => a.active !== false); | ||
| const defaultApp = activeApps.find((a: any) => a.isDefault === true) || activeApps[0]; | ||
|
|
||
| const { loading } = useMetadata(); | ||
|
|
||
| if (loading) return <LoadingScreen />; | ||
| if (defaultApp) { | ||
| return <Navigate to={`/apps/${defaultApp.name}`} replace />; | ||
| } | ||
| return ( | ||
| <div className="h-screen flex items-center justify-center"> | ||
| <Empty> | ||
| <EmptyTitle>{error ? 'Failed to Load Configuration' : 'No Apps Configured'}</EmptyTitle> | ||
| <EmptyDescription> | ||
| {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> | ||
| <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> | ||
| ); | ||
|
|
||
| // Always redirect to home page | ||
| return <Navigate to="/home" replace />; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -531,6 +504,16 @@ export function App() { | |
| <Route path="/login" element={<LoginPage />} /> | ||
| <Route path="/register" element={<RegisterPage />} /> | ||
| <Route path="/forgot-password" element={<ForgotPasswordPage />} /> | ||
| {/* Home Dashboard — unified workspace landing page */} | ||
| <Route path="/home" element={ | ||
| <AuthGuard fallback={<Navigate to="/login" />} loadingFallback={<LoadingScreen />}> | ||
| <ConnectedShell> | ||
| <Suspense fallback={<LoadingScreen />}> | ||
| <HomePage /> | ||
| </Suspense> | ||
| </ConnectedShell> | ||
| </AuthGuard> | ||
| } /> | ||
|
Comment on lines
+507
to
+516
|
||
| {/* Top-level system routes — accessible without any app */} | ||
| <Route path="/system/*" element={ | ||
| <AuthGuard fallback={<Navigate to="/login" />} loadingFallback={<LoadingScreen />}> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,99 @@ | ||||||
| /** | ||||||
| * AppCard | ||||||
| * | ||||||
| * Display card for an application with icon, name, description, and favorite toggle. | ||||||
| * | ||||||
| * @module | ||||||
| */ | ||||||
|
|
||||||
| import { Star, StarOff } from 'lucide-react'; | ||||||
| import { Card, CardContent, Button } from '@object-ui/components'; | ||||||
| import { useObjectTranslation } from '@object-ui/i18n'; | ||||||
| import { resolveI18nLabel } from '../../utils'; | ||||||
| import { useFavorites } from '../../hooks/useFavorites'; | ||||||
| import { getIcon } from '../../utils/getIcon'; | ||||||
| import { cn } from '@object-ui/components'; | ||||||
|
|
||||||
| interface AppCardProps { | ||||||
| app: any; | ||||||
| onClick: () => void; | ||||||
| isFavorite: boolean; | ||||||
| } | ||||||
|
|
||||||
| export function AppCard({ app, onClick, isFavorite }: AppCardProps) { | ||||||
| const { t } = useObjectTranslation(); | ||||||
| const { toggleFavorite } = useFavorites(); | ||||||
|
|
||||||
| const Icon = getIcon(app.icon); | ||||||
| const label = resolveI18nLabel(app.label, t) || app.name; | ||||||
| const description = resolveI18nLabel(app.description, t); | ||||||
| const primaryColor = app.branding?.primaryColor; | ||||||
|
|
||||||
| const handleToggleFavorite = (e: React.MouseEvent) => { | ||||||
| e.stopPropagation(); | ||||||
| toggleFavorite({ | ||||||
| id: `app:${app.name}`, | ||||||
| label, | ||||||
| href: `/apps/${app.name}`, | ||||||
| type: 'object', | ||||||
|
||||||
| type: 'object', | |
| type: 'app', |
Copilot
AI
Apr 1, 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.
backgroundColor: ${primaryColor}20`` assumes primaryColor is a 6-digit hex color. In this repo, tests already use `'#000'` (3-digit), which would produce `'#20'` (invalid CSS), so the branding background won't render reliably. Use a helper that safely applies alpha for 3/6/8-digit hex (or use `rgb()`/`color-mix()`/CSS variables) instead of string concatenation.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,153 @@ | ||||||
| /** | ||||||
| * HomePage | ||||||
| * | ||||||
| * Unified Home Dashboard (Workspace) that displays all available applications, | ||||||
| * quick actions, recent items, and favorites. Inspired by Airtable/Notion home pages. | ||||||
| * | ||||||
| * Features: | ||||||
| * - Display all active applications as cards | ||||||
| * - Quick actions for creating apps, importing data, etc. | ||||||
| * - Recent apps section (from useRecentItems) | ||||||
| * - Starred/Favorite apps section (from useFavorites) | ||||||
| * - Empty state guidance for new users | ||||||
| * - Responsive grid layout | ||||||
| * - i18n support | ||||||
| * | ||||||
| * @module | ||||||
| */ | ||||||
|
|
||||||
| import { useNavigate } from 'react-router-dom'; | ||||||
| import { useMetadata } from '../../context/MetadataProvider'; | ||||||
| import { useRecentItems } from '../../hooks/useRecentItems'; | ||||||
| import { useFavorites } from '../../hooks/useFavorites'; | ||||||
| import { useObjectTranslation } from '@object-ui/i18n'; | ||||||
| import { resolveI18nLabel } from '../../utils'; | ||||||
|
||||||
| import { resolveI18nLabel } from '../../utils'; |
Copilot
AI
Apr 1, 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.
useMetadata() also exposes an error, but this page ignores it and will show the "no apps configured" empty state even if metadata loading failed. That can be misleading (and is a regression from the previous RootRedirect error state). Consider rendering an explicit error empty-state when error is set.
Copilot
AI
Apr 1, 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.
The comment says "Get recent apps (only apps, not objects/dashboards)", but the filter includes object, dashboard, and page. This is misleading documentation and makes the later naming (recentApps) harder to follow.
| // Get recent apps (only apps, not objects/dashboards) | |
| // Get recent workspace items (objects, dashboards, and pages) for the RecentApps section |
Copilot
AI
Apr 1, 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.
Several user-facing strings here are hard-coded (e.g., "Loading workspace...") instead of going through useObjectTranslation. This contradicts the PR's stated i18n support and will leave these strings untranslated.
Copilot
AI
Apr 1, 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.
The empty state strings/buttons are hard-coded (e.g., "Welcome to ObjectUI", "Create Your First App", "System Settings") instead of using t(...). This conflicts with the claimed i18n support for the Home dashboard and will leave these strings untranslated.
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.
This roadmap entry claims full i18n support for the Home dashboard, but the new
HomePagestill contains several hard-coded English strings (e.g., loading + empty state copy/buttons). Either update the implementation to fully use i18n keys or soften this checklist item so the roadmap stays accurate.