fix: inline @shared constants for standalone build

This commit is contained in:
mpak
2026-06-11 14:26:02 +05:00
parent a36f96c290
commit 17a93dde6b
7 changed files with 142 additions and 24 deletions
@@ -24,7 +24,7 @@ import TableRow from '@mui/material/TableRow'
import TextField from '@mui/material/TextField' import TextField from '@mui/material/TextField'
import Tooltip from '@mui/material/Tooltip' import Tooltip from '@mui/material/Tooltip'
import Typography from '@mui/material/Typography' 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 { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { import {
fetchTestChecklistResults, fetchTestChecklistResults,
@@ -1,13 +1,21 @@
import { export const DELIVERY_CARRIER_CODES = Object.freeze([
DELIVERY_CARRIERS as SHARED_DELIVERY_CARRIERS, 'RUSSIAN_POST',
DELIVERY_CARRIER_LABELS, 'OZON_PVZ',
deliveryCarrierLabelRu as sharedDeliveryCarrierLabelRu, 'YANDEX_PVZ',
} from '@shared/constants/delivery-carrier' 'FIVE_POST',
'WB_PVZ',
export const DELIVERY_CARRIER_CODES = SHARED_DELIVERY_CARRIERS as typeof SHARED_DELIVERY_CARRIERS ] as const)
export type DeliveryCarrierCode = (typeof DELIVERY_CARRIER_CODES)[number] export type DeliveryCarrierCode = (typeof DELIVERY_CARRIER_CODES)[number]
export const DELIVERY_CARRIER_LABELS: Record<DeliveryCarrierCode, string> = {
RUSSIAN_POST: 'Почта России',
OZON_PVZ: 'Озон доставка (пункт выдачи)',
YANDEX_PVZ: 'Яндекс доставка (пункт выдачи)',
FIVE_POST: '5Post (пункт выдачи)',
WB_PVZ: 'WB доставка (пункт выдачи)',
}
export const DELIVERY_CARRIER_OPTIONS: ReadonlyArray<{ code: DeliveryCarrierCode; label: string }> = export const DELIVERY_CARRIER_OPTIONS: ReadonlyArray<{ code: DeliveryCarrierCode; label: string }> =
DELIVERY_CARRIER_CODES.map((code) => ({ DELIVERY_CARRIER_CODES.map((code) => ({
code, code,
@@ -15,5 +23,6 @@ export const DELIVERY_CARRIER_OPTIONS: ReadonlyArray<{ code: DeliveryCarrierCode
})) }))
export function deliveryCarrierLabelRu(code: string | null | undefined): string | null { export function deliveryCarrierLabelRu(code: string | null | undefined): string | null {
return sharedDeliveryCarrierLabelRu(code) if (!code) return null
return (DELIVERY_CARRIER_LABELS as Record<string, string>)[code] ?? code
} }
+36 -7
View File
@@ -1,17 +1,46 @@
import { export const ORDER_STATUSES = Object.freeze([
ORDER_STATUSES as SHARED_ORDER_STATUSES, 'DRAFT',
getNextAdminStatuses as sharedGetNextAdminStatuses, 'PENDING_PAYMENT',
} from '@shared/constants/order-status' 'PAID',
'IN_PROGRESS',
export const ORDER_STATUSES = SHARED_ORDER_STATUSES as typeof SHARED_ORDER_STATUSES 'SHIPPED',
'READY_FOR_PICKUP',
'DONE',
'CANCELLED',
] as const)
export type OrderStatus = (typeof ORDER_STATUSES)[number] export type OrderStatus = (typeof ORDER_STATUSES)[number]
const ADMIN_ORDER_TRANSITIONS: Record<string, readonly string[] | { readonly delivery: readonly string[]; readonly pickup: readonly string[] }> = 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[] { 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 { export function canTransitionOrderStatus(from: string, to: string, deliveryType: 'delivery' | 'pickup'): boolean {
if (from === to) return true if (from === to) return true
return getAdminNextOrderStatuses(from, deliveryType).includes(to as OrderStatus) 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)
}
@@ -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[]
+3 -3
View File
@@ -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 { export function formatAdminImageMaxSizeHint(): string {
return `${Math.round(ADMIN_UPLOAD_IMAGE_MAX_BYTES / (1024 * 1024))} МБ` return `${Math.round(ADMIN_UPLOAD_IMAGE_MAX_BYTES / (1024 * 1024))} МБ`
+1 -2
View File
@@ -2,8 +2,7 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["src/*"], "@/*": ["src/*"]
"@shared/*": ["../../shop-server/shared/*"]
}, },
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "es2023", "target": "es2023",
+1 -3
View File
@@ -4,7 +4,6 @@ import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
const rootDir = fileURLToPath(new URL('.', import.meta.url)) const rootDir = fileURLToPath(new URL('.', import.meta.url))
const projectRoot = path.resolve(rootDir, '..', '..', 'shop-server')
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
@@ -12,12 +11,11 @@ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
'@': path.resolve(rootDir, 'src'), '@': path.resolve(rootDir, 'src'),
'@shared': path.resolve(projectRoot, 'shared'),
}, },
}, },
server: { server: {
fs: { fs: {
allow: [path.resolve(rootDir, '..'), projectRoot], allow: [path.resolve(rootDir, '..')],
}, },
port: 5173, port: 5173,
proxy: { proxy: {