diff --git a/docs/superpowers/specs/2026-05-25-cart-added-snackbar-design.md b/docs/superpowers/specs/2026-05-25-cart-added-snackbar-design.md new file mode 100644 index 0000000..70427bb --- /dev/null +++ b/docs/superpowers/specs/2026-05-25-cart-added-snackbar-design.md @@ -0,0 +1,91 @@ +# Cart Added Snackbar — Design Spec + +**Date:** 2026-05-25 + +## Goal + +При добавлении товара в корзину показывать глобальное уведомление (Snackbar) с кнопкой «Перейти в корзину». + +## Scope + +- `AddToCartButton` (каталог, карточки товаров) +- `ToggleCartIcon` (страница товара, toggle add/remove) +- Глобальный Snackbar, рендерится один раз + +## Architecture + +### 1. Effector store — `shared/model/cart-notifications.ts` + +Минимальный стор: только булев флаг открытия. + +```ts +import { createEvent, createStore } from 'effector' + +export const cartAdded = createEvent() +export const cartDismissed = createEvent() + +export const $cartSnackOpen = createStore(false) + .on(cartAdded, () => true) + .on(cartDismissed, () => false) +``` + +### 2. UI Component — `shared/ui/CartSnackbar.tsx` + +Компонент подписывается на `$cartSnackOpen` через `useUnit`. + +- **Текст:** «Товар добавлен в корзину» +- **Кнопка действия:** «Перейти в корзину» → `navigate('/cart')` + `cartDismissed()` +- **Закрытие (крестик):** `cartDismissed()` +- **Авто-закрытие:** 4 секунды через `setTimeout` +- **Позиция:** `anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}` +- **Стиль:** MUI `` + `` + +Если пользователь быстро добавит несколько товаров — каждый `cartAdded()` перезапускает таймер. Очередь не нужна. + +### 3. Интеграция в `AddToCartButton` + +В `onSuccess` мутации добавляется `cartAdded()`: + +```ts +const addMut = useMutation({ + mutationFn: () => addToCart({ productId, qty }), + onSuccess: () => { + void qc.invalidateQueries({ queryKey: ['me', 'cart'] }) + cartAdded() + }, +}) +``` + +`productTitle` не передаётся — текст уведомления универсальный. + +### 4. Интеграция в `ToggleCartIcon` + +Только add-мутация: + +```ts +const addMut = useMutation({ + mutationFn: () => addToCart({ productId, qty: 1 }), + onSuccess: () => { + void qc.invalidateQueries({ queryKey: ['me', 'cart'] }) + cartAdded() + }, +}) +``` + +Remove-мутация без уведомления. + +### 5. Mount point — `app/AppProviders.tsx` + +`` рендерится один раз в `AppProviders.tsx` (или `App.tsx`), чтобы быть доступным во всём приложении. + +## Dependencies + +- effector / effector-react (уже используется) +- @mui/material — Snackbar, Alert (уже используется) +- react-router-dom — useNavigate (уже используется) + +## Testing + +- Unit-тест `CartSnackbar`: открывается по `cartAdded()`, закрывается по `cartDismissed()` +- Unit-тест `AddToCartButton`: вызывает `cartAdded()` в `onSuccess` +- Unit-тест `ToggleCartIcon`: вызывает `cartAdded()` только для add-мутации