Files
Deep-Package-Inspection/src/components/layout/SidebarProfile.tsx
T

166 lines
6.8 KiB
TypeScript

// src/components/layout/SidebarProfile.tsx
"use client";
import { useState, useRef, useEffect } from "react";
import { User as UserIcon, LogOut, Eye } from "lucide-react";
import { useRouter } from "next/navigation";
import { SafeImage } from "../ui/SafeImage";
import { getViewAsStatusSync } from "@/lib/admin-api";
const ROLE_DISPLAY_NAMES: Record<string, string> = {
SUPER_ADMIN: 'Super Admin',
EXECUTIVE: 'Executive',
TENANT_ADMIN: 'Tenant Admin',
SOC_ANALYST: 'SOC Analyst',
ENGINEER: 'Engineer',
AGENT_VIEWER: 'Agent Viewer',
// Company-facing roles — client/customer accounts
COMPANY_ADMIN: 'Company Admin',
COMPANY_OPERATOR: 'Company Operator',
COMPANY_VIEWER: 'Company Viewer',
};
const getRoleDisplayName = (role?: string) => {
if (!role) return 'Standard User';
return ROLE_DISPLAY_NAMES[role] || 'Standard User';
};
interface ProfileProps {
user: any;
onOpenSettings: () => void;
}
export default function SidebarProfile({ user, onOpenSettings }: ProfileProps) {
const [isAccountDropdownOpen, setIsAccountDropdownOpen] = useState(false);
const [viewAsStatus, setViewAsStatus] = useState<any | null>(null);
const accountDropdownRef = useRef<HTMLDivElement>(null);
const router = useRouter();
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (accountDropdownRef.current && !accountDropdownRef.current.contains(event.target as Node)) {
setIsAccountDropdownOpen(false);
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
useEffect(() => {
// Cek apakah sedang dalam mode View-As untuk menampilkan label yang benar
const status = getViewAsStatusSync();
setViewAsStatus(status);
}, []);
// Jika dalam View-As mode, gunakan label dari view-as (bukan role dari server yang jadi AGENT_VIEWER)
const isViewAsMode = viewAsStatus?.active;
// Bangun label yang ditampilkan di sidebar bawah profil
const roleLabel = (() => {
if (!isViewAsMode) return getRoleDisplayName(user?.role);
const viewAsType = viewAsStatus?.view_as_type;
if (viewAsType === 'user') {
const userName = viewAsStatus?.user_name || 'Unknown';
const userRole = viewAsStatus?.user_role || '';
const roleShort = ROLE_DISPLAY_NAMES[userRole] || userRole.replace(/_/g, ' ');
return `Viewing as: ${userName} (${roleShort})`;
}
// Direct agent view-as: tampilkan label agent
const agentLabel = viewAsStatus?.agent_label || viewAsStatus?.agent_uuid || 'Unknown';
return `Viewing Agent: ${agentLabel}`;
})();
const initial = user?.account_name
? user.account_name.charAt(0)
: (user?.username ? user.username.charAt(0) : 'U');
return (
<div className="relative flex-1 min-w-0" ref={accountDropdownRef}>
<button
onClick={() => setIsAccountDropdownOpen(!isAccountDropdownOpen)}
className="flex w-full items-center gap-3 rounded-xl p-2 hover:bg-secondary/50 transition-colors"
>
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-gradient-to-tr from-primary to-blue-400 overflow-hidden">
{user?.profile_picture ? (
<SafeImage src={`/api/uploads/${user.profile_picture}`} alt="Profile" className="w-full h-full object-cover" />
) : (
<span className="font-bold text-white uppercase text-xs">
{initial}
</span>
)}
</div>
<div className="flex flex-col items-start overflow-hidden text-left min-w-0 flex-1">
<span className="text-sm font-semibold text-foreground truncate w-full">
{user?.account_name || user?.username || 'Loading...'}
</span>
<span className={`text-[10px] truncate w-full ${isViewAsMode ? 'text-amber-400' : 'text-primary'}`}>
{isViewAsMode && <Eye className="inline w-2.5 h-2.5 mr-0.5" />}
{roleLabel}
</span>
</div>
</button>
{isAccountDropdownOpen && (
<div className="absolute bottom-full left-0 mb-2 w-60 lg:bottom-0 lg:left-full lg:ml-4 rounded-xl border border-border bg-card/95 backdrop-blur-xl p-2 shadow-2xl z-50 animate-fade-in">
<div className="flex flex-col items-center justify-center p-3 mb-2 border-b border-border/50">
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full bg-gradient-to-tr from-primary to-blue-400 overflow-hidden mb-2">
{user?.profile_picture ? (
<SafeImage src={`/api/uploads/${user.profile_picture}`} alt="Profile" className="w-full h-full object-cover" />
) : (
<span className="font-bold text-white uppercase text-lg">
{initial}
</span>
)}
</div>
<span className="text-sm font-bold text-foreground text-center break-words w-full leading-tight">
{user?.account_name || user?.username || 'Loading...'}
</span>
{isViewAsMode ? (
<span className="text-xs text-amber-400 mt-1 text-center font-medium flex items-center gap-1">
<Eye className="w-3 h-3" />
{(() => {
const vt = viewAsStatus?.view_as_type;
if (vt === 'user') {
const un = viewAsStatus?.user_name || 'Unknown';
const ur = ROLE_DISPLAY_NAMES[viewAsStatus?.user_role] || viewAsStatus?.user_role?.replace(/_/g, ' ') || '';
return `${un} (${ur})`;
}
return viewAsStatus?.agent_label || viewAsStatus?.agent_uuid || 'Unknown';
})()}
</span>
) : (
<span className="text-xs text-primary mt-1 text-center font-medium">
{getRoleDisplayName(user?.role)}
</span>
)}
</div>
{!isViewAsMode && (
<button
onClick={() => {
setIsAccountDropdownOpen(false);
onOpenSettings();
}}
className="w-full flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-muted-foreground hover:bg-secondary hover:text-foreground transition-colors"
>
<UserIcon className="h-4 w-4" /> Account Settings
</button>
)}
<div className="h-px bg-border/50 my-1 mx-2" />
<button
onClick={async () => {
await fetch('/api/auth/logout', { method: 'POST' });
window.location.href = "/login";
}}
className="w-full flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-red-500 hover:bg-red-500/10 transition-colors"
>
<LogOut className="h-4 w-4" /> Sign Out
</button>
</div>
)}
</div>
);
}