enh
This commit is contained in:
parent
aa7035b77c
commit
f80f234b98
7 changed files with 142 additions and 11 deletions
File diff suppressed because one or more lines are too long
|
|
@ -114,6 +114,7 @@ export const commonRouter = router({
|
|||
productQuantity: product.productQuantity,
|
||||
storeId: product.store?.id || null,
|
||||
isOutOfStock: product.isOutOfStock,
|
||||
isFlashAvailable: product.isFlashAvailable,
|
||||
nextDeliveryDate: nextDeliveryDate ? nextDeliveryDate.toISOString() : null,
|
||||
images: product.images, // Already signed URLs from cache
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,59 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import { View, Alert } from 'react-native';
|
||||
import { FlashDeliveryProducts } from '@/components/SlotSpecificView';
|
||||
import { useSlotStore } from '@/components/stores/slotStore';
|
||||
import FlashDeliveryNote from '@/components/FlashDeliveryNote';
|
||||
import { useFlashNavigationStore } from '@/components/stores/flashNavigationStore';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import { useFlashCartStore } from '@/src/store/flashCartStore';
|
||||
import { tw, BottomDialog, MyText, MyTouchableOpacity } from 'common-ui';
|
||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||
import { useAddToCart } from '@/hooks/cart-query-hooks';
|
||||
|
||||
const FlashAddToCartDialog = () => {
|
||||
const { addedFlashProduct, clearAddedFlashProduct } = useFlashCartStore();
|
||||
|
||||
const addToCart = useAddToCart({
|
||||
showSuccessAlert: false,
|
||||
showErrorAlert: false,
|
||||
refetchCart: true,
|
||||
}, "flash");
|
||||
|
||||
const product = addedFlashProduct?.product;
|
||||
const isOpen = !!addedFlashProduct;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isOpen && product) {
|
||||
addToCart.mutate(
|
||||
{ productId: product.id, quantity: 1, slotId: 0 },
|
||||
{
|
||||
onSuccess: () => {
|
||||
clearAddedFlashProduct();
|
||||
setTimeout(() => {
|
||||
Alert.alert('Added to Cart', `Added ${product.name} for delivery in 1hr`);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [isOpen, product]);
|
||||
|
||||
if (!isOpen || !product) return null;
|
||||
|
||||
return null;
|
||||
return (
|
||||
<BottomDialog open={isOpen} onClose={clearAddedFlashProduct}>
|
||||
<View style={tw`p-6 items-center`}>
|
||||
<View style={tw`w-16 h-16 bg-pink-50 rounded-full items-center justify-center mb-4`}>
|
||||
<MaterialIcons name="bolt" size={32} color="#f81260" />
|
||||
</View>
|
||||
<MyText style={tw`text-xl font-bold text-gray-900 mb-2`}>1 hr Delivery</MyText>
|
||||
<MyText style={tw`text-gray-600 text-center mb-4`}>Adding {product.name}...</MyText>
|
||||
</View>
|
||||
</BottomDialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default function FlashDeliveryView() {
|
||||
const { storeId } = useLocalSearchParams<{ storeId?: string }>();
|
||||
|
|
@ -37,6 +86,7 @@ export default function FlashDeliveryView() {
|
|||
baseUrl="/(drawer)/(tabs)/flash-delivery"
|
||||
onProductPress={handleProductPress}
|
||||
/>
|
||||
<FlashAddToCartDialog />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,11 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
|||
Alert.alert("Error", "No available delivery slot for this product");
|
||||
return;
|
||||
}
|
||||
addToCart(item.id, 1, slotId, () => {});
|
||||
const slot = slotMap[slotId];
|
||||
const deliveryTime = slot ? dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A') : '';
|
||||
addToCart(item.id, 1, slotId, () => {
|
||||
Alert.alert('Added to Cart', `Added ${item.name} for delivery at ${deliveryTime}`);
|
||||
});
|
||||
} else if (cartItem) {
|
||||
updateCartItem.mutate({ itemId: cartItem.id, quantity: newQuantity });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -354,8 +354,14 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
|||
|
||||
const handleAddToCart = (productId: number) => {
|
||||
setIsLoadingDialogOpen(true);
|
||||
|
||||
addToCart(productId, 1, slotId || 0, () => setIsLoadingDialogOpen(false));
|
||||
const item = filteredProducts.find((p: any) => p.id === productId);
|
||||
const deliveryTime = slotQuery.data?.deliveryTime ? dayjs(slotQuery.data.deliveryTime).format('ddd, DD MMM • h:mm A') : '';
|
||||
addToCart(productId, 1, slotId || 0, () => {
|
||||
setIsLoadingDialogOpen(false);
|
||||
if (item) {
|
||||
Alert.alert('Added to Cart', `Added ${item.name} for delivery at ${deliveryTime}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (slotQuery.isLoading || (storeIdNum && productsQuery?.isLoading)) {
|
||||
|
|
@ -441,8 +447,13 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc
|
|||
|
||||
const handleAddToCart = (productId: number) => {
|
||||
setIsLoadingDialogOpen(true);
|
||||
|
||||
addToCart(productId, 1, 0, () => setIsLoadingDialogOpen(false));
|
||||
const item = flashProducts.find((p: any) => p.id === productId);
|
||||
addToCart(productId, 1, 0, () => {
|
||||
setIsLoadingDialogOpen(false);
|
||||
if (item) {
|
||||
Alert.alert('Added to Cart', `Added ${item.name} for delivery in 1hr`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (storeIdNum && productsQuery?.isLoading) {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,29 @@
|
|||
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 } from '@/hooks/cart-query-hooks';
|
||||
import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
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,
|
||||
|
|
@ -32,6 +42,7 @@ export default function AddToCartDialog() {
|
|||
|
||||
const product = addedToCartProduct?.product;
|
||||
|
||||
|
||||
// Pre-select cart's slotId and quantity if item is already in cart
|
||||
useEffect(() => {
|
||||
if (isOpen && product) {
|
||||
|
|
@ -81,7 +92,16 @@ export default function AddToCartDialog() {
|
|||
// 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 },
|
||||
|
|
@ -100,7 +120,7 @@ export default function AddToCartDialog() {
|
|||
|
||||
return (
|
||||
<BottomDialog open={isOpen} onClose={clearAddedToCartProduct}>
|
||||
<View style={tw`p-6 max-h-[500px]`}>
|
||||
<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" />
|
||||
|
|
@ -120,7 +140,10 @@ export default function AddToCartDialog() {
|
|||
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)}
|
||||
onPress={() => {
|
||||
setSelectedSlotId(slot.id);
|
||||
setSelectedFlashDelivery(false);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
||||
|
|
@ -138,6 +161,30 @@ export default function AddToCartDialog() {
|
|||
))}
|
||||
</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>
|
||||
<Quantifier
|
||||
|
|
@ -150,9 +197,9 @@ export default function AddToCartDialog() {
|
|||
|
||||
<View style={tw`flex-row gap-3 mt-4`}>
|
||||
<MyTouchableOpacity
|
||||
style={tw`flex-1 bg-brand500 py-3.5 rounded-xl items-center ${!selectedSlotId ? 'opacity-50' : ''}`}
|
||||
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}
|
||||
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')}
|
||||
|
|
|
|||
18
apps/user-ui/src/store/flashCartStore.ts
Normal file
18
apps/user-ui/src/store/flashCartStore.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { create } from 'zustand';
|
||||
|
||||
interface AddedFlashProduct {
|
||||
productId: number;
|
||||
product: any;
|
||||
}
|
||||
|
||||
interface FlashCartStore {
|
||||
addedFlashProduct: AddedFlashProduct | null;
|
||||
setAddedFlashProduct: (product: AddedFlashProduct | null) => void;
|
||||
clearAddedFlashProduct: () => void;
|
||||
}
|
||||
|
||||
export const useFlashCartStore = create<FlashCartStore>((set) => ({
|
||||
addedFlashProduct: null,
|
||||
setAddedFlashProduct: (product) => set({ addedFlashProduct: product }),
|
||||
clearAddedFlashProduct: () => set({ addedFlashProduct: null }),
|
||||
}));
|
||||
Loading…
Add table
Reference in a new issue