feat: улучшена навигационная панель с полной интеграцией темы и локализации
Some checks failed
continuous-integration/drone/pr Build is failing

- Обновлен LayoutWrapper с улучшенным UI навигации
- Добавлен dropdown меню пользователя с аватаром
- Интегрированы ThemeToggle и LanguageSelector в навигацию
- Переключатели темы и языка теперь всегда видны
- Добавлены флаги стран в селектор языков
- Создана страница редактирования профиля /profile
- Улучшены стили для темной темы в navbar
- Добавлены CSS стили для навигации и профиля
This commit is contained in:
2025-11-09 15:45:01 +09:00
parent 2ef7b4fa95
commit a963281be0
7 changed files with 1771 additions and 58 deletions

View File

@@ -13,7 +13,10 @@ import LanguageSelector from './LanguageSelector'
import '../layout.css'
interface User {
id: number
username: string
email: string
full_name: string
avatar: string | null
}
@@ -37,9 +40,14 @@ export function LayoutWrapper({ children }: { children: ReactNode }) {
return res.json()
})
.then(data => {
// fullname или username
const name = data.full_name?.trim() || data.username
setUser({ username: name, avatar: data.avatar })
// Заполняем полную информацию о пользователе
setUser({
id: data.id,
username: data.username,
email: data.email,
full_name: data.full_name || '',
avatar: data.avatar
})
})
.catch(() => {
// сбросить некорректный токен
@@ -61,62 +69,111 @@ export function LayoutWrapper({ children }: { children: ReactNode }) {
<>
{/* Шапка не выводим на публичных страницах /[username] */}
{!isPublicUserPage && (
<nav className="navbar navbar-expand bg-light fixed-top shadow-sm">
<nav className="navbar navbar-expand-lg theme-bg-secondary fixed-top shadow-sm border-bottom theme-border">
<div className="container">
<Link href="/" className="navbar-brand d-flex align-items-center">
<Image
src="/assets/img/CAT.png"
alt="CatLink"
width={89}
height={89}
width={32}
height={32}
className="me-2"
/>
<span className="ms-2">CatLink</span>
<span className="fw-bold">CatLink</span>
</Link>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navcol-1"
title={t('common.menu')}
/>
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navcol-1">
{!user && (
<Link href="/auth/login" className="btn btn-primary ms-auto">
<i className="fa fa-user"></i>
<span className="d-none d-sm-inline"> {t('common.login')}</span>
</Link>
)}
{user && (
<div className="ms-auto d-flex align-items-center gap-3">
<ThemeToggle />
<LanguageSelector />
<Image
src={
user.avatar && user.avatar.startsWith('http')
? user.avatar
: user.avatar
? `${process.env.NEXT_PUBLIC_API_URL || 'https://links.shareon.kr'}${user.avatar}`
: '/assets/img/avatar-dhg.png'
}
alt="Avatar"
width={32}
height={32}
className="rounded-circle"
/>
<span>{user.username}</span>
{!isDashboard && (
<Link href="/dashboard" className="btn btn-outline-secondary btn-sm">
{/* Левое меню */}
<ul className="navbar-nav me-auto">
{user && (
<li className="nav-item">
<Link href="/dashboard" className="nav-link">
<i className="fas fa-tachometer-alt me-1"></i>
{t('dashboard.title')}
</Link>
)}
<button
onClick={handleLogout}
className="btn btn-outline-danger btn-sm"
>
{t('common.logout')}
</button>
</div>
)}
</li>
)}
</ul>
{/* Правое меню */}
<div className="d-flex align-items-center gap-2">
{/* Переключатели темы и языка всегда видны */}
<ThemeToggle />
<LanguageSelector />
{!user ? (
<div className="d-flex gap-2 ms-2">
<Link href="/auth/login" className="btn btn-outline-primary btn-sm">
<i className="fas fa-sign-in-alt me-1"></i>
<span className="d-none d-sm-inline">{t('common.login')}</span>
</Link>
<Link href="/auth/register" className="btn btn-primary btn-sm">
<i className="fas fa-user-plus me-1"></i>
<span className="d-none d-sm-inline">{t('common.register')}</span>
</Link>
</div>
) : (
<div className="dropdown ms-2">
<button
className="btn btn-link text-decoration-none d-flex align-items-center dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<Image
src={
user.avatar && user.avatar.startsWith('http')
? user.avatar
: user.avatar
? `${process.env.NEXT_PUBLIC_API_URL || 'https://links.shareon.kr'}${user.avatar}`
: '/assets/img/avatar-dhg.png'
}
alt="Avatar"
width={32}
height={32}
className="rounded-circle me-2"
/>
<span className="text-dark fw-medium d-none d-md-inline">
{user.full_name?.trim() || user.username}
</span>
</button>
<ul className="dropdown-menu dropdown-menu-end">
<li>
<Link href="/profile" className="dropdown-item">
<i className="fas fa-user me-2"></i>
{t('profile.edit')}
</Link>
</li>
<li>
<Link href="/dashboard" className="dropdown-item">
<i className="fas fa-tachometer-alt me-2"></i>
{t('dashboard.title')}
</Link>
</li>
<li><hr className="dropdown-divider" /></li>
<li>
<button
onClick={handleLogout}
className="dropdown-item text-danger"
>
<i className="fas fa-sign-out-alt me-2"></i>
{t('common.logout')}
</button>
</li>
</ul>
</div>
)}
</div>
</div>
</div>
</nav>