183 lines
5.9 KiB
TypeScript
183 lines
5.9 KiB
TypeScript
import { useEffect, useState } from 'react'
|
||
import AppBar from '@mui/material/AppBar'
|
||
import Badge from '@mui/material/Badge'
|
||
import Box from '@mui/material/Box'
|
||
import Button from '@mui/material/Button'
|
||
import IconButton from '@mui/material/IconButton'
|
||
import { alpha, useTheme } from '@mui/material/styles'
|
||
import Toolbar from '@mui/material/Toolbar'
|
||
import Tooltip from '@mui/material/Tooltip'
|
||
import Typography from '@mui/material/Typography'
|
||
import useMediaQuery from '@mui/material/useMediaQuery'
|
||
import { useQuery } from '@tanstack/react-query'
|
||
import { useUnit } from 'effector-react'
|
||
import { Menu, Package } from 'lucide-react'
|
||
import { Link as RouterLink, useNavigate } from 'react-router-dom'
|
||
import { useThemeController } from '@/app/providers/theme-controller'
|
||
import { fetchMyCart } from '@/entities/cart/api/cart-api'
|
||
import { fetchMyOrders } from '@/entities/order/api/order-api'
|
||
import { CartBadge } from '@/features/cart/cart-badge'
|
||
import { UserMenu } from '@/features/user/user-menu'
|
||
import { STORE_NAME } from '@/shared/config'
|
||
import { $user, logout, tokenSet } from '@/shared/model/auth'
|
||
import type { ColorScheme } from '@/shared/model/theme'
|
||
import { BearLogo } from '@/shared/ui/BearLogo'
|
||
import { ModeSwitcher } from '@/shared/ui/ModeSwitcher'
|
||
import { SchemeSwitcher } from '@/shared/ui/SchemeSwitcher'
|
||
import { NavigationDrawer } from '@/widgets/navigation-drawer'
|
||
|
||
type NavItem = { label: string; to: string }
|
||
|
||
const navItems: NavItem[] = [{ label: 'Каталог', to: '/' }]
|
||
|
||
export function AppHeader() {
|
||
const { mode, resolvedMode, scheme, setScheme, cycleMode } = useThemeController()
|
||
const user = useUnit($user)
|
||
const navigate = useNavigate()
|
||
const isAdmin = Boolean(user?.isAdmin)
|
||
const headerNavItems = isAdmin ? [...navItems, { label: 'Админка', to: '/admin' }] : navItems
|
||
const theme = useTheme()
|
||
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
|
||
|
||
const cartQuery = useQuery({
|
||
queryKey: ['me', 'cart'],
|
||
queryFn: fetchMyCart,
|
||
enabled: Boolean(user) && !isAdmin,
|
||
})
|
||
const cartCount = cartQuery.data?.items?.length ?? 0
|
||
|
||
const ordersQuery = useQuery({
|
||
queryKey: ['me', 'orders'],
|
||
queryFn: fetchMyOrders,
|
||
enabled: Boolean(user) && !isAdmin,
|
||
})
|
||
const activeOrdersCount = (ordersQuery.data?.items ?? []).filter(
|
||
(o) => o.status !== 'DONE' && o.status !== 'CANCELLED',
|
||
).length
|
||
|
||
const [mobileOpen, setMobileOpen] = useState(false)
|
||
const [scrolled, setScrolled] = useState(false)
|
||
|
||
useEffect(() => {
|
||
const handler = () => setScrolled(window.scrollY > 0)
|
||
handler()
|
||
window.addEventListener('scroll', handler, { passive: true })
|
||
return () => window.removeEventListener('scroll', handler)
|
||
}, [])
|
||
|
||
const go = (to: string) => {
|
||
setMobileOpen(false)
|
||
navigate(to)
|
||
}
|
||
|
||
const onLogout = () => {
|
||
tokenSet(null)
|
||
logout()
|
||
setMobileOpen(false)
|
||
navigate('/')
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<AppBar
|
||
position="sticky"
|
||
color="primary"
|
||
elevation={scrolled ? 2 : 0}
|
||
sx={{
|
||
borderBottom: 1,
|
||
borderColor: 'divider',
|
||
bgcolor: alpha(theme.palette.primary.main, 0.88),
|
||
backdropFilter: 'blur(8px)',
|
||
transition: 'box-shadow 0.2s ease, background-color 0.2s ease',
|
||
}}
|
||
>
|
||
<Toolbar>
|
||
{isMobile && (
|
||
<IconButton
|
||
color="inherit"
|
||
onClick={() => setMobileOpen(true)}
|
||
aria-label="Открыть меню"
|
||
edge="start"
|
||
sx={{ mr: 1 }}
|
||
>
|
||
<Menu />
|
||
</IconButton>
|
||
)}
|
||
|
||
<Box
|
||
component={RouterLink}
|
||
to="/"
|
||
sx={{
|
||
flexGrow: 1,
|
||
textDecoration: 'none',
|
||
color: 'inherit',
|
||
minWidth: 0,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: 1,
|
||
}}
|
||
>
|
||
<BearLogo sx={{ fontSize: 28 }} />
|
||
<Typography variant="h6" sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
||
{STORE_NAME}
|
||
</Typography>
|
||
</Box>
|
||
|
||
{!isMobile &&
|
||
headerNavItems.map((i) => (
|
||
<Button key={i.to} component={RouterLink} to={i.to} color="inherit">
|
||
{i.label}
|
||
</Button>
|
||
))}
|
||
|
||
{!isAdmin && (
|
||
<>
|
||
{user && (
|
||
<Tooltip title="Заказы">
|
||
<IconButton color="inherit" sx={{ ml: 1 }} onClick={() => navigate('/me/orders')} aria-label="Заказы">
|
||
<Badge color="secondary" badgeContent={activeOrdersCount} invisible={activeOrdersCount === 0}>
|
||
<Package />
|
||
</Badge>
|
||
</IconButton>
|
||
</Tooltip>
|
||
)}
|
||
|
||
<CartBadge user={user} cartCount={cartCount} onNavigate={navigate} />
|
||
</>
|
||
)}
|
||
|
||
{!isAdmin && <UserMenu user={user} onNavigate={navigate} onLogout={onLogout} />}
|
||
|
||
{isAdmin && user && !isMobile && (
|
||
<Button color="inherit" onClick={onLogout} sx={{ ml: 1 }}>
|
||
Выход
|
||
</Button>
|
||
)}
|
||
|
||
{!isMobile && (
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, ml: 1.5 }}>
|
||
<SchemeSwitcher value={scheme} onChange={(s: ColorScheme) => setScheme(s)} />
|
||
<ModeSwitcher mode={mode} resolvedMode={resolvedMode} onCycleMode={cycleMode} />
|
||
</Box>
|
||
)}
|
||
</Toolbar>
|
||
</AppBar>
|
||
|
||
<NavigationDrawer
|
||
open={mobileOpen}
|
||
onClose={() => setMobileOpen(false)}
|
||
user={user}
|
||
isAdmin={isAdmin}
|
||
navItems={headerNavItems}
|
||
scheme={scheme}
|
||
mode={mode}
|
||
resolvedMode={resolvedMode}
|
||
onSchemeChange={(s: ColorScheme) => setScheme(s)}
|
||
onCycleMode={cycleMode}
|
||
onNavigate={go}
|
||
onLogout={onLogout}
|
||
/>
|
||
</>
|
||
)
|
||
}
|