Files
shop-server/docs/superpowers/specs/2026-05-27-refactoring-audit-design.md
T
2026-05-27 20:56:08 +05:00

8.3 KiB
Raw Blame History

Refactoring Audit — Code Quality Improvements

Date: 2026-05-27

Overview

Системный рефакторинг кодовой базы shop (craftshop monorepo) по 5 направлениям: toast-уведомления, пустые catch-блоки, серверное дублирование, клиентское дублирование, обработка ошибок API.

Подпроект 1: Система toast-уведомлений

Проблема

  • Единственный CartSnackbar (Effector) только для "товар добавлен в корзину"
  • Остальные операции без обратной связи — только inline <Alert> на страницах
  • Нет централизованного показа ошибок от API

Решение

1.1 Effector-стор уведомлений (client/src/shared/model/notification.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)

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)

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)

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 (проверка типов)