77import { useState , useEffect } from 'react' ;
88import { ObjectStackClient } from '@objectstack/client' ;
99import { ObjectForm } from '@object-ui/plugin-form' ;
10+ import { ObjectGrid } from '@object-ui/plugin-grid' ;
11+ import { Dialog , DialogContent , DialogHeader , DialogTitle , DialogDescription , Button } from '@object-ui/components' ;
1012import { ObjectStackDataSource } from './dataSource' ;
11- import { ContactList } from './components/ContactList' ;
1213import type { Contact } from './types' ;
1314import './App.css' ;
15+ import { Plus , LayoutDashboard , Users , Settings } from 'lucide-react' ;
1416
1517export function App ( ) {
1618 const [ client , setClient ] = useState < ObjectStackClient | null > ( null ) ;
1719 const [ dataSource , setDataSource ] = useState < ObjectStackDataSource | null > ( null ) ;
18- const [ editingContact , setEditingContact ] = useState < Contact | null > ( null ) ;
19- const [ refreshTrigger , setRefreshTrigger ] = useState ( 0 ) ;
20+ const [ isDialogOpen , setIsDialogOpen ] = useState ( false ) ;
21+ const [ editingRecord , setEditingRecord ] = useState < Contact | null > ( null ) ;
2022 const [ connected , setConnected ] = useState ( false ) ;
2123 const [ error , setError ] = useState < string | null > ( null ) ;
24+ const [ refreshKey , setRefreshKey ] = useState ( 0 ) ;
2225
2326 useEffect ( ( ) => {
2427 initializeClient ( ) ;
@@ -47,26 +50,24 @@ export function App() {
4750 }
4851 }
4952
50- function handleFormSuccess ( ) {
51- setEditingContact ( null ) ;
52- setRefreshTrigger ( prev => prev + 1 ) ;
53- }
53+ const handleCreate = ( ) => {
54+ setEditingRecord ( null ) ;
55+ setIsDialogOpen ( true ) ;
56+ } ;
5457
55- function handleEditContact ( contact : Contact ) {
56- setEditingContact ( contact ) ;
57- // Scroll to top on mobile
58- if ( window . innerWidth < 1024 ) {
59- window . scrollTo ( { top : 0 , behavior : 'smooth' } ) ;
60- }
61- }
58+ const handleEdit = ( record : any ) => {
59+ setEditingRecord ( record ) ;
60+ setIsDialogOpen ( true ) ;
61+ } ;
6262
63- function handleCancelEdit ( ) {
64- setEditingContact ( null ) ;
63+ const handleSuccess = ( ) => {
64+ setIsDialogOpen ( false ) ;
65+ setRefreshKey ( k => k + 1 ) ;
6566 }
6667
6768 if ( error ) {
6869 return (
69- < div className = "min-h-screen flex items-center justify-center p-6" >
70+ < div className = "min-h-screen flex items-center justify-center p-6 bg-slate-50 " >
7071 < div className = "max-w-md w-full p-8 border border-red-300 bg-red-50 rounded-lg shadow-sm text-center" >
7172 < h1 className = "text-xl font-bold text-red-800 mb-2" > Connection Error</ h1 >
7273 < p className = "text-red-600 mb-6" > { error } </ p >
@@ -83,100 +84,135 @@ export function App() {
8384
8485 if ( ! connected || ! client || ! dataSource ) {
8586 return (
86- < div className = "min-h-screen flex flex-col items-center justify-center p-6 space-y-4" >
87+ < div className = "min-h-screen flex flex-col items-center justify-center p-6 space-y-4 bg-slate-50 " >
8788 < div className = "w-8 h-8 rounded-full border-4 border-gray-200 border-t-gray-900 animate-spin" > </ div >
8889 < div className = "text-center" >
89- < h1 className = "text-lg font-semibold mb-1" > Connecting to ObjectStack...</ h1 >
90+ < h1 className = "text-lg font-semibold mb-1 text-gray-900 " > Connecting to ObjectStack...</ h1 >
9091 < p className = "text-gray-600 text-sm" > Initializing MSW and ObjectStack Client...</ p >
9192 </ div >
9293 </ div >
9394 ) ;
9495 }
9596
9697 return (
97- < div className = "max-w-7xl mx-auto px-6 py-12 min-h-screen" >
98- < header className = "text-center mb-12" >
99- < h1 className = "text-4xl font-bold tracking-tight mb-4" >
100- ObjectForm + MSW Integration
101- </ h1 >
102- < p className = "text-gray-600 text-lg mb-6" >
103- Complete CRUD operations using < code className = "text-sm bg-gray-100 px-2 py-1 rounded font-mono" > ObjectForm</ code > with Mock Service Worker
104- </ p >
105- < div className = "inline-flex items-center gap-2 px-4 py-2 rounded-full bg-green-50 border border-green-200 text-green-700 text-sm font-medium" >
106- < span className = "relative flex h-2 w-2" >
107- < span className = "animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" > </ span >
108- < span className = "relative inline-flex rounded-full h-2 w-2 bg-green-500" > </ span >
109- </ span >
110- MSW Active - All API calls are mocked
111- </ div >
112- </ header >
113-
114- < main className = "grid grid-cols-1 lg:grid-cols-12 gap-8" >
115- { /* Form Section */ }
116- < section className = "lg:col-span-5 lg:sticky lg:top-6 lg:self-start" >
117- < div className = "bg-white p-6 rounded-lg border border-gray-200 shadow-sm" >
118- < h2 className = "text-xl font-bold mb-4" >
119- { editingContact ? 'Edit Contact' : 'Create New Contact' }
120- </ h2 >
121-
122- < ObjectForm
123- schema = { {
124- type : 'object-form' ,
125- objectName : 'contact' ,
126- mode : editingContact ? 'edit' : 'create' ,
127- recordId : editingContact ?. id ,
128- fields : [
129- 'name' ,
130- 'email' ,
131- 'phone' ,
132- 'company' ,
133- 'position' ,
134- 'priority' ,
135- 'salary' , // Added
136- 'commission_rate' , // Added
137- 'birthdate' , // Added
138- 'last_contacted' , // Added
139- 'available_time' , // Added
140- 'profile_url' , // Added
141- 'department' , // Added
142- 'resume' , // Added
143- 'avatar' , // Added
144- 'is_active' ,
145- 'notes'
146- ] ,
147- layout : 'vertical' ,
148- columns : 1 ,
149- showSubmit : true ,
150- showCancel : editingContact !== null ,
151- submitText : editingContact ? 'Update Contact' : 'Create Contact' ,
152- cancelText : 'Cancel' ,
153- onSuccess : handleFormSuccess ,
154- onCancel : handleCancelEdit ,
155- } }
156- dataSource = { dataSource }
157- />
98+ < div className = "flex h-screen w-full bg-slate-50 overflow-hidden" >
99+ { /* Sidebar */ }
100+ < aside className = "w-64 bg-white border-r border-slate-200 hidden md:flex flex-col flex-shrink-0" >
101+ < div className = "h-16 flex items-center px-6 border-b border-slate-100" >
102+ < span className = "font-bold text-xl text-slate-800 tracking-tight" > ObjectUI</ span >
103+ </ div >
104+ < nav className = "flex-1 p-4 space-y-1 overflow-y-auto" >
105+ < Button variant = "ghost" className = "w-full justify-start font-medium" disabled >
106+ < LayoutDashboard className = "mr-2 h-4 w-4 text-slate-500" /> Dashboard
107+ </ Button >
108+ < Button variant = "secondary" className = "w-full justify-start font-medium text-slate-900 bg-slate-100" >
109+ < Users className = "mr-2 h-4 w-4 text-slate-700" /> Contacts
110+ </ Button >
111+ < Button variant = "ghost" className = "w-full justify-start font-medium text-slate-600 hover:text-slate-900" >
112+ < Settings className = "mr-2 h-4 w-4 text-slate-500" /> Settings
113+ </ Button >
114+ </ nav >
115+ < div className = "p-4 border-t border-slate-100" >
116+ < div className = "flex items-center gap-3" >
117+ < div className = "w-8 h-8 rounded-full bg-slate-200" > </ div >
118+ < div className = "text-sm" >
119+ < p className = "font-medium text-slate-700" > Admin User</ p >
120+ < p className = "text-slate-500 text-xs" > admin@example.com</ p >
121+ </ div >
122+ </ div >
158123 </ div >
159- </ section >
124+ </ aside >
160125
161- { /* List Section */ }
162- < section className = "lg:col-span-7" >
163- < ContactList
164- client = { client }
165- onEdit = { handleEditContact }
166- refreshTrigger = { refreshTrigger }
167- />
168- </ section >
169- </ main >
126+ { /* Main Content */ }
127+ < main className = "flex-1 flex flex-col min-w-0 h-full relative" >
128+ { /* Header */ }
129+ < header className = "flex-shrink-0 h-16 bg-white border-b border-slate-200 flex items-center justify-between px-6 shadow-sm z-10 w-full" >
130+ < div >
131+ < h1 className = "text-xl font-semibold text-slate-800" > Contacts</ h1 >
132+ < p className = "text-sm text-slate-500" > Manage your organization's contacts</ p >
133+ </ div >
134+ < Button onClick = { handleCreate } className = "bg-blue-600 hover:bg-blue-700 text-white shadow-sm" >
135+ < Plus className = "mr-2 h-4 w-4" /> New Contact
136+ </ Button >
137+ </ header >
138+
139+ { /* Table Area */ }
140+ < div className = "flex-1 overflow-hidden p-6 relative" >
141+ < div className = "rounded-xl border border-slate-200 bg-white shadow-sm h-full flex flex-col overflow-hidden" >
142+ < ObjectGrid
143+ key = { refreshKey }
144+ schema = { {
145+ type : 'object-grid' ,
146+ objectName : 'contact' ,
147+ filters : [ ] ,
148+ searchableFields : [ 'name' , 'email' , 'company' ] ,
149+ columns : [
150+ { field : 'name' , label : 'Name' , width : 200 } ,
151+ { field : 'email' , label : 'Email' , width : 220 } ,
152+ { field : 'phone' , label : 'Phone' , width : 150 } ,
153+ { field : 'company' , label : 'Company' , width : 180 } ,
154+ { field : 'role' , label : 'Role' , width : 150 } ,
155+ { field : 'status' , label : 'Status' }
156+ ]
157+ } }
158+ dataSource = { dataSource }
159+ onEdit = { handleEdit }
160+ className = "h-full w-full"
161+ />
162+ </ div >
163+ </ div >
164+ </ main >
170165
171- < footer className = "mt-16 pt-8 border-t border-gray-200 text-center text-sm text-gray-500 space-y-2" >
172- < p >
173- This example demonstrates ObjectForm integration with MSW.
174- All API calls are intercepted and mocked in the browser.
175- </ p >
176- < p className = "font-mono text-xs text-gray-400" >
177- React + TypeScript + Vite + MSW + ObjectForm
178- </ p >
179- </ footer >
166+ { /* Form Dialog */ }
167+ < Dialog open = { isDialogOpen } onOpenChange = { setIsDialogOpen } >
168+ < DialogContent className = "sm:max-w-2xl max-h-[90vh] overflow-y-auto flex flex-col gap-0 p-0" >
169+ < DialogHeader className = "p-6 pb-2 border-b border-slate-100" >
170+ < DialogTitle className = "text-xl" > { editingRecord ? 'Edit Contact' : 'New Contact' } </ DialogTitle >
171+ < DialogDescription >
172+ { editingRecord ? 'Update contact details below.' : 'Fill in the information to create a new contact.' }
173+ </ DialogDescription >
174+ </ DialogHeader >
175+
176+ < div className = "p-6 pt-4 flex-1 overflow-y-auto" >
177+ < ObjectForm
178+ schema = { {
179+ type : 'object-form' ,
180+ objectName : 'contact' ,
181+ mode : editingRecord ? 'edit' : 'create' ,
182+ recordId : editingRecord ?. id ,
183+ fields : [
184+ 'name' ,
185+ 'email' ,
186+ 'phone' ,
187+ 'company' ,
188+ 'position' ,
189+ 'priority' ,
190+ 'salary' ,
191+ 'commission_rate' ,
192+ 'birthdate' ,
193+ 'last_contacted' ,
194+ 'available_time' ,
195+ 'profile_url' ,
196+ 'department' ,
197+ 'resume' ,
198+ 'avatar' ,
199+ 'is_active' ,
200+ 'notes'
201+ ] ,
202+ layout : 'vertical' ,
203+ columns : 2 ,
204+ onSuccess : handleSuccess ,
205+ onCancel : ( ) => setIsDialogOpen ( false ) ,
206+ showSubmit : true ,
207+ showCancel : true ,
208+ submitText : editingRecord ? 'Save Changes' : 'Create Contact' ,
209+ } }
210+ dataSource = { dataSource }
211+ className = "space-y-4"
212+ />
213+ </ div >
214+ </ DialogContent >
215+ </ Dialog >
180216 </ div >
181217 ) ;
182218}
0 commit comments