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,
|
productQuantity: product.productQuantity,
|
||||||
storeId: product.store?.id || null,
|
storeId: product.store?.id || null,
|
||||||
isOutOfStock: product.isOutOfStock,
|
isOutOfStock: product.isOutOfStock,
|
||||||
|
isFlashAvailable: product.isFlashAvailable,
|
||||||
nextDeliveryDate: nextDeliveryDate ? nextDeliveryDate.toISOString() : null,
|
nextDeliveryDate: nextDeliveryDate ? nextDeliveryDate.toISOString() : null,
|
||||||
images: product.images, // Already signed URLs from cache
|
images: product.images, // Already signed URLs from cache
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,59 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||||
|
import { View, Alert } from 'react-native';
|
||||||
import { FlashDeliveryProducts } from '@/components/SlotSpecificView';
|
import { FlashDeliveryProducts } from '@/components/SlotSpecificView';
|
||||||
import { useSlotStore } from '@/components/stores/slotStore';
|
import { useSlotStore } from '@/components/stores/slotStore';
|
||||||
import FlashDeliveryNote from '@/components/FlashDeliveryNote';
|
import FlashDeliveryNote from '@/components/FlashDeliveryNote';
|
||||||
import { useFlashNavigationStore } from '@/components/stores/flashNavigationStore';
|
import { useFlashNavigationStore } from '@/components/stores/flashNavigationStore';
|
||||||
import { useFocusEffect } from '@react-navigation/native';
|
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() {
|
export default function FlashDeliveryView() {
|
||||||
const { storeId } = useLocalSearchParams<{ storeId?: string }>();
|
const { storeId } = useLocalSearchParams<{ storeId?: string }>();
|
||||||
|
|
@ -37,6 +86,7 @@ export default function FlashDeliveryView() {
|
||||||
baseUrl="/(drawer)/(tabs)/flash-delivery"
|
baseUrl="/(drawer)/(tabs)/flash-delivery"
|
||||||
onProductPress={handleProductPress}
|
onProductPress={handleProductPress}
|
||||||
/>
|
/>
|
||||||
|
<FlashAddToCartDialog />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,11 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
Alert.alert("Error", "No available delivery slot for this product");
|
Alert.alert("Error", "No available delivery slot for this product");
|
||||||
return;
|
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) {
|
} else if (cartItem) {
|
||||||
updateCartItem.mutate({ itemId: cartItem.id, quantity: newQuantity });
|
updateCartItem.mutate({ itemId: cartItem.id, quantity: newQuantity });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -354,8 +354,14 @@ export function SlotProducts({ slotId:slotIdParent, storeId:storeIdParent, baseU
|
||||||
|
|
||||||
const handleAddToCart = (productId: number) => {
|
const handleAddToCart = (productId: number) => {
|
||||||
setIsLoadingDialogOpen(true);
|
setIsLoadingDialogOpen(true);
|
||||||
|
const item = filteredProducts.find((p: any) => p.id === productId);
|
||||||
addToCart(productId, 1, slotId || 0, () => setIsLoadingDialogOpen(false));
|
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)) {
|
if (slotQuery.isLoading || (storeIdNum && productsQuery?.isLoading)) {
|
||||||
|
|
@ -441,8 +447,13 @@ export function FlashDeliveryProducts({ storeId:storeIdParent, baseUrl, onProduc
|
||||||
|
|
||||||
const handleAddToCart = (productId: number) => {
|
const handleAddToCart = (productId: number) => {
|
||||||
setIsLoadingDialogOpen(true);
|
setIsLoadingDialogOpen(true);
|
||||||
|
const item = flashProducts.find((p: any) => p.id === productId);
|
||||||
addToCart(productId, 1, 0, () => setIsLoadingDialogOpen(false));
|
addToCart(productId, 1, 0, () => {
|
||||||
|
setIsLoadingDialogOpen(false);
|
||||||
|
if (item) {
|
||||||
|
Alert.alert('Added to Cart', `Added ${item.name} for delivery in 1hr`);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (storeIdNum && productsQuery?.isLoading) {
|
if (storeIdNum && productsQuery?.isLoading) {
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,29 @@
|
||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { View, ScrollView } from 'react-native';
|
import { View, ScrollView } from 'react-native';
|
||||||
|
import { useRouter } from 'expo-router';
|
||||||
import { tw, BottomDialog, MyText, MyTouchableOpacity, Quantifier } from 'common-ui';
|
import { tw, BottomDialog, MyText, MyTouchableOpacity, Quantifier } from 'common-ui';
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import { useCartStore } from '@/src/store/cartStore';
|
import { useCartStore } from '@/src/store/cartStore';
|
||||||
|
import { useFlashCartStore } from '@/src/store/flashCartStore';
|
||||||
import { trpc } from '@/src/trpc-client';
|
import { trpc } from '@/src/trpc-client';
|
||||||
import { useAddToCart, useGetCart, useUpdateCartItem } from '@/hooks/cart-query-hooks';
|
import { useAddToCart, useGetCart, useUpdateCartItem } from '@/hooks/cart-query-hooks';
|
||||||
|
import { useGetEssentialConsts } from '@/src/api-hooks/essential-consts.api';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
export default function AddToCartDialog() {
|
export default function AddToCartDialog() {
|
||||||
|
const router = useRouter();
|
||||||
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore();
|
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore();
|
||||||
const [quantity, setQuantity] = useState(1);
|
const [quantity, setQuantity] = useState(1);
|
||||||
const [selectedSlotId, setSelectedSlotId] = useState<number | null>(null);
|
const [selectedSlotId, setSelectedSlotId] = useState<number | null>(null);
|
||||||
|
const [selectedFlashDelivery, setSelectedFlashDelivery] = useState(false);
|
||||||
|
|
||||||
const { data: slotsData } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
const { data: slotsData } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
||||||
const { data: cartData } = useGetCart();
|
const { data: cartData } = useGetCart();
|
||||||
|
const { data: constsData } = useGetEssentialConsts();
|
||||||
|
// const isFlashDeliveryEnabled = constsData?.isFlashDeliveryEnabled === true;
|
||||||
|
const isFlashDeliveryEnabled = true;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const addToCart = useAddToCart({
|
const addToCart = useAddToCart({
|
||||||
showSuccessAlert: false,
|
showSuccessAlert: false,
|
||||||
|
|
@ -32,6 +42,7 @@ export default function AddToCartDialog() {
|
||||||
|
|
||||||
const product = addedToCartProduct?.product;
|
const product = addedToCartProduct?.product;
|
||||||
|
|
||||||
|
|
||||||
// Pre-select cart's slotId and quantity if item is already in cart
|
// Pre-select cart's slotId and quantity if item is already in cart
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isOpen && product) {
|
if (isOpen && product) {
|
||||||
|
|
@ -81,7 +92,16 @@ export default function AddToCartDialog() {
|
||||||
// Determine if updating existing item (quantity > 1 means it's an update)
|
// Determine if updating existing item (quantity > 1 means it's an update)
|
||||||
const isUpdate = (cartItem?.quantity || 0) > 1;
|
const isUpdate = (cartItem?.quantity || 0) > 1;
|
||||||
|
|
||||||
|
// Check if flash delivery option should be shown
|
||||||
|
const showFlashOption = product?.isFlashAvailable === true && isFlashDeliveryEnabled;
|
||||||
|
|
||||||
const handleAddToCart = () => {
|
const handleAddToCart = () => {
|
||||||
|
if (selectedFlashDelivery) {
|
||||||
|
useFlashCartStore.getState().setAddedFlashProduct({ productId: product.id, product });
|
||||||
|
router.push('/(drawer)/(tabs)/flash-delivery/(products)');
|
||||||
|
clearAddedToCartProduct();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isUpdate && cartItem?.id) {
|
if (isUpdate && cartItem?.id) {
|
||||||
updateCartItem.mutate(
|
updateCartItem.mutate(
|
||||||
{ itemId: cartItem.id, quantity },
|
{ itemId: cartItem.id, quantity },
|
||||||
|
|
@ -100,7 +120,7 @@ export default function AddToCartDialog() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BottomDialog open={isOpen} onClose={clearAddedToCartProduct}>
|
<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`flex-row items-center mb-2`}>
|
||||||
<View style={tw`w-10 h-10 bg-blue-50 rounded-full items-center justify-center mr-3`}>
|
<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" />
|
<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 ${
|
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'
|
selectedSlotId === slot.id ? 'border-brand500' : 'border-gray-100'
|
||||||
}`}
|
}`}
|
||||||
onPress={() => setSelectedSlotId(slot.id)}
|
onPress={() => {
|
||||||
|
setSelectedSlotId(slot.id);
|
||||||
|
setSelectedFlashDelivery(false);
|
||||||
|
}}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
||||||
|
|
@ -138,6 +161,30 @@ export default function AddToCartDialog() {
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</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`}>
|
<View style={tw`mt-4`}>
|
||||||
<MyText style={tw`text-sm font-bold text-gray-900 mb-2`}>Quantity</MyText>
|
<MyText style={tw`text-sm font-bold text-gray-900 mb-2`}>Quantity</MyText>
|
||||||
<Quantifier
|
<Quantifier
|
||||||
|
|
@ -150,9 +197,9 @@ export default function AddToCartDialog() {
|
||||||
|
|
||||||
<View style={tw`flex-row gap-3 mt-4`}>
|
<View style={tw`flex-row gap-3 mt-4`}>
|
||||||
<MyTouchableOpacity
|
<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}
|
onPress={handleAddToCart}
|
||||||
disabled={(addToCart.isLoading || updateCartItem.isLoading) || !selectedSlotId}
|
disabled={(addToCart.isLoading || updateCartItem.isLoading) || (!selectedSlotId && !selectedFlashDelivery)}
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-white font-bold`}>
|
<MyText style={tw`text-white font-bold`}>
|
||||||
{addToCart.isLoading || updateCartItem.isLoading ? (isUpdate ? 'Updating...' : 'Adding...') : (isUpdate ? 'Update Item' : 'Add to Cart')}
|
{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