feat: user dropdown in Header with theme picker and logout
Replace static logout button with a dropdown triggered by clicking the username badge. Dropdown shows accent color presets and a logout button. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,31 @@
|
||||
'use client'
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
import { useThemeStore, ACCENT_PRESETS } from '@/store/themeStore'
|
||||
|
||||
export default function Header() {
|
||||
const { user, clearAuth } = useAuthStore()
|
||||
const { accentIdx, setAccent } = useThemeStore()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const [open, setOpen] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
if (open) document.addEventListener('mousedown', handler)
|
||||
return () => document.removeEventListener('mousedown', handler)
|
||||
}, [open])
|
||||
|
||||
const handleLogout = () => {
|
||||
setOpen(false)
|
||||
clearAuth()
|
||||
router.push('/')
|
||||
}
|
||||
@@ -68,25 +84,76 @@ export default function Header() {
|
||||
<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
</svg>
|
||||
)}
|
||||
<div className="flex items-center gap-2 bg-surface border border-white/[0.07] rounded-xl px-3 py-1.5">
|
||||
<div className="w-5 h-5 rounded-md bg-accent/20 flex items-center justify-center shrink-0">
|
||||
<span className="text-[10px] font-display font-extrabold text-accent">
|
||||
{user.username[0].toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[12px] font-medium text-app-text hidden sm:block">{user.username}</span>
|
||||
|
||||
{/* User badge with dropdown */}
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="flex items-center gap-1 text-[11px] font-display font-semibold text-muted hover:text-[#ff6b6b] transition-colors duration-150 cursor-pointer ml-1 border-l border-white/[0.07] pl-2"
|
||||
title="Выйти из аккаунта"
|
||||
onClick={() => setOpen(v => !v)}
|
||||
className="flex items-center gap-2 bg-surface border border-white/[0.07] rounded-xl px-3 py-1.5 cursor-pointer transition-colors duration-150 hover:border-white/[0.14]"
|
||||
>
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<polyline points="16,17 21,12 16,7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
<div className="w-5 h-5 rounded-md bg-accent/20 flex items-center justify-center shrink-0">
|
||||
<span className="text-[10px] font-display font-extrabold text-accent">
|
||||
{user.username[0].toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[12px] font-medium text-app-text hidden sm:block">{user.username}</span>
|
||||
<svg
|
||||
width="10" height="10" viewBox="0 0 24 24" fill="none"
|
||||
className="text-muted transition-transform duration-200 ml-0.5"
|
||||
style={{ transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||||
>
|
||||
<path d="M6 9l6 6 6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">Выйти</span>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div
|
||||
className="absolute right-0 top-full mt-1.5 w-52 bg-surface border border-white/[0.09] rounded-xl shadow-xl z-50 py-1 overflow-hidden"
|
||||
style={{ boxShadow: '0 8px 32px rgba(0,0,0,0.5)' }}
|
||||
>
|
||||
{/* Theme section */}
|
||||
<div className="px-3 pt-2.5 pb-2">
|
||||
<p className="text-[10px] font-display font-semibold tracking-[1px] uppercase text-muted mb-2">
|
||||
Цвет темы
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{ACCENT_PRESETS.map((preset, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setAccent(i)}
|
||||
title={preset.name}
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded-lg text-[11px] font-display font-semibold transition-all duration-150 cursor-pointer border"
|
||||
style={accentIdx === i
|
||||
? { background: `rgba(${preset.rgb},0.15)`, color: preset.accent, borderColor: `${preset.accent}55` }
|
||||
: { background: 'transparent', color: '#555', borderColor: 'rgba(255,255,255,0.06)' }
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="w-2.5 h-2.5 rounded-full shrink-0"
|
||||
style={{ background: preset.accent, boxShadow: accentIdx === i ? `0 0 5px ${preset.accent}90` : 'none' }}
|
||||
/>
|
||||
{preset.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-white/[0.07] mx-1 my-1" />
|
||||
|
||||
{/* Logout */}
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="w-full flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium text-muted hover:text-[#ff6b6b] hover:bg-white/[0.04] transition-all duration-150 cursor-pointer"
|
||||
>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<polyline points="16,17 21,12 16,7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<line x1="21" y1="12" x2="9" y2="12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
||||
</svg>
|
||||
Выйти
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user