|
1 | | -/** |
2 | | - * App Component - Admin Console |
3 | | - * |
4 | | - * Demonstrates the "Platform Capabilities" of ObjectStack: |
5 | | - * 1. Metadata Discovery (MetadataExplorer) |
6 | | - * 2. Generic Data Table (ObjectDataTable) |
7 | | - * 3. Generic Form (ObjectDataForm) |
8 | | - */ |
9 | | - |
10 | 1 | import { useState, useEffect } from 'react'; |
11 | 2 | import { ObjectStackClient } from '@objectstack/client'; |
12 | | -import { Database } from 'lucide-react'; |
13 | | -import { MetadataExplorer } from './components/MetadataExplorer'; |
| 3 | +import { AppSidebar } from "./components/app-sidebar" |
| 4 | +import { |
| 5 | + Breadcrumb, |
| 6 | + BreadcrumbItem, |
| 7 | + BreadcrumbLink, |
| 8 | + BreadcrumbList, |
| 9 | + BreadcrumbPage, |
| 10 | + BreadcrumbSeparator, |
| 11 | +} from "@/components/ui/breadcrumb" |
| 12 | +import { Separator } from "@/components/ui/separator" |
| 13 | +import { |
| 14 | + SidebarInset, |
| 15 | + SidebarProvider, |
| 16 | + SidebarTrigger, |
| 17 | +} from "@/components/ui/sidebar" |
14 | 18 | import { ObjectDataTable } from './components/ObjectDataTable'; |
15 | | -import { ObjectDataForm } from './components/ObjectDataForm'; |
16 | | -import './App.css'; |
| 19 | +import { Toaster } from "@/components/ui/toaster" |
17 | 20 |
|
18 | | -export function App() { |
| 21 | +export default function App() { |
19 | 22 | const [client, setClient] = useState<ObjectStackClient | null>(null); |
20 | | - const [connected, setConnected] = useState(false); |
21 | | - const [error, setError] = useState<string | null>(null); |
22 | | - |
23 | | - // Selection State |
24 | | - const [selectedObject, setSelectedObject] = useState<string | null>('todo_task'); |
25 | | - const [view, setView] = useState<'list' | 'form'>('list'); |
26 | | - const [editingRecord, setEditingRecord] = useState<any>(null); // null = create new |
27 | | - const [refreshKey, setRefreshKey] = useState(0); |
| 23 | + const [selectedObject, setSelectedObject] = useState<string | null>(null); |
28 | 24 |
|
29 | 25 | useEffect(() => { |
30 | | - initializeClient(); |
| 26 | + // In a real app, this might come from env or config |
| 27 | + // Using simple browser URL relative path for vite proxy or direct connection |
| 28 | + const baseUrl = '/api/v1'; |
| 29 | + |
| 30 | + // Create client |
| 31 | + const newClient = new ObjectStackClient({ |
| 32 | + baseUrl, |
| 33 | + }); |
| 34 | + |
| 35 | + setClient(newClient); |
31 | 36 | }, []); |
32 | 37 |
|
33 | | - async function initializeClient() { |
34 | | - try { |
35 | | - const stackClient = new ObjectStackClient({ |
36 | | - baseUrl: '' // Mocked by MSW to /api/v1 |
37 | | - }); |
38 | | - |
39 | | - // Wait for MSW |
40 | | - await new Promise(resolve => setTimeout(resolve, 500)); |
41 | | - await stackClient.connect(); |
42 | | - setClient(stackClient); |
43 | | - setConnected(true); |
44 | | - console.log('✅ ObjectStack Client connected'); |
45 | | - } catch (err: any) { |
46 | | - setError(err.message || 'Failed to connect'); |
47 | | - } |
48 | | - } |
49 | | - |
50 | | - // --- Actions --- |
51 | | - |
52 | | - const handleCreate = () => { |
53 | | - setEditingRecord(null); // New record |
54 | | - setView('form'); |
55 | | - }; |
56 | | - |
57 | | - const handleEdit = (record: any) => { |
58 | | - setEditingRecord(record); |
59 | | - setView('form'); |
60 | | - }; |
61 | | - |
62 | | - const handleFormSuccess = () => { |
63 | | - setView('list'); |
64 | | - setEditingRecord(null); |
65 | | - setRefreshKey(k => k + 1); |
66 | | - }; |
67 | | - |
68 | | - if (!connected || !client) { |
69 | | - return ( |
70 | | - <div className="min-h-screen flex items-center justify-center bg-background"> |
71 | | - <div className="text-center space-y-4"> |
72 | | - <h2 className="text-xl font-bold text-foreground">Connecting to Platform...</h2> |
73 | | - {error && <p className="text-destructive font-medium">{error}</p>} |
74 | | - </div> |
75 | | - </div> |
76 | | - ); |
77 | | - } |
78 | | - |
79 | 38 | return ( |
80 | | - <div className="min-h-screen bg-background font-sans flex flex-col text-foreground"> |
81 | | - {/* Top Header */} |
82 | | - <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> |
83 | | - <div className="container flex h-14 items-center px-4 md:px-6"> |
84 | | - <div className="mr-4 hidden md:flex items-center space-x-2"> |
85 | | - <div className="font-bold text-xl tracking-tight"> |
86 | | - Object<span className="text-muted-foreground">Stack</span> |
87 | | - </div> |
88 | | - <div className="hidden sm:inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80"> |
89 | | - Console |
90 | | - </div> |
| 39 | + <SidebarProvider> |
| 40 | + <AppSidebar |
| 41 | + client={client} |
| 42 | + selectedObject={selectedObject} |
| 43 | + onSelectObject={setSelectedObject} |
| 44 | + /> |
| 45 | + <SidebarInset> |
| 46 | + <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4"> |
| 47 | + <SidebarTrigger className="-ml-1" /> |
| 48 | + <Separator orientation="vertical" className="mr-2 h-4" /> |
| 49 | + <Breadcrumb> |
| 50 | + <BreadcrumbList> |
| 51 | + <BreadcrumbItem className="hidden md:block"> |
| 52 | + <BreadcrumbLink href="#"> |
| 53 | + ObjectStack |
| 54 | + </BreadcrumbLink> |
| 55 | + </BreadcrumbItem> |
| 56 | + <BreadcrumbSeparator className="hidden md:block" /> |
| 57 | + <BreadcrumbItem> |
| 58 | + <BreadcrumbPage>{selectedObject ? selectedObject : 'Home'}</BreadcrumbPage> |
| 59 | + </BreadcrumbItem> |
| 60 | + </BreadcrumbList> |
| 61 | + </Breadcrumb> |
| 62 | + </header> |
| 63 | + <div className="flex flex-1 flex-col gap-4 p-4"> |
| 64 | + {selectedObject ? ( |
| 65 | + <div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min p-4"> |
| 66 | + {client && ( |
| 67 | + <ObjectDataTable |
| 68 | + client={client} |
| 69 | + objectApiName={selectedObject} |
| 70 | + onEdit={(record) => console.log('Edit', record)} |
| 71 | + /> |
| 72 | + )} |
91 | 73 | </div> |
92 | | - <div className="flex flex-1 items-center justify-between space-x-2 md:justify-end"> |
93 | | - <div className="text-sm text-muted-foreground flex items-center gap-2"> |
94 | | - <span className="relative flex h-2 w-2"> |
95 | | - <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span> |
96 | | - <span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span> |
97 | | - </span> |
98 | | - v1.0.0 (Memory Kernel) |
99 | | - </div> |
| 74 | + ) : ( |
| 75 | + <div className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm"> |
| 76 | + <div className="flex flex-col items-center gap-1 text-center"> |
| 77 | + <h3 className="text-2xl font-bold tracking-tight"> |
| 78 | + Welcome to ObjectStack |
| 79 | + </h3> |
| 80 | + <p className="text-sm text-muted-foreground"> |
| 81 | + Select an object from the sidebar to view its records. |
| 82 | + </p> |
| 83 | + </div> |
100 | 84 | </div> |
| 85 | + )} |
101 | 86 | </div> |
102 | | - </header> |
103 | | - |
104 | | - {/* Main Layout */} |
105 | | - <div className="flex flex-1 overflow-hidden"> |
106 | | - {/* Sidebar */} |
107 | | - <aside className="hidden w-64 flex-col border-r bg-muted/10 md:flex"> |
108 | | - <div className="p-4 font-medium text-sm text-muted-foreground uppercase tracking-wider"> |
109 | | - Explorer |
110 | | - </div> |
111 | | - <div className="flex-1 overflow-hidden px-2 pb-4"> |
112 | | - <MetadataExplorer |
113 | | - client={client} |
114 | | - selectedObject={selectedObject} |
115 | | - onSelectObject={(obj) => { |
116 | | - setSelectedObject(obj); |
117 | | - setView('list'); |
118 | | - setEditingRecord(null); |
119 | | - }} |
120 | | - /> |
121 | | - </div> |
122 | | - </aside> |
123 | | - |
124 | | - {/* Content Area */} |
125 | | - <main className="flex-1 overflow-hidden p-4 md:p-6 flex flex-col bg-muted/5 relative"> |
126 | | - {selectedObject ? ( |
127 | | - <> |
128 | | - <ObjectDataTable |
129 | | - key={`${selectedObject}-${refreshKey}`} |
130 | | - client={client} |
131 | | - objectApiName={selectedObject} |
132 | | - onEdit={handleEdit} |
133 | | - /> |
134 | | - {view === 'form' && ( |
135 | | - <ObjectDataForm |
136 | | - client={client} |
137 | | - objectApiName={selectedObject} |
138 | | - record={editingRecord} |
139 | | - onSuccess={handleFormSuccess} |
140 | | - onCancel={() => setView('list')} |
141 | | - /> |
142 | | - )} |
143 | | - </> |
144 | | - ) : ( |
145 | | - <div className="flex flex-col items-center justify-center h-full text-muted-foreground"> |
146 | | - <Database className="h-12 w-12 mb-4 opacity-20" /> |
147 | | - <div className="text-xl font-medium">Select an Object</div> |
148 | | - <p className="opacity-60">Choose an object from the sidebar to manage data.</p> |
149 | | - </div> |
150 | | - )} |
151 | | - </main> |
152 | | - </div> |
153 | | - </div> |
154 | | - ); |
| 87 | + </SidebarInset> |
| 88 | + <Toaster /> |
| 89 | + </SidebarProvider> |
| 90 | + ) |
155 | 91 | } |
0 commit comments