feat: /settings page, move theme picker out of Header dropdown

Create dedicated /settings page with Внешний вид section (accent color
presets). Header dropdown now has a Settings link + Logout button only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 13:19:23 +03:00
parent d2f7332701
commit 9e12a7721b
2 changed files with 72 additions and 29 deletions

View File

@@ -0,0 +1,59 @@
'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import { useAuthStore } from '@/store/authStore'
import { useThemeStore, ACCENT_PRESETS } from '@/store/themeStore'
import Header from '@/components/Header'
export default function SettingsPage() {
const { user } = useAuthStore()
const { accentIdx, setAccent } = useThemeStore()
const router = useRouter()
useEffect(() => {
if (!user) router.replace('/login')
}, [user, router])
if (!user) return null
return (
<main className="max-w-app mx-auto relative z-10">
<Header />
<div className="animate-fadeUp">
<h2 className="font-display text-xl font-extrabold tracking-tight text-app-text mb-5">Настройки</h2>
{/* Appearance */}
<section className="bg-surface border border-white/[0.07] rounded-app p-5 mb-3">
<p className="text-[11px] font-display font-semibold tracking-[1.2px] uppercase text-muted mb-4">
Внешний вид
</p>
<p className="text-[13px] text-muted mb-3">Акцентный цвет</p>
<div className="flex flex-wrap gap-2.5">
{ACCENT_PRESETS.map((preset, i) => (
<button
key={i}
onClick={() => setAccent(i)}
className="flex items-center gap-2.5 px-3.5 py-2.5 rounded-[10px] text-[13px] font-display font-semibold transition-all duration-150 cursor-pointer border"
style={accentIdx === i
? { background: `rgba(${preset.rgb},0.12)`, color: preset.accent, borderColor: `${preset.accent}55` }
: { background: 'rgba(255,255,255,0.03)', color: '#666', borderColor: 'rgba(255,255,255,0.07)' }
}
>
<span
className="w-3.5 h-3.5 rounded-full shrink-0"
style={{
background: preset.accent,
boxShadow: accentIdx === i ? `0 0 8px ${preset.accent}90` : 'none',
}}
/>
{preset.name}
</button>
))}
</div>
</section>
</div>
</main>
)
}

View File

@@ -4,11 +4,9 @@ import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import { useRef, useState, useEffect } from 'react' import { useRef, useState, useEffect } from 'react'
import { useAuthStore } from '@/store/authStore' import { useAuthStore } from '@/store/authStore'
import { useThemeStore, ACCENT_PRESETS } from '@/store/themeStore'
export default function Header() { export default function Header() {
const { user, clearAuth } = useAuthStore() const { user, clearAuth } = useAuthStore()
const { accentIdx, setAccent } = useThemeStore()
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@@ -111,34 +109,20 @@ export default function Header() {
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" 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)' }} style={{ boxShadow: '0 8px 32px rgba(0,0,0,0.5)' }}
> >
{/* Theme section */} {/* Settings link */}
<div className="px-3 pt-2.5 pb-2"> <Link
<p className="text-[10px] font-display font-semibold tracking-[1px] uppercase text-muted mb-2"> href="/settings"
Цвет темы onClick={() => setOpen(false)}
</p> className="flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium text-app-text hover:bg-white/[0.04] transition-all duration-150"
<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 <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
className="w-2.5 h-2.5 rounded-full shrink-0" <circle cx="12" cy="12" r="3" />
style={{ background: preset.accent, boxShadow: accentIdx === i ? `0 0 5px ${preset.accent}90` : 'none' }} <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
/> </svg>
{preset.name} Настройки
</button> </Link>
))}
</div>
</div>
<div className="border-t border-white/[0.07] mx-1 my-1" /> <div className="border-t border-white/[0.07] mx-1 my-0.5" />
{/* Logout */} {/* Logout */}
<button <button