better-auth-studio 1.0.6 → 1.0.8

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 (51) hide show
  1. package/dist/config.d.ts +1 -0
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js +126 -29
  4. package/dist/config.js.map +1 -1
  5. package/dist/routes.d.ts.map +1 -1
  6. package/dist/routes.js +28 -2
  7. package/dist/routes.js.map +1 -1
  8. package/package.json +8 -1
  9. package/frontend/index.html +0 -13
  10. package/frontend/package-lock.json +0 -4675
  11. package/frontend/package.json +0 -52
  12. package/frontend/pnpm-lock.yaml +0 -4020
  13. package/frontend/postcss.config.js +0 -6
  14. package/frontend/src/App.tsx +0 -36
  15. package/frontend/src/components/CommandPalette.tsx +0 -219
  16. package/frontend/src/components/Layout.tsx +0 -159
  17. package/frontend/src/components/ui/badge.tsx +0 -40
  18. package/frontend/src/components/ui/button.tsx +0 -53
  19. package/frontend/src/components/ui/card.tsx +0 -78
  20. package/frontend/src/components/ui/input.tsx +0 -20
  21. package/frontend/src/components/ui/label.tsx +0 -19
  22. package/frontend/src/components/ui/select.tsx +0 -71
  23. package/frontend/src/index.css +0 -130
  24. package/frontend/src/lib/utils.ts +0 -6
  25. package/frontend/src/main.tsx +0 -10
  26. package/frontend/src/pages/Dashboard.tsx +0 -231
  27. package/frontend/src/pages/OrganizationDetails.tsx +0 -1281
  28. package/frontend/src/pages/Organizations.tsx +0 -874
  29. package/frontend/src/pages/Sessions.tsx +0 -623
  30. package/frontend/src/pages/Settings.tsx +0 -1019
  31. package/frontend/src/pages/TeamDetails.tsx +0 -666
  32. package/frontend/src/pages/Users.tsx +0 -728
  33. package/frontend/tailwind.config.js +0 -75
  34. package/frontend/tsconfig.json +0 -31
  35. package/frontend/tsconfig.node.json +0 -10
  36. package/frontend/vite.config.ts +0 -31
  37. package/src/auth-adapter.ts +0 -473
  38. package/src/cli.ts +0 -51
  39. package/src/config.ts +0 -320
  40. package/src/data.ts +0 -351
  41. package/src/routes.ts +0 -1585
  42. package/src/studio.ts +0 -86
  43. package/test-project/README.md +0 -0
  44. package/test-project/better-auth.db +0 -0
  45. package/test-project/better-auth_migrations/2025-08-27T15-55-04.099Z.sql +0 -7
  46. package/test-project/better-auth_migrations/2025-09-04T02-33-19.422Z.sql +0 -7
  47. package/test-project/package.json +0 -29
  48. package/test-project/pnpm-lock.yaml +0 -1728
  49. package/test-project/src/auth.ts +0 -47
  50. package/test-project/src/index.ts +0 -40
  51. package/tsconfig.json +0 -21
