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