@nimbleflux/fluxbase-sdk-react 2026.3.6-rc.1

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.
Files changed (42) hide show
  1. package/.nvmrc +1 -0
  2. package/README-ADMIN.md +1076 -0
  3. package/README.md +195 -0
  4. package/examples/AdminDashboard.tsx +513 -0
  5. package/examples/README.md +163 -0
  6. package/package.json +66 -0
  7. package/src/context.test.tsx +147 -0
  8. package/src/context.tsx +33 -0
  9. package/src/index.test.ts +255 -0
  10. package/src/index.ts +175 -0
  11. package/src/test-setup.ts +22 -0
  12. package/src/test-utils.tsx +215 -0
  13. package/src/use-admin-auth.test.ts +175 -0
  14. package/src/use-admin-auth.ts +187 -0
  15. package/src/use-admin-hooks.test.ts +457 -0
  16. package/src/use-admin-hooks.ts +309 -0
  17. package/src/use-auth-config.test.ts +145 -0
  18. package/src/use-auth-config.ts +101 -0
  19. package/src/use-auth.test.ts +313 -0
  20. package/src/use-auth.ts +164 -0
  21. package/src/use-captcha.test.ts +273 -0
  22. package/src/use-captcha.ts +250 -0
  23. package/src/use-client-keys.test.ts +286 -0
  24. package/src/use-client-keys.ts +185 -0
  25. package/src/use-graphql.test.ts +424 -0
  26. package/src/use-graphql.ts +392 -0
  27. package/src/use-query.test.ts +348 -0
  28. package/src/use-query.ts +211 -0
  29. package/src/use-realtime.test.ts +359 -0
  30. package/src/use-realtime.ts +180 -0
  31. package/src/use-saml.test.ts +269 -0
  32. package/src/use-saml.ts +221 -0
  33. package/src/use-storage.test.ts +549 -0
  34. package/src/use-storage.ts +508 -0
  35. package/src/use-table-export.ts +481 -0
  36. package/src/use-users.test.ts +264 -0
  37. package/src/use-users.ts +198 -0
  38. package/tsconfig.json +28 -0
  39. package/tsconfig.tsbuildinfo +1 -0
  40. package/tsup.config.ts +11 -0
  41. package/typedoc.json +33 -0
  42. package/vitest.config.ts +22 -0
@@ -0,0 +1,513 @@
1
+ /**
2
+ * Complete Admin Dashboard Example
3
+ *
4
+ * This example demonstrates all admin hooks in a real-world dashboard interface.
5
+ *
6
+ * Features:
7
+ * - Admin authentication with protected routes
8
+ * - User management with pagination
9
+ * - API key management
10
+ * - Webhook configuration
11
+ * - App and system settings
12
+ * - Real-time statistics
13
+ */
14
+
15
+ import React, { useState } from 'react'
16
+ import {
17
+ useAdminAuth,
18
+ useUsers,
19
+ useAPIKeys,
20
+ useWebhooks,
21
+ useAppSettings,
22
+ useSystemSettings
23
+ } from '@nimbleflux/fluxbase-sdk-react'
24
+ import type { EnrichedUser } from '@nimbleflux/fluxbase-sdk'
25
+
26
+ // ============================================================================
27
+ // Admin Login Component
28
+ // ============================================================================
29
+
30
+ function AdminLogin() {
31
+ const { isAuthenticated, isLoading, error, login } = useAdminAuth({ autoCheck: true })
32
+ const [email, setEmail] = useState('')
33
+ const [password, setPassword] = useState('')
34
+ const [loginError, setLoginError] = useState<string | null>(null)
35
+
36
+ const handleSubmit = async (e: React.FormEvent) => {
37
+ e.preventDefault()
38
+ setLoginError(null)
39
+
40
+ try {
41
+ await login(email, password)
42
+ } catch (err) {
43
+ setLoginError((err as Error).message)
44
+ }
45
+ }
46
+
47
+ if (isLoading) {
48
+ return (
49
+ <div className="flex items-center justify-center min-h-screen">
50
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
51
+ </div>
52
+ )
53
+ }
54
+
55
+ if (isAuthenticated) {
56
+ return null // Will be redirected by parent
57
+ }
58
+
59
+ return (
60
+ <div className="min-h-screen flex items-center justify-center bg-gray-50">
61
+ <div className="max-w-md w-full space-y-8 p-8 bg-white rounded-lg shadow">
62
+ <div>
63
+ <h2 className="text-center text-3xl font-extrabold text-gray-900">
64
+ Admin Login
65
+ </h2>
66
+ </div>
67
+ <form className="mt-8 space-y-6" onSubmit={handleSubmit}>
68
+ <div className="rounded-md shadow-sm -space-y-px">
69
+ <div>
70
+ <input
71
+ type="email"
72
+ required
73
+ className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
74
+ placeholder="Email address"
75
+ value={email}
76
+ onChange={(e) => setEmail(e.target.value)}
77
+ />
78
+ </div>
79
+ <div>
80
+ <input
81
+ type="password"
82
+ required
83
+ className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm"
84
+ placeholder="Password"
85
+ value={password}
86
+ onChange={(e) => setPassword(e.target.value)}
87
+ />
88
+ </div>
89
+ </div>
90
+
91
+ {(loginError || error) && (
92
+ <div className="text-red-600 text-sm">
93
+ {loginError || error?.message}
94
+ </div>
95
+ )}
96
+
97
+ <div>
98
+ <button
99
+ type="submit"
100
+ className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
101
+ >
102
+ Sign in
103
+ </button>
104
+ </div>
105
+ </form>
106
+ </div>
107
+ </div>
108
+ )
109
+ }
110
+
111
+ // ============================================================================
112
+ // Statistics Overview
113
+ // ============================================================================
114
+
115
+ function Overview() {
116
+ const { users, total: totalUsers, isLoading: loadingUsers } = useUsers({
117
+ autoFetch: true,
118
+ limit: 5
119
+ })
120
+ const { keys, isLoading: loadingKeys } = useAPIKeys({ autoFetch: true })
121
+ const { webhooks, isLoading: loadingWebhooks } = useWebhooks({ autoFetch: true })
122
+ const { settings, isLoading: loadingSettings } = useAppSettings({ autoFetch: true })
123
+
124
+ const isLoading = loadingUsers || loadingKeys || loadingWebhooks || loadingSettings
125
+
126
+ if (isLoading) {
127
+ return <div className="text-center py-8">Loading overview...</div>
128
+ }
129
+
130
+ const enabledWebhooks = webhooks.filter(w => w.enabled).length
131
+ const activeKeys = keys.filter(k => !k.expires_at || new Date(k.expires_at) > new Date()).length
132
+
133
+ return (
134
+ <div className="space-y-6">
135
+ <h2 className="text-2xl font-bold">Dashboard Overview</h2>
136
+
137
+ {/* Statistics Cards */}
138
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
139
+ <div className="bg-white rounded-lg shadow p-6">
140
+ <div className="text-sm font-medium text-gray-500">Total Users</div>
141
+ <div className="mt-2 text-3xl font-semibold text-gray-900">{totalUsers}</div>
142
+ <div className="mt-2 text-xs text-gray-500">Registered accounts</div>
143
+ </div>
144
+
145
+ <div className="bg-white rounded-lg shadow p-6">
146
+ <div className="text-sm font-medium text-gray-500">client keys</div>
147
+ <div className="mt-2 text-3xl font-semibold text-gray-900">{activeKeys}</div>
148
+ <div className="mt-2 text-xs text-gray-500">{keys.length - activeKeys} expired</div>
149
+ </div>
150
+
151
+ <div className="bg-white rounded-lg shadow p-6">
152
+ <div className="text-sm font-medium text-gray-500">Webhooks</div>
153
+ <div className="mt-2 text-3xl font-semibold text-gray-900">{enabledWebhooks}</div>
154
+ <div className="mt-2 text-xs text-gray-500">{webhooks.length - enabledWebhooks} disabled</div>
155
+ </div>
156
+
157
+ <div className="bg-white rounded-lg shadow p-6">
158
+ <div className="text-sm font-medium text-gray-500">Features</div>
159
+ <div className="mt-2 space-y-1">
160
+ <div className="flex items-center text-sm">
161
+ <span className={`w-2 h-2 rounded-full mr-2 ${settings?.features?.enable_realtime ? 'bg-green-500' : 'bg-gray-300'}`}></span>
162
+ Realtime
163
+ </div>
164
+ <div className="flex items-center text-sm">
165
+ <span className={`w-2 h-2 rounded-full mr-2 ${settings?.features?.enable_storage ? 'bg-green-500' : 'bg-gray-300'}`}></span>
166
+ Storage
167
+ </div>
168
+ <div className="flex items-center text-sm">
169
+ <span className={`w-2 h-2 rounded-full mr-2 ${settings?.features?.enable_functions ? 'bg-green-500' : 'bg-gray-300'}`}></span>
170
+ Functions
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ {/* Recent Users */}
177
+ <div className="bg-white rounded-lg shadow">
178
+ <div className="p-6">
179
+ <h3 className="text-lg font-medium mb-4">Recent Users</h3>
180
+ <div className="space-y-3">
181
+ {users.slice(0, 5).map((user) => (
182
+ <div key={user.id} className="flex items-center justify-between">
183
+ <div>
184
+ <div className="font-medium">{user.email}</div>
185
+ <div className="text-sm text-gray-500">
186
+ {new Date(user.created_at).toLocaleDateString()}
187
+ </div>
188
+ </div>
189
+ <div className="flex items-center space-x-2">
190
+ <span className={`px-2 py-1 text-xs rounded ${
191
+ user.role === 'admin' ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-800'
192
+ }`}>
193
+ {user.role}
194
+ </span>
195
+ <span className={`px-2 py-1 text-xs rounded ${
196
+ user.email_confirmed ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
197
+ }`}>
198
+ {user.email_confirmed ? 'Verified' : 'Pending'}
199
+ </span>
200
+ </div>
201
+ </div>
202
+ ))}
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ )
208
+ }
209
+
210
+ // ============================================================================
211
+ // User Management
212
+ // ============================================================================
213
+
214
+ function UserManagement() {
215
+ const [page, setPage] = useState(0)
216
+ const [searchEmail, setSearchEmail] = useState('')
217
+ const [roleFilter, setRoleFilter] = useState<'admin' | 'user' | ''>('')
218
+ const limit = 20
219
+
220
+ const {
221
+ users,
222
+ total,
223
+ isLoading,
224
+ error,
225
+ refetch,
226
+ inviteUser,
227
+ updateUserRole,
228
+ deleteUser,
229
+ resetPassword
230
+ } = useUsers({
231
+ autoFetch: true,
232
+ limit,
233
+ offset: page * limit,
234
+ email: searchEmail || undefined,
235
+ role: roleFilter || undefined
236
+ })
237
+
238
+ const handleInvite = async () => {
239
+ const email = prompt('Enter user email:')
240
+ if (!email) return
241
+
242
+ const isAdmin = confirm('Grant admin privileges?')
243
+ try {
244
+ await inviteUser(email, isAdmin ? 'admin' : 'user')
245
+ alert('User invited successfully!')
246
+ } catch (err) {
247
+ alert('Failed to invite user: ' + (err as Error).message)
248
+ }
249
+ }
250
+
251
+ const handleRoleToggle = async (userId: string, currentRole: string) => {
252
+ const newRole = currentRole === 'admin' ? 'user' : 'admin'
253
+ if (confirm(`Change role to ${newRole}?`)) {
254
+ await updateUserRole(userId, newRole)
255
+ }
256
+ }
257
+
258
+ const handleDelete = async (userId: string, email: string) => {
259
+ if (confirm(`Delete user ${email}? This cannot be undone.`)) {
260
+ await deleteUser(userId)
261
+ }
262
+ }
263
+
264
+ const handleResetPassword = async (userId: string) => {
265
+ try {
266
+ const newPassword = await resetPassword(userId)
267
+ alert(`New password: ${newPassword}\n\nMake sure to save this - it won't be shown again!`)
268
+ } catch (err) {
269
+ alert('Failed to reset password: ' + (err as Error).message)
270
+ }
271
+ }
272
+
273
+ if (error) {
274
+ return <div className="text-red-600">Error: {error.message}</div>
275
+ }
276
+
277
+ return (
278
+ <div className="space-y-6">
279
+ <div className="flex justify-between items-center">
280
+ <h2 className="text-2xl font-bold">User Management</h2>
281
+ <button
282
+ onClick={handleInvite}
283
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
284
+ >
285
+ Invite User
286
+ </button>
287
+ </div>
288
+
289
+ {/* Filters */}
290
+ <div className="bg-white rounded-lg shadow p-4">
291
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
292
+ <input
293
+ type="text"
294
+ placeholder="Search by email..."
295
+ value={searchEmail}
296
+ onChange={(e) => {
297
+ setSearchEmail(e.target.value)
298
+ setPage(0)
299
+ }}
300
+ className="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
301
+ />
302
+ <select
303
+ value={roleFilter}
304
+ onChange={(e) => {
305
+ setRoleFilter(e.target.value as any)
306
+ setPage(0)
307
+ }}
308
+ className="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
309
+ >
310
+ <option value="">All Roles</option>
311
+ <option value="admin">Admin</option>
312
+ <option value="user">User</option>
313
+ </select>
314
+ <button
315
+ onClick={refetch}
316
+ className="px-4 py-2 border border-gray-300 rounded hover:bg-gray-50"
317
+ >
318
+ Refresh
319
+ </button>
320
+ </div>
321
+ </div>
322
+
323
+ {/* Users Table */}
324
+ <div className="bg-white rounded-lg shadow overflow-hidden">
325
+ {isLoading ? (
326
+ <div className="text-center py-8">Loading users...</div>
327
+ ) : (
328
+ <>
329
+ <table className="min-w-full divide-y divide-gray-200">
330
+ <thead className="bg-gray-50">
331
+ <tr>
332
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
333
+ User
334
+ </th>
335
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
336
+ Role
337
+ </th>
338
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
339
+ Status
340
+ </th>
341
+ <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
342
+ Created
343
+ </th>
344
+ <th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
345
+ Actions
346
+ </th>
347
+ </tr>
348
+ </thead>
349
+ <tbody className="bg-white divide-y divide-gray-200">
350
+ {users.map((user) => (
351
+ <tr key={user.id}>
352
+ <td className="px-6 py-4 whitespace-nowrap">
353
+ <div className="text-sm font-medium text-gray-900">{user.email}</div>
354
+ <div className="text-xs text-gray-500">{user.id.substring(0, 8)}</div>
355
+ </td>
356
+ <td className="px-6 py-4 whitespace-nowrap">
357
+ <span className={`px-2 py-1 text-xs rounded ${
358
+ user.role === 'admin' ? 'bg-purple-100 text-purple-800' : 'bg-gray-100 text-gray-800'
359
+ }`}>
360
+ {user.role}
361
+ </span>
362
+ </td>
363
+ <td className="px-6 py-4 whitespace-nowrap">
364
+ <span className={`px-2 py-1 text-xs rounded ${
365
+ user.email_confirmed ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'
366
+ }`}>
367
+ {user.email_confirmed ? 'Verified' : 'Pending'}
368
+ </span>
369
+ </td>
370
+ <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
371
+ {new Date(user.created_at).toLocaleDateString()}
372
+ </td>
373
+ <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium space-x-2">
374
+ <button
375
+ onClick={() => handleRoleToggle(user.id, user.role)}
376
+ className="text-blue-600 hover:text-blue-900"
377
+ >
378
+ Toggle Role
379
+ </button>
380
+ <button
381
+ onClick={() => handleResetPassword(user.id)}
382
+ className="text-yellow-600 hover:text-yellow-900"
383
+ >
384
+ Reset PW
385
+ </button>
386
+ <button
387
+ onClick={() => handleDelete(user.id, user.email)}
388
+ className="text-red-600 hover:text-red-900"
389
+ >
390
+ Delete
391
+ </button>
392
+ </td>
393
+ </tr>
394
+ ))}
395
+ </tbody>
396
+ </table>
397
+
398
+ {/* Pagination */}
399
+ <div className="bg-gray-50 px-6 py-3 flex items-center justify-between">
400
+ <div className="text-sm text-gray-700">
401
+ Showing {page * limit + 1} to {Math.min((page + 1) * limit, total)} of {total} users
402
+ </div>
403
+ <div className="space-x-2">
404
+ <button
405
+ onClick={() => setPage(page - 1)}
406
+ disabled={page === 0}
407
+ className="px-3 py-1 border border-gray-300 rounded disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100"
408
+ >
409
+ Previous
410
+ </button>
411
+ <button
412
+ onClick={() => setPage(page + 1)}
413
+ disabled={(page + 1) * limit >= total}
414
+ className="px-3 py-1 border border-gray-300 rounded disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100"
415
+ >
416
+ Next
417
+ </button>
418
+ </div>
419
+ </div>
420
+ </>
421
+ )}
422
+ </div>
423
+ </div>
424
+ )
425
+ }
426
+
427
+ // ============================================================================
428
+ // Main Dashboard
429
+ // ============================================================================
430
+
431
+ type TabType = 'overview' | 'users' | 'keys' | 'webhooks' | 'settings'
432
+
433
+ export default function AdminDashboard() {
434
+ const { user, isAuthenticated, isLoading, logout } = useAdminAuth({ autoCheck: true })
435
+ const [activeTab, setActiveTab] = useState<TabType>('overview')
436
+
437
+ if (isLoading) {
438
+ return (
439
+ <div className="flex items-center justify-center min-h-screen">
440
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
441
+ </div>
442
+ )
443
+ }
444
+
445
+ if (!isAuthenticated) {
446
+ return <AdminLogin />
447
+ }
448
+
449
+ const tabs: { id: TabType; label: string }[] = [
450
+ { id: 'overview', label: 'Overview' },
451
+ { id: 'users', label: 'Users' },
452
+ { id: 'keys', label: 'client keys' },
453
+ { id: 'webhooks', label: 'Webhooks' },
454
+ { id: 'settings', label: 'Settings' }
455
+ ]
456
+
457
+ return (
458
+ <div className="min-h-screen bg-gray-100">
459
+ {/* Header */}
460
+ <header className="bg-white shadow">
461
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
462
+ <div className="flex justify-between items-center">
463
+ <h1 className="text-2xl font-bold text-gray-900">Admin Dashboard</h1>
464
+ <div className="flex items-center space-x-4">
465
+ <div className="text-sm text-gray-700">
466
+ <span className="font-medium">{user?.email}</span>
467
+ <span className="ml-2 px-2 py-1 text-xs rounded bg-purple-100 text-purple-800">
468
+ {user?.role}
469
+ </span>
470
+ </div>
471
+ <button
472
+ onClick={logout}
473
+ className="px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-900"
474
+ >
475
+ Logout
476
+ </button>
477
+ </div>
478
+ </div>
479
+ </div>
480
+ </header>
481
+
482
+ {/* Navigation Tabs */}
483
+ <nav className="bg-white shadow-sm">
484
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
485
+ <div className="flex space-x-8">
486
+ {tabs.map((tab) => (
487
+ <button
488
+ key={tab.id}
489
+ onClick={() => setActiveTab(tab.id)}
490
+ className={`py-4 px-1 border-b-2 font-medium text-sm ${
491
+ activeTab === tab.id
492
+ ? 'border-blue-500 text-blue-600'
493
+ : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
494
+ }`}
495
+ >
496
+ {tab.label}
497
+ </button>
498
+ ))}
499
+ </div>
500
+ </div>
501
+ </nav>
502
+
503
+ {/* Main Content */}
504
+ <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
505
+ {activeTab === 'overview' && <Overview />}
506
+ {activeTab === 'users' && <UserManagement />}
507
+ {activeTab === 'keys' && <div className="text-center py-8">client keys management (implement using useAPIKeys)</div>}
508
+ {activeTab === 'webhooks' && <div className="text-center py-8">Webhooks management (implement using useWebhooks)</div>}
509
+ {activeTab === 'settings' && <div className="text-center py-8">Settings management (implement using useAppSettings and useSystemSettings)</div>}
510
+ </main>
511
+ </div>
512
+ )
513
+ }
@@ -0,0 +1,163 @@
1
+ # React Admin Hooks Examples
2
+
3
+ This directory contains complete, production-ready examples demonstrating the Fluxbase React admin hooks.
4
+
5
+ ## Examples
6
+
7
+ ### AdminDashboard.tsx
8
+
9
+ A complete admin dashboard application demonstrating all admin hooks in action.
10
+
11
+ **Features:**
12
+ - Admin authentication with protected routes
13
+ - User management with pagination and search
14
+ - Real-time statistics dashboard
15
+ - Modern UI with Tailwind CSS
16
+ - Full TypeScript support
17
+
18
+ **Hooks demonstrated:**
19
+ - `useAdminAuth()` - Authentication state management
20
+ - `useUsers()` - User CRUD operations with pagination
21
+ - `useAPIKeys()` - API key management
22
+ - `useWebhooks()` - Webhook configuration
23
+ - `useAppSettings()` - Application settings
24
+ - `useSystemSettings()` - System settings
25
+
26
+ **Run the example:**
27
+
28
+ ```bash
29
+ # Install dependencies
30
+ npm install @fluxbase/sdk @fluxbase/sdk-react @tanstack/react-query
31
+
32
+ # Copy AdminDashboard.tsx to your project
33
+ # Add Tailwind CSS to your project (optional, for styling)
34
+ ```
35
+
36
+ **Basic usage:**
37
+
38
+ ```tsx
39
+ import { createClient } from '@fluxbase/sdk'
40
+ import { FluxbaseProvider } from '@fluxbase/sdk-react'
41
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
42
+ import AdminDashboard from './AdminDashboard'
43
+
44
+ const client = createClient({ url: 'http://localhost:8080' })
45
+ const queryClient = new QueryClient()
46
+
47
+ function App() {
48
+ return (
49
+ <QueryClientProvider client={queryClient}>
50
+ <FluxbaseProvider client={client}>
51
+ <AdminDashboard />
52
+ </FluxbaseProvider>
53
+ </QueryClientProvider>
54
+ )
55
+ }
56
+ ```
57
+
58
+ ## Key Patterns
59
+
60
+ ### 1. Authentication Flow
61
+
62
+ ```tsx
63
+ const { isAuthenticated, isLoading, login, logout } = useAdminAuth({
64
+ autoCheck: true // Automatically check auth status on mount
65
+ })
66
+
67
+ if (isLoading) return <LoadingSpinner />
68
+ if (!isAuthenticated) return <LoginPage />
69
+ return <Dashboard />
70
+ ```
71
+
72
+ ### 2. Data Fetching with Pagination
73
+
74
+ ```tsx
75
+ const [page, setPage] = useState(0)
76
+ const limit = 20
77
+
78
+ const { users, total, isLoading } = useUsers({
79
+ autoFetch: true,
80
+ limit,
81
+ offset: page * limit
82
+ })
83
+ ```
84
+
85
+ ### 3. Search and Filters
86
+
87
+ ```tsx
88
+ const [searchEmail, setSearchEmail] = useState('')
89
+ const [roleFilter, setRoleFilter] = useState<'admin' | 'user' | ''>('')
90
+
91
+ const { users, refetch } = useUsers({
92
+ autoFetch: true,
93
+ email: searchEmail || undefined,
94
+ role: roleFilter || undefined
95
+ })
96
+
97
+ // Refetch when filters change
98
+ useEffect(() => {
99
+ refetch()
100
+ }, [searchEmail, roleFilter, refetch])
101
+ ```
102
+
103
+ ### 4. Optimistic Updates
104
+
105
+ All mutation functions automatically refetch data:
106
+
107
+ ```tsx
108
+ const { users, inviteUser, deleteUser } = useUsers({ autoFetch: true })
109
+
110
+ // These automatically refetch the user list after success
111
+ await inviteUser('new@example.com', 'user')
112
+ await deleteUser(userId)
113
+ // users state is now updated
114
+ ```
115
+
116
+ ### 5. Error Handling
117
+
118
+ ```tsx
119
+ const { data, error, isLoading, refetch } = useUsers({ autoFetch: true })
120
+
121
+ if (isLoading) return <LoadingSpinner />
122
+ if (error) return <ErrorMessage error={error} onRetry={refetch} />
123
+ return <DataDisplay data={data} />
124
+ ```
125
+
126
+ ## Styling
127
+
128
+ The example uses Tailwind CSS utility classes, but you can easily adapt it to your preferred styling solution:
129
+
130
+ - **CSS Modules**: Replace `className` with module imports
131
+ - **Styled Components**: Replace elements with styled components
132
+ - **Material-UI**: Use MUI components instead of native HTML
133
+ - **Chakra UI**: Use Chakra components for rapid development
134
+
135
+ ## TypeScript
136
+
137
+ All examples are fully typed. The hooks provide complete type safety:
138
+
139
+ ```tsx
140
+ import type { EnrichedUser, APIKey, Webhook } from '@fluxbase/sdk-react'
141
+
142
+ const { users }: { users: EnrichedUser[] } = useUsers({ autoFetch: true })
143
+ const { keys }: { keys: APIKey[] } = useAPIKeys({ autoFetch: true })
144
+ ```
145
+
146
+ ## Next Steps
147
+
148
+ 1. **Customize the UI** - Adapt the styling to match your design system
149
+ 2. **Add more features** - Implement client keys, webhooks, and settings tabs
150
+ 3. **Add validation** - Add form validation for user inputs
151
+ 4. **Add loading states** - Improve loading and error states
152
+ 5. **Add notifications** - Show success/error toasts for mutations
153
+ 6. **Add search** - Enhance search with debouncing and advanced filters
154
+
155
+ ## Documentation
156
+
157
+ - [Complete Admin Hooks Guide](../README-ADMIN.md) - Full API reference and examples
158
+ - [React Hooks Documentation](../../docs/docs/sdks/react-hooks.md) - Core hooks documentation
159
+ - [Admin API Documentation](../../docs/docs/sdk/admin.md) - Admin operations reference
160
+
161
+ ## License
162
+
163
+ MIT