@@ -1,874 +0,0 @@
1
- import { useState, useEffect } from 'react'
2
- import { useNavigate } from 'react-router-dom'
3
- import { toast } from 'sonner'
4
- import {
5
- Building2,
6
- Search,
7
- Filter,
8
- Edit,
9
- Trash2,
10
- Plus,
11
- Eye,
12
- X,
13
- Database
14
- } from 'lucide-react'
15
- import { Button } from '../components/ui/button'
16
- import { Input } from '../components/ui/input'
17
- import { Label } from '../components/ui/label'
18
- import { Select, SelectItem } from '../components/ui/select'
19
-
20
- interface Organization {
21
- id: string
22
- name: string
23
- slug: string
24
- metadata?: any
25
- createdAt: string
26
- updatedAt: string
27
- }
28
-
29
- interface PluginStatus {
30
- enabled: boolean
31
- error?: string
32
- configPath?: string | null
33
- availablePlugins?: string[]
34
- organizationPlugin?: any
35
- }
36
-
37
- export default function Organizations() {
38
- const navigate = useNavigate()
39
- const [organizations, setOrganizations] = useState<Organization[]>([])
40
- const [loading, setLoading] = useState(true)
41
- const [pluginStatus, setPluginStatus] = useState<PluginStatus | null>(null)
42
- const [searchTerm, setSearchTerm] = useState('')
43
- const [filter, setFilter] = useState('all')
44
- const [showCreateModal, setShowCreateModal] = useState(false)
45
- const [showEditModal, setShowEditModal] = useState(false)
46
- const [showDeleteModal, setShowDeleteModal] = useState(false)
47
- const [showViewModal, setShowViewModal] = useState(false)
48
- const [showSeedModal, setShowSeedModal] = useState(false)
49
- const [selectedOrganization, setSelectedOrganization] = useState<Organization | null>(null)
50
- const [seedingLogs, setSeedingLogs] = useState<string[]>([])
51
- const [createFormData, setCreateFormData] = useState({ name: '', slug: '' })
52
- const [editFormData, setEditFormData] = useState({ name: '', slug: '' })
53
-
54
- useEffect(() => {
55
- checkPluginStatus()
56
- }, [])
57
-
58
- // Utility function to generate slug from name
59
- const generateSlug = (name: string): string => {
60
- return name
61
- .toLowerCase()
62
- .replace(/\s+/g, '-') // Replace spaces with hyphens
63
- .replace(/[^a-z0-9-]/g, '') // Remove special characters except hyphens
64
- .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen
65
- .replace(/^-|-$/g, '') // Remove leading/trailing hyphens
66
- }
67
-
68
- // Handle create form name change with auto-slug generation
69
- const handleCreateNameChange = (name: string) => {
70
- const slug = generateSlug(name)
71
- setCreateFormData({ name, slug })
72
- }
73
-
74
- // Handle create form slug change (manual override)
75
- const handleCreateSlugChange = (slug: string) => {
76
- setCreateFormData(prev => ({ ...prev, slug: generateSlug(slug) }))
77
- }
78
-
79
- // Handle edit form name change with auto-slug generation
80
- const handleEditNameChange = (name: string) => {
81
- const slug = generateSlug(name)
82
- setEditFormData({ name, slug })
83
- }
84
-
85
- // Handle edit form slug change (manual override)
86
- const handleEditSlugChange = (slug: string) => {
87
- setEditFormData(prev => ({ ...prev, slug: generateSlug(slug) }))
88
- }
89
-
90
- const checkPluginStatus = async () => {
91
- try {
92
- const response = await fetch('/api/plugins/organization/status')
93
- const status = await response.json()
94
- setPluginStatus(status)
95
-
96
- if (status.enabled) {
97
- await fetchOrganizations()
98
- } else {
99
- setLoading(false)
100
- }
101
- } catch (error) {
102
- console.error('Failed to check plugin status:', error)
103
- setPluginStatus({ enabled: false, error: 'Failed to check plugin status' })
104
- setLoading(false)
105
- }
106
- }
107
-
108
- const fetchOrganizations = async () => {
109
- try {
110
- // Request all organizations by setting a high limit
111
- const response = await fetch('/api/organizations?limit=10000')
112
- const data = await response.json()
113
- setOrganizations(data.organizations || [])
114
- } catch (error) {
115
- console.error('Failed to fetch organizations:', error)
116
- toast.error('Failed to fetch organizations')
117
- } finally {
118
- setLoading(false)
119
- }
120
- }
121
-
122
- const handleSeedOrganizations = async (count: number) => {
123
- setSeedingLogs([])
124
-
125
- try {
126
- const response = await fetch('/api/seed/organizations', {
127
- method: 'POST',
128
- headers: { 'Content-Type': 'application/json' },
129
- body: JSON.stringify({ count })
130
- })
131
-
132
- const result = await response.json()
133
-
134
- if (result.success) {
135
- setSeedingLogs(result.results.map((r: any) =>
136
- `✅ Created organization: ${r.organization.name} (${r.organization.slug})`
137
- ))
138
- // Refresh the organizations list to show updated count
139
- await fetchOrganizations()
140
- } else {
141
- setSeedingLogs([`❌ Error: ${result.error || 'Failed to seed organizations'}`])
142
- }
143
- } catch (error) {
144
- setSeedingLogs([`❌ Error: ${error}`])
145
- }
146
- }
147
-
148
- const handleSeedTeams = async (count: number) => {
149
- setSeedingLogs([])
150
-
151
- try {
152
- const response = await fetch('/api/seed/teams', {
153
- method: 'POST',
154
- headers: { 'Content-Type': 'application/json' },
155
- body: JSON.stringify({ count })
156
- })
157
-
158
- const result = await response.json()
159
-
160
- if (result.success) {
161
- setSeedingLogs(result.results.map((r: any) =>
162
- `✅ Created team: ${r.team.name}`
163
- ))
164
- // Refresh the organizations list to show updated count
165
- await fetchOrganizations()
166
- } else {
167
- setSeedingLogs([`❌ Error: ${result.error || 'Failed to seed teams'}`])
168
- }
169
- } catch (error) {
170
- setSeedingLogs([`❌ Error: ${error}`])
171
- }
172
- }
173
-
174
- const openViewModal = (organization: Organization) => {
175
- setSelectedOrganization(organization)
176
- setShowViewModal(true)
177
- }
178
-
179
- const openEditModal = (organization: Organization) => {
180
- setSelectedOrganization(organization)
181
- setEditFormData({ name: organization.name, slug: organization.slug })
182
- setShowEditModal(true)
183
- }
184
-
185
- const openDeleteModal = (organization: Organization) => {
186
- setSelectedOrganization(organization)
187
- setShowDeleteModal(true)
188
- }
189
-
190
- const handleCreateOrganization = async () => {
191
- if (!createFormData.name) {
192
- toast.error('Please fill in the organization name')
193
- return
194
- }
195
-
196
- const toastId = toast.loading('Creating organization...')
197
-
198
- try {
199
- const response = await fetch('/api/organizations', {
200
- method: 'POST',
201
- headers: { 'Content-Type': 'application/json' },
202
- body: JSON.stringify({
203
- name: createFormData.name,
204
- slug: createFormData.slug
205
- })
206
- })
207
-
208
- const result = await response.json()
209
-
210
- if (result.success) {
211
- // Refresh the organizations list to show the new organization
212
- await fetchOrganizations()
213
- setShowCreateModal(false)
214
- // Clear the form
215
- setCreateFormData({ name: '', slug: '' })
216
- toast.success('Organization created successfully!', { id: toastId })
217
- } else {
218
- toast.error(`Error creating organization: ${result.error || 'Unknown error'}`, { id: toastId })
219
- }
220
- } catch (error) {
221
- console.error('Error creating organization:', error)
222
- toast.error('Error creating organization', { id: toastId })
223
- }
224
- }
225
-
226
- const handleUpdateOrganization = async () => {
227
- if (!selectedOrganization) {
228
- toast.error('No organization selected')
229
- return
230
- }
231
-
232
- if (!editFormData.name) {
233
- toast.error('Please fill in the organization name')
234
- return
235
- }
236
-
237
- const toastId = toast.loading('Updating organization...')
238
-
239
- try {
240
- const response = await fetch(`/api/organizations/${selectedOrganization.id}`, {
241
- method: 'PUT',
242
- headers: { 'Content-Type': 'application/json' },
243
- body: JSON.stringify({
244
- name: editFormData.name,
245
- slug: editFormData.slug
246
- })
247
- })
248
-
249
- const result = await response.json()
250
-
251
- if (result.success) {
252
- // Refresh the organizations list to show the updated organization
253
- await fetchOrganizations()
254
- setShowEditModal(false)
255
- setSelectedOrganization(null)
256
- setEditFormData({ name: '', slug: '' })
257
- toast.success('Organization updated successfully!', { id: toastId })
258
- } else {
259
- toast.error(`Error updating organization: ${result.error || 'Unknown error'}`, { id: toastId })
260
- }
261
- } catch (error) {
262
- console.error('Error updating organization:', error)
263
- toast.error('Error updating organization', { id: toastId })
264
- }
265
- }
266
-
267
- const handleDeleteOrganization = async () => {
268
- if (!selectedOrganization) {
269
- toast.error('No organization selected')
270
- return
271
- }
272
-
273
- const toastId = toast.loading('Deleting organization...')
274
-
275
- try {
276
- const response = await fetch(`/api/organizations/${selectedOrganization.id}`, {
277
- method: 'DELETE',
278
- headers: { 'Content-Type': 'application/json' }
279
- })
280
-
281
- const result = await response.json()
282
-
283
- if (result.success) {
284
- // Refresh the organizations list to remove the deleted organization
285
- await fetchOrganizations()
286
- setShowDeleteModal(false)
287
- setSelectedOrganization(null)
288
- toast.success('Organization deleted successfully!', { id: toastId })
289
- } else {
290
- toast.error(`Error deleting organization: ${result.error || 'Unknown error'}`, { id: toastId })
291
- }
292
- } catch (error) {
293
- console.error('Error deleting organization:', error)
294
- toast.error('Error deleting organization', { id: toastId })
295
- }
296
- }
297
-
298
- const filteredOrganizations = organizations.filter(organization => {
299
- const matchesSearch = organization.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
300
- organization.slug.toLowerCase().includes(searchTerm.toLowerCase())
301
- const matchesFilter = filter === 'all' || organization.metadata?.status === filter
302
- return matchesSearch && matchesFilter
303
- })
304
-
305
- if (loading) {
306
- return (
307
- <div className="flex items-center justify-center h-64">
308
- <div className="text-white">Loading all organizations from database...</div>
309
- </div>
310
- )
311
- }
312
-
313
- // Show plugin setup prompt if organization plugin is not enabled
314
- if (pluginStatus && !pluginStatus.enabled) {
315
- return (
316
- <div className="space-y-6 p-6">
317
- {/* Header */}
318
- <div className="flex items-center justify-between">
319
- <div>
320
- <h1 className="text-2xl text-white font-light">Organizations</h1>
321
- <p className="text-gray-400 mt-1">Manage your application organizations</p>
322
- </div>
323
- </div>
324
-
325
- {/* Plugin Setup Card */}
326
- <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-8">
327
- <div className="flex items-start space-x-4">
328
- <div className="flex-shrink-0">
329
- <Building2 className="w-12 h-12 text-white" />
330
- </div>
331
- <div className="flex-1">
332
- <h3 className="text-xl text-white font-light mb-2">Organization Plugin Required</h3>
333
- <p className="text-gray-300 mb-6">
334
- To use Organizations in Better Auth Studio, you need to enable the organization plugin in your Better Auth configuration.
335
- </p>
336
-
337
- <div className="bg-black/50 border border-dashed border-white/20 rounded-none p-4 mb-6">
338
- <h4 className="text-white font-light mb-3">Follow these steps:</h4>
339
- <ol className="text-gray-300 space-y-2 text-sm list-decimal list-inside">
340
- <li>Import the plugin in your auth configuration file{pluginStatus.configPath && (
341
- <span className="text-gray-400"> ({pluginStatus.configPath})</span>
342
- )}:</li>
343
- </ol>
344
-
345
- <div className="mt-4 bg-black/70 border border-dashed border-white/10 rounded-none p-3 overflow-x-auto">
346
- <pre className="text-sm text-gray-300">
347
- <span className="text-blue-400">import</span> {`{ betterAuth }`} <span className="text-blue-400">from</span> <span className="text-green-400">"better-auth"</span> <br />
348
- <span className="text-blue-400">import</span> {`{ organization }`} <span className="text-blue-400">from</span> <span className="text-green-400">"better-auth/plugins/organization"</span> <br />
349
-
350
- <span className="text-blue-400">export const</span> <span className="text-yellow-300">auth</span> = <span className="text-yellow-300">betterAuth</span>({`{`} <br />
351
- <span className="text-gray-500 pl-10">// ... your existing configuration</span> <br />
352
- <span className="text-red-300 pl-10">plugins</span>: [ <br />
353
- <span className="text-yellow-300 pl-12">organization({})</span><br />
354
- <span className="pl-10">]</span> <br />
355
- {`}`}) <br />
356
- </pre>
357
- </div>
358
-
359
- <div className="mt-4">
360
- <p className="text-gray-400 text-sm">2. Do migrations to create the organizations table</p>
361
- </div>
362
- <div className="mt-2">
363
- <p className="text-gray-400 text-sm">3. Restart your application to apply the changes</p>
364
- </div>
365
- </div>
366
-
367
- {pluginStatus.availablePlugins && pluginStatus.availablePlugins.length > 0 && (
368
- <div className="mb-4">
369
- <p className="text-gray-400 text-sm">
370
- Currently enabled plugins: {pluginStatus.availablePlugins.join(', ')}
371
- </p>
372
- </div>
373
- )}
374
-
375
- <Button
376
- onClick={() => window.location.reload()}
377
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
378
- >
379
- Check Again
380
- </Button>
381
-
382
- <div className="mt-4 text-xs text-gray-500">
383
- Need help? Check the{' '}
384
- <a
385
- href="https://better-auth.com/docs/plugins/organization"
386
- target="_blank"
387
- rel="noopener noreferrer"
388
- className="text-white hover:underline"
389
- >
390
- Better Auth Organization Plugin Documentation
391
- </a>
392
- </div>
393
- </div>
394
- </div>
395
- </div>
396
- </div>
397
- )
398
- }
399
-
400
- return (
401
- <div className="space-y-6 p-6">
402
- {/* Header */}
403
- <div className="flex items-center justify-between">
404
- <div>
405
- <h1 className="text-2xl text-white font-light">Organizations ({organizations.length})</h1>
406
- <p className="text-gray-400 mt-1">Manage your organizations and teams</p>
407
- </div>
408
- <div className="flex items-center space-x-3">
409
- <Button
410
- className="border border-dashed border-white/20 text-white hover:bg-white/10 bg-transparent rounded-none"
411
- onClick={() => setShowSeedModal(true)}
412
- >
413
- <Database className="w-4 h-4 mr-2" />
414
- Seed
415
- </Button>
416
- <Button
417
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
418
- onClick={() => setShowCreateModal(true)}
419
- >
420
- <Plus className="w-4 h-4 mr-2" />
421
- Add Organization
422
- </Button>
423
- </div>
424
- </div>
425
-
426
- {/* Filters */}
427
- <div className="flex items-center space-x-4">
428
- <div className="flex-1 relative">
429
- <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
430
- <Input
431
- placeholder="Search organizations..."
432
- value={searchTerm}
433
- onChange={(e) => setSearchTerm(e.target.value)}
434
- className="pl-10 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
435
- />
436
- </div>
437
-
438
- <div className="flex items-center space-x-2">
439
- <Filter className="w-4 h-4 text-gray-400" />
440
- <Select value={filter} onChange={(e) => setFilter(e.target.value)}>
441
- <SelectItem value="all">All</SelectItem>
442
- <SelectItem value="active">Active</SelectItem>
443
- <SelectItem value="inactive">Inactive</SelectItem>
444
- </Select>
445
- </div>
446
- </div>
447
-
448
- {/* Organizations Table */}
449
- <div className="bg-black/30 border border-dashed border-white/20 rounded-none">
450
- <div className="overflow-x-auto">
451
- <table className="w-full">
452
- <thead>
453
- <tr className="border-b border-dashed border-white/10">
454
- <th className="text-left py-4 px-4 text-white font-light">Organization</th>
455
- <th className="text-left py-4 px-4 text-white font-light">Slug</th>
456
- <th className="text-left py-4 px-4 text-white font-light">Created</th>
457
- <th className="text-right py-4 px-4 text-white font-light">Actions</th>
458
- </tr>
459
- </thead>
460
- <tbody>
461
- {filteredOrganizations.map((organization) => (
462
- <tr
463
- key={organization.id}
464
- className="border-b border-dashed border-white/5 hover:bg-white/5 cursor-pointer"
465
- onClick={() => navigate(`/organizations/${organization.id}`)}
466
- >
467
- <td className="py-4 px-4">
468
- <div className="flex items-center space-x-3">
469
- <div className="w-10 h-10 rounded-none border border-dashed border-white/20 bg-white/10 flex items-center justify-center">
470
- <Building2 className="w-5 h-5 text-white" />
471
- </div>
472
- <div>
473
- <div className="text-white font-light">{organization.name}</div>
474
- <div className="text-sm text-gray-400">ID: {organization.id}</div>
475
- </div>
476
- </div>
477
- </td>
478
- <td className="py-4 px-4 text-white">{organization.slug}</td>
479
- <td className="py-4 px-4 text-sm text-gray-400">
480
- <div className="flex flex-col">
481
- {new Date(organization.createdAt).toLocaleDateString()}
482
- <p className="text-xs">{new Date(organization.createdAt).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}</p>
483
- </div>
484
- </td>
485
- <td className="py-4 px-4 text-right">
486
- <div className="flex items-center justify-end space-x-2">
487
- <Button
488
- variant="ghost"
489
- size="sm"
490
- className="text-gray-400 hover:text-white rounded-none"
491
- onClick={(e) => {
492
- e.stopPropagation()
493
- openViewModal(organization)
494
- }}
495
- >
496
- <Eye className="w-4 h-4" />
497
- </Button>
498
- <Button
499
- variant="ghost"
500
- size="sm"
501
- className="text-gray-400 hover:text-white rounded-none"
502
- onClick={(e) => {
503
- e.stopPropagation()
504
- openEditModal(organization)
505
- }}
506
- >
507
- <Edit className="w-4 h-4" />
508
- </Button>
509
- <Button
510
- variant="ghost"
511
- size="sm"
512
- className="text-red-400 hover:text-red-300 rounded-none"
513
- onClick={(e) => {
514
- e.stopPropagation()
515
- openDeleteModal(organization)
516
- }}
517
- >
518
- <Trash2 className="w-4 h-4" />
519
- </Button>
520
- </div>
521
- </td>
522
- </tr>
523
- ))}
524
- </tbody>
525
- </table>
526
- </div>
527
- </div>
528
-
529
- {/* Seed Modal */}
530
- {showSeedModal && (
531
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
532
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-lg rounded-none">
533
- <div className="flex items-center justify-between mb-6">
534
- <h3 className="text-lg text-white font-light">Seed Data</h3>
535
- <Button
536
- variant="ghost"
537
- size="sm"
538
- onClick={() => setShowSeedModal(false)}
539
- className="text-gray-400 hover:text-white rounded-none"
540
- >
541
- <X className="w-4 h-4" />
542
- </Button>
543
- </div>
544
- <div className="space-y-6">
545
- {/* Organization Seeding */}
546
- <div className="space-y-4">
547
- <div className="flex items-center space-x-2">
548
- <Building2 className="w-5 h-5 text-white" />
549
- <h4 className="text-white font-light">Seed Organizations</h4>
550
- </div>
551
- <div className="flex items-center space-x-3">
552
- <div className="flex-1">
553
- <Label htmlFor="organization-count" className="text-sm text-gray-400 font-light">Number of organizations</Label>
554
- <Input
555
- id="organization-count"
556
- type="number"
557
- min="1"
558
- max="100"
559
- defaultValue="5"
560
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
561
- />
562
- </div>
563
- <Button
564
- onClick={() => {
565
- const count = parseInt((document.getElementById('organization-count') as HTMLInputElement)?.value || '5')
566
- handleSeedOrganizations(count)
567
- }}
568
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none mt-6"
569
- >
570
- Seed Organizations
571
- </Button>
572
- </div>
573
- </div>
574
-
575
- {/* Team Seeding */}
576
- <div className="space-y-4">
577
- <div className="flex items-center space-x-2">
578
- <Plus className="w-5 h-5 text-white" />
579
- <h4 className="text-white font-light">Seed Teams</h4>
580
- </div>
581
- <div className="flex items-center space-x-3">
582
- <div className="flex-1">
583
- <Label htmlFor="team-count" className="text-sm text-gray-400 font-light">Number of teams</Label>
584
- <Input
585
- id="team-count"
586
- type="number"
587
- min="1"
588
- max="100"
589
- defaultValue="5"
590
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
591
- />
592
- </div>
593
- <Button
594
- onClick={() => {
595
- const count = parseInt((document.getElementById('team-count') as HTMLInputElement)?.value || '5')
596
- handleSeedTeams(count)
597
- }}
598
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none mt-6"
599
- >
600
- Seed Teams
601
- </Button>
602
- </div>
603
- </div>
604
-
605
- {/* Seeding Logs */}
606
- {seedingLogs.length > 0 && (
607
- <div className="mt-6">
608
- <div className="flex items-center justify-between mb-3">
609
- <h5 className="text-sm text-white font-light">Seeding Log</h5>
610
- <details className="group">
611
- <summary className="cursor-pointer text-sm text-gray-400 font-light hover:text-white">
612
- View Details ({seedingLogs.length} items)
613
- </summary>
614
- <div className="mt-3 p-4 bg-black/50 border border-dashed border-white/20 rounded-none">
615
- <div className="space-y-2 max-h-48 overflow-y-auto">
616
- {seedingLogs.map((log, index) => (
617
- <div key={index} className="text-xs font-mono text-gray-300 flex items-start space-x-2">
618
- <span className="text-green-400">✓</span>
619
- <span>{log}</span>
620
- </div>
621
- ))}
622
- </div>
623
- </div>
624
- </details>
625
- </div>
626
- </div>
627
- )}
628
- </div>
629
- <div className="flex justify-end mt-6 pt-6 border-t border-dashed border-white/10">
630
- <Button
631
- variant="outline"
632
- onClick={() => setShowSeedModal(false)}
633
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
634
- >
635
- Close
636
- </Button>
637
- </div>
638
- </div>
639
- </div>
640
- )}
641
-
642
- {/* Create Organization Modal */}
643
- {showCreateModal && (
644
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
645
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
646
- <div className="flex items-center justify-between mb-4">
647
- <h3 className="text-lg text-white font-light">Create Organization</h3>
648
- <Button
649
- variant="ghost"
650
- size="sm"
651
- onClick={() => setShowCreateModal(false)}
652
- className="text-gray-400 hover:text-white rounded-none"
653
- >
654
- <X className="w-4 h-4" />
655
- </Button>
656
- </div>
657
- <div className="space-y-4">
658
- <div>
659
- <Label htmlFor="create-name" className="text-sm text-gray-400 font-light">Name</Label>
660
- <Input
661
- id="create-name"
662
- value={createFormData.name}
663
- onChange={(e) => handleCreateNameChange(e.target.value)}
664
- placeholder="e.g. Acme Corp"
665
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
666
- />
667
- </div>
668
- <div>
669
- <Label htmlFor="create-slug" className="text-sm text-gray-400 font-light">Slug</Label>
670
- <Input
671
- id="create-slug"
672
- value={createFormData.slug}
673
- onChange={(e) => handleCreateSlugChange(e.target.value)}
674
- placeholder="e.g. acme-corp"
675
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
676
- />
677
- <p className="text-xs text-gray-500 mt-1">
678
- Auto-generated from name. You can edit it manually.
679
- </p>
680
- </div>
681
- </div>
682
- <div className="flex justify-end space-x-3 mt-6">
683
- <Button
684
- variant="outline"
685
- onClick={() => {
686
- setShowCreateModal(false)
687
- setCreateFormData({ name: '', slug: '' })
688
- }}
689
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
690
- >
691
- Cancel
692
- </Button>
693
- <Button
694
- onClick={handleCreateOrganization}
695
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
696
- >
697
- Create
698
- </Button>
699
- </div>
700
- </div>
701
- </div>
702
- )}
703
-
704
- {/* Edit Organization Modal */}
705
- {showEditModal && selectedOrganization && (
706
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
707
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
708
- <div className="flex items-center justify-between mb-4">
709
- <h3 className="text-lg text-white font-light">Edit Organization</h3>
710
- <Button
711
- variant="ghost"
712
- size="sm"
713
- onClick={() => setShowEditModal(false)}
714
- className="text-gray-400 hover:text-white rounded-none"
715
- >
716
- <X className="w-4 h-4" />
717
- </Button>
718
- </div>
719
- <div className="space-y-4">
720
- <div className="flex items-center space-x-3">
721
- <div className="w-16 h-16 rounded-none border border-dashed border-white/20 bg-white/10 flex items-center justify-center">
722
- <Building2 className="w-8 h-8 text-white" />
723
- </div>
724
- <div>
725
- <div className="text-white font-light">{selectedOrganization.name}</div>
726
- <div className="text-sm text-gray-400">{selectedOrganization.slug}</div>
727
- </div>
728
- </div>
729
- <div>
730
- <Label htmlFor="edit-name" className="text-sm text-gray-400 font-light">Name</Label>
731
- <Input
732
- id="edit-name"
733
- value={editFormData.name}
734
- onChange={(e) => handleEditNameChange(e.target.value)}
735
- placeholder="e.g. Acme Corp"
736
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
737
- />
738
- </div>
739
- <div>
740
- <Label htmlFor="edit-slug" className="text-sm text-gray-400 font-light">Slug</Label>
741
- <Input
742
- id="edit-slug"
743
- value={editFormData.slug}
744
- onChange={(e) => handleEditSlugChange(e.target.value)}
745
- placeholder="e.g. acme-corp"
746
- className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
747
- />
748
- <p className="text-xs text-gray-500 mt-1">
749
- Auto-generated from name. You can edit it manually.
750
- </p>
751
- </div>
752
- </div>
753
- <div className="flex justify-end space-x-3 mt-6">
754
- <Button
755
- variant="outline"
756
- onClick={() => {
757
- setShowEditModal(false)
758
- setEditFormData({ name: '', slug: '' })
759
- }}
760
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
761
- >
762
- Cancel
763
- </Button>
764
- <Button
765
- onClick={handleUpdateOrganization}
766
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
767
- >
768
- Update
769
- </Button>
770
- </div>
771
- </div>
772
- </div>
773
- )}
774
-
775
- {/* Delete Organization Modal */}
776
- {showDeleteModal && selectedOrganization && (
777
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
778
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
779
- <div className="flex items-center justify-between mb-4">
780
- <h3 className="text-lg text-white font-light">Delete Organization</h3>
781
- <Button
782
- variant="ghost"
783
- size="sm"
784
- onClick={() => setShowDeleteModal(false)}
785
- className="text-gray-400 hover:text-white rounded-none"
786
- >
787
- <X className="w-4 h-4" />
788
- </Button>
789
- </div>
790
- <div className="space-y-4">
791
- <div className="flex items-center space-x-3">
792
- <div className="w-16 h-16 rounded-none border border-dashed border-white/20 bg-white/10 flex items-center justify-center">
793
- <Building2 className="w-8 h-8 text-white" />
794
- </div>
795
- <div>
796
- <div className="text-white font-light">{selectedOrganization.name}</div>
797
- <div className="text-sm text-gray-400">{selectedOrganization.slug}</div>
798
- </div>
799
- </div>
800
- <p className="text-gray-400">Are you sure you want to delete this organization? This action cannot be undone.</p>
801
- </div>
802
- <div className="flex justify-end space-x-3 mt-6">
803
- <Button
804
- variant="outline"
805
- onClick={() => setShowDeleteModal(false)}
806
- className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
807
- >
808
- Cancel
809
- </Button>
810
- <Button
811
- onClick={handleDeleteOrganization}
812
- className="bg-red-600 hover:bg-red-700 text-white border border-red-600 rounded-none"
813
- >
814
- Delete
815
- </Button>
816
- </div>
817
- </div>
818
- </div>
819
- )}
820
-
821
- {/* View Organization Modal */}
822
- {showViewModal && selectedOrganization && (
823
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
824
- <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
825
- <div className="flex items-center justify-between mb-4">
826
- <h3 className="text-lg text-white font-light">Organization Details</h3>
827
- <Button
828
- variant="ghost"
829
- size="sm"
830
- onClick={() => setShowViewModal(false)}
831
- className="text-gray-400 hover:text-white rounded-none"
832
- >
833
- <X className="w-4 h-4" />
834
- </Button>
835
- </div>
836
- <div className="space-y-4">
837
- <div className="flex items-center space-x-3">
838
- <div className="w-16 h-16 rounded-none border border-dashed border-white/20 bg-white/10 flex items-center justify-center">
839
- <Building2 className="w-8 h-8 text-white" />
840
- </div>
841
- <div>
842
- <div className="text-white font-light">{selectedOrganization.name}</div>
843
- <div className="text-sm text-gray-400">{selectedOrganization.slug}</div>
844
- </div>
845
- </div>
846
- <div className="space-y-2">
847
- <div className="flex justify-between">
848
- <span className="text-gray-400">ID:</span>
849
- <span className="text-white text-sm">{selectedOrganization.id}</span>
850
- </div>
851
- <div className="flex justify-between">
852
- <span className="text-gray-400">Created:</span>
853
- <span className="text-white text-sm">{new Date(selectedOrganization.createdAt).toLocaleString()}</span>
854
- </div>
855
- <div className="flex justify-between">
856
- <span className="text-gray-400">Updated:</span>
857
- <span className="text-white text-sm">{new Date(selectedOrganization.updatedAt).toLocaleString()}</span>
858
- </div>
859
- </div>
860
- </div>
861
- <div className="flex justify-end mt-6">
862
- <Button
863
- onClick={() => setShowViewModal(false)}
864
- className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
865
- >
866
- Close
867
- </Button>
868
- </div>
869
- </div>
870
- </div>
871
- )}
872
- </div>
873
- )
874
- }