8.3 KiB
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 Slidetransition, 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(исправить отображение ошибки)
Порядок реализации
Подпроекты независимы и могут выполняться в любом порядке. Рекомендуемый порядок:
- Подпроект 2 (пустые catch) — быстрые и безопасные изменения, хороший разогрев
- Подпроект 1 (toast) — база для подпроекта 5, новая функциональность
- Подпроект 5 (useMutationWithToast) — строится поверх подпроекта 1
- Подпроект 3 (сервер) — standalone
- Подпроект 4 (клиент) — standalone
Тестирование
- Каждый подпроект:
npm run lint(илиnpm run lint:fix) +npm testдля своей директории - Финальная проверка:
cd client && npm run build(проверка типов)