From 30c6969a9afe10f524a0f29e30823cd37edfc779 Mon Sep 17 00:00:00 2001 From: mpak Date: Thu, 11 Jun 2026 14:35:28 +0500 Subject: [PATCH] fix: duplicate shared/constants/ into client repo --- .../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 +- shared/constants/delivery-carrier.d.ts | 11 + shared/constants/delivery-carrier.js | 14 + shared/constants/notification-events.d.ts | 36 +++ shared/constants/notification-events.js | 26 ++ shared/constants/order-status.d.ts | 17 + shared/constants/order-status.js | 38 +++ shared/constants/payment-method.d.ts | 1 + shared/constants/payment-method.js | 1 + shared/constants/test-checklist-items.d.ts | 8 + shared/constants/test-checklist-items.js | 304 ++++++++++++++++++ shared/constants/upload-limits.d.ts | 5 + shared/constants/upload-limits.js | 9 + 19 files changed, 494 insertions(+), 142 deletions(-) delete mode 100644 client/src/shared/constants/test-checklist-items.ts create mode 100755 shared/constants/delivery-carrier.d.ts create mode 100755 shared/constants/delivery-carrier.js create mode 100755 shared/constants/notification-events.d.ts create mode 100755 shared/constants/notification-events.js create mode 100755 shared/constants/order-status.d.ts create mode 100755 shared/constants/order-status.js create mode 100755 shared/constants/payment-method.d.ts create mode 100755 shared/constants/payment-method.js create mode 100755 shared/constants/test-checklist-items.d.ts create mode 100755 shared/constants/test-checklist-items.js create mode 100755 shared/constants/upload-limits.d.ts create mode 100755 shared/constants/upload-limits.js diff --git a/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx b/client/src/pages/admin-test-checklist/ui/AdminTestChecklistPage.tsx index 647ee8c..0ea74f7 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 c52db9a..8641b6d 100755 --- a/client/src/shared/constants/delivery-carrier.ts +++ b/client/src/shared/constants/delivery-carrier.ts @@ -1,21 +1,13 @@ -export const DELIVERY_CARRIER_CODES = Object.freeze([ - 'RUSSIAN_POST', - 'OZON_PVZ', - 'YANDEX_PVZ', - 'FIVE_POST', - 'WB_PVZ', -] as const) +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 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, @@ -23,6 +15,5 @@ export const DELIVERY_CARRIER_OPTIONS: ReadonlyArray<{ code: DeliveryCarrierCode })) export function deliveryCarrierLabelRu(code: string | null | undefined): string | null { - if (!code) return null - return (DELIVERY_CARRIER_LABELS as Record)[code] ?? code + return sharedDeliveryCarrierLabelRu(code) } diff --git a/client/src/shared/constants/order.ts b/client/src/shared/constants/order.ts index e2ee641..93c5bc8 100755 --- a/client/src/shared/constants/order.ts +++ b/client/src/shared/constants/order.ts @@ -1,46 +1,17 @@ -export const ORDER_STATUSES = Object.freeze([ - 'DRAFT', - 'PENDING_PAYMENT', - 'PAID', - 'IN_PROGRESS', - 'SHIPPED', - 'READY_FOR_PICKUP', - 'DONE', - 'CANCELLED', -] as const) +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 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 getNextAdminStatuses(status, deliveryType) as OrderStatus[] + return sharedGetNextAdminStatuses(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 deleted file mode 100644 index 05adb0b..0000000 --- a/client/src/shared/constants/test-checklist-items.ts +++ /dev/null @@ -1,83 +0,0 @@ -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 8dc4813..fa3076e 100755 --- a/client/src/shared/constants/upload-limits.ts +++ b/client/src/shared/constants/upload-limits.ts @@ -1,8 +1,8 @@ -const MB = 1024 * 1024 +import { ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT } from '@shared/constants/upload-limits' -export const ADMIN_UPLOAD_IMAGE_MAX_BYTES = 20 * MB +export const ADMIN_UPLOAD_IMAGE_MAX_BYTES = ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT -export const OTHER_UPLOAD_MAX_FILE_BYTES = 2 * MB +export const OTHER_UPLOAD_MAX_FILE_BYTES = 2 * 1024 * 1024 // 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 25f849e..b23d30e 100755 --- a/client/tsconfig.app.json +++ b/client/tsconfig.app.json @@ -2,7 +2,8 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@/*": ["src/*"] + "@/*": ["src/*"], + "@shared/*": ["../shared/*"] }, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "es2023", diff --git a/client/vite.config.ts b/client/vite.config.ts index cc0e4ef..21180e3 100755 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -4,6 +4,7 @@ import react from '@vitejs/plugin-react' import { defineConfig } from 'vite' const rootDir = fileURLToPath(new URL('.', import.meta.url)) +const projectRoot = path.resolve(rootDir, '..') // https://vite.dev/config/ export default defineConfig({ @@ -11,11 +12,12 @@ export default defineConfig({ resolve: { alias: { '@': path.resolve(rootDir, 'src'), + '@shared': path.resolve(projectRoot, 'shared'), }, }, server: { fs: { - allow: [path.resolve(rootDir, '..')], + allow: [projectRoot], }, port: 5173, proxy: { diff --git a/shared/constants/delivery-carrier.d.ts b/shared/constants/delivery-carrier.d.ts new file mode 100755 index 0000000..d48d06e --- /dev/null +++ b/shared/constants/delivery-carrier.d.ts @@ -0,0 +1,11 @@ +export declare const DELIVERY_CARRIERS: readonly ['RUSSIAN_POST', 'OZON_PVZ', 'YANDEX_PVZ', 'FIVE_POST', 'WB_PVZ'] + +export declare const DELIVERY_CARRIER_LABELS: { + readonly RUSSIAN_POST: 'Почта России' + readonly OZON_PVZ: 'Озон доставка (пункт выдачи)' + readonly YANDEX_PVZ: 'Яндекс доставка (пункт выдачи)' + readonly FIVE_POST: '5Post (пункт выдачи)' + readonly WB_PVZ: 'WB доставка (пункт выдачи)' +} + +export declare function deliveryCarrierLabelRu(code: string | null | undefined): string | null diff --git a/shared/constants/delivery-carrier.js b/shared/constants/delivery-carrier.js new file mode 100755 index 0000000..f3afe51 --- /dev/null +++ b/shared/constants/delivery-carrier.js @@ -0,0 +1,14 @@ +export const DELIVERY_CARRIERS = Object.freeze(['RUSSIAN_POST', 'OZON_PVZ', 'YANDEX_PVZ', 'FIVE_POST', 'WB_PVZ']) + +export const DELIVERY_CARRIER_LABELS = Object.freeze({ + RUSSIAN_POST: 'Почта России', + OZON_PVZ: 'Озон доставка (пункт выдачи)', + YANDEX_PVZ: 'Яндекс доставка (пункт выдачи)', + FIVE_POST: '5Post (пункт выдачи)', + WB_PVZ: 'WB доставка (пункт выдачи)', +}) + +export function deliveryCarrierLabelRu(code) { + if (!code) return null + return DELIVERY_CARRIER_LABELS[code] ?? code +} diff --git a/shared/constants/notification-events.d.ts b/shared/constants/notification-events.d.ts new file mode 100755 index 0000000..ef4ab8e --- /dev/null +++ b/shared/constants/notification-events.d.ts @@ -0,0 +1,36 @@ +export type NotificationEventType = + | 'order:created' + | 'order:statusChanged' + | 'orderMessage:sent' + | 'orderMessage:adminReply' + | 'payment:statusChanged' + | 'auth:codeRequested' + | 'order:deliveryFeeAdjusted' + +export type NotificationChannel = 'email' | 'telegram' + +export type NotificationStatus = 'pending' | 'sent' | 'failed' + +export const NOTIFICATION_EVENTS: { + ORDER_CREATED: NotificationEventType + ORDER_STATUS_CHANGED: NotificationEventType + ORDER_MESSAGE_SENT: NotificationEventType + ORDER_MESSAGE_ADMIN_REPLY: NotificationEventType + PAYMENT_STATUS_CHANGED: NotificationEventType + AUTH_CODE_REQUESTED: NotificationEventType + DELIVERY_FEE_ADJUSTED: NotificationEventType +} + +export const NOTIFICATION_CHANNELS: { + EMAIL: NotificationChannel + TELEGRAM: NotificationChannel +} + +export const NOTIFICATION_STATUSES: { + PENDING: NotificationStatus + SENT: NotificationStatus + FAILED: NotificationStatus +} + +export const MAX_RETRY_ATTEMPTS: number +export const RETRY_DELAYS_MS: number[] diff --git a/shared/constants/notification-events.js b/shared/constants/notification-events.js new file mode 100755 index 0000000..c22f625 --- /dev/null +++ b/shared/constants/notification-events.js @@ -0,0 +1,26 @@ +/** @typedef {'order:created' | 'order:statusChanged' | 'orderMessage:sent' | 'orderMessage:adminReply' | 'payment:statusChanged' | 'auth:codeRequested'} NotificationEventType */ + +export const NOTIFICATION_EVENTS = { + ORDER_CREATED: 'order:created', + ORDER_STATUS_CHANGED: 'order:statusChanged', + ORDER_MESSAGE_SENT: 'orderMessage:sent', + ORDER_MESSAGE_ADMIN_REPLY: 'orderMessage:adminReply', + PAYMENT_STATUS_CHANGED: 'payment:statusChanged', + AUTH_CODE_REQUESTED: 'auth:codeRequested', + DELIVERY_FEE_ADJUSTED: 'order:deliveryFeeAdjusted', +} + +export const NOTIFICATION_CHANNELS = { + EMAIL: 'email', + TELEGRAM: 'telegram', +} + +export const NOTIFICATION_STATUSES = { + PENDING: 'pending', + SENT: 'sent', + FAILED: 'failed', +} + +export const MAX_RETRY_ATTEMPTS = 3 + +export const RETRY_DELAYS_MS = [5_000, 30_000, 120_000] diff --git a/shared/constants/order-status.d.ts b/shared/constants/order-status.d.ts new file mode 100755 index 0000000..ad0a04f --- /dev/null +++ b/shared/constants/order-status.d.ts @@ -0,0 +1,17 @@ +export declare const ORDER_STATUSES: readonly [ + 'DRAFT', + 'PENDING_PAYMENT', + 'PAID', + 'IN_PROGRESS', + 'SHIPPED', + 'READY_FOR_PICKUP', + 'DONE', + 'CANCELLED', +] + +export type OrderStatus = (typeof ORDER_STATUSES)[number] + +export declare const ADMIN_ORDER_TRANSITIONS: Record + +export declare function getNextAdminStatuses(from: string, deliveryType: string): string[] +export declare function canTransitionAdminOrderStatus(order: { status: string; deliveryType: string }, next: string): boolean diff --git a/shared/constants/order-status.js b/shared/constants/order-status.js new file mode 100755 index 0000000..6d9a54e --- /dev/null +++ b/shared/constants/order-status.js @@ -0,0 +1,38 @@ +export const ORDER_STATUSES = Object.freeze([ + "DRAFT", + "PENDING_PAYMENT", + "PAID", + "IN_PROGRESS", + "SHIPPED", + "READY_FOR_PICKUP", + "DONE", + "CANCELLED", +]); + +/** + * Допустимые переходы статусов, доступные админу. + * Значение — массив из next-статусов. + * Для IN_PROGRESS: объект с ключами по deliveryType. + */ +export const ADMIN_ORDER_TRANSITIONS = 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"], + }), +}); + +export function getNextAdminStatuses(from, deliveryType) { + const transition = ADMIN_ORDER_TRANSITIONS[from]; + if (!transition) return []; + if (Array.isArray(transition)) return [...transition]; + return transition[deliveryType] ? [...transition[deliveryType]] : []; +} + +export function canTransitionAdminOrderStatus(order, next) { + const from = order.status; + if (from === next) return true; + return getNextAdminStatuses(from, order.deliveryType).includes(next); +} diff --git a/shared/constants/payment-method.d.ts b/shared/constants/payment-method.d.ts new file mode 100755 index 0000000..70a9fee --- /dev/null +++ b/shared/constants/payment-method.d.ts @@ -0,0 +1 @@ +export declare const PAYMENT_METHODS: readonly ['online', 'on_pickup'] diff --git a/shared/constants/payment-method.js b/shared/constants/payment-method.js new file mode 100755 index 0000000..9616e19 --- /dev/null +++ b/shared/constants/payment-method.js @@ -0,0 +1 @@ +export const PAYMENT_METHODS = Object.freeze(['online', 'on_pickup']) diff --git a/shared/constants/test-checklist-items.d.ts b/shared/constants/test-checklist-items.d.ts new file mode 100755 index 0000000..85c683b --- /dev/null +++ b/shared/constants/test-checklist-items.d.ts @@ -0,0 +1,8 @@ +export interface TestChecklistItem { + key: string; + section: string; + action: string; + expectedResult: string; +} + +export declare const TEST_CHECKLIST_ITEMS: readonly TestChecklistItem[]; diff --git a/shared/constants/test-checklist-items.js b/shared/constants/test-checklist-items.js new file mode 100755 index 0000000..662f82c --- /dev/null +++ b/shared/constants/test-checklist-items.js @@ -0,0 +1,304 @@ +export const TEST_CHECKLIST_ITEMS = 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: "Уведомления приходят в реальном времени", + }, +]); diff --git a/shared/constants/upload-limits.d.ts b/shared/constants/upload-limits.d.ts new file mode 100755 index 0000000..88f7e54 --- /dev/null +++ b/shared/constants/upload-limits.d.ts @@ -0,0 +1,5 @@ +export declare const ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT: 20971520 + +export declare const ADMIN_UPLOAD_IMAGE_MAX_BYTES: 20971520 + +export declare function formatAdminImageMaxSizeHint(): string diff --git a/shared/constants/upload-limits.js b/shared/constants/upload-limits.js new file mode 100755 index 0000000..ffea1d9 --- /dev/null +++ b/shared/constants/upload-limits.js @@ -0,0 +1,9 @@ +const MB = 1024 * 1024 + +export const ADMIN_UPLOAD_IMAGE_MAX_FILE_BYTES_DEFAULT = 20 * MB + +export const ADMIN_UPLOAD_IMAGE_MAX_BYTES = 20 * MB + +export function formatAdminImageMaxSizeHint() { + return '20 МБ' +}