44 ExclamationTriangleIcon ,
55} from "@heroicons/react/24/outline" ;
66import { LinkIcon } from "@heroicons/react/24/solid" ;
7- import { useFetcher , useNavigation } from "@remix-run/react" ;
7+ import { useFetcher , useNavigate , useNavigation , useSubmit } from "@remix-run/react" ;
88import { LayoutGroup , motion } from "framer-motion" ;
99import { type ReactNode , useCallback , useEffect , useRef , useState } from "react" ;
1010import { AIChatIcon } from "~/assets/icons/AIChatIcon" ;
@@ -37,17 +37,20 @@ import { LogsIcon } from "~/assets/icons/LogsIcon";
3737import { PlusIcon } from "~/assets/icons/PlusIcon" ;
3838import { QueuesIcon } from "~/assets/icons/QueuesIcon" ;
3939import { RunsIcon } from "~/assets/icons/RunsIcon" ;
40+ import { ShieldIcon } from "~/assets/icons/ShieldIcon" ;
4041import { SlidersIcon } from "~/assets/icons/SlidersIcon" ;
4142import { TasksIcon } from "~/assets/icons/TasksIcon" ;
4243import { UsageIcon } from "~/assets/icons/UsageIcon" ;
4344import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon" ;
4445import { CreditCardIcon } from "~/assets/icons/CreditCardIcon" ;
46+ import { UserCrossIcon } from "~/assets/icons/UserCrossIcon" ;
4547import { UserGroupIcon } from "~/assets/icons/UserGroupIcon" ;
4648import { RolesIcon } from "~/assets/icons/RolesIcon" ;
4749import { PadlockIcon } from "~/assets/icons/PadlockIcon" ;
4850import { SlackIcon } from "~/assets/icons/SlackIcon" ;
4951import { VercelLogo } from "~/components/integrations/VercelLogo" ;
5052import { Avatar } from "~/components/primitives/Avatar" ;
53+ import { UserProfilePhoto } from "~/components/UserProfilePhoto" ;
5154import { type MatchedEnvironment } from "~/hooks/useEnvironment" ;
5255import { useFeatureFlags } from "~/hooks/useFeatureFlags" ;
5356import { useFeatures } from "~/hooks/useFeatures" ;
@@ -67,6 +70,8 @@ import { IncidentStatusPanel, useIncidentStatus } from "~/routes/resources.incid
6770import { cn } from "~/utils/cn" ;
6871import {
6972 accountPath ,
73+ accountSecurityPath ,
74+ personalAccessTokensPath ,
7075 adminPath ,
7176 branchesPath ,
7277 concurrencyPath ,
@@ -110,7 +115,6 @@ import {
110115import { FreePlanUsage } from "../billing/FreePlanUsage" ;
111116import { ConnectionIcon , DevPresencePanel , useDevPresence } from "../DevPresence" ;
112117import { AlphaBadge , NewBadge } from "../FeatureBadges" ;
113- import { ImpersonationBanner } from "../ImpersonationBanner" ;
114118import { Button , ButtonContent , LinkButton } from "../primitives/Buttons" ;
115119import { Dialog , DialogTrigger } from "../primitives/Dialog" ;
116120import { Paragraph } from "../primitives/Paragraph" ;
@@ -305,30 +309,9 @@ export function SideMenu({
305309 isCollapsed = { isCollapsed }
306310 />
307311 </ div >
308- { isAdmin && ! user . isImpersonating ? (
309- < CollapsibleElement isCollapsed = { isCollapsed } >
310- < TooltipProvider disableHoverableContent = { true } >
311- < Tooltip >
312- < TooltipTrigger >
313- < LinkButton
314- variant = "minimal/medium"
315- to = { adminPath ( ) }
316- TrailingIcon = { HomeIcon }
317- trailingIconClassName = "h-4.5 w-4.5"
318- className = "h-8 w-8"
319- />
320- </ TooltipTrigger >
321- < TooltipContent side = "bottom" className = { "text-xs" } >
322- Admin dashboard
323- </ TooltipContent >
324- </ Tooltip >
325- </ TooltipProvider >
326- </ CollapsibleElement >
327- ) : isAdmin && user . isImpersonating ? (
328- < CollapsibleElement isCollapsed = { isCollapsed } >
329- < ImpersonationBanner />
330- </ CollapsibleElement >
331- ) : null }
312+ < CollapsibleElement isCollapsed = { isCollapsed } >
313+ < AccountMenu isAdmin = { isAdmin } isImpersonating = { user . isImpersonating } />
314+ </ CollapsibleElement >
332315 </ div >
333316 < div
334317 className = { cn (
@@ -975,6 +958,8 @@ function OrgSelector({
975958 />
976959 ) }
977960 < Integrations organization = { organization } />
961+ </ div >
962+ < div className = "border-t border-charcoal-700 p-1" >
978963 { organizations . length > 1 ? (
979964 < SwitchOrganizations organizations = { organizations } organization = { organization } />
980965 ) : (
@@ -987,14 +972,116 @@ function OrgSelector({
987972 />
988973 ) }
989974 </ div >
990- < div className = "border-t border-charcoal-700 p-1" >
975+ </ PopoverContent >
976+ </ Popover >
977+ ) ;
978+ }
979+
980+ function AccountMenu ( { isAdmin, isImpersonating } : { isAdmin : boolean ; isImpersonating : boolean } ) {
981+ const [ isOpen , setIsOpen ] = useState ( false ) ;
982+ const navigation = useNavigation ( ) ;
983+ const navigate = useNavigate ( ) ;
984+ const submit = useSubmit ( ) ;
985+
986+ useEffect ( ( ) => {
987+ setIsOpen ( false ) ;
988+ } , [ navigation . location ?. pathname ] ) ;
989+
990+ const stopImpersonating = ( ) =>
991+ submit ( null , { action : "/resources/impersonation" , method : "delete" } ) ;
992+
993+ useShortcutKeys ( {
994+ shortcut : isAdmin
995+ ? { modifiers : [ "mod" ] , key : "esc" , enabledOnInputElements : true }
996+ : undefined ,
997+ action : ( ) => {
998+ if ( isImpersonating ) {
999+ stopImpersonating ( ) ;
1000+ } else {
1001+ navigate ( adminPath ( ) ) ;
1002+ }
1003+ } ,
1004+ } ) ;
1005+
1006+ return (
1007+ < Popover onOpenChange = { ( open ) => setIsOpen ( open ) } open = { isOpen } >
1008+ < SimpleTooltip
1009+ button = {
1010+ < PopoverTrigger className = "group flex size-8 items-center justify-center rounded transition-colors hover:bg-charcoal-750 focus-custom" >
1011+ < UserProfilePhoto className = "size-6" />
1012+ </ PopoverTrigger >
1013+ }
1014+ content = "Account"
1015+ side = "bottom"
1016+ sideOffset = { 8 }
1017+ disableHoverableContent
1018+ />
1019+ < PopoverContent
1020+ className = "min-w-[16rem] overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
1021+ side = "bottom"
1022+ sideOffset = { 4 }
1023+ align = "start"
1024+ style = { { maxHeight : `calc(var(--radix-popover-content-available-height) - 10vh)` } }
1025+ >
1026+ { isAdmin && (
1027+ < div className = "flex flex-col gap-1 border-b border-charcoal-700 p-1" >
1028+ { isImpersonating ? (
1029+ < PopoverMenuItem
1030+ title = {
1031+ < div className = "flex w-full items-center justify-between" >
1032+ < span className = "text-amber-400" > Stop impersonating</ span >
1033+ < span className = "flex items-center gap-1" >
1034+ < ShortcutKey shortcut = { { modifiers : [ "mod" ] } } variant = "medium/bright" />
1035+ < ShortcutKey shortcut = { { key : "esc" } } variant = "medium/bright" />
1036+ </ span >
1037+ </ div >
1038+ }
1039+ icon = { UserCrossIcon }
1040+ onClick = { stopImpersonating }
1041+ leadingIconClassName = { cn ( SIDE_MENU_POPOVER_ITEM_ICON , "text-amber-400" ) }
1042+ className = { SIDE_MENU_POPOVER_ITEM_LABEL }
1043+ />
1044+ ) : (
1045+ < PopoverMenuItem
1046+ to = { adminPath ( ) }
1047+ title = {
1048+ < div className = "flex w-full items-center justify-between" >
1049+ < span > Admin dashboard</ span >
1050+ < span className = "flex items-center gap-1" >
1051+ < ShortcutKey shortcut = { { modifiers : [ "mod" ] } } variant = "medium/bright" />
1052+ < ShortcutKey shortcut = { { key : "esc" } } variant = "medium/bright" />
1053+ </ span >
1054+ </ div >
1055+ }
1056+ icon = { HomeIcon }
1057+ leadingIconClassName = { SIDE_MENU_POPOVER_ITEM_ICON }
1058+ className = { SIDE_MENU_POPOVER_ITEM_LABEL }
1059+ />
1060+ ) }
1061+ </ div >
1062+ ) }
1063+ < div className = "flex flex-col gap-1 p-1" >
9911064 < PopoverMenuItem
9921065 to = { accountPath ( ) }
993- title = "Account "
1066+ title = "Profile "
9941067 icon = { AvatarCircleIcon }
9951068 leadingIconClassName = { SIDE_MENU_POPOVER_ITEM_ICON }
9961069 className = { SIDE_MENU_POPOVER_ITEM_LABEL }
9971070 />
1071+ < PopoverMenuItem
1072+ to = { personalAccessTokensPath ( ) }
1073+ title = "Personal Access Tokens"
1074+ icon = { ShieldIcon }
1075+ leadingIconClassName = { SIDE_MENU_POPOVER_ITEM_ICON }
1076+ className = { SIDE_MENU_POPOVER_ITEM_LABEL }
1077+ />
1078+ < PopoverMenuItem
1079+ to = { accountSecurityPath ( ) }
1080+ title = "Security"
1081+ icon = { PadlockIcon }
1082+ leadingIconClassName = { SIDE_MENU_POPOVER_ITEM_ICON }
1083+ className = { SIDE_MENU_POPOVER_ITEM_LABEL }
1084+ />
9981085 </ div >
9991086 < div className = "border-t border-charcoal-700 p-1" >
10001087 < PopoverMenuItem
0 commit comments