dressed_for_succes_store/frontend/hooks/use-cart-provider.tsx

278 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import React, { createContext, useContext, useState, useEffect, ReactNode, useMemo, useRef } from "react" // Добавляем useRef
import { useToast } from "@/components/ui/use-toast"
import type { CartItemCreate } from "@/lib/cart"
// Объединяем импорты из cart-store
import cartStore, { Cart, createEmptyCart } from "@/lib/cart-store"
import { apiStatus } from "@/lib/api"
// Глобальная переменная для отслеживания последней синхронизации
let lastSyncTimestamp = 0
const SYNC_THROTTLE_MS = 5000 // Минимальное время между синхронизациями (5 секунд)
// Интерфейс контекста корзины
interface CartContextType {
cart: Cart
loading: boolean
error: string | null
itemCount: number
addToCart: (item: CartItemCreate) => Promise<boolean>
updateCartItem: (id: number, quantity: number) => Promise<boolean>
removeFromCart: (id: number) => Promise<boolean>
clearCart: () => Promise<boolean>
synchronizeCart: () => Promise<void>
isAuthenticated: boolean
}
// Создание контекста
const CartContext = createContext<CartContextType | undefined>(undefined)
// Удаляем дублирующийся импорт
// Провайдер контекста
export function CartProvider({ children }: { children: ReactNode }) {
// Инициализируем пустой корзиной, чтобы серверный и первый клиентский рендер совпадали
const [cart, setCart] = useState<Cart>(createEmptyCart())
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [syncInProgress, setSyncInProgress] = useState(false)
// Внутреннее состояние для отслеживания аутентификации
const [isAuthenticated, setIsAuthenticated] = useState(apiStatus.isAuthenticated);
const initialSyncDoneRef = useRef(false); // Флаг для отслеживания первичной синхронизации
const { toast } = useToast()
// Подписка на изменения корзины
useEffect(() => {
const unsubscribe = cartStore.subscribe(() => {
setCart(cartStore.getState())
})
return () => unsubscribe()
}, [])
// После монтирования на клиенте, устанавливаем актуальное состояние из cartStore
useEffect(() => {
setCart(cartStore.getState());
}, []);
// Следим за изменением глобального apiStatus.isAuthenticated
useEffect(() => {
// Обновляем внутреннее состояние только если оно действительно изменилось
if (apiStatus.isAuthenticated !== isAuthenticated) {
setIsAuthenticated(apiStatus.isAuthenticated);
// Сбрасываем флаг синхронизации при изменении статуса (например, при логауте)
if (!apiStatus.isAuthenticated) {
initialSyncDoneRef.current = false;
}
}
// Эта зависимость все еще может быть не идеальной, но лучше, чем ничего.
// В идеале, статус аутентификации должен приходить из AuthContext.
}, [apiStatus.isAuthenticated, isAuthenticated]);
// Синхронизация корзины при изменении статуса аутентификации на true
useEffect(() => {
// Запускаем синхронизацию только если пользователь стал аутентифицированным
// и первичная синхронизация для этой сессии еще не выполнялась.
if (isAuthenticated && !initialSyncDoneRef.current) {
const now = Date.now();
// Дополнительно проверяем троттлинг на всякий случай
if (!syncInProgress && now - lastSyncTimestamp > SYNC_THROTTLE_MS) {
console.log("Запуск первичной синхронизации корзины для аутентифицированного пользователя.");
synchronizeCart();
initialSyncDoneRef.current = true; // Отмечаем, что первичная синхронизация запущена
}
}
}, [isAuthenticated, syncInProgress]); // Зависим от внутреннего состояния isAuthenticated
// Синхронизация локальной корзины с сервером с дроттлингом
const synchronizeCart = async () => {
try {
// Если синхронизация уже идет, не запускаем новую
if (syncInProgress) return
setSyncInProgress(true)
setLoading(true)
// Фиксируем время последней синхронизации
lastSyncTimestamp = Date.now()
await cartStore.syncWithServer()
} catch (err) {
setError('Не удалось синхронизировать корзину')
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось синхронизировать корзину',
})
} finally {
setLoading(false)
setSyncInProgress(false)
}
}
// Добавление товара в корзину
const addToCart = async (item: CartItemCreate) => {
try {
setLoading(true)
setError(null)
const result = await cartStore.addToCart(item)
if (result) {
toast({
title: 'Товар добавлен',
description: 'Товар успешно добавлен в корзину',
})
return true
} else {
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось добавить товар в корзину',
})
return false
}
} catch (err) {
setError('Не удалось добавить товар в корзину')
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось добавить товар в корзину',
})
return false
} finally {
setLoading(false)
}
}
// Обновление количества товара в корзине
const updateCartItem = async (id: number, quantity: number) => {
try {
setLoading(true)
setError(null)
const result = await cartStore.updateCartItem(id, quantity)
if (!result) {
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось обновить товар в корзине',
})
}
return result
} catch (err) {
setError('Не удалось обновить товар в корзине')
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось обновить товар в корзине',
})
return false
} finally {
setLoading(false)
}
}
// Удаление товара из корзины
const removeFromCart = async (id: number) => {
try {
setLoading(true)
setError(null)
const result = await cartStore.removeFromCart(id)
if (result) {
toast({
title: 'Товар удален',
description: 'Товар успешно удален из корзины',
})
return true
} else {
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось удалить товар из корзины',
})
return false
}
} catch (err) {
setError('Не удалось удалить товар из корзины')
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось удалить товар из корзины',
})
return false
} finally {
setLoading(false)
}
}
// Очистка корзины
const clearCart = async () => {
try {
setLoading(true)
setError(null)
const result = await cartStore.clearCart()
if (result) {
toast({
title: 'Корзина очищена',
description: 'Корзина успешно очищена',
})
return true
} else {
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось очистить корзину',
})
return false
}
} catch (err) {
setError('Не удалось очистить корзину')
toast({
variant: 'destructive',
title: 'Ошибка',
description: 'Не удалось очистить корзину',
})
return false
} finally {
setLoading(false)
}
}
// Вычисляем itemCount на основе состояния cart
const itemCount = useMemo(() => {
return cart.items.reduce((sum, item) => sum + item.quantity, 0);
}, [cart.items]);
const value = {
cart,
loading,
error,
addToCart,
updateCartItem,
removeFromCart,
clearCart,
synchronizeCart,
itemCount, // Используем вычисленное значение
isAuthenticated // Используем внутреннее состояние
}
return (
<CartContext.Provider value={value}>
{children}
</CartContext.Provider>
)
}
// Хук для использования контекста корзины
export function useCart() {
const context = useContext(CartContext)
if (context === undefined) {
throw new Error("useCart должен использоваться внутри CartProvider")
}
return context
}