|
| 1 | +/** |
| 2 | + * HomePage |
| 3 | + * |
| 4 | + * Unified Home Dashboard (Workspace) that displays all available applications, |
| 5 | + * quick actions, recent items, and favorites. Inspired by Airtable/Notion home pages. |
| 6 | + * |
| 7 | + * Features: |
| 8 | + * - Display all active applications as cards |
| 9 | + * - Quick actions for creating apps, importing data, etc. |
| 10 | + * - Recent apps section (from useRecentItems) |
| 11 | + * - Starred/Favorite apps section (from useFavorites) |
| 12 | + * - Empty state guidance for new users |
| 13 | + * - Responsive grid layout |
| 14 | + * - i18n support |
| 15 | + * |
| 16 | + * @module |
| 17 | + */ |
| 18 | + |
| 19 | +import { useNavigate } from 'react-router-dom'; |
| 20 | +import { useMetadata } from '../../context/MetadataProvider'; |
| 21 | +import { useRecentItems } from '../../hooks/useRecentItems'; |
| 22 | +import { useFavorites } from '../../hooks/useFavorites'; |
| 23 | +import { useObjectTranslation } from '@object-ui/i18n'; |
| 24 | +import { resolveI18nLabel } from '../../utils'; |
| 25 | +import { QuickActions } from './QuickActions'; |
| 26 | +import { AppCard } from './AppCard'; |
| 27 | +import { RecentApps } from './RecentApps'; |
| 28 | +import { StarredApps } from './StarredApps'; |
| 29 | +import { Empty, EmptyTitle, EmptyDescription, Button } from '@object-ui/components'; |
| 30 | +import { Plus, Settings } from 'lucide-react'; |
| 31 | + |
| 32 | +export function HomePage() { |
| 33 | + const navigate = useNavigate(); |
| 34 | + const { t } = useObjectTranslation(); |
| 35 | + const { apps, loading } = useMetadata(); |
| 36 | + const { recentItems } = useRecentItems(); |
| 37 | + const { favorites } = useFavorites(); |
| 38 | + |
| 39 | + // Filter active apps |
| 40 | + const activeApps = apps.filter((a: any) => a.active !== false); |
| 41 | + |
| 42 | + // Get recent apps (only apps, not objects/dashboards) |
| 43 | + const recentApps = recentItems |
| 44 | + .filter(item => item.type === 'object' || item.type === 'dashboard' || item.type === 'page') |
| 45 | + .slice(0, 6); |
| 46 | + |
| 47 | + // Get starred apps |
| 48 | + const starredApps = favorites |
| 49 | + .filter(item => item.type === 'object' || item.type === 'dashboard' || item.type === 'page') |
| 50 | + .slice(0, 8); |
| 51 | + |
| 52 | + if (loading) { |
| 53 | + return ( |
| 54 | + <div className="min-h-screen flex items-center justify-center"> |
| 55 | + <div className="text-muted-foreground">Loading workspace...</div> |
| 56 | + </div> |
| 57 | + ); |
| 58 | + } |
| 59 | + |
| 60 | + // Empty state - no apps configured |
| 61 | + if (activeApps.length === 0) { |
| 62 | + return ( |
| 63 | + <div className="min-h-screen flex items-center justify-center p-6"> |
| 64 | + <Empty> |
| 65 | + <EmptyTitle>Welcome to ObjectUI</EmptyTitle> |
| 66 | + <EmptyDescription> |
| 67 | + Get started by creating your first application or configure your system settings. |
| 68 | + </EmptyDescription> |
| 69 | + <div className="mt-6 flex flex-col sm:flex-row items-center gap-3"> |
| 70 | + <Button |
| 71 | + onClick={() => navigate('/create-app')} |
| 72 | + data-testid="create-first-app-btn" |
| 73 | + > |
| 74 | + <Plus className="mr-2 h-4 w-4" /> |
| 75 | + Create Your First App |
| 76 | + </Button> |
| 77 | + <Button |
| 78 | + variant="outline" |
| 79 | + onClick={() => navigate('/system')} |
| 80 | + data-testid="go-to-settings-btn" |
| 81 | + > |
| 82 | + <Settings className="mr-2 h-4 w-4" /> |
| 83 | + System Settings |
| 84 | + </Button> |
| 85 | + </div> |
| 86 | + </Empty> |
| 87 | + </div> |
| 88 | + ); |
| 89 | + } |
| 90 | + |
| 91 | + return ( |
| 92 | + <div className="min-h-screen bg-background"> |
| 93 | + {/* Header */} |
| 94 | + <div className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> |
| 95 | + <div className="container mx-auto px-6 py-6"> |
| 96 | + <div className="flex items-center justify-between"> |
| 97 | + <div> |
| 98 | + <h1 className="text-3xl font-bold tracking-tight"> |
| 99 | + {t('home.title', { defaultValue: 'Home' })} |
| 100 | + </h1> |
| 101 | + <p className="text-muted-foreground mt-1"> |
| 102 | + {t('home.subtitle', { defaultValue: 'Your workspace dashboard' })} |
| 103 | + </p> |
| 104 | + </div> |
| 105 | + <div className="flex items-center gap-2"> |
| 106 | + <Button |
| 107 | + variant="outline" |
| 108 | + onClick={() => navigate('/system')} |
| 109 | + data-testid="home-settings-btn" |
| 110 | + > |
| 111 | + <Settings className="mr-2 h-4 w-4" /> |
| 112 | + {t('common.settings', { defaultValue: 'Settings' })} |
| 113 | + </Button> |
| 114 | + </div> |
| 115 | + </div> |
| 116 | + </div> |
| 117 | + </div> |
| 118 | + |
| 119 | + {/* Main Content */} |
| 120 | + <div className="container mx-auto px-6 py-8 space-y-8"> |
| 121 | + {/* Quick Actions */} |
| 122 | + <QuickActions /> |
| 123 | + |
| 124 | + {/* Starred/Favorite Apps */} |
| 125 | + {starredApps.length > 0 && ( |
| 126 | + <StarredApps items={starredApps} /> |
| 127 | + )} |
| 128 | + |
| 129 | + {/* Recent Apps */} |
| 130 | + {recentApps.length > 0 && ( |
| 131 | + <RecentApps items={recentApps} /> |
| 132 | + )} |
| 133 | + |
| 134 | + {/* All Applications */} |
| 135 | + <section> |
| 136 | + <h2 className="text-2xl font-semibold tracking-tight mb-4"> |
| 137 | + {t('home.allApps', { defaultValue: 'All Applications' })} |
| 138 | + </h2> |
| 139 | + <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> |
| 140 | + {activeApps.map((app: any) => ( |
| 141 | + <AppCard |
| 142 | + key={app.name} |
| 143 | + app={app} |
| 144 | + onClick={() => navigate(`/apps/${app.name}`)} |
| 145 | + isFavorite={favorites.some(f => f.id === `app:${app.name}`)} |
| 146 | + /> |
| 147 | + ))} |
| 148 | + </div> |
| 149 | + </section> |
| 150 | + </div> |
| 151 | + </div> |
| 152 | + ); |
| 153 | +} |
0 commit comments