|
1 | 1 | // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. |
2 | 2 |
|
3 | | -import { useState, useEffect, useCallback } from 'react'; |
4 | | -import { ObjectStackClient } from '@objectstack/client'; |
5 | | -import { ObjectStackProvider } from '@objectstack/client-react'; |
6 | | -import { ErrorBoundary } from './components/ErrorBoundary'; |
7 | | -import { AppSidebar } from "./components/app-sidebar" |
8 | | -import { SiteHeader } from "@/components/site-header" |
9 | | -import { SidebarProvider } from "@/components/ui/sidebar" |
10 | | -import { DeveloperOverview } from './components/DeveloperOverview'; |
11 | | -import { PackageManager } from './components/PackageManager'; |
12 | | -import { ApiConsolePage } from './components/ApiConsolePage'; |
13 | | -import { Toaster } from "@/components/ui/toaster" |
14 | | -import { AiChatPanel } from "@/components/AiChatPanel" |
15 | | -import { getApiBaseUrl, config } from './lib/config'; |
16 | | -import { PluginRegistryProvider, PluginHost } from './plugins'; |
17 | | -import { builtInPlugins } from './plugins/built-in'; |
18 | | -import type { InstalledPackage } from '@objectstack/spec/kernel'; |
| 3 | +/** |
| 4 | + * App Component |
| 5 | + * |
| 6 | + * Main application wrapper that provides the TanStack Router instance. |
| 7 | + */ |
19 | 8 |
|
20 | | -type ViewType = 'overview' | 'packages' | 'object' | 'metadata' | 'api-console'; |
| 9 | +import { RouterProvider } from '@tanstack/react-router'; |
| 10 | +import { router } from './router'; |
21 | 11 |
|
22 | 12 | export default function App() { |
23 | | - const [client, setClient] = useState<ObjectStackClient | null>(null); |
24 | | - const [packages, setPackages] = useState<InstalledPackage[]>([]); |
25 | | - const [selectedPackage, setSelectedPackage] = useState<InstalledPackage | null>(null); |
26 | | - const [selectedObject, setSelectedObject] = useState<string | null>(null); |
27 | | - const [selectedMeta, setSelectedMeta] = useState<{ type: string; name: string } | null>(null); |
28 | | - const [selectedView, setSelectedView] = useState<ViewType>('overview'); |
29 | | - |
30 | | - // 1. Create client once |
31 | | - useEffect(() => { |
32 | | - const baseUrl = getApiBaseUrl(); |
33 | | - console.log(`[App] Connecting to API: ${baseUrl} (mode: ${config.mode})`); |
34 | | - setClient(new ObjectStackClient({ baseUrl })); |
35 | | - }, []); |
36 | | - |
37 | | - // 2. Fetch installed packages from the server API |
38 | | - useEffect(() => { |
39 | | - if (!client) return; |
40 | | - let mounted = true; |
41 | | - |
42 | | - async function loadPackages() { |
43 | | - try { |
44 | | - const result = await client!.packages.list(); |
45 | | - const all: InstalledPackage[] = result?.packages || []; |
46 | | - // Filter out the root dev-workspace — it's the monorepo aggregator, not a real package |
47 | | - const items = all.filter((p) => p.manifest?.version !== '0.0.0' && p.manifest?.id !== 'dev-workspace'); |
48 | | - console.log('[App] Fetched packages:', items.map((p) => p.manifest?.name || p.manifest?.id)); |
49 | | - if (mounted && items.length > 0) { |
50 | | - setPackages(items); |
51 | | - setSelectedPackage(items[0]); |
52 | | - } |
53 | | - } catch (err) { |
54 | | - console.error('[App] Failed to fetch packages:', err); |
55 | | - } |
56 | | - } |
57 | | - |
58 | | - loadPackages(); |
59 | | - return () => { mounted = false; }; |
60 | | - }, [client]); |
61 | | - |
62 | | - const handleSelectPackage = useCallback((pkg: InstalledPackage) => { |
63 | | - setSelectedPackage(pkg); |
64 | | - setSelectedObject(null); |
65 | | - setSelectedMeta(null); |
66 | | - setSelectedView('overview'); |
67 | | - }, []); |
68 | | - |
69 | | - const handleSelectObject = useCallback((name: string) => { |
70 | | - if (name) { |
71 | | - setSelectedObject(name); |
72 | | - setSelectedView('object'); |
73 | | - } else { |
74 | | - setSelectedObject(null); |
75 | | - setSelectedView('overview'); |
76 | | - } |
77 | | - }, []); |
78 | | - |
79 | | - const handleSelectView = useCallback((view: ViewType) => { |
80 | | - setSelectedView(view); |
81 | | - setSelectedObject(null); |
82 | | - setSelectedMeta(null); |
83 | | - }, []); |
84 | | - |
85 | | - const handleSelectMeta = useCallback((type: string, name: string) => { |
86 | | - setSelectedMeta({ type, name }); |
87 | | - setSelectedObject(null); |
88 | | - setSelectedView('metadata'); |
89 | | - }, []); |
90 | | - |
91 | | - const handleNavigate = useCallback((view: string, detail?: string) => { |
92 | | - if (view === 'packages') handleSelectView('packages'); |
93 | | - else if (detail) handleSelectObject(detail); |
94 | | - }, [handleSelectView, handleSelectObject]); |
95 | | - |
96 | | - if (!client) return ( |
97 | | - <div className="flex min-h-screen items-center justify-center bg-background"> |
98 | | - <div className="text-center space-y-2"> |
99 | | - <div className="h-8 w-8 mx-auto animate-spin rounded-full border-4 border-muted border-t-primary" /> |
100 | | - <p className="text-sm text-muted-foreground">Connecting to ObjectStack…</p> |
101 | | - </div> |
102 | | - </div> |
103 | | - ); |
104 | | - |
105 | | - return ( |
106 | | - <ObjectStackProvider client={client}> |
107 | | - <PluginRegistryProvider plugins={builtInPlugins}> |
108 | | - <ErrorBoundary> |
109 | | - <SidebarProvider> |
110 | | - <AppSidebar |
111 | | - selectedObject={selectedObject} |
112 | | - onSelectObject={handleSelectObject} |
113 | | - selectedMeta={selectedMeta} |
114 | | - onSelectMeta={handleSelectMeta} |
115 | | - packages={packages} |
116 | | - selectedPackage={selectedPackage} |
117 | | - onSelectPackage={handleSelectPackage} |
118 | | - onSelectView={handleSelectView} |
119 | | - selectedView={selectedView} |
120 | | - /> |
121 | | - <main className="flex min-w-0 flex-1 flex-col h-svh overflow-hidden bg-background"> |
122 | | - <SiteHeader |
123 | | - selectedObject={selectedObject} |
124 | | - selectedMeta={selectedMeta} |
125 | | - selectedView={selectedView} |
126 | | - packageLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id} |
127 | | - /> |
128 | | - <div className="flex flex-1 flex-col overflow-hidden"> |
129 | | - {selectedView === 'object' && selectedObject ? ( |
130 | | - <PluginHost |
131 | | - metadataType="object" |
132 | | - metadataName={selectedObject} |
133 | | - packageId={selectedPackage?.manifest?.id} |
134 | | - /> |
135 | | - ) : selectedView === 'metadata' && selectedMeta ? ( |
136 | | - <PluginHost |
137 | | - metadataType={selectedMeta.type} |
138 | | - metadataName={selectedMeta.name} |
139 | | - packageId={selectedPackage?.manifest?.id} |
140 | | - /> |
141 | | - ) : selectedView === 'packages' ? ( |
142 | | - <PackageManager /> |
143 | | - ) : selectedView === 'api-console' ? ( |
144 | | - <ApiConsolePage /> |
145 | | - ) : ( |
146 | | - <DeveloperOverview |
147 | | - packages={packages} |
148 | | - selectedPackage={selectedPackage} |
149 | | - onNavigate={handleNavigate} |
150 | | - /> |
151 | | - )} |
152 | | - </div> |
153 | | - </main> |
154 | | - <Toaster /> |
155 | | - <AiChatPanel /> |
156 | | - </SidebarProvider> |
157 | | - </ErrorBoundary> |
158 | | - </PluginRegistryProvider> |
159 | | - </ObjectStackProvider> |
160 | | - ); |
| 13 | + return <RouterProvider router={router} />; |
161 | 14 | } |
0 commit comments