Skip to content

Commit 51f451c

Browse files
committed
2 parents a382837 + f7a6057 commit 51f451c

31 files changed

Lines changed: 2740 additions & 293 deletions

examples/app-react-crud/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
<head>
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<link rel="preconnect" href="https://fonts.googleapis.com">
7+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
69
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
710
<title>ObjectStack MSW + React CRUD Example</title>
811
</head>

examples/app-react-crud/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,19 @@
2323
"@objectstack/plugin-msw": "workspace:*",
2424
"@objectstack/runtime": "workspace:*",
2525
"@objectstack/spec": "workspace:*",
26+
"@radix-ui/react-dialog": "^1.1.15",
27+
"@radix-ui/react-label": "^2.1.8",
2628
"@radix-ui/react-scroll-area": "^1.2.10",
29+
"@radix-ui/react-separator": "^1.1.8",
2730
"@radix-ui/react-slot": "^1.2.4",
31+
"@radix-ui/react-toast": "^1.2.15",
32+
"@radix-ui/react-tooltip": "^1.2.8",
2833
"class-variance-authority": "^0.7.1",
2934
"clsx": "^2.1.1",
3035
"lucide-react": "^0.562.0",
3136
"react": "^18.3.1",
3237
"react-dom": "^18.3.1",
38+
"react-resizable-panels": "^4.6.1",
3339
"tailwind-merge": "^3.4.0"
3440
},
3541
"devDependencies": {

examples/app-react-crud/src/App.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@import "tailwindcss";
2+
@config "../tailwind.config.js";
23

34
@layer base {
45
:root {
Lines changed: 77 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,91 @@
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-
101
import { useState, useEffect } from 'react';
112
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"
1418
import { ObjectDataTable } from './components/ObjectDataTable';
15-
import { ObjectDataForm } from './components/ObjectDataForm';
16-
import './App.css';
19+
import { Toaster } from "@/components/ui/toaster"
1720

18-
export function App() {
21+
export default function App() {
1922
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);
2824

2925
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);
3136
}, []);
3237

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-
7938
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+
)}
9173
</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>
10084
</div>
85+
)}
10186
</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+
)
15591
}
Lines changed: 31 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { useState, useEffect } from 'react';
22
import { ObjectStackClient } from '@objectstack/client';
3-
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
4-
import { ScrollArea } from "@/components/ui/scroll-area";
53
import { Button } from "@/components/ui/button";
4+
import { Badge } from "@/components/ui/badge";
65
import { Database, Package } from 'lucide-react';
76

87
interface MetadataExplorerProps {
@@ -44,52 +43,37 @@ export function MetadataExplorer({ client, selectedObject, onSelectObject }: Met
4443
}, [client]);
4544

4645
return (
47-
<Card className="h-full flex flex-col border-border/60">
48-
<CardHeader className="p-4 border-b bg-muted/20">
49-
<CardTitle className="text-sm font-bold flex items-center gap-2">
50-
<Database className="h-4 w-4" />
51-
Registered Objects
52-
</CardTitle>
53-
</CardHeader>
54-
<CardContent className="flex-1 p-0 overflow-hidden">
55-
<ScrollArea className="h-full">
56-
<div className="p-2 space-y-1">
57-
{loading && (
58-
<div className="p-4 text-center text-sm text-muted-foreground animate-pulse">
59-
Loading objects...
60-
</div>
61-
)}
62-
63-
{objects.map(obj => (
64-
<Button
65-
key={obj.name}
66-
variant={selectedObject === obj.name ? "secondary" : "ghost"}
67-
size="sm"
68-
onClick={() => onSelectObject(obj.name)}
69-
className="w-full justify-between font-normal h-9"
70-
>
71-
<div className="flex items-center gap-2 truncate">
72-
<Package className="h-3.5 w-3.5 text-muted-foreground" />
73-
<span>{obj.label}</span>
74-
</div>
75-
<span className="text-xs text-muted-foreground font-mono opacity-50 ml-2 shrink-0">
76-
{obj.name}
77-
</span>
78-
</Button>
79-
))}
46+
<div className="space-y-1">
47+
{loading && (
48+
<div className="p-4 text-center text-sm text-muted-foreground animate-pulse">
49+
Loading objects...
50+
</div>
51+
)}
52+
53+
{objects.map(obj => (
54+
<Button
55+
key={obj.name}
56+
variant={selectedObject === obj.name ? "secondary" : "ghost"}
57+
size="sm"
58+
onClick={() => onSelectObject(obj.name)}
59+
className="w-full justify-between font-normal h-9"
60+
>
61+
<div className="flex items-center gap-2 truncate">
62+
<Package className="h-3.5 w-3.5 text-muted-foreground" />
63+
<span>{obj.label}</span>
64+
</div>
65+
<Badge variant="outline" className="text-[10px] h-5 py-0 px-1.5 font-normal opacity-70">
66+
{obj.name}
67+
</Badge>
68+
</Button>
69+
))}
8070

81-
{!loading && objects.length === 0 && (
82-
<div className="p-8 text-center text-muted-foreground text-sm flex flex-col items-center gap-2">
83-
<Database className="h-8 w-8 opacity-20" />
84-
<p>No objects found</p>
85-
</div>
86-
)}
71+
{!loading && objects.length === 0 && (
72+
<div className="p-8 text-center text-muted-foreground text-sm flex flex-col items-center gap-2">
73+
<Database className="h-8 w-8 opacity-20" />
74+
<p>No objects found</p>
8775
</div>
88-
</ScrollArea>
89-
</CardContent>
90-
<CardFooter className="p-2 border-t bg-muted/20 text-xs text-muted-foreground justify-center">
91-
Total: {objects.length} Objects
92-
</CardFooter>
93-
</Card>
76+
)}
77+
</div>
9478
);
9579
}

0 commit comments

Comments
 (0)