diff --git a/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx b/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx index 6bfb0da..f07d5e0 100755 --- a/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx +++ b/apps/user-ui/app/(drawer)/(tabs)/home/index.tsx @@ -422,6 +422,7 @@ export default function Dashboard() { ) } showDeliveryInfo={false} + useAddToCartDialog={true} miniView={true} /> diff --git a/apps/user-ui/app/(drawer)/(tabs)/home/search-results/index.tsx b/apps/user-ui/app/(drawer)/(tabs)/home/search-results/index.tsx index 8bcb128..912c298 100644 --- a/apps/user-ui/app/(drawer)/(tabs)/home/search-results/index.tsx +++ b/apps/user-ui/app/(drawer)/(tabs)/home/search-results/index.tsx @@ -1,34 +1,21 @@ import React, { useState, useRef, useEffect } from "react"; -import { - View, - Dimensions, - Image, - Alert, - Platform, -} from "react-native"; +import { View, Dimensions } from "react-native"; import { useRouter, useLocalSearchParams } from "expo-router"; import { - theme, tw, useManualRefresh, useMarkDataFetchers, - LoadingDialog, - AppContainer, MyFlatList, MyText, - MyTextInput, - MyTouchableOpacity, - SearchBar } from "common-ui"; -import dayjs from "dayjs"; + SearchBar, +} from "common-ui"; import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import { trpc } from "@/src/trpc-client"; -import { useGetCart, useAddToCart } from '@/hooks/cart-query-hooks'; import ProductCard from "@/components/ProductCard"; import FloatingCartBar from "@/components/floating-cart-bar"; const { width: screenWidth } = Dimensions.get("window"); -const itemWidth = (screenWidth - 48) / 2; // 48 = padding horizontal (16*2) + gap (16) - +const itemWidth = (screenWidth - 48) / 2; export default function SearchResults() { const router = useRouter(); @@ -36,29 +23,20 @@ export default function SearchResults() { const query = (q as string) || ""; const [inputQuery, setInputQuery] = useState(query); const [searchQuery, setSearchQuery] = useState(query); - const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false); const searchInputRef = useRef(null); useEffect(() => { - // Auto-focus search bar on mount setTimeout(() => { searchInputRef.current?.focus(); }, 100); }, []); - const { - data: productsData, - isLoading, - error, - refetch, - } = trpc.common.product.getAllProductsSummary.useQuery({ - searchQuery: searchQuery || undefined, - }); - - const { data: cartData, refetch: refetchCart } = useGetCart(); + const { data: productsData, isLoading, error, refetch } = + trpc.common.product.getAllProductsSummary.useQuery({ + searchQuery: searchQuery || undefined, + }); const products = productsData?.products || []; - const addToCart = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }); useManualRefresh(() => { refetch(); @@ -68,43 +46,6 @@ export default function SearchResults() { refetch(); }); - const handleAddToCart = (productId: number) => { - setIsLoadingDialogOpen(true); - addToCart.mutate( - { productId, quantity: 1 }, - { - onSuccess: () => { - Alert.alert("Success", "Item added to cart!"); - refetchCart(); - }, - onError: (error: any) => { - Alert.alert("Error", error.message || "Failed to add item to cart"); - }, - onSettled: () => { - setIsLoadingDialogOpen(false); - }, - } - ); - }; - - const handleBuyNow = (productId: number) => { - setIsLoadingDialogOpen(true); - addToCart.mutate( - { productId, quantity: 1 }, - { - onSuccess: () => { - router.push(`/(drawer)/(tabs)/home/cart?select=${productId}`); - }, - onError: (error: any) => { - Alert.alert("Error", error.message || "Failed to add item to cart"); - }, - onSettled: () => { - setIsLoadingDialogOpen(false); - }, - } - ); - }; - const handleSearch = () => { setSearchQuery(inputQuery); }; @@ -136,16 +77,18 @@ export default function SearchResults() { router.push(`/(drawer)/(tabs)/home/product-detail/${item.id}`)} + onPress={() => + router.push(`/(drawer)/(tabs)/home/product-detail/${item.id}`) + } showDeliveryInfo={false} + useAddToCartDialog={true} /> )} keyExtractor={(item, index) => index.toString()} - columnWrapperStyle={{ gap: 16, justifyContent: 'center' }} + columnWrapperStyle={{ gap: 16, justifyContent: "center" }} contentContainerStyle={[tw`pb-24`, { gap: 16 }]} ListHeaderComponent={ - {/* Search Bar */} - - {/* Section Title */} - {searchQuery ? `Search Results for "${searchQuery}"` : 'All Products'} + {searchQuery + ? `Search Results for "${searchQuery}"` + : "All Products"} } /> - - - + - + ); -} \ No newline at end of file +} diff --git a/apps/user-ui/app/(drawer)/(tabs)/order-again/index.tsx b/apps/user-ui/app/(drawer)/(tabs)/order-again/index.tsx index e6637c1..7bd04bc 100644 --- a/apps/user-ui/app/(drawer)/(tabs)/order-again/index.tsx +++ b/apps/user-ui/app/(drawer)/(tabs)/order-again/index.tsx @@ -1,54 +1,31 @@ -import React, { useState } from "react"; -import { - View, - Dimensions, - Image, - Platform, - Alert, - ScrollView, -} from "react-native"; +import React from "react"; +import { View, Dimensions, ScrollView } from "react-native"; import { LinearGradient } from "expo-linear-gradient"; import { useRouter } from "expo-router"; import { - theme, tw, useManualRefresh, useMarkDataFetchers, - LoadingDialog, AppContainer, MyFlatList, MyText, } from "common-ui"; import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import ProductCard from "@/components/ProductCard"; - import { trpc } from "@/src/trpc-client"; -import { useGetCart, useAddToCart } from '@/hooks/cart-query-hooks'; -import { useProductSlotIdentifier } from '@/hooks/useProductSlotIdentifier'; import FloatingCartBar from "@/components/floating-cart-bar"; import TabLayoutWrapper from "@/components/TabLayoutWrapper"; const { width: screenWidth } = Dimensions.get("window"); -const itemWidth = (screenWidth - 48) / 2; // 2 items per row with padding +const itemWidth = (screenWidth - 48) / 2; export default function OrderAgain() { const router = useRouter(); - const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false); - const { - data: recentProductsData, - isLoading, - error, - refetch, - } = trpc.user.order.getRecentlyOrderedProducts.useQuery({ - limit: 20, - }); - - - - const { data: cartData, refetch: refetchCart } = useGetCart(); - const { addToCart = () => {} } = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }) || {}; - const { getQuickestSlot } = useProductSlotIdentifier(); + const { data: recentProductsData, isLoading, error, refetch } = + trpc.user.order.getRecentlyOrderedProducts.useQuery({ + limit: 20, + }); const recentProducts = recentProductsData?.products || []; @@ -60,37 +37,14 @@ export default function OrderAgain() { refetch(); }); - const handleAddToCart = (productId: number) => { - const slotId = getQuickestSlot(productId); - if (!slotId) { - Alert.alert("Error", "No available delivery slot for this product"); - return; - } - - setIsLoadingDialogOpen(true); - addToCart(productId, 1, slotId, () => setIsLoadingDialogOpen(false)); - }; - - const handleBuyNow = (productId: number) => { - const slotId = getQuickestSlot(productId); - if (!slotId) { - Alert.alert("Error", "No available delivery slot for this product"); - return; - } - - setIsLoadingDialogOpen(true); - addToCart(productId, 1, slotId, () => { - setIsLoadingDialogOpen(false); - router.push(`/(drawer)/(tabs)/home/cart?select=${productId}`); - }); - }; - if (isLoading) { return ( - Loading your recent orders... + + Loading your recent orders... + ); @@ -102,7 +56,9 @@ export default function OrderAgain() { Oops! - Failed to load recent orders + + Failed to load recent orders + ); @@ -112,7 +68,7 @@ export default function OrderAgain() { - + Order Again - + Reorder your favorite items quickly - {/* White Section */} - {/* Section Title */} - Recently Ordered + + Recently Ordered + {recentProducts.length === 0 ? ( @@ -152,22 +113,27 @@ export default function OrderAgain() { No recent orders - Items you've ordered recently will appear here + Items you've ordered recently will appear here ) : ( {recentProducts.map((item, index) => ( - + router.push(`/(drawer)/(tabs)/order-again/product-detail/${item.id}`)} + onPress={() => + router.push( + `/(drawer)/(tabs)/order-again/product-detail/${item.id}` + ) + } showDeliveryInfo={false} - // iconType="flash" + useAddToCartDialog={true} /> ))} @@ -177,10 +143,9 @@ export default function OrderAgain() { - ); -} \ No newline at end of file +} diff --git a/apps/user-ui/app/(drawer)/(tabs)/stores/store-detail/[id].tsx b/apps/user-ui/app/(drawer)/(tabs)/stores/store-detail/[id].tsx index 58f1ebf..bc8855a 100644 --- a/apps/user-ui/app/(drawer)/(tabs)/stores/store-detail/[id].tsx +++ b/apps/user-ui/app/(drawer)/(tabs)/stores/store-detail/[id].tsx @@ -1,76 +1,44 @@ -import React, { useState } from 'react'; +import React from "react"; +import { View, Dimensions } from "react-native"; +import { useRouter, useLocalSearchParams } from "expo-router"; import { - View, - Dimensions, - Image, - TouchableOpacity, - Alert, - Platform, -} from 'react-native'; -import { useRouter, useLocalSearchParams } from 'expo-router'; -import { theme, tw, useManualRefresh, MyFlatList, useDrawerTitle, useMarkDataFetchers, LoadingDialog, MyText, MyTouchableOpacity } from 'common-ui'; -import dayjs from 'dayjs'; -import MaterialIcons from '@expo/vector-icons/MaterialIcons'; -import FontAwesome5 from '@expo/vector-icons/FontAwesome5'; -import { trpc } from '@/src/trpc-client'; -import { useAddToCart } from '@/hooks/cart-query-hooks'; -import ProductCard from '@/components/ProductCard'; -import FloatingCartBar from '@/components/floating-cart-bar'; - -const { width: screenWidth } = Dimensions.get('window'); -const itemWidth = (screenWidth - 48) / 2; // 48 = padding horizontal (16*2) + gap (16) + theme, + tw, + useManualRefresh, + useMarkDataFetchers, + MyFlatList, + useDrawerTitle, + MyText, +} from "common-ui"; +import MaterialIcons from "@expo/vector-icons/MaterialIcons"; +import FontAwesome5 from "@expo/vector-icons/FontAwesome5"; +import { trpc } from "@/src/trpc-client"; +import ProductCard from "@/components/ProductCard"; +import FloatingCartBar from "@/components/floating-cart-bar"; +const { width: screenWidth } = Dimensions.get("window"); +const itemWidth = (screenWidth - 48) / 2; export default function StoreDetail() { const router = useRouter(); - // const { storeId } = useLocalSearchParams(); const { id: storeId } = useLocalSearchParams(); const storeIdNum = parseInt(storeId as string); - const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false); - const { data: storeData, isLoading, refetch, error } = trpc.user.stores.getStoreWithProducts.useQuery( - { storeId: storeIdNum }, - { enabled: !!storeIdNum } - ); - + const { data: storeData, isLoading, refetch, error } = + trpc.user.stores.getStoreWithProducts.useQuery( + { storeId: storeIdNum }, + { enabled: !!storeIdNum } + ); + + useManualRefresh(() => { + refetch(); + }); useMarkDataFetchers(() => { refetch(); }); - useDrawerTitle(storeData?.store?.name || 'Store', [storeData?.store?.name]); - - const addToCart = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true }); - - const handleAddToCart = (productId: number) => { - setIsLoadingDialogOpen(true); - addToCart.mutate({ productId, quantity: 1 }, { - onSuccess: () => { - Alert.alert('Success', 'Item added to cart!'); - }, - onError: (error: any) => { - Alert.alert('Error', error.message || 'Failed to add item to cart'); - }, - onSettled: () => { - setIsLoadingDialogOpen(false); - }, - }); - }; - - const handleBuyNow = (productId: number) => { - setIsLoadingDialogOpen(true); - addToCart.mutate({ productId, quantity: 1 }, { - onSuccess: () => { - router.push(`/(drawer)/(tabs)/home/cart?select=${productId}`); - }, - onError: (error: any) => { - Alert.alert('Error', error.message || 'Failed to add item to cart'); - }, - onSettled: () => { - setIsLoadingDialogOpen(false); - }, - }); - }; + useDrawerTitle(storeData?.store?.name || "Store", [storeData?.store?.name]); if (isLoading) { return ( @@ -85,51 +53,75 @@ export default function StoreDetail() { Oops! - Store not found or error loading + + Store not found or error loading + ); } - + return ( ( - router.push(`/(drawer)/(tabs)/stores/store-detail/product-detail/${item.id}`)} - showDeliveryInfo={false} - miniView={true} - /> + + router.push( + `/(drawer)/(tabs)/stores/store-detail/product-detail/${item.id}` + ) + } + showDeliveryInfo={false} + miniView={true} + useAddToCartDialog={true} + /> )} keyExtractor={(item, index) => index.toString()} columnWrapperStyle={{ gap: 16 }} contentContainerStyle={[tw`px-4 pb-24`, { gap: 16 }]} ListHeaderComponent={ - - - + + + - {storeData?.store?.name} + + {storeData?.store?.name} + {storeData?.store?.description && ( - {storeData?.store?.description} + + {storeData?.store?.description} + )} - Products from this Store + + Products from this Store + } /> - ); -} \ No newline at end of file +} diff --git a/apps/user-ui/app/_layout.tsx b/apps/user-ui/app/_layout.tsx index 23b8acb..e064c6a 100755 --- a/apps/user-ui/app/_layout.tsx +++ b/apps/user-ui/app/_layout.tsx @@ -25,6 +25,7 @@ import UpdateChecker from "@/components/UpdateChecker"; import { RefreshProvider } from "../../../packages/ui/src/lib/refresh-context"; import WebViewWrapper from "@/components/WebViewWrapper"; import BackHandlerWrapper from "@/components/BackHandler"; +import AddToCartDialog from "@/src/components/AddToCartDialog"; import React from "react"; export default function RootLayout() { @@ -61,6 +62,7 @@ export default function RootLayout() { + diff --git a/apps/user-ui/components/QuickDeliveryAddressSelector.tsx b/apps/user-ui/components/QuickDeliveryAddressSelector.tsx index e407084..dc28eeb 100644 --- a/apps/user-ui/components/QuickDeliveryAddressSelector.tsx +++ b/apps/user-ui/components/QuickDeliveryAddressSelector.tsx @@ -121,174 +121,157 @@ const QuickDeliveryAddressSelector: React.FC )} {!isForFlashDelivery && ( - - - router.back()} - style={tw`p-2 -ml-2`} - activeOpacity={0.7} - > - - - - Delivery At {getCurrentSlotDisplay()} - - - {/* Trigger Component with Separate Chevrons */} - - - {/* Regular Delivery Time Section */} + <> + setDialogOpen(true)} - style={tw`flex-row items-center justify-between mb-2`} + onPress={() => router.back()} + style={tw`p-1`} activeOpacity={0.7} > - - {/* - Delivery Time - */} - - Delivery at: {getCurrentSlotDisplay()} - - - + + + setDialogOpen(true)} + style={tw`flex-row items-center bg-brand50 border border-brand100 rounded-lg px-3 py-2`} + activeOpacity={0.7} + > + + Delivery Time + + {getCurrentSlotDisplay()} + + + + - {/* Address Section */} - setDialogOpen(true)} - style={tw`flex-row items-center justify-between`} - activeOpacity={0.7} - > - - {/* - Delivery Address - */} - - TO: {getCurrentAddressDisplay()} - - - - - - - {/* Consolidated Dialog - 80% height */} - setDialogOpen(false)} - > - - - Select Delivery Options - - - - {/* Section 1: Delivery Time Selection */} - - - Select Delivery Time - - - - {isForFlashDelivery ? ( - - - - - 1 Hr Delivery - within 1 hour - - - - Selected automatically for flash delivery - - - ) : ( - slotOptions.map(slot => ( - { - onSlotChange?.(slot.id); - setDialogOpen(false); - }} - activeOpacity={0.7} - > - - Delivery: {slot.deliveryTime} - - - Orders Close at: {slot.closeTime} - - - )) - )} - - - - {/* Divider */} - - - {/* Section 2: Address Selection */} - - - Select Delivery Address - - - - {!isAuthenticated ? ( - - - - - Authentication Required - - - - Please log in to select and manage delivery addresses. - - - ) : addressOptions.length === 0 ? ( - - - No delivery addresses available. Please add an address first. - - - ) : ( - addressOptions.map(address => ( - { - setSelectedAddressId(address.id); - setDialogOpen(false); - }} - activeOpacity={0.7} - > - - {address.name} - - - {address.address} - - - Phone: {address.phone} - - - )) - )} - - + {/* Address dropdown - temporarily hidden + setDialogOpen(true)} + style={tw`flex-row items-center bg-brand50 border border-brand100 rounded-lg px-3 py-2`} + activeOpacity={0.7} + > + + Address + + {getCurrentAddressDisplay()} + + + + + */} - - - )} + setDialogOpen(false)} + > + + + Select Delivery Time + + + + {isForFlashDelivery ? ( + + + + + 1 Hr Delivery - within 1 hour + + + + Selected automatically for flash delivery + + + ) : ( + slotOptions.map(slot => ( + { + onSlotChange?.(slot.id); + setDialogOpen(false); + }} + activeOpacity={0.7} + > + + Delivery: {slot.deliveryTime} + + + Orders Close at: {slot.closeTime} + + + )) + )} + + + + + {/* Address section - temporarily hidden + setAddressDialogOpen(false)} + > + + + Select Delivery Address + + + + {!isAuthenticated ? ( + + + + + Authentication Required + + + + Please log in to select and manage delivery addresses. + + + ) : addressOptions.length === 0 ? ( + + + No delivery addresses available. Please add an address first. + + + ) : ( + addressOptions.map(address => ( + { + setSelectedAddressId(address.id); + setAddressDialogOpen(false); + }} + activeOpacity={0.7} + > + + {address.name} + + + {address.address} + + + Phone: {address.phone} + + + )) + )} + + + + */} + + )} ); }; diff --git a/apps/user-ui/components/floating-cart-bar.tsx b/apps/user-ui/components/floating-cart-bar.tsx index db5b60c..fa76b2a 100644 --- a/apps/user-ui/components/floating-cart-bar.tsx +++ b/apps/user-ui/components/floating-cart-bar.tsx @@ -162,8 +162,8 @@ useEffect(() => { <> ₹{totalCartValue} - {` • ${itemCount} ${itemCount === 1 ? "Item" : "Items"}`} + )} diff --git a/apps/user-ui/src/components/AddToCartDialog.tsx b/apps/user-ui/src/components/AddToCartDialog.tsx index 9ed7d60..e5ff411 100644 --- a/apps/user-ui/src/components/AddToCartDialog.tsx +++ b/apps/user-ui/src/components/AddToCartDialog.tsx @@ -4,7 +4,7 @@ import { tw, BottomDialog, MyText, MyTouchableOpacity, Quantifier } from 'common import MaterialIcons from '@expo/vector-icons/MaterialIcons'; import { useCartStore } from '@/src/store/cartStore'; import { trpc } from '@/src/trpc-client'; -import { useAddToCart, useGetCart } from '@/hooks/cart-query-hooks'; +import { useAddToCart, useGetCart, useUpdateCartItem } from '@/hooks/cart-query-hooks'; import dayjs from 'dayjs'; export default function AddToCartDialog() { @@ -21,15 +21,26 @@ export default function AddToCartDialog() { refetchCart: true, }); + const updateCartItem = useUpdateCartItem({ + showSuccessAlert: false, + showErrorAlert: false, + refetchCart: true, + }); + const isOpen = !!addedToCartProduct; const product = addedToCartProduct?.product; - // Pre-select cart's slotId if item is already in cart + // 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 { @@ -64,13 +75,25 @@ export default function AddToCartDialog() { .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; + const handleAddToCart = () => { - const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0; - - addToCart.mutate( - { productId: product.id, quantity, slotId }, - { onSuccess: () => clearAddedToCartProduct() } - ); + 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; @@ -78,11 +101,16 @@ export default function AddToCartDialog() { return ( - + - Select Delivery Slot + + Select Delivery Slot + {product?.name && ( + {product.name} + )} + @@ -124,10 +152,10 @@ export default function AddToCartDialog() { - {addToCart.isLoading ? 'Adding...' : 'Add to Cart'} + {addToCart.isLoading || updateCartItem.isLoading ? (isUpdate ? 'Updating...' : 'Adding...') : (isUpdate ? 'Update Item' : 'Add to Cart')}