better-auth-studio 1.0.6 → 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,1281 +0,0 @@
|
|
|
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
|
-
}
|