MCPcopy Create free account
hub / github.com/Olanetsoft/article-idea-generator / UserMenu

Function UserMenu

components/UserMenu/UserMenu.tsx:68–235  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

66 className="flex items-center gap-3 px-3 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors"
67 >
68 {icon}
69 {label}
70 </Link>
71 );
72}
73
74export function UserMenu() {
75 const { user, profile, isLoading, signOut } = useAuth();
76 const [isOpen, setIsOpen] = useState(false);
77 const menuRef = useRef<HTMLDivElement>(null);
78 const triggerRef = useRef<HTMLButtonElement>(null);
79
80 // Close on outside click / Escape, returning focus to the trigger
81 useDismiss(menuRef, isOpen, () => setIsOpen(false), {
82 returnFocusRef: triggerRef,
83 });
84
85 const handleSignOut = async () => {
86 try {
87 await signOut();
88 setIsOpen(false);
89 } catch (error) {
90 console.error("Sign out error:", error);
91 toast.error("Failed to sign out. Please try again.");
92 }
93 };
94
95 const closeMenu = () => setIsOpen(false);
96
97 if (isLoading) {
98 // Placeholder avoids the signed-in state flashing to nothing/"Sign In"
99 return (
100 <div
101 className="w-8 h-8 rounded-full bg-gray-200 dark:bg-zinc-800 animate-pulse"
102 aria-hidden="true"
103 />
104 );
105 }
106
107 if (!user) {
108 return null;
109 }
110
111 // Get avatar URL from profile OR from user metadata (Google provides it in user_metadata)
112 const avatarUrl =
113 profile?.avatar_url ||
114 (user.user_metadata?.avatar_url as string | undefined) ||
115 (user.user_metadata?.picture as string | undefined);
116 const fullName =
117 profile?.full_name ||
118 (user.user_metadata?.full_name as string | undefined) ||
119 (user.user_metadata?.name as string | undefined);
120
121 return (
122 <div ref={menuRef} className="relative">
123 <button
124 ref={triggerRef}
125 onClick={() => setIsOpen(!isOpen)}

Callers

nothing calls this directly

Calls 1

useAuthFunction · 0.90

Tested by

no test coverage detected