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.
- package/package.json +8 -1
- package/frontend/index.html +0 -13
- package/frontend/package-lock.json +0 -4675
- package/frontend/package.json +0 -52
- package/frontend/pnpm-lock.yaml +0 -4020
- package/frontend/postcss.config.js +0 -6
- package/frontend/src/App.tsx +0 -36
- package/frontend/src/components/CommandPalette.tsx +0 -219
- package/frontend/src/components/Layout.tsx +0 -159
- package/frontend/src/components/ui/badge.tsx +0 -40
- package/frontend/src/components/ui/button.tsx +0 -53
- package/frontend/src/components/ui/card.tsx +0 -78
- package/frontend/src/components/ui/input.tsx +0 -20
- package/frontend/src/components/ui/label.tsx +0 -19
- package/frontend/src/components/ui/select.tsx +0 -71
- package/frontend/src/index.css +0 -130
- package/frontend/src/lib/utils.ts +0 -6
- package/frontend/src/main.tsx +0 -10
- package/frontend/src/pages/Dashboard.tsx +0 -231
- package/frontend/src/pages/OrganizationDetails.tsx +0 -1281
- package/frontend/src/pages/Organizations.tsx +0 -874
- package/frontend/src/pages/Sessions.tsx +0 -623
- package/frontend/src/pages/Settings.tsx +0 -1019
- package/frontend/src/pages/TeamDetails.tsx +0 -666
- package/frontend/src/pages/Users.tsx +0 -728
- package/frontend/tailwind.config.js +0 -75
- package/frontend/tsconfig.json +0 -31
- package/frontend/tsconfig.node.json +0 -10
- package/frontend/vite.config.ts +0 -31
- package/src/auth-adapter.ts +0 -473
- package/src/cli.ts +0 -51
- package/src/config.ts +0 -320
- package/src/data.ts +0 -351
- package/src/routes.ts +0 -1585
- package/src/studio.ts +0 -86
- package/test-project/README.md +0 -0
- package/test-project/better-auth.db +0 -0
- package/test-project/better-auth_migrations/2025-08-27T15-55-04.099Z.sql +0 -7
- package/test-project/better-auth_migrations/2025-09-04T02-33-19.422Z.sql +0 -7
- package/test-project/package.json +0 -29
- package/test-project/pnpm-lock.yaml +0 -1728
- package/test-project/src/auth.ts +0 -47
- package/test-project/src/index.ts +0 -40
- 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
|
-
}
|