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