Merge branch 'redisign'
This commit is contained in:
@@ -18,7 +18,7 @@ export function MainLayout({ children }: PropsWithChildren) {
|
|||||||
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
|
|
||||||
<Box component="main" sx={{ flex: 1, py: 3 }}>
|
<Box component="main" sx={{ flex: 1, py: { xs: 4, md: 6 } }}>
|
||||||
<Container maxWidth="lg">{children}</Container>
|
<Container maxWidth="lg">{children}</Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -29,32 +29,29 @@ export function MainLayout({ children }: PropsWithChildren) {
|
|||||||
borderTop: 1,
|
borderTop: 1,
|
||||||
borderColor: 'divider',
|
borderColor: 'divider',
|
||||||
bgcolor: 'background.default',
|
bgcolor: 'background.default',
|
||||||
py: { xs: 3, md: 4 },
|
py: { xs: 4, md: 6 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="lg">
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={4}>
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||||
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 700 }}>
|
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 600, letterSpacing: '-0.25px' }}>
|
||||||
Магазин
|
Магазин
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1.5}>
|
||||||
<Link component={RouterLink} to="/" color="inherit" underline="hover" variant="body2">
|
<Link component={RouterLink} to="/" color="inherit" underline="hover" variant="body2">
|
||||||
Каталог
|
Каталог
|
||||||
</Link>
|
</Link>
|
||||||
<Link component={RouterLink} to="/privacy" color="inherit" underline="hover" variant="body2">
|
|
||||||
Политика конфиденциальности
|
|
||||||
</Link>
|
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
Изделия ручной работы: вещи с характером и вниманием к деталям.
|
Изделия ручной работы: вещи с характером и вниманием к деталям.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||||
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 700 }}>
|
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 600, letterSpacing: '-0.25px' }}>
|
||||||
Покупателям
|
Покупателям
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1.5}>
|
||||||
<Link component={RouterLink} to="/me" color="inherit" underline="hover" variant="body2">
|
<Link component={RouterLink} to="/me" color="inherit" underline="hover" variant="body2">
|
||||||
Личный кабинет
|
Личный кабинет
|
||||||
</Link>
|
</Link>
|
||||||
@@ -66,11 +63,11 @@ export function MainLayout({ children }: PropsWithChildren) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||||
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 700 }}>
|
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 600, letterSpacing: '-0.25px' }}>
|
||||||
Контакты
|
Контакты
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={0.75}>
|
<Stack spacing={1}>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
Email:{' '}
|
Email:{' '}
|
||||||
<Link href={`mailto:${STORE_EMAIL}`} underline="hover">
|
<Link href={`mailto:${STORE_EMAIL}`} underline="hover">
|
||||||
@@ -97,15 +94,26 @@ export function MainLayout({ children }: PropsWithChildren) {
|
|||||||
</Link>
|
</Link>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
|
||||||
|
<Typography variant="subtitle1" gutterBottom sx={{ fontWeight: 600, letterSpacing: '-0.25px' }}>
|
||||||
|
Юридическая информация
|
||||||
|
</Typography>
|
||||||
|
<Stack spacing={1.5}>
|
||||||
|
<Link component={RouterLink} to="/privacy" color="inherit" underline="hover" variant="body2">
|
||||||
|
Политика конфиденциальности
|
||||||
|
</Link>
|
||||||
|
<Link component={RouterLink} to="/terms" color="inherit" underline="hover" variant="body2">
|
||||||
|
Пользовательское соглашение
|
||||||
|
</Link>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Divider sx={{ my: 2 }} />
|
<Divider sx={{ my: 3 }} />
|
||||||
<Typography
|
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
|
||||||
variant="caption"
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', textAlign: 'center' }}>
|
||||||
color="text.secondary"
|
© {year} {STORE_NAME}
|
||||||
sx={{ display: 'block', textAlign: { xs: 'left', sm: 'center' } }}
|
</Typography>
|
||||||
>
|
</Box>
|
||||||
© {year} {STORE_NAME}
|
|
||||||
</Typography>
|
|
||||||
</Container>
|
</Container>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -24,97 +24,135 @@ function AppThemeInner({ children }: PropsWithChildren) {
|
|||||||
case 'forest':
|
case 'forest':
|
||||||
return {
|
return {
|
||||||
...common,
|
...common,
|
||||||
primary: { main: isDark ? '#4CAF50' : '#2E7D32' },
|
primary: { main: isDark ? '#8FBC8F' : '#2E8B57' },
|
||||||
secondary: { main: isDark ? '#A1887F' : '#6D4C41' },
|
secondary: { main: isDark ? '#CD853F' : '#8B4513' },
|
||||||
info: { main: isDark ? '#29B6F6' : '#0288D1' },
|
info: { main: isDark ? '#4682B4' : '#1E90FF' },
|
||||||
success: { main: isDark ? '#66BB6A' : '#2E7D32' },
|
success: { main: isDark ? '#90EE90' : '#32CD32' },
|
||||||
warning: { main: isDark ? '#FFB74D' : '#ED6C02' },
|
warning: { main: isDark ? '#FFD700' : '#FFA500' },
|
||||||
error: { main: isDark ? '#EF5350' : '#D32F2F' },
|
error: { main: isDark ? '#F08080' : '#CD5C5C' },
|
||||||
divider: isDark ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.10)',
|
divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)',
|
||||||
text,
|
text,
|
||||||
chip,
|
chip,
|
||||||
background: isDark
|
background: isDark
|
||||||
? { default: '#0E1510', paper: '#121B14' }
|
? { default: '#0F1720', paper: '#1A242E' }
|
||||||
: { default: '#F6FAF6', paper: '#FFFFFF' },
|
: { default: '#F8F6F3', paper: '#FFFFFF' },
|
||||||
}
|
}
|
||||||
case 'ocean':
|
case 'ocean':
|
||||||
return {
|
return {
|
||||||
...common,
|
...common,
|
||||||
primary: { main: isDark ? '#42A5F5' : '#1565C0' },
|
primary: { main: isDark ? '#5F9EA0' : '#20B2AA' },
|
||||||
secondary: { main: isDark ? '#4DD0E1' : '#00838F' },
|
secondary: { main: isDark ? '#7B68EE' : '#6A5ACD' },
|
||||||
info: { main: isDark ? '#4FC3F7' : '#0288D1' },
|
info: { main: isDark ? '#87CEEB' : '#00BFFF' },
|
||||||
success: { main: isDark ? '#26C6DA' : '#00838F' },
|
success: { main: isDark ? '#98FB98' : '#00FA9A' },
|
||||||
warning: { main: isDark ? '#FFCC80' : '#ED6C02' },
|
warning: { main: isDark ? '#FFE4B5' : '#FFDAB9' },
|
||||||
error: { main: isDark ? '#EF5350' : '#D32F2F' },
|
error: { main: isDark ? '#FF6347' : '#FF4500' },
|
||||||
divider: isDark ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.10)',
|
divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)',
|
||||||
text,
|
text,
|
||||||
chip,
|
chip,
|
||||||
background: isDark
|
background: isDark
|
||||||
? { default: '#0B1220', paper: '#0F172A' }
|
? { default: '#0A1A2A', paper: '#0F1D35' }
|
||||||
: { default: '#F6FAFF', paper: '#FFFFFF' },
|
: { default: '#F0F8FF', paper: '#FFFFFF' },
|
||||||
}
|
}
|
||||||
case 'berry':
|
case 'berry':
|
||||||
return {
|
return {
|
||||||
...common,
|
...common,
|
||||||
primary: { main: isDark ? '#BA68C8' : '#7B1FA2' },
|
primary: { main: isDark ? '#9370DB' : '#8A2BE2' },
|
||||||
secondary: { main: isDark ? '#F06292' : '#C2185B' },
|
secondary: { main: isDark ? '#FF69B4' : '#FF1493' },
|
||||||
info: { main: isDark ? '#64B5F6' : '#1976D2' },
|
info: { main: isDark ? '#00CED1' : '#00BFFF' },
|
||||||
success: { main: isDark ? '#81C784' : '#2E7D32' },
|
success: { main: isDark ? '#00FF7F' : '#7CFC00' },
|
||||||
warning: { main: isDark ? '#FFB74D' : '#ED6C02' },
|
warning: { main: isDark ? '#FFD700' : '#FFA500' },
|
||||||
error: { main: isDark ? '#EF5350' : '#D32F2F' },
|
error: { main: isDark ? '#FF4500' : '#FF6347' },
|
||||||
divider: isDark ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.10)',
|
divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)',
|
||||||
text,
|
text,
|
||||||
chip,
|
chip,
|
||||||
background: isDark
|
background: isDark
|
||||||
? { default: '#140A17', paper: '#1B0F20' }
|
? { default: '#1A0A1A', paper: '#250E25' }
|
||||||
: { default: '#FFF7FD', paper: '#FFFFFF' },
|
: { default: '#FFF0F5', paper: '#FFFFFF' },
|
||||||
}
|
}
|
||||||
case 'craft':
|
case 'craft':
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
...common,
|
...common,
|
||||||
primary: { main: isDark ? '#BCAAA4' : '#6D4C41' },
|
primary: { main: isDark ? '#90A4AE' : '#546E7A' },
|
||||||
secondary: { main: isDark ? '#FFCCBC' : '#8D6E63' },
|
secondary: { main: isDark ? '#78909C' : '#78909C' },
|
||||||
info: { main: isDark ? '#90CAF9' : '#1976D2' },
|
info: { main: isDark ? '#7986CB' : '#3F51B5' },
|
||||||
success: { main: isDark ? '#A5D6A7' : '#2E7D32' },
|
success: { main: isDark ? '#66BB6A' : '#43A047' },
|
||||||
warning: { main: isDark ? '#FFB74D' : '#ED6C02' },
|
warning: { main: isDark ? '#FFB74D' : '#F57C00' },
|
||||||
error: { main: isDark ? '#EF9A9A' : '#D32F2F' },
|
error: { main: isDark ? '#EF5350' : '#D32F2F' },
|
||||||
divider: isDark ? 'rgba(255,255,255,0.10)' : 'rgba(0,0,0,0.10)',
|
divider: isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)',
|
||||||
text,
|
text,
|
||||||
chip,
|
chip,
|
||||||
background: isDark
|
background: isDark
|
||||||
? { default: '#12100F', paper: '#191615' }
|
? { default: '#121212', paper: '#1E1E1E' }
|
||||||
: { default: '#FAF8F5', paper: '#FFFFFF' },
|
: { default: '#F5F5F5', paper: '#FFFFFF' },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
shape: { borderRadius: 12 },
|
shape: { borderRadius: 12 },
|
||||||
typography: {
|
typography: {
|
||||||
fontFamily: '"Segoe UI", system-ui, sans-serif',
|
fontFamily: '"Outfit", "Segoe UI", system-ui, sans-serif',
|
||||||
h4: { fontWeight: 700 },
|
h4: { fontWeight: 700, letterSpacing: '-0.5px' },
|
||||||
h5: { fontWeight: 600 },
|
h5: { fontWeight: 600, letterSpacing: '-0.25px' },
|
||||||
|
h6: { fontWeight: 600 },
|
||||||
|
subtitle1: { fontWeight: 600 },
|
||||||
|
subtitle2: { fontWeight: 500 },
|
||||||
|
body1: { fontSize: '0.875rem' },
|
||||||
|
body2: { fontSize: '0.75rem' },
|
||||||
|
button: { textTransform: 'none', fontWeight: 600 },
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
MuiButton: {
|
MuiButton: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: { textTransform: 'none', borderRadius: 12, fontWeight: 600 },
|
root: {
|
||||||
|
textTransform: 'none',
|
||||||
|
borderRadius: 12,
|
||||||
|
fontWeight: 600,
|
||||||
|
transition: 'all 0.2s ease-in-out',
|
||||||
|
},
|
||||||
contained: {
|
contained: {
|
||||||
boxShadow: '0 4px 14px 0 rgba(0,0,0,0.15)',
|
boxShadow: '0 4px 14px 0 rgba(0,0,0,0.15)',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
boxShadow: '0 6px 20px 0 rgba(0,0,0,0.25)',
|
boxShadow: '0 6px 20px 0 rgba(0,0,0,0.25)',
|
||||||
transform: 'translateY(-1px)',
|
transform: 'translateY(-2px)',
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.15)',
|
||||||
|
transform: 'translateY(0)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
outlined: {
|
outlined: {
|
||||||
'&:hover': { boxShadow: '0 2px 8px 0 rgba(0,0,0,0.1)' },
|
border: '1px solid',
|
||||||
|
'&:hover': {
|
||||||
|
boxShadow: '0 2px 8px 0 rgba(0,0,0,0.1)',
|
||||||
|
borderWidth: '2px',
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
boxShadow: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
'&:hover': {
|
||||||
|
boxShadow: '0 1px 3px 0 rgba(0,0,0,0.1)',
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
backgroundColor: 'action.selected',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiIconButton: {
|
MuiIconButton: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
transition: 'all 0.2s ease',
|
transition: 'all 0.2s ease-in-out',
|
||||||
'&:hover': { transform: 'scale(1.1)' },
|
'&:hover': {
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
transform: 'scale(1.1)',
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
backgroundColor: 'action.selected',
|
||||||
|
transform: 'scale(0.95)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { lazy, Suspense } from 'react'
|
import { lazy, Suspense } from 'react'
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { MainLayout } from '@/app/layout/MainLayout'
|
import { MainLayout } from '@/app/layout/MainLayout'
|
||||||
import { AboutPage } from '@/pages/about'
|
import { AboutPage } from '@/pages/about'
|
||||||
// import { AdminLayoutPage } from '@/pages/admin-layout'
|
// import { AdminLayoutPage } from '@/pages/admin-layout'
|
||||||
@@ -9,8 +9,10 @@ import { CheckoutPage } from '@/pages/checkout'
|
|||||||
import { HomePage } from '@/pages/home'
|
import { HomePage } from '@/pages/home'
|
||||||
import { InfoPage } from '@/pages/info'
|
import { InfoPage } from '@/pages/info'
|
||||||
// import { MeLayoutPage } from '@/pages/me'
|
// import { MeLayoutPage } from '@/pages/me'
|
||||||
|
import { NotFoundPage } from '@/pages/not-found'
|
||||||
import { PrivacyPolicyPage } from '@/pages/privacy-policy'
|
import { PrivacyPolicyPage } from '@/pages/privacy-policy'
|
||||||
import { ProductPage } from '@/pages/product'
|
import { ProductPage } from '@/pages/product'
|
||||||
|
import { TermsPage } from '@/pages/terms'
|
||||||
import { SkeletonPage } from '@/shared/ui/SkeletonPage'
|
import { SkeletonPage } from '@/shared/ui/SkeletonPage'
|
||||||
|
|
||||||
const AdminLayoutPage = lazy(() => import('@/pages/admin-layout').then((m) => ({ default: m.AdminLayoutPage })))
|
const AdminLayoutPage = lazy(() => import('@/pages/admin-layout').then((m) => ({ default: m.AdminLayoutPage })))
|
||||||
@@ -36,6 +38,7 @@ export function AppRoutes() {
|
|||||||
<Route path="/about" element={<AboutPage />} />
|
<Route path="/about" element={<AboutPage />} />
|
||||||
<Route path="/info" element={<InfoPage />} />
|
<Route path="/info" element={<InfoPage />} />
|
||||||
<Route path="/privacy" element={<PrivacyPolicyPage />} />
|
<Route path="/privacy" element={<PrivacyPolicyPage />} />
|
||||||
|
<Route path="/terms" element={<TermsPage />} />
|
||||||
<Route
|
<Route
|
||||||
path="/me/*"
|
path="/me/*"
|
||||||
element={
|
element={
|
||||||
@@ -45,7 +48,7 @@ export function AppRoutes() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path="/products/:id" element={<ProductPage />} />
|
<Route path="/products/:id" element={<ProductPage />} />
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { NotFoundPage } from './ui/NotFoundPage'
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { Box, Typography, Button, Stack, Paper } from '@mui/material'
|
||||||
|
import { Link as RouterLink } from 'react-router-dom'
|
||||||
|
|
||||||
|
export function NotFoundPage() {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
minHeight: '100vh',
|
||||||
|
bgcolor: 'background.default',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
py: 4,
|
||||||
|
px: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paper sx={{ p: 4, borderRadius: 3, bgcolor: 'background.paper' }}>
|
||||||
|
<Stack spacing={3} sx={{ alignItems: 'center', textAlign: 'center' }}>
|
||||||
|
<Box sx={{ fontSize: 96, lineHeight: 1 }}>404</Box>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
|
Страница не найдена
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ maxWidth: 400 }}>
|
||||||
|
Извините, но запрашиваемая страница не существует или была удалена.
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="row" spacing={2} sx={{ mt: 2 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = '/'
|
||||||
|
}}
|
||||||
|
sx={{ px: 4 }}
|
||||||
|
>
|
||||||
|
Вернуться на главную
|
||||||
|
</Button>
|
||||||
|
<Button variant="outlined" size="large" component={RouterLink} to="/" sx={{ px: 4 }}>
|
||||||
|
Посмотреть каталог
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -194,7 +194,14 @@ export function ProductPage() {
|
|||||||
{reviewsQuery.isLoading && <Typography color="text.secondary">Загрузка отзывов…</Typography>}
|
{reviewsQuery.isLoading && <Typography color="text.secondary">Загрузка отзывов…</Typography>}
|
||||||
{reviewsQuery.isError && <Alert severity="warning">Не удалось загрузить отзывы.</Alert>}
|
{reviewsQuery.isError && <Alert severity="warning">Не удалось загрузить отзывы.</Alert>}
|
||||||
{reviewsQuery.data && reviewsQuery.data.total === 0 && (
|
{reviewsQuery.data && reviewsQuery.data.total === 0 && (
|
||||||
<Typography color="text.secondary">Пока нет опубликованных отзывов на этот товар.</Typography>
|
<Box sx={{ py: 3 }}>
|
||||||
|
<Typography variant="h6" color="text.secondary" sx={{ mb: 1 }}>
|
||||||
|
Отзывов пока нет
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ maxWidth: 400 }}>
|
||||||
|
Будьте первым, кто оставит отзыв на этот товар. Ваше мнение поможет улучшить качество наших изделий.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
{reviewsQuery.data && reviewsQuery.data.items.length > 0 && (
|
{reviewsQuery.data && reviewsQuery.data.items.length > 0 && (
|
||||||
<Stack spacing={1.25}>
|
<Stack spacing={1.25}>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { TermsPage } from './ui/TermsPage'
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import Paper from '@mui/material/Paper'
|
||||||
|
import Typography from '@mui/material/Typography'
|
||||||
|
import { STORE_EMAIL, STORE_PUBLIC_SITE_URL } from '@/shared/config'
|
||||||
|
|
||||||
|
const SITE_URL = STORE_PUBLIC_SITE_URL || (typeof window !== 'undefined' ? window.location.origin : '')
|
||||||
|
|
||||||
|
const OP_NAME = 'Индивидуальный предприниматель Новоселова Наталия Владимировна'
|
||||||
|
const OP_INN = '402900832341'
|
||||||
|
const OP_OGRN = '305402922700051'
|
||||||
|
const OP_ADDR = '248000, Россия, г. Калуга, ул. Никитина, д. 12А'
|
||||||
|
|
||||||
|
const sections = [
|
||||||
|
{
|
||||||
|
title: '1. Общие положения',
|
||||||
|
items: [
|
||||||
|
`1.1. Настоящее Пользовательское соглашение (далее — «Соглашение») определяет порядок и условия использования материалов и сервисов, размещённых в сети Интернет по адресу ${SITE_URL} (далее — «Сайт»), Пользователями данного Сайта.`,
|
||||||
|
`1.2. Использование Пользователями Сайта означает, что они безоговорочно принимают и обязуются соблюдать все условия настоящего Соглашения.`,
|
||||||
|
'1.3. В настоящем Соглашении используются следующие термины:',
|
||||||
|
`— Администратор — ${OP_NAME}, ИНН ${OP_INN}, ОГРН ${OP_OGRN}, адрес: ${OP_ADDR}, которому принадлежат все соответствующие права на Сайт.`,
|
||||||
|
`— Акцепт — полное и безоговорочное принятие условий настоящего Соглашения, размещённого на Сайте по адресу ${SITE_URL}/terms, осуществляемое путём совершения Пользователем любых действий по использованию Сайта.`,
|
||||||
|
'— Аутентификационные данные Пользователя — адрес электронной почты Пользователя и пароль (код доступа), которые в совокупности признаются простой электронной подписью Пользователя.',
|
||||||
|
'— Пользователь — лицо, осуществляющее доступ к Сайту и использующее материалы и сервисы, размещённые на Сайте.',
|
||||||
|
'— Контент — любое информационно значимое наполнение Сайта, включая фото, текст и иные медиаматериалы.',
|
||||||
|
'— Личный кабинет — персонализированная часть Сайта, посредством которой обеспечивается обмен информацией между Пользователем и Сайтом.',
|
||||||
|
'— Персональные данные — любая информация, относящаяся к определённому или определяемому на основании такой информации физическому лицу.',
|
||||||
|
'— Обработка персональных данных — любое действие (операция) или совокупность действий с персональными данными, включая сбор, запись, систематизацию, накопление, хранение, уточнение, использование, передачу, обезличивание, блокирование, удаление, уничтожение.',
|
||||||
|
`— Сайт — ресурс в сети Интернет по адресу ${SITE_URL}, представляющий собой совокупность информации и объектов интеллектуальной собственности.`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '2. Предмет соглашения',
|
||||||
|
items: [
|
||||||
|
'2.1. В соответствии с настоящим Соглашением Администратор предоставляет любому Пользователю право безвозмездного использования Сайта в пределах его объявленных функциональных возможностей.',
|
||||||
|
'2.2. Использование Сайта осуществляется в соответствии с принципом «как есть» (as is). Администратор не гарантирует, что Сайт будет работать непрерывно, быстро и без ошибок.',
|
||||||
|
'2.3. Пользователь считается присоединившимся к Соглашению в соответствии со ст. 438 ГК РФ при совершении любых из следующих действий:',
|
||||||
|
'— просмотр материалов, размещённых на Сайте;',
|
||||||
|
'— использование сервисов Сайта;',
|
||||||
|
'— регистрация в Личном кабинете;',
|
||||||
|
'— направление сообщений с использованием онлайн-форм на Сайте;',
|
||||||
|
'— иное использование Сайта.',
|
||||||
|
'2.4. Используя Сайт, Пользователь подтверждает, что ознакомился с условиями Соглашения в полном объёме и безоговорочно принимает их.',
|
||||||
|
'2.5. Положения Соглашения не устанавливают между Администратором и Пользователем агентских отношений, отношений товарищества или иных отношений, прямо не предусмотренных Соглашением.',
|
||||||
|
'2.6. Все возможные споры, вытекающие из Соглашения, подлежат разрешению в соответствии с действующим законодательством РФ.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3. Регистрация',
|
||||||
|
items: [
|
||||||
|
'3.1. Для использования отдельных функций Сайта Пользователю необходимо пройти процедуру регистрации, в результате которой ему будет предоставлен доступ в Личный кабинет.',
|
||||||
|
'3.2. При регистрации Пользователь обязуется предоставить достоверную и полную информацию и поддерживать её в актуальном состоянии.',
|
||||||
|
'3.3. В случае предоставления неверной информации Администратор вправе заблокировать Личный кабинет либо удалить учётную запись Пользователя.',
|
||||||
|
'3.4. Пользователь самостоятельно несёт ответственность за сохранность своих Аутентификационных данных и за все действия, совершённые с их использованием.',
|
||||||
|
'3.5. Пользователь обязан незамедлительно уведомить Администратора о любом случае несанкционированного доступа к Личному кабинету.',
|
||||||
|
'3.6. Пользователь не вправе воспроизводить, копировать, продавать и использовать в коммерческих целях Сайт или его Контент без разрешения Администратора.',
|
||||||
|
'3.7. При регистрации Пользователь даёт согласие на получение информационных и рекламных сообщений от Администратора на указанный адрес электронной почты.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '4. Права и обязанности Администратора',
|
||||||
|
items: [
|
||||||
|
'4.1. Администратор вправе осуществлять сбор мнений и отзывов Пользователей для формирования статистических данных и улучшения качества Сайта.',
|
||||||
|
'4.2. Администратор вправе направлять Пользователю информационные сообщения, связанные с функционированием Сайта, исполнением договоров, восстановлением пароля.',
|
||||||
|
'4.3. Администратор оставляет за собой право заблокировать Личный кабинет Пользователя в случае нарушения условий Соглашения.',
|
||||||
|
'4.4. Сайт может быть частично или полностью недоступен по причине проведения технических работ. Администратор вправе приостанавливать работу Сайта по своему усмотрению.',
|
||||||
|
'4.5. Администратор не несёт ответственности за ошибки, сбои линий связи, неправомерный доступ к информации Пользователя, возникшие не по вине Администратора.',
|
||||||
|
'4.6. Администратор предпримет разумные усилия для устранения технических сбоев и ошибок, но не гарантирует их полного отсутствия.',
|
||||||
|
'4.7. Пользователю не предоставляется никаких интеллектуальных прав на Сайт и его составные части, кроме прямо предусмотренных Соглашением.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '5. Права и обязанности Пользователя',
|
||||||
|
items: [
|
||||||
|
'5.1. Пользователь обязуется знакомиться с актуальной версией Соглашения при каждом посещении Сайта.',
|
||||||
|
'5.2. Пользователь обязуется предоставлять достоверную информацию при использовании Сайта.',
|
||||||
|
'5.3. Пользователь обязуется не совершать действий, нарушающих российское законодательство, нормы морали и нравственности, а также действий, приводящих к нарушению работы Сайта.',
|
||||||
|
'5.4. Использование материалов Сайта без согласия правообладателей не допускается. При цитировании материалов Сайта ссылка на Сайт обязательна.',
|
||||||
|
'5.5. Пользователь обязуется не нарушать права и законные интересы третьих лиц, не причинять вред деловой репутации.',
|
||||||
|
'5.6. Пользователь не вправе нарушать нормальную работу отдельных сервисов и Сайта в целом.',
|
||||||
|
'5.7. Пользователь обязан самостоятельно отслеживать внесение изменений в настоящее Соглашение.',
|
||||||
|
'5.8. Пользователь вправе прекратить доступ к Личному кабинету, направив уведомление Администратору.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '6. Ограничение ответственности',
|
||||||
|
items: [
|
||||||
|
'6.1. Администратор гарантирует достоверность и полноту только той информации, которую он разместил на Сайте самостоятельно.',
|
||||||
|
'6.2. Администратор не несёт ответственности за недостоверность информации, размещённой третьими лицами, в том числе Пользователями.',
|
||||||
|
'6.3. Администратор не гарантирует, что Сайт будет соответствовать требованиям Пользователя, работать непрерывно и без ошибок.',
|
||||||
|
'6.4. Администратор не несёт ответственности перед Пользователем за любые убытки, включая упущенную выгоду, потерю данных, вред деловой репутации, причинённые в связи с использованием Сайта.',
|
||||||
|
'6.5. Администратор исходит из того, что все формы на Сайте заполняет непосредственно Пользователь. Ответственность за достоверность предоставленных данных несёт Пользователь.',
|
||||||
|
'6.6. Администратор не несёт ответственности за утрату или порчу данных в результате невыполнения Пользователем условий Соглашения.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '7. Доступ к ресурсам третьих лиц',
|
||||||
|
items: [
|
||||||
|
'7.1. Доступ Пользователя к Сайту может вызывать обращение к интернет-ресурсам третьих лиц (реклама, сбор статистики).',
|
||||||
|
'7.2. Владельцы таких ресурсов имеют техническую возможность собирать информацию о Пользователях и самостоятельно определяют условия её использования.',
|
||||||
|
'7.3. При переходе на сторонние ресурсы Пользователи самостоятельно определяют пределы использования своей информации согласно правилам соответствующих ресурсов.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '8. Информация, хранящаяся на стороне браузера',
|
||||||
|
items: [
|
||||||
|
'8.1. Администратор использует cookie-файлы для определения уникального идентификатора доступа Пользователя к Сайту.',
|
||||||
|
'8.2. Цели использования cookie:',
|
||||||
|
'— поддержка функциональности Сайта, требующей использования cookie;',
|
||||||
|
'— измерение аудитории Сайта;',
|
||||||
|
'— определение статистических предпочтений Пользователей;',
|
||||||
|
'— исследование корреляции статистических данных.',
|
||||||
|
'8.3. Пользователь может запретить использование cookie в настройках браузера, однако это может привести к частичной или полной потере функциональности Сайта.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '9. Согласие на обработку персональных данных',
|
||||||
|
items: [
|
||||||
|
`9.1. Обработка персональных данных Пользователей осуществляется Администратором в соответствии с Политикой конфиденциальности, размещённой по адресу ${SITE_URL}/privacy.`,
|
||||||
|
'9.2. Передавая свои персональные данные при регистрации или заполнении форм на Сайте, Пользователь даёт согласие на их обработку Администратором.',
|
||||||
|
'9.3. Администратор обрабатывает следующие персональные данные: Ф. И. О., адрес электронной почты, номер телефона, IP-адрес, тип браузера, данные о действиях на Сайте.',
|
||||||
|
'9.4. Цели обработки персональных данных: обеспечение функционирования Сайта, оказание информационной поддержки, предоставление персонализированных сервисов, направление информационных сообщений.',
|
||||||
|
`9.5. Согласие на обработку персональных данных может быть отозвано Пользователем путём направления заявления на адрес электронной почты: ${STORE_EMAIL}.`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '10. Изменение условий и расторжение соглашения',
|
||||||
|
items: [
|
||||||
|
'10.1. Соглашение может быть расторгнуто в любое время по инициативе любой из сторон. Администратор уведомляет о расторжении путём размещения информации на Сайте.',
|
||||||
|
`10.2. Пользователь может расторгнуть Соглашение, направив уведомление на адрес электронной почты: ${STORE_EMAIL}.`,
|
||||||
|
'10.3. Администратор вправе в одностороннем порядке изменять условия Соглашения. Новая редакция вступает в силу с момента размещения на Сайте.',
|
||||||
|
'10.4. Продолжение использования Сайта после изменения условий означает согласие Пользователя с новой редакцией. При несогласии Пользователь обязуется прекратить использование Сайта.',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '11. Информация об Администраторе',
|
||||||
|
items: [
|
||||||
|
`${OP_NAME}`,
|
||||||
|
`ИНН: ${OP_INN}`,
|
||||||
|
`ОГРН: ${OP_OGRN}`,
|
||||||
|
`Адрес: ${OP_ADDR}`,
|
||||||
|
`Телефон: +7 (900) 000-00-00`, // TODO: заменить на реальный номер телефона
|
||||||
|
`Email: ${STORE_EMAIL}`, // TODO: заменить на реальный email при настройке STORE_EMAIL
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function TermsPage() {
|
||||||
|
return (
|
||||||
|
<Box sx={{ maxWidth: 800, mx: 'auto', py: { xs: 3, md: 5 }, px: { xs: 2, md: 0 } }}>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
component="h1"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontWeight: 700, fontSize: { xs: '1.5rem', md: '2rem' } }}
|
||||||
|
>
|
||||||
|
Пользовательское соглашение
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ mb: 4, pb: 3, borderBottom: '1px solid', borderColor: 'divider' }}
|
||||||
|
>
|
||||||
|
Последнее обновление: 19 мая 2026 г.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box component="section" sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
|
{sections.map((section) => (
|
||||||
|
<Paper
|
||||||
|
key={section.title}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
p: { xs: 2, md: 3 },
|
||||||
|
borderRadius: 3,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderLeft: '4px solid',
|
||||||
|
borderLeftColor: 'primary.main',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
component="h2"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontWeight: 600, fontSize: '1rem', color: 'primary.main' }}
|
||||||
|
>
|
||||||
|
{section.title}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box component="ul" sx={{ m: 0, p: 0, listStyle: 'none' }}>
|
||||||
|
{section.items.map((item, idx) => (
|
||||||
|
<Box
|
||||||
|
component="li"
|
||||||
|
key={idx}
|
||||||
|
sx={{
|
||||||
|
color: 'text.primary',
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
lineHeight: 1.65,
|
||||||
|
mb: 0.5,
|
||||||
|
'&:last-child': { mb: 0 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -10,10 +10,10 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SCHEMES: { key: ColorScheme; color: string; label: string; icon: React.ReactNode }[] = [
|
const SCHEMES: { key: ColorScheme; color: string; label: string; icon: React.ReactNode }[] = [
|
||||||
{ key: 'craft', color: '#6D4C41', label: 'Крафт', icon: <Hammer size={16} /> },
|
{ key: 'craft', color: '#546E7A', label: 'Крафт', icon: <Hammer size={16} /> },
|
||||||
{ key: 'forest', color: '#2E7D32', label: 'Лес', icon: <Trees size={16} /> },
|
{ key: 'forest', color: '#2E8B57', label: 'Лес', icon: <Trees size={16} /> },
|
||||||
{ key: 'ocean', color: '#1565C0', label: 'Океан', icon: <WavesHorizontal size={16} /> },
|
{ key: 'ocean', color: '#20B2AA', label: 'Океан', icon: <WavesHorizontal size={16} /> },
|
||||||
{ key: 'berry', color: '#7B1FA2', label: 'Ягоды', icon: <Cherry size={16} /> },
|
{ key: 'berry', color: '#8A2BE2', label: 'Ягоды', icon: <Cherry size={16} /> },
|
||||||
]
|
]
|
||||||
|
|
||||||
export function SchemeSwitcher({ value, onChange, orientation = 'horizontal' }: Props) {
|
export function SchemeSwitcher({ value, onChange, orientation = 'horizontal' }: Props) {
|
||||||
|
|||||||
@@ -53,7 +53,15 @@ export function ReviewsBlock() {
|
|||||||
)}
|
)}
|
||||||
{q.isError && <Alert severity="error">Не удалось загрузить отзывы.</Alert>}
|
{q.isError && <Alert severity="error">Не удалось загрузить отзывы.</Alert>}
|
||||||
{!q.isLoading && !q.isError && q.data && items.length === 0 && (
|
{!q.isLoading && !q.isError && q.data && items.length === 0 && (
|
||||||
<Typography color="text.secondary">Пока нет опубликованных отзывов о товарах.</Typography>
|
<Box sx={{ py: 4 }}>
|
||||||
|
<Typography variant="h6" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
|
Отзывов пока нет
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ maxWidth: 400 }}>
|
||||||
|
Будьте первым, кто оставит отзыв о наших товарах. Ваше мнение поможет другим покупателям сделать правильный
|
||||||
|
выбор.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
{items.length > 0 && (
|
{items.length > 0 && (
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
@@ -71,8 +79,34 @@ export function ReviewsBlock() {
|
|||||||
...(zebra ? { bgcolor: 'action.hover' } : {}),
|
...(zebra ? { bgcolor: 'action.hover' } : {}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ alignItems: { sm: 'flex-start' } }}>
|
||||||
<Stack direction="row" spacing={1.5} sx={{ minWidth: { sm: 220 }, alignItems: 'center' }}>
|
{r.imageUrl && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: { xs: 80, sm: 80 },
|
||||||
|
height: { xs: 80, sm: 80 },
|
||||||
|
borderRadius: 1.5,
|
||||||
|
border: 1,
|
||||||
|
borderColor: 'divider',
|
||||||
|
overflow: 'hidden',
|
||||||
|
flexShrink: 0,
|
||||||
|
alignSelf: { xs: 'flex-start', sm: 'center' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<OptimizedImage
|
||||||
|
src={r.imageUrl}
|
||||||
|
alt="Фото к отзыву"
|
||||||
|
widths={[160, 320]}
|
||||||
|
sizes="80px"
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'cover',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Stack direction="row" spacing={1.5} sx={{ minWidth: { sm: 200 }, alignItems: 'center' }}>
|
||||||
<Avatar sx={{ bgcolor: 'primary.main', color: 'primary.contrastText', fontWeight: 800 }}>
|
<Avatar sx={{ bgcolor: 'primary.main', color: 'primary.contrastText', fontWeight: 800 }}>
|
||||||
{initials(r.authorDisplay)}
|
{initials(r.authorDisplay)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
@@ -111,31 +145,6 @@ export function ReviewsBlock() {
|
|||||||
<RichTextMessageContent value={text} tone="review" />
|
<RichTextMessageContent value={text} tone="review" />
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
{r.imageUrl && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
mt: 1.5,
|
|
||||||
width: 120,
|
|
||||||
height: 120,
|
|
||||||
borderRadius: 1.5,
|
|
||||||
border: 1,
|
|
||||||
borderColor: 'divider',
|
|
||||||
overflow: 'hidden',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<OptimizedImage
|
|
||||||
src={r.imageUrl}
|
|
||||||
alt="Фото к отзыву"
|
|
||||||
widths={[320, 640]}
|
|
||||||
sizes="120px"
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
objectFit: 'cover',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user