@@ -7,6 +7,7 @@ import { useSession } from '@/lib/auth/auth-client'
77import { getSubscriptionAccessState } from '@/lib/billing/client/utils'
88import { getBaseUrl } from '@/lib/core/utils/urls'
99import { generateSlug , isAdminOrOwner , type Member } from '@/lib/workspaces/organization'
10+ import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
1011import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel'
1112import {
1213 NoOrganizationView ,
@@ -38,23 +39,52 @@ export function TeamManagement({
3839 organizationId,
3940 billingHref = `/organization/${ organizationId } /settings/billing` ,
4041} : TeamManagementProps ) {
41- const { data : session , isPending : isSessionPending } = useSession ( )
42- const { isInvitationsDisabled } = usePermissionConfig ( )
42+ const { data : session , isPending : isSessionPending , error : sessionError } = useSession ( )
43+ const {
44+ isInvitationsDisabled,
45+ isPermissionLoading : isPermissionConfigLoading ,
46+ isPermissionError : isPermissionConfigError ,
47+ } = usePermissionConfig ( )
4348
4449 const { data : userSubscriptionData } = useSubscriptionData ( )
4550 const subscriptionAccess = getSubscriptionAccessState ( userSubscriptionData ?. data )
4651 const hasTeamPlan = subscriptionAccess . hasUsableTeamAccess
4752 const hasEnterprisePlan = subscriptionAccess . hasUsableEnterpriseAccess
4853
49- const { data : organization , isLoading, error : orgError } = useOrganization ( organizationId )
54+ const {
55+ data : organization ,
56+ isLoading,
57+ isError : isOrganizationError ,
58+ error : orgError ,
59+ } = useOrganization ( organizationId )
5060 const adminOrOwner = isAdminOrOwner ( organization , session ?. user ?. email )
5161
52- const { data : organizationBillingData , isLoading : isOrgBillingLoading } = useOrganizationBilling (
53- organizationId ,
54- { enabled : adminOrOwner }
55- )
56-
57- const { data : roster , isLoading : isLoadingRoster } = useOrganizationRoster ( organizationId )
62+ const {
63+ data : organizationBillingData ,
64+ isLoading : isOrgBillingLoading ,
65+ isError : isOrganizationBillingError ,
66+ } = useOrganizationBilling ( organizationId , { enabled : adminOrOwner } )
67+
68+ const {
69+ data : roster ,
70+ isLoading : isLoadingRoster ,
71+ isError : isRosterError ,
72+ } = useOrganizationRoster ( organizationId )
73+ const hasLoadError =
74+ Boolean ( sessionError ) ||
75+ isOrganizationError ||
76+ ( adminOrOwner && isOrganizationBillingError ) ||
77+ isRosterError ||
78+ isPermissionConfigError
79+ const isMembersLoading =
80+ ! hasLoadError &&
81+ ( isSessionPending ||
82+ isLoading ||
83+ ( adminOrOwner && isOrgBillingLoading ) ||
84+ isLoadingRoster ||
85+ isPermissionConfigLoading )
86+ const dataState = hasLoadError ? 'error' : isMembersLoading ? 'loading' : 'ready'
87+ const canManage = adminOrOwner && dataState === 'ready'
5888
5989 const removeMemberMutation = useRemoveMember ( )
6090 const transferOwnershipMutation = useTransferOwnership ( )
@@ -131,6 +161,13 @@ export function TeamManagement({
131161 }
132162 } , [ hasTeamPlan , hasEnterprisePlan , session ?. user ?. name , orgName ] )
133163
164+ useEffect ( ( ) => {
165+ if ( canManage ) return
166+ setInviteModalOpen ( false )
167+ setTransferDialogOpen ( false )
168+ setRemoveMemberDialog ( ( current ) => ( current . open ? { ...current , open : false } : current ) )
169+ } , [ canManage ] )
170+
134171 const handleOrgNameChange = useCallback ( ( e : React . ChangeEvent < HTMLInputElement > ) => {
135172 const newName = e . target . value
136173 setOrgName ( newName )
@@ -156,7 +193,7 @@ export function TeamManagement({
156193
157194 const handleRemoveMember = useCallback (
158195 async ( member : Member ) => {
159- if ( ! session ?. user ) return
196+ if ( ! canManage || ! session ?. user ) return
160197
161198 if ( ! member . user ?. id ) {
162199 logger . error ( 'Member object missing user ID' , { member } )
@@ -176,12 +213,12 @@ export function TeamManagement({
176213 isExternalRemoval : member . role === 'external' ,
177214 } )
178215 } ,
179- [ session ?. user ]
216+ [ canManage , session ?. user ]
180217 )
181218
182219 const confirmRemoveMember = useCallback ( async ( ) => {
183220 const { memberId, isSelfRemoval } = removeMemberDialog
184- if ( ! session ?. user || ! memberId ) return
221+ if ( ! canManage || ! session ?. user || ! memberId ) return
185222
186223 try {
187224 await removeMemberMutation . mutateAsync ( {
@@ -205,30 +242,34 @@ export function TeamManagement({
205242 } , [
206243 removeMemberDialog . memberId ,
207244 removeMemberDialog . isSelfRemoval ,
245+ canManage ,
208246 session ?. user ?. id ,
209247 organizationId ,
210248 removeMemberMutation ,
211249 ] )
212250
213251 const handleTransferDialogOpenChange = useCallback (
214252 ( next : boolean ) => {
253+ if ( next && ! canManage ) return
215254 setTransferDialogOpen ( next )
216255 if ( ! next ) {
217256 transferOwnershipMutation . reset ( )
218257 setTransferPortalError ( null )
219258 }
220259 } ,
221- [ transferOwnershipMutation ]
260+ [ canManage , transferOwnershipMutation ]
222261 )
223262
224263 const handleOpenTransferDialog = useCallback ( ( ) => {
264+ if ( ! canManage ) return
225265 transferOwnershipMutation . reset ( )
226266 setTransferPortalError ( null )
227267 setTransferDialogOpen ( true )
228- } , [ transferOwnershipMutation ] )
268+ } , [ canManage , transferOwnershipMutation ] )
229269
230270 const handleConfirmTransfer = useCallback (
231271 async ( newOwnerUserId : string ) => {
272+ if ( ! canManage ) return
232273 try {
233274 const result = await transferOwnershipMutation . mutateAsync ( {
234275 orgId : organizationId ,
@@ -245,10 +286,11 @@ export function TeamManagement({
245286 logger . error ( 'Failed to transfer ownership' , error )
246287 }
247288 } ,
248- [ organizationId , transferOwnershipMutation ]
289+ [ canManage , organizationId , transferOwnershipMutation ]
249290 )
250291
251292 const handleOpenTransferBillingPortal = useCallback ( ( ) => {
293+ if ( ! canManage ) return
252294 setTransferPortalError ( null )
253295 const portalWindow = window . open ( '' , '_blank' )
254296 openBillingPortal . mutate (
@@ -276,14 +318,22 @@ export function TeamManagement({
276318 } ,
277319 }
278320 )
279- } , [ organizationId , openBillingPortal ] )
321+ } , [ canManage , organizationId , openBillingPortal ] )
280322
281323 const queryError = orgError
282324 const errorMessage = queryError instanceof Error ? queryError . message : null
283325 const displayOrganization = organization
284326
285- if ( isLoading && ! displayOrganization ) {
286- return null
327+ if ( isMembersLoading && ! displayOrganization ) {
328+ return < section aria-label = 'Organization members' aria-busy data-members-state = 'loading' />
329+ }
330+
331+ if ( hasLoadError && ! displayOrganization ) {
332+ return (
333+ < section aria-label = 'Organization members' aria-busy = { false } data-members-state = 'error' >
334+ < SettingsEmptyState > Unable to load organization members</ SettingsEmptyState >
335+ </ section >
336+ )
287337 }
288338
289339 if ( ! displayOrganization ) {
@@ -308,12 +358,13 @@ export function TeamManagement({
308358 < >
309359 < section
310360 aria-label = 'Organization members'
311- aria-busy = { isSessionPending || isLoading || isLoadingRoster }
361+ aria-busy = { isMembersLoading }
362+ data-members-state = { dataState }
312363 className = 'flex flex-col gap-7'
313364 >
314365 < SettingsPanel
315366 actions = {
316- adminOrOwner
367+ canManage
317368 ? [
318369 {
319370 text : 'Invite' ,
@@ -327,7 +378,7 @@ export function TeamManagement({
327378 : [ ]
328379 }
329380 >
330- { adminOrOwner && (
381+ { canManage && (
331382 < TeamSeatsOverview
332383 billingHref = { billingHref }
333384 subscriptionData = { orgSubscription }
@@ -338,19 +389,23 @@ export function TeamManagement({
338389 />
339390 ) }
340391
341- < OrganizationMemberLists
342- canManage = { adminOrOwner }
343- organizationId = { displayOrganization . id }
344- roster = { roster ?? null }
345- isLoadingRoster = { isLoadingRoster }
346- currentUserId = { session ?. user ?. id ?? '' }
347- onRemoveMember = { handleRemoveMember }
348- onTransferOwnership = { handleOpenTransferDialog }
349- />
392+ { hasLoadError ? (
393+ < SettingsEmptyState > Unable to load organization members</ SettingsEmptyState >
394+ ) : isMembersLoading ? null : (
395+ < OrganizationMemberLists
396+ canManage = { canManage }
397+ organizationId = { displayOrganization . id }
398+ roster = { roster ?? null }
399+ isLoadingRoster = { false }
400+ currentUserId = { session ?. user ?. id ?? '' }
401+ onRemoveMember = { handleRemoveMember }
402+ onTransferOwnership = { handleOpenTransferDialog }
403+ />
404+ ) }
350405 </ SettingsPanel >
351406 </ section >
352407
353- { adminOrOwner && (
408+ { canManage && (
354409 < OrganizationInviteModal
355410 open = { inviteModalOpen }
356411 onOpenChange = { setInviteModalOpen }
@@ -362,7 +417,7 @@ export function TeamManagement({
362417 ) }
363418
364419 < TransferOwnershipDialog
365- open = { transferDialogOpen }
420+ open = { canManage && transferDialogOpen }
366421 onOpenChange = { handleTransferDialogOpenChange }
367422 members = { roster ?. members ?? [ ] }
368423 isLoadingMembers = { isLoadingRoster }
@@ -377,7 +432,7 @@ export function TeamManagement({
377432 />
378433
379434 < RemoveMemberDialog
380- open = { removeMemberDialog . open }
435+ open = { canManage && removeMemberDialog . open }
381436 memberName = { removeMemberDialog . memberName }
382437 isSelfRemoval = { removeMemberDialog . isSelfRemoval }
383438 isExternalRemoval = { removeMemberDialog . isExternalRemoval }
0 commit comments