92 lines
3.2 KiB
Markdown
92 lines
3.2 KiB
Markdown
# 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 `<Snackbar>` + `<Alert severity="success">`
|
||
|
||
Если пользователь быстро добавит несколько товаров — каждый `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`
|
||
|
||
`<CartSnackbar />` рендерится один раз в `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-мутации
|