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