- Create code-first route tree in src/app/routing/routeTree.tsx - Replace ProtectedRoute with beforeLoad auth guards - Add useSearchParamsCompat for URLSearchParams access - Update App.tsx, layouts, and all page/widget imports - Add frontend tooling: biome, prettier, env config - Update all tests for TanStack Router compatibility - Remove react-router-dom dependency, @tanstack/router-plugin - Consolidate biome config at root level
143 lines
3.5 KiB
TypeScript
143 lines
3.5 KiB
TypeScript
import { Link, Outlet, useNavigate } from '@tanstack/react-router'
|
||
import { useSession } from '@/entities/session'
|
||
import { SearchBar } from '@/widgets/search-bar'
|
||
|
||
export function AppLayout() {
|
||
const { isAuthenticated, user, logout } = useSession()
|
||
const navigate = useNavigate()
|
||
|
||
async function handleLogout() {
|
||
await logout()
|
||
navigate('/')
|
||
}
|
||
|
||
return (
|
||
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
|
||
<header
|
||
style={{
|
||
background: 'var(--color-surface)',
|
||
borderBottom: '1px solid #e0e0e0',
|
||
padding: '12px 24px',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
flexWrap: 'wrap',
|
||
gap: 24,
|
||
}}
|
||
>
|
||
<Link
|
||
to="/"
|
||
style={{
|
||
fontSize: 20,
|
||
fontWeight: 700,
|
||
color: 'var(--color-text)',
|
||
textDecoration: 'none',
|
||
}}
|
||
>
|
||
MoexVibe
|
||
</Link>
|
||
<div style={{ flex: '1 1 280px', minWidth: 220, maxWidth: 420 }}>
|
||
<SearchBar />
|
||
</div>
|
||
<Link
|
||
to="/portfolios"
|
||
style={{
|
||
fontSize: 14,
|
||
color: 'var(--color-text)',
|
||
textDecoration: 'none',
|
||
fontWeight: 500,
|
||
}}
|
||
>
|
||
Портфели
|
||
</Link>
|
||
<Link
|
||
to="/broker"
|
||
style={{
|
||
fontSize: 14,
|
||
color: 'var(--color-text)',
|
||
textDecoration: 'none',
|
||
fontWeight: 500,
|
||
}}
|
||
>
|
||
Брокер
|
||
</Link>
|
||
<Link
|
||
to="/screener"
|
||
style={{
|
||
fontSize: 14,
|
||
color: 'var(--color-text)',
|
||
textDecoration: 'none',
|
||
fontWeight: 500,
|
||
}}
|
||
>
|
||
Скринер
|
||
</Link>
|
||
|
||
<div
|
||
style={{
|
||
marginLeft: 'auto',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: 12,
|
||
flexWrap: 'wrap',
|
||
}}
|
||
>
|
||
{isAuthenticated ? (
|
||
<>
|
||
<Link
|
||
to="/profile"
|
||
style={{
|
||
fontSize: 14,
|
||
color: 'var(--color-text)',
|
||
textDecoration: 'none',
|
||
}}
|
||
>
|
||
{user?.name || user?.email}
|
||
</Link>
|
||
<button
|
||
onClick={handleLogout}
|
||
style={{
|
||
padding: '6px 16px',
|
||
background: 'transparent',
|
||
color: 'var(--color-text-secondary)',
|
||
border: '1px solid #e0e0e0',
|
||
borderRadius: 'var(--border-radius)',
|
||
fontSize: 14,
|
||
cursor: 'pointer',
|
||
}}
|
||
>
|
||
Выйти
|
||
</button>
|
||
</>
|
||
) : (
|
||
<Link
|
||
to="/login"
|
||
style={{
|
||
padding: '6px 16px',
|
||
background: 'var(--color-primary)',
|
||
color: '#fff',
|
||
textDecoration: 'none',
|
||
borderRadius: 'var(--border-radius)',
|
||
fontSize: 14,
|
||
fontWeight: 600,
|
||
}}
|
||
>
|
||
Войти
|
||
</Link>
|
||
)}
|
||
</div>
|
||
</header>
|
||
<main
|
||
style={{
|
||
flex: 1,
|
||
padding: 'clamp(16px, 4vw, 24px)',
|
||
maxWidth: 1200,
|
||
width: '100%',
|
||
margin: '0 auto',
|
||
}}
|
||
>
|
||
<Outlet />
|
||
</main>
|
||
</div>
|
||
)
|
||
}
|