This commit is contained in:
Kirill
2026-05-24 13:59:14 +05:00
parent 2fe426b70a
commit c2c4099fd7
10 changed files with 853 additions and 189 deletions
@@ -1 +0,0 @@
{"type":"server-started","port":53669,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:53669","screen_dir":"/mnt/d/my_projects/shop/.superpowers/brainstorm/7074-1779609479/content","state_dir":"/mnt/d/my_projects/shop/.superpowers/brainstorm/7074-1779609479/state"}
@@ -0,0 +1 @@
{"reason":"idle timeout","timestamp":1779612416287}
+54 -15
View File
@@ -1,4 +1,6 @@
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import Container from '@mui/material/Container'
import Divider from '@mui/material/Divider'
import Stack from '@mui/material/Stack' import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { DeliverySection } from './sections/DeliverySection' import { DeliverySection } from './sections/DeliverySection'
@@ -9,21 +11,58 @@ import { ReturnsSection } from './sections/ReturnsSection'
export function InfoPage() { export function InfoPage() {
return ( return (
<Box> <Container maxWidth="lg" sx={{ py: { xs: 4 } }}>
<Typography variant="h4" gutterBottom> {/* Hero */}
Информация для покупателей <Box sx={{ mb: 8 }}>
</Typography> <Typography
<Typography color="text.secondary" sx={{ mb: 3 }}> variant="h3"
Как оформить заказ, как проходит доставка, оплата и другие важные детали. sx={{
</Typography> fontWeight: 700,
fontSize: { xs: '2rem', md: '2.75rem' },
letterSpacing: '-0.035em',
lineHeight: 1.1,
mb: 2,
textWrap: 'balance',
}}
>
Информация для покупателей
</Typography>
<Typography
sx={{
fontSize: '1rem',
color: 'text.secondary',
lineHeight: 1.7,
maxWidth: '58ch',
}}
>
Как оформить заказ, как проходит доставка, оплата и другие важные детали.
</Typography>
</Box>
<Stack spacing={3}> {/* Main content grid */}
<HowToOrderSection /> <Box
<DeliverySection /> sx={{
<PaymentSection /> display: 'grid',
<OrderStatusesSection /> gridTemplateColumns: { xs: '1fr', lg: '1fr 1fr' },
<ReturnsSection /> gap: { xs: 6, md: 8 },
</Stack> }}
</Box> >
{/* Left column */}
<Stack spacing={6}>
<HowToOrderSection />
<Divider />
<DeliverySection />
</Stack>
{/* Right column */}
<Stack spacing={6}>
<PaymentSection />
<Divider />
<OrderStatusesSection />
<Divider />
<ReturnsSection />
</Stack>
</Box>
</Container>
) )
} }
@@ -1,6 +1,5 @@
import Grid from '@mui/material/Grid' import Box from '@mui/material/Box'
import Link from '@mui/material/Link' import Link from '@mui/material/Link'
import Paper from '@mui/material/Paper'
import Stack from '@mui/material/Stack' import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { Package, Store } from 'lucide-react' import { Package, Store } from 'lucide-react'
@@ -10,57 +9,163 @@ import { PICKUP_ADDRESS_FULL } from '@/shared/constants/pickup-point'
export function DeliverySection() { export function DeliverySection() {
return ( return (
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}> <Box>
<Typography variant="h5" gutterBottom> <Typography
variant="h5"
sx={{
fontWeight: 700,
letterSpacing: '-0.03em',
lineHeight: 1.15,
mb: 3,
}}
>
Доставка Доставка
</Typography> </Typography>
<Grid container spacing={2}>
<Grid size={{ xs: 12, sm: 6 }}> <Stack
<Paper variant="outlined" sx={{ p: 2, borderRadius: 2, height: '100%' }}> direction={{ xs: 'column', sm: 'row' }}
<Stack spacing={1.5}> spacing={3}
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}> sx={{
<Store size={28} /> p: 3,
<Typography variant="h6">Самовывоз</Typography> borderRadius: '14px',
</Stack> border: '1px solid',
<Typography variant="body2" color="text.secondary"> borderColor: 'divider',
Бесплатно. }}
</Typography> >
<Typography variant="body2" color="text.secondary"> {/* Pickup */}
{PICKUP_ADDRESS_FULL} <Box sx={{ flex: 1 }}>
</Typography> <Stack spacing={2}>
<Typography variant="body2" color="text.secondary"> <Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}>
Перед визитом согласуем время чтобы заказ точно был готов к выдаче. <Box
</Typography> sx={{
<Link component={RouterLink} to="/about" variant="body2"> width: 40,
Посмотреть на карте height: 40,
</Link> borderRadius: '10px',
</Stack> backgroundColor: 'action.hover',
</Paper> display: 'flex',
</Grid> alignItems: 'center',
<Grid size={{ xs: 12, sm: 6 }}> justifyContent: 'center',
<Paper variant="outlined" sx={{ p: 2, borderRadius: 2, height: '100%' }}> color: 'text.secondary',
<Stack spacing={1.5}> }}
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}> >
<Package size={28} /> <Store size={20} />
<Typography variant="h6">Доставка по России</Typography> </Box>
</Stack> <Typography
<Typography variant="body2" color="text.secondary"> sx={{
Доступные службы доставки: fontWeight: 600,
</Typography> fontSize: '0.95rem',
<Stack component="ul" spacing={0.5} sx={{ pl: 2, m: 0, listStyle: 'disc' }}> letterSpacing: '-0.01em',
{DELIVERY_CARRIER_OPTIONS.map((c) => ( }}
<Typography key={c.code} component="li" variant="body2" color="text.secondary"> >
{c.label} Самовывоз
</Typography>
))}
</Stack>
<Typography variant="body2" color="text.secondary">
Стоимость рассчитывается по тарифу перевозчика. Админ скорректирует цену после оформления заказа.
</Typography> </Typography>
</Stack> </Stack>
</Paper> <Typography
</Grid> sx={{
</Grid> fontSize: '0.8rem',
</Paper> color: 'text.secondary',
lineHeight: 1.65,
}}
>
Бесплатно.
</Typography>
<Typography
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.65,
}}
>
{PICKUP_ADDRESS_FULL}
</Typography>
<Typography
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.65,
}}
>
Перед визитом согласуем время чтобы заказ точно был готов к выдаче.
</Typography>
<Link component={RouterLink} to="/about" sx={{ fontSize: '0.8rem', fontWeight: 500, mt: 0.5 }}>
Посмотреть на карте
</Link>
</Stack>
</Box>
<Box
sx={{
width: 1,
display: { xs: 'none', sm: 'block' },
borderLeft: '1px solid',
borderColor: 'divider',
}}
/>
{/* Delivery */}
<Box sx={{ flex: 1 }}>
<Stack spacing={2}>
<Stack direction="row" spacing={1.5} sx={{ alignItems: 'center' }}>
<Box
sx={{
width: 40,
height: 40,
borderRadius: '10px',
backgroundColor: 'action.hover',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'text.secondary',
}}
>
<Package size={20} />
</Box>
<Typography
sx={{
fontWeight: 600,
fontSize: '0.95rem',
letterSpacing: '-0.01em',
}}
>
Доставка по России
</Typography>
</Stack>
<Typography
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.65,
}}
>
Доступные службы доставки:
</Typography>
<Stack component="ul" spacing={0.75} sx={{ pl: 2, m: 0, listStyle: 'disc' }}>
{DELIVERY_CARRIER_OPTIONS.map((c) => (
<Typography
key={c.code}
component="li"
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.5,
}}
>
{c.label}
</Typography>
))}
</Stack>
<Typography
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.65,
}}
>
Стоимость рассчитывается по тарифу перевозчика. Админ скорректирует цену после оформления заказа.
</Typography>
</Stack>
</Box>
</Stack>
</Box>
) )
} }
@@ -1,11 +1,7 @@
import { useState } from 'react' import { useState } from 'react'
import Box from '@mui/material/Box' import Box from '@mui/material/Box'
import Link from '@mui/material/Link' import Link from '@mui/material/Link'
import Paper from '@mui/material/Paper' import Stack from '@mui/material/Stack'
import Step from '@mui/material/Step'
import StepContent from '@mui/material/StepContent'
import StepLabel from '@mui/material/StepLabel'
import Stepper from '@mui/material/Stepper'
import Tab from '@mui/material/Tab' import Tab from '@mui/material/Tab'
import Tabs from '@mui/material/Tabs' import Tabs from '@mui/material/Tabs'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
@@ -27,12 +23,12 @@ import { PICKUP_ADDRESS_SHORT } from '@/shared/constants/pickup-point'
const commonSteps = [ const commonSteps = [
{ {
label: 'Выберите товары', label: 'Выберите товары',
icon: <ShoppingCart size={20} />, icon: <ShoppingCart size={18} />,
text: 'Найдите нужные изделия в каталоге и добавьте их в корзину. Вы можете выбрать несколько товаров от разных мастеров — все они соберутся в одном заказе.', text: 'Найдите нужные изделия в каталоге и добавьте их в корзину. Вы можете выбрать несколько товаров от разных мастеров — все они соберутся в одном заказе.',
}, },
{ {
label: 'Проверьте корзину', label: 'Проверьте корзину',
icon: <ClipboardList size={20} />, icon: <ClipboardList size={18} />,
text: 'Перейдите в корзину и проверьте состав заказа: названия товаров, количество и итоговую сумму. Здесь же можно изменить количество или удалить позиции.', text: 'Перейдите в корзину и проверьте состав заказа: названия товаров, количество и итоговую сумму. Здесь же можно изменить количество или удалить позиции.',
}, },
] ]
@@ -40,22 +36,22 @@ const commonSteps = [
const deliverySteps = [ const deliverySteps = [
{ {
label: 'Укажите адрес доставки и получателя', label: 'Укажите адрес доставки и получателя',
icon: <MapPin size={20} />, icon: <MapPin size={18} />,
text: 'Заполните имя, телефон, email и адрес доставки. Если у вас уже есть сохранённые адреса — выберите из списка или добавьте новый в личном кабинете.', text: 'Заполните имя, телефон, email и адрес доставки. Если у вас уже есть сохранённые адреса — выберите из списка или добавьте новый в личном кабинете.',
}, },
{ {
label: 'Выберите способ доставки', label: 'Выберите способ доставки',
icon: <Truck size={20} />, icon: <Truck size={18} />,
text: 'Доступны: Почта России, Озон ПВЗ, Яндекс ПВЗ, 5Post, WB ПВЗ. После оформления админ рассчитает точную стоимость доставки и скорректирует цену заказа.', text: 'Доступны: Почта России, Озон ПВЗ, Яндекс ПВЗ, 5Post, WB ПВЗ. После оформления админ рассчитает точную стоимость доставки и скорректирует цену заказа.',
}, },
{ {
label: 'Оплатите заказ', label: 'Оплатите заказ',
icon: <CreditCard size={20} />, icon: <CreditCard size={18} />,
text: 'Онлайн-оплата через ЮKassa — банковские карты и СБП. Перенаправление на защищённую платёжную страницу.', text: 'Онлайн-оплата через ЮKassa — банковские карты и СБП. Перенаправление на защищённую платёжную страницу.',
}, },
{ {
label: 'Получите заказ', label: 'Получите заказ',
icon: <PackageOpen size={20} />, icon: <PackageOpen size={18} />,
text: 'После отправки вы получите трек-номер для отслеживания. Следите за статусом заказа в личном кабинете.', text: 'После отправки вы получите трек-номер для отслеживания. Следите за статусом заказа в личном кабинете.',
}, },
] ]
@@ -63,33 +59,86 @@ const deliverySteps = [
const pickupSteps = [ const pickupSteps = [
{ {
label: 'Подтвердите заказ', label: 'Подтвердите заказ',
icon: <CheckCircle size={20} />, icon: <CheckCircle size={18} />,
text: 'Выберите способ оплаты: онлайн через ЮKassa (карты, СБП) или при получении (наличные / карта).', text: 'Выберите способ оплаты: онлайн через ЮKassa (карты, СБП) или при получении (наличные / карта).',
}, },
{ {
label: 'Согласуйте время получения', label: 'Согласуйте время получения',
icon: <Clock size={20} />, icon: <Clock size={18} />,
text: 'Админ свяжется с вами, чтобы договориться об удобном времени выдачи заказа.', text: 'Админ свяжется с вами, чтобы договориться об удобном времени выдачи заказа.',
}, },
{ {
label: 'Получите заказ', label: 'Получите заказ',
icon: <Store size={20} />, icon: <Store size={18} />,
text: `Адрес: ${PICKUP_ADDRESS_SHORT}. Перед визитом согласуем время, чтобы заказ точно был готов.`, text: `Адрес: ${PICKUP_ADDRESS_SHORT}. Перед визитом согласуем время, чтобы заказ точно был готов.`,
}, },
] ]
function StepRow({ step, isLast }: { step: typeof commonSteps[0]; isLast: boolean }) {
return (
<Box sx={{ display: 'flex', gap: 2.5, pb: isLast ? 0 : 2.5, position: 'relative' }}>
<Stack spacing={0} sx={{ alignItems: 'center', flexShrink: 0, width: 20, pt: 0.25 }}>
<Box
sx={{
width: 20,
height: 20,
borderRadius: '6px',
backgroundColor: 'action.hover',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'text.secondary',
flexShrink: 0,
zIndex: 1,
}}
>
{step.icon}
</Box>
{!isLast && (
<Box
sx={{
width: 1.5,
flex: 1,
minHeight: 16,
backgroundColor: 'divider',
borderRadius: 1,
mt: 0.5,
}}
/>
)}
</Stack>
<Box sx={{ flex: 1, pt: 0.1 }}>
<Typography
sx={{
fontWeight: 600,
fontSize: '0.875rem',
letterSpacing: '-0.01em',
mb: 0.5,
}}
>
{step.label}
</Typography>
<Typography
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.65,
}}
>
{step.text}
</Typography>
</Box>
</Box>
)
}
function BranchStepper({ steps }: { steps: typeof deliverySteps }) { function BranchStepper({ steps }: { steps: typeof deliverySteps }) {
return ( return (
<Stepper orientation="vertical" activeStep={-1}> <Stack spacing={0} sx={{ mt: 1 }}>
{steps.map((step) => ( {steps.map((step, i) => (
<Step key={step.label} completed={false}> <StepRow key={step.label} step={step} isLast={i === steps.length - 1} />
<StepLabel slots={{ stepIcon: () => step.icon }}>{step.label}</StepLabel>
<StepContent>
<Typography color="text.secondary">{step.text}</Typography>
</StepContent>
</Step>
))} ))}
</Stepper> </Stack>
) )
} }
@@ -97,34 +146,88 @@ export function HowToOrderSection() {
const [tab, setTab] = useState(0) const [tab, setTab] = useState(0)
return ( return (
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}> <Box>
<Typography variant="h5" gutterBottom> <Typography
variant="h5"
sx={{
fontWeight: 700,
letterSpacing: '-0.03em',
lineHeight: 1.15,
mb: 3,
}}
>
Как оформить заказ Как оформить заказ
</Typography> </Typography>
<BranchStepper steps={commonSteps} /> <BranchStepper steps={commonSteps} />
<Box sx={{ mt: 2, mb: 1 }}>
<Tabs value={tab} onChange={(_, v) => setTab(v)} variant="fullWidth"> <Box sx={{ mt: 2.5, mb: 1 }}>
<Tabs
value={tab}
onChange={(_, v) => setTab(v)}
variant="fullWidth"
sx={{
minHeight: 36,
'& .MuiTab-root': {
minHeight: 36,
fontSize: '0.8rem',
fontWeight: 600,
letterSpacing: '-0.01em',
},
'& .MuiTabs-indicator': {
height: 2,
borderRadius: 1,
},
}}
>
<Tab label="Доставка" /> <Tab label="Доставка" />
<Tab label="Самовывоз" /> <Tab label="Самовывоз" />
</Tabs> </Tabs>
</Box> </Box>
{tab === 0 && <BranchStepper steps={deliverySteps} />} {tab === 0 && <BranchStepper steps={deliverySteps} />}
{tab === 1 && ( {tab === 1 && (
<Box> <Box>
<BranchStepper steps={pickupSteps} /> <BranchStepper steps={pickupSteps} />
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}> <Typography variant="body2" sx={{ mt: 1.5 }}>
<Link component={RouterLink} to="/about"> <Link component={RouterLink} to="/about" sx={{ fontSize: '0.8rem', fontWeight: 500 }}>
Посмотреть на карте Посмотреть на карте
</Link> </Link>
</Typography> </Typography>
</Box> </Box>
)} )}
<Box sx={{ mt: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
<Star size={18} /> <Box
<Typography variant="body2" color="text.secondary"> sx={{
mt: 3,
display: 'flex',
alignItems: 'flex-start',
gap: 1.5,
p: 2,
borderRadius: '10px',
backgroundColor: 'action.hover',
}}
>
<Box
sx={{
color: 'text.secondary',
flexShrink: 0,
mt: 0.1,
}}
>
<Star size={16} />
</Box>
<Typography
variant="body2"
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.6,
}}
>
После получения заказа вы можете оставить отзыв в личном кабинете. После получения заказа вы можете оставить отзыв в личном кабинете.
</Typography> </Typography>
</Box> </Box>
</Paper> </Box>
) )
} }
@@ -1,47 +1,247 @@
import type { ReactElement } from 'react' import Box from '@mui/material/Box'
import Chip from '@mui/material/Chip'
import Paper from '@mui/material/Paper'
import Stack from '@mui/material/Stack' import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { Banknote, CheckCircle, Package, PackageCheck, PackageSearch, Store, XCircle } from 'lucide-react'
import { ORDER_STATUS_DATA, type StatusIconName } from '@/shared/lib/order-status-data' import { ORDER_STATUS_DATA, type StatusIconName } from '@/shared/lib/order-status-data'
const iconMap: Record<StatusIconName, ReactElement> = { const iconMap: Record<StatusIconName, React.ReactElement> = {
banknote: <Banknote size={18} />, banknote: (
'check-circle': <CheckCircle size={18} />, <svg
'package-search': <PackageSearch size={18} />, width="20"
package: <Package size={18} />, height="20"
'package-check': <PackageCheck size={18} />, viewBox="0 0 24 24"
store: <Store size={18} />, fill="none"
'x-circle': <XCircle size={18} />, stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="2" y="6" width="20" height="12" rx="2" />
<circle cx="12" cy="12" r="3" />
<path d="M6 12h.01M18 12h.01" />
</svg>
),
'check-circle': (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
),
'package-search': (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="16" cy="12" r="4" />
<path d="M19 19l-3-3" />
<path d="M21 10V7a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 7v3" />
<path d="M3 10l9 5 9-5" />
</svg>
),
package: (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16.5 9.4l-9-5.19M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" y1="22.08" x2="12" y2="12" />
</svg>
),
'package-check': (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M16.5 9.4l-9-5.19M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" y1="22.08" x2="12" y2="12" />
<polyline points="9 13 11 15 15 11" />
</svg>
),
store: (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
),
'x-circle': (
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="10" />
<path d="M15 9l-6 6M9 9l6 6" />
</svg>
),
}
const colorMap: Record<string, { bg: string; border: string; text: string; dot: string; iconBg: string }> = {
warning: {
bg: 'rgba(245, 124, 0, 0.04)',
border: 'rgba(245, 124, 0, 0.15)',
text: '#c27a00',
dot: '#F57C00',
iconBg: 'rgba(245, 124, 0, 0.1)',
},
success: {
bg: 'rgba(46, 139, 87, 0.04)',
border: 'rgba(46, 139, 87, 0.15)',
text: '#1e6e42',
dot: '#2E8B57',
iconBg: 'rgba(46, 139, 87, 0.1)',
},
info: {
bg: 'rgba(84, 110, 122, 0.04)',
border: 'rgba(84, 110, 122, 0.15)',
text: '#3d5a68',
dot: '#546E7A',
iconBg: 'rgba(84, 110, 122, 0.1)',
},
error: {
bg: 'rgba(211, 47, 47, 0.04)',
border: 'rgba(211, 47, 47, 0.15)',
text: '#a83232',
dot: '#D32F2F',
iconBg: 'rgba(211, 47, 47, 0.1)',
},
} }
export function OrderStatusesSection() { export function OrderStatusesSection() {
return ( return (
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}> <Box>
<Typography variant="h5" gutterBottom> <Box sx={{ mb: 4 }}>
Статусы заказа <Typography
</Typography> variant="h5"
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> sx={{
Текущий статус отображается в личном кабинете. Вот что означает каждый из них: fontWeight: 700,
</Typography> letterSpacing: '-0.03em',
<Stack spacing={2}> lineHeight: 1.15,
{ORDER_STATUS_DATA.map((s) => ( mb: 1,
<Stack key={s.code} direction="row" spacing={2} sx={{ alignItems: 'flex-start' }}> }}
<Chip >
icon={iconMap[s.iconName]} Статусы заказа
label={s.label} </Typography>
color={s.color} <Typography
size="small" sx={{
variant="outlined" fontSize: '0.875rem',
sx={{ minWidth: 180, flexShrink: 0 }} color: 'text.secondary',
/> lineHeight: 1.6,
<Typography variant="body2" color="text.secondary" sx={{ pt: 0.3 }}> maxWidth: '56ch',
{s.description} }}
</Typography> >
</Stack> Текущий статус отображается в личном кабинете. Каждый этап отражает, что происходит с вашим заказом.
))} </Typography>
</Box>
<Stack spacing={0} sx={{ position: 'relative' }}>
{ORDER_STATUS_DATA.map((s, index) => {
const colors = colorMap[s.color] ?? colorMap.info
const isLast = index === ORDER_STATUS_DATA.length - 1
return (
<Box
key={s.code}
sx={{
display: 'flex',
gap: 3,
pb: isLast ? 0 : 3,
position: 'relative',
}}
>
{/* Content column */}
<Box sx={{ flex: 1, pt: 0.25 }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 1.5,
mb: 0.75,
}}
>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 36,
height: 36,
borderRadius: '10px',
backgroundColor: colors.iconBg,
color: colors.text,
flexShrink: 0,
}}
>
{iconMap[s.iconName]}
</Box>
<Typography
sx={{
fontWeight: 600,
fontSize: '0.9rem',
color: colors.text,
letterSpacing: '-0.01em',
}}
>
{s.label}
</Typography>
</Box>
<Typography
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.65,
maxWidth: '60ch',
pl: '45px',
}}
>
{s.description}
</Typography>
</Box>
</Box>
)
})}
</Stack> </Stack>
</Paper> </Box>
) )
} }
@@ -1,19 +1,16 @@
import List from '@mui/material/List' import Box from '@mui/material/Box'
import ListItem from '@mui/material/ListItem' import Stack from '@mui/material/Stack'
import ListItemIcon from '@mui/material/ListItemIcon'
import ListItemText from '@mui/material/ListItemText'
import Paper from '@mui/material/Paper'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { Banknote, CreditCard } from 'lucide-react' import { Banknote, CreditCard } from 'lucide-react'
const methods = [ const methods = [
{ {
icon: <CreditCard size={22} />, icon: <CreditCard size={18} />,
primary: 'Онлайн-оплата через ЮKassa (карты, СБП)', primary: 'Онлайн-оплата через ЮKassa',
secondary: 'Оплата после подтверждения заказа админом. Перенаправление на защищённую платёжную страницу ЮKassa.', secondary: 'Карты, СБП. Оплата после подтверждения заказа админом. Перенаправление на защищённую платёжную страницу.',
}, },
{ {
icon: <Banknote size={22} />, icon: <Banknote size={18} />,
primary: 'Оплата при получении', primary: 'Оплата при получении',
secondary: 'Наличными или картой при самовывозе.', secondary: 'Наличными или картой при самовывозе.',
}, },
@@ -21,22 +18,86 @@ const methods = [
export function PaymentSection() { export function PaymentSection() {
return ( return (
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}> <Box>
<Typography variant="h5" gutterBottom> <Typography
variant="h5"
sx={{
fontWeight: 700,
letterSpacing: '-0.03em',
lineHeight: 1.15,
mb: 1,
}}
>
Оплата Оплата
</Typography> </Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}> <Typography
Оплата происходит после подтверждения заказа админом. Вы получите уведомление, когда заказ будет подтверждён и sx={{
готов к оплате. fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.65,
mb: 3,
maxWidth: '56ch',
}}
>
Оплата происходит после подтверждения заказа админом. Вы получите уведомление, когда заказ будет подтверждён и готов к оплате.
</Typography> </Typography>
<List disablePadding>
<Stack spacing={2}>
{methods.map((m) => ( {methods.map((m) => (
<ListItem key={m.primary} disableGutters> <Stack
<ListItemIcon sx={{ minWidth: 40 }}>{m.icon}</ListItemIcon> key={m.primary}
<ListItemText primary={m.primary} secondary={m.secondary} /> direction="row"
</ListItem> spacing={2}
sx={{
alignItems: 'flex-start',
p: 2.5,
borderRadius: '12px',
border: '1px solid',
borderColor: 'divider',
transition: 'all 0.2s ease',
'&:hover': {
borderColor: 'action.disabledBackground',
},
}}
>
<Box
sx={{
width: 36,
height: 36,
borderRadius: '8px',
backgroundColor: 'action.hover',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'text.secondary',
flexShrink: 0,
}}
>
{m.icon}
</Box>
<Stack spacing={0.25}>
<Typography
sx={{
fontWeight: 600,
fontSize: '0.875rem',
letterSpacing: '-0.01em',
}}
>
{m.primary}
</Typography>
<Typography
sx={{
fontSize: '0.8rem',
color: 'text.secondary',
lineHeight: 1.6,
}}
>
{m.secondary}
</Typography>
</Stack>
</Stack>
))} ))}
</List> </Stack>
</Paper> </Box>
) )
} }
@@ -1,35 +1,67 @@
import Paper from '@mui/material/Paper' import Box from '@mui/material/Box'
import Stack from '@mui/material/Stack' import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
export function ReturnsSection() { export function ReturnsSection() {
return ( return (
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}> <Box>
<Typography variant="h5" gutterBottom> <Typography
variant="h5"
sx={{
fontWeight: 700,
letterSpacing: '-0.03em',
lineHeight: 1.15,
mb: 3,
}}
>
Возврат и гарантии Возврат и гарантии
</Typography> </Typography>
<Stack spacing={2}>
<Paper variant="outlined" sx={{ p: 2, borderRadius: 2 }}> <Stack spacing={2.5}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }} gutterBottom> <Box>
<Typography
sx={{
fontWeight: 600,
fontSize: '0.875rem',
letterSpacing: '-0.01em',
mb: 1,
}}
>
Возврат Возврат
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography
Если товар не соответствует описанию или имеет производственный дефект, свяжитесь с нами в течение 7 дней sx={{
после получения. Мы заменим изделие на аналогичное или вернём деньги. Возврат товара надлежащего качества fontSize: '0.8rem',
возможен в течение 14 дней, если изделие не было в употреблении и сохранён его товарный вид. color: 'text.secondary',
lineHeight: 1.65,
}}
>
Если товар не соответствует описанию или имеет производственный дефект, свяжитесь с нами в течение 7 дней после получения. Мы заменим изделие на аналогичное или вернём деньги. Возврат товара надлежащего качества возможен в течение 14 дней, если изделие не было в употреблении и сохранён его товарный вид.
</Typography> </Typography>
</Paper> </Box>
<Paper variant="outlined" sx={{ p: 2, borderRadius: 2 }}>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }} gutterBottom> <Box>
<Typography
sx={{
fontWeight: 600,
fontSize: '0.875rem',
letterSpacing: '-0.01em',
mb: 1,
}}
>
Гарантия качества Гарантия качества
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography
Мы отвечаем за качество каждого изделия ручной работы. Все дефекты, возникшие не по вине покупателя, sx={{
устраняются или компенсируются заменой изделия. Если у вас возникли вопросы по качеству напишите нам, и мы fontSize: '0.8rem',
решим проблему в кратчайшие сроки. color: 'text.secondary',
lineHeight: 1.65,
}}
>
Мы отвечаем за качество каждого изделия ручной работы. Все дефекты, возникшие не по вине покупателя, устраняются или компенсируются заменой изделия. Если у вас возникли вопросы по качеству напишите нам, и мы решим проблему в кратчайшие сроки.
</Typography> </Typography>
</Paper> </Box>
</Stack> </Stack>
</Paper> </Box>
) )
} }
+138 -14
View File
@@ -1,34 +1,158 @@
import type { ReactElement } from 'react' import { type ReactNode, useState } from 'react'
import Chip from '@mui/material/Chip' import Box from '@mui/material/Box'
import Tooltip from '@mui/material/Tooltip' import Tooltip from '@mui/material/Tooltip'
import { Banknote, CheckCircle, Package, PackageCheck, PackageSearch, Store, XCircle } from 'lucide-react' import Typography from '@mui/material/Typography'
import { getOrderStatusData, type StatusIconName } from '@/shared/lib/order-status-data' import { getOrderStatusData, type StatusIconName } from '@/shared/lib/order-status-data'
const iconMap: Record<StatusIconName, ReactElement> = { const iconMap: Record<StatusIconName, ReactNode> = {
banknote: <Banknote size={18} />, banknote: (
'check-circle': <CheckCircle size={18} />, <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
'package-search': <PackageSearch size={18} />, <rect x="2" y="6" width="20" height="12" rx="2" />
package: <Package size={18} />, <circle cx="12" cy="12" r="3" />
'package-check': <PackageCheck size={18} />, <path d="M6 12h.01M18 12h.01" />
store: <Store size={18} />, </svg>
'x-circle': <XCircle size={18} />, ),
'check-circle': (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
),
'package-search': (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M16 16a4 4 0 1 0 0-8 4 4 0 0 0 0 8z" />
<path d="M19 19l-3-3" />
<path d="M21 10V7a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 7v3" />
<path d="M3 10l9 5 9-5" />
</svg>
),
package: (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M16.5 9.4l-9-5.19M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" y1="22.08" x2="12" y2="12" />
</svg>
),
'package-check': (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M16.5 9.4l-9-5.19M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
<polyline points="3.27 6.96 12 12.01 20.73 6.96" />
<line x1="12" y1="22.08" x2="12" y2="12" />
<polyline points="9 13 11 15 15 11" />
</svg>
),
store: (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
<polyline points="9 22 9 12 15 12 15 22" />
</svg>
),
'x-circle': (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10" />
<path d="M15 9l-6 6M9 9l6 6" />
</svg>
),
}
const colorMap: Record<string, { bg: string; border: string; text: string; dot: string }> = {
warning: {
bg: 'rgba(245, 124, 0, 0.06)',
border: 'rgba(245, 124, 0, 0.25)',
text: '#c27a00',
dot: '#F57C00',
},
success: {
bg: 'rgba(46, 139, 87, 0.06)',
border: 'rgba(46, 139, 87, 0.25)',
text: '#1e6e42',
dot: '#2E8B57',
},
info: {
bg: 'rgba(84, 110, 122, 0.06)',
border: 'rgba(84, 110, 122, 0.25)',
text: '#3d5a68',
dot: '#546E7A',
},
error: {
bg: 'rgba(211, 47, 47, 0.06)',
border: 'rgba(211, 47, 47, 0.25)',
text: '#a83232',
dot: '#D32F2F',
},
} }
interface OrderStatusChipProps { interface OrderStatusChipProps {
status: string status: string
tooltipOverride?: string tooltipOverride?: string
size?: 'sm' | 'md'
} }
export function OrderStatusChip({ status, tooltipOverride }: OrderStatusChipProps) { export function OrderStatusChip({ status, tooltipOverride, size = 'md' }: OrderStatusChipProps) {
const data = getOrderStatusData(status) const data = getOrderStatusData(status)
const label = data?.label ?? status const label = data?.label ?? status
const color = data?.color ?? 'default' const colorKey = data?.color ?? 'default'
const icon = data ? iconMap[data.iconName] : undefined const icon = data ? iconMap[data.iconName] : undefined
const tooltip = tooltipOverride ?? data?.description ?? '' const tooltip = tooltipOverride ?? data?.description ?? ''
const colors = colorMap[colorKey] ?? colorMap.info
const [hovered, setHovered] = useState(false)
const padding = size === 'sm' ? '3px 10px' : '5px 14px'
const fontSize = size === 'sm' ? '0.7rem' : '0.75rem'
const iconSize = size === 'sm' ? 14 : 16
return ( return (
<Tooltip title={tooltip} arrow placement="top"> <Tooltip title={tooltip} arrow placement="top">
<Chip icon={icon} label={label} color={color as 'default'} size="small" variant="outlined" /> <Box
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: '6px',
padding: padding,
borderRadius: '999px',
border: `1px solid ${colors.border}`,
backgroundColor: hovered ? colors.bg : 'transparent',
transition: 'all 0.2s cubic-bezier(0.16, 1, 0.3, 1)',
cursor: 'default',
'&:active': {
transform: 'scale(0.97)',
},
}}
>
<Box
sx={{
width: '6px',
height: '6px',
borderRadius: '50%',
backgroundColor: colors.dot,
flexShrink: 0,
}}
/>
<Box
sx={{
color: colors.text,
display: 'flex',
alignItems: 'center',
width: iconSize,
height: iconSize,
}}
>
{icon}
</Box>
<Typography
sx={{
fontSize,
fontWeight: 600,
color: colors.text,
lineHeight: 1.2,
letterSpacing: '-0.01em',
}}
>
{label}
</Typography>
</Box>
</Tooltip> </Tooltip>
) )
} }
Binary file not shown.