11import { useState , useEffect } from 'react' ;
22import {
33 Spinner ,
4+ Card ,
5+ CardHeader ,
6+ CardTitle ,
7+ CardDescription
48} from '@objectql/ui' ;
59import { useAuth } from '../context/AuthContext' ;
610import { useRouter } from '../hooks/useRouter' ;
@@ -13,17 +17,47 @@ export default function Dashboard() {
1317 const { user } = useAuth ( ) ;
1418 const [ loading , setLoading ] = useState ( true ) ;
1519 const [ objects , setObjects ] = useState < Record < string , any > > ( { } ) ;
20+ const [ apps , setApps ] = useState < any [ ] > ( [ ] ) ;
1621 const { path, navigate } = useRouter ( ) ;
1722
1823 // Parse path
19- // /object/project/123 -> parts ['', 'object', 'project', '123']
24+ // Support patterns:
25+ // 1. /object/:objectName/:recordId?
26+ // 2. /app/:appName/object/:objectName/:recordId?
2027 const parts = path . split ( '/' ) ;
21- const objectName = parts [ 1 ] === 'object' ? parts [ 2 ] : null ;
22- const isObjectView = parts [ 1 ] === 'object' ;
23- const recordId = isObjectView ? parts [ 3 ] : null ; // Index 3 is ID because parts[2] is name
28+
29+ let objectName : string | null = null ;
30+ let recordId : string | undefined = undefined ;
31+ let appName : string | null = null ;
32+
33+ if ( parts [ 1 ] === 'object' ) {
34+ objectName = parts [ 2 ] ;
35+ recordId = parts [ 3 ] ;
36+ } else if ( parts [ 1 ] === 'app' && parts [ 3 ] === 'object' ) {
37+ appName = parts [ 2 ] ;
38+ objectName = parts [ 4 ] ;
39+ recordId = parts [ 5 ] ;
40+ }
41+
42+ // Wrap navigate to preserve app context
43+ const wrappedNavigate = ( to : string ) => {
44+ if ( appName && to . startsWith ( '/object/' ) ) {
45+ navigate ( to . replace ( '/object/' , `/app/${ appName } /object/` ) ) ;
46+ } else {
47+ navigate ( to ) ;
48+ }
49+ } ;
2450
2551 useEffect ( ( ) => {
2652 if ( user ) {
53+ fetch ( '/api/v6/data/app?limit=100' , { headers : getHeaders ( ) } )
54+ . then ( res => res . json ( ) )
55+ . then ( result => {
56+ const data = Array . isArray ( result ) ? result : ( result . data || [ ] ) ;
57+ setApps ( data ) ;
58+ } )
59+ . catch ( console . error ) ;
60+
2761 // Fetch objects
2862 fetch ( '/api/v6/metadata/object' , { headers : getHeaders ( ) } )
2963 . then ( res => res . json ( ) )
@@ -38,12 +72,7 @@ export default function Dashboard() {
3872
3973 const objNames = Object . keys ( objectsMap ) ;
4074 setObjects ( objectsMap ) ;
41-
42- const currentPath = window . location . pathname ;
43- if ( ( currentPath === '/' || currentPath === '/object' ) && objNames . length > 0 ) {
44- navigate ( `/object/${ objNames [ 0 ] } ` ) ;
45- }
46-
75+ // Auto-redirect removed to show App List
4776 setLoading ( false ) ;
4877 } )
4978 . catch ( err => {
@@ -74,7 +103,7 @@ export default function Dashboard() {
74103 < ObjectDetailView
75104 objectName = { objectName }
76105 recordId = { recordId }
77- navigate = { navigate }
106+ navigate = { wrappedNavigate }
78107 objectSchema = { objects [ objectName ] }
79108 />
80109 ) ;
@@ -84,21 +113,60 @@ export default function Dashboard() {
84113 objectName = { objectName }
85114 user = { user }
86115 isCreating = { false }
87- navigate = { navigate }
116+ navigate = { wrappedNavigate }
88117 objectSchema = { objects [ objectName ] }
89118 />
90119 ) ;
91120 }
92121
93122 return (
94- < div className = "flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm" >
95- < div className = "flex flex-col items-center gap-1 text-center" >
96- < h3 className = "text-2xl font-bold tracking-tight" >
97- Welcome to ObjectQL
98- </ h3 >
99- < p className = "text-sm text-muted-foreground" >
100- Select an object from the sidebar to get started.
101- </ p >
123+ < div className = "p-6 max-w-7xl mx-auto w-full" >
124+ < div className = "flex items-center justify-between mb-8" >
125+ < div >
126+ < h2 className = "text-3xl font-bold tracking-tight" > Applications</ h2 >
127+ < p className = "text-muted-foreground mt-2" >
128+ Select an application to start working.
129+ </ p >
130+ </ div >
131+ </ div >
132+
133+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" >
134+ { apps . map ( app => (
135+ < Card
136+ key = { app . _id || app . id }
137+ className = "hover:shadow-md transition-all cursor-pointer border-2 hover:border-primary/20"
138+ onClick = { ( ) => {
139+ // Navigate to first object or app root
140+ if ( app . objects && Array . isArray ( app . objects ) && app . objects . length > 0 ) {
141+ navigate ( `/app/${ app . slug } /object/${ app . objects [ 0 ] } ` ) ;
142+ } else {
143+ navigate ( `/app/${ app . slug } ` ) ;
144+ }
145+ } }
146+ >
147+ < CardHeader className = "flex flex-row items-start gap-4 space-y-0" >
148+ < div className = "p-3 rounded-lg bg-primary/10 text-primary" >
149+ < i className = { `${ app . icon || 'ri-apps-line' } text-xl` } > </ i >
150+ </ div >
151+ < div className = "space-y-1" >
152+ < CardTitle className = "text-lg" > { app . name } </ CardTitle >
153+ < CardDescription className = "line-clamp-2" >
154+ { app . description || 'No description provided' }
155+ </ CardDescription >
156+ </ div >
157+ </ CardHeader >
158+ </ Card >
159+ ) ) }
160+
161+ { apps . length === 0 && (
162+ < div className = "col-span-full flex flex-col items-center justify-center p-12 border-2 border-dashed rounded-lg" >
163+ < div className = "p-4 rounded-full bg-muted mb-4" >
164+ < i className = "ri-apps-line text-2xl text-muted-foreground" > </ i >
165+ </ div >
166+ < h3 className = "text-lg font-semibold" > No Applications Found</ h3 >
167+ < p className = "text-muted-foreground" > Admin needs to create an application first.</ p >
168+ </ div >
169+ ) }
102170 </ div >
103171 </ div >
104172 ) ;
0 commit comments