Merge branch 'refactor'

This commit is contained in:
@kirill.komarov
2026-05-13 22:07:46 +05:00
parent 3c9797af4a
commit a06f9cf2c4
85 changed files with 3762 additions and 2072 deletions
+3 -28
View File
@@ -1,37 +1,12 @@
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'
import { MainLayout } from '@/app/layout/MainLayout'
import { BrowserRouter } from 'react-router-dom'
import { AppProviders } from '@/app/providers/AppProviders'
import { AboutPage } from '@/pages/about'
import { AdminLayoutPage } from '@/pages/admin-layout'
import { AuthCallbackPage, AuthPage } from '@/pages/auth'
import { CartPage } from '@/pages/cart'
import { CheckoutPage } from '@/pages/checkout'
import { HomePage } from '@/pages/home'
import { InfoPage } from '@/pages/info'
import { MeLayoutPage } from '@/pages/me'
import { PrivacyPolicyPage } from '@/pages/privacy-policy'
import { ProductPage } from '@/pages/product'
import { AppRoutes } from '@/app/routes'
export function App() {
return (
<AppProviders>
<BrowserRouter>
<MainLayout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/admin/*" element={<AdminLayoutPage />} />
<Route path="/auth" element={<AuthPage />} />
<Route path="/auth/callback" element={<AuthCallbackPage />} />
<Route path="/cart" element={<CartPage />} />
<Route path="/checkout" element={<CheckoutPage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/info" element={<InfoPage />} />
<Route path="/privacy" element={<PrivacyPolicyPage />} />
<Route path="/me/*" element={<MeLayoutPage />} />
<Route path="/products/:id" element={<ProductPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</MainLayout>
<AppRoutes />
</BrowserRouter>
</AppProviders>
)
+28 -141
View File
@@ -1,21 +1,15 @@
import { useState } from 'react'
import AccountCircleOutlinedIcon from '@mui/icons-material/AccountCircleOutlined'
import DarkModeOutlinedIcon from '@mui/icons-material/DarkModeOutlined'
import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined'
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined'
import MenuOutlinedIcon from '@mui/icons-material/MenuOutlined'
import ShoppingCartOutlinedIcon from '@mui/icons-material/ShoppingCartOutlined'
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 Divider from '@mui/material/Divider'
import Drawer from '@mui/material/Drawer'
import FormControl from '@mui/material/FormControl'
import IconButton from '@mui/material/IconButton'
import InputLabel from '@mui/material/InputLabel'
import ListItemText from '@mui/material/ListItemText'
import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import Select from '@mui/material/Select'
import type { SelectChangeEvent } from '@mui/material/Select'
@@ -31,9 +25,12 @@ import type { ColorScheme } from '@/app/providers/theme-controller'
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 { BearLogo } from '@/shared/ui/BearLogo'
import { NavigationDrawer } from '@/widgets/navigation-drawer'
type NavItem = { label: string; to: string }
@@ -175,7 +172,6 @@ export function AppHeader() {
queryFn: fetchMyCart,
enabled: Boolean(user) && !isAdmin,
})
const cartCount = cartQuery.data?.items?.length ?? 0
const ordersQuery = useQuery({
@@ -183,53 +179,46 @@ export function AppHeader() {
queryFn: fetchMyOrders,
enabled: Boolean(user) && !isAdmin,
})
const activeOrdersCount = (ordersQuery.data?.items ?? []).filter(
(o) => o.status !== 'DONE' && o.status !== 'CANCELLED',
).length
const [userAnchorEl, setUserAnchorEl] = useState<null | HTMLElement>(null)
const userMenuOpen = Boolean(userAnchorEl)
const [mobileOpen, setMobileOpen] = useState(false)
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
const onSchemeChange = (e: SelectChangeEvent<string>) => {
setScheme(e.target.value as ColorScheme)
}
const onSchemeChange = (e: SelectChangeEvent<string>) => setScheme(e.target.value as ColorScheme)
const onModeChange = (e: SelectChangeEvent<string>) => {
const v = e.target.value
if (v === 'system' || v === 'light' || v === 'dark') setMode(v)
}
const openUserMenu = (e: React.MouseEvent<HTMLElement>) => setUserAnchorEl(e.currentTarget)
const closeUserMenu = () => setUserAnchorEl(null)
const openMobile = () => setMobileOpen(true)
const closeMobile = () => setMobileOpen(false)
const go = (to: string) => {
closeMobile()
closeUserMenu()
setMobileOpen(false)
navigate(to)
}
const onLogout = () => {
tokenSet(null)
logout()
closeMobile()
closeUserMenu()
setMobileOpen(false)
navigate('/')
}
const themeControls = { scheme, mode, resolvedMode, onSchemeChange, onModeChange, onCycleMode: cycleMode }
return (
<>
<AppBar position="sticky" color="primary" elevation={0} sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Toolbar>
{isMobile && (
<IconButton color="inherit" onClick={openMobile} aria-label="Открыть меню" edge="start" sx={{ mr: 1 }}>
<IconButton
color="inherit"
onClick={() => setMobileOpen(true)}
aria-label="Открыть меню"
edge="start"
sx={{ mr: 1 }}
>
<MenuOutlinedIcon />
</IconButton>
)}
@@ -272,58 +261,11 @@ export function AppHeader() {
</Tooltip>
)}
<Tooltip title={user ? 'Корзина' : 'Авторизуйтесь для совершения покупок'}>
<IconButton
color="inherit"
sx={{ ml: 1 }}
onClick={() => {
if (!user) navigate('/auth')
else navigate('/cart')
}}
aria-label="Корзина"
>
<Badge color="secondary" badgeContent={user ? cartCount : 0} invisible={!user || cartCount === 0}>
<ShoppingCartOutlinedIcon />
</Badge>
</IconButton>
</Tooltip>
<CartBadge user={user} cartCount={cartCount} onNavigate={navigate} />
</>
)}
{!isAdmin && (
<>
<IconButton color="inherit" onClick={openUserMenu} sx={{ ml: 1 }} aria-label="Пользователь">
<Badge
variant="dot"
color="success"
overlap="circular"
invisible={!user}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
>
<AccountCircleOutlinedIcon />
</Badge>
</IconButton>
<Menu
anchorEl={userAnchorEl}
open={userMenuOpen}
onClose={closeUserMenu}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
>
{user ? (
<>
<MenuItem onClick={() => go('/me')}>
<ListItemText primary={(user.name && user.name.trim()) || user.email} secondary="Профиль" />
</MenuItem>
<MenuItem onClick={onLogout}>Выход</MenuItem>
</>
) : (
<MenuItem onClick={() => go('/auth')}>Войти / регистрация</MenuItem>
)}
</Menu>
</>
)}
{!isAdmin && <UserMenu user={user} onNavigate={navigate} onLogout={onLogout} />}
{isAdmin && user && !isMobile && (
<Button color="inherit" onClick={onLogout} sx={{ ml: 1 }}>
@@ -331,76 +273,21 @@ export function AppHeader() {
</Button>
)}
{!isMobile && (
<ThemeControlsDesktop
scheme={scheme}
mode={mode}
resolvedMode={resolvedMode}
onSchemeChange={onSchemeChange}
onModeChange={onModeChange}
onCycleMode={cycleMode}
/>
)}
{!isMobile && <ThemeControlsDesktop {...themeControls} />}
</Toolbar>
</AppBar>
<Drawer
<NavigationDrawer
open={mobileOpen}
onClose={closeMobile}
slotProps={{ paper: { sx: { width: 320, maxWidth: '85vw' } } }}
ModalProps={{ keepMounted: true }}
>
<Box sx={{ p: 2 }}>
<Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
<BearLogo sx={{ fontSize: 28 }} />
<Typography variant="h6">{STORE_NAME}</Typography>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
{headerNavItems.map((i) => (
<Button key={i.to} variant="text" onClick={() => go(i.to)} sx={{ justifyContent: 'flex-start' }}>
{i.label}
</Button>
))}
{!isAdmin && (
<Button variant="text" onClick={() => go(user ? '/cart' : '/auth')} sx={{ justifyContent: 'flex-start' }}>
Корзина
</Button>
)}
{user && !isAdmin && (
<Button variant="text" onClick={() => go('/me/orders')} sx={{ justifyContent: 'flex-start' }}>
Заказы
</Button>
)}
{!isAdmin && (
<Button variant="text" onClick={() => go(user ? '/me' : '/auth')} sx={{ justifyContent: 'flex-start' }}>
{user ? 'Профиль' : 'Вход / регистрация'}
</Button>
)}
{!user && isAdmin && (
<Button variant="text" onClick={() => go('/auth')} sx={{ justifyContent: 'flex-start' }}>
Вход / регистрация
</Button>
)}
{user && (
<Button variant="text" color="error" onClick={onLogout} sx={{ justifyContent: 'flex-start' }}>
Выход
</Button>
)}
</Box>
<Divider sx={{ my: 2 }} />
<ThemeControlsMobile
scheme={scheme}
mode={mode}
resolvedMode={resolvedMode}
onSchemeChange={onSchemeChange}
onModeChange={onModeChange}
onCycleMode={cycleMode}
/>
</Box>
</Drawer>
onClose={() => setMobileOpen(false)}
user={user}
isAdmin={isAdmin}
navItems={headerNavItems}
themeControls={themeControls}
onNavigate={go}
onLogout={onLogout}
ThemeControlsMobile={ThemeControlsMobile}
/>
</>
)
}
+33
View File
@@ -0,0 +1,33 @@
import { Navigate, Route, Routes } from 'react-router-dom'
import { MainLayout } from '@/app/layout/MainLayout'
import { AboutPage } from '@/pages/about'
import { AdminLayoutPage } from '@/pages/admin-layout'
import { AuthCallbackPage, AuthPage } from '@/pages/auth'
import { CartPage } from '@/pages/cart'
import { CheckoutPage } from '@/pages/checkout'
import { HomePage } from '@/pages/home'
import { InfoPage } from '@/pages/info'
import { MeLayoutPage } from '@/pages/me'
import { PrivacyPolicyPage } from '@/pages/privacy-policy'
import { ProductPage } from '@/pages/product'
export function AppRoutes() {
return (
<MainLayout>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/admin/*" element={<AdminLayoutPage />} />
<Route path="/auth" element={<AuthPage />} />
<Route path="/auth/callback" element={<AuthCallbackPage />} />
<Route path="/cart" element={<CartPage />} />
<Route path="/checkout" element={<CheckoutPage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/info" element={<InfoPage />} />
<Route path="/privacy" element={<PrivacyPolicyPage />} />
<Route path="/me/*" element={<MeLayoutPage />} />
<Route path="/products/:id" element={<ProductPage />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</MainLayout>
)
}