261 lines
9.7 KiB
TypeScript
261 lines
9.7 KiB
TypeScript
import React, { useState, useMemo, useEffect } from 'react';
|
|
import { View, ScrollView } from 'react-native';
|
|
import { useRouter } from 'expo-router';
|
|
import { tw, BottomDialog, MyText, MyTouchableOpacity, Quantifier } from 'common-ui';
|
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
|
import { useCartStore } from '@/src/store/cartStore';
|
|
import { useFlashCartStore } from '@/src/store/flashCartStore';
|
|
import { trpc } from '@/src/trpc-client';
|
|
import { useAddToCart, useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks';
|
|
import { useGetEssentialConsts } from '@/src/hooks/prominent-api-hooks';
|
|
import dayjs from 'dayjs';
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
|
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 router = useRouter();
|
|
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore();
|
|
const [quantity, setQuantity] = useState(1);
|
|
const [selectedSlotId, setSelectedSlotId] = useState<number | null>(null);
|
|
const [selectedFlashDelivery, setSelectedFlashDelivery] = useState(false);
|
|
|
|
const { data: slotsData } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
|
const { data: cartData } = useGetCart();
|
|
const { data: constsData } = useGetEssentialConsts();
|
|
// const isFlashDeliveryEnabled = constsData?.isFlashDeliveryEnabled === true;
|
|
const isFlashDeliveryEnabled = true;
|
|
|
|
|
|
|
|
const addToCart = useAddToCart({
|
|
showSuccessAlert: false,
|
|
showErrorAlert: false,
|
|
refetchCart: true,
|
|
});
|
|
|
|
const updateCartItem = useUpdateCartItem({
|
|
showSuccessAlert: false,
|
|
showErrorAlert: false,
|
|
refetchCart: true,
|
|
});
|
|
|
|
const removeFromCart = useRemoveFromCart({
|
|
showSuccessAlert: false,
|
|
showErrorAlert: false,
|
|
refetchCart: true,
|
|
});
|
|
|
|
const isOpen = !!addedToCartProduct;
|
|
|
|
|
|
const product = addedToCartProduct?.product;
|
|
|
|
|
|
// Pre-select cart's slotId and quantity if item is already in cart
|
|
useEffect(() => {
|
|
if (isOpen && product) {
|
|
const cartItem = cartData?.items?.find((item: any) => item.productId === product.id);
|
|
const cartQuantity = cartItem?.quantity || 0;
|
|
|
|
// Set quantity: 0 → 1, >1 → keep as is
|
|
setQuantity(cartQuantity === 0 ? 1 : cartQuantity);
|
|
|
|
if (cartItem?.slotId) {
|
|
setSelectedSlotId(cartItem.slotId);
|
|
} else {
|
|
setSelectedSlotId(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);
|
|
|
|
// Find cart item for this product
|
|
const cartItem = cartData?.items?.find((item: any) => item.productId === product?.id);
|
|
|
|
// Determine if updating existing item (quantity > 1 means it's an update)
|
|
const isUpdate = (cartItem?.quantity || 0) >= 1;
|
|
|
|
// Check if flash delivery option should be shown
|
|
const showFlashOption = product?.isFlashAvailable === true && isFlashDeliveryEnabled;
|
|
|
|
const handleAddToCart = () => {
|
|
if (selectedFlashDelivery) {
|
|
useFlashCartStore.getState().setAddedFlashProduct({ productId: product.id, product });
|
|
router.push('/(drawer)/(tabs)/flash-delivery/(products)');
|
|
clearAddedToCartProduct();
|
|
return;
|
|
}
|
|
if (isUpdate && cartItem?.id) {
|
|
updateCartItem.mutate(
|
|
{ itemId: cartItem.id, quantity },
|
|
{ onSuccess: () => clearAddedToCartProduct() }
|
|
);
|
|
} else {
|
|
const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0;
|
|
addToCart.mutate(
|
|
{ productId: product.id, quantity, slotId },
|
|
{ onSuccess: () => clearAddedToCartProduct() }
|
|
);
|
|
}
|
|
};
|
|
|
|
if (!isOpen || !addedToCartProduct) return null;
|
|
|
|
return (
|
|
<BottomDialog open={isOpen} onClose={clearAddedToCartProduct}>
|
|
<SafeAreaView>
|
|
<View style={tw`p-6 max-h-[650px]`}>
|
|
<View style={tw`flex-row items-center mb-2`}>
|
|
<View style={tw`w-10 h-10 bg-blue-50 rounded-full items-center justify-center mr-3`}>
|
|
<MaterialIcons name="schedule" size={20} color="#3B82F6" />
|
|
</View>
|
|
<View>
|
|
<MyText style={tw`text-xl font-bold text-gray-900`}>Select Delivery Slot</MyText>
|
|
{product?.name && (
|
|
<MyText style={tw`text-sm text-gray-500`}>
|
|
{product.name} ({product.productQuantity}{product.unitNotation ? ` ${product.unitNotation}` : ''})
|
|
</MyText>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
<ScrollView showsVerticalScrollIndicator={false}>
|
|
{availableSlots.map((slot: any) => (
|
|
<MyTouchableOpacity
|
|
key={slot.id}
|
|
style={tw`flex-row items-start mb-4 bg-gray-50 p-4 rounded-xl border border-gray-100 ${selectedSlotId === slot.id ? 'border-brand500' : 'border-gray-100'
|
|
}`}
|
|
onPress={() => {
|
|
setSelectedSlotId(slot.id);
|
|
setSelectedFlashDelivery(false);
|
|
}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
|
<View style={tw`ml-3 flex-1`}>
|
|
<MyText style={tw`text-gray-900 font-bold text-base`}>
|
|
{dayjs(slot.deliveryTime).format('ddd, DD MMM • ')}{formatTimeRange(slot.deliveryTime)}
|
|
</MyText>
|
|
</View>
|
|
{selectedSlotId === slot.id ? (
|
|
<MaterialIcons name="check-circle" size={24} color="#3B82F6" style={tw`mt-0.5`} />
|
|
) : (
|
|
<MaterialIcons name="check-box-outline-blank" size={24} color="#9CA3AF" style={tw`mt-0.5`} />
|
|
)}
|
|
</MyTouchableOpacity>
|
|
))}
|
|
</ScrollView>
|
|
|
|
{showFlashOption && (
|
|
<MyTouchableOpacity
|
|
key="flash-delivery"
|
|
style={tw`flex-row items-center mb-4 bg-pink-50 p-4 rounded-xl border ${selectedFlashDelivery ? 'border-pink-500' : 'border-pink-200'
|
|
}`}
|
|
onPress={() => {
|
|
setSelectedFlashDelivery(true);
|
|
setSelectedSlotId(null);
|
|
}}
|
|
activeOpacity={0.7}
|
|
>
|
|
<MaterialIcons name="bolt" size={20} color="#f81260" />
|
|
<View style={tw`ml-3 flex-1`}>
|
|
<MyText style={tw`text-gray-900 font-bold text-base`}>1 hr Delivery</MyText>
|
|
</View>
|
|
{selectedFlashDelivery ? (
|
|
<MaterialIcons name="check-circle" size={24} color="#f81260" />
|
|
) : (
|
|
<MaterialIcons name="check-box-outline-blank" size={24} color="#f81260" />
|
|
)}
|
|
</MyTouchableOpacity>
|
|
)}
|
|
|
|
<View style={tw`mt-4`}>
|
|
<MyText style={tw`text-sm font-bold text-gray-900 mb-2`}>Quantity</MyText>
|
|
<View style={tw`flex-row items-center`}>
|
|
<View style={tw`flex-1`}>
|
|
<Quantifier
|
|
value={quantity}
|
|
setValue={setQuantity}
|
|
step={1}
|
|
unit={product.unitNotation}
|
|
/>
|
|
</View>
|
|
{isUpdate && (
|
|
<MyTouchableOpacity
|
|
onPress={() => {
|
|
if (cartItem?.id) {
|
|
removeFromCart.mutate(
|
|
{ itemId: cartItem.id },
|
|
{ onSuccess: () => clearAddedToCartProduct() }
|
|
);
|
|
} else {
|
|
clearAddedToCartProduct();
|
|
}
|
|
}}
|
|
style={tw`p-2 ml-3 bg-red-50 rounded-lg border border-red-200`}
|
|
>
|
|
<MaterialIcons name="delete-outline" size={24} color="#EF4444" />
|
|
</MyTouchableOpacity>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
<View style={tw`flex-row gap-3 mt-4 pb-12`}>
|
|
<MyTouchableOpacity
|
|
style={tw`flex-1 bg-brand500 py-3.5 rounded-xl items-center ${(!selectedSlotId && !selectedFlashDelivery) ? 'opacity-50' : ''}`}
|
|
onPress={handleAddToCart}
|
|
disabled={(addToCart.isLoading || updateCartItem.isLoading) || (!selectedSlotId && !selectedFlashDelivery)}
|
|
>
|
|
<MyText style={tw`text-white font-bold`}>
|
|
{addToCart.isLoading || updateCartItem.isLoading ? (isUpdate ? 'Updating...' : 'Adding...') : (isUpdate ? 'Update Item' : 'Add to Cart')}
|
|
</MyText>
|
|
</MyTouchableOpacity>
|
|
<MyTouchableOpacity
|
|
style={tw`flex-1 bg-gray-100 py-3.5 rounded-xl items-center`}
|
|
onPress={clearAddedToCartProduct}
|
|
>
|
|
<MyText style={tw`text-gray-700 font-bold`}>Cancel</MyText>
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
</View>
|
|
</SafeAreaView>
|
|
</BottomDialog>
|
|
);
|
|
}
|