import { useEffect, useMemo, useState } from 'react' import { useNavigate } from 'react-router-dom' import { Minus, Package, Plus, ShoppingBag, Trash2 } from 'lucide-react' import { useToast } from '@meshkee/dashboard-ui' import { useAuth } from '../../context/AuthContext' import { ApiError, isAbortError } from '../../lib/api' import { loadGuestCart, saveGuestCart, type GuestCartItem, } from '../../lib/guestCart' import { getCart, removeCartItem, updateCartItem, type Cart, } from '../../services/cartService' import { syncGuestCartToServer } from '../../services/syncGuestCart' import { formatIrtPrice } from '../../utils/irtPrice' import { CheckoutActionBar } from '../../components/checkout/CheckoutActionBar' import styles from './CheckoutSteps.module.css' type DisplayItem = { id: string title: string variant?: string image: string | null quantity: number lineTotal: number stockQuantity: number | null } function guestToDisplay(items: GuestCartItem[]): DisplayItem[] { return items.map((item) => ({ id: item.id, title: item.name, image: item.image, quantity: item.quantity, lineTotal: item.price * item.quantity, stockQuantity: null, })) } function formatVariantLabel(item: Cart['items'][number]): string | undefined { if (item.selections.length > 0) { return item.selections.map((selection) => selection.value).join(' · ') } const title = item.productNameFa?.trim() || item.productTitle if (item.label && item.label !== title && item.label !== item.productTitle) { return item.label } return undefined } function apiToDisplay(cart: Cart): DisplayItem[] { return cart.items.map((item) => ({ id: item.id, title: item.productNameFa?.trim() || item.productTitle, variant: formatVariantLabel(item), image: item.productImage, quantity: item.quantity, lineTotal: item.lineTotal, stockQuantity: item.stockQuantity, })) } export function CheckoutCartStep() { const navigate = useNavigate() const { showToast } = useToast() const { user } = useAuth() const isAuthenticated = Boolean(user) const [cart, setCart] = useState(null) const [guestItems, setGuestItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState('') const [busyItemId, setBusyItemId] = useState(null) useEffect(() => { const controller = new AbortController() async function load() { setLoading(true) setError('') const guest = loadGuestCart() setGuestItems(guest) if (!isAuthenticated) { setCart(null) setLoading(false) return } try { let data = await getCart(controller.signal) if (controller.signal.aborted) return if (data.cart.items.length === 0 && guest.length > 0) { const synced = await syncGuestCartToServer(controller.signal) if (controller.signal.aborted) return data = { cart: synced } } setCart(data.cart) if ((data.cart?.items?.length ?? 0) > 0) { setGuestItems([]) } } catch (err) { if (isAbortError(err)) return setError(err instanceof ApiError ? err.message : 'بارگذاری سبد خرید ممکن نشد.') } finally { if (!controller.signal.aborted) setLoading(false) } } void load() return () => controller.abort() }, [isAuthenticated]) const apiHasItems = (cart?.items.length ?? 0) > 0 const usingGuest = !isAuthenticated || !apiHasItems const items = useMemo( () => (usingGuest ? guestToDisplay(guestItems) : cart ? apiToDisplay(cart) : []), [usingGuest, guestItems, cart], ) const subtotal = useMemo( () => items.reduce((sum, item) => sum + item.lineTotal, 0), [items], ) const totalQuantity = useMemo( () => items.reduce((sum, item) => sum + item.quantity, 0), [items], ) function persistGuest(next: GuestCartItem[]) { setGuestItems(next) saveGuestCart(next) } async function handleQuantityChange(itemId: string, nextQuantity: number) { if (usingGuest) { if (nextQuantity <= 0) { persistGuest(guestItems.filter((item) => item.id !== itemId)) return } persistGuest( guestItems.map((item) => item.id === itemId ? { ...item, quantity: nextQuantity } : item, ), ) return } setBusyItemId(itemId) try { const data = await updateCartItem(itemId, nextQuantity) setCart(data.cart) } catch (err) { showToast(err instanceof ApiError ? err.message : 'به‌روزرسانی تعداد ممکن نشد.', 'error') } finally { setBusyItemId(null) } } async function handleRemove(itemId: string) { if (usingGuest) { persistGuest(guestItems.filter((item) => item.id !== itemId)) showToast('محصول از سبد حذف شد.', 'success') return } setBusyItemId(itemId) try { const data = await removeCartItem(itemId) setCart(data.cart) showToast('محصول از سبد حذف شد.', 'success') } catch (err) { showToast(err instanceof ApiError ? err.message : 'حذف محصول ممکن نشد.', 'error') } finally { setBusyItemId(null) } } async function handleContinue() { if (!isAuthenticated) { navigate(`/login?redirect=${encodeURIComponent('/checkout/delivery')}`, { state: { from: '/checkout/delivery' }, }) return } if (usingGuest && guestItems.length > 0) { setLoading(true) try { const cart = await syncGuestCartToServer() setCart(cart) setGuestItems([]) if (cart.items.length === 0) { showToast('انتقال سبد خرید ممکن نشد. دوباره تلاش کنید.', 'error') return } } catch (err) { showToast( err instanceof ApiError ? err.message : 'انتقال سبد خرید ممکن نشد.', 'error', ) return } finally { setLoading(false) } } navigate('/checkout/delivery') } if (loading) { return
در حال بارگذاری سبد خرید...
} if (error && items.length === 0) { return (
{error}
) } return (

سبد خرید شما

{items.length === 0 ? 'سبد خرید شما خالی است.' : 'آماده تکمیل سفارش هستید.'} {!isAuthenticated && items.length > 0 ? ' برای ادامه، در مرحله بعد وارد شوید.' : ''}

{items.length === 0 ? (

برای شروع خرید، از فروشگاه محصول اضافه کنید.

) : (
{items.map((item) => { const busy = busyItemId === item.id const atMax = item.stockQuantity !== null && item.quantity >= item.stockQuantity return (
{item.image ? ( ) : (
)}
{item.title}
{item.variant ? (
{item.variant}
) : null}
{item.quantity}
{formatIrtPrice(item.lineTotal)}
) })}
)} {items.length > 0 && ( <>
تعداد کالا {totalQuantity.toLocaleString('fa-IR')} عدد
جمع کل {formatIrtPrice(subtotal)}
)}
) }