better-auth-studio 1.0.0

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 (72) hide show
  1. package/README.md +1 -0
  2. package/dist/auth-adapter.d.ts +24 -0
  3. package/dist/auth-adapter.d.ts.map +1 -0
  4. package/dist/auth-adapter.js +481 -0
  5. package/dist/auth-adapter.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +49 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +25 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +308 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/data.d.ts +38 -0
  15. package/dist/data.d.ts.map +1 -0
  16. package/dist/data.js +275 -0
  17. package/dist/data.js.map +1 -0
  18. package/dist/routes.d.ts +3 -0
  19. package/dist/routes.d.ts.map +1 -0
  20. package/dist/routes.js +1490 -0
  21. package/dist/routes.js.map +1 -0
  22. package/dist/studio.d.ts +10 -0
  23. package/dist/studio.d.ts.map +1 -0
  24. package/dist/studio.js +70 -0
  25. package/dist/studio.js.map +1 -0
  26. package/frontend/index.html +13 -0
  27. package/frontend/package-lock.json +4675 -0
  28. package/frontend/package.json +52 -0
  29. package/frontend/pnpm-lock.yaml +4020 -0
  30. package/frontend/postcss.config.js +6 -0
  31. package/frontend/src/App.tsx +36 -0
  32. package/frontend/src/components/CommandPalette.tsx +219 -0
  33. package/frontend/src/components/Layout.tsx +159 -0
  34. package/frontend/src/components/ui/badge.tsx +40 -0
  35. package/frontend/src/components/ui/button.tsx +53 -0
  36. package/frontend/src/components/ui/card.tsx +78 -0
  37. package/frontend/src/components/ui/input.tsx +20 -0
  38. package/frontend/src/components/ui/label.tsx +19 -0
  39. package/frontend/src/components/ui/select.tsx +71 -0
  40. package/frontend/src/index.css +130 -0
  41. package/frontend/src/lib/utils.ts +6 -0
  42. package/frontend/src/main.tsx +10 -0
  43. package/frontend/src/pages/Dashboard.tsx +231 -0
  44. package/frontend/src/pages/OrganizationDetails.tsx +1281 -0
  45. package/frontend/src/pages/Organizations.tsx +874 -0
  46. package/frontend/src/pages/Sessions.tsx +623 -0
  47. package/frontend/src/pages/Settings.tsx +1019 -0
  48. package/frontend/src/pages/TeamDetails.tsx +666 -0
  49. package/frontend/src/pages/Users.tsx +728 -0
  50. package/frontend/tailwind.config.js +75 -0
  51. package/frontend/tsconfig.json +31 -0
  52. package/frontend/tsconfig.node.json +10 -0
  53. package/frontend/vite.config.ts +31 -0
  54. package/package.json +59 -0
  55. package/public/assets/main-C-TXCXVW.css +1 -0
  56. package/public/assets/main-CCzTTP3P.js +296 -0
  57. package/public/index.html +14 -0
  58. package/src/auth-adapter.ts +471 -0
  59. package/src/cli.ts +51 -0
  60. package/src/config.ts +318 -0
  61. package/src/data.ts +351 -0
  62. package/src/routes.ts +1585 -0
  63. package/src/studio.ts +86 -0
  64. package/test-project/README.md +0 -0
  65. package/test-project/better-auth.db +0 -0
  66. package/test-project/better-auth_migrations/2025-08-27T15-55-04.099Z.sql +7 -0
  67. package/test-project/better-auth_migrations/2025-09-04T02-33-19.422Z.sql +7 -0
  68. package/test-project/package.json +29 -0
  69. package/test-project/pnpm-lock.yaml +1728 -0
  70. package/test-project/src/auth.ts +47 -0
  71. package/test-project/src/index.ts +40 -0
  72. package/tsconfig.json +21 -0
@@ -0,0 +1,1281 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { useParams, Link, useNavigate } from 'react-router-dom'
3
+ import { toast } from 'sonner'
4
+ import {
5
+ Building2,
6
+ ArrowLeft,
7
+ Edit,
8
+ Users,
9
+ Calendar,
10
+ UserPlus,
11
+ Trash2,
12
+ Eye,
13
+ Mail,
14
+ X,
15
+ Send,
16
+ Clock,
17
+ CheckCircle
18
+ } from 'lucide-react'
19
+ import { Button } from '../components/ui/button'
20
+ import { Badge } from '../components/ui/badge'
21
+ import { Input } from '../components/ui/input'
22
+ import { Label } from '../components/ui/label'
23
+
24
+ interface Organization {
25
+ id: string
26
+ name: string
27
+ slug: string
28
+ metadata?: any
29
+ createdAt: string
30
+ updatedAt: string
31
+ }
32
+
33
+ interface Team {
34
+ id: string
35
+ name: string
36
+ organizationId: string
37
+ metadata?: any
38
+ createdAt: string
39
+ updatedAt: string
40
+ memberCount?: number
41
+ }
42
+
43
+ interface Invitation {
44
+ id: string
45
+ email: string
46
+ role: string
47
+ status: 'pending' | 'accepted' | 'expired'
48
+ organizationId: string
49
+ teamId?: string
50
+ inviterId: string
51
+ expiresAt: string
52
+ createdAt: string
53
+ }
54
+
55
+ interface Member {
56
+ id: string
57
+ userId: string
58
+ organizationId: string
59
+ role: string
60
+ joinedAt: string
61
+ user: {
62
+ id: string
63
+ name: string
64
+ email: string
65
+ image?: string
66
+ emailVerified: boolean
67
+ }
68
+ }
69
+
70
+ // interface TeamMember {
71
+ // id: string
72
+ // userId: string
73
+ // teamId: string
74
+ // role: string
75
+ // joinedAt: string
76
+ // user: {
77
+ // id: string
78
+ // name: string
79
+ // email: string
80
+ // image?: string
81
+ // }
82
+ // }
83
+
84
+ export default function OrganizationDetails() {
85
+ const { orgId } = useParams<{ orgId: string }>()
86
+ const navigate = useNavigate()
87
+ const [organization, setOrganization] = useState<Organization | null>(null)
88
+ const [teams, setTeams] = useState<Team[]>([])
89
+ const [invitations, setInvitations] = useState<Invitation[]>([])
90
+ const [members, setMembers] = useState<Member[]>([])
91
+ const [loading, setLoading] = useState(true)
92
+ const [activeTab, setActiveTab] = useState<'details' | 'members' | 'invitations' | 'teams'>('details')
93
+ const [teamsEnabled, setTeamsEnabled] = useState(false)
94
+
95
+ // Modal states
96
+ const [showInviteModal, setShowInviteModal] = useState(false)
97
+ const [showSeedMembersModal, setShowSeedMembersModal] = useState(false)
98
+ const [showCreateTeamModal, setShowCreateTeamModal] = useState(false)
99
+ const [showEditTeamModal, setShowEditTeamModal] = useState(false)
100
+ const [showDeleteTeamModal, setShowDeleteTeamModal] = useState(false)
101
+ const [selectedTeam, setSelectedTeam] = useState<Team | null>(null)
102
+
103
+ // Form states
104
+ const [inviteEmail, setInviteEmail] = useState('')
105
+ const [teamFormData, setTeamFormData] = useState({ name: '' })
106
+ const [seedingLogs, setSeedingLogs] = useState<string[]>([])
107
+
108
+ useEffect(() => {
109
+ if (orgId) {
110
+ fetchOrganization()
111
+ checkTeamsEnabled()
112
+ fetchInvitations()
113
+ fetchMembers()
114
+ fetchTeams()
115
+ }
116
+ }, [orgId])
117
+
118
+ useEffect(() => {
119
+ if (teamsEnabled && orgId) {
120
+ fetchTeams()
121
+ }
122
+ }, [])
123
+
124
+ useEffect(() => {
125
+ if (activeTab === 'members' && orgId) {
126
+ fetchMembers()
127
+ }
128
+ }, [activeTab, orgId])
129
+
130
+ useEffect(() => {
131
+ if (activeTab === 'invitations' && orgId) {
132
+ fetchInvitations()
133
+ }
134
+ }, [activeTab, orgId])
135
+
136
+ const handleTeamNameChange = (name: string) => {
137
+ setTeamFormData({ name })
138
+ }
139
+
140
+ const fetchOrganization = async () => {
141
+ try {
142
+ const response = await fetch(`/api/organizations/${orgId}`)
143
+ const data = await response.json()
144
+
145
+ if (data.success) {
146
+ setOrganization(data.organization)
147
+ } else {
148
+ toast.error('Organization not found')
149
+ }
150
+ } catch (error) {
151
+ console.error('Failed to fetch organization:', error)
152
+ toast.error('Failed to load organization')
153
+ } finally {
154
+ setLoading(false)
155
+ }
156
+ }
157
+
158
+ const checkTeamsEnabled = async () => {
159
+ try {
160
+ const response = await fetch('/api/plugins/teams/status')
161
+ const data = await response.json()
162
+ setTeamsEnabled(data.enabled)
163
+ } catch (error) {
164
+ console.error('Failed to check teams status:', error)
165
+ setTeamsEnabled(false)
166
+ }
167
+ }
168
+
169
+ const fetchTeams = async () => {
170
+ try {
171
+ const response = await fetch(`/api/organizations/${orgId}/teams`)
172
+ const data = await response.json()
173
+
174
+ if (data.success) {
175
+ setTeams(data.teams || [])
176
+ }
177
+ } catch (error) {
178
+ console.error('Failed to fetch teams:', error)
179
+ toast.error('Failed to load teams')
180
+ }
181
+ }
182
+
183
+ const fetchInvitations = async () => {
184
+ try {
185
+ const response = await fetch(`/api/organizations/${orgId}/invitations`)
186
+ const data = await response.json()
187
+
188
+ if (data.success) {
189
+ setInvitations(data.invitations || [])
190
+ }
191
+ } catch (error) {
192
+ console.error('Failed to fetch invitations:', error)
193
+ toast.error('Failed to load invitations')
194
+ }
195
+ }
196
+
197
+ const fetchMembers = async () => {
198
+ try {
199
+ const response = await fetch(`/api/organizations/${orgId}/members`)
200
+ const data = await response.json()
201
+
202
+ if (data.success) {
203
+ setMembers(data.members || [])
204
+ }
205
+ } catch (error) {
206
+ console.error('Failed to fetch members:', error)
207
+ toast.error('Failed to load members')
208
+ }
209
+ }
210
+
211
+ const handleSeedMembers = async (count: number) => {
212
+ setSeedingLogs([])
213
+
214
+ try {
215
+ const response = await fetch(`/api/organizations/${orgId}/seed-members`, {
216
+ method: 'POST',
217
+ headers: { 'Content-Type': 'application/json' },
218
+ body: JSON.stringify({ count })
219
+ })
220
+
221
+ const result = await response.json()
222
+
223
+ if (result.success) {
224
+ setSeedingLogs(result.results.map((r: any) =>
225
+ r.success
226
+ ? `✅ Added member: ${r.member.user.name} (${r.member.user.email})`
227
+ : `❌ Failed: ${r.error}`
228
+ ))
229
+ await fetchMembers()
230
+ toast.success(`Successfully added ${result.results.filter((r: any) => r.success).length} members!`)
231
+ } else {
232
+ setSeedingLogs([`❌ Error: ${result.error || 'Failed to seed members'}`])
233
+ toast.error(result.error || 'Failed to seed members')
234
+ }
235
+ } catch (error) {
236
+ setSeedingLogs([`❌ Error: ${error}`])
237
+ }
238
+ }
239
+
240
+ const handleInviteUser = async () => {
241
+ if (!inviteEmail) {
242
+ toast.error('Please enter an email address')
243
+ return
244
+ }
245
+
246
+ const toastId = toast.loading('Sending invitation...')
247
+
248
+ try {
249
+ const response = await fetch(`/api/organizations/${orgId}/invitations`, {
250
+ method: 'POST',
251
+ headers: { 'Content-Type': 'application/json' },
252
+ body: JSON.stringify({
253
+ email: inviteEmail,
254
+ role: 'member'
255
+ })
256
+ })
257
+
258
+ const result = await response.json()
259
+
260
+ if (result.success) {
261
+ await fetchInvitations()
262
+ setShowInviteModal(false)
263
+ setInviteEmail('')
264
+ toast.success('Invitation sent successfully!', { id: toastId })
265
+ } else {
266
+ toast.error(`Error sending invitation: ${result.error || 'Unknown error'}`, { id: toastId })
267
+ }
268
+ } catch (error) {
269
+ console.error('Error sending invitation:', error)
270
+ toast.error('Error sending invitation', { id: toastId })
271
+ }
272
+ }
273
+
274
+ const handleCancelInvitation = async (invitationId: string) => {
275
+ const toastId = toast.loading('Cancelling invitation...')
276
+
277
+ try {
278
+ const response = await fetch(`/api/invitations/${invitationId}`, {
279
+ method: 'DELETE',
280
+ headers: { 'Content-Type': 'application/json' }
281
+ })
282
+
283
+ const result = await response.json()
284
+
285
+ if (result.success) {
286
+ await fetchInvitations()
287
+ toast.success('Invitation cancelled successfully!', { id: toastId })
288
+ } else {
289
+ toast.error(`Error cancelling invitation: ${result.error || 'Unknown error'}`, { id: toastId })
290
+ }
291
+ } catch (error) {
292
+ console.error('Error cancelling invitation:', error)
293
+ toast.error('Error cancelling invitation', { id: toastId })
294
+ }
295
+ }
296
+
297
+ const handleResendInvitation = async (invitationId: string, email: string) => {
298
+ const toastId = toast.loading('Resending invitation...')
299
+
300
+ try {
301
+ const response = await fetch(`/api/invitations/${invitationId}/resend`, {
302
+ method: 'POST',
303
+ headers: { 'Content-Type': 'application/json' }
304
+ })
305
+
306
+ const result = await response.json()
307
+
308
+ if (result.success) {
309
+ await fetchInvitations()
310
+ toast.success(`Invitation resent to ${email}!`, { id: toastId })
311
+ } else {
312
+ toast.error(`Error resending invitation: ${result.error || 'Unknown error'}`, { id: toastId })
313
+ }
314
+ } catch (error) {
315
+ console.error('Error resending invitation:', error)
316
+ toast.error('Error resending invitation', { id: toastId })
317
+ }
318
+ }
319
+
320
+ const handleRemoveMember = async (memberId: string, userName: string) => {
321
+ const toastId = toast.loading(`Removing ${userName}...`)
322
+
323
+ try {
324
+ const response = await fetch(`/api/members/${memberId}`, {
325
+ method: 'DELETE',
326
+ headers: { 'Content-Type': 'application/json' }
327
+ })
328
+
329
+ const result = await response.json()
330
+
331
+ if (result.success) {
332
+ await fetchMembers()
333
+ toast.success(`${userName} removed from organization!`, { id: toastId })
334
+ } else {
335
+ toast.error(`Error removing member: ${result.error || 'Unknown error'}`, { id: toastId })
336
+ }
337
+ } catch (error) {
338
+ console.error('Error removing member:', error)
339
+ toast.error('Error removing member', { id: toastId })
340
+ }
341
+ }
342
+
343
+ const handleCreateTeam = async () => {
344
+ if (!teamFormData.name) {
345
+ toast.error('Please enter a team name')
346
+ return
347
+ }
348
+
349
+ const toastId = toast.loading('Creating team...')
350
+
351
+ try {
352
+ const response = await fetch(`/api/organizations/${orgId}/teams`, {
353
+ method: 'POST',
354
+ headers: { 'Content-Type': 'application/json' },
355
+ body: JSON.stringify({
356
+ name: teamFormData.name,
357
+ organizationId: orgId
358
+ })
359
+ })
360
+
361
+ const result = await response.json()
362
+
363
+ if (result.success) {
364
+ await fetchTeams()
365
+ setShowCreateTeamModal(false)
366
+ setTeamFormData({ name: '' })
367
+ toast.success('Team created successfully!', { id: toastId })
368
+ } else {
369
+ toast.error(`Error creating team: ${result.error || 'Unknown error'}`, { id: toastId })
370
+ }
371
+ } catch (error) {
372
+ console.error('Error creating team:', error)
373
+ toast.error('Error creating team', { id: toastId })
374
+ }
375
+ }
376
+
377
+ const handleUpdateTeam = async () => {
378
+ if (!selectedTeam || !teamFormData.name) {
379
+ toast.error('Please enter a team name')
380
+ return
381
+ }
382
+
383
+ const toastId = toast.loading('Updating team...')
384
+
385
+ try {
386
+ const response = await fetch(`/api/teams/${selectedTeam.id}`, {
387
+ method: 'PUT',
388
+ headers: { 'Content-Type': 'application/json' },
389
+ body: JSON.stringify({
390
+ name: teamFormData.name
391
+ })
392
+ })
393
+
394
+ const result = await response.json()
395
+
396
+ if (result.success) {
397
+ await fetchTeams()
398
+ setShowEditTeamModal(false)
399
+ setSelectedTeam(null)
400
+ setTeamFormData({ name: '' })
401
+ toast.success('Team updated successfully!', { id: toastId })
402
+ } else {
403
+ toast.error(`Error updating team: ${result.error || 'Unknown error'}`, { id: toastId })
404
+ }
405
+ } catch (error) {
406
+ console.error('Error updating team:', error)
407
+ toast.error('Error updating team', { id: toastId })
408
+ }
409
+ }
410
+
411
+ const handleDeleteTeam = async () => {
412
+ if (!selectedTeam) {
413
+ toast.error('No team selected')
414
+ return
415
+ }
416
+
417
+ const toastId = toast.loading('Deleting team...')
418
+
419
+ try {
420
+ const response = await fetch(`/api/teams/${selectedTeam.id}`, {
421
+ method: 'DELETE',
422
+ headers: { 'Content-Type': 'application/json' }
423
+ })
424
+
425
+ const result = await response.json()
426
+
427
+ if (result.success) {
428
+ await fetchTeams()
429
+ setShowDeleteTeamModal(false)
430
+ setSelectedTeam(null)
431
+ toast.success('Team deleted successfully!', { id: toastId })
432
+ } else {
433
+ toast.error(`Error deleting team: ${result.error || 'Unknown error'}`, { id: toastId })
434
+ }
435
+ } catch (error) {
436
+ console.error('Error deleting team:', error)
437
+ toast.error('Error deleting team', { id: toastId })
438
+ }
439
+ }
440
+
441
+ const openEditTeamModal = (team: Team) => {
442
+ setSelectedTeam(team)
443
+ setTeamFormData({ name: team.name })
444
+ setShowEditTeamModal(true)
445
+ }
446
+
447
+ const openDeleteTeamModal = (team: Team) => {
448
+ setSelectedTeam(team)
449
+ setShowDeleteTeamModal(true)
450
+ }
451
+
452
+ if (loading) {
453
+ return (
454
+ <div className="flex items-center justify-center h-64">
455
+ <div className="text-white">Loading organization details...</div>
456
+ </div>
457
+ )
458
+ }
459
+
460
+ if (!organization) {
461
+ return (
462
+ <div className="space-y-6 p-6">
463
+ <div className="flex items-center space-x-4">
464
+ <Link to="/organizations">
465
+ <Button variant="ghost" className="text-gray-400 hover:text-white rounded-none">
466
+ <ArrowLeft className="w-4 h-4 mr-2" />
467
+ Back to Organizations
468
+ </Button>
469
+ </Link>
470
+ </div>
471
+ <br />
472
+ <div className="text-center py-12">
473
+ <Building2 className="w-16 h-16 text-gray-400 mx-auto mb-4" />
474
+ <h2 className="text-xl text-white font-light mb-2">Organization Not Found</h2>
475
+ <p className="text-gray-400">The organization you're looking for doesn't exist.</p>
476
+ </div>
477
+ </div>
478
+ )
479
+ }
480
+
481
+ return (
482
+ <div className="space-y-6 p-6">
483
+ {/* Header */}
484
+ <div className="flex items-center justify-between">
485
+ <div className="flex items-center space-x-4">
486
+ <Link to="/organizations">
487
+ <Button variant="ghost" className="text-gray-400 hover:text-white rounded-none">
488
+ <ArrowLeft className="w-4 h-4 mr-2" />
489
+ Back to Organizations
490
+ </Button>
491
+ </Link>
492
+
493
+ </div>
494
+
495
+ <div className="flex items-center space-x-3">
496
+ <Button
497
+ onClick={() => setShowSeedMembersModal(true)}
498
+ className="border border-dashed border-white/20 text-white bg-transparent hover:bg-white/10 rounded-none"
499
+ >
500
+ <Users className="w-4 h-4 mr-2" />
501
+ Seed Members
502
+ </Button>
503
+ <Button
504
+ onClick={() => setShowInviteModal(true)}
505
+ className="border border-dashed border-white/20 text-white bg-transparent hover:bg-white/10 rounded-none"
506
+ >
507
+ <Mail className="w-4 h-4 mr-2" />
508
+ Invite User
509
+ </Button>
510
+ <Button className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none">
511
+ <Edit className="w-4 h-4 mr-2" />
512
+ Edit Organization
513
+ </Button>
514
+ </div>
515
+ </div>
516
+ <div className="flex items-center space-x-3">
517
+ <div className="w-12 h-12 bg-white/10 border border-dashed border-white/20 rounded-none flex items-center justify-center">
518
+ <Building2 className="w-6 h-6 text-white" />
519
+ </div>
520
+ <div>
521
+ <h1 className="text-2xl text-white font-light">{organization.name}</h1>
522
+ <p className="text-gray-400">{organization.slug}</p>
523
+ </div>
524
+ </div>
525
+
526
+ {/* Tabs */}
527
+ <div className="border-b border-white/10">
528
+ <nav className="flex space-x-8">
529
+ <button
530
+ onClick={() => setActiveTab('details')}
531
+ className={`flex items-center space-x-2 px-3 py-4 text-sm font-medium border-b-2 transition-all duration-200 ${activeTab === 'details'
532
+ ? 'border-white text-white'
533
+ : 'border-transparent text-gray-400 hover:text-white hover:border-gray-300'
534
+ }`}
535
+ >
536
+ <Building2 className="w-4 h-4" />
537
+ <span>Details</span>
538
+ <Badge variant="secondary" className="text-xs bg-white/10 border border-white/20 rounded-sm">
539
+ {members.length + invitations.length + teams.length}
540
+ </Badge>
541
+ </button>
542
+ <button
543
+ onClick={() => setActiveTab('members')}
544
+ className={`flex items-center space-x-2 px-3 py-4 text-sm font-medium border-b-2 transition-all duration-200 ${activeTab === 'members'
545
+ ? 'border-white text-white'
546
+ : 'border-transparent text-gray-400 hover:text-white hover:border-gray-300'
547
+ }`}
548
+ >
549
+ <Users className="w-4 h-4" />
550
+ <span>Members</span>
551
+ {members.length > 0 && (
552
+ <Badge variant="secondary" className="text-xs bg-white/10 border border-white/20 rounded-sm">
553
+ {members.length}
554
+ </Badge>
555
+ )}
556
+ </button>
557
+ <button
558
+ onClick={() => setActiveTab('invitations')}
559
+ className={`flex items-center space-x-2 px-3 py-4 text-sm font-medium border-b-2 transition-all duration-200 ${activeTab === 'invitations'
560
+ ? 'border-white text-white'
561
+ : 'border-transparent text-gray-400 hover:text-white hover:border-gray-300'
562
+ }`}
563
+ >
564
+ <Mail className="w-4 h-4" />
565
+ <span>Invitations</span>
566
+ {invitations.length > 0 && (
567
+ <Badge variant="secondary" className="text-xs bg-white/10 border border-white/20 rounded-sm">
568
+ {invitations.length}
569
+ </Badge>
570
+ )}
571
+ </button>
572
+ <button
573
+ onClick={() => setActiveTab('teams')}
574
+ className={`flex items-center space-x-2 px-3 py-4 text-sm font-medium border-b-2 transition-all duration-200 ${activeTab === 'teams'
575
+ ? 'border-white text-white'
576
+ : 'border-transparent text-gray-400 hover:text-white hover:border-gray-300'
577
+ }`}
578
+ >
579
+ <Users className="w-4 h-4" />
580
+ <span>Teams</span>
581
+ {teams.length > 0 && (
582
+ <Badge variant="secondary" className="text-xs bg-white/10 border border-white/20 rounded-sm">
583
+ {teams.length}
584
+ </Badge>
585
+ )}
586
+ </button>
587
+ </nav>
588
+ </div>
589
+
590
+ {/* Tab Content */}
591
+ {activeTab === 'details' && (
592
+ <div className="space-y-6">
593
+ {/* Organization Information */}
594
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-6">
595
+ <h3 className="text-lg text-white font-light mb-4">Organization Information</h3>
596
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
597
+ <div>
598
+ <label className="text-sm text-gray-400 font-light">Name</label>
599
+ <p className="text-white mt-1">{organization.name}</p>
600
+ </div>
601
+ <div>
602
+ <label className="text-sm text-gray-400 font-light">Slug</label>
603
+ <p className="text-white mt-1">{organization.slug}</p>
604
+ </div>
605
+ <div>
606
+ <label className="text-sm text-gray-400 font-light">Created</label>
607
+ <p className="text-white mt-1">
608
+ {new Date(organization.createdAt).toLocaleDateString('en-US', {
609
+ year: 'numeric',
610
+ month: 'long',
611
+ day: 'numeric'
612
+ })}
613
+ </p>
614
+ </div>
615
+ <div>
616
+ <label className="text-sm text-gray-400 font-light">Last Updated</label>
617
+ <p className="text-white mt-1">
618
+ {new Date(organization.updatedAt).toLocaleDateString('en-US', {
619
+ year: 'numeric',
620
+ month: 'long',
621
+ day: 'numeric'
622
+ })}
623
+ </p>
624
+ </div>
625
+ </div>
626
+ </div>
627
+
628
+ {/* Organization Stats */}
629
+ <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
630
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-6">
631
+ <div className="flex items-center space-x-3">
632
+ <Users className="w-8 h-8 text-white" />
633
+ <div>
634
+ <p className="text-2xl text-white font-light">{members.length}</p>
635
+ <p className="text-sm text-gray-400">Members</p>
636
+ </div>
637
+ </div>
638
+ </div>
639
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-6">
640
+ <div className="flex items-center space-x-3">
641
+ <Users className="w-8 h-8 text-white" />
642
+ <div>
643
+ <p className="text-2xl text-white font-light">{teams.length}</p>
644
+ <p className="text-sm text-gray-400">Teams</p>
645
+ </div>
646
+ </div>
647
+ </div>
648
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-6">
649
+ <div className="flex items-center space-x-3">
650
+ <Mail className="w-8 h-8 text-white" />
651
+ <div>
652
+ <p className="text-2xl text-white font-light">{invitations.length}</p>
653
+ <p className="text-sm text-gray-400">Invitations</p>
654
+ </div>
655
+ </div>
656
+ </div>
657
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-6">
658
+ <div className="flex items-center space-x-3">
659
+ <Calendar className="w-8 h-8 text-white" />
660
+ <div>
661
+ <p className="text-2xl text-white font-light">
662
+ {Math.ceil((new Date().getTime() - new Date(organization.createdAt).getTime()) / (1000 * 60 * 60 * 24))}
663
+ </p>
664
+ <p className="text-sm text-gray-400">Days Active</p>
665
+ </div>
666
+ </div>
667
+ </div>
668
+ </div>
669
+ </div>
670
+ )}
671
+
672
+ {activeTab === 'teams' && (
673
+ <div className="space-y-6">
674
+ {/* Teams Header */}
675
+ <div className="flex items-center justify-between">
676
+ <div>
677
+ <h3 className="text-lg text-white font-light">Teams ({teams.length})</h3>
678
+ <p className="text-gray-400 mt-1">Manage teams within this organization</p>
679
+ </div>
680
+ <Button
681
+ onClick={() => setShowCreateTeamModal(true)}
682
+ className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
683
+ >
684
+ <UserPlus className="w-4 h-4 mr-2" />
685
+ Create Team
686
+ </Button>
687
+ </div>
688
+
689
+ {/* Teams List */}
690
+ {teams.length > 0 ? (
691
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none">
692
+ <div className="overflow-x-auto">
693
+ <table className="w-full">
694
+ <thead>
695
+ <tr className="border-b border-dashed border-white/10">
696
+ <th className="text-left py-4 px-4 text-white font-light">Team</th>
697
+ <th className="text-left py-4 px-4 text-white font-light">Members</th>
698
+ <th className="text-left py-4 px-4 text-white font-light">Created</th>
699
+ <th className="text-right py-4 px-4 text-white font-light">Actions</th>
700
+ </tr>
701
+ </thead>
702
+ <tbody>
703
+ {teams.map((team) => (
704
+ <tr key={team.id} className="border-b border-dashed border-white/5 hover:bg-white/5">
705
+ <td className="py-4 px-4">
706
+ <div className="flex items-center space-x-3">
707
+ <div className="w-10 h-10 bg-white/10 border border-dashed border-white/20 rounded-none flex items-center justify-center">
708
+ <Users className="w-5 h-5 text-white" />
709
+ </div>
710
+ <div>
711
+ <div className="text-white font-light">{team.name}</div>
712
+ <div className="text-sm text-gray-400">Team ID: {team.id}</div>
713
+ </div>
714
+ </div>
715
+ </td>
716
+ <td className="py-4 px-4 text-white">{team.memberCount || 0}</td>
717
+ <td className="py-4 px-4 text-sm text-gray-400">
718
+ {new Date(team.createdAt).toLocaleDateString()}
719
+ </td>
720
+ <td className="py-4 px-4 text-right">
721
+ <div className="flex items-center justify-end space-x-2">
722
+ <Button
723
+ variant="outline"
724
+ size="sm"
725
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
726
+ onClick={() => navigate(`/teams/${team.id}`)}
727
+ >
728
+ <Eye className="w-4 h-4 mr-1" />
729
+ View
730
+ </Button>
731
+ <Button
732
+ variant="outline"
733
+ size="sm"
734
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
735
+ onClick={() => openEditTeamModal(team)}
736
+ >
737
+ <Edit className="w-4 h-4 mr-1" />
738
+ Edit
739
+ </Button>
740
+ <Button
741
+ variant="outline"
742
+ size="sm"
743
+ className="border border-dashed border-red-400/50 text-red-400 hover:bg-red-400/10 rounded-none"
744
+ onClick={() => openDeleteTeamModal(team)}
745
+ >
746
+ <Trash2 className="w-4 h-4 mr-1" />
747
+ Delete
748
+ </Button>
749
+ </div>
750
+ </td>
751
+ </tr>
752
+ ))}
753
+ </tbody>
754
+ </table>
755
+ </div>
756
+ </div>
757
+ ) : (
758
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-12">
759
+ <div className="text-center">
760
+ <Users className="w-16 h-16 text-gray-400 mx-auto mb-4" />
761
+ <h3 className="text-xl text-white font-light mb-2">No Teams Yet</h3>
762
+ <p className="text-gray-400 mb-6">
763
+ Create your first team to start organizing members within this organization.
764
+ </p>
765
+ <Button
766
+ onClick={() => setShowCreateTeamModal(true)}
767
+ className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
768
+ >
769
+ <UserPlus className="w-4 h-4 mr-2" />
770
+ Create First Team
771
+ </Button>
772
+ </div>
773
+ </div>
774
+ )}
775
+ </div>
776
+ )}
777
+
778
+ {activeTab === 'members' && (
779
+ <div className="space-y-6">
780
+ {/* Members Header */}
781
+ <div className="flex items-center justify-between">
782
+ <div>
783
+ <h3 className="text-lg text-white font-light">Members ({members.length})</h3>
784
+ <p className="text-gray-400 mt-1">Manage organization members and their roles</p>
785
+ </div>
786
+ </div>
787
+
788
+ {/* Members List */}
789
+ {members.length > 0 ? (
790
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none">
791
+ <div className="overflow-x-auto">
792
+ <table className="w-full">
793
+ <thead>
794
+ <tr className="border-b border-dashed border-white/10">
795
+ <th className="text-left py-4 px-4 text-white font-light">User</th>
796
+ <th className="text-left py-4 px-4 text-white font-light">Email</th>
797
+ <th className="text-left py-4 px-4 text-white font-light">Role</th>
798
+ <th className="text-left py-4 px-4 text-white font-light">Joined</th>
799
+ <th className="text-right py-4 px-4 text-white font-light">Actions</th>
800
+ </tr>
801
+ </thead>
802
+ <tbody>
803
+ {members.map((member) => (
804
+ <tr key={member.id} className="border-b border-dashed border-white/5 hover:bg-white/5">
805
+ <td className="py-4 px-4">
806
+ <div className="flex items-center space-x-3">
807
+ <img
808
+ src={member.user.image || `https://api.dicebear.com/7.x/avataaars/svg?seed=${member.user.id}`}
809
+ alt={member.user.name}
810
+ className="w-10 h-10 rounded-none border border-dashed border-white/20"
811
+ />
812
+ <div>
813
+ <div className="text-white font-light">{member.user.name}</div>
814
+ <div className="text-sm text-gray-400">ID: {member.user.id}</div>
815
+ </div>
816
+ </div>
817
+ </td>
818
+ <td className="py-4 px-4 text-white">{member.user.email}</td>
819
+ <td className="py-4 px-4">
820
+ <Badge variant="secondary" className="text-xs bg-blue-900/10 border border-dashed rounded-none border-blue-500/30 text-blue-400/70 capitalize">
821
+ {member.role}
822
+ </Badge>
823
+ </td>
824
+ <td className="py-4 px-4 text-sm text-gray-400">
825
+ {new Date(member.joinedAt).toLocaleDateString()}
826
+ </td>
827
+ <td className="py-4 px-4 text-right">
828
+ <div className="flex items-center justify-end space-x-2">
829
+ <Button
830
+ variant="outline"
831
+ size="sm"
832
+ className="border border-dashed border-red-400/50 text-red-400 hover:bg-red-400/10 rounded-none"
833
+ onClick={() => handleRemoveMember(member.id, member.user.name)}
834
+ >
835
+ <Trash2 className="w-4 h-4 mr-1" />
836
+ Remove
837
+ </Button>
838
+ </div>
839
+ </td>
840
+ </tr>
841
+ ))}
842
+ </tbody>
843
+ </table>
844
+ </div>
845
+ </div>
846
+ ) : (
847
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-12">
848
+ <div className="text-center">
849
+ <Users className="w-16 h-16 text-gray-400 mx-auto mb-4" />
850
+ <h3 className="text-xl text-white font-light mb-2">No Members Yet</h3>
851
+ <p className="text-gray-400 mb-6">
852
+ Invite users or seed members from existing users to get started.
853
+ </p>
854
+ <div className="flex items-center justify-center space-x-3">
855
+ <Button
856
+ onClick={() => setShowSeedMembersModal(true)}
857
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 bg-transparent rounded-none"
858
+ >
859
+ <Users className="w-4 h-4 mr-2" />
860
+ Seed Members
861
+ </Button>
862
+ <Button
863
+ onClick={() => setShowInviteModal(true)}
864
+ className="bg-white hover:bg-white/90 bg-transparent text-black border border-white/20 rounded-none"
865
+ >
866
+ <Mail className="w-4 h-4 mr-2" />
867
+ Invite User
868
+ </Button>
869
+ </div>
870
+ </div>
871
+ </div>
872
+ )}
873
+ </div>
874
+ )}
875
+
876
+ {activeTab === 'invitations' && (
877
+ <div className="space-y-6">
878
+ {/* Invitations Header */}
879
+ <div className="flex items-center justify-between">
880
+ <div>
881
+ <h3 className="text-lg text-white font-light">Invitations ({invitations.length})</h3>
882
+ <p className="text-gray-400 mt-1">Manage pending invitations to this organization</p>
883
+ </div>
884
+ </div>
885
+
886
+ {/* Invitations List */}
887
+ {invitations.length > 0 ? (
888
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none">
889
+ <div className="overflow-x-auto">
890
+ <table className="w-full">
891
+ <thead>
892
+ <tr className="border-b border-dashed border-white/10">
893
+ <th className="text-left py-4 px-4 text-white font-light">Email</th>
894
+ <th className="text-left py-4 px-4 text-white font-light">Role</th>
895
+ <th className="text-left py-4 px-4 text-white font-light">Status</th>
896
+ <th className="text-left py-4 px-4 text-white font-light">Expires</th>
897
+ <th className="text-right py-4 px-4 text-white font-light">Actions</th>
898
+ </tr>
899
+ </thead>
900
+ <tbody>
901
+ {invitations.map((invitation) => (
902
+ <tr key={invitation.id} className="border-b border-dashed border-white/5 hover:bg-white/5">
903
+ <td className="py-4 px-4">
904
+ <div className="flex items-center space-x-3">
905
+ <div className="w-10 h-10 bg-white/10 border border-dashed border-white/20 rounded-none flex items-center justify-center">
906
+ <Mail className="w-5 h-5 text-white" />
907
+ </div>
908
+ <div>
909
+ <div className="text-white font-light">{invitation.email}</div>
910
+ <div className="text-sm text-gray-400">Invited {new Date(invitation.createdAt).toLocaleDateString()}</div>
911
+ </div>
912
+ </div>
913
+ </td>
914
+ <td className="py-4 px-4 text-white capitalize">{invitation.role}</td>
915
+ <td className="py-4 px-4">
916
+ <Badge
917
+ variant="secondary"
918
+ className={`text-xs font-normal rounded-none border-dashed flex items-center gap-1 w-fit ${
919
+ invitation.status === 'pending'
920
+ ? 'bg-yellow-900/10 border border-yellow-500/30 text-yellow-400/70'
921
+ : invitation.status === 'accepted'
922
+ ? 'bg-green-900/10 border border-green-500/30 text-green-400/70'
923
+ : 'bg-red-900/10 border border-red-500/30 text-red-400/70'
924
+ }`}
925
+ >
926
+ {invitation.status === 'pending' && <Clock className="w-3 h-3" />}
927
+ {invitation.status === 'accepted' && <CheckCircle className="w-3 h-3" />}
928
+ {invitation.status === 'expired' && <X className="w-3 h-3" />}
929
+ {invitation.status}
930
+ </Badge>
931
+ </td>
932
+ <td className="py-4 px-4 text-sm text-gray-400">
933
+ {new Date(invitation.expiresAt).toLocaleDateString()}
934
+ </td>
935
+ <td className="py-4 px-4 text-right">
936
+ <div className="flex items-center justify-end space-x-2">
937
+ <Button
938
+ variant="outline"
939
+ size="sm"
940
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
941
+ onClick={() => handleResendInvitation(invitation.id, invitation.email)}
942
+ >
943
+ <Send className="w-4 h-4 mr-1" />
944
+ Resend
945
+ </Button>
946
+ <Button
947
+ variant="outline"
948
+ size="sm"
949
+ className="border border-dashed border-red-400/50 text-red-400 hover:bg-red-400/10 rounded-none"
950
+ onClick={() => handleCancelInvitation(invitation.id)}
951
+ >
952
+ <Trash2 className="w-4 h-4 mr-1" />
953
+ Cancel
954
+ </Button>
955
+ </div>
956
+ </td>
957
+ </tr>
958
+ ))}
959
+ </tbody>
960
+ </table>
961
+ </div>
962
+ </div>
963
+ ) : (
964
+ <div className="bg-black/30 border border-dashed border-white/20 rounded-none p-12">
965
+ <div className="text-center">
966
+ <Mail className="w-16 h-16 text-gray-400 mx-auto mb-4" />
967
+ <h3 className="text-xl text-white font-light mb-2">No Invitations</h3>
968
+ <p className="text-gray-400 mb-6">
969
+ Start inviting users to join this organization.
970
+ </p>
971
+ <Button
972
+ onClick={() => setShowInviteModal(true)}
973
+ className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
974
+ >
975
+ <Mail className="w-4 h-4 mr-2" />
976
+ Send First Invitation
977
+ </Button>
978
+ </div>
979
+ </div>
980
+ )}
981
+ </div>
982
+ )}
983
+
984
+ {/* Invite User Modal */}
985
+ {showInviteModal && (
986
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
987
+ <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
988
+ <div className="flex items-center justify-between mb-4">
989
+ <h3 className="text-lg text-white font-light">Invite User</h3>
990
+ <Button
991
+ variant="ghost"
992
+ size="sm"
993
+ onClick={() => {
994
+ setShowInviteModal(false)
995
+ setInviteEmail('')
996
+ }}
997
+ className="text-gray-400 hover:text-white rounded-none"
998
+ >
999
+ <X className="w-4 h-4" />
1000
+ </Button>
1001
+ </div>
1002
+ <div className="space-y-4">
1003
+ <div>
1004
+ <Label htmlFor="invite-email" className="text-sm text-gray-400 font-light">Email Address</Label>
1005
+ <Input
1006
+ id="invite-email"
1007
+ type="email"
1008
+ value={inviteEmail}
1009
+ onChange={(e) => setInviteEmail(e.target.value)}
1010
+ placeholder="user@example.com"
1011
+ className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
1012
+ />
1013
+ </div>
1014
+ </div>
1015
+ <div className="flex justify-end space-x-3 mt-6">
1016
+ <Button
1017
+ variant="outline"
1018
+ onClick={() => {
1019
+ setShowInviteModal(false)
1020
+ setInviteEmail('')
1021
+ }}
1022
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
1023
+ >
1024
+ Cancel
1025
+ </Button>
1026
+ <Button
1027
+ onClick={handleInviteUser}
1028
+ className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
1029
+ >
1030
+ <Send className="w-4 h-4 mr-2" />
1031
+ Send Invitation
1032
+ </Button>
1033
+ </div>
1034
+ </div>
1035
+ </div>
1036
+ )}
1037
+
1038
+ {/* Create Team Modal */}
1039
+ {showCreateTeamModal && (
1040
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
1041
+ <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
1042
+ <div className="flex items-center justify-between mb-4">
1043
+ <h3 className="text-lg text-white font-light">Create Team</h3>
1044
+ <Button
1045
+ variant="ghost"
1046
+ size="sm"
1047
+ onClick={() => {
1048
+ setShowCreateTeamModal(false)
1049
+ setTeamFormData({ name: '' })
1050
+ }}
1051
+ className="text-gray-400 hover:text-white rounded-none"
1052
+ >
1053
+ <X className="w-4 h-4" />
1054
+ </Button>
1055
+ </div>
1056
+ <div className="space-y-4">
1057
+ <div>
1058
+ <Label htmlFor="team-name" className="text-sm text-gray-400 font-light">Team Name</Label>
1059
+ <Input
1060
+ id="team-name"
1061
+ value={teamFormData.name}
1062
+ onChange={(e) => handleTeamNameChange(e.target.value)}
1063
+ placeholder="e.g. Development Team"
1064
+ className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
1065
+ />
1066
+ </div>
1067
+ </div>
1068
+ <div className="flex justify-end space-x-3 mt-6">
1069
+ <Button
1070
+ variant="outline"
1071
+ onClick={() => {
1072
+ setShowCreateTeamModal(false)
1073
+ setTeamFormData({ name: '' })
1074
+ }}
1075
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
1076
+ >
1077
+ Cancel
1078
+ </Button>
1079
+ <Button
1080
+ onClick={handleCreateTeam}
1081
+ className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
1082
+ >
1083
+ Create Team
1084
+ </Button>
1085
+ </div>
1086
+ </div>
1087
+ </div>
1088
+ )}
1089
+
1090
+ {/* Edit Team Modal */}
1091
+ {showEditTeamModal && selectedTeam && (
1092
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
1093
+ <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
1094
+ <div className="flex items-center justify-between mb-4">
1095
+ <h3 className="text-lg text-white font-light">Edit Team</h3>
1096
+ <Button
1097
+ variant="ghost"
1098
+ size="sm"
1099
+ onClick={() => {
1100
+ setShowEditTeamModal(false)
1101
+ setTeamFormData({ name: '' })
1102
+ }}
1103
+ className="text-gray-400 hover:text-white rounded-none"
1104
+ >
1105
+ <X className="w-4 h-4" />
1106
+ </Button>
1107
+ </div>
1108
+ <div className="space-y-4">
1109
+ <div className="flex items-center space-x-3">
1110
+ <div className="w-16 h-16 bg-white/10 border border-dashed border-white/20 rounded-none flex items-center justify-center">
1111
+ <Users className="w-8 h-8 text-white" />
1112
+ </div>
1113
+ <div>
1114
+ <div className="text-white font-light">{selectedTeam.name}</div>
1115
+ <div className="text-sm text-gray-400">Team ID: {selectedTeam.id}</div>
1116
+ </div>
1117
+ </div>
1118
+ <div>
1119
+ <Label htmlFor="edit-team-name" className="text-sm text-gray-400 font-light">Team Name</Label>
1120
+ <Input
1121
+ id="edit-team-name"
1122
+ value={teamFormData.name}
1123
+ onChange={(e) => handleTeamNameChange(e.target.value)}
1124
+ placeholder="e.g. Development Team"
1125
+ className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
1126
+ />
1127
+ </div>
1128
+ </div>
1129
+ <div className="flex justify-end space-x-3 mt-6">
1130
+ <Button
1131
+ variant="outline"
1132
+ onClick={() => {
1133
+ setShowEditTeamModal(false)
1134
+ setTeamFormData({ name: '' })
1135
+ }}
1136
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
1137
+ >
1138
+ Cancel
1139
+ </Button>
1140
+ <Button
1141
+ onClick={handleUpdateTeam}
1142
+ className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none"
1143
+ >
1144
+ Update Team
1145
+ </Button>
1146
+ </div>
1147
+ </div>
1148
+ </div>
1149
+ )}
1150
+
1151
+ {/* Delete Team Modal */}
1152
+ {showDeleteTeamModal && selectedTeam && (
1153
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
1154
+ <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-md rounded-none">
1155
+ <div className="flex items-center justify-between mb-4">
1156
+ <h3 className="text-lg text-white font-light">Delete Team</h3>
1157
+ <Button
1158
+ variant="ghost"
1159
+ size="sm"
1160
+ onClick={() => setShowDeleteTeamModal(false)}
1161
+ className="text-gray-400 hover:text-white rounded-none"
1162
+ >
1163
+ <X className="w-4 h-4" />
1164
+ </Button>
1165
+ </div>
1166
+ <div className="space-y-4">
1167
+ <div className="flex items-center space-x-3">
1168
+ <div className="w-16 h-16 bg-white/10 border border-dashed border-white/20 rounded-none flex items-center justify-center">
1169
+ <Users className="w-8 h-8 text-white" />
1170
+ </div>
1171
+ <div>
1172
+ <div className="text-white font-light">{selectedTeam.name}</div>
1173
+ <div className="text-sm text-gray-400">Team ID: {selectedTeam.id}</div>
1174
+ </div>
1175
+ </div>
1176
+ <p className="text-gray-400">Are you sure you want to delete this team? This action cannot be undone.</p>
1177
+ </div>
1178
+ <div className="flex justify-end space-x-3 mt-6">
1179
+ <Button
1180
+ variant="outline"
1181
+ onClick={() => setShowDeleteTeamModal(false)}
1182
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
1183
+ >
1184
+ Cancel
1185
+ </Button>
1186
+ <Button
1187
+ onClick={handleDeleteTeam}
1188
+ className="bg-red-600 hover:bg-red-700 text-white border border-red-600 rounded-none"
1189
+ >
1190
+ Delete Team
1191
+ </Button>
1192
+ </div>
1193
+ </div>
1194
+ </div>
1195
+ )}
1196
+
1197
+ {/* Seed Members Modal */}
1198
+ {showSeedMembersModal && (
1199
+ <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
1200
+ <div className="bg-black/90 border border-dashed border-white/20 p-6 w-full max-w-lg rounded-none">
1201
+ <div className="flex items-center justify-between mb-6">
1202
+ <h3 className="text-lg text-white font-light">Seed Members</h3>
1203
+ <Button
1204
+ variant="ghost"
1205
+ size="sm"
1206
+ onClick={() => setShowSeedMembersModal(false)}
1207
+ className="text-gray-400 hover:text-white rounded-none"
1208
+ >
1209
+ <X className="w-4 h-4" />
1210
+ </Button>
1211
+ </div>
1212
+ <div className="space-y-6">
1213
+ <div className="space-y-4">
1214
+ <div className="flex items-center space-x-2">
1215
+ <Users className="w-5 h-5 text-white" />
1216
+ <h4 className="text-white font-light">Add Members from Existing Users</h4>
1217
+ </div>
1218
+ <div className="flex items-center space-x-3">
1219
+ <div className="flex-1">
1220
+ <Label htmlFor="member-count" className="text-sm text-gray-400 font-light">Number of members to add</Label>
1221
+ <Input
1222
+ id="member-count"
1223
+ type="number"
1224
+ min="1"
1225
+ max="50"
1226
+ defaultValue="5"
1227
+ className="mt-1 border border-dashed border-white/20 bg-black/30 text-white rounded-none"
1228
+ />
1229
+ </div>
1230
+ <Button
1231
+ onClick={() => {
1232
+ const count = parseInt((document.getElementById('member-count') as HTMLInputElement)?.value || '5')
1233
+ handleSeedMembers(count)
1234
+ }}
1235
+ className="bg-white hover:bg-white/90 text-black border border-white/20 rounded-none mt-6"
1236
+ >
1237
+ Seed Members
1238
+ </Button>
1239
+ </div>
1240
+ </div>
1241
+
1242
+ {/* Seeding Logs */}
1243
+ {seedingLogs.length > 0 && (
1244
+ <div className="mt-6">
1245
+ <h5 className="text-sm text-white font-light">Seeding Log</h5>
1246
+ <br />
1247
+ <div className="flex w-full mb-3">
1248
+ <details className="group w-full">
1249
+ <summary className="cursor-pointer text-sm text-gray-400 font-light hover:text-white">
1250
+ View Details ({seedingLogs.length} items)
1251
+ </summary>
1252
+ <div className="mt-3 p-4 bg-black/50 border border-dashed border-white/20 rounded-none">
1253
+ <div className="space-y-2 max-h-48 overflow-y-auto">
1254
+ {seedingLogs.map((log, index) => (
1255
+ <div key={index} className="text-xs font-mono text-gray-300 flex items-start space-x-2">
1256
+ <span className="text-green-400">✓</span>
1257
+ <span>{log}</span>
1258
+ </div>
1259
+ ))}
1260
+ </div>
1261
+ </div>
1262
+ </details>
1263
+ </div>
1264
+ </div>
1265
+ )}
1266
+ </div>
1267
+ <div className="flex justify-end mt-6 pt-6 border-t border-dashed border-white/10">
1268
+ <Button
1269
+ variant="outline"
1270
+ onClick={() => setShowSeedMembersModal(false)}
1271
+ className="border border-dashed border-white/20 text-white hover:bg-white/10 rounded-none"
1272
+ >
1273
+ Close
1274
+ </Button>
1275
+ </div>
1276
+ </div>
1277
+ </div>
1278
+ )}
1279
+ </div>
1280
+ )
1281
+ }