freshyo/apps/web-ui/src/components/AddToCartDialog.tsx
2026-08-08 12:40:19 +05:30

241 lines
9.4 KiB
TypeScript

import React, { useState, useMemo, useEffect } from 'react'
import { useNavigate } from '@tanstack/react-router'
import { BottomDialog, p, div, Quantifier } from 'web-components'
import { useSlots } from '../hooks/prominent-api-hooks'
import { useAddToCart, useUpdateCartItem, useRemoveFromCart, useGetCart } from '../hooks/cart-query-hooks'
import { useCartStore } from '../lib/stores/cart-store'
import { useCentralSlotStore } from '../lib/stores/central-slot-store'
import { ShoppingCart, Truck, Zap, X } from 'lucide-react'
import dayjs from 'dayjs'
const formatTimeRange = (deliveryTime: string) => {
const time = dayjs(deliveryTime)
const endTime = time.add(1, 'hour')
const startPeriod = time.format('A')
const endPeriod = endTime.format('A')
if (startPeriod === endPeriod) {
return `${time.format('h')}-${endTime.format('h')} ${startPeriod}`
} else {
return `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`
}
}
export default function AddToCartDialog() {
const navigate = useNavigate()
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore()
const [quantity, setQuantity] = useState(1)
const [selectedSlotId, setSelectedSlotId] = useState<number | null>(null)
const [selectedFlashDelivery, setSelectedFlashDelivery] = useState(false)
const { data: slotsData } = useSlots()
const { data: cartData } = useGetCart()
const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap)
const isFlashDeliveryEnabled = true
const addToCart = useAddToCart('regular')
const updateItem = useUpdateCartItem('regular')
const removeItem = useRemoveFromCart('regular')
const isOpen = !!addedToCartProduct
const product = addedToCartProduct?.product
useEffect(() => {
if (isOpen && product) {
const cartItem = cartData?.items?.find((item: any) => item.productId === product.id)
const cartQuantity = cartItem?.quantity || 0
setQuantity(cartQuantity === 0 ? 1 : cartQuantity)
setSelectedSlotId(cartItem?.slotId || null)
}
}, [isOpen, cartData, product])
const { slotMap, productSlotIdsMap } = useMemo(() => {
const slotMap: Record<number, any> = {}
const productSlotIdsMap: Record<number, number[]> = {}
if (slotsData?.slots) {
slotsData.slots.forEach((slot: any) => {
slotMap[slot.id] = slot
slot.products?.forEach((p: any) => {
if (!productSlotIdsMap[p.id]) {
productSlotIdsMap[p.id] = []
}
productSlotIdsMap[p.id].push(slot.id)
})
})
}
return { slotMap, productSlotIdsMap }
}, [slotsData])
const availableSlotIds = productSlotIdsMap[product?.id] || []
const availableSlots = availableSlotIds
.map((slotId) => slotMap[slotId])
.filter(Boolean)
.filter((slot) => dayjs(slot.deliveryTime).isAfter(dayjs()))
const cartItem = cartData?.items?.find((item: any) => item.productId === product?.id)
const isUpdate = (cartItem?.quantity || 0) >= 1
const productAvailability = productSlotsMap[product?.id]
const showFlashOption = productAvailability?.isFlashAvailable === true && isFlashDeliveryEnabled
const handleAddToCart = () => {
if (selectedFlashDelivery) {
navigate({ to: '/flash' })
clearAddedToCartProduct()
return
}
if (isUpdate && cartItem) {
updateItem.mutate(
{ productId: product.id, quantity, slotId: selectedSlotId, deliveryDate: selectedSlotId ? slotMap[selectedSlotId]?.deliveryTime : null },
{ onSuccess: () => clearAddedToCartProduct() }
)
} else {
const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0
addToCart.mutate(
{
productId: product.id,
quantity,
storeId: product.storeId || 1,
slotId,
deliveryDate: slotMap[slotId]?.deliveryTime || null,
},
{ onSuccess: () => clearAddedToCartProduct() }
)
}
}
const handleRemove = () => {
if (cartItem) {
removeItem.mutate(product.id, { onSuccess: () => clearAddedToCartProduct() })
} else {
clearAddedToCartProduct()
}
}
if (!isOpen || !addedToCartProduct) return null
return (
<BottomDialog open={isOpen} onClose={clearAddedToCartProduct}>
<div className="px-2">
<div className="flex items-start gap-3 mb-4">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-blue-50">
<Truck className="h-5 w-5 text-blue-500" />
</div>
<div className="flex-1">
<p className="font-bold text-lg">
Select Delivery Slot
</p>
{product?.name && (
<p className="text-sm text-gray-500">
{product.name} ({product.productQuantity}{product.unitNotation ? ` ${product.unitNotation}` : ''})
</p>
)}
</div>
<button onClick={clearAddedToCartProduct} className="text-gray-400 hover:text-gray-600">
<X className="h-5 w-5" />
</button>
</div>
<div className="max-h-[40vh] space-y-3 overflow-y-auto mb-4">
{availableSlots.map((slot: any) => (
<div
key={slot.id}
onClick={() => {
setSelectedSlotId(slot.id)
setSelectedFlashDelivery(false)
}}
className={`flex items-start gap-3 rounded-xl border bg-gray-50 p-4 ${
selectedSlotId === slot.id ? 'border-brand-500' : 'border-gray-100'
}`}
>
<Truck className="mt-0.5 h-5 w-5 shrink-0 text-blue-500" />
<p className="font-bold flex-1 text-sm">
{dayjs(slot.deliveryTime).format('ddd, DD MMM • ')}{formatTimeRange(slot.deliveryTime)}
</p>
{selectedSlotId === slot.id ? (
<svg className="h-6 w-6 shrink-0 text-brand-500" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg>
) : (
<svg className="h-6 w-6 shrink-0 text-gray-300" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
</svg>
)}
</div>
))}
</div>
{showFlashOption && (
<div
onClick={() => {
setSelectedFlashDelivery(true)
setSelectedSlotId(null)
}}
className={`flex items-center gap-3 rounded-xl border p-4 mb-4 ${
selectedFlashDelivery ? 'border-pink-500 bg-pink-50' : 'border-pink-200 bg-pink-50'
}`}
>
<Zap className="h-5 w-5 shrink-0 text-pink-500" />
<p className="font-bold flex-1 text-sm">
1 hr Delivery
</p>
{selectedFlashDelivery ? (
<svg className="h-6 w-6 shrink-0 text-pink-500" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg>
) : (
<svg className="h-6 w-6 shrink-0 text-pink-300" fill="currentColor" viewBox="0 0 24 24">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
</svg>
)}
</div>
)}
<div className="mb-4">
<p className="font-bold mb-2 text-sm">
Quantity
</p>
<div className="flex items-center gap-3">
<Quantifier value={quantity} setValue={setQuantity} step={1} unit={product?.unitNotation} />
{isUpdate && (
<div
onClick={handleRemove}
className="rounded-lg border border-red-200 bg-red-50 p-2"
>
<svg className="h-5 w-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</div>
)}
</div>
</div>
<div className="flex gap-3 pb-4">
<button
onClick={handleAddToCart}
disabled={(!selectedSlotId && !selectedFlashDelivery) || addToCart.isPending || updateItem.isPending}
className={`flex flex-1 items-center justify-center gap-2 rounded-xl py-3 font-bold text-white transition-colors ${
(!selectedSlotId && !selectedFlashDelivery) || addToCart.isPending || updateItem.isPending
? 'bg-brand-500/50'
: 'bg-brand-500 hover:bg-brand-600'
}`}
>
<ShoppingCart className="h-4 w-4" />
{addToCart.isPending || updateItem.isPending
? isUpdate ? 'Updating...' : 'Adding...'
: isUpdate ? 'Update Item' : 'Add to Cart'}
</button>
<button
onClick={clearAddedToCartProduct}
className="flex-1 rounded-xl bg-gray-100 py-3 font-bold text-gray-700 hover:bg-gray-200 transition-colors"
>
Cancel
</button>
</div>
</div>
</BottomDialog>
)
}