Implement unified Home Dashboard for multi-app workspace overview#1169
Implement unified Home Dashboard for multi-app workspace overview#1169
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ecent items Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/a9fae3f9-cf33-49ec-8ac5-4a3590b476ab Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Agent-Logs-Url: https://github.com/objectstack-ai/objectui/sessions/a9fae3f9-cf33-49ec-8ac5-4a3590b476ab Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Implements a unified Console “Home” dashboard (/home) as a workspace landing page, and changes the root route (/) to redirect to it instead of auto-opening the first app—aligning Console navigation with a multi-app workspace overview model.
Changes:
- Added a new authenticated
/homeroute that renders a newHomePagedashboard with Quick Actions, starred items, recent items, and an apps grid. - Updated
RootRedirectto always navigate to/home. - Added a shared Lucide icon resolver utility (
getIcon) and documented completion inROADMAP.md.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| ROADMAP.md | Updates “Last Updated” and documents the Unified Home Dashboard milestone/checklist. |
| apps/console/src/App.tsx | Adds /home route and changes / redirect behavior to point to /home. |
| apps/console/src/utils/getIcon.ts | Adds a shared Lucide icon resolver helper. |
| apps/console/src/pages/home/HomePage.tsx | New workspace dashboard page composing quick actions, starred/recent sections, and app cards. |
| apps/console/src/pages/home/QuickActions.tsx | New quick-action cards section for common workspace tasks. |
| apps/console/src/pages/home/AppCard.tsx | New app card component with branding + favorite toggle. |
| apps/console/src/pages/home/RecentApps.tsx | New “recently accessed” section component. |
| apps/console/src/pages/home/StarredApps.tsx | New “starred” section component. |
| apps/console/src/pages/home/index.ts | Barrel exports for the new home dashboard components. |
| import { useRecentItems } from '../../hooks/useRecentItems'; | ||
| import { useFavorites } from '../../hooks/useFavorites'; | ||
| import { useObjectTranslation } from '@object-ui/i18n'; | ||
| import { resolveI18nLabel } from '../../utils'; |
There was a problem hiding this comment.
resolveI18nLabel is imported but never used. With noUnusedLocals: true in apps/console/tsconfig.json, this will fail TypeScript compilation. Remove the import or use it where intended.
| import { resolveI18nLabel } from '../../utils'; |
| import { useNavigate } from 'react-router-dom'; | ||
| import { useObjectTranslation } from '@object-ui/i18n'; | ||
| import { Card, CardContent } from '@object-ui/components'; | ||
| import { Plus, Upload, Settings, Database, FileText } from 'lucide-react'; |
There was a problem hiding this comment.
Upload and FileText are imported but unused. Because this project enables noUnusedLocals, this will cause a build/typecheck failure. Remove the unused imports or use them in actions.
| import { Plus, Upload, Settings, Database, FileText } from 'lucide-react'; | |
| import { Plus, Settings, Database } from 'lucide-react'; |
| // Filter active apps | ||
| const activeApps = apps.filter((a: any) => a.active !== false); | ||
|
|
||
| // Get recent apps (only apps, not objects/dashboards) |
There was a problem hiding this comment.
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 |
| if (loading) { | ||
| return ( | ||
| <div className="min-h-screen flex items-center justify-center"> | ||
| <div className="text-muted-foreground">Loading workspace...</div> | ||
| </div> |
There was a problem hiding this comment.
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.
| id: `app:${app.name}`, | ||
| label, | ||
| href: `/apps/${app.name}`, | ||
| type: 'object', |
There was a problem hiding this comment.
toggleFavorite is storing an app favorite with type: 'object'. This causes the item to be mislabeled (and downstream UIs that render by type will treat an app as an object). Consider extending FavoriteItem['type'] to include 'app' (and updating filters/UI), or store a separate kind/entityType to avoid semantic mismatch.
| type: 'object', | |
| type: 'app', |
| import * as LucideIcons from 'lucide-react'; | ||
| import { Database } from 'lucide-react'; | ||
|
|
||
| /** | ||
| * Resolve a Lucide icon by name (kebab-case or PascalCase) | ||
| * Falls back to Database icon if not found | ||
| */ | ||
| export function getIcon(name?: string): React.ElementType { | ||
| if (!name) return Database; | ||
| if ((LucideIcons as any)[name]) return (LucideIcons as any)[name]; | ||
| const pascal = name | ||
| .split('-') | ||
| .map(p => p.charAt(0).toUpperCase() + p.slice(1)) | ||
| .join(''); | ||
| if ((LucideIcons as any)[pascal]) return (LucideIcons as any)[pascal]; |
There was a problem hiding this comment.
import * as LucideIcons from 'lucide-react' pulls in the entire icon module and generally prevents tree-shaking, which can noticeably increase bundle size for the HomePage chunk. If icons are limited to a known set, consider importing only the needed icons and using a typed mapping instead of LucideIcons as any lookups.
| import * as LucideIcons from 'lucide-react'; | |
| import { Database } from 'lucide-react'; | |
| /** | |
| * Resolve a Lucide icon by name (kebab-case or PascalCase) | |
| * Falls back to Database icon if not found | |
| */ | |
| export function getIcon(name?: string): React.ElementType { | |
| if (!name) return Database; | |
| if ((LucideIcons as any)[name]) return (LucideIcons as any)[name]; | |
| const pascal = name | |
| .split('-') | |
| .map(p => p.charAt(0).toUpperCase() + p.slice(1)) | |
| .join(''); | |
| if ((LucideIcons as any)[pascal]) return (LucideIcons as any)[pascal]; | |
| import { Database } from 'lucide-react'; | |
| const iconMap: Record<string, React.ElementType> = { | |
| Database, | |
| database: Database, | |
| }; | |
| /** | |
| * Resolve a Lucide icon by name (kebab-case or PascalCase) | |
| * Falls back to Database icon if not found | |
| */ | |
| export function getIcon(name?: string): React.ElementType { | |
| if (!name) return Database; | |
| // Try direct lookup (supports both exact and kebab-case keys present in iconMap) | |
| const direct = iconMap[name]; | |
| if (direct) return direct; | |
| // Convert kebab-case to PascalCase (e.g., "database" or "database-icon" -> "DatabaseIcon") | |
| const pascal = name | |
| .split('-') | |
| .map(part => part.charAt(0).toUpperCase() + part.slice(1)) | |
| .join(''); | |
| const pascalMatch = iconMap[pascal]; | |
| if (pascalMatch) return pascalMatch; |
| {/* 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> | ||
| } /> |
There was a problem hiding this comment.
This introduces a new top-level /home route and changes / to redirect there, but there are no accompanying console tests covering the new routing/empty-state behavior. Given the existing Vitest coverage in apps/console, please add tests verifying root redirect -> /home and that HomePage renders expected sections based on metadata/favorites/recent items.
| - [x] **Recent items** — Display recently accessed objects, dashboards, and pages using `useRecentItems` hook | ||
| - [x] **Starred items** — Display user-favorited items using `useFavorites` hook with star/unstar toggle | ||
| - [x] **Empty state** — Helpful guidance for new users with "Create First App" and "System Settings" CTAs | ||
| - [x] **i18n support** — All labels support internationalization via `useObjectTranslation` |
There was a problem hiding this comment.
This roadmap entry claims full i18n support for the Home dashboard, but the new HomePage still 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.
| - [x] **i18n support** — All labels support internationalization via `useObjectTranslation` | |
| - [x] **i18n wiring** — Home dashboard uses `useObjectTranslation` for labels; remaining hard-coded loading/empty-state/button text is tracked as follow-up work |
| const { apps, loading } = useMetadata(); | ||
| const { recentItems } = useRecentItems(); | ||
| const { favorites } = useFavorites(); | ||
|
|
There was a problem hiding this comment.
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.
| <div className="min-h-screen flex items-center justify-center p-6"> | ||
| <Empty> | ||
| <EmptyTitle>Welcome to ObjectUI</EmptyTitle> | ||
| <EmptyDescription> | ||
| Get started by creating your first application or configure your system settings. | ||
| </EmptyDescription> | ||
| <div className="mt-6 flex flex-col sm:flex-row items-center gap-3"> | ||
| <Button | ||
| onClick={() => navigate('/create-app')} | ||
| data-testid="create-first-app-btn" | ||
| > | ||
| <Plus className="mr-2 h-4 w-4" /> | ||
| Create Your First App | ||
| </Button> | ||
| <Button | ||
| variant="outline" | ||
| onClick={() => navigate('/system')} | ||
| data-testid="go-to-settings-btn" | ||
| > | ||
| <Settings className="mr-2 h-4 w-4" /> | ||
| System Settings | ||
| </Button> | ||
| </div> | ||
| </Empty> |
There was a problem hiding this comment.
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.
Replaces direct-to-app redirect behavior with a centralized workspace landing page displaying all applications, quick actions, and recent activity. Follows Airtable/Notion UX patterns.
Implementation
Route & Navigation
/homeroute with lazy-loadedHomePagecomponentRootRedirectto navigate to/homeinstead of first available appAuthGuard,MetadataProvider, andConnectedShellHome Dashboard Components
HomePage— Main landing page with responsive grid layoutsQuickActions— Cards for Create App, Manage Objects, System SettingsAppCard— App display with icon, description, branding colors, favorite toggleRecentApps— Last 6 accessed items viauseRecentItemshookStarredApps— User favorites viauseFavoriteshookShared Utilities
getIcon()fromCommandPalettetoutils/getIcon.tsfor Lucide icon resolutionStructure
User Flow Changes
Before: Login → auto-redirect to first/default app → no workspace overview
After: Login →
/homeworkspace dashboard → user selects app