241 lines
9.4 KiB
TypeScript
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>
|
|
)
|
|
}
|