@@ -9,6 +9,7 @@ import { useParams, useRouter } from 'next/navigation'
99import {
1010 RoleLockTooltip ,
1111 type WorkspaceRoleSource ,
12+ workspaceMemberRemovalLockReason ,
1213 workspaceRoleLockReason ,
1314} from '@/components/permissions'
1415import { canMutateWorkspaceSettingsSection } from '@/components/settings/navigation'
@@ -56,6 +57,7 @@ interface Teammate {
5657 invitationId ?: string
5758 token ?: string
5859 roleSource ?: WorkspaceRoleSource
60+ isOrgAdmin ?: boolean
5961 isBilledAccount ?: boolean
6062}
6163
@@ -139,6 +141,7 @@ export function Teammates() {
139141 isPending : false ,
140142 userId : member . userId ,
141143 roleSource : member . roleSource ,
144+ isOrgAdmin : member . isOrgAdmin ,
142145 isBilledAccount : member . isBilledAccount ,
143146 } ) )
144147
@@ -206,20 +209,33 @@ export function Teammates() {
206209 searchTerm . trim ( ) ? `No teammates found matching “${ searchTerm } ”` : 'No teammates yet'
207210 }
208211 >
209- { filteredTeammates . map ( ( teammate ) => (
210- < MemberRow
211- key = { teammate . key }
212- name = { teammate . name }
213- email = { teammate . email }
214- image = { teammate . image }
215- status = { teammate . status }
216- roleControl = { ( ( ) => {
217- const lockReason = teammate . isPending
218- ? null
219- : workspaceRoleLockReason ( teammate . roleSource , {
220- isBilledAccount : teammate . isBilledAccount ,
221- } )
222- return (
212+ { filteredTeammates . map ( ( teammate ) => {
213+ const lockReason = teammate . isPending
214+ ? null
215+ : workspaceRoleLockReason ( teammate . roleSource , {
216+ isBilledAccount : teammate . isBilledAccount ,
217+ } )
218+ /**
219+ * Removal is refused for a different set than the role control locks,
220+ * so it carries its own reason — and stays visible-but-disabled rather
221+ * than hidden, so the row explains the derived access instead of
222+ * leaving an Admin who cannot be acted on.
223+ */
224+ const removalLockReason = teammate . isPending
225+ ? null
226+ : workspaceMemberRemovalLockReason ( {
227+ isOrgAdmin : teammate . isOrgAdmin ,
228+ isBilledAccount : teammate . isBilledAccount ,
229+ } )
230+
231+ return (
232+ < MemberRow
233+ key = { teammate . key }
234+ name = { teammate . name }
235+ email = { teammate . email }
236+ image = { teammate . image }
237+ status = { teammate . status }
238+ roleControl = {
223239 < RoleLockTooltip reason = { lockReason } >
224240 < ChipDropdown
225241 value = { teammate . role }
@@ -234,83 +250,85 @@ export function Teammates() {
234250 }
235251 />
236252 </ RoleLockTooltip >
237- )
238- } ) ( ) }
239- menu = {
240- < RowActionsMenu
241- label = 'Teammate actions'
242- actions = { [
243- {
244- label : 'Copy email' ,
245- onSelect : ( ) => copyToClipboard ( teammate . email ) ,
246- } ,
247- ... ( canManage && teammate . isPending
248- ? [
249- {
250- label : 'Resend invite' ,
251- onSelect : ( ) => {
252- if ( teammate . invitationId ) {
253- resendInvitation . mutate ( {
254- invitationId : teammate . invitationId ,
255- workspaceId ,
256- } )
257- }
253+ }
254+ menu = {
255+ < RowActionsMenu
256+ label = 'Teammate actions'
257+ actions = { [
258+ {
259+ label : 'Copy email' ,
260+ onSelect : ( ) => copyToClipboard ( teammate . email ) ,
261+ } ,
262+ ... ( canManage && teammate . isPending
263+ ? [
264+ {
265+ label : 'Resend invite' ,
266+ onSelect : ( ) => {
267+ if ( teammate . invitationId ) {
268+ resendInvitation . mutate ( {
269+ invitationId : teammate . invitationId ,
270+ workspaceId ,
271+ } )
272+ }
273+ } ,
258274 } ,
259- } ,
260- {
261- label : 'Copy invite link' ,
262- onSelect : ( ) => {
263- if ( teammate . invitationId && teammate . token ) {
264- copyToClipboard (
265- buildInviteLink ( teammate . invitationId , teammate . token )
266- )
267- }
275+ {
276+ label : 'Copy invite link' ,
277+ onSelect : ( ) => {
278+ if ( teammate . invitationId && teammate . token ) {
279+ copyToClipboard (
280+ buildInviteLink ( teammate . invitationId , teammate . token )
281+ )
282+ }
283+ } ,
268284 } ,
269- } ,
270- {
271- label : 'Revoke invite' ,
272- destructive : true ,
273- onSelect : ( ) => {
274- if ( teammate . invitationId ) {
275- cancelInvitation . mutate ( {
276- invitationId : teammate . invitationId ,
277- workspaceId ,
278- } )
279- }
285+ {
286+ label : 'Revoke invite' ,
287+ destructive : true ,
288+ onSelect : ( ) => {
289+ if ( teammate . invitationId ) {
290+ cancelInvitation . mutate ( {
291+ invitationId : teammate . invitationId ,
292+ workspaceId ,
293+ } )
294+ }
295+ } ,
280296 } ,
281- } ,
282- ]
283- : [ ] ) ,
284- ...( canManage && ! teammate . isPending && teammate . userId !== viewer ?. userId
285- ? [
286- {
287- label : 'Remove' ,
288- destructive : true ,
289- onSelect : ( ) => {
290- if ( teammate . userId ) {
291- removeMember . mutate (
292- { userId : teammate . userId , workspaceId } ,
293- {
294- onError : ( error ) => {
295- toast . error ( "Couldn't remove teammate" , {
296- description : getErrorMessage (
297- error ,
298- 'Please try again in a moment.'
299- ) ,
300- } )
301- } ,
302- }
303- )
304- }
297+ ]
298+ : [ ] ) ,
299+ ...( canManage && ! teammate . isPending && teammate . userId !== viewer ?. userId
300+ ? [
301+ {
302+ label : 'Remove' ,
303+ destructive : true ,
304+ disabled : removalLockReason !== null ,
305+ tooltip : removalLockReason ?? undefined ,
306+ onSelect : ( ) => {
307+ if ( teammate . userId ) {
308+ removeMember . mutate (
309+ { userId : teammate . userId , workspaceId } ,
310+ {
311+ onError : ( error ) => {
312+ toast . error ( "Couldn't remove teammate" , {
313+ description : getErrorMessage (
314+ error ,
315+ 'Please try again in a moment.'
316+ ) ,
317+ } )
318+ } ,
319+ }
320+ )
321+ }
322+ } ,
305323 } ,
306- } ,
307- ]
308- : [ ] ) ,
309- ] }
310- />
311- }
312- />
313- ) ) }
324+ ]
325+ : [ ] ) ,
326+ ] }
327+ />
328+ }
329+ />
330+ )
331+ } ) }
314332 </ MemberSection >
315333 </ SettingsPanel >
316334
0 commit comments