# Refactoring Audit — Code Quality Improvements Date: 2026-05-27 ## Overview Системный рефакторинг кодовой базы shop (craftshop monorepo) по 5 направлениям: toast-уведомления, пустые catch-блоки, серверное дублирование, клиентское дублирование, обработка ошибок API. ## Подпроект 1: Система toast-уведомлений ### Проблема - Единственный `CartSnackbar` (Effector) только для "товар добавлен в корзину" - Остальные операции без обратной связи — только inline `` на страницах - Нет централизованного показа ошибок от API ### Решение **1.1 Effector-стор уведомлений** (`client/src/shared/model/notification.ts`) ```ts type NotificationType = 'success' | 'error' | 'info' | 'warning' interface Notification { id: string type: NotificationType message: string autoHideDuration?: number } // События addNotification — добавить (генерирует id + пушит в очередь) dismissNotification — закрыть по id dismissAll — закрыть все // Стор $notifications: Notification[] — очередь, макс 3 видимых ``` **1.2 Компонент `NotificationStack`** (`client/src/shared/ui/NotificationStack/`) - Использует MUI `Snackbar` + `Alert` (уже стилизованы через тему) - Позиция: `bottom: 80px` - `Slide` transition, auto-hide по таймеру - Крестик для ручного закрытия - Не более 3 одновременных уведомлений (остальные в очереди) **1.3 Миграция CartSnackbar** - `cartAdded` событие пишет в `addNotification({ type: 'info', message: 'Товар добавлен в корзину' })` - Старый `CartSnackbar` удаляется (компонент, стор, тесты) - `ToggleCartIcon` и `AddToCartButton` — убрать прямой вызов `cartAdded`, заменить на `addNotification` **1.4 Интеграция с `useMutationWithToast`** (опционально, см. подпроект 5) ### Файлы - Новые: `shared/model/notification.ts`, `shared/ui/NotificationStack/NotificationStack.tsx`, `shared/ui/NotificationStack/index.ts` - Изменённые: `app/App.tsx` (+ NotificationStack), удаление `features/cart/model/cart-notifications.ts`, `features/cart/ui/CartSnackbar/` - Тесты: `shared/model/notification.test.ts`, `shared/ui/NotificationStack/NotificationStack.test.tsx` --- ## Подпроект 2: Пустые catch-блоки ### Проблема 17 мест с `catch { /* ignore */ }` или пустым телом. ### Решение Минимальное логирование с контекстом: | Файл | Уровень | Сообщение | |------|---------|-----------| | `persist-token.ts` (3 места) | `console.warn` | `'[persist-token] Failed to ...'` | | `theme-controller.tsx` (2 места) | `console.warn` | `'[theme] Failed to ...'` | | `CookieConsentBanner.tsx` (2 места) | `console.warn` | `'[cookie-consent] Failed to ...'` | | `SseProvider.tsx` | `console.warn` | `'[sse] Connection error:'` | | `admin-gallery` (сервер) | `request.log.error` | Контекст операции | | Остальные серверные | `request.log.error` | Контекст операции | ### Файлы Только изменения в существующих файлах. Новых файлов нет. --- ## Подпроект 3: Сервер — дублирование в роутах ### Проблема - ~25 мест с ручным try/catch-паттерном - Дублирование валидации галерейных изображений (POST/PATCH admin/products) - Дублирование `prisma.order.findFirst({ where: { id, userId } })` в 3 файлах ### Решение **3.1 `asyncHandler`** (`server/src/lib/async-handler.js`) ```js function asyncHandler(fn) { return async (request, reply) => { try { return await fn(request, reply) } catch (err) { request.log.error(err) const statusCode = err.statusCode || 500 const message = err.statusCode ? err.message : 'Internal server error' return reply.code(statusCode).send({ error: message }) } } } ``` **3.2 `validateGalleryImages`** (`server/src/lib/validate-gallery-images.js`) - Проверка существования `galleryImages` в БД - Проверка наличия resized-версий - Используется в POST и PATCH admin/products **3.3 `findUserOrder`** (`server/src/lib/find-user-order.js`) - `prisma.order.findFirst({ where: { id, userId }, ... })` с included relations ### Файлы - Новые: `server/src/lib/async-handler.js`, `server/src/lib/validate-gallery-images.js`, `server/src/lib/find-user-order.js` - Изменённые: ~10 server route files --- ## Подпроект 4: Клиент — дублирование ### Проблема - 4 копии `useQuery({ queryKey: ['me', 'cart'], ... })` - Дублирование `orderStatusLabelRu` и `ORDER_STATUS_DATA` ### Решение **4.1 `useCartQuery`** (`client/src/entities/cart/lib/use-cart-query.ts`) ```ts export function useCartQuery() { const user = useAuthUser() return useQuery({ queryKey: ['me', 'cart'], queryFn: fetchMyCart, enabled: Boolean(user), }) } ``` Замена в 4 компонентах. **4.2 Дубль статусов** - `ORDER_STATUS_DATA` — единственный источник - `orderStatusLabelRu` удалить, импорты переписать на `ORDER_STATUS_DATA` ### Файлы - Новые: `entities/cart/lib/use-cart-query.ts` - Изменённые: `AppHeader.tsx`, `CartPage.tsx`, `CheckoutPage.tsx`, `ToggleCartIcon.tsx`, удаление `order-status-labels.ts` --- ## Подпроект 5: Обработка ошибок API (клиент) ### Проблема - Нет централизованного показа ошибок API - `CheckoutPage` показывает сырое `(error as Error).message` ### Решение **5.1 `useMutationWithToast`** (`client/src/shared/lib/use-mutation-with-toast.ts`) ```ts function useMutationWithToast(options) { return useMutation({ ...options, onSuccess: (data, ...rest) => { if (options.successMessage) addNotification({ type: 'success', message: options.successMessage }) options.onSuccess?.(data, ...rest) }, onError: (error, ...rest) => { addNotification({ type: 'error', message: getApiErrorMessage(error) }) options.onError?.(error, ...rest) }, }) } ``` **5.2 `getApiErrorMessage`** улучшение (`client/src/shared/lib`) - Если сервер вернул `{ error: string }` — показать его - Ошибка сети → "Нет соединения с сервером." - 500 → "Произошла ошибка. Попробуйте позже." - Остальное → стандартное сообщение ### Файлы - Новые: `shared/lib/use-mutation-with-toast.ts` - Изменённые: `shared/lib/get-api-error-message.ts`, `CheckoutPage.tsx` (исправить отображение ошибки) --- ## Порядок реализации Подпроекты независимы и могут выполняться в любом порядке. Рекомендуемый порядок: 1. **Подпроект 2** (пустые catch) — быстрые и безопасные изменения, хороший разогрев 2. **Подпроект 1** (toast) — база для подпроекта 5, новая функциональность 3. **Подпроект 5** (useMutationWithToast) — строится поверх подпроекта 1 4. **Подпроект 3** (сервер) — standalone 5. **Подпроект 4** (клиент) — standalone ## Тестирование - Каждый подпроект: `npm run lint` (или `npm run lint:fix`) + `npm test` для своей директории - Финальная проверка: `cd client && npm run build` (проверка типов)