import React, { useState, useEffect, useMemo } from "react"; import { View, ScrollView, Image, Alert, Platform, TouchableOpacity, } from "react-native"; import { useRouter } from "expo-router"; import { tw, useManualRefresh, AppContainer, useMarkDataFetchers, MyText, MyTouchableOpacity, BottomDropdown, BottomDialog , Quantifier } from "common-ui"; import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import { useHideTabNav } from "@/src/hooks/useHideTabNav"; import TestingPhaseNote from "@/components/TestingPhaseNote"; import dayjs from "dayjs"; import { trpc } from "@/src/trpc-client"; import { useGetCart, useUpdateCartItem, useRemoveFromCart } from '@/hooks/cart-query-hooks'; import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api'; interface CartItem { id: number; productId: number; quantity: number; product: { price: number; isOutOfStock: boolean; name: string; images: string[]; unit?: string; incrementStep?: number; productQuantity?: number; } | null; } interface CartPageProps { isFlashDelivery?: boolean; } export default function CartPage({ isFlashDelivery = false }: CartPageProps) { // Hide tabs when cart page is active useHideTabNav(); const cartType: "regular" | "flash" = isFlashDelivery ? "flash" : "regular"; const [quantities, setQuantities] = useState>({}); const { data: cartData, isLoading, error, refetch: refetchCart, } = useGetCart({ refetchOnWindowFocus: true }, cartType); // Extract product IDs from cart items const productIds = cartData?.items.map(item => item.productId) || []; // Get cart slots for the products in cart const { data: slotsData, refetch: refetchSlots, error: slotsError } = trpc.user.cart.getCartSlots.useQuery( { productIds }, { enabled: productIds.length > 0, refetchOnWindowFocus: false } ); const generateCouponDescription = (coupon: any): string => { let desc = ""; if (coupon.discountPercent) { desc += `${coupon.discountPercent}% off`; } else if (coupon.flatDiscount) { desc += `₹${coupon.flatDiscount} off`; } if (coupon.minOrder) { desc += ` on orders above ₹${coupon.minOrder}`; } if (coupon.maxValue) { desc += ` (max discount ₹${coupon.maxValue})`; } return desc; }; const { data: couponsRaw, error: couponsError } = trpc.user.coupon.getEligible.useQuery(); const { data: constsData } = useGetEssentialConsts(); const { data: productsData } = trpc.user.product.getAllProductsSummary.useQuery(); const cartItems = cartData?.items || []; // Memoized flash-eligible product IDs const flashEligibleProductIds = useMemo(() => { if (!productsData) return new Set(); return new Set( productsData .filter((product: any) => product.isFlashAvailable) .map((product: any) => product.id) ); }, [productsData]); // Base total price without discounts for coupon eligibility check const baseTotalPrice = useMemo( () => cartItems .filter((item) => !item.product?.isOutOfStock) .reduce( (sum, item) => sum + (item.product?.price || 0) * (quantities[item.id] || item.quantity), 0 ), [cartItems, quantities] ); const eligibleCoupons = useMemo(() => { if (!couponsRaw?.data) return []; return couponsRaw.data .map((coupon) => { let isEligible = true; let ineligibilityReason = ""; if ( coupon.maxLimitForUser && coupon.usages.length >= coupon.maxLimitForUser ) { isEligible = false; ineligibilityReason = "Usage limit exceeded"; } if (coupon.minOrder && parseFloat(coupon.minOrder) > baseTotalPrice) { isEligible = false; ineligibilityReason = `Min order ₹${coupon.minOrder}`; } return { id: coupon.id, code: coupon.couponCode, discountType: coupon.discountPercent ? "percentage" : "flat", discountValue: parseFloat( coupon.discountPercent || coupon.flatDiscount || "0" ), maxValue: coupon.maxValue ? parseFloat(coupon.maxValue) : undefined, minOrder: coupon.minOrder ? parseFloat(coupon.minOrder) : undefined, description: generateCouponDescription(coupon), exclusiveApply: coupon.exclusiveApply, isEligible, ineligibilityReason: isEligible ? undefined : ineligibilityReason, }; }) .filter( (coupon) => coupon.ineligibilityReason !== "Usage limit exceeded" ); }, [couponsRaw, baseTotalPrice]); const updateCartItem = useUpdateCartItem({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, cartType); const removeFromCart = useRemoveFromCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }, cartType); useMarkDataFetchers(() => { refetchCart(); refetchSlots(); }); useManualRefresh(() => { refetchCart(); refetchSlots(); }); const [selectedSlots, setSelectedSlots] = useState>({}); const [selectedCouponId, setSelectedCouponId] = useState(null); const [couponDialogOpen, setCouponDialogOpen] = useState(false); const router = useRouter(); // Process slots: flatten and unique const availableSlots = React.useMemo(() => { if (!slotsData) return []; const allSlots = Object.values(slotsData).flat(); const uniqueSlots = allSlots.filter( (slot, index, self) => index === self.findIndex((s) => s.id === slot.id) ); return uniqueSlots.map((slot) => ({ label: `Delivery: ${dayjs(slot.deliveryTime).format( "ddd DD MMM, h:mm a" )} - Close time: ${dayjs(slot.freezeTime).format("h:mm a")}`, value: slot.id, })); }, [slotsData]); // Get available slots for a specific product const getAvailableSlotsForProduct = React.useMemo(() => { return (productId: number) => { if (!slotsData || !slotsData[productId]) return []; return slotsData[productId].map((slot) => ({ label: `Delivery: ${dayjs(slot.deliveryTime).format( "ddd DD MMM, h:mm a" )} - Close time: ${dayjs(slot.freezeTime).format("h:mm a")}`, value: slot.id, })); }; }, [slotsData]); // Calculate coupon discount const selectedCoupons = useMemo( () => selectedCouponId ? eligibleCoupons?.filter((coupon) => coupon.id === selectedCouponId) : [], [eligibleCoupons, selectedCouponId] ); const totalPrice = cartItems .filter((item) => !item.product?.isOutOfStock) .reduce((sum, item) => { const quantity = quantities[item.id] || item.quantity; const price = isFlashDelivery ? (item.product?.flashPrice ?? item.product?.price ?? 0) : (item.product?.price || 0); return sum + price * quantity; }, 0); const dropdownData = useMemo( () => eligibleCoupons?.map((coupon) => { const discount = coupon.discountType === "percentage" ? Math.min( (totalPrice * coupon.discountValue) / 100, coupon.maxValue || Infinity ) : Math.min(coupon.discountValue, coupon.maxValue || totalPrice); const saveString = !isNaN(discount) ? ` (Save ₹${discount})` : ""; const baseLabel = `${coupon.code} - ${coupon.description}${coupon.isEligible ? saveString : "" }`; const label = coupon.isEligible ? baseLabel : `${baseLabel} (${coupon.ineligibilityReason})`; return { label, value: coupon.id, disabled: !coupon.isEligible, }; }) || [], [eligibleCoupons, totalPrice] ); const discountAmount = useMemo( () => selectedCoupons?.reduce( (sum, coupon) => sum + (coupon.discountType === "percentage" ? Math.min( (totalPrice * coupon.discountValue) / 100, coupon.maxValue || Infinity ) : Math.min(coupon.discountValue, coupon.maxValue || totalPrice)), 0 ) || 0, [selectedCoupons, totalPrice] ); const finalTotal = totalPrice - discountAmount; const deliveryCharge = useMemo( () => { const threshold = isFlashDelivery ? constsData?.flashFreeDeliveryThreshold : constsData?.freeDeliveryThreshold; const charge = isFlashDelivery ? constsData?.flashDeliveryCharge : constsData?.deliveryCharge; return finalTotal < threshold ? charge : 0; }, [finalTotal, constsData, isFlashDelivery] ); const finalTotalWithDelivery = finalTotal + deliveryCharge; const hasAvailableItems = cartItems.some(item => !item.product?.isOutOfStock); useEffect(() => { const initial: Record = {}; cartItems.forEach((item) => { initial[item.id] = item.quantity; }); setQuantities(initial); }, [cartData]); // Auto-select delivery slots for each cart item useEffect(() => { if (cartItems.length > 0) { const newSelectedSlots = { ...selectedSlots }; cartItems.forEach(item => { // Skip if already has a selected slot if (selectedSlots[item.id]) return; if (isFlashDelivery) { // For flash delivery, always use slot 0 newSelectedSlots[item.id] = 0; } else { // For regular delivery, find earliest available slot const productSlots = slotsData?.[item.productId]; if (!productSlots || productSlots.length === 0) return; const now = dayjs(); const upcomingSlots = productSlots.filter(slot => dayjs(slot.deliveryTime).isAfter(now) ).sort((a, b) => dayjs(a.deliveryTime).diff(dayjs(b.deliveryTime)) ); if (upcomingSlots.length > 0) { // Select the earliest available slot for this product const earliestSlot = upcomingSlots[0]; newSelectedSlots[item.id] = earliestSlot.id; } } }); setSelectedSlots(newSelectedSlots); } }, [slotsData, cartItems, isFlashDelivery]); if (isLoading) { return ( Loading cart... ); } if (error) { return ( Oops! Failed to load your cart ); } return ( {/* Fixed Cart Type Header */} router.back()} style={tw`p-2 -ml-2 mr-1`} activeOpacity={0.7} > {isFlashDelivery ? "Flash Delivery Cart" : "Scheduled Delivery Cart"} {/* Cart Items */} {cartItems.length === 0 ? ( Your cart is empty Looks like you haven't added anything to your cart yet. router.push("/(drawer)/(tabs)/home")} > Start Shopping ) : ( <> {cartItems.map((item, index) => { const productSlots = getAvailableSlotsForProduct(item.productId); const selectedSlotForItem = selectedSlots[item.id]; const isFlashEligible = isFlashDelivery ? flashEligibleProductIds.has(item.productId) : true; // const isAvailable = (productSlots.length > 0 || isFlashDelivery) && !item.product?.isOutOfStock && isFlashEligible; let isAvailable = true; if(item.product?.isOutOfStock) { isAvailable = false; } else if(isFlashDelivery) { if(!isFlashEligible) { isAvailable = false; } } else { if(productSlots.length === 0) { isAvailable = false; } } // if (item.product?.isOutOfStock) { // isAvailable = false; // } else if (isFlashDelivery) { // isAvailable = isFlashEligible; // } const quantity = quantities[item.id] || item.quantity; const price = isFlashDelivery ? (item.product?.flashPrice ?? item.product?.price ?? 0) : (item.product?.price || 0); const itemPrice = price * quantity; return ( {item.product.name} {(() => { const qty = item.product?.productQuantity || 1; const unit = item.product?.unit || ''; if (unit?.toLowerCase() === 'kg' && qty < 1) { return `${Math.round(qty * 1000)}g`; } return `${qty}${unit}`; })()} { if (value === 0) { // Show confirmation alert before removing item Alert.alert( "Remove Item", "Are you sure you want to remove this item from your cart?", [ { text: "Cancel", style: "cancel", onPress: () => { // Reset quantity back to 1 setQuantities((prev) => ({ ...prev, [item.id]: 1 })); } }, { text: "Remove", style: "destructive", onPress: () => { // Proceed with removal removeFromCart.mutate( { itemId: item.id }, { onSuccess: () => { refetchCart(); }, onError: (error: any) => { Alert.alert("Error", error.message || "Failed to remove item"); // Restore quantity on error setQuantities((prev) => ({ ...prev, [item.id]: 1 })); }, } ); } } ] ); } else { // Update quantity normally setQuantities((prev) => ({ ...prev, [item.id]: value, })); updateCartItem.mutate({ itemId: item.id, quantity: value, }); } }} step={item.product.incrementStep} unit={item.product?.unit} /> {/* Delivery Slot Selection per Product - Hidden for Flash Delivery */} {!isFlashDelivery && ( { setSelectedSlots((prev) => ({ ...prev, [item.id]: Number(value) })); }} disabled={productSlots.length === 0} triggerComponent={({ onPress, disabled, displayText }) => { const selectedSlotForItem = selectedSlots[item.id]; const selectedSlot = productSlots.find(slot => slot.value === selectedSlotForItem); const deliveryTimeText = selectedSlot ? selectedSlot.label.split(' - ')[0].replace('Delivery: ', '') : null; return ( {deliveryTimeText || (productSlots.length === 0 ? "No delivery slots available" : "Choose delivery slot")} Change ); }} /> ₹{itemPrice} { Alert.alert( "Remove Item", `Remove ${item.product.name} from cart?`, [ { text: "Cancel", style: "cancel" }, { text: "Remove", style: "destructive", onPress: () => { removeFromCart.mutate( { itemId: item.id }, { onSuccess: () => { refetchCart(); }, onError: (error: any) => { Alert.alert( "Error", error.message || "Failed to remove item" ); }, } ); }, }, ] ); }} style={tw`p-1`} > )} {/* Price for Flash Delivery (already in same row as slot) */} {isFlashDelivery && ( ₹{itemPrice} { Alert.alert( "Remove Item", `Remove ${item.product.name} from cart?`, [ { text: "Cancel", style: "cancel" }, { text: "Remove", style: "destructive", onPress: () => { removeFromCart.mutate( { itemId: item.id }, { onSuccess: () => { refetchCart(); }, onError: (error: any) => { Alert.alert( "Error", error.message || "Failed to remove item" ); }, } ); }, }, ] ); }} style={tw`p-1`} > )} {!isAvailable && ( {item.product?.isOutOfStock ? "Out of Stock" : isFlashDelivery && !flashEligibleProductIds.has(item.productId) ? "Not available for flash delivery. Please remove" : "No delivery slots available"} )} {/* Gray horizontal line between items (except for the last item) */} {index < cartItems.length - 1 && ( )} ); })} )} {/* Cart Type Switcher */} {cartItems.length > 0 && ( {/* First row: Text content */} {isFlashDelivery ? "Flash Delivery" : "Scheduled Delivery"} {isFlashDelivery ? "30 min delivery • Immediate pickup" : "Choose your preferred delivery time" } {/* Second row: Navigation trigger */} { if (isFlashDelivery) { // Switch from flash to scheduled delivery router.push("/(drawer)/(tabs)/home" as any); } else { // Switch from scheduled to flash delivery router.push("/(drawer)/(tabs)/flash-delivery/(products)"); } }} activeOpacity={0.8} > {isFlashDelivery ? "Go to Scheduled Delivery" : "Go to Flash Delivery"} )} {/* Coupon Selection */} {hasAvailableItems && ( Offers & Coupons { setSelectedCouponId(value ? Number(value) : null); }} placeholder={ eligibleCoupons.length === 0 ? "No coupons available" : "Select a coupon" } /> {eligibleCoupons.length === 0 && ( No coupons available for this order )} {selectedCouponId && ( setSelectedCouponId(null)} > Remove coupon )} )} {/* Bottom Checkout Bar - Now Static */} {hasAvailableItems && ( {/* Bill Header */} Bill Details {/* Item Total */} Item Total ₹{totalPrice} {/* Discount */} {discountAmount > 0 && ( Product Discount -₹{discountAmount} )} {/* Applied Coupon */} {selectedCoupons.length > 0 && ( Coupon Applied setCouponDialogOpen(true)}> {selectedCoupons[0].code} )} {/* Delivery Fee */} Delivery Fee {deliveryCharge === 0 && (constsData?.deliveryCharge || 0) > 0 && ( ₹{constsData?.deliveryCharge} )} {deliveryCharge === 0 ? 'Free' : `₹${deliveryCharge}`} {/* Free Delivery Nudge */} {deliveryCharge > 0 && (constsData?.freeDeliveryThreshold || 0) > 0 && finalTotal < (constsData?.freeDeliveryThreshold || 0) && ( Add products worth ₹{((constsData?.freeDeliveryThreshold || 0) - finalTotal).toFixed(0)} for free delivery )} {/* Divider */} {/* Grand Total */} To Pay ₹{finalTotalWithDelivery} {/* Savings Banner */} {(discountAmount > 0 || deliveryCharge === 0) && ( You saved ₹{discountAmount + (deliveryCharge === 0 ? (constsData?.deliveryCharge || 0) : 0)} on this order )} {/* Action Buttons */} router.push(isFlashDelivery ? "/(drawer)/(tabs)/flash-delivery" : "/(drawer)/(tabs)/home")} > Shop More { const availableItems = cartItems .filter(item => { if (item.product?.isOutOfStock) return false; if (isFlashDelivery) { // Check if product supports flash delivery return flashEligibleProductIds.has(item.productId); } return selectedSlots[item.id]; // Regular delivery requires slot selection }) .map(item => item.id); if (availableItems.length === 0) { // Determine why no items are available const outOfStockItems = cartItems.filter(item => item.product?.isOutOfStock); const inStockItems = cartItems.filter(item => !item.product?.isOutOfStock); let errorTitle = "Cannot Proceed"; let errorMessage = ""; if (outOfStockItems.length === cartItems.length) { // All items are out of stock errorTitle = "Items Unavailable"; errorMessage = "All items in your cart are currently out of stock. Please remove them and add available items."; } else if (isFlashDelivery) { // Check if any items are flash-eligible const flashEligibleItems = inStockItems.filter(item => flashEligibleProductIds.has(item.productId) ); if (flashEligibleItems.length === 0) { errorTitle = "Flash Delivery Unavailable"; errorMessage = "None of the items in your cart are available for flash delivery. Please remove ineligible items or switch to regular delivery."; } else { errorTitle = "Some Items Not Available"; errorMessage = "Some items in your cart are not available for flash delivery. You can proceed with only the eligible items, or remove ineligible items."; } } else { // Regular delivery - check slot selection const itemsWithoutSlots = inStockItems.filter(item => !selectedSlots[item.id]); if (itemsWithoutSlots.length > 0) { errorTitle = "Delivery Slot Required"; errorMessage = `${itemsWithoutSlots.length} item(s) don't have a delivery slot. Please remove them or select a slot for each item.`; } else { errorTitle = "Cannot Proceed"; errorMessage = "Please check your cart items and try again."; } } Alert.alert(errorTitle, errorMessage); return; } // Check if there are items without slots (for regular delivery) if (!isFlashDelivery && availableItems.length < cartItems.length) { const itemsWithoutSlots = cartItems.filter(item => !selectedSlots[item.id] && !item.product?.isOutOfStock); if (itemsWithoutSlots.length > 0) { Alert.alert( "Delivery Slot Required", `${itemsWithoutSlots.length} item(s) don't have a delivery slot. Please select a slot or remove the item.` ); return; } } // Group items by selected slot const itemsBySlot: Record = {}; availableItems.forEach(itemId => { const slotId = isFlashDelivery ? 0 : selectedSlots[itemId]; if (!itemsBySlot[slotId]) { itemsBySlot[slotId] = []; } itemsBySlot[slotId].push(itemId); }); // Create checkout URL with slot groupings const slotParams = Object.entries(itemsBySlot) .map(([slotId, itemIds]) => `${slotId}:${itemIds.join(',')}`) .join(';'); router.push( `${isFlashDelivery ? '/(drawer)/(tabs)/flash-delivery/checkout' : '/(drawer)/(tabs)/home/checkout'}?slots=${encodeURIComponent(slotParams)}${selectedCouponId ? `&coupons=${selectedCouponId}` : ''}&deliveryPrice=${deliveryCharge}` as any ); }} > Checkout )} {/* Coupon Details Dialog */} setCouponDialogOpen(false)} > Applied Coupons {selectedCoupons.map((coupon) => ( {coupon.code} {coupon.description} {coupon.discountType === "percentage" ? `${coupon.discountValue}% OFF` : `₹${coupon.discountValue} OFF`} {coupon.maxValue && ( Maximum discount up to ₹{coupon.maxValue} )} ))} setCouponDialogOpen(false)} > Close ); }