mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-13 00:14:02 +00:00
feat(frontend): top-right profile dropdown for OIDC sessions
Replace the OIDC user chip in the sidebar with an account avatar in the top-right action bar (the existing UserButton placeholder). The avatar — a brand-purple circle with the user's initial, or the IdP picture — opens a popover showing name, email, and a sign-out action, reusing the existing Avatar/Popover primitives with a hover ring. Renders only under AUTH_TYPE=oidc.
This commit is contained in:
1 parent
321d88d5be
commit
78c3fc1593
4 files changed
+88
-54
No files matched your search
@@ -4,7 +4,6 @@ import { useDispatch, useSelector } from 'react-redux';
|
||||
import { NavLink, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
LayoutGrid,
|
||||
LogOut,
|
||||
Menu,
|
||||
PanelLeftClose,
|
||||
PanelLeftOpen,
|
||||
@@ -79,15 +78,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
const selectedAgent = useSelector(selectSelectedAgent);
|
||||
|
||||
const { isMobile, isTablet } = useMediaQuery();
|
||||
const {
|
||||
showTokenModal,
|
||||
handleTokenSubmit,
|
||||
authType,
|
||||
logout,
|
||||
userEmail,
|
||||
userName,
|
||||
userPicture,
|
||||
} = useTokenAuth();
|
||||
const { showTokenModal, handleTokenSubmit } = useTokenAuth();
|
||||
|
||||
const [isDeletingConversation, setIsDeletingConversation] = useState(false);
|
||||
const [uploadModalState, setUploadModalState] =
|
||||
@@ -656,48 +647,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
{t('settings.label')}
|
||||
</p>
|
||||
</NavLink>
|
||||
{authType === 'oidc' && (
|
||||
<div className="mx-4 my-auto flex items-center gap-2.5 py-0.5 pr-1 pl-3">
|
||||
{userPicture ? (
|
||||
<Avatar
|
||||
src={userPicture}
|
||||
alt={userName || userEmail || 'User avatar'}
|
||||
className="size-6"
|
||||
imgClassName="size-6 rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<Avatar className="size-6">
|
||||
<span className="bg-sidebar-accent text-foreground flex size-6 items-center justify-center rounded-full text-xs font-medium dark:text-white">
|
||||
{(userName || userEmail || '?').charAt(0).toUpperCase()}
|
||||
</span>
|
||||
</Avatar>
|
||||
)}
|
||||
<span className="flex min-w-0 flex-1 flex-col items-start">
|
||||
{userName && (
|
||||
<p className="text-foreground max-w-[160px] truncate text-sm dark:text-white">
|
||||
{userName}
|
||||
</p>
|
||||
)}
|
||||
{userEmail && (
|
||||
<p className="text-muted-foreground max-w-[160px] truncate text-xs">
|
||||
{userEmail}
|
||||
</p>
|
||||
)}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={logout}
|
||||
data-testid="oidc-signout"
|
||||
aria-label={t('auth.signOut')}
|
||||
title={t('auth.signOut')}
|
||||
className="text-muted-foreground hover:text-foreground shrink-0 rounded-full"
|
||||
>
|
||||
<LogOut className="size-5" strokeWidth={1.75} />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-foreground flex flex-col justify-end dark:text-white">
|
||||
<div className="flex items-center justify-between py-1">
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
updateConversationId,
|
||||
} from '../conversation/conversationSlice';
|
||||
import { Button } from './ui/button';
|
||||
import ProfileButton from './ProfileButton';
|
||||
|
||||
interface ActionButtonsProps {
|
||||
className?: string;
|
||||
@@ -86,7 +87,7 @@ export default function ActionButtons({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div>{/* <UserButton /> */}</div>
|
||||
<ProfileButton />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { LogOut } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import useTokenAuth from '../hooks/useTokenAuth';
|
||||
import { Avatar } from './ui/avatar';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover';
|
||||
|
||||
/**
|
||||
* Top-right account menu for OIDC sessions: an avatar that opens a popover
|
||||
* showing the signed-in name/email and a sign-out action. Renders nothing for
|
||||
* other auth modes, which carry no user identity.
|
||||
*/
|
||||
export default function ProfileButton() {
|
||||
const { t } = useTranslation();
|
||||
const { authType, userName, userEmail, userPicture, logout } = useTokenAuth();
|
||||
|
||||
if (authType !== 'oidc' || (!userName && !userEmail)) return null;
|
||||
|
||||
const initial = (userName || userEmail || '?').charAt(0).toUpperCase();
|
||||
const renderAvatar = (size: string, text: string) =>
|
||||
userPicture ? (
|
||||
<Avatar
|
||||
src={userPicture}
|
||||
alt={userName || userEmail || 'User avatar'}
|
||||
className={size}
|
||||
imgClassName={`${size} rounded-full object-cover`}
|
||||
/>
|
||||
) : (
|
||||
<Avatar className={size}>
|
||||
<span
|
||||
className={`bg-primary text-primary-foreground flex ${size} items-center justify-center rounded-full ${text} font-medium`}
|
||||
>
|
||||
{initial}
|
||||
</span>
|
||||
</Avatar>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={t('auth.account')}
|
||||
className="ring-offset-background focus-visible:ring-ring hover:ring-primary/40 rounded-full transition outline-none hover:ring-2 hover:ring-offset-2 focus-visible:ring-2 focus-visible:ring-offset-2"
|
||||
>
|
||||
{renderAvatar('size-8', 'text-sm')}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" sideOffset={8} className="w-64 p-0">
|
||||
<div className="flex items-center gap-3 p-4">
|
||||
{renderAvatar('size-10', 'text-base')}
|
||||
<span className="flex min-w-0 flex-col">
|
||||
{userName && (
|
||||
<p className="text-foreground truncate text-sm font-medium dark:text-white">
|
||||
{userName}
|
||||
</p>
|
||||
)}
|
||||
{userEmail && (
|
||||
<p className="text-muted-foreground truncate text-xs">
|
||||
{userEmail}
|
||||
</p>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="dark:border-sidebar-border border-t" />
|
||||
<div className="p-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={logout}
|
||||
data-testid="oidc-signout"
|
||||
className="text-foreground hover:bg-sidebar-accent flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm dark:text-white"
|
||||
>
|
||||
<LogOut
|
||||
className="text-muted-foreground size-4 shrink-0"
|
||||
strokeWidth={1.75}
|
||||
/>
|
||||
{t('auth.signOut')}
|
||||
</button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -36,7 +36,8 @@
|
||||
"signInWith": "Sign in with {{provider}}",
|
||||
"notAuthorized": "Your account isn't authorized to access this workspace. Sign in with a different account or contact your administrator.",
|
||||
"accountDisabled": "Your account has been disabled. Contact your administrator.",
|
||||
"signOut": "Sign out"
|
||||
"signOut": "Sign out",
|
||||
"account": "Account"
|
||||
},
|
||||
"settings": {
|
||||
"label": "Settings",
|
||||
|
||||
Reference in new issue
Block a user