diff --git a/client/src/pages/info/ui/sections/DeliverySection.tsx b/client/src/pages/info/ui/sections/DeliverySection.tsx index 23a6d2a..2a6cc01 100644 --- a/client/src/pages/info/ui/sections/DeliverySection.tsx +++ b/client/src/pages/info/ui/sections/DeliverySection.tsx @@ -2,7 +2,7 @@ import Grid from '@mui/material/Grid' import Paper from '@mui/material/Paper' import Stack from '@mui/material/Stack' import Typography from '@mui/material/Typography' -import { Package, Store, Truck } from 'lucide-react' +import { Package, Store } from 'lucide-react' import { PICKUP_ADDRESS_FULL } from '@/shared/constants/pickup-point' const deliveries = [ @@ -12,16 +12,7 @@ const deliveries = [ lines: ['Бесплатно.', PICKUP_ADDRESS_FULL, 'Перед визитом согласуем время — чтобы заказ точно был готов к выдаче.'], }, { - title: 'Курьер по городу', - icon: , - lines: [ - 'Доставка в пределах города.', - 'Сроки и стоимость зависят от адреса и веса заказа.', - 'Мастер свяжется с вами для уточнения деталей после оформления.', - ], - }, - { - title: 'Почта / СДЭК', + title: 'Почта / Службы доставки', icon: , lines: [ 'Отправка в другие города.', diff --git a/client/src/pages/info/ui/sections/PaymentSection.tsx b/client/src/pages/info/ui/sections/PaymentSection.tsx index a32639f..d558824 100644 --- a/client/src/pages/info/ui/sections/PaymentSection.tsx +++ b/client/src/pages/info/ui/sections/PaymentSection.tsx @@ -15,7 +15,7 @@ const methods = [ { icon: , primary: 'Оплата при получении', - secondary: 'Оплата наличными или картой при получении заказа у курьера или в пункте самовывоза.', + secondary: 'Оплата наличными или картой при получении заказа.', }, ] diff --git a/docs/superpowers/plans/2026-05-19-info-page-static.md b/docs/superpowers/plans/2026-05-19-info-page-static.md new file mode 100644 index 0000000..6efdf63 --- /dev/null +++ b/docs/superpowers/plans/2026-05-19-info-page-static.md @@ -0,0 +1,636 @@ +# Static Info Page Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace admin-managed dynamic InfoPageBlock CRUD with a hardcoded static React page featuring process schemas, delivery cards, and payment info. + +**Architecture:** New `InfoPage.tsx` container composes four hardcoded section components (no API calls, no DB reads). All admin CRUD files, server routes, Prisma model, and entity layer are removed. + +**Tech Stack:** React + TypeScript + MUI, lucide-react icons. + +--- + +## File Structure + +### Created + +- `client/src/pages/info/ui/sections/HowToOrderSection.tsx` — Stepper with 5 purchase steps +- `client/src/pages/info/ui/sections/DeliverySection.tsx` — 3 delivery option cards in Grid +- `client/src/pages/info/ui/sections/PaymentSection.tsx` — List of payment methods +- `client/src/pages/info/ui/sections/ReturnsSection.tsx` — Returns & warranty Paper blocks + +### Modified + +- `client/src/pages/info/ui/InfoPage.tsx` — Rewrite as static container without useQuery +- `client/src/pages/admin-layout/ui/AdminLayoutPage.tsx` — Remove info page nav item and import +- `server/src/routes/api.js` — Remove import and registration call +- `server/prisma/schema.prisma` — Remove InfoPageBlock model + +### Deleted + +- `client/src/pages/admin-info/ui/AdminInfoPage.tsx` +- `client/src/pages/admin-info/index.ts` +- `client/src/entities/info/api/info-page-api.ts` +- `client/src/entities/info/model/types.ts` +- `client/src/entities/info/index.ts` +- `server/src/routes/api/info-page.js` + +--- + +### Task 1: Create HowToOrderSection + +**Files:** + +- Create: `client/src/pages/info/ui/sections/HowToOrderSection.tsx` + +- [ ] **Step 1: Write the component** + +```tsx +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 Typography from "@mui/material/Typography"; +import { + CheckCircle, + ClipboardList, + Mail, + ShoppingCart, + Truck, +} from "lucide-react"; + +const steps = [ + { + label: "Выберите товары", + icon: , + text: "Найдите нужные изделия в каталоге и добавьте их в корзину. Вы можете выбрать несколько товаров от разных мастеров — все они соберутся в одном заказе.", + }, + { + label: "Проверьте корзину", + icon: , + text: "Перейдите в корзину и проверьте состав заказа: названия товаров, количество и итоговую сумму. Здесь же можно изменить количество или удалить позиции.", + }, + { + label: "Укажите контакты и адрес", + icon: , + text: "Заполните имя, телефон и email для связи. Укажите адрес доставки — город, улицу, дом и квартиру. Эти данные нужны для расчёта стоимости и сроков.", + }, + { + label: "Выберите доставку и оплату", + icon: , + text: "Выберите способ доставки: самовывоз, курьер или почта/СДЭК. Затем укажите способ оплаты: картой онлайн или при получении.", + }, + { + label: "Подтвердите заказ", + icon: , + text: "Проверьте все данные ещё раз и нажмите «Оформить заказ». После этого мастер получит уведомление и начнёт подготовку вашего изделия.", + }, +]; + +export function HowToOrderSection() { + return ( + + + Как оформить заказ + + + {steps.map((step, idx) => ( + + step.icon}> + {step.label} + + + {step.text} + + + ))} + + + ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add client/src/pages/info/ui/sections/HowToOrderSection.tsx +git commit -m "feat: add HowToOrderSection with purchase step stepper" +``` + +--- + +### Task 2: Create DeliverySection + +**Files:** + +- Create: `client/src/pages/info/ui/sections/DeliverySection.tsx` + +- [ ] **Step 1: Write the component** + +```tsx +import Grid from "@mui/material/Grid"; +import Paper from "@mui/material/Paper"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { Package, Store, Truck } from "lucide-react"; +import { PICKUP_ADDRESS_FULL } from "@/shared/constants/pickup-point"; + +const deliveries = [ + { + title: "Самовывоз", + icon: , + lines: [ + "Бесплатно.", + PICKUP_ADDRESS_FULL, + "Перед визитом согласуем время — чтобы заказ точно был готов к выдаче.", + ], + }, + { + title: "Курьер по городу", + icon: , + lines: [ + "Доставка в пределах города.", + "Сроки и стоимость зависят от адреса и веса заказа.", + "Мастер свяжется с вами для уточнения деталей после оформления.", + ], + }, + { + title: "Почта / СДЭК", + icon: , + lines: [ + "Отправка в другие города.", + "Каждому заказу присваивается трек-номер для отслеживания.", + "Стоимость рассчитывается по тарифу перевозчика при оформлении.", + ], + }, +]; + +export function DeliverySection() { + return ( + + + Доставка + + + {deliveries.map((d) => ( + + + + + {d.icon} + {d.title} + + {d.lines.map((line, i) => ( + + {line} + + ))} + + + + ))} + + + ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add client/src/pages/info/ui/sections/DeliverySection.tsx +git commit -m "feat: add DeliverySection with pickup, courier, and postal cards" +``` + +--- + +### Task 3: Create PaymentSection + +**Files:** + +- Create: `client/src/pages/info/ui/sections/PaymentSection.tsx` + +- [ ] **Step 1: Write the component** + +```tsx +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 Typography from "@mui/material/Typography"; +import { Banknote, CreditCard } from "lucide-react"; + +const methods = [ + { + icon: , + primary: "Банковская карта онлайн", + secondary: + "Оплата картой Visa, Mastercard или МИР сразу при оформлении заказа.", + }, + { + icon: , + primary: "Оплата при получении", + secondary: "Оплата наличными или картой при получении заказа.", + }, +]; + +export function PaymentSection() { + return ( + + + Оплата + + + Оплата происходит после подтверждения заказа мастером. Вы получите + уведомление, когда заказ будет подтверждён и готов к оплате. + + + {methods.map((m) => ( + + {m.icon} + + + ))} + + + ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add client/src/pages/info/ui/sections/PaymentSection.tsx +git commit -m "feat: add PaymentSection with card and cash methods" +``` + +--- + +### Task 4: Create ReturnsSection + +**Files:** + +- Create: `client/src/pages/info/ui/sections/ReturnsSection.tsx` + +- [ ] **Step 1: Write the component** + +```tsx +import Paper from "@mui/material/Paper"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; + +export function ReturnsSection() { + return ( + + + Возврат и гарантии + + + + + Возврат + + + Если товар не соответствует описанию или имеет производственный + дефект, свяжитесь с нами в течение 7 дней после получения. Мы + заменим изделие на аналогичное или вернём деньги. Возврат товара + надлежащего качества возможен в течение 14 дней, если изделие не + было в употреблении и сохранён его товарный вид. + + + + + Гарантия качества + + + Мы отвечаем за качество каждого изделия ручной работы. Все дефекты, + возникшие не по вине покупателя, устраняются или компенсируются + заменой изделия. Если у вас возникли вопросы по качеству — напишите + нам, и мы решим проблему в кратчайшие сроки. + + + + + ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add client/src/pages/info/ui/sections/ReturnsSection.tsx +git commit -m "feat: add ReturnsSection with return and warranty blocks" +``` + +--- + +### Task 5: Rewrite InfoPage as static container + +**Files:** + +- Modify: `client/src/pages/info/ui/InfoPage.tsx` + +- [ ] **Step 1: Rewrite InfoPage.tsx** + +```tsx +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { DeliverySection } from "./sections/DeliverySection"; +import { HowToOrderSection } from "./sections/HowToOrderSection"; +import { PaymentSection } from "./sections/PaymentSection"; +import { ReturnsSection } from "./sections/ReturnsSection"; + +export function InfoPage() { + return ( + + + Информация для покупателей + + + Как оформить заказ, как проходит доставка, оплата и другие важные + детали. + + + + + + + + + + ); +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add client/src/pages/info/ui/InfoPage.tsx +git commit -m "feat: rewrite InfoPage as static container with section components" +``` + +--- + +### Task 6: Delete admin info page + +**Files:** + +- Delete: `client/src/pages/admin-info/ui/AdminInfoPage.tsx` +- Delete: `client/src/pages/admin-info/index.ts` + +- [ ] **Step 1: Delete files** + +```bash +rm client/src/pages/admin-info/ui/AdminInfoPage.tsx +rm client/src/pages/admin-info/index.ts +``` + +- [ ] **Step 2: Commit** + +```bash +git add client/src/pages/admin-info/ +git commit -m "feat: remove admin info page CRUD" +``` + +--- + +### Task 7: Delete entities/info + +**Files:** + +- Delete: `client/src/entities/info/api/info-page-api.ts` +- Delete: `client/src/entities/info/model/types.ts` +- Delete: `client/src/entities/info/index.ts` + +- [ ] **Step 1: Delete files** + +```bash +rm client/src/entities/info/api/info-page-api.ts +rm client/src/entities/info/model/types.ts +rm client/src/entities/info/index.ts +``` + +- [ ] **Step 2: Commit** + +```bash +git add client/src/entities/info/ +git commit -m "feat: remove info entity (admin CRUD layer)" +``` + +--- + +### Task 8: Clean up AdminLayoutPage + +**Files:** + +- Modify: `client/src/pages/admin-layout/ui/AdminLayoutPage.tsx` + +- [ ] **Step 1: Remove import** + +```tsx +// REMOVE line 23: +import { AdminInfoPage } from "@/pages/admin-info"; +``` + +Execute this edit: In `client/src/pages/admin-layout/ui/AdminLayoutPage.tsx`, remove the import line: + +```tsx +import { AdminInfoPage } from "@/pages/admin-info"; +``` + +- [ ] **Step 2: Remove FileText from lucide-react import** + +In line 18, change: + +```tsx +import { + Bell, + FileText, + Image, + LayoutGrid, + ListOrdered, + MessageSquare, + Store, + Users, +} from "lucide-react"; +``` + +to: + +```tsx +import { + Bell, + Image, + LayoutGrid, + ListOrdered, + MessageSquare, + Store, + Users, +} from "lucide-react"; +``` + +- [ ] **Step 3: Remove nav item** + +Remove the nav item entry (line 64): + +```tsx +{ to: '/admin/info', label: 'Инфо-страница', icon: }, +``` + +- [ ] **Step 4: Remove route** + +Remove the route line 192: + +```tsx +} /> +``` + +- [ ] **Step 5: Commit** + +```bash +git add client/src/pages/admin-layout/ui/AdminLayoutPage.tsx +git commit -m "feat: remove info page from admin navigation and routes" +``` + +--- + +### Task 9: Delete server info-page routes + +**Files:** + +- Delete: `server/src/routes/api/info-page.js` +- Modify: `server/src/routes/api.js` + +- [ ] **Step 1: Delete the routes file** + +```bash +rm server/src/routes/api/info-page.js +``` + +- [ ] **Step 2: Clean up api.js** + +In `server/src/routes/api.js`: + +Remove the import line 10: + +```js +import { registerInfoPageRoutes } from "./api/info-page.js"; +``` + +Remove the call line 21: + +```js +await registerInfoPageRoutes(fastify); +``` + +- [ ] **Step 3: Commit** + +```bash +git add server/src/routes/api/info-page.js server/src/routes/api.js +git commit -m "feat: remove server info-page routes" +``` + +--- + +### Task 10: Remove InfoPageBlock model from Prisma schema + +**Files:** + +- Modify: `server/prisma/schema.prisma` + +- [ ] **Step 1: Remove model from schema** + +Remove lines 262-273 from `server/prisma/schema.prisma`: + +```prisma +model InfoPageBlock { + id String @id @default(cuid()) + key String @unique + title String + body String + sort Int @default(0) + published Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([published, sort]) +} +``` + +Also remove the blank line before it (line 261). + +- [ ] **Step 2: Run migration** + +```bash +cd server && npm run db:migrate +``` + +Expected: Prisma creates a new migration dropping the `InfoPageBlock` table. + +- [ ] **Step 3: Commit** + +```bash +git add server/prisma/schema.prisma server/prisma/migrations/ +git commit -m "feat: remove InfoPageBlock model from Prisma schema" +``` + +--- + +### Task 11: Verify build and lint + +- [ ] **Step 1: Run server tests** + +```bash +cd server && npm test +``` + +Expected: all tests pass (no info-page tests exist, but other tests should still pass after removing routes). + +- [ ] **Step 2: Run client lint** + +```bash +cd client && npm run lint +``` + +Expected: no errors. + +- [ ] **Step 3: Run client format check** + +```bash +cd client && npm run format:check +``` + +Expected: all files formatted. + +- [ ] **Step 4: Run client tests** + +```bash +cd client && npm test +``` + +Expected: all tests pass. + +- [ ] **Step 5: Build client** + +```bash +cd client && npm run build +``` + +Expected: tsc + Vite build succeed with no errors. + +- [ ] **Step 6: Commit if any fixes** + +```bash +git add -A +git commit -m "chore: lint and build fixes after info page migration" +``` diff --git a/docs/superpowers/specs/2026-05-19-info-page-static-design.md b/docs/superpowers/specs/2026-05-19-info-page-static-design.md new file mode 100644 index 0000000..97bcd3f --- /dev/null +++ b/docs/superpowers/specs/2026-05-19-info-page-static-design.md @@ -0,0 +1,102 @@ +# 2026-05-19 — Статическая страница «О покупке» (удаление админ-CRUD) + +## Цель + +Убрать ручное наполнение информационной страницы админом через `InfoPageBlock` CRUD. +Заменить на статическую страницу с хардкод-контентом в React-компонентах: схемы процессов, +пошаговые инструкции, карточки доставки, список оплат, условия возврата. + +## Что удаляется + +| Файл/директория | Действие | +|---|---| +| `client/src/pages/admin-info/` | Удалить целиком (AdminInfoPage + index.ts) | +| `client/src/entities/info/` | Удалить целиком (api, model, index.ts) | +| `server/src/routes/api/info-page.js` | Удалить целиком | +| `server/src/routes/api.js` | Убрать `import` + вызов `registerInfoPageRoutes` | +| `server/prisma/schema.prisma` | Удалить модель `InfoPageBlock` и связанный индекс | +| `client/src/pages/admin-layout/ui/AdminLayoutPage.tsx` | Убрать пункт меню «Инфо-страница», импорт и роут | +| Миграция `20260503144425_...` | Не трогать, Prisma обработает через `db:migrate` | + +После удаления модели Prisma нужно выполнить `npm run db:migrate` в `server/`. + +## Что остаётся + +- Публичный роут `GET /api/info-page/blocks` удаляется — страница больше не ходит на сервер +- Роут `/info` в `client/src/app/routes/index.tsx` остаётся как есть +- Ссылка «О покупке» в футере `MainLayout.tsx` остаётся как есть + +## Новая структура страницы + +``` +client/src/pages/info/ + ui/ + InfoPage.tsx -- контейнер: заголовок + секции + sections/ + HowToOrderSection.tsx -- Stepper: 5 шагов покупки + DeliverySection.tsx -- Grid-карточки: самовывоз, курьер, почта + PaymentSection.tsx -- List со способами оплаты + ReturnsSection.tsx -- Paper-блоки: возврат, гарантия + index.ts -- export { InfoPage } +``` + +## Дизайн секций + +### InfoPage (контейнер) + +- Typography `variant="h4"`: «Информация для покупателей» +- Typography `color="text.secondary"` с текущим подзаголовком +- Секции рендерятся последовательно с `Stack spacing={4}` + +### HowToOrderSection + +- MUI `Stepper` вертикальный, `activeStep=-1` (все шаги видны, не активные) +- 5 шагов с `StepLabel`, каждый содержит: + - Заголовок шага + - Пояснительный текст + - Иконку через `StepIconComponent` или проп icon в `Step` + +Шаги: +1. «Выберите товары» (ShoppingCart) — Найдите нужное в каталоге, добавьте в корзину +2. «Проверьте корзину» (ClipboardList) — Проверьте состав заказа, количество и итоговую сумму +3. «Укажите контакты и адрес» (Mail) — Заполните имя, телефон, email и адрес доставки +4. «Выберите доставку и оплату» (Truck) — Выберите удобный способ получения и оплаты +5. «Подтвердите заказ» (CheckCircle) — Проверьте всё ещё раз и нажмите «Оформить заказ» + +### DeliverySection + +- Три карточки в `Grid container spacing={2}` с `Paper variant="outlined"`: + - **Самовывоз** (Store) — Бесплатно. Адрес. Перед визитом согласуем время. + - **Курьер по городу** (Truck) — Доставка в пределах города. Сроки и стоимость уточняются. + - **Почта / СДЭК** (Package) — Отправка с трек-номером. Стоимость по тарифу перевозчика. + +### PaymentSection + +- MUI `List` с `ListItem` элементами, каждый с `ListItemIcon`: + - Банковская карта онлайн (CreditCard) + - Оплата при получении (Banknote) +- Текст: «Оплата происходит после подтверждения заказа мастером.» + +### ReturnsSection + +- Два `Paper variant="outlined"` блока: + - «Возврат» — Если товар не соответствует описанию или есть дефект, свяжитесь с нами. Мы заменим изделие или вернём деньги. + - «Гарантия» — Мы отвечаем за качество каждого изделия. Все дефекты, возникшие не по вине покупателя, устраняем или меняем изделие. + +## Константы + +Использовать существующие: +- `PICKUP_ADDRESS_FULL`, `PICKUP_COORDINATES` из `@/shared/constants/pickup-point` +- `STORE_EMAIL` из `@/shared/constants/store` +- Для способов оплаты — текст захардкожен, т.к. payment-method из shared/constants содержит только ключи + +## Иконки + +Все иконки из `lucide-react`: ShoppingCart, ClipboardList, Mail, Truck, CheckCircle, Store, Package, CreditCard, Banknote. + +## Что НЕ входит в scope + +- Сохранение обратной совместимости с текущей динамической страницей +- Миграция существующих данных InfoPageBlock (они просто удаляются) +- Автообновление контента админом (страница статическая) +- Телефоны поддержки (если понадобятся — отдельной задачей) diff --git a/server/prisma/prisma/dev.db b/server/prisma/prisma/dev.db new file mode 100644 index 0000000..e72aaa9 Binary files /dev/null and b/server/prisma/prisma/dev.db differ