пва
This commit is contained in:
@@ -0,0 +1 @@
|
||||
5700
|
||||
@@ -0,0 +1 @@
|
||||
{"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 @@
|
||||
7319
|
||||
@@ -3,6 +3,7 @@ import Stack from '@mui/material/Stack'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { DeliverySection } from './sections/DeliverySection'
|
||||
import { HowToOrderSection } from './sections/HowToOrderSection'
|
||||
import { OrderStatusesSection } from './sections/OrderStatusesSection'
|
||||
import { PaymentSection } from './sections/PaymentSection'
|
||||
import { ReturnsSection } from './sections/ReturnsSection'
|
||||
|
||||
@@ -20,6 +21,7 @@ export function InfoPage() {
|
||||
<HowToOrderSection />
|
||||
<DeliverySection />
|
||||
<PaymentSection />
|
||||
<OrderStatusesSection />
|
||||
<ReturnsSection />
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -1,27 +1,13 @@
|
||||
import Grid from '@mui/material/Grid'
|
||||
import Link from '@mui/material/Link'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { Package, Store } from 'lucide-react'
|
||||
import { Link as RouterLink } from 'react-router-dom'
|
||||
import { DELIVERY_CARRIER_OPTIONS } from '@/shared/constants/delivery-carrier'
|
||||
import { PICKUP_ADDRESS_FULL } from '@/shared/constants/pickup-point'
|
||||
|
||||
const deliveries = [
|
||||
{
|
||||
title: 'Самовывоз',
|
||||
icon: <Store size={28} />,
|
||||
lines: ['Бесплатно.', PICKUP_ADDRESS_FULL, 'Перед визитом согласуем время — чтобы заказ точно был готов к выдаче.'],
|
||||
},
|
||||
{
|
||||
title: 'Почта / Службы доставки',
|
||||
icon: <Package size={28} />,
|
||||
lines: [
|
||||
'Отправка в другие города.',
|
||||
'Каждому заказу присваивается трек-номер для отслеживания.',
|
||||
'Стоимость рассчитывается по тарифу перевозчика при оформлении.',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export function DeliverySection() {
|
||||
return (
|
||||
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}>
|
||||
@@ -29,23 +15,51 @@ export function DeliverySection() {
|
||||
Доставка
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
{deliveries.map((d) => (
|
||||
<Grid key={d.title} size={{ xs: 12, sm: 6, md: 4 }}>
|
||||
<Paper variant="outlined" sx={{ p: 2, borderRadius: 2, height: '100%' }}>
|
||||
<Stack spacing={1.5}>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
{d.icon}
|
||||
<Typography variant="h6">{d.title}</Typography>
|
||||
</Stack>
|
||||
{d.lines.map((line, i) => (
|
||||
<Typography key={i} variant="body2" color="text.secondary">
|
||||
{line}
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<Paper variant="outlined" sx={{ p: 2, borderRadius: 2, height: '100%' }}>
|
||||
<Stack spacing={1.5}>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
<Store size={28} />
|
||||
<Typography variant="h6">Самовывоз</Typography>
|
||||
</Stack>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Бесплатно.
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{PICKUP_ADDRESS_FULL}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Перед визитом согласуем время — чтобы заказ точно был готов к выдаче.
|
||||
</Typography>
|
||||
<Link component={RouterLink} to="/about" variant="body2">
|
||||
Посмотреть на карте
|
||||
</Link>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
<Paper variant="outlined" sx={{ p: 2, borderRadius: 2, height: '100%' }}>
|
||||
<Stack spacing={1.5}>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
||||
<Package size={28} />
|
||||
<Typography variant="h6">Доставка по России</Typography>
|
||||
</Stack>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Доступные службы доставки:
|
||||
</Typography>
|
||||
<Stack component="ul" spacing={0.5} sx={{ pl: 2, m: 0, listStyle: 'disc' }}>
|
||||
{DELIVERY_CARRIER_OPTIONS.map((c) => (
|
||||
<Typography key={c.code} component="li" variant="body2" color="text.secondary">
|
||||
{c.label}
|
||||
</Typography>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Grid>
|
||||
))}
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Стоимость рассчитывается по тарифу перевозчика. Админ скорректирует цену после оформления заказа.
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
import { useState } from 'react'
|
||||
import Box from '@mui/material/Box'
|
||||
import Link from '@mui/material/Link'
|
||||
import Paper from '@mui/material/Paper'
|
||||
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 Tabs from '@mui/material/Tabs'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import { CheckCircle, ClipboardList, Mail, ShoppingCart, Truck } from 'lucide-react'
|
||||
import {
|
||||
CheckCircle,
|
||||
ClipboardList,
|
||||
Clock,
|
||||
CreditCard,
|
||||
MapPin,
|
||||
PackageOpen,
|
||||
ShoppingCart,
|
||||
Star,
|
||||
Store,
|
||||
Truck,
|
||||
} from 'lucide-react'
|
||||
import { Link as RouterLink } from 'react-router-dom'
|
||||
import { PICKUP_ADDRESS_SHORT } from '@/shared/constants/pickup-point'
|
||||
|
||||
const steps = [
|
||||
const commonSteps = [
|
||||
{
|
||||
label: 'Выберите товары',
|
||||
icon: <ShoppingCart size={20} />,
|
||||
@@ -17,39 +35,96 @@ const steps = [
|
||||
icon: <ClipboardList size={20} />,
|
||||
text: 'Перейдите в корзину и проверьте состав заказа: названия товаров, количество и итоговую сумму. Здесь же можно изменить количество или удалить позиции.',
|
||||
},
|
||||
]
|
||||
|
||||
const deliverySteps = [
|
||||
{
|
||||
label: 'Укажите контакты и адрес',
|
||||
icon: <Mail size={20} />,
|
||||
text: 'Заполните имя, телефон и email для связи. Укажите адрес доставки — город, улицу, дом и квартиру. Эти данные нужны для расчёта стоимости и сроков.',
|
||||
label: 'Укажите адрес доставки и получателя',
|
||||
icon: <MapPin size={20} />,
|
||||
text: 'Заполните имя, телефон, email и адрес доставки. Если у вас уже есть сохранённые адреса — выберите из списка или добавьте новый в личном кабинете.',
|
||||
},
|
||||
{
|
||||
label: 'Выберите доставку и оплату',
|
||||
label: 'Выберите способ доставки',
|
||||
icon: <Truck size={20} />,
|
||||
text: 'Выберите способ доставки: самовывоз, курьер или почта/СДЭК. Затем укажите способ оплаты: картой онлайн или при получении.',
|
||||
text: 'Доступны: Почта России, Озон ПВЗ, Яндекс ПВЗ, 5Post, WB ПВЗ. После оформления админ рассчитает точную стоимость доставки и скорректирует цену заказа.',
|
||||
},
|
||||
{
|
||||
label: 'Подтвердите заказ',
|
||||
icon: <CheckCircle size={20} />,
|
||||
text: 'Проверьте все данные ещё раз и нажмите «Оформить заказ». После этого мастер получит уведомление и начнёт подготовку вашего изделия.',
|
||||
label: 'Оплатите заказ',
|
||||
icon: <CreditCard size={20} />,
|
||||
text: 'Онлайн-оплата через ЮKassa — банковские карты и СБП. Перенаправление на защищённую платёжную страницу.',
|
||||
},
|
||||
{
|
||||
label: 'Получите заказ',
|
||||
icon: <PackageOpen size={20} />,
|
||||
text: 'После отправки вы получите трек-номер для отслеживания. Следите за статусом заказа в личном кабинете.',
|
||||
},
|
||||
]
|
||||
|
||||
const pickupSteps = [
|
||||
{
|
||||
label: 'Подтвердите заказ',
|
||||
icon: <CheckCircle size={20} />,
|
||||
text: 'Выберите способ оплаты: онлайн через ЮKassa (карты, СБП) или при получении (наличные / карта).',
|
||||
},
|
||||
{
|
||||
label: 'Согласуйте время получения',
|
||||
icon: <Clock size={20} />,
|
||||
text: 'Админ свяжется с вами, чтобы договориться об удобном времени выдачи заказа.',
|
||||
},
|
||||
{
|
||||
label: 'Получите заказ',
|
||||
icon: <Store size={20} />,
|
||||
text: `Адрес: ${PICKUP_ADDRESS_SHORT}. Перед визитом согласуем время, чтобы заказ точно был готов.`,
|
||||
},
|
||||
]
|
||||
|
||||
function BranchStepper({ steps }: { steps: typeof deliverySteps }) {
|
||||
return (
|
||||
<Stepper orientation="vertical" activeStep={-1}>
|
||||
{steps.map((step) => (
|
||||
<Step key={step.label} completed={false}>
|
||||
<StepLabel slots={{ stepIcon: () => step.icon }}>{step.label}</StepLabel>
|
||||
<StepContent>
|
||||
<Typography color="text.secondary">{step.text}</Typography>
|
||||
</StepContent>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
)
|
||||
}
|
||||
|
||||
export function HowToOrderSection() {
|
||||
const [tab, setTab] = useState(0)
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Как оформить заказ
|
||||
</Typography>
|
||||
<Stepper orientation="vertical" activeStep={-1}>
|
||||
{steps.map((step) => (
|
||||
<Step key={step.label} completed={false}>
|
||||
<StepLabel slots={{ stepIcon: () => step.icon }}>{step.label}</StepLabel>
|
||||
<StepContent>
|
||||
<Typography color="text.secondary">{step.text}</Typography>
|
||||
</StepContent>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
<BranchStepper steps={commonSteps} />
|
||||
<Box sx={{ mt: 2, mb: 1 }}>
|
||||
<Tabs value={tab} onChange={(_, v) => setTab(v)} variant="fullWidth">
|
||||
<Tab label="Доставка" />
|
||||
<Tab label="Самовывоз" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
{tab === 0 && <BranchStepper steps={deliverySteps} />}
|
||||
{tab === 1 && (
|
||||
<Box>
|
||||
<BranchStepper steps={pickupSteps} />
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
<Link component={RouterLink} to="/about">
|
||||
Посмотреть на карте
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ mt: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Star size={18} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
После получения заказа вы можете оставить отзыв в личном кабинете.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Paper from '@mui/material/Paper'
|
||||
import Stack from '@mui/material/Stack'
|
||||
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'
|
||||
|
||||
const iconMap: Record<StatusIconName, ReactElement> = {
|
||||
banknote: <Banknote size={18} />,
|
||||
'check-circle': <CheckCircle size={18} />,
|
||||
'package-search': <PackageSearch size={18} />,
|
||||
package: <Package size={18} />,
|
||||
'package-check': <PackageCheck size={18} />,
|
||||
store: <Store size={18} />,
|
||||
'x-circle': <XCircle size={18} />,
|
||||
}
|
||||
|
||||
export function OrderStatusesSection() {
|
||||
return (
|
||||
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Статусы заказа
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
Текущий статус отображается в личном кабинете. Вот что означает каждый из них:
|
||||
</Typography>
|
||||
<Stack spacing={2}>
|
||||
{ORDER_STATUS_DATA.map((s) => (
|
||||
<Stack key={s.code} direction="row" spacing={2} sx={{ alignItems: 'flex-start' }}>
|
||||
<Chip
|
||||
icon={iconMap[s.iconName]}
|
||||
label={s.label}
|
||||
color={s.color}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ minWidth: 180, flexShrink: 0 }}
|
||||
/>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ pt: 0.3 }}>
|
||||
{s.description}
|
||||
</Typography>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import { Banknote, CreditCard } from 'lucide-react'
|
||||
const methods = [
|
||||
{
|
||||
icon: <CreditCard size={22} />,
|
||||
primary: 'Банковская карта онлайн',
|
||||
secondary: 'Оплата картой Visa, Mastercard или МИР сразу при оформлении заказа.',
|
||||
primary: 'Онлайн-оплата через ЮKassa (карты, СБП)',
|
||||
secondary: 'Оплата после подтверждения заказа админом. Перенаправление на защищённую платёжную страницу ЮKassa.',
|
||||
},
|
||||
{
|
||||
icon: <Banknote size={22} />,
|
||||
primary: 'Оплата при получении',
|
||||
secondary: 'Оплата наличными или картой при получении заказа.',
|
||||
secondary: 'Наличными или картой при самовывозе.',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -26,7 +26,7 @@ export function PaymentSection() {
|
||||
Оплата
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
|
||||
Оплата происходит после подтверждения заказа мастером. Вы получите уведомление, когда заказ будет подтверждён и
|
||||
Оплата происходит после подтверждения заказа админом. Вы получите уведомление, когда заказ будет подтверждён и
|
||||
готов к оплате.
|
||||
</Typography>
|
||||
<List disablePadding>
|
||||
|
||||
@@ -25,7 +25,7 @@ import { deliveryCarrierLabelRu } from '@/shared/constants/delivery-carrier'
|
||||
import { PICKUP_ADDRESS_FULL } from '@/shared/constants/pickup-point'
|
||||
import { formatPriceRub } from '@/shared/lib/format-price'
|
||||
import { parseOrderAddressSnapshot } from '@/shared/lib/order-address-snapshot'
|
||||
import { orderStatusLabelRu } from '@/shared/lib/order-status-labels'
|
||||
import { OrderStatusChip } from '@/shared/ui/OrderStatusChip'
|
||||
|
||||
export function OrderDetailPage() {
|
||||
const { id } = useParams()
|
||||
@@ -130,7 +130,14 @@ export function OrderDetailPage() {
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ mb: 2, alignItems: { sm: 'center' } }}>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Typography variant="h4">Заказ #{order.id.slice(-6)}</Typography>
|
||||
<Typography color="text.secondary">Статус: {orderStatusLabelRu(order.status)}</Typography>
|
||||
<OrderStatusChip
|
||||
status={order.status}
|
||||
tooltipOverride={
|
||||
order.status === 'PENDING_PAYMENT' && order.deliveryType === 'delivery' && !order.deliveryFeeLocked
|
||||
? 'Оплата происходит после подтверждения заказа админом. Вы получите уведомление, когда заказ будет подтверждён и готов к оплате.'
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
<Button component={RouterLink} to="/me/orders" variant="outlined">
|
||||
К списку
|
||||
|
||||
@@ -4,3 +4,6 @@ export const PICKUP_COORDINATES = { lat: 58.09898000206914, lng: 57.813169680997
|
||||
/** Полная строка адреса для текстовых блоков. */
|
||||
export const PICKUP_ADDRESS_FULL =
|
||||
'618909, Россия, Пермский край, Лысьвенский муниципальный округ, Лысьва, улица Мира, 34'
|
||||
|
||||
/** Короткий адрес для компактных блоков. */
|
||||
export const PICKUP_ADDRESS_SHORT = 'Лысьва, ул. Мира, 34'
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
export type StatusColor = 'warning' | 'success' | 'info' | 'error'
|
||||
export type StatusIconName = 'banknote' | 'check-circle' | 'package-search' | 'package' | 'package-check' | 'store' | 'x-circle'
|
||||
|
||||
export interface OrderStatusData {
|
||||
code: string
|
||||
label: string
|
||||
iconName: StatusIconName
|
||||
color: StatusColor
|
||||
description: string
|
||||
}
|
||||
|
||||
export const ORDER_STATUS_DATA: ReadonlyArray<OrderStatusData> = [
|
||||
{
|
||||
code: 'PENDING_PAYMENT',
|
||||
label: 'Ожидает оплаты',
|
||||
iconName: 'banknote',
|
||||
color: 'warning',
|
||||
description:
|
||||
'Заказ оформлен и подтверждён администратором. Оплатите онлайн через ЮKassa или дождитесь получения (для самовывоза с оплатой при получении).',
|
||||
},
|
||||
{
|
||||
code: 'PAID',
|
||||
label: 'Оплачен',
|
||||
iconName: 'check-circle',
|
||||
color: 'success',
|
||||
description: 'Оплата получена. Админ скоро возьмёт заказ в работу.',
|
||||
},
|
||||
{
|
||||
code: 'IN_PROGRESS',
|
||||
label: 'Подготовка к отправке',
|
||||
iconName: 'package-search',
|
||||
color: 'info',
|
||||
description: 'Админ готовит заказ к отправке или выдаче. Скоро статус обновится.',
|
||||
},
|
||||
{
|
||||
code: 'SHIPPED',
|
||||
label: 'Отправлен',
|
||||
iconName: 'package',
|
||||
color: 'info',
|
||||
description: 'Заказ передан в службу доставки. Трек-номер для отслеживания(при наличии) будет указан в сообщении админа.',
|
||||
},
|
||||
{
|
||||
code: 'READY_FOR_PICKUP',
|
||||
label: 'Готов к получению',
|
||||
iconName: 'store',
|
||||
color: 'success',
|
||||
description: 'Заказ готов к самовывозу. Приезжайте в согласованное время.',
|
||||
},
|
||||
{
|
||||
code: 'DONE',
|
||||
label: 'Завершён',
|
||||
iconName: 'package-check',
|
||||
color: 'success',
|
||||
description: 'Заказ получен. Вы можете оставить отзыв в личном кабинете.',
|
||||
},
|
||||
{
|
||||
code: 'CANCELLED',
|
||||
label: 'Отменён',
|
||||
iconName: 'x-circle',
|
||||
color: 'error',
|
||||
description: 'Заказ отменён. Если оплата была произведена, средства вернутся на карту.',
|
||||
},
|
||||
]
|
||||
|
||||
export function getOrderStatusData(code: string): OrderStatusData | undefined {
|
||||
return ORDER_STATUS_DATA.find((s) => s.code === code)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export function orderStatusLabelRu(code: string): string {
|
||||
DRAFT: 'Черновик',
|
||||
PENDING_PAYMENT: 'Ожидает оплаты',
|
||||
PAID: 'Оплачен',
|
||||
IN_PROGRESS: 'В работе',
|
||||
IN_PROGRESS: 'Подготовка к отправке',
|
||||
SHIPPED: 'Отправлен',
|
||||
READY_FOR_PICKUP: 'Готово к получению',
|
||||
DONE: 'Завершён',
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { ReactElement } from 'react'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import { Banknote, CheckCircle, Package, PackageCheck, PackageSearch, Store, XCircle } from 'lucide-react'
|
||||
import { getOrderStatusData, type StatusIconName } from '@/shared/lib/order-status-data'
|
||||
|
||||
const iconMap: Record<StatusIconName, ReactElement> = {
|
||||
banknote: <Banknote size={18} />,
|
||||
'check-circle': <CheckCircle size={18} />,
|
||||
'package-search': <PackageSearch size={18} />,
|
||||
package: <Package size={18} />,
|
||||
'package-check': <PackageCheck size={18} />,
|
||||
store: <Store size={18} />,
|
||||
'x-circle': <XCircle size={18} />,
|
||||
}
|
||||
|
||||
interface OrderStatusChipProps {
|
||||
status: string
|
||||
tooltipOverride?: string
|
||||
}
|
||||
|
||||
export function OrderStatusChip({ status, tooltipOverride }: OrderStatusChipProps) {
|
||||
const data = getOrderStatusData(status)
|
||||
const label = data?.label ?? status
|
||||
const color = data?.color ?? 'default'
|
||||
const icon = data ? iconMap[data.iconName] : undefined
|
||||
const tooltip = tooltipOverride ?? data?.description ?? ''
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltip} arrow placement="top">
|
||||
<Chip icon={icon} label={label} color={color as 'default'} size="small" variant="outlined" />
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
# Редизайн страницы /info — Спецификация
|
||||
|
||||
**Дата:** 2026-05-24
|
||||
**Страница:** `/info` (Информация для покупателей)
|
||||
**Подход:** Минимальные правки — обновить контент существующих секций, не меняя структуру страницы.
|
||||
|
||||
---
|
||||
|
||||
## 1. Секция «Как оформить заказ» (HowToOrderSection)
|
||||
|
||||
MUI Stepper с ветвлением на шаге 3. Два сценария: доставка и самовывоз.
|
||||
|
||||
### Общие шаги (1–2):
|
||||
|
||||
| Шаг | Название | Описание |
|
||||
|-----|----------|----------|
|
||||
| 1 | Выберите товары | Найдите нужное в каталоге, добавьте в корзину |
|
||||
| 2 | Проверьте корзину | Убедитесь, что состав и количество верны |
|
||||
|
||||
### Ветвление на шаге 3:
|
||||
|
||||
#### Доставка:
|
||||
|
||||
| Шаг | Название | Описание |
|
||||
|-----|----------|----------|
|
||||
| 3 | Укажите адрес доставки и получателя | Имя, телефон, email, адрес (или добавьте новый в личном кабинете) |
|
||||
| 4 | Выберите способ доставки | Почта России, Озон ПВЗ, Яндекс ПВЗ, 5Post, WB ПВЗ. Дождитесь расчёта стоимости доставки — мастер скорректирует цену |
|
||||
| 5 | Оплатите заказ | Онлайн через ЮKassa (карты, СБП) |
|
||||
| 6 | Получите заказ | Отслеживайте по трек-номеру |
|
||||
|
||||
#### Самовывоз:
|
||||
|
||||
| Шаг | Название | Описание |
|
||||
|-----|----------|----------|
|
||||
| 3 | Подтвердите заказ | Выберите оплату: онлайн через ЮKassa или при получении |
|
||||
| 4 | Согласуйте время получения | Мастер свяжется с вами, чтобы договориться о времени |
|
||||
| 5 | Получите заказ | Адрес: ул. Мира, 34 (ссылка на /about — карта) |
|
||||
|
||||
### Финальный шаг (после получения):
|
||||
После получения заказа можно оставить отзыв.
|
||||
|
||||
---
|
||||
|
||||
## 2. Секция «Доставка» (DeliverySection)
|
||||
|
||||
Две карточки:
|
||||
|
||||
### Самовывоз
|
||||
- Бесплатно
|
||||
- Адрес: 618909, Россия, Пермский край, Лысьвенский муниципальный округ, Лысьва, улица Мира, 34
|
||||
- Ссылка на /about (карта)
|
||||
- Перед визитом согласуем время — чтобы заказ точно был готов к выдаче
|
||||
|
||||
### Доставка по России
|
||||
Перечислить все 5 служб:
|
||||
- Почта России
|
||||
- Озон доставка (пункт выдачи)
|
||||
- Яндекс доставка (пункт выдачи)
|
||||
- 5Post (пункт выдачи)
|
||||
- WB доставка (пункт выдачи)
|
||||
- Каждому заказу присваивается трек-номер для отслеживания
|
||||
- Стоимость рассчитывается по тарифу перевозчика, мастер скорректирует цену после оформления
|
||||
|
||||
---
|
||||
|
||||
## 3. Секция «Оплата» (PaymentSection)
|
||||
|
||||
Два способа:
|
||||
|
||||
| Способ | Описание |
|
||||
|--------|----------|
|
||||
| Онлайн-оплата через ЮKassa (карты, СБП) | Оплата после подтверждения заказа мастером. Перенаправление на защищённую платёжную страницу ЮKassa |
|
||||
| Оплата при получении | Наличными или картой при самовывозе |
|
||||
|
||||
Примечание: оплата происходит после подтверждения заказа мастером.
|
||||
|
||||
---
|
||||
|
||||
## 4. Секция «Возврат» (ReturnsSection)
|
||||
|
||||
Без изменений — текущий контент остаётся.
|
||||
|
||||
---
|
||||
|
||||
## Файлы для изменения
|
||||
|
||||
| Файл | Что менять |
|
||||
|------|-----------|
|
||||
| `client/src/pages/info/ui/sections/HowToOrderSection.tsx` | Переписать stepper с ветвлением |
|
||||
| `client/src/pages/info/ui/sections/DeliverySection.tsx` | Перечислить carriers, добавить ссылку на /about |
|
||||
| `client/src/pages/info/ui/sections/PaymentSection.tsx` | Заменить «Банковская карта онлайн» на «ЮKassa (карты, СБП)» |
|
||||
Binary file not shown.
@@ -10,10 +10,10 @@ export function renderOrderStatusChangedTg({ orderId, oldStatus, newStatus }) {
|
||||
DRAFT: 'Черновик',
|
||||
PENDING_PAYMENT: 'Ожидает оплаты',
|
||||
PAID: 'Оплачен',
|
||||
IN_PROGRESS: 'В работе',
|
||||
IN_PROGRESS: 'Подготовка к отправке',
|
||||
READY_FOR_PICKUP: 'Готов к выдаче',
|
||||
SHIPPED: 'Отправлен',
|
||||
DONE: 'Выполнен',
|
||||
DONE: 'Завершён',
|
||||
CANCELLED: 'Отменён',
|
||||
}
|
||||
return `🔄 Заказ #${orderId.slice(0, 8)}\n${labels[oldStatus] || oldStatus} → <b>${labels[newStatus] || newStatus}</b>`
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
export const ORDER_STATUSES = Object.freeze([
|
||||
'DRAFT',
|
||||
'PENDING_PAYMENT',
|
||||
'PAID',
|
||||
'IN_PROGRESS',
|
||||
'SHIPPED',
|
||||
'READY_FOR_PICKUP',
|
||||
'DONE',
|
||||
'CANCELLED',
|
||||
])
|
||||
"DRAFT",
|
||||
"PENDING_PAYMENT",
|
||||
"PAID",
|
||||
"IN_PROGRESS",
|
||||
"SHIPPED",
|
||||
"READY_FOR_PICKUP",
|
||||
"DONE",
|
||||
"CANCELLED",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Допустимые переходы статусов, доступные админу.
|
||||
@@ -15,24 +15,24 @@ export const ORDER_STATUSES = Object.freeze([
|
||||
* Для IN_PROGRESS: объект с ключами по deliveryType.
|
||||
*/
|
||||
export const ADMIN_ORDER_TRANSITIONS = Object.freeze({
|
||||
DRAFT: ['PENDING_PAYMENT', 'CANCELLED'],
|
||||
PENDING_PAYMENT: ['PAID', 'CANCELLED'],
|
||||
PAID: ['IN_PROGRESS', 'CANCELLED'],
|
||||
DRAFT: ["PENDING_PAYMENT", "CANCELLED"],
|
||||
PENDING_PAYMENT: ["PAID", "CANCELLED"],
|
||||
PAID: ["IN_PROGRESS", "CANCELLED"],
|
||||
IN_PROGRESS: Object.freeze({
|
||||
delivery: ['SHIPPED', 'CANCELLED'],
|
||||
pickup: ['READY_FOR_PICKUP', 'CANCELLED'],
|
||||
delivery: ["SHIPPED", "CANCELLED"],
|
||||
pickup: ["READY_FOR_PICKUP", "CANCELLED"],
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
export function getNextAdminStatuses(from, deliveryType) {
|
||||
const transition = ADMIN_ORDER_TRANSITIONS[from]
|
||||
if (!transition) return []
|
||||
if (Array.isArray(transition)) return [...transition]
|
||||
return transition[deliveryType] ? [...transition[deliveryType]] : []
|
||||
const transition = ADMIN_ORDER_TRANSITIONS[from];
|
||||
if (!transition) return [];
|
||||
if (Array.isArray(transition)) return [...transition];
|
||||
return transition[deliveryType] ? [...transition[deliveryType]] : [];
|
||||
}
|
||||
|
||||
export function canTransitionAdminOrderStatus(order, next) {
|
||||
const from = order.status
|
||||
if (from === next) return true
|
||||
return getNextAdminStatuses(from, order.deliveryType).includes(next)
|
||||
const from = order.status;
|
||||
if (from === next) return true;
|
||||
return getNextAdminStatuses(from, order.deliveryType).includes(next);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user