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:
59
apps/web/src/app/settings/page.tsx
Normal file
59
apps/web/src/app/settings/page.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -4,11 +4,9 @@ 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)
|
||||
@@ -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"
|
||||
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)' }
|
||||
}
|
||||
{/* Settings link */}
|
||||
<Link
|
||||
href="/settings"
|
||||
onClick={() => setOpen(false)}
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<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>
|
||||
Настройки
|
||||
</Link>
|
||||
|
||||
<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 */}
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user