From 17a93dde6bda455bcbbe0f7866ae7ca5fda5257c Mon Sep 17 00:00:00 2001 From: mpak Date: Thu, 11 Jun 2026 14:26:02 +0500 Subject: [PATCH] fix: inline @shared constants for standalone build --- .../ui/AdminTestChecklistPage.tsx | 2 +- .../src/shared/constants/delivery-carrier.ts | 25 ++++-- client/src/shared/constants/order.ts | 43 ++++++++-- .../shared/constants/test-checklist-items.ts | 83 +++++++++++++++++++ client/src/shared/constants/upload-limits.ts | 6 +- client/tsconfig.app.json | 3 +- client/vite.config.ts | 4 +- 7 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 client/src/shared/constants/test-checklist-items.ts diff --git a/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx b/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx index 0ea74f7..647ee8c 100755 --- a/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx +++ b/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx @@ -24,7 +24,7 @@ import TableRow from '@mui/material/TableRow' import TextField from '@mui/material/TextField' import Tooltip from '@mui/material/Tooltip' import Typography from '@mui/material/Typography' -import { TEST_CHECKLIST_ITEMS } from '@shared/constants/test-checklist-items' +import { TEST_CHECKLIST_ITEMS } from '@/shared/constants/test-checklist-items' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { fetchTestChecklistResults, diff --git a/client/src/shared/constants/delivery-carrier.ts b/client/src/shared/constants/delivery-carrier.ts index 8641b6d..c52db9a 100755 --- a/client/src/shared/constants/delivery-carrier.ts +++ b/client/src/shared/constants/delivery-carrier.ts @@ -1,13 +1,21 @@ -import { - DELIVERY_CARRIERS as SHARED_DELIVERY_CARRIERS, - DELIVERY_CARRIER_LABELS, - deliveryCarrierLabelRu as sharedDeliveryCarrierLabelRu, -} from '@shared/constants/delivery-carrier' - -export const DELIVERY_CARRIER_CODES = SHARED_DELIVERY_CARRIERS as typeof SHARED_DELIVERY_CARRIERS +export const DELIVERY_CARRIER_CODES = Object.freeze([ + 'RUSSIAN_POST', + 'OZON_PVZ', + 'YANDEX_PVZ', + 'FIVE_POST', + 'WB_PVZ', +] as const) export type DeliveryCarrierCode = (typeof DELIVERY_CARRIER_CODES)[number] +export const DELIVERY_CARRIER_LABELS: Record = { + RUSSIAN_POST: 'Почта России', + OZON_PVZ: 'Озон доставка (пункт выдачи)', + YANDEX_PVZ: 'Яндекс доставка (пункт выдачи)', + FIVE_POST: '5Post (пункт выдачи)', + WB_PVZ: 'WB доставка (пункт выдачи)', +} + export const DELIVERY_CARRIER_OPTIONS: ReadonlyArray<{ code: DeliveryCarrierCode; label: string }> = DELIVERY_CARRIER_CODES.map((code) => ({ code, @@ -15,5 +23,6 @@ export const DELIVERY_CARRIER_OPTIONS: ReadonlyArray<{ code: DeliveryCarrierCode })) export function deliveryCarrierLabelRu(code: string | null | undefined): string | null { - return sharedDeliveryCarrierLabelRu(code) + if (!code) return null + return (DELIVERY_CARRIER_LABELS as Record)[code] ?? code } diff --git a/client/src/shared/constants/order.ts b/client/src/shared/constants/order.ts index 93c5bc8..e2ee641 100755 --- a/client/src/shared/constants/order.ts +++ b/client/src/shared/constants/order.ts @@ -1,17 +1,46 @@ -import { - ORDER_STATUSES as SHARED_ORDER_STATUSES, - getNextAdminStatuses as sharedGetNextAdminStatuses, -} from '@shared/constants/order-status' - -export const ORDER_STATUSES = SHARED_ORDER_STATUSES as typeof SHARED_ORDER_STATUSES +export const ORDER_STATUSES = Object.freeze([ + 'DRAFT', + 'PENDING_PAYMENT', + 'PAID', + 'IN_PROGRESS', + 'SHIPPED', + 'READY_FOR_PICKUP', + 'DONE', + 'CANCELLED', +] as const) export type OrderStatus = (typeof ORDER_STATUSES)[number] +const ADMIN_ORDER_TRANSITIONS: Record = Object.freeze({ + DRAFT: ['PENDING_PAYMENT', 'CANCELLED'], + PENDING_PAYMENT: ['PAID', 'CANCELLED'], + PAID: ['IN_PROGRESS', 'CANCELLED'], + IN_PROGRESS: Object.freeze({ + delivery: ['SHIPPED', 'CANCELLED'], + pickup: ['READY_FOR_PICKUP', 'CANCELLED'], + }), +}) + +function getNextAdminStatuses(from: string, deliveryType: string): string[] { + const transition = ADMIN_ORDER_TRANSITIONS[from] + if (!transition) return [] + if (Array.isArray(transition)) return [...transition] + return transition[deliveryType] ? [...transition[deliveryType]] : [] +} + export function getAdminNextOrderStatuses(status: string, deliveryType: 'delivery' | 'pickup'): OrderStatus[] { - return sharedGetNextAdminStatuses(status, deliveryType) as OrderStatus[] + return getNextAdminStatuses(status, deliveryType) as OrderStatus[] } export function canTransitionOrderStatus(from: string, to: string, deliveryType: 'delivery' | 'pickup'): boolean { if (from === to) return true return getAdminNextOrderStatuses(from, deliveryType).includes(to as OrderStatus) } + +export function canTransitionAdminOrderStatus( + order: { status: string; deliveryType: string }, + next: string, +): boolean { + if (order.status === next) return true + return getNextAdminStatuses(order.status, order.deliveryType).includes(next) +} diff --git a/client/src/shared/constants/test-checklist-items.ts b/client/src/shared/constants/test-checklist-items.ts new file mode 100644 index 0000000..05adb0b --- /dev/null +++ b/client/src/shared/constants/test-checklist-items.ts @@ -0,0 +1,83 @@ +export interface TestChecklistItem { + key: string + section: string + action: string + expectedResult: string +} + +export const TEST_CHECKLIST_ITEMS: readonly TestChecklistItem[] = Object.freeze([ + // Авторизация + { key: 'auth.register-email', section: 'Авторизация', action: 'Зарегистрироваться по email', expectedResult: 'Код приходит на почту, аккаунт создаётся' }, + { key: 'auth.login-password', section: 'Авторизация', action: 'Войти по паролю', expectedResult: 'Корректный пароль пускает, неправильный — ошибка' }, + { key: 'auth.oauth-vk', section: 'Авторизация', action: 'Войти через OAuth VK', expectedResult: 'Редирект на VK, callback, авторизация успешна' }, + { key: 'auth.oauth-yandex', section: 'Авторизация', action: 'Войти через OAuth Yandex', expectedResult: 'Редирект на Yandex, callback, авторизация успешна' }, + { key: 'auth.reset-password', section: 'Авторизация', action: 'Сбросить пароль', expectedResult: 'Письмо приходит, ссылка работает, пароль меняется' }, + { key: 'auth.logout', section: 'Авторизация', action: 'Выйти из аккаунта', expectedResult: 'Сессия очищается, редирект на страницу входа' }, + + // Каталог и товары + { key: 'catalog.homepage', section: 'Каталог и товары', action: 'Открыть главную страницу', expectedResult: 'Слайдер грузится, товары отображаются' }, + { key: 'catalog.filters', section: 'Каталог и товары', action: 'Применить фильтры', expectedResult: 'Фильтры по категории, цене, материалам работают' }, + { key: 'catalog.product-page', section: 'Каталог и товары', action: 'Открыть страницу товара', expectedResult: 'Фото, описание, цена, кнопка "В корзину" отображаются' }, + { key: 'catalog.seo', section: 'Каталог и товары', action: 'Проверить SEO-метаданные', expectedResult: 'Title, meta, slug корректные' }, + + // Корзина + { key: 'cart.add', section: 'Корзина', action: 'Добавить товар в корзину', expectedResult: 'Счётчик корзины обновляется' }, + { key: 'cart.change-qty', section: 'Корзина', action: 'Изменить количество товара', expectedResult: 'Сумма пересчитывается' }, + { key: 'cart.remove', section: 'Корзина', action: 'Удалить товар из корзины', expectedResult: 'Товар убирается, сумма пересчитывается' }, + + // Чекаут + { key: 'checkout.address', section: 'Чекаут', action: 'Выбрать адрес доставки', expectedResult: 'Можно выбрать из сохранённых или добавить новый' }, + { key: 'checkout.delivery', section: 'Чекаут', action: 'Выбрать способ доставки', expectedResult: 'Почта, OZON, Яндекс, 5post — доступны' }, + { key: 'checkout.payment', section: 'Чекаут', action: 'Выбрать способ оплаты', expectedResult: 'Онлайн / при получении — доступны' }, + { key: 'checkout.comment', section: 'Чекаут', action: 'Добавить комментарий к заказу', expectedResult: 'Поле работает, текст сохраняется' }, + { key: 'checkout.create', section: 'Чекаут', action: 'Создать заказ', expectedResult: 'Заказ создаётся, статус DRAFT' }, + + // Оплата + { key: 'payment.yookassa', section: 'Оплата', action: 'Оплатить через ЮKassa', expectedResult: 'Редирект на оплату, webhook обрабатывается' }, + { key: 'payment.status', section: 'Оплата', action: 'Проверить статус платежа', expectedResult: 'Статус обновляется после webhook' }, + + // Профиль пользователя + { key: 'profile.avatar', section: 'Профиль пользователя', action: 'Управление аватаром', expectedResult: 'Загрузка, отображение, удаление работают' }, + { key: 'profile.settings', section: 'Профиль пользователя', action: 'Изменить настройки профиля', expectedResult: 'Email, имя, способы входа обновляются' }, + { key: 'profile.addresses', section: 'Профиль пользователя', action: 'Управление адресами', expectedResult: 'Добавление, редактирование, удаление, по умолчанию' }, + { key: 'profile.orders', section: 'Профиль пользователя', action: 'Просмотр заказов', expectedResult: 'Список, детали, статусы отображаются' }, + { key: 'profile.messages', section: 'Профиль пользователя', action: 'Сообщения по заказу', expectedResult: 'Отправка, получение, read state работают' }, + { key: 'profile.notifications', section: 'Профиль пользователя', action: 'Настройки уведомлений', expectedResult: 'Вкл/выкл каналов работают' }, + { key: 'profile.delete-account', section: 'Профиль пользователя', action: 'Удалить аккаунт', expectedResult: 'Данные удаляются' }, + + // Админ — Товары + { key: 'admin-products.list', section: 'Админ — Товары', action: 'Открыть список товаров', expectedResult: 'Пагинация, поиск работают' }, + { key: 'admin-products.create', section: 'Админ — Товары', action: 'Создать товар', expectedResult: 'Все поля сохраняются, фото загружаются, публикация работает' }, + { key: 'admin-products.edit', section: 'Админ — Товары', action: 'Редактировать товар', expectedResult: 'Изменения сохраняются' }, + { key: 'admin-products.delete', section: 'Админ — Товары', action: 'Удалить товар', expectedResult: 'Товар удаляется' }, + { key: 'admin-products.images', section: 'Админ — Товары', action: 'Управление изображениями товара', expectedResult: 'Добавление, сортировка, удаление работают' }, + + // Админ — Категории + { key: 'admin-categories.crud', section: 'Админ — Категории', action: 'CRUD категорий', expectedResult: 'Создание, редактирование, удаление, сортировка работают' }, + + // Админ — Заказы + { key: 'admin-orders.list', section: 'Админ — Заказы', action: 'Открыть список заказов', expectedResult: 'Фильтрация по статусу, внимание отображается' }, + { key: 'admin-orders.details', section: 'Админ — Заказы', action: 'Открыть детали заказа', expectedResult: 'Состав, статус, смена статуса работают' }, + { key: 'admin-orders.messages', section: 'Админ — Заказы', action: 'Ответить на сообщение заказа', expectedResult: 'Сообщение отправляется пользователю' }, + + // Админ — Отзывы + { key: 'admin-reviews.list', section: 'Админ — Отзывы', action: 'Открыть список отзывов', expectedResult: 'Фильтрация pending/approved/rejected работает' }, + { key: 'admin-reviews.moderate', section: 'Админ — Отзывы', action: 'Модерировать отзыв', expectedResult: 'Approve/reject работают' }, + + // Админ — Пользователи + { key: 'admin-users.list', section: 'Админ — Пользователи', action: 'Открыть список пользователей', expectedResult: 'Email, дата регистрации отображаются' }, + { key: 'admin-users.orders', section: 'Админ — Пользователи', action: 'Просмотр заказов пользователя', expectedResult: 'Заказы пользователя отображаются' }, + + // Админ — Галерея + { key: 'admin-gallery.upload', section: 'Админ — Галерея', action: 'Управление галереей', expectedResult: 'Загрузка, удаление, использование в слайдере работают' }, + + // Админ — Настройки + { key: 'admin-settings.notifications', section: 'Админ — Настройки', action: 'Настройки уведомлений админа', expectedResult: 'Email, telegram настраиваются' }, + + // Инфо-страницы + { key: 'info.pages', section: 'Инфо-страницы', action: 'Открыть инфо-страницы', expectedResult: 'Доставка, оплата, как заказать, статусы заказов отображаются' }, + { key: 'info.legal', section: 'Инфо-страницы', action: 'Открыть юридические страницы', expectedResult: 'Политика конфиденциальности, условия использования отображаются' }, + + // SSE / Realtime + { key: 'sse.notifications', section: 'SSE / Realtime', action: 'Проверить SSE-уведомления', expectedResult: 'Уведомления приходят в реальном времени' }, +]) as unknown as TestChecklistItem[] diff --git a/client/src/shared/constants/upload-limits.ts b/client/src/shared/constants/upload-limits.ts index fa3076e..8dc4813 100755 --- a/client/src/shared/constants/upload-limits.ts +++ b/client/src/shared/constants/upload-limits.ts @@ -1,8 +1,8 @@ -import { ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT } from '@shared/constants/upload-limits' +const MB = 1024 * 1024 -export const ADMIN_UPLOAD_IMAGE_MAX_BYTES = ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT +export const ADMIN_UPLOAD_IMAGE_MAX_BYTES = 20 * MB -export const OTHER_UPLOAD_MAX_FILE_BYTES = 2 * 1024 * 1024 // 2 MB +export const OTHER_UPLOAD_MAX_FILE_BYTES = 2 * MB export function formatAdminImageMaxSizeHint(): string { return `${Math.round(ADMIN_UPLOAD_IMAGE_MAX_BYTES / (1024 * 1024))} МБ` diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json index b1a4739..25f849e 100755 --- a/client/tsconfig.app.json +++ b/client/tsconfig.app.json @@ -2,8 +2,7 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@/*": ["src/*"], - "@shared/*": ["../../shop-server/shared/*"] + "@/*": ["src/*"] }, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "es2023", diff --git a/client/vite.config.ts b/client/vite.config.ts index cb6a6fc..cc0e4ef 100755 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -4,7 +4,6 @@ import react from '@vitejs/plugin-react' import { defineConfig } from 'vite' const rootDir = fileURLToPath(new URL('.', import.meta.url)) -const projectRoot = path.resolve(rootDir, '..', '..', 'shop-server') // https://vite.dev/config/ export default defineConfig({ @@ -12,12 +11,11 @@ export default defineConfig({ resolve: { alias: { '@': path.resolve(rootDir, 'src'), - '@shared': path.resolve(projectRoot, 'shared'), }, }, server: { fs: { - allow: [path.resolve(rootDir, '..'), projectRoot], + allow: [path.resolve(rootDir, '..')], }, port: 5173, proxy: {