1
0
Fork 0

feat(impersonation): open user picker from account menu

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-01 17:17:57 -04:00
parent 7624327ab9
commit 39e13da41b
2 changed files with 43 additions and 14 deletions

View file

@ -274,15 +274,9 @@ export function AppSidebar({
<hr />
<NavSecondary
items={
!adminUrl && !canImpersonate
? data.navSecondary
: [
...data.navSecondary,
...(adminUrl ? [{ title: "Admin", url: adminUrl, icon: Shield }] : []),
...(canImpersonate
? [{ title: "Impersonate", url: "/impersonate", icon: UserCog }]
: []),
]
adminUrl
? [...data.navSecondary, { title: "Admin", url: adminUrl, icon: Shield }]
: data.navSecondary
}
className="mt-auto"
/>
@ -327,6 +321,7 @@ export function AppSidebar({
avatar: "",
}}
showCallsign={showCallsign}
canImpersonate={canImpersonate}
/>
</SidebarFooter>
</Sidebar>

View file

@ -1,6 +1,6 @@
"use client";
import { BadgeCheck, Bird, Bomb, ChevronsUpDown, FileTextIcon, LogOut } from "lucide-react";
import { BadgeCheck, Bird, Bomb, ChevronsUpDown, FileTextIcon, LogOut, Users2 } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
@ -21,10 +21,14 @@ import {
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar";
import type { User } from "@/payload-types";
import { ImpersonationModal } from "@/components/frontend/impersonation/ImpersonationModal";
import { getImpersonationUsers } from "@/app/(frontend)/impersonate/actions";
export function NavUser({
user,
showCallsign = false,
canImpersonate = false,
}: {
user: {
displayName: string;
@ -32,10 +36,14 @@ export function NavUser({
avatar: string;
};
showCallsign?: boolean;
canImpersonate?: boolean;
}) {
const { isMobile } = useSidebar();
const router = useRouter();
const [loggingOut, setLoggingOut] = useState(false);
const [showImpersonationModal, setShowImpersonationModal] = useState(false);
const [impersonationUsers, setImpersonationUsers] = useState<any[]>([]);
const [loadingImpersonationUsers, setLoadingImpersonationUsers] = useState(false);
const primaryLine = showCallsign ? user.username : user.displayName;
const secondaryLine = showCallsign ? user.displayName : user.username;
@ -53,6 +61,24 @@ export function NavUser({
router.refresh();
};
const handleOpenImpersonationModal = async () => {
setLoadingImpersonationUsers(true);
try {
const users = await getImpersonationUsers();
setImpersonationUsers(users);
} catch (error) {
console.error("Failed to load impersonation users:", error);
} finally {
setLoadingImpersonationUsers(false);
}
setShowImpersonationModal(true);
};
const handleImpersonationStarted = async () => {
router.push("/");
router.refresh();
};
return (
<SidebarMenu>
<SidebarMenuItem>
@ -122,10 +148,12 @@ export function NavUser({
Account
</Link>
</DropdownMenuItem>
{/* <DropdownMenuItem> */}
{/* <Bell /> */}
{/* Notifications */}
{/* </DropdownMenuItem> */}
{canImpersonate && (
<DropdownMenuItem onClick={handleOpenImpersonationModal}>
<Users2 />
Impersonate
</DropdownMenuItem>
)}
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem onSelect={() => handleLogout()}>
@ -135,6 +163,12 @@ export function NavUser({
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
<ImpersonationModal
open={showImpersonationModal}
onOpenChange={setShowImpersonationModal}
users={impersonationUsers}
onStart={handleImpersonationStarted}
/>
</SidebarMenu>
);
}