better-auth-studio 1.0.5 → 1.0.7

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 (44) hide show
  1. package/package.json +8 -1
  2. package/frontend/index.html +0 -13
  3. package/frontend/package-lock.json +0 -4675
  4. package/frontend/package.json +0 -52
  5. package/frontend/pnpm-lock.yaml +0 -4020
  6. package/frontend/postcss.config.js +0 -6
  7. package/frontend/src/App.tsx +0 -36
  8. package/frontend/src/components/CommandPalette.tsx +0 -219
  9. package/frontend/src/components/Layout.tsx +0 -159
  10. package/frontend/src/components/ui/badge.tsx +0 -40
  11. package/frontend/src/components/ui/button.tsx +0 -53
  12. package/frontend/src/components/ui/card.tsx +0 -78
  13. package/frontend/src/components/ui/input.tsx +0 -20
  14. package/frontend/src/components/ui/label.tsx +0 -19
  15. package/frontend/src/components/ui/select.tsx +0 -71
  16. package/frontend/src/index.css +0 -130
  17. package/frontend/src/lib/utils.ts +0 -6
  18. package/frontend/src/main.tsx +0 -10
  19. package/frontend/src/pages/Dashboard.tsx +0 -231
  20. package/frontend/src/pages/OrganizationDetails.tsx +0 -1281
  21. package/frontend/src/pages/Organizations.tsx +0 -874
  22. package/frontend/src/pages/Sessions.tsx +0 -623
  23. package/frontend/src/pages/Settings.tsx +0 -1019
  24. package/frontend/src/pages/TeamDetails.tsx +0 -666
  25. package/frontend/src/pages/Users.tsx +0 -728
  26. package/frontend/tailwind.config.js +0 -75
  27. package/frontend/tsconfig.json +0 -31
  28. package/frontend/tsconfig.node.json +0 -10
  29. package/frontend/vite.config.ts +0 -31
  30. package/src/auth-adapter.ts +0 -473
  31. package/src/cli.ts +0 -51
  32. package/src/config.ts +0 -320
  33. package/src/data.ts +0 -351
  34. package/src/routes.ts +0 -1585
  35. package/src/studio.ts +0 -86
  36. package/test-project/README.md +0 -0
  37. package/test-project/better-auth.db +0 -0
  38. package/test-project/better-auth_migrations/2025-08-27T15-55-04.099Z.sql +0 -7
  39. package/test-project/better-auth_migrations/2025-09-04T02-33-19.422Z.sql +0 -7
  40. package/test-project/package.json +0 -29
  41. package/test-project/pnpm-lock.yaml +0 -1728
  42. package/test-project/src/auth.ts +0 -47
  43. package/test-project/src/index.ts +0 -40
  44. package/tsconfig.json +0 -21
@@ -1,728 +0,0 @@
1
- import { useState, useEffect } from 'react'
2
- import { toast } from 'sonner'
3
- import {
4
- Users as UsersIcon,
5
- Search,
6
- Filter,
7
- Edit,
8
- Trash2,
9
- UserPlus,
10
- Mail,
11
- Eye,
12
- X,
13
- Database,
14
- Check
15
- } from 'lucide-react'
16
- import { Button } from '../components/ui/button'
17
- import { Input } from '../components/ui/input'
18
- import { Label } from '../components/ui/label'
19
- import { Select, SelectItem } from '../components/ui/select'
20
-
21
- interface User {
22
- id: string
23
- name: string
24
- email: string
25
- emailVerified: boolean
26
- image?: string
27
- createdAt: string
28
- updatedAt: string
29
- }
30
-
31
- export default function Users() {
32
- const [users, setUsers] = useState<User[]>([])
33
- const [loading, setLoading] = useState(true)
34
- const [searchTerm, setSearchTerm] = useState('')
35
- const [filter, setFilter] = useState('all')
36
- const [currentPage, setCurrentPage] = useState(1)
37
- const [usersPerPage] = useState(50)
38
- const [showCreateModal, setShowCreateModal] = useState(false)
39
- const [showEditModal, setShowEditModal] = useState(false)
40
- const [showDeleteModal, setShowDeleteModal] = useState(false)
41
- const [showViewModal, setShowViewModal] = useState(false)
42
- const [showSeedModal, setShowSeedModal] = useState(false)
43
- const [selectedUser, setSelectedUser] = useState<User | null>(null)
44
- const [seedingLogs, setSeedingLogs] = useState<string[]>([])
45
-
46
- useEffect(() => {
47
- fetchUsers()
48
- }, [])
49
-
50
- const fetchUsers = async () => {
51
- try {
52
- // Request all users by setting a high limit to ensure we get all 1,012 users
53
- const response = await fetch('/api/users?limit=10000')
54
- const data = await response.json()
55
- setUsers(data.users || [])
56
- } catch (error) {
57
- console.error('Failed to fetch users:', error)
58
- } finally {
59
- setLoading(false)
60
- }
61
- }
62
-
63
- const handleSeedUsers = async (count: number) => {
64
- setSeedingLogs([])
65
-
66
- try {
67
- const response = await fetch('/api/seed/users', {
68
- method: 'POST',
69
- headers: { 'Content-Type': 'application/json' },
70
- body: JSON.stringify({ count })
71
- })
72
-
73
- const result = await response.json()
74
-
75
- if (result.success) {
76
- setSeedingLogs(result.results.map((r: any) =>
77
- `✅ Created user: ${r.user.name} (${r.user.email})`
78
- ))
79
- // Refresh the users list to show updated count
80
- await fetchUsers()
81
- } else {
82
- setSeedingLogs([`❌ Error: ${result.error || 'Failed to seed users'}`])
83
- }
84
- } catch (error) {
85
- setSeedingLogs([`❌ Error: ${error}`])
86
- }
87
- }
88
-
89
- const openViewModal = (user: User) => {
90
- setSelectedUser(user)
91
- setShowViewModal(true)
92
- }
93
-
94
- const openEditModal = (user: User) => {
95
- setSelectedUser(user)
96
- setShowEditModal(true)
97
- }
98
-
99
- const openDeleteModal = (user: User) => {
100
- setSelectedUser(user)
101
- setShowDeleteModal(true)
102
- }
103
-
104
- const handleCreateUser = async () => {
105
- const name = (document.getElementById('create-name') as HTMLInputElement)?.value
106
- const email = (document.getElementById('create-email') as HTMLInputElement)?.value
107
- const password = (document.getElementById('create-password') as HTMLInputElement)?.value
108
-
109
- if (!name || !email || !password) {
110
- toast.error('Please fill in all fields')
111
- return
112
- }
113
-
114
- const toastId = toast.loading('Creating user...')
115
-
116
- try {
117
- const response = await fetch('/api/users', {
118
- method: 'POST',
119
- headers: { 'Content-Type': 'application/json' },
120
- body: JSON.stringify({ name, email, password })
121
- })
122
-
123
- const result = await response.json()
124
-
125
- if (result.success) {
126
- // Refresh the users list to show the new user
127
- await fetchUsers()
128
- setShowCreateModal(false)
129
- // Clear the form
130
- ;(document.getElementById('create-name') as HTMLInputElement).value = ''
131
- ;(document.getElementById('create-email') as HTMLInputElement).value = ''
132
- ;(document.getElementById('create-password') as HTMLInputElement).value = ''
133
- toast.success('User created successfully!', { id: toastId })
134
- } else {
135
- toast.error(`Error creating user: ${result.error || 'Unknown error'}`, { id: toastId })
136
- }
137
- } catch (error) {
138
- console.error('Error creating user:', error)
139
- toast.error('Error creating user', { id: toastId })
140
- }
141
- }
142
-
143
- const handleUpdateUser = async () => {
144
- if (!selectedUser) {
145
- toast.error('No user selected')
146
- return
147
- }
148
-
149
- const name = (document.getElementById('edit-name') as HTMLInputElement)?.value
150
- const email = (document.getElementById('edit-email') as HTMLInputElement)?.value
151
-
152
- if (!name || !email) {
153
- toast.error('Please fill in all fields')
154
- return
155
- }
156
-
157
- const toastId = toast.loading('Updating user...')
158
-
159
- try {
160
- const response = await fetch(`/api/users/${selectedUser.id}`, {
161
- method: 'PUT',
162
- headers: { 'Content-Type': 'application/json' },
163
- body: JSON.stringify({ name, email })
164
- })
165
-
166
- const result = await response.json()
167
-
168
- if (result.success) {
169
- // Refresh the users list to show the updated user
170
- await fetchUsers()
171
- setShowEditModal(false)
172
- setSelectedUser(null)
173
- toast.success('User updated successfully!', { id: toastId })
174
- } else {
175
- toast.error(`Error updating user: ${result.error || 'Unknown error'}`, { id: toastId })
176
- }
177
- } catch (error) {
178
- console.error('Error updating user:', error)
179
- toast.error('Error updating user', { id: toastId })
180
- }
181
- }
182
-
183
- const handleDeleteUser = async () => {
184
- if (!selectedUser) {
185
- toast.error('No user selected')
186
- return
187
- }
188
-
189
- const toastId = toast.loading('Deleting user...')
190
-
191
- try {
192
- const response = await fetch(`/api/users/${selectedUser.id}`, {
193
- method: 'DELETE',
194
- headers: { 'Content-Type': 'application/json' }
195
- })
196
-
197
- const result = await response.json()
198
-
199
- if (result.success) {
200
- // Refresh the users list to remove the deleted user
201
- await fetchUsers()
202
- setShowDeleteModal(false)
203
- setSelectedUser(null)
204
- toast.success('User deleted successfully!', { id: toastId })
205
- } else {
206
- toast.error(`Error deleting user: ${result.error || 'Unknown error'}`, { id: toastId })
207
- }
208
- } catch (error) {
209
- console.error('Error deleting user:', error)
210
- toast.error('Error deleting user', { id: toastId })
211
- }
212
- }
213
-
214
- const filteredUsers = users.filter(user => {
215
- const matchesSearch = user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
216
- user.email.toLowerCase().includes(searchTerm.toLowerCase())
217
- const matchesFilter = filter === 'all'
218
- return matchesSearch && matchesFilter
219
- })
220
-
221
- // Pagination logic
222
- const totalPages = Math.ceil(filteredUsers.length / usersPerPage)
223
- const startIndex = (currentPage - 1) * usersPerPage
224
- const endIndex = startIndex + usersPerPage
225
- const currentUsers = filteredUsers.slice(startIndex, endIndex)
226
-
227
- const handlePageChange = (page: number) => {
228
- setCurrentPage(page)
229
- }
230
-
231
- if (loading) {
232
- return (
233
- <div className="flex items-center justify-center h-64">
234
- <div className="text-white">Loading all users from database...</div>
235
- </div>
236
- )
237
- }
238
-
239
- return (
240
- <div className="space-y-6 p-6">
241
- {/* Header */}
242
- <div className="flex items-center justify-between">
243
- <div>
244
- <h1 className="text-2xl text-white font-light">Users ({users.length})</h1>
245
- <p className="text-gray-400 mt-1">Manage your application users</p>
246
- </div>
247
- <div className="flex items-center space-x-3">
248
- <Button
249
- className="border border-dashed border-white/20 text-white hover:bg-white/10 bg-transparent rounded-none"
250
- onClick={() => setShowSeedModal(true)}
251
- >
252
- <Database className="w-4 h-4 mr-2" />
253
- Seed
254
- </Button>
255
- <Button
256
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
257
- onClick={() => setShowCreateModal(true)}
258
- >
259
- <UserPlus className="w-4 h-4 mr-2" />
260
- Add User
261
- </Button>
262
- </div>
263
- </div>
264
-
265
- {/* Filters */}
266
- <div className="flex items-center space-x-4">
267
- <div className="flex-1 relative">
268
- <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
269
- <Input
270
- placeholder="Search users..."
271
- value={searchTerm}
272
- onChange={(e) => setSearchTerm(e.target.value)}
273
- className="pl-10 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
274
- />
275
- </div>
276
-
277
- <div className="flex items-center space-x-2">
278
- <Filter className="w-4 h-4 text-gray-400" />
279
- <Select value={filter} onChange={(e) => setFilter(e.target.value)}>
280
- <SelectItem value="all">All</SelectItem>
281
- <SelectItem value="active">Active</SelectItem>
282
- <SelectItem value="inactive">Inactive</SelectItem>
283
- </Select>
284
- </div>
285
- </div>
286
-
287
- {/* Users Table */}
288
- <div className="bg-black/30 border border-dashed border-white/20 rounded-none">
289
- <div className="overflow-x-auto">
290
- <table className="w-full">
291
- <thead>
292
- <tr className="border-b border-dashed border-white/10">
293
- <th className="text-left py-4 px-4 text-white font-light">User</th>
294
- <th className="text-left py-4 px-4 text-white font-light">Email</th>
295
- <th className="text-left py-4 px-4 text-white font-light">Email Verified</th>
296
- <th className="text-left py-4 px-4 text-white font-light">Created</th>
297
- <th className="text-right py-4 px-4 text-white font-light">Actions</th>
298
- </tr>
299
- </thead>
300
- <tbody>
301
- {currentUsers.map((user) => (
302
- <tr key={user.id} className="border-b border-dashed border-white/5 hover:bg-white/5">
303
- <td className="py-4 px-4">
304
- <div className="flex items-center space-x-3">
305
- <img
306
- src={user.image || `https://api.dicebear.com/7.x/avataaars/svg?seed=${user.id}`}
307
- alt={user.name}
308
- className="w-10 h-10 rounded-none border border-dashed border-white/20"
309
- />
310
- <div>
311
- <div className="text-white font-light">{user.name}</div>
312
- <div className="text-sm text-gray-400">ID: {user.id}</div>
313
- </div>
314
- </div>
315
- </td>
316
- <td className="py-4 px-4 text-white">{user.email}</td>
317
- <td className="py-4 px-4">
318
- <div className="flex items-center space-x-2">
319
- {user.emailVerified ? (
320
- <Check className="w-4 h-4 text-green-400" />
321
- ) : (
322
- <Mail className="w-4 h-4 text-yellow-400" />
323
- )}
324
- <span className="text-sm text-gray-400">
325
- {user.emailVerified ? 'Verified' : 'Not Verified'}
326
- </span>
327
- </div>
328
- </td>
329
- <td className="py-4 px-4 text-sm text-gray-400">
330
- <div className='flex flex-col'>
331
- {new Date(user.createdAt).toLocaleDateString()}
332
- <p className='text-xs'>{new Date(user.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}</p>
333
- </div>
334
- </td>
335
- <td className="py-4 px-4 text-right">
336
- <div className="flex items-center justify-end space-x-2">
337
- <Button
338
- variant="ghost"
339
- size="sm"
340
- className="text-gray-400 hover:text-white rounded-none"
341
- onClick={() => openViewModal(user)}
342
- >
343
- <Eye className="w-4 h-4" />
344
- </Button>
345
- <Button
346
- variant="ghost"
347
- size="sm"
348
- className="text-gray-400 hover:text-white rounded-none"
349
- onClick={() => openEditModal(user)}
350
- >
351
- <Edit className="w-4 h-4" />
352
- </Button>
353
- <Button
354
- variant="ghost"
355
- size="sm"
356
- className="text-red-400 hover:text-red-300 rounded-none"
357
- onClick={() => openDeleteModal(user)}
358
- >
359
- <Trash2 className="w-4 h-4" />
360
- </Button>
361
- </div>
362
- </td>
363
- </tr>
364
- ))}
365
- </tbody>
366
- </table>
367
- </div>
368
-
369
- {/* Pagination */}
370
- {totalPages > 1 && (
371
- <div className="flex items-center justify-between mt-6">
372
- <div className="text-sm text-gray-400">
373
- Showing {startIndex + 1} to {Math.min(endIndex, filteredUsers.length)} of {filteredUsers.length} users
374
- </div>
375
- <div className="flex items-center space-x-2">
376
- <Button
377
- variant="outline"
378
- size="sm"
379
- onClick={() => handlePageChange(currentPage - 1)}
380
- disabled={currentPage === 1}
381
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
382
- >
383
- Previous
384
- </Button>
385
-
386
- {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
387
- <Button
388
- key={page}
389
- variant={currentPage === page ? "default" : "outline"}
390
- size="sm"
391
- onClick={() => handlePageChange(page)}
392
- className={
393
- currentPage === page
394
- ? "bg-white text-black rounded-none"
395
- : "border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
396
- }
397
- >
398
- {page}
399
- </Button>
400
- ))}
401
-
402
- <Button
403
- variant="outline"
404
- size="sm"
405
- onClick={() => handlePageChange(currentPage + 1)}
406
- disabled={currentPage === totalPages}
407
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
408
- >
409
- Next
410
- </Button>
411
- </div>
412
- </div>
413
- )}
414
- </div>
415
-
416
- {/* Seed Modal */}
417
- {showSeedModal && (
418
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
419
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-lg rounded-none">
420
- <div className="flex items-center justify-between mb-6">
421
- <h3 className="text-lg text-white font-light">Seed Data</h3>
422
- <Button
423
- variant="ghost"
424
- size="sm"
425
- onClick={() => setShowSeedModal(false)}
426
- className="text-gray-400 hover:text-white rounded-none"
427
- >
428
- <X className="w-4 h-4" />
429
- </Button>
430
- </div>
431
- <div className="space-y-6">
432
- {/* User Seeding */}
433
- <div className="space-y-4">
434
- <div className="flex items-center space-x-2">
435
- <UsersIcon className="w-5 h-5 text-white" />
436
- <h4 className="text-white font-light">Seed Users</h4>
437
- </div>
438
- <div className="flex items-center space-x-3">
439
- <div className="flex-1">
440
- <Label htmlFor="user-count" className="text-sm text-gray-400 font-light">Number of users</Label>
441
- <Input
442
- id="user-count"
443
- type="number"
444
- min="1"
445
- max="100"
446
- defaultValue="5"
447
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
448
- />
449
- </div>
450
- <Button
451
- onClick={() => {
452
- const count = parseInt((document.getElementById('user-count') as HTMLInputElement)?.value || '5')
453
- handleSeedUsers(count)
454
- }}
455
- className="bg-transparent hover:bg-white/90 bg-white text-black border border-white/20 rounded-none mt-6"
456
- >
457
- Seed Users
458
- </Button>
459
- </div>
460
- </div>
461
-
462
- {seedingLogs.length > 0 && (
463
- <div className="mt-6">
464
- <h5 className="text-sm text-white font-light">Seeding Log</h5>
465
- <br />
466
- <div className="flex w-full mb-3">
467
- <details className="group w-full">
468
- <summary className="cursor-pointer text-sm text-gray-400 font-light hover:text-white">
469
- View Details ({seedingLogs.length} items)
470
- </summary>
471
- <div className="mt-3 p-4 bg-black/50 border border-dashed border-white/20 rounded-none">
472
- <div className="space-y-2 max-h-48 overflow-y-auto">
473
- {seedingLogs.map((log, index) => (
474
- <div key={index} className="text-xs font-mono text-gray-300 flex items-start space-x-2">
475
- <span className="text-green-400">✓</span>
476
- <span>{log}</span>
477
- </div>
478
- ))}
479
- </div>
480
- </div>
481
- </details>
482
- </div>
483
- </div>
484
- )}
485
- </div>
486
- <div className="flex justify-end mt-6 pt-6 border-t border-dashed border-white/10">
487
- <Button
488
- variant="outline"
489
- onClick={() => setShowSeedModal(false)}
490
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
491
- >
492
- Close
493
- </Button>
494
- </div>
495
- </div>
496
- </div>
497
- )}
498
-
499
- {/* Create User Modal */}
500
- {showCreateModal && (
501
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
502
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
503
- <div className="flex items-center justify-between mb-4">
504
- <h3 className="text-lg text-white font-light">Create User</h3>
505
- <Button
506
- variant="ghost"
507
- size="sm"
508
- onClick={() => setShowCreateModal(false)}
509
- className="text-gray-400 hover:text-white rounded-none"
510
- >
511
- <X className="w-4 h-4" />
512
- </Button>
513
- </div>
514
- <div className="space-y-4">
515
- <div>
516
- <Label htmlFor="create-name" className="text-sm text-gray-400 font-light">Name</Label>
517
- <Input
518
- id="create-name"
519
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
520
- />
521
- </div>
522
- <div>
523
- <Label htmlFor="create-email" className="text-sm text-gray-400 font-light">Email</Label>
524
- <Input
525
- id="create-email"
526
- type="email"
527
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
528
- />
529
- </div>
530
- <div>
531
- <Label htmlFor="create-password" className="text-sm text-gray-400 font-light">Password</Label>
532
- <Input
533
- id="create-password"
534
- type="password"
535
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
536
- />
537
- </div>
538
- </div>
539
- <div className="flex justify-end space-x-3 mt-6">
540
- <Button
541
- variant="outline"
542
- onClick={() => setShowCreateModal(false)}
543
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
544
- >
545
- Cancel
546
- </Button>
547
- <Button
548
- onClick={handleCreateUser}
549
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
550
- >
551
- Create
552
- </Button>
553
- </div>
554
- </div>
555
- </div>
556
- )}
557
-
558
- {/* Edit User Modal */}
559
- {showEditModal && selectedUser && (
560
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
561
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
562
- <div className="flex items-center justify-between mb-4">
563
- <h3 className="text-lg text-white font-light">Edit User</h3>
564
- <Button
565
- variant="ghost"
566
- size="sm"
567
- onClick={() => setShowEditModal(false)}
568
- className="text-gray-400 hover:text-white rounded-none"
569
- >
570
- <X className="w-4 h-4" />
571
- </Button>
572
- </div>
573
- <div className="space-y-4">
574
- <div className="flex items-center space-x-3">
575
- <img
576
- src={selectedUser.image || `https://api.dicebear.com/7.x/avataaars/svg?seed=${selectedUser.id}`}
577
- alt={selectedUser.name}
578
- className="w-16 h-16 rounded-none border border-dashed border-white/20"
579
- />
580
- <div>
581
- <div className="text-white font-light">{selectedUser.name}</div>
582
- <div className="text-sm text-gray-400">{selectedUser.email}</div>
583
- </div>
584
- </div>
585
- <div>
586
- <Label htmlFor="edit-name" className="text-sm text-gray-400 font-light">Name</Label>
587
- <Input
588
- id="edit-name"
589
- defaultValue={selectedUser.name}
590
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
591
- />
592
- </div>
593
- <div>
594
- <Label htmlFor="edit-email" className="text-sm text-gray-400 font-light">Email</Label>
595
- <Input
596
- id="edit-email"
597
- type="email"
598
- defaultValue={selectedUser.email}
599
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
600
- />
601
- </div>
602
- </div>
603
- <div className="flex justify-end space-x-3 mt-6">
604
- <Button
605
- variant="outline"
606
- onClick={() => setShowEditModal(false)}
607
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
608
- >
609
- Cancel
610
- </Button>
611
- <Button
612
- onClick={handleUpdateUser}
613
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
614
- >
615
- Update
616
- </Button>
617
- </div>
618
- </div>
619
- </div>
620
- )}
621
- {/* Delete User Modal */}
622
- {showDeleteModal && selectedUser && (
623
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
624
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
625
- <div className="flex items-center justify-between mb-4">
626
- <h3 className="text-lg text-white font-light">Delete User</h3>
627
- <Button
628
- variant="ghost"
629
- size="sm"
630
- onClick={() => setShowDeleteModal(false)}
631
- className="text-gray-400 hover:text-white rounded-none"
632
- >
633
- <X className="w-4 h-4" />
634
- </Button>
635
- </div>
636
- <div className="space-y-4">
637
- <div className="flex items-center space-x-3">
638
- <img
639
- src={selectedUser.image || `https://api.dicebear.com/7.x/avataaars/svg?seed=${selectedUser.id}`}
640
- alt={selectedUser.name}
641
- className="w-16 h-16 rounded-none border border-dashed border-white/20"
642
- />
643
- <div>
644
- <div className="text-white font-light">{selectedUser.name}</div>
645
- <div className="text-sm text-gray-400">{selectedUser.email}</div>
646
- </div>
647
- </div>
648
- <p className="text-gray-400">Are you sure you want to delete this user? This action cannot be undone.</p>
649
- </div>
650
- <div className="flex justify-end space-x-3 mt-6">
651
- <Button
652
- variant="outline"
653
- onClick={() => setShowDeleteModal(false)}
654
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
655
- >
656
- Cancel
657
- </Button>
658
- <Button
659
- onClick={handleDeleteUser}
660
- className="bg-red-600 hover:bg-red-700 text-white border border-red-600 rounded-none"
661
- >
662
- Delete
663
- </Button>
664
- </div>
665
- </div>
666
- </div>
667
- )}
668
-
669
- {/* View User Modal */}
670
- {showViewModal && selectedUser && (
671
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
672
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
673
- <div className="flex items-center justify-between mb-4">
674
- <h3 className="text-lg text-white font-light">User Details</h3>
675
- <Button
676
- variant="ghost"
677
- size="sm"
678
- onClick={() => setShowViewModal(false)}
679
- className="text-gray-400 hover:text-white rounded-none"
680
- >
681
- <X className="w-4 h-4" />
682
- </Button>
683
- </div>
684
- <div className="space-y-4">
685
- <div className="flex items-center space-x-3">
686
- <img
687
- src={selectedUser.image || `https://api.dicebear.com/7.x/avataaars/svg?seed=${selectedUser.id}`}
688
- alt={selectedUser.name}
689
- className="w-16 h-16 rounded-none border border-dashed border-white/20"
690
- />
691
- <div>
692
- <div className="text-white font-light">{selectedUser.name}</div>
693
- <div className="text-sm text-gray-400">{selectedUser.email}</div>
694
- </div>
695
- </div>
696
- <div className="space-y-2">
697
- <div className="flex justify-between">
698
- <span className="text-gray-400">ID:</span>
699
- <span className="text-white text-sm">{selectedUser.id}</span>
700
- </div>
701
- <div className="flex justify-between">
702
- <span className="text-gray-400">Email Verified:</span>
703
- <span className="text-white text-sm">{selectedUser.emailVerified ? 'Yes' : 'No'}</span>
704
- </div>
705
- <div className="flex justify-between">
706
- <span className="text-gray-400">Created:</span>
707
- <span className="text-white text-sm">{new Date(selectedUser.createdAt).toLocaleString()}</span>
708
- </div>
709
- <div className="flex justify-between">
710
- <span className="text-gray-400">Updated:</span>
711
- <span className="text-white text-sm">{new Date(selectedUser.updatedAt).toLocaleString()}</span>
712
- </div>
713
- </div>
714
- </div>
715
- <div className="flex justify-end mt-6">
716
- <Button
717
- onClick={() => setShowViewModal(false)}
718
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
719
- >
720
- Close
721
- </Button>
722
- </div>
723
- </div>
724
- </div>
725
- )}
726
- </div>
727
- )
728
- }