From cb4661dc136143d6a88c3e948c6f8351d3e37e00 Mon Sep 17 00:00:00 2001 From: Kirill Date: Tue, 19 May 2026 15:32:45 +0500 Subject: [PATCH] test commit --- .../info/ui/sections/DeliverySection.tsx | 13 +- .../pages/info/ui/sections/PaymentSection.tsx | 2 +- .../plans/2026-05-19-info-page-static.md | 636 ++++++++++++++++++ .../2026-05-19-info-page-static-design.md | 102 +++ server/prisma/prisma/dev.db | Bin 0 -> 311296 bytes 5 files changed, 741 insertions(+), 12 deletions(-) create mode 100644 docs/superpowers/plans/2026-05-19-info-page-static.md create mode 100644 docs/superpowers/specs/2026-05-19-info-page-static-design.md create mode 100644 server/prisma/prisma/dev.db 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 0000000000000000000000000000000000000000..e72aaa953026346fe3580096297f2556a80b59ec GIT binary patch literal 311296 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 literal 0 HcmV?d00001