@@ -593,44 +593,58 @@ function RolePicker({
593593 const error =
594594 fetcher . data && "error" in fetcher . data && fetcher . data . error ? fetcher . data . error : null ;
595595
596+ const picker = (
597+ < Select
598+ defaultValue = { currentRoleId ?? "" }
599+ items = { roles }
600+ variant = "tertiary/small"
601+ disabled = { ! canManageMembers || isSubmitting }
602+ dropdownIcon
603+ text = { ( v ) => roles . find ( ( r ) => r . id === v ) ?. name ?? "No role" }
604+ setValue = { ( next ) => {
605+ if ( typeof next !== "string" || next === ( currentRoleId ?? "" ) ) return ;
606+ // Upgrade-link rows have a value too (Ariakit needs one to
607+ // make the row interactive — without it the Link inside
608+ // doesn't even register the click), but they shouldn't
609+ // submit the role-change form. The Link navigates the user
610+ // to the plan-selection page; we just bail here.
611+ if ( ! assignable . has ( next ) ) return ;
612+ fetcher . submit (
613+ { _formType : "set-role" , userId : memberUserId , roleId : next } ,
614+ { method : "post" }
615+ ) ;
616+ } }
617+ >
618+ { ( items ) =>
619+ items . map ( ( role ) => {
620+ const isAssignable = assignable . has ( role . id ) ;
621+ return isAssignable ? (
622+ < SelectItem key = { role . id } value = { role . id } >
623+ { role . name }
624+ </ SelectItem >
625+ ) : (
626+ < SelectLinkItem key = { role . id } value = { role . id } to = { v3BillingPath ( organization ) } >
627+ { role . name } (upgrade)
628+ </ SelectLinkItem >
629+ ) ;
630+ } )
631+ }
632+ </ Select >
633+ ) ;
634+
596635 return (
597636 < div className = "flex flex-col items-end gap-1" >
598- < Select
599- defaultValue = { currentRoleId ?? "" }
600- items = { roles }
601- variant = "tertiary/small"
602- disabled = { ! canManageMembers || isSubmitting }
603- dropdownIcon
604- text = { ( v ) => roles . find ( ( r ) => r . id === v ) ?. name ?? "No role" }
605- setValue = { ( next ) => {
606- if ( typeof next !== "string" || next === ( currentRoleId ?? "" ) ) return ;
607- // Upgrade-link rows have a value too (Ariakit needs one to
608- // make the row interactive — without it the Link inside
609- // doesn't even register the click), but they shouldn't
610- // submit the role-change form. The Link navigates the user
611- // to the plan-selection page; we just bail here.
612- if ( ! assignable . has ( next ) ) return ;
613- fetcher . submit (
614- { _formType : "set-role" , userId : memberUserId , roleId : next } ,
615- { method : "post" }
616- ) ;
617- } }
618- >
619- { ( items ) =>
620- items . map ( ( role ) => {
621- const isAssignable = assignable . has ( role . id ) ;
622- return isAssignable ? (
623- < SelectItem key = { role . id } value = { role . id } >
624- { role . name }
625- </ SelectItem >
626- ) : (
627- < SelectLinkItem key = { role . id } value = { role . id } to = { v3BillingPath ( organization ) } >
628- { role . name } (upgrade)
629- </ SelectLinkItem >
630- ) ;
631- } )
632- }
633- </ Select >
637+ { canManageMembers ? (
638+ picker
639+ ) : (
640+ // Disabled <Select> swallows hover events on its own, so wrap it
641+ // in a div the tooltip can attach to.
642+ < SimpleTooltip
643+ button = { < div className = "cursor-not-allowed" > { picker } </ div > }
644+ content = "You don't have permission to change roles"
645+ disableHoverableContent
646+ />
647+ ) }
634648 { error ? (
635649 < span className = "text-xs text-error" role = "alert" >
636650 { error }
0 commit comments