@fluxbase/sdk-react 0.0.1-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1076 @@
1
+ # Fluxbase React Admin Hooks
2
+
3
+ Comprehensive React hooks for building admin dashboards and management interfaces with Fluxbase.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Quick Start](#quick-start)
9
+ - [Admin Authentication](#admin-authentication)
10
+ - [User Management](#user-management)
11
+ - [API Keys](#api-keys)
12
+ - [Webhooks](#webhooks)
13
+ - [Settings Management](#settings-management)
14
+ - [Complete Examples](#complete-examples)
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @fluxbase/sdk @fluxbase/sdk-react @tanstack/react-query
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```tsx
25
+ import { createClient } from '@fluxbase/sdk'
26
+ import { FluxbaseProvider } from '@fluxbase/sdk-react'
27
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
28
+ import AdminDashboard from './AdminDashboard'
29
+
30
+ const client = createClient({ url: 'http://localhost:8080' })
31
+ const queryClient = new QueryClient()
32
+
33
+ function App() {
34
+ return (
35
+ <QueryClientProvider client={queryClient}>
36
+ <FluxbaseProvider client={client}>
37
+ <AdminDashboard />
38
+ </FluxbaseProvider>
39
+ </QueryClientProvider>
40
+ )
41
+ }
42
+ ```
43
+
44
+ ## Admin Authentication
45
+
46
+ ### useAdminAuth
47
+
48
+ Hook for managing admin authentication state.
49
+
50
+ ```tsx
51
+ import { useAdminAuth } from '@fluxbase/sdk-react'
52
+
53
+ function AdminLogin() {
54
+ const { user, isAuthenticated, isLoading, error, login, logout } = useAdminAuth({
55
+ autoCheck: true // Automatically check if admin is authenticated on mount
56
+ })
57
+
58
+ const [email, setEmail] = useState('')
59
+ const [password, setPassword] = useState('')
60
+
61
+ const handleLogin = async (e: React.FormEvent) => {
62
+ e.preventDefault()
63
+ try {
64
+ await login(email, password)
65
+ // Redirect to admin dashboard
66
+ } catch (err) {
67
+ console.error('Login failed:', err)
68
+ }
69
+ }
70
+
71
+ if (isLoading) {
72
+ return <div>Checking authentication...</div>
73
+ }
74
+
75
+ if (isAuthenticated) {
76
+ return (
77
+ <div>
78
+ <p>Logged in as: {user?.email}</p>
79
+ <p>Role: {user?.role}</p>
80
+ <button onClick={logout}>Logout</button>
81
+ </div>
82
+ )
83
+ }
84
+
85
+ return (
86
+ <form onSubmit={handleLogin}>
87
+ <input
88
+ type="email"
89
+ value={email}
90
+ onChange={(e) => setEmail(e.target.value)}
91
+ placeholder="Admin email"
92
+ required
93
+ />
94
+ <input
95
+ type="password"
96
+ value={password}
97
+ onChange={(e) => setPassword(e.target.value)}
98
+ placeholder="Password"
99
+ required
100
+ />
101
+ <button type="submit">Login</button>
102
+ {error && <p className="error">{error.message}</p>}
103
+ </form>
104
+ )
105
+ }
106
+ ```
107
+
108
+ ### Protected Admin Routes
109
+
110
+ ```tsx
111
+ import { useAdminAuth } from '@fluxbase/sdk-react'
112
+ import { Navigate } from 'react-router-dom'
113
+
114
+ function ProtectedAdminRoute({ children }: { children: React.ReactNode }) {
115
+ const { isAuthenticated, isLoading } = useAdminAuth({ autoCheck: true })
116
+
117
+ if (isLoading) {
118
+ return <div>Loading...</div>
119
+ }
120
+
121
+ if (!isAuthenticated) {
122
+ return <Navigate to="/admin/login" replace />
123
+ }
124
+
125
+ return <>{children}</>
126
+ }
127
+
128
+ // Usage
129
+ <Routes>
130
+ <Route path="/admin/login" element={<AdminLogin />} />
131
+ <Route
132
+ path="/admin/*"
133
+ element={
134
+ <ProtectedAdminRoute>
135
+ <AdminDashboard />
136
+ </ProtectedAdminRoute>
137
+ }
138
+ />
139
+ </Routes>
140
+ ```
141
+
142
+ ## User Management
143
+
144
+ ### useUsers
145
+
146
+ Hook for managing users with pagination and CRUD operations.
147
+
148
+ ```tsx
149
+ import { useUsers } from '@fluxbase/sdk-react'
150
+ import { useState } from 'react'
151
+
152
+ function UserManagement() {
153
+ const [page, setPage] = useState(0)
154
+ const limit = 20
155
+
156
+ const {
157
+ users,
158
+ total,
159
+ isLoading,
160
+ error,
161
+ refetch,
162
+ inviteUser,
163
+ updateUserRole,
164
+ deleteUser,
165
+ resetPassword
166
+ } = useUsers({
167
+ autoFetch: true,
168
+ limit,
169
+ offset: page * limit
170
+ })
171
+
172
+ const handleInvite = async () => {
173
+ const email = prompt('Enter email:')
174
+ const role = confirm('Admin role?') ? 'admin' : 'user'
175
+ if (email) {
176
+ await inviteUser(email, role)
177
+ }
178
+ }
179
+
180
+ const handleRoleChange = async (userId: string, currentRole: string) => {
181
+ const newRole = currentRole === 'admin' ? 'user' : 'admin'
182
+ await updateUserRole(userId, newRole)
183
+ }
184
+
185
+ const handleDelete = async (userId: string) => {
186
+ if (confirm('Delete this user?')) {
187
+ await deleteUser(userId)
188
+ }
189
+ }
190
+
191
+ const handleResetPassword = async (userId: string) => {
192
+ const newPassword = await resetPassword(userId)
193
+ alert(`New password: ${newPassword}`)
194
+ }
195
+
196
+ if (isLoading) return <div>Loading users...</div>
197
+ if (error) return <div>Error: {error.message}</div>
198
+
199
+ return (
200
+ <div>
201
+ <div className="header">
202
+ <h2>User Management ({total} users)</h2>
203
+ <button onClick={handleInvite}>Invite User</button>
204
+ <button onClick={refetch}>Refresh</button>
205
+ </div>
206
+
207
+ <table>
208
+ <thead>
209
+ <tr>
210
+ <th>Email</th>
211
+ <th>Role</th>
212
+ <th>Status</th>
213
+ <th>Created</th>
214
+ <th>Actions</th>
215
+ </tr>
216
+ </thead>
217
+ <tbody>
218
+ {users.map((user) => (
219
+ <tr key={user.id}>
220
+ <td>{user.email}</td>
221
+ <td>
222
+ <span className={`badge ${user.role}`}>{user.role}</span>
223
+ </td>
224
+ <td>
225
+ <span className={`status ${user.email_confirmed ? 'confirmed' : 'pending'}`}>
226
+ {user.email_confirmed ? 'Confirmed' : 'Pending'}
227
+ </span>
228
+ </td>
229
+ <td>{new Date(user.created_at).toLocaleDateString()}</td>
230
+ <td>
231
+ <button onClick={() => handleRoleChange(user.id, user.role)}>
232
+ Toggle Role
233
+ </button>
234
+ <button onClick={() => handleResetPassword(user.id)}>
235
+ Reset Password
236
+ </button>
237
+ <button onClick={() => handleDelete(user.id)}>Delete</button>
238
+ </td>
239
+ </tr>
240
+ ))}
241
+ </tbody>
242
+ </table>
243
+
244
+ <div className="pagination">
245
+ <button disabled={page === 0} onClick={() => setPage(page - 1)}>
246
+ Previous
247
+ </button>
248
+ <span>
249
+ Page {page + 1} of {Math.ceil(total / limit)}
250
+ </span>
251
+ <button
252
+ disabled={(page + 1) * limit >= total}
253
+ onClick={() => setPage(page + 1)}
254
+ >
255
+ Next
256
+ </button>
257
+ </div>
258
+ </div>
259
+ )
260
+ }
261
+ ```
262
+
263
+ ### User Search and Filters
264
+
265
+ ```tsx
266
+ import { useUsers } from '@fluxbase/sdk-react'
267
+ import { useState, useEffect } from 'react'
268
+
269
+ function UserSearch() {
270
+ const [searchEmail, setSearchEmail] = useState('')
271
+ const [roleFilter, setRoleFilter] = useState<'admin' | 'user' | undefined>()
272
+
273
+ const { users, isLoading, refetch } = useUsers({
274
+ autoFetch: true,
275
+ email: searchEmail || undefined,
276
+ role: roleFilter
277
+ })
278
+
279
+ // Refetch when filters change
280
+ useEffect(() => {
281
+ refetch()
282
+ }, [searchEmail, roleFilter, refetch])
283
+
284
+ return (
285
+ <div>
286
+ <input
287
+ type="text"
288
+ placeholder="Search by email..."
289
+ value={searchEmail}
290
+ onChange={(e) => setSearchEmail(e.target.value)}
291
+ />
292
+ <select value={roleFilter || ''} onChange={(e) => setRoleFilter(e.target.value as any)}>
293
+ <option value="">All Roles</option>
294
+ <option value="admin">Admin</option>
295
+ <option value="user">User</option>
296
+ </select>
297
+
298
+ {isLoading ? (
299
+ <div>Searching...</div>
300
+ ) : (
301
+ <ul>
302
+ {users.map((user) => (
303
+ <li key={user.id}>
304
+ {user.email} - {user.role}
305
+ </li>
306
+ ))}
307
+ </ul>
308
+ )}
309
+ </div>
310
+ )
311
+ }
312
+ ```
313
+
314
+ ## API Keys
315
+
316
+ ### useAPIKeys
317
+
318
+ Hook for managing API keys.
319
+
320
+ ```tsx
321
+ import { useAPIKeys } from '@fluxbase/sdk-react'
322
+ import { useState } from 'react'
323
+
324
+ function APIKeyManagement() {
325
+ const { keys, isLoading, error, createKey, updateKey, revokeKey, deleteKey } = useAPIKeys({
326
+ autoFetch: true
327
+ })
328
+
329
+ const [showCreateForm, setShowCreateForm] = useState(false)
330
+ const [newKeyData, setNewKeyData] = useState<{
331
+ name: string
332
+ description: string
333
+ expiresInDays: number
334
+ }>({ name: '', description: '', expiresInDays: 365 })
335
+
336
+ const handleCreate = async (e: React.FormEvent) => {
337
+ e.preventDefault()
338
+ try {
339
+ const expiresAt = new Date()
340
+ expiresAt.setDate(expiresAt.getDate() + newKeyData.expiresInDays)
341
+
342
+ const result = await createKey({
343
+ name: newKeyData.name,
344
+ description: newKeyData.description,
345
+ expires_at: expiresAt.toISOString()
346
+ })
347
+
348
+ // Show the key (only time it's visible)
349
+ alert(`API Key created!\n\nKey: ${result.key}\n\nSave this securely - it won't be shown again!`)
350
+
351
+ setShowCreateForm(false)
352
+ setNewKeyData({ name: '', description: '', expiresInDays: 365 })
353
+ } catch (err) {
354
+ console.error('Failed to create key:', err)
355
+ }
356
+ }
357
+
358
+ const handleRevoke = async (keyId: string) => {
359
+ if (confirm('Revoke this API key? It will immediately stop working.')) {
360
+ await revokeKey(keyId)
361
+ }
362
+ }
363
+
364
+ const handleDelete = async (keyId: string) => {
365
+ if (confirm('Permanently delete this API key?')) {
366
+ await deleteKey(keyId)
367
+ }
368
+ }
369
+
370
+ if (isLoading) return <div>Loading API keys...</div>
371
+ if (error) return <div>Error: {error.message}</div>
372
+
373
+ return (
374
+ <div>
375
+ <div className="header">
376
+ <h2>API Keys ({keys.length})</h2>
377
+ <button onClick={() => setShowCreateForm(!showCreateForm)}>
378
+ {showCreateForm ? 'Cancel' : 'Create New Key'}
379
+ </button>
380
+ </div>
381
+
382
+ {showCreateForm && (
383
+ <form onSubmit={handleCreate} className="create-form">
384
+ <h3>Create New API Key</h3>
385
+ <input
386
+ type="text"
387
+ placeholder="Key name (e.g., Backend Service)"
388
+ value={newKeyData.name}
389
+ onChange={(e) => setNewKeyData({ ...newKeyData, name: e.target.value })}
390
+ required
391
+ />
392
+ <textarea
393
+ placeholder="Description (optional)"
394
+ value={newKeyData.description}
395
+ onChange={(e) => setNewKeyData({ ...newKeyData, description: e.target.value })}
396
+ />
397
+ <label>
398
+ Expires in:
399
+ <input
400
+ type="number"
401
+ min="1"
402
+ max="3650"
403
+ value={newKeyData.expiresInDays}
404
+ onChange={(e) => setNewKeyData({ ...newKeyData, expiresInDays: parseInt(e.target.value) })}
405
+ />
406
+ days
407
+ </label>
408
+ <button type="submit">Create Key</button>
409
+ </form>
410
+ )}
411
+
412
+ <table>
413
+ <thead>
414
+ <tr>
415
+ <th>Name</th>
416
+ <th>Description</th>
417
+ <th>Created</th>
418
+ <th>Expires</th>
419
+ <th>Actions</th>
420
+ </tr>
421
+ </thead>
422
+ <tbody>
423
+ {keys.map((key) => (
424
+ <tr key={key.id}>
425
+ <td>{key.name}</td>
426
+ <td>{key.description}</td>
427
+ <td>{new Date(key.created_at).toLocaleDateString()}</td>
428
+ <td>
429
+ {key.expires_at ? (
430
+ <span className={new Date(key.expires_at) < new Date() ? 'expired' : ''}>
431
+ {new Date(key.expires_at).toLocaleDateString()}
432
+ </span>
433
+ ) : (
434
+ 'Never'
435
+ )}
436
+ </td>
437
+ <td>
438
+ <button onClick={() => handleRevoke(key.id)}>Revoke</button>
439
+ <button onClick={() => handleDelete(key.id)}>Delete</button>
440
+ </td>
441
+ </tr>
442
+ ))}
443
+ </tbody>
444
+ </table>
445
+ </div>
446
+ )
447
+ }
448
+ ```
449
+
450
+ ## Webhooks
451
+
452
+ ### useWebhooks
453
+
454
+ Hook for managing webhooks and monitoring deliveries.
455
+
456
+ ```tsx
457
+ import { useWebhooks } from '@fluxbase/sdk-react'
458
+ import { useState } from 'react'
459
+
460
+ function WebhookManagement() {
461
+ const {
462
+ webhooks,
463
+ isLoading,
464
+ error,
465
+ createWebhook,
466
+ updateWebhook,
467
+ deleteWebhook,
468
+ testWebhook,
469
+ getDeliveries,
470
+ retryDelivery
471
+ } = useWebhooks({ autoFetch: true })
472
+
473
+ const [showCreateForm, setShowCreateForm] = useState(false)
474
+ const [selectedWebhook, setSelectedWebhook] = useState<string | null>(null)
475
+ const [deliveries, setDeliveries] = useState<any[]>([])
476
+
477
+ const handleCreate = async (e: React.FormEvent) => {
478
+ e.preventDefault()
479
+ const formData = new FormData(e.target as HTMLFormElement)
480
+
481
+ await createWebhook({
482
+ name: formData.get('name') as string,
483
+ url: formData.get('url') as string,
484
+ events: ['INSERT', 'UPDATE', 'DELETE'],
485
+ table: formData.get('table') as string,
486
+ schema: 'public',
487
+ enabled: true,
488
+ secret: `webhook_secret_${Math.random().toString(36).substring(7)}`
489
+ })
490
+
491
+ setShowCreateForm(false)
492
+ }
493
+
494
+ const handleTest = async (webhookId: string) => {
495
+ try {
496
+ await testWebhook(webhookId)
497
+ alert('Test webhook sent! Check your endpoint.')
498
+ } catch (err) {
499
+ alert('Test failed: ' + (err as Error).message)
500
+ }
501
+ }
502
+
503
+ const handleToggle = async (webhookId: string, currentlyEnabled: boolean) => {
504
+ await updateWebhook(webhookId, { enabled: !currentlyEnabled })
505
+ }
506
+
507
+ const viewDeliveries = async (webhookId: string) => {
508
+ const result = await getDeliveries(webhookId, { limit: 20 })
509
+ setDeliveries(result.deliveries)
510
+ setSelectedWebhook(webhookId)
511
+ }
512
+
513
+ if (isLoading) return <div>Loading webhooks...</div>
514
+ if (error) return <div>Error: {error.message}</div>
515
+
516
+ return (
517
+ <div>
518
+ <div className="header">
519
+ <h2>Webhooks ({webhooks.length})</h2>
520
+ <button onClick={() => setShowCreateForm(!showCreateForm)}>
521
+ {showCreateForm ? 'Cancel' : 'Create Webhook'}
522
+ </button>
523
+ </div>
524
+
525
+ {showCreateForm && (
526
+ <form onSubmit={handleCreate} className="create-form">
527
+ <h3>Create New Webhook</h3>
528
+ <input name="name" placeholder="Webhook name" required />
529
+ <input name="url" type="url" placeholder="https://example.com/webhook" required />
530
+ <input name="table" placeholder="Table name (e.g., users)" required />
531
+ <button type="submit">Create</button>
532
+ </form>
533
+ )}
534
+
535
+ <table>
536
+ <thead>
537
+ <tr>
538
+ <th>Name</th>
539
+ <th>URL</th>
540
+ <th>Table</th>
541
+ <th>Events</th>
542
+ <th>Status</th>
543
+ <th>Actions</th>
544
+ </tr>
545
+ </thead>
546
+ <tbody>
547
+ {webhooks.map((webhook) => (
548
+ <tr key={webhook.id}>
549
+ <td>{webhook.name}</td>
550
+ <td className="url">{webhook.url}</td>
551
+ <td>{webhook.schema}.{webhook.table}</td>
552
+ <td>{webhook.events.join(', ')}</td>
553
+ <td>
554
+ <span className={`status ${webhook.enabled ? 'enabled' : 'disabled'}`}>
555
+ {webhook.enabled ? 'Enabled' : 'Disabled'}
556
+ </span>
557
+ </td>
558
+ <td>
559
+ <button onClick={() => handleToggle(webhook.id, webhook.enabled)}>
560
+ {webhook.enabled ? 'Disable' : 'Enable'}
561
+ </button>
562
+ <button onClick={() => handleTest(webhook.id)}>Test</button>
563
+ <button onClick={() => viewDeliveries(webhook.id)}>Deliveries</button>
564
+ <button onClick={() => deleteWebhook(webhook.id)}>Delete</button>
565
+ </td>
566
+ </tr>
567
+ ))}
568
+ </tbody>
569
+ </table>
570
+
571
+ {selectedWebhook && (
572
+ <div className="deliveries">
573
+ <h3>Recent Deliveries</h3>
574
+ <button onClick={() => setSelectedWebhook(null)}>Close</button>
575
+ <table>
576
+ <thead>
577
+ <tr>
578
+ <th>Status</th>
579
+ <th>Response Code</th>
580
+ <th>Attempt</th>
581
+ <th>Created</th>
582
+ <th>Actions</th>
583
+ </tr>
584
+ </thead>
585
+ <tbody>
586
+ {deliveries.map((delivery) => (
587
+ <tr key={delivery.id}>
588
+ <td>
589
+ <span className={`status ${delivery.status}`}>{delivery.status}</span>
590
+ </td>
591
+ <td>{delivery.response_status_code || 'N/A'}</td>
592
+ <td>{delivery.attempt_count}</td>
593
+ <td>{new Date(delivery.created_at).toLocaleString()}</td>
594
+ <td>
595
+ {delivery.status === 'failed' && (
596
+ <button onClick={() => retryDelivery(selectedWebhook, delivery.id)}>
597
+ Retry
598
+ </button>
599
+ )}
600
+ </td>
601
+ </tr>
602
+ ))}
603
+ </tbody>
604
+ </table>
605
+ </div>
606
+ )}
607
+ </div>
608
+ )
609
+ }
610
+ ```
611
+
612
+ ## Settings Management
613
+
614
+ ### useAppSettings
615
+
616
+ Hook for managing application-wide settings.
617
+
618
+ ```tsx
619
+ import { useAppSettings } from '@fluxbase/sdk-react'
620
+
621
+ function AppSettingsPanel() {
622
+ const { settings, isLoading, error, updateSettings } = useAppSettings({
623
+ autoFetch: true
624
+ })
625
+
626
+ const handleToggleFeature = async (feature: string, enabled: boolean) => {
627
+ await updateSettings({
628
+ features: {
629
+ ...settings?.features,
630
+ [feature]: enabled
631
+ }
632
+ })
633
+ }
634
+
635
+ const handleUpdateSecurity = async (e: React.FormEvent) => {
636
+ e.preventDefault()
637
+ const formData = new FormData(e.target as HTMLFormElement)
638
+
639
+ await updateSettings({
640
+ security: {
641
+ enable_rate_limiting: formData.get('rateLimiting') === 'on',
642
+ rate_limit_requests_per_minute: parseInt(formData.get('rateLimit') as string)
643
+ }
644
+ })
645
+ }
646
+
647
+ if (isLoading) return <div>Loading settings...</div>
648
+ if (error) return <div>Error: {error.message}</div>
649
+ if (!settings) return <div>No settings found</div>
650
+
651
+ return (
652
+ <div>
653
+ <h2>Application Settings</h2>
654
+
655
+ <section>
656
+ <h3>Features</h3>
657
+ <label>
658
+ <input
659
+ type="checkbox"
660
+ checked={settings.features?.enable_realtime ?? false}
661
+ onChange={(e) => handleToggleFeature('enable_realtime', e.target.checked)}
662
+ />
663
+ Enable Realtime
664
+ </label>
665
+ <label>
666
+ <input
667
+ type="checkbox"
668
+ checked={settings.features?.enable_storage ?? false}
669
+ onChange={(e) => handleToggleFeature('enable_storage', e.target.checked)}
670
+ />
671
+ Enable Storage
672
+ </label>
673
+ <label>
674
+ <input
675
+ type="checkbox"
676
+ checked={settings.features?.enable_functions ?? false}
677
+ onChange={(e) => handleToggleFeature('enable_functions', e.target.checked)}
678
+ />
679
+ Enable Functions
680
+ </label>
681
+ </section>
682
+
683
+ <section>
684
+ <h3>Security</h3>
685
+ <form onSubmit={handleUpdateSecurity}>
686
+ <label>
687
+ <input
688
+ type="checkbox"
689
+ name="rateLimiting"
690
+ defaultChecked={settings.security?.enable_rate_limiting ?? false}
691
+ />
692
+ Enable Rate Limiting
693
+ </label>
694
+ <label>
695
+ Requests per minute:
696
+ <input
697
+ type="number"
698
+ name="rateLimit"
699
+ defaultValue={settings.security?.rate_limit_requests_per_minute ?? 60}
700
+ min="1"
701
+ />
702
+ </label>
703
+ <button type="submit">Update Security</button>
704
+ </form>
705
+ </section>
706
+ </div>
707
+ )
708
+ }
709
+ ```
710
+
711
+ ### useSystemSettings
712
+
713
+ Hook for managing system-wide key-value settings.
714
+
715
+ ```tsx
716
+ import { useSystemSettings } from '@fluxbase/sdk-react'
717
+ import { useState } from 'react'
718
+
719
+ function SystemSettingsPanel() {
720
+ const { settings, isLoading, error, getSetting, updateSetting, deleteSetting } = useSystemSettings({
721
+ autoFetch: true
722
+ })
723
+
724
+ const [editingKey, setEditingKey] = useState<string | null>(null)
725
+ const [editValue, setEditValue] = useState('')
726
+
727
+ const handleEdit = (key: string, currentValue: any) => {
728
+ setEditingKey(key)
729
+ setEditValue(JSON.stringify(currentValue, null, 2))
730
+ }
731
+
732
+ const handleSave = async () => {
733
+ if (!editingKey) return
734
+
735
+ try {
736
+ const parsedValue = JSON.parse(editValue)
737
+ await updateSetting(editingKey, { value: parsedValue })
738
+ setEditingKey(null)
739
+ } catch (err) {
740
+ alert('Invalid JSON')
741
+ }
742
+ }
743
+
744
+ const handleCreate = async () => {
745
+ const key = prompt('Setting key:')
746
+ const valueStr = prompt('Setting value (JSON):')
747
+ const description = prompt('Description:')
748
+
749
+ if (key && valueStr) {
750
+ try {
751
+ const value = JSON.parse(valueStr)
752
+ await updateSetting(key, { value, description: description || undefined })
753
+ } catch (err) {
754
+ alert('Invalid JSON')
755
+ }
756
+ }
757
+ }
758
+
759
+ const handleDelete = async (key: string) => {
760
+ if (confirm(`Delete setting "${key}"?`)) {
761
+ await deleteSetting(key)
762
+ }
763
+ }
764
+
765
+ if (isLoading) return <div>Loading settings...</div>
766
+ if (error) return <div>Error: {error.message}</div>
767
+
768
+ return (
769
+ <div>
770
+ <div className="header">
771
+ <h2>System Settings ({settings.length})</h2>
772
+ <button onClick={handleCreate}>Create Setting</button>
773
+ </div>
774
+
775
+ <table>
776
+ <thead>
777
+ <tr>
778
+ <th>Key</th>
779
+ <th>Value</th>
780
+ <th>Description</th>
781
+ <th>Actions</th>
782
+ </tr>
783
+ </thead>
784
+ <tbody>
785
+ {settings.map((setting) => (
786
+ <tr key={setting.key}>
787
+ <td><code>{setting.key}</code></td>
788
+ <td>
789
+ {editingKey === setting.key ? (
790
+ <textarea
791
+ value={editValue}
792
+ onChange={(e) => setEditValue(e.target.value)}
793
+ rows={5}
794
+ />
795
+ ) : (
796
+ <pre>{JSON.stringify(setting.value, null, 2)}</pre>
797
+ )}
798
+ </td>
799
+ <td>{setting.description}</td>
800
+ <td>
801
+ {editingKey === setting.key ? (
802
+ <>
803
+ <button onClick={handleSave}>Save</button>
804
+ <button onClick={() => setEditingKey(null)}>Cancel</button>
805
+ </>
806
+ ) : (
807
+ <>
808
+ <button onClick={() => handleEdit(setting.key, setting.value)}>Edit</button>
809
+ <button onClick={() => handleDelete(setting.key)}>Delete</button>
810
+ </>
811
+ )}
812
+ </td>
813
+ </tr>
814
+ ))}
815
+ </tbody>
816
+ </table>
817
+ </div>
818
+ )
819
+ }
820
+ ```
821
+
822
+ ## Complete Examples
823
+
824
+ ### Full Admin Dashboard
825
+
826
+ ```tsx
827
+ import { useAdminAuth, useUsers, useAPIKeys, useWebhooks, useAppSettings } from '@fluxbase/sdk-react'
828
+ import { useState } from 'react'
829
+
830
+ function AdminDashboard() {
831
+ const { user, isAuthenticated, logout } = useAdminAuth({ autoCheck: true })
832
+ const { users, total: totalUsers } = useUsers({ autoFetch: true, limit: 5 })
833
+ const { keys } = useAPIKeys({ autoFetch: true })
834
+ const { webhooks } = useWebhooks({ autoFetch: true })
835
+ const { settings } = useAppSettings({ autoFetch: true })
836
+
837
+ const [activeTab, setActiveTab] = useState<'overview' | 'users' | 'keys' | 'webhooks' | 'settings'>('overview')
838
+
839
+ if (!isAuthenticated) {
840
+ return <div>Please log in to access admin dashboard</div>
841
+ }
842
+
843
+ return (
844
+ <div className="admin-dashboard">
845
+ <header>
846
+ <h1>Admin Dashboard</h1>
847
+ <div className="user-info">
848
+ <span>{user?.email}</span>
849
+ <button onClick={logout}>Logout</button>
850
+ </div>
851
+ </header>
852
+
853
+ <nav>
854
+ <button onClick={() => setActiveTab('overview')}>Overview</button>
855
+ <button onClick={() => setActiveTab('users')}>Users</button>
856
+ <button onClick={() => setActiveTab('keys')}>API Keys</button>
857
+ <button onClick={() => setActiveTab('webhooks')}>Webhooks</button>
858
+ <button onClick={() => setActiveTab('settings')}>Settings</button>
859
+ </nav>
860
+
861
+ <main>
862
+ {activeTab === 'overview' && (
863
+ <div className="overview">
864
+ <h2>Overview</h2>
865
+ <div className="stats">
866
+ <div className="stat-card">
867
+ <h3>Total Users</h3>
868
+ <p className="stat-value">{totalUsers}</p>
869
+ </div>
870
+ <div className="stat-card">
871
+ <h3>API Keys</h3>
872
+ <p className="stat-value">{keys.length}</p>
873
+ </div>
874
+ <div className="stat-card">
875
+ <h3>Webhooks</h3>
876
+ <p className="stat-value">{webhooks.length}</p>
877
+ </div>
878
+ <div className="stat-card">
879
+ <h3>Realtime</h3>
880
+ <p className="stat-value">
881
+ {settings?.features?.enable_realtime ? 'Enabled' : 'Disabled'}
882
+ </p>
883
+ </div>
884
+ </div>
885
+
886
+ <div className="recent-users">
887
+ <h3>Recent Users</h3>
888
+ <ul>
889
+ {users.slice(0, 5).map((u) => (
890
+ <li key={u.id}>
891
+ {u.email} - {u.role}
892
+ </li>
893
+ ))}
894
+ </ul>
895
+ </div>
896
+ </div>
897
+ )}
898
+
899
+ {activeTab === 'users' && <UserManagement />}
900
+ {activeTab === 'keys' && <APIKeyManagement />}
901
+ {activeTab === 'webhooks' && <WebhookManagement />}
902
+ {activeTab === 'settings' && <AppSettingsPanel />}
903
+ </main>
904
+ </div>
905
+ )
906
+ }
907
+ ```
908
+
909
+ ### Multi-Tab Admin Interface
910
+
911
+ ```tsx
912
+ import { Tabs, TabList, Tab, TabPanels, TabPanel } from '@reach/tabs'
913
+ import {
914
+ useUsers,
915
+ useAPIKeys,
916
+ useWebhooks,
917
+ useAppSettings,
918
+ useSystemSettings
919
+ } from '@fluxbase/sdk-react'
920
+
921
+ function AdminTabs() {
922
+ return (
923
+ <Tabs>
924
+ <TabList>
925
+ <Tab>Users</Tab>
926
+ <Tab>API Keys</Tab>
927
+ <Tab>Webhooks</Tab>
928
+ <Tab>App Settings</Tab>
929
+ <Tab>System Settings</Tab>
930
+ </TabList>
931
+
932
+ <TabPanels>
933
+ <TabPanel>
934
+ <UserManagement />
935
+ </TabPanel>
936
+ <TabPanel>
937
+ <APIKeyManagement />
938
+ </TabPanel>
939
+ <TabPanel>
940
+ <WebhookManagement />
941
+ </TabPanel>
942
+ <TabPanel>
943
+ <AppSettingsPanel />
944
+ </TabPanel>
945
+ <TabPanel>
946
+ <SystemSettingsPanel />
947
+ </TabPanel>
948
+ </TabPanels>
949
+ </Tabs>
950
+ )
951
+ }
952
+ ```
953
+
954
+ ### Real-time Updates
955
+
956
+ All hooks support automatic refetching with the `refetchInterval` option:
957
+
958
+ ```tsx
959
+ function LiveUserList() {
960
+ const { users, total } = useUsers({
961
+ autoFetch: true,
962
+ refetchInterval: 5000 // Refetch every 5 seconds
963
+ })
964
+
965
+ return (
966
+ <div>
967
+ <h2>Live Users ({total})</h2>
968
+ <ul>
969
+ {users.map((user) => (
970
+ <li key={user.id}>{user.email}</li>
971
+ ))}
972
+ </ul>
973
+ <small>Updates every 5 seconds</small>
974
+ </div>
975
+ )
976
+ }
977
+ ```
978
+
979
+ ## Best Practices
980
+
981
+ ### Error Handling
982
+
983
+ ```tsx
984
+ function RobustComponent() {
985
+ const { users, error, isLoading, refetch } = useUsers({ autoFetch: true })
986
+
987
+ if (isLoading) {
988
+ return <LoadingSpinner />
989
+ }
990
+
991
+ if (error) {
992
+ return (
993
+ <ErrorState
994
+ message={error.message}
995
+ onRetry={refetch}
996
+ />
997
+ )
998
+ }
999
+
1000
+ return <UserList users={users} />
1001
+ }
1002
+ ```
1003
+
1004
+ ### Optimistic Updates
1005
+
1006
+ All mutation functions automatically refetch data after successful operations:
1007
+
1008
+ ```tsx
1009
+ const { users, inviteUser } = useUsers({ autoFetch: true })
1010
+
1011
+ // This will automatically refetch the user list after inviting
1012
+ await inviteUser('new@example.com', 'user')
1013
+ // users state is now updated with the new user
1014
+ ```
1015
+
1016
+ ### Manual Refetch
1017
+
1018
+ ```tsx
1019
+ const { users, refetch } = useUsers({ autoFetch: false })
1020
+
1021
+ // Manually fetch when needed
1022
+ useEffect(() => {
1023
+ refetch()
1024
+ }, [someCondition])
1025
+ ```
1026
+
1027
+ ### Performance Optimization
1028
+
1029
+ ```tsx
1030
+ // Don't auto-fetch on mount if data isn't immediately needed
1031
+ const { users, refetch } = useUsers({ autoFetch: false })
1032
+
1033
+ // Fetch only when tab is active
1034
+ useEffect(() => {
1035
+ if (isTabActive) {
1036
+ refetch()
1037
+ }
1038
+ }, [isTabActive, refetch])
1039
+ ```
1040
+
1041
+ ## TypeScript Support
1042
+
1043
+ All hooks are fully typed with comprehensive TypeScript interfaces:
1044
+
1045
+ ```tsx
1046
+ import type { EnrichedUser, APIKey, Webhook } from '@fluxbase/sdk-react'
1047
+
1048
+ function TypedComponent() {
1049
+ const { users }: { users: EnrichedUser[] } = useUsers({ autoFetch: true })
1050
+ const { keys }: { keys: APIKey[] } = useAPIKeys({ autoFetch: true })
1051
+ const { webhooks }: { webhooks: Webhook[] } = useWebhooks({ autoFetch: true })
1052
+
1053
+ // Full type safety
1054
+ }
1055
+ ```
1056
+
1057
+ ## API Reference
1058
+
1059
+ ### Common Hook Options
1060
+
1061
+ All hooks support these common options:
1062
+
1063
+ - `autoFetch?: boolean` - Automatically fetch data on component mount (default: `true`)
1064
+ - `refetchInterval?: number` - Automatically refetch data every N milliseconds (default: `0` - disabled)
1065
+
1066
+ ### Common Hook Returns
1067
+
1068
+ All hooks return these common fields:
1069
+
1070
+ - `isLoading: boolean` - Whether data is currently being fetched
1071
+ - `error: Error | null` - Any error that occurred during fetch/mutation
1072
+ - `refetch: () => Promise<void>` - Manually trigger a data refetch
1073
+
1074
+ ## License
1075
+
1076
+ MIT