diff --git a/.superpowers/brainstorm/7074-1779609479/state/server-info b/.superpowers/brainstorm/7074-1779609479/state/server-info
deleted file mode 100644
index a9aa216..0000000
--- a/.superpowers/brainstorm/7074-1779609479/state/server-info
+++ /dev/null
@@ -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"}
diff --git a/.superpowers/brainstorm/7074-1779609479/state/server-stopped b/.superpowers/brainstorm/7074-1779609479/state/server-stopped
new file mode 100644
index 0000000..37b7b01
--- /dev/null
+++ b/.superpowers/brainstorm/7074-1779609479/state/server-stopped
@@ -0,0 +1 @@
+{"reason":"idle timeout","timestamp":1779612416287}
diff --git a/client/src/pages/info/ui/InfoPage.tsx b/client/src/pages/info/ui/InfoPage.tsx
index 81dd0f5..7f47023 100644
--- a/client/src/pages/info/ui/InfoPage.tsx
+++ b/client/src/pages/info/ui/InfoPage.tsx
@@ -1,4 +1,6 @@
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 Typography from '@mui/material/Typography'
import { DeliverySection } from './sections/DeliverySection'
@@ -9,21 +11,58 @@ import { ReturnsSection } from './sections/ReturnsSection'
export function InfoPage() {
return (
-
-
- Информация для покупателей
-
-
- Как оформить заказ, как проходит доставка, оплата и другие важные детали.
-
+
+ {/* Hero */}
+
+
+ Информация для покупателей
+
+
+ Как оформить заказ, как проходит доставка, оплата и другие важные детали.
+
+
-
-
-
-
-
-
-
-
+ {/* Main content grid */}
+
+ {/* Left column */}
+
+
+
+
+
+
+ {/* Right column */}
+
+
+
+
+
+
+
+
+
)
}
diff --git a/client/src/pages/info/ui/sections/DeliverySection.tsx b/client/src/pages/info/ui/sections/DeliverySection.tsx
index 72c6377..77ba4cc 100644
--- a/client/src/pages/info/ui/sections/DeliverySection.tsx
+++ b/client/src/pages/info/ui/sections/DeliverySection.tsx
@@ -1,6 +1,5 @@
-import Grid from '@mui/material/Grid'
+import Box from '@mui/material/Box'
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'
@@ -10,57 +9,163 @@ import { PICKUP_ADDRESS_FULL } from '@/shared/constants/pickup-point'
export function DeliverySection() {
return (
-
-
+
+
Доставка
-
-
-
-
-
-
- Самовывоз
-
-
- Бесплатно.
-
-
- {PICKUP_ADDRESS_FULL}
-
-
- Перед визитом согласуем время — чтобы заказ точно был готов к выдаче.
-
-
- Посмотреть на карте
-
-
-
-
-
-
-
-
-
- Доставка по России
-
-
- Доступные службы доставки:
-
-
- {DELIVERY_CARRIER_OPTIONS.map((c) => (
-
- {c.label}
-
- ))}
-
-
- Стоимость рассчитывается по тарифу перевозчика. Админ скорректирует цену после оформления заказа.
+
+
+ {/* Pickup */}
+
+
+
+
+
+
+
+ Самовывоз
-
-
-
-
+
+ Бесплатно.
+
+
+ {PICKUP_ADDRESS_FULL}
+
+
+ Перед визитом согласуем время — чтобы заказ точно был готов к выдаче.
+
+
+ Посмотреть на карте
+
+
+
+
+
+
+ {/* Delivery */}
+
+
+
+
+
+
+
+ Доставка по России
+
+
+
+ Доступные службы доставки:
+
+
+ {DELIVERY_CARRIER_OPTIONS.map((c) => (
+
+ {c.label}
+
+ ))}
+
+
+ Стоимость рассчитывается по тарифу перевозчика. Админ скорректирует цену после оформления заказа.
+
+
+
+
+
)
}
diff --git a/client/src/pages/info/ui/sections/HowToOrderSection.tsx b/client/src/pages/info/ui/sections/HowToOrderSection.tsx
index e776bb0..a5aeab7 100644
--- a/client/src/pages/info/ui/sections/HowToOrderSection.tsx
+++ b/client/src/pages/info/ui/sections/HowToOrderSection.tsx
@@ -1,11 +1,7 @@
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 Stack from '@mui/material/Stack'
import Tab from '@mui/material/Tab'
import Tabs from '@mui/material/Tabs'
import Typography from '@mui/material/Typography'
@@ -27,12 +23,12 @@ import { PICKUP_ADDRESS_SHORT } from '@/shared/constants/pickup-point'
const commonSteps = [
{
label: 'Выберите товары',
- icon: ,
+ icon: ,
text: 'Найдите нужные изделия в каталоге и добавьте их в корзину. Вы можете выбрать несколько товаров от разных мастеров — все они соберутся в одном заказе.',
},
{
label: 'Проверьте корзину',
- icon: ,
+ icon: ,
text: 'Перейдите в корзину и проверьте состав заказа: названия товаров, количество и итоговую сумму. Здесь же можно изменить количество или удалить позиции.',
},
]
@@ -40,22 +36,22 @@ const commonSteps = [
const deliverySteps = [
{
label: 'Укажите адрес доставки и получателя',
- icon: ,
+ icon: ,
text: 'Заполните имя, телефон, email и адрес доставки. Если у вас уже есть сохранённые адреса — выберите из списка или добавьте новый в личном кабинете.',
},
{
label: 'Выберите способ доставки',
- icon: ,
+ icon: ,
text: 'Доступны: Почта России, Озон ПВЗ, Яндекс ПВЗ, 5Post, WB ПВЗ. После оформления админ рассчитает точную стоимость доставки и скорректирует цену заказа.',
},
{
label: 'Оплатите заказ',
- icon: ,
+ icon: ,
text: 'Онлайн-оплата через ЮKassa — банковские карты и СБП. Перенаправление на защищённую платёжную страницу.',
},
{
label: 'Получите заказ',
- icon: ,
+ icon: ,
text: 'После отправки вы получите трек-номер для отслеживания. Следите за статусом заказа в личном кабинете.',
},
]
@@ -63,33 +59,86 @@ const deliverySteps = [
const pickupSteps = [
{
label: 'Подтвердите заказ',
- icon: ,
+ icon: ,
text: 'Выберите способ оплаты: онлайн через ЮKassa (карты, СБП) или при получении (наличные / карта).',
},
{
label: 'Согласуйте время получения',
- icon: ,
+ icon: ,
text: 'Админ свяжется с вами, чтобы договориться об удобном времени выдачи заказа.',
},
{
label: 'Получите заказ',
- icon: ,
+ icon: ,
text: `Адрес: ${PICKUP_ADDRESS_SHORT}. Перед визитом согласуем время, чтобы заказ точно был готов.`,
},
]
+function StepRow({ step, isLast }: { step: typeof commonSteps[0]; isLast: boolean }) {
+ return (
+
+
+
+ {step.icon}
+
+ {!isLast && (
+
+ )}
+
+
+
+ {step.label}
+
+
+ {step.text}
+
+
+
+ )
+}
+
function BranchStepper({ steps }: { steps: typeof deliverySteps }) {
return (
-
- {steps.map((step) => (
-
- step.icon }}>{step.label}
-
- {step.text}
-
-
+
+ {steps.map((step, i) => (
+
))}
-
+
)
}
@@ -97,34 +146,88 @@ export function HowToOrderSection() {
const [tab, setTab] = useState(0)
return (
-
-
+
+
Как оформить заказ
+
-
- setTab(v)} variant="fullWidth">
+
+
+ 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 === 0 && }
{tab === 1 && (
-
-
+
+
Посмотреть на карте
)}
-
-
-
+
+
+
+
+
+
После получения заказа вы можете оставить отзыв в личном кабинете.
-
+
)
}
diff --git a/client/src/pages/info/ui/sections/OrderStatusesSection.tsx b/client/src/pages/info/ui/sections/OrderStatusesSection.tsx
index 29c079a..fbd3738 100644
--- a/client/src/pages/info/ui/sections/OrderStatusesSection.tsx
+++ b/client/src/pages/info/ui/sections/OrderStatusesSection.tsx
@@ -1,47 +1,247 @@
-import type { ReactElement } from 'react'
-import Chip from '@mui/material/Chip'
-import Paper from '@mui/material/Paper'
+import Box from '@mui/material/Box'
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 = {
- banknote: ,
- 'check-circle': ,
- 'package-search': ,
- package: ,
- 'package-check': ,
- store: ,
- 'x-circle': ,
+const iconMap: Record = {
+ banknote: (
+
+ ),
+ 'check-circle': (
+
+ ),
+ 'package-search': (
+
+ ),
+ package: (
+
+ ),
+ 'package-check': (
+
+ ),
+ store: (
+
+ ),
+ 'x-circle': (
+
+ ),
+}
+
+const colorMap: Record = {
+ 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() {
return (
-
-
- Статусы заказа
-
-
- Текущий статус отображается в личном кабинете. Вот что означает каждый из них:
-
-
- {ORDER_STATUS_DATA.map((s) => (
-
-
-
- {s.description}
-
-
- ))}
+
+
+
+ Статусы заказа
+
+
+ Текущий статус отображается в личном кабинете. Каждый этап отражает, что происходит с вашим заказом.
+
+
+
+
+ {ORDER_STATUS_DATA.map((s, index) => {
+ const colors = colorMap[s.color] ?? colorMap.info
+ const isLast = index === ORDER_STATUS_DATA.length - 1
+
+ return (
+
+ {/* Content column */}
+
+
+
+ {iconMap[s.iconName]}
+
+
+ {s.label}
+
+
+
+ {s.description}
+
+
+
+ )
+ })}
-
+
)
}
diff --git a/client/src/pages/info/ui/sections/PaymentSection.tsx b/client/src/pages/info/ui/sections/PaymentSection.tsx
index 4288910..0e43938 100644
--- a/client/src/pages/info/ui/sections/PaymentSection.tsx
+++ b/client/src/pages/info/ui/sections/PaymentSection.tsx
@@ -1,19 +1,16 @@
-import List from '@mui/material/List'
-import ListItem from '@mui/material/ListItem'
-import ListItemIcon from '@mui/material/ListItemIcon'
-import ListItemText from '@mui/material/ListItemText'
-import Paper from '@mui/material/Paper'
+import Box from '@mui/material/Box'
+import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
import { Banknote, CreditCard } from 'lucide-react'
const methods = [
{
- icon: ,
- primary: 'Онлайн-оплата через ЮKassa (карты, СБП)',
- secondary: 'Оплата после подтверждения заказа админом. Перенаправление на защищённую платёжную страницу ЮKassa.',
+ icon: ,
+ primary: 'Онлайн-оплата через ЮKassa',
+ secondary: 'Карты, СБП. Оплата после подтверждения заказа админом. Перенаправление на защищённую платёжную страницу.',
},
{
- icon: ,
+ icon: ,
primary: 'Оплата при получении',
secondary: 'Наличными или картой при самовывозе.',
},
@@ -21,22 +18,86 @@ const methods = [
export function PaymentSection() {
return (
-
-
+
+
Оплата
-
- Оплата происходит после подтверждения заказа админом. Вы получите уведомление, когда заказ будет подтверждён и
- готов к оплате.
+
+ Оплата происходит после подтверждения заказа админом. Вы получите уведомление, когда заказ будет подтверждён и готов к оплате.
-
+
+
{methods.map((m) => (
-
- {m.icon}
-
-
+
+
+ {m.icon}
+
+
+
+ {m.primary}
+
+
+ {m.secondary}
+
+
+
))}
-
-
+
+
)
}
diff --git a/client/src/pages/info/ui/sections/ReturnsSection.tsx b/client/src/pages/info/ui/sections/ReturnsSection.tsx
index 9e691f8..63fc641 100644
--- a/client/src/pages/info/ui/sections/ReturnsSection.tsx
+++ b/client/src/pages/info/ui/sections/ReturnsSection.tsx
@@ -1,35 +1,67 @@
-import Paper from '@mui/material/Paper'
+import Box from '@mui/material/Box'
import Stack from '@mui/material/Stack'
import Typography from '@mui/material/Typography'
export function ReturnsSection() {
return (
-
-
+
+
Возврат и гарантии
-
-
-
+
+
+
+
Возврат
-
- Если товар не соответствует описанию или имеет производственный дефект, свяжитесь с нами в течение 7 дней
- после получения. Мы заменим изделие на аналогичное или вернём деньги. Возврат товара надлежащего качества
- возможен в течение 14 дней, если изделие не было в употреблении и сохранён его товарный вид.
+
+ Если товар не соответствует описанию или имеет производственный дефект, свяжитесь с нами в течение 7 дней после получения. Мы заменим изделие на аналогичное или вернём деньги. Возврат товара надлежащего качества возможен в течение 14 дней, если изделие не было в употреблении и сохранён его товарный вид.
-
-
-
+
+
+
+
Гарантия качества
-
- Мы отвечаем за качество каждого изделия ручной работы. Все дефекты, возникшие не по вине покупателя,
- устраняются или компенсируются заменой изделия. Если у вас возникли вопросы по качеству — напишите нам, и мы
- решим проблему в кратчайшие сроки.
+
+ Мы отвечаем за качество каждого изделия ручной работы. Все дефекты, возникшие не по вине покупателя, устраняются или компенсируются заменой изделия. Если у вас возникли вопросы по качеству — напишите нам, и мы решим проблему в кратчайшие сроки.
-
+
-
+
)
}
diff --git a/client/src/shared/ui/OrderStatusChip.tsx b/client/src/shared/ui/OrderStatusChip.tsx
index 560f5a3..5e50236 100644
--- a/client/src/shared/ui/OrderStatusChip.tsx
+++ b/client/src/shared/ui/OrderStatusChip.tsx
@@ -1,34 +1,158 @@
-import type { ReactElement } from 'react'
-import Chip from '@mui/material/Chip'
+import { type ReactNode, useState } from 'react'
+import Box from '@mui/material/Box'
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'
-const iconMap: Record = {
- banknote: ,
- 'check-circle': ,
- 'package-search': ,
- package: ,
- 'package-check': ,
- store: ,
- 'x-circle': ,
+const iconMap: Record = {
+ banknote: (
+
+ ),
+ 'check-circle': (
+
+ ),
+ 'package-search': (
+
+ ),
+ package: (
+
+ ),
+ 'package-check': (
+
+ ),
+ store: (
+
+ ),
+ 'x-circle': (
+
+ ),
+}
+
+const colorMap: Record = {
+ 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 {
status: 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 label = data?.label ?? status
- const color = data?.color ?? 'default'
+ const colorKey = data?.color ?? 'default'
const icon = data ? iconMap[data.iconName] : undefined
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 (
-
+ 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)',
+ },
+ }}
+ >
+
+
+ {icon}
+
+
+ {label}
+
+
)
}
diff --git a/server/prisma/prisma/dev.db b/server/prisma/prisma/dev.db
index b8d8010..d8c0596 100644
Binary files a/server/prisma/prisma/dev.db and b/server/prisma/prisma/dev.db differ