diff --git a/.claude/settings.local.json b/.claude/settings.local.json index eda2924..fc2f304 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -85,7 +85,7 @@ "hooks": [ { "type": "prompt", - "prompt": "Verifique se as alterações feitas nesta sessão afetam alguma informação documentada no CLAUDE.md ou nos ficheiros em docs/. Se sim, atualize os ficheiros relevantes para refletir as mudanças (novos componentes, mudanças de arquitetura, novos utilitários, alterações no modelo de dados, etc.). Se nenhuma alteração relevante foi feita, não é necessário atualizar." + "prompt": "Se as alterações desta sessão introduziram novos componentes públicos exportados, mudanças de arquitetura, novos hooks/utilitários partilhados, alterações no modelo de dados ou novos padrões de código — atualize os ficheiros relevantes em CLAUDE.md ou docs/. Mudanças puramente visuais (animações, z-index, estilos, refactors internos de componente) não requerem atualização de documentação e esta verificação pode terminar sem acção." } ] } diff --git a/src/components/AppSidebar.tsx b/src/components/AppSidebar.tsx index e503ef0..e48b223 100644 --- a/src/components/AppSidebar.tsx +++ b/src/components/AppSidebar.tsx @@ -25,12 +25,6 @@ import { Tooltip, TooltipTrigger, TooltipContent, - DropdownMenu, - DropdownMenuTrigger, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, } from "@/components/ui" import { canAccessView } from "@/lib/accessControl" @@ -103,6 +97,121 @@ function getInitials(email?: string) { return name.slice(0, 2).toUpperCase() } +interface UserIdentityPanelProps { + userEmail?: string + userAvatarUrl?: string | null + onGoToProfile: () => void + onSignOut: () => void +} + +function UserIdentityPanel({ userEmail, userAvatarUrl, onGoToProfile, onSignOut }: UserIdentityPanelProps) { + const [open, setOpen] = useState(false) + const triggerRef = useRef(null) + const panelRef = useRef(null) + + useEffect(() => { + if (!open) return + function handleClick(e: MouseEvent) { + if ( + !triggerRef.current?.contains(e.target as Node) && + !panelRef.current?.contains(e.target as Node) + ) { + setOpen(false) + } + } + document.addEventListener("mousedown", handleClick) + return () => document.removeEventListener("mousedown", handleClick) + }, [open]) + + const initials = getInitials(userEmail) + const displayName = userEmail?.split("@")[0].replace(/[._-]/g, " ") ?? "" + const formattedName = displayName + .split(" ") + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(" ") + + return ( +
+ setOpen((v) => !v)} + whileHover={{ scale: 1.06 }} + whileTap={{ scale: 0.94 }} + transition={{ type: "spring", stiffness: 400, damping: 28 }} + className={cn( + "w-9 h-9 rounded-xl flex items-center justify-center text-[11px] font-bold overflow-hidden shrink-0 cursor-pointer", + "bg-foreground text-background", + open && "ring-2 ring-foreground/30 ring-offset-1 ring-offset-[oklch(0.948_0.005_250)] dark:ring-offset-[oklch(0.138_0.009_250)]" + )} + aria-label="Menu do utilizador" + aria-expanded={open} + > + {userAvatarUrl ? ( + + ) : ( + initials + )} + + + + {open && ( + + {/* Identity header */} +
+
+ {userAvatarUrl ? ( + + ) : ( + initials + )} +
+
+

{formattedName}

+

{userEmail}

+
+
+ + {/* Actions */} +
+ {[ + { icon: UserCircle, label: "Meu Perfil", action: () => { onGoToProfile(); setOpen(false) }, delay: 0.04 }, + { icon: LogOut, label: "Sair", action: () => { onSignOut(); setOpen(false) }, delay: 0.08 }, + ].map(({ icon: Icon, label, action, delay }) => ( + + + {label} + + ))} +
+
+ )} +
+
+ ) +} + function getClientInitials(name: string) { const parts = name.trim().split(/\s+/) if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase() @@ -615,41 +724,13 @@ export function AppSidebar() { Ajuda - {/* User avatar */} - - - - - - - {userEmail} - - - onViewChange("profile")}> - - Meu Perfil - - - - - Sair - - - + {/* User avatar — identity panel */} + onViewChange("profile")} + onSignOut={onSignOut} + /> ) diff --git a/src/components/ClientPickerLayout.tsx b/src/components/ClientPickerLayout.tsx index 11f2603..d0b0bcd 100644 --- a/src/components/ClientPickerLayout.tsx +++ b/src/components/ClientPickerLayout.tsx @@ -70,7 +70,7 @@ export function ClientPickerLayout({
{/* Mini-sidebar — sempre recolhida */} -