1- import { useState , useEffect } from 'react' ;
1+ import { useState , useEffect , useCallback } from 'react' ;
22import { ObjectStackClient } from '@objectstack/client' ;
3+ import { ObjectStackProvider } from '@objectstack/client-react' ;
4+ import { ErrorBoundary } from './components/ErrorBoundary' ;
35import { AppSidebar } from "./components/app-sidebar"
46import { SiteHeader } from "@/components/site-header"
57import { SidebarProvider } from "@/components/ui/sidebar"
68import { DeveloperOverview } from './components/DeveloperOverview' ;
79import { ObjectExplorer } from './components/ObjectExplorer' ;
8- import { ObjectDataForm } from './components/ObjectDataForm' ;
910import { PackageManager } from './components/PackageManager' ;
1011import { Toaster } from "@/components/ui/toaster"
1112import { getApiBaseUrl , config } from './lib/config' ;
1213import type { InstalledPackage } from '@objectstack/spec/kernel' ;
1314
15+ type ViewType = 'overview' | 'packages' | 'object' ;
16+
1417export default function App ( ) {
1518 const [ client , setClient ] = useState < ObjectStackClient | null > ( null ) ;
1619 const [ packages , setPackages ] = useState < InstalledPackage [ ] > ( [ ] ) ;
1720 const [ selectedPackage , setSelectedPackage ] = useState < InstalledPackage | null > ( null ) ;
1821 const [ selectedObject , setSelectedObject ] = useState < string | null > ( null ) ;
19- const [ selectedView , setSelectedView ] = useState < 'overview' | 'packages' | 'object' > ( 'overview' ) ;
20- const [ editingRecord , setEditingRecord ] = useState < any > ( null ) ;
21- const [ showForm , setShowForm ] = useState ( false ) ;
22+ const [ selectedView , setSelectedView ] = useState < ViewType > ( 'overview' ) ;
2223
23- // 1. Create client
24+ // 1. Create client once
2425 useEffect ( ( ) => {
2526 const baseUrl = getApiBaseUrl ( ) ;
2627 console . log ( `[App] Connecting to API: ${ baseUrl } (mode: ${ config . mode } )` ) ;
27-
28- const newClient = new ObjectStackClient ( {
29- baseUrl,
30- } ) ;
31- setClient ( newClient ) ;
28+ setClient ( new ObjectStackClient ( { baseUrl } ) ) ;
3229 } , [ ] ) ;
3330
3431 // 2. Fetch installed packages from the server API
@@ -38,125 +35,85 @@ export default function App() {
3835
3936 async function loadPackages ( ) {
4037 try {
41- // Spec: GET /api/v1/packages → ListPackagesResponse = { packages: InstalledPackage[], total }
4238 const result = await client ! . packages . list ( ) ;
4339 const items : InstalledPackage [ ] = result ?. packages || [ ] ;
44-
45- console . log ( '[App] Fetched packages from API:' , items . map ( ( p ) => p . manifest ?. name || p . manifest ?. id ) ) ;
46-
40+ console . log ( '[App] Fetched packages:' , items . map ( ( p ) => p . manifest ?. name || p . manifest ?. id ) ) ;
4741 if ( mounted && items . length > 0 ) {
4842 setPackages ( items ) ;
4943 setSelectedPackage ( items [ 0 ] ) ;
5044 }
5145 } catch ( err ) {
52- console . error ( '[App] Failed to fetch packages from API :' , err ) ;
46+ console . error ( '[App] Failed to fetch packages:' , err ) ;
5347 }
5448 }
5549
5650 loadPackages ( ) ;
5751 return ( ) => { mounted = false ; } ;
5852 } , [ client ] ) ;
5953
60- function handleEdit ( record : any ) {
61- setEditingRecord ( record ) ;
62- setShowForm ( true ) ;
63- }
64-
65- function handleFormSuccess ( ) {
66- setShowForm ( false ) ;
67- setEditingRecord ( null ) ;
68- // Force a re-render of the table by toggling selected object
69- const current = selectedObject ;
70- setSelectedObject ( null ) ;
71- setTimeout ( ( ) => setSelectedObject ( current ) , 0 ) ;
72- }
73-
74- function handleFormCancel ( ) {
75- setShowForm ( false ) ;
76- setEditingRecord ( null ) ;
77- }
78-
79- function handleSelectPackage ( pkg : InstalledPackage ) {
54+ const handleSelectPackage = useCallback ( ( pkg : InstalledPackage ) => {
8055 setSelectedPackage ( pkg ) ;
8156 setSelectedObject ( null ) ;
8257 setSelectedView ( 'overview' ) ;
83- setShowForm ( false ) ;
84- setEditingRecord ( null ) ;
85- }
58+ } , [ ] ) ;
8659
87- function handleSelectObject ( name : string ) {
60+ const handleSelectObject = useCallback ( ( name : string ) => {
8861 if ( name ) {
8962 setSelectedObject ( name ) ;
9063 setSelectedView ( 'object' ) ;
9164 } else {
9265 setSelectedObject ( null ) ;
9366 setSelectedView ( 'overview' ) ;
9467 }
95- }
68+ } , [ ] ) ;
9669
97- function handleSelectView ( view : 'overview' | 'packages' ) {
70+ const handleSelectView = useCallback ( ( view : ViewType ) => {
9871 setSelectedView ( view ) ;
9972 setSelectedObject ( null ) ;
100- setShowForm ( false ) ;
101- setEditingRecord ( null ) ;
102- }
73+ } , [ ] ) ;
74+
75+ const handleNavigate = useCallback ( ( view : string , detail ?: string ) => {
76+ if ( view === 'packages' ) handleSelectView ( 'packages' ) ;
77+ else if ( detail ) handleSelectObject ( detail ) ;
78+ } , [ handleSelectView , handleSelectObject ] ) ;
79+
80+ if ( ! client ) return null ;
10381
10482 return (
105- < SidebarProvider >
106- < AppSidebar
107- client = { client }
108- selectedObject = { selectedObject }
109- onSelectObject = { handleSelectObject }
110- packages = { packages }
111- selectedPackage = { selectedPackage }
112- onSelectPackage = { handleSelectPackage }
113- onSelectView = { handleSelectView }
114- selectedView = { selectedView }
115- />
116- < main className = "flex min-w-0 flex-1 flex-col bg-background" >
117- < SiteHeader
118- selectedObject = { selectedObject }
83+ < ObjectStackProvider client = { client } >
84+ < ErrorBoundary >
85+ < SidebarProvider >
86+ < AppSidebar
87+ selectedObject = { selectedObject }
88+ onSelectObject = { handleSelectObject }
89+ packages = { packages }
90+ selectedPackage = { selectedPackage }
91+ onSelectPackage = { handleSelectPackage }
92+ onSelectView = { handleSelectView }
11993 selectedView = { selectedView }
120- packageLabel = { selectedPackage ?. manifest ?. name || selectedPackage ?. manifest ?. id }
12194 />
122- < div className = "flex flex-1 flex-col overflow-hidden" >
123- { selectedView === 'object' && selectedObject ? (
124- client && (
125- < ObjectExplorer
126- client = { client }
127- objectApiName = { selectedObject }
128- onEdit = { handleEdit }
129- />
130- )
131- ) : selectedView === 'packages' ? (
132- client && < PackageManager client = { client } />
133- ) : (
134- client && (
95+ < main className = "flex min-w-0 flex-1 flex-col bg-background" >
96+ < SiteHeader
97+ selectedObject = { selectedObject }
98+ selectedView = { selectedView }
99+ packageLabel = { selectedPackage ?. manifest ?. name || selectedPackage ?. manifest ?. id }
100+ />
101+ < div className = "flex flex-1 flex-col overflow-hidden" >
102+ { selectedView === 'object' && selectedObject ? (
103+ < ObjectExplorer objectApiName = { selectedObject } />
104+ ) : selectedView === 'packages' ? (
105+ < PackageManager />
106+ ) : (
135107 < DeveloperOverview
136- client = { client }
137108 packages = { packages }
138- onNavigate = { ( view , detail ) => {
139- if ( view === 'packages' ) handleSelectView ( 'packages' ) ;
140- else if ( detail ) handleSelectObject ( detail ) ;
141- } }
109+ onNavigate = { handleNavigate }
142110 />
143- )
144- ) }
145- </ div >
146- </ main >
147-
148- { /* Form Dialog */ }
149- { showForm && client && selectedObject && (
150- < ObjectDataForm
151- client = { client }
152- objectApiName = { selectedObject }
153- record = { editingRecord && Object . keys ( editingRecord ) . length > 0 ? editingRecord : undefined }
154- onSuccess = { handleFormSuccess }
155- onCancel = { handleFormCancel }
156- />
157- ) }
158-
159- < Toaster />
160- </ SidebarProvider >
161- )
111+ ) }
112+ </ div >
113+ </ main >
114+ < Toaster />
115+ </ SidebarProvider >
116+ </ ErrorBoundary >
117+ </ ObjectStackProvider >
118+ ) ;
162119}
0 commit comments