11'use client' ;
22
3+ import { authClient } from '@databuddy/auth/client' ;
34import {
4- ArrowRightIcon ,
55 BuildingsIcon ,
66 CalendarIcon ,
77 CheckIcon ,
88 GearIcon ,
9- TrashIcon ,
109} from '@phosphor-icons/react' ;
1110import dayjs from 'dayjs' ;
1211import relativeTime from 'dayjs/plugin/relativeTime' ;
13- import Link from 'next/link ' ;
12+ import { useRouter } from 'next/navigation ' ;
1413import { useState } from 'react' ;
1514import { toast } from 'sonner' ;
16- import {
17- AlertDialog ,
18- AlertDialogAction ,
19- AlertDialogCancel ,
20- AlertDialogContent ,
21- AlertDialogDescription ,
22- AlertDialogFooter ,
23- AlertDialogHeader ,
24- AlertDialogTitle ,
25- } from '@/components/ui/alert-dialog' ;
2615import { Avatar , AvatarFallback , AvatarImage } from '@/components/ui/avatar' ;
2716import { Badge } from '@/components/ui/badge' ;
2817import { Button } from '@/components/ui/button' ;
@@ -34,42 +23,31 @@ import {
3423 TooltipProvider ,
3524 TooltipTrigger ,
3625} from '@/components/ui/tooltip' ;
37- import {
38- type ActiveOrganization ,
39- type Organization ,
40- useOrganizations ,
41- } from '@/hooks/use-organizations' ;
4226import { cn , getOrganizationInitials } from '@/lib/utils' ;
4327import { EmptyState } from './empty-state' ;
4428
4529dayjs . extend ( relativeTime ) ;
4630
4731interface OrganizationsListProps {
48- organizations : Organization [ ] ;
49- activeOrganization : ActiveOrganization ;
32+ organizations : ReturnType < typeof authClient . useListOrganizations > [ 'data' ] ;
33+ activeOrganization : ReturnType < typeof authClient . useActiveOrganization > [ 'data' ] ;
5034 isLoading : boolean ;
5135}
5236
5337function OrganizationSkeleton ( ) {
5438 return (
5539 < Card className = "group relative overflow-hidden" >
56- < CardContent className = "p-4 sm:p-6" >
57- < div className = "space-y-4 sm:space-y-6" >
58- < div className = "flex items-start gap-3 sm:gap-4" >
59- < Skeleton className = "h-10 w-10 flex-shrink-0 rounded-full sm:h-12 sm:w-12" />
60- < div className = "min-w-0 flex-1 space-y-2" >
61- < Skeleton className = "h-3 w-28 sm:h-4 sm:w-32" />
62- < Skeleton className = "h-3 w-20 sm:h-3 sm:w-24" />
63- < Skeleton className = "h-3 w-24 sm:h-3 sm:w-28" />
64- </ div >
65- </ div >
66- < div className = "space-y-3" >
67- < Skeleton className = "h-9 w-full sm:h-10" />
68- < div className = "flex gap-2 sm:gap-3" >
69- < Skeleton className = "h-9 flex-1 sm:h-10" />
70- < Skeleton className = "h-9 w-9 sm:h-10 sm:w-10" />
40+ < CardContent className = "p-4" >
41+ < div className = "space-y-3" >
42+ < div className = "flex items-start gap-3" >
43+ < Skeleton className = "h-10 w-10 flex-shrink-0 rounded-full" />
44+ < div className = "min-w-0 flex-1 space-y-1.5" >
45+ < Skeleton className = "h-3 w-32" />
46+ < Skeleton className = "h-3 w-24" />
47+ < Skeleton className = "h-3 w-28" />
7148 </ div >
7249 </ div >
50+ < Skeleton className = "h-3 w-40" />
7351 </ div >
7452 </ CardContent >
7553 </ Card >
@@ -96,38 +74,30 @@ export function OrganizationsList({
9674 activeOrganization,
9775 isLoading,
9876} : OrganizationsListProps ) {
99- const {
100- setActiveOrganization,
101- deleteOrganizationAsync,
102- isSettingActiveOrganization,
103- isDeletingOrganization,
104- } = useOrganizations ( ) ;
105- const [ deletingId , setDeletingId ] = useState < string | null > ( null ) ;
106- const [ confirmDelete , setConfirmDelete ] = useState < {
107- id : string ;
108- name : string ;
109- } | null > ( null ) ;
110-
111- const handleSetActive = ( organizationId : string ) => {
112- setActiveOrganization ( organizationId ) ;
113- } ;
114-
115- const handleDelete = ( organizationId : string , organizationName : string ) => {
116- setConfirmDelete ( { id : organizationId , name : organizationName } ) ;
117- } ;
118-
119- const confirmDeleteAction = async ( ) => {
120- if ( ! confirmDelete ) {
77+ const router = useRouter ( ) ;
78+ const [ processingId , setProcessingId ] = useState < string | null > ( null ) ;
79+
80+ const handleCardClick = async ( orgId : string ) => {
81+ const isCurrentlyActive = activeOrganization ?. id === orgId ;
82+
83+ if ( isCurrentlyActive ) {
84+ router . push ( '/organizations/settings' ) ;
12185 return ;
12286 }
123- setDeletingId ( confirmDelete . id ) ;
87+
88+ setProcessingId ( orgId ) ;
12489 try {
125- await deleteOrganizationAsync ( confirmDelete . id ) ;
90+ const { error } = await authClient . organization . setActive ( { organizationId : orgId } ) ;
91+ if ( error ) {
92+ toast . error ( error . message || 'Failed to switch workspace' ) ;
93+ } else {
94+ toast . success ( 'Workspace updated' ) ;
95+ router . push ( '/organizations/settings' ) ;
96+ }
12697 } catch ( _error ) {
127- toast . error ( 'Failed to delete organization ' ) ;
98+ toast . error ( 'Failed to switch workspace ' ) ;
12899 } finally {
129- setDeletingId ( null ) ;
130- setConfirmDelete ( null ) ;
100+ setProcessingId ( null ) ;
131101 }
132102 } ;
133103
@@ -150,22 +120,24 @@ export function OrganizationsList({
150120 return (
151121 < div className = "p-4 sm:p-6" >
152122 < div className = "grid grid-cols-1 gap-4 sm:gap-6 sm:grid-cols-2 lg:grid-cols-3" >
153- { organizations . map ( ( org ) => {
123+ { organizations ? .map ( ( org ) => {
154124 const isActive = activeOrganization ?. id === org . id ;
155- const isDeleting = deletingId === org . id ;
125+ const isProcessing = processingId === org . id ;
156126
157127 return (
158128 < Card
159129 className = { cn (
160- 'group relative overflow-hidden transition-all duration-200 hover:shadow-sm ' ,
130+ 'group relative cursor-pointer overflow-hidden transition-all duration-200' ,
161131 isActive
162132 ? 'border-primary/30 bg-primary/5 shadow-sm'
163- : 'hover:border-border/60 hover:bg-muted/30'
133+ : 'hover:border-border/60 hover:bg-muted/30' ,
134+ isProcessing && 'opacity-70 pointer-events-none'
164135 ) }
165136 key = { org . id }
137+ onClick = { ( ) => handleCardClick ( org . id ) }
166138 >
167139 { isActive && (
168- < div className = "absolute top-2 right-2 " >
140+ < div className = "absolute top-3 right-3 " >
169141 < Badge
170142 className = "border-primary/20 bg-primary/10 text-primary text-xs"
171143 variant = "secondary"
@@ -176,143 +148,46 @@ export function OrganizationsList({
176148 </ div >
177149 ) }
178150
179- < CardContent className = "p-4 sm:p-6" >
180- < div className = "space-y-4 sm:space-y-6" >
151+ { isProcessing && (
152+ < div className = "absolute inset-0 flex items-center justify-center bg-background/50 backdrop-blur-sm" >
153+ < div className = "h-6 w-6 animate-spin rounded-full border border-primary/30 border-t-primary" />
154+ </ div >
155+ ) }
156+
157+ < CardContent className = "p-4" >
158+ < div className = "space-y-3" >
181159 { /* Organization Info */ }
182- < div className = "flex items-start gap-3 sm:gap-4 " >
183- < Avatar className = "h-10 w-10 flex-shrink-0 border border-border/30 sm:h-12 sm:w-12 " >
160+ < div className = "flex items-start gap-3" >
161+ < Avatar className = "h-10 w-10 flex-shrink-0 border border-border/30" >
184162 < AvatarImage alt = { org . name } src = { org . logo || undefined } />
185- < AvatarFallback className = "bg-accent font-medium text-xs sm:text-sm " >
163+ < AvatarFallback className = "bg-accent font-medium text-xs" >
186164 { getOrganizationInitials ( org . name ) }
187165 </ AvatarFallback >
188166 </ Avatar >
189167 < div className = "min-w-0 flex-1" >
190- < h3 className = "truncate font-semibold text-sm sm:text-base " >
168+ < h3 className = "truncate font-semibold text-sm" >
191169 { org . name }
192170 </ h3 >
193- < p className = "truncate text-muted-foreground text-xs sm:text-sm " >
171+ < p className = "truncate text-muted-foreground text-xs" >
194172 @{ org . slug }
195173 </ p >
196- < div className = "mt-1 flex items-center gap-1 sm:mt-2 " >
174+ < div className = "mt-0.5 flex items-center gap-1" >
197175 < CalendarIcon
198- className = "h-3 w-3 text-muted-foreground sm:h-4 sm:w-4 "
176+ className = "h-3 w-3 text-muted-foreground"
199177 size = { 12 }
200178 />
201- < span className = "text-muted-foreground text-xs sm:text-sm " >
179+ < span className = "text-muted-foreground text-xs" >
202180 Created { dayjs ( org . createdAt ) . fromNow ( ) }
203181 </ span >
204182 </ div >
205183 </ div >
206184 </ div >
207-
208- { /* Actions */ }
209- < div className = "space-y-3" >
210- { isActive ? (
211- < Button
212- className = "h-9 w-full rounded font-medium sm:h-10"
213- disabled
214- variant = "secondary"
215- >
216- < CheckIcon className = "mr-2 h-3 w-3 sm:h-4 sm:w-4" size = { 12 } />
217- < span className = "text-xs sm:text-sm" > Current Organization</ span >
218- </ Button >
219- ) : (
220- < Button
221- className = "h-9 w-full rounded font-medium sm:h-10"
222- disabled = { isSettingActiveOrganization }
223- onClick = { ( ) => handleSetActive ( org . id ) }
224- >
225- { isSettingActiveOrganization ? (
226- < >
227- < div className = "mr-2 h-3 w-3 animate-spin rounded-full border border-primary-foreground/30 border-t-primary-foreground sm:h-4 sm:w-4" />
228- < span className = "text-xs sm:text-sm" > Switching...</ span >
229- </ >
230- ) : (
231- < >
232- < ArrowRightIcon
233- className = "mr-2 h-3 w-3 sm:h-4 sm:w-4"
234- size = { 12 }
235- />
236- < span className = "text-xs sm:text-sm" > Switch Organization</ span >
237- </ >
238- ) }
239- </ Button >
240- ) }
241-
242- < div className = "flex items-center gap-2 sm:gap-3" >
243- < TooltipProvider >
244- < Tooltip >
245- < TooltipTrigger asChild >
246- < Button
247- asChild
248- className = "h-9 flex-1 rounded font-medium sm:h-10"
249- variant = "outline"
250- >
251- < Link href = "/organizations/settings" >
252- < GearIcon className = "mr-2 h-3 w-3 sm:h-4 sm:w-4" size = { 12 } />
253- < span className = "text-xs sm:text-sm" > Settings</ span >
254- </ Link >
255- </ Button >
256- </ TooltipTrigger >
257- < TooltipContent side = "bottom" >
258- < p > Manage organization settings</ p >
259- </ TooltipContent >
260- </ Tooltip >
261- </ TooltipProvider >
262-
263- < TooltipProvider >
264- < Tooltip >
265- < TooltipTrigger asChild >
266- < Button
267- className = "h-9 w-9 rounded p-0 hover:border-destructive/30 hover:bg-destructive/10 hover:text-destructive sm:h-10 sm:w-10"
268- disabled = { isDeleting || isDeletingOrganization }
269- onClick = { ( ) => handleDelete ( org . id , org . name ) }
270- variant = "outline"
271- >
272- { isDeleting ? (
273- < div className = "h-3 w-3 animate-spin rounded-full border border-destructive/30 border-t-destructive sm:h-4 sm:w-4" />
274- ) : (
275- < TrashIcon className = "h-3 w-3 sm:h-4 sm:w-4" size = { 12 } />
276- ) }
277- </ Button >
278- </ TooltipTrigger >
279- < TooltipContent side = "bottom" >
280- < p > Delete organization</ p >
281- </ TooltipContent >
282- </ Tooltip >
283- </ TooltipProvider >
284- </ div >
285- </ div >
286185 </ div >
287186 </ CardContent >
288187 </ Card >
289188 ) ;
290189 } ) }
291190 </ div >
292-
293- < AlertDialog
294- onOpenChange = { ( open ) => ! open && setConfirmDelete ( null ) }
295- open = { ! ! confirmDelete }
296- >
297- < AlertDialogContent >
298- < AlertDialogHeader >
299- < AlertDialogTitle > Delete organization</ AlertDialogTitle >
300- < AlertDialogDescription >
301- This will permanently delete "{ confirmDelete ?. name } " and all
302- associated resources. This action cannot be undone.
303- </ AlertDialogDescription >
304- </ AlertDialogHeader >
305- < AlertDialogFooter >
306- < AlertDialogCancel > Cancel</ AlertDialogCancel >
307- < AlertDialogAction
308- className = "bg-destructive text-destructive-foreground hover:bg-destructive/90"
309- onClick = { confirmDeleteAction }
310- >
311- Delete
312- </ AlertDialogAction >
313- </ AlertDialogFooter >
314- </ AlertDialogContent >
315- </ AlertDialog >
316191 </ div >
317192 ) ;
318193}
0 commit comments