enh
This commit is contained in:
parent
02cb713dd1
commit
aa7035b77c
8 changed files with 310 additions and 398 deletions
|
|
@ -422,6 +422,7 @@ export default function Dashboard() {
|
|||
)
|
||||
}
|
||||
showDeliveryInfo={false}
|
||||
useAddToCartDialog={true}
|
||||
miniView={true}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -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<any>(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() {
|
|||
<ProductCard
|
||||
item={item}
|
||||
itemWidth={itemWidth}
|
||||
onPress={() => 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={
|
||||
<View style={tw`pt-4 pb-2 px-4`}>
|
||||
{/* Search Bar */}
|
||||
<SearchBar
|
||||
ref={searchInputRef}
|
||||
value={inputQuery}
|
||||
|
|
@ -153,21 +96,19 @@ export default function SearchResults() {
|
|||
onSubmitEditing={handleSearch}
|
||||
returnKeyType="search"
|
||||
/>
|
||||
|
||||
{/* Section Title */}
|
||||
<View style={tw`flex-row justify-between items-center mb-2`}>
|
||||
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
||||
{searchQuery ? `Search Results for "${searchQuery}"` : 'All Products'}
|
||||
{searchQuery
|
||||
? `Search Results for "${searchQuery}"`
|
||||
: "All Products"}
|
||||
</MyText>
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
|
||||
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
||||
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
||||
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
||||
<FloatingCartBar />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<AppContainer>
|
||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||
<MaterialIcons name="refresh" size={48} color="#3B82F6" />
|
||||
<MyText style={tw`text-gray-500 font-medium mt-4`}>Loading your recent orders...</MyText>
|
||||
<MyText style={tw`text-gray-500 font-medium mt-4`}>
|
||||
Loading your recent orders...
|
||||
</MyText>
|
||||
</View>
|
||||
</AppContainer>
|
||||
);
|
||||
|
|
@ -102,7 +56,9 @@ export default function OrderAgain() {
|
|||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||
<MaterialIcons name="error-outline" size={48} color="#EF4444" />
|
||||
<MyText style={tw`text-gray-900 text-lg font-bold mt-4`}>Oops!</MyText>
|
||||
<MyText style={tw`text-gray-500 mt-2`}>Failed to load recent orders</MyText>
|
||||
<MyText style={tw`text-gray-500 mt-2`}>
|
||||
Failed to load recent orders
|
||||
</MyText>
|
||||
</View>
|
||||
</AppContainer>
|
||||
);
|
||||
|
|
@ -112,7 +68,7 @@ export default function OrderAgain() {
|
|||
<TabLayoutWrapper>
|
||||
<ScrollView style={tw`flex-1`} contentContainerStyle={tw`pb-20`}>
|
||||
<LinearGradient
|
||||
colors={['#194185', '#1570EF']} // brand900 to brand600
|
||||
colors={["#194185", "#1570EF"]}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={tw`pb-8 pt-6 px-5 rounded-b-[32px] shadow-lg mb-4`}
|
||||
|
|
@ -123,22 +79,27 @@ export default function OrderAgain() {
|
|||
<View style={tw`bg-white/20 p-1 rounded-full mr-2`}>
|
||||
<MaterialIcons name="refresh" size={14} color="#FFF" />
|
||||
</View>
|
||||
<MyText style={tw`text-brand100 text-xs font-bold uppercase tracking-widest`}>
|
||||
<MyText
|
||||
style={tw`text-brand100 text-xs font-bold uppercase tracking-widest`}
|
||||
>
|
||||
Order Again
|
||||
</MyText>
|
||||
</View>
|
||||
<MyText style={tw`text-white text-sm font-medium opacity-90 ml-1`} numberOfLines={1}>
|
||||
<MyText
|
||||
style={tw`text-white text-sm font-medium opacity-90 ml-1`}
|
||||
numberOfLines={1}
|
||||
>
|
||||
Reorder your favorite items quickly
|
||||
</MyText>
|
||||
</View>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
{/* White Section */}
|
||||
<View style={tw`bg-white`}>
|
||||
{/* Section Title */}
|
||||
<View style={tw`flex-row items-center mb-2 px-4 pt-4`}>
|
||||
<MyText style={tw`text-lg font-bold text-gray-900`}>Recently Ordered</MyText>
|
||||
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
||||
Recently Ordered
|
||||
</MyText>
|
||||
</View>
|
||||
|
||||
{recentProducts.length === 0 ? (
|
||||
|
|
@ -152,22 +113,27 @@ export default function OrderAgain() {
|
|||
No recent orders
|
||||
</MyText>
|
||||
<MyText style={tw`text-gray-500 mt-2 text-center px-8`}>
|
||||
Items you've ordered recently will appear here
|
||||
Items you've ordered recently will appear here
|
||||
</MyText>
|
||||
</View>
|
||||
) : (
|
||||
<View style={tw`px-4 pb-4`}>
|
||||
<View style={tw`flex-row flex-wrap justify-between`}>
|
||||
{recentProducts.map((item, index) => (
|
||||
<View key={item.id} style={tw`mb-4 ${index % 2 === 0 ? 'mr-2' : ''}`}>
|
||||
<View
|
||||
key={item.id}
|
||||
style={tw`mb-4 ${index % 2 === 0 ? "mr-2" : ""}`}
|
||||
>
|
||||
<ProductCard
|
||||
item={item}
|
||||
// handleAddToCart={handleAddToCart}
|
||||
// handleBuyNow={handleBuyNow}
|
||||
itemWidth={itemWidth}
|
||||
onPress={() => 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}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
|
|
@ -177,7 +143,6 @@ export default function OrderAgain() {
|
|||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
||||
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
||||
<FloatingCartBar />
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -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,7 +53,9 @@ export default function StoreDetail() {
|
|||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||
<MaterialIcons name="error-outline" size={48} color="#EF4444" />
|
||||
<MyText style={tw`text-gray-900 text-lg font-bold mt-4`}>Oops!</MyText>
|
||||
<MyText style={tw`text-gray-500 mt-2`}>Store not found or error loading</MyText>
|
||||
<MyText style={tw`text-gray-500 mt-2`}>
|
||||
Store not found or error loading
|
||||
</MyText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -96,36 +66,58 @@ export default function StoreDetail() {
|
|||
data={storeData?.products || []}
|
||||
numColumns={2}
|
||||
renderItem={({ item }) => (
|
||||
<ProductCard
|
||||
item={item}
|
||||
itemWidth={itemWidth}
|
||||
onPress={() => router.push(`/(drawer)/(tabs)/stores/store-detail/product-detail/${item.id}`)}
|
||||
showDeliveryInfo={false}
|
||||
miniView={true}
|
||||
/>
|
||||
<ProductCard
|
||||
item={item}
|
||||
itemWidth={itemWidth}
|
||||
onPress={() =>
|
||||
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={
|
||||
<View style={tw`pt-4 pb-6`}>
|
||||
<View style={tw`bg-white p-6 rounded-2xl shadow-sm border border-gray-100 items-center`}>
|
||||
<View style={tw`w-16 h-16 bg-pink-50 rounded-full items-center justify-center mb-4`}>
|
||||
<FontAwesome5 name="store" size={28} color={theme.colors.brand500} />
|
||||
<View
|
||||
style={tw`bg-white p-6 rounded-2xl shadow-sm border border-gray-100 items-center`}
|
||||
>
|
||||
<View
|
||||
style={tw`w-16 h-16 bg-pink-50 rounded-full items-center justify-center mb-4`}
|
||||
>
|
||||
<FontAwesome5
|
||||
name="store"
|
||||
size={28}
|
||||
color={theme.colors.brand500}
|
||||
/>
|
||||
</View>
|
||||
<MyText style={tw`text-2xl font-bold text-gray-900 text-center mb-2`}>{storeData?.store?.name}</MyText>
|
||||
<MyText
|
||||
style={tw`text-2xl font-bold text-gray-900 text-center mb-2`}
|
||||
>
|
||||
{storeData?.store?.name}
|
||||
</MyText>
|
||||
{storeData?.store?.description && (
|
||||
<MyText style={tw`text-gray-500 text-center leading-5 px-4`}>{storeData?.store?.description}</MyText>
|
||||
<MyText
|
||||
style={tw`text-gray-500 text-center leading-5 px-4`}
|
||||
>
|
||||
{storeData?.store?.description}
|
||||
</MyText>
|
||||
)}
|
||||
</View>
|
||||
<View style={tw`flex-row items-center mt-6 mb-2`}>
|
||||
<MaterialIcons name="grid-view" size={20} color="#374151" />
|
||||
<MyText style={tw`text-lg font-bold text-gray-900 ml-2`}>Products from this Store</MyText>
|
||||
<MyText style={tw`text-lg font-bold text-gray-900 ml-2`}>
|
||||
Products from this Store
|
||||
</MyText>
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
<LoadingDialog open={isLoadingDialogOpen} message="Processing..." />
|
||||
|
||||
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
||||
<FloatingCartBar />
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<RefreshProvider queryClient={queryClient}>
|
||||
<BackHandlerWrapper />
|
||||
<Stack screenOptions={{ headerShown: false }} />
|
||||
<AddToCartDialog />
|
||||
</RefreshProvider>
|
||||
</LocationTestWrapper>
|
||||
</PaperProvider>
|
||||
|
|
|
|||
|
|
@ -121,174 +121,157 @@ const QuickDeliveryAddressSelector: React.FC<QuickDeliveryAddressSelectorProps>
|
|||
)}
|
||||
|
||||
{!isForFlashDelivery && (
|
||||
<View style={tw`flex-1 mr-2`}>
|
||||
<View style={tw`flex-row items-center mb-1`}>
|
||||
<MyTouchableOpacity
|
||||
onPress={() => router.back()}
|
||||
style={tw`p-2 -ml-2`}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MaterialIcons name="chevron-left" size={24} />
|
||||
</MyTouchableOpacity>
|
||||
<MyText style={[tw`text-lg font-bold ml-2`]}>
|
||||
Delivery At {getCurrentSlotDisplay()}
|
||||
</MyText>
|
||||
</View>
|
||||
{/* Trigger Component with Separate Chevrons */}
|
||||
<View style={tw`bg-brand50 border border-brand100 rounded-lg p-3`}>
|
||||
|
||||
{/* Regular Delivery Time Section */}
|
||||
<>
|
||||
<View style={tw`flex-1 flex-row items-center gap-2`}>
|
||||
<MyTouchableOpacity
|
||||
onPress={() => setDialogOpen(true)}
|
||||
style={tw`flex-row items-center justify-between mb-2`}
|
||||
onPress={() => router.back()}
|
||||
style={tw`p-1`}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={tw`flex-1`}>
|
||||
{/* <MyText style={tw`text-xs text-brand500 font-bold uppercase tracking-wider leading-3`}>
|
||||
Delivery Time
|
||||
</MyText> */}
|
||||
<MyText style={tw`text-sm font-bold text-brand900 leading-4`}>
|
||||
Delivery at: {getCurrentSlotDisplay()}
|
||||
</MyText>
|
||||
</View>
|
||||
<MaterialIcons name="keyboard-arrow-down" size={20} color={theme.colors.brand500} />
|
||||
<MaterialIcons name="chevron-left" size={24} />
|
||||
</MyTouchableOpacity>
|
||||
|
||||
<View style={tw`flex-1 gap-1`}>
|
||||
<MyTouchableOpacity
|
||||
onPress={() => setDialogOpen(true)}
|
||||
style={tw`flex-row items-center bg-brand50 border border-brand100 rounded-lg px-3 py-2`}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={tw`flex-1`}>
|
||||
<MyText style={tw`text-xs text-brand500 font-bold uppercase`}>Delivery Time</MyText>
|
||||
<MyText style={tw`text-sm font-bold text-brand900`}>
|
||||
{getCurrentSlotDisplay()}
|
||||
</MyText>
|
||||
</View>
|
||||
<MaterialIcons name="keyboard-arrow-down" size={18} color={theme.colors.brand500} />
|
||||
</MyTouchableOpacity>
|
||||
|
||||
{/* Address Section */}
|
||||
<MyTouchableOpacity
|
||||
onPress={() => setDialogOpen(true)}
|
||||
style={tw`flex-row items-center justify-between`}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={tw`flex-1`}>
|
||||
{/* <MyText style={tw`text-xs text-brand500 font-bold uppercase tracking-wider leading-3`}>
|
||||
Delivery Address
|
||||
</MyText> */}
|
||||
<MyText style={tw`text-sm font-bold text-brand900 leading-4`} numberOfLines={1}>
|
||||
TO: {getCurrentAddressDisplay()}
|
||||
</MyText>
|
||||
</View>
|
||||
<MaterialIcons name="keyboard-arrow-down" size={20} color={theme.colors.brand500} />
|
||||
</MyTouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* Consolidated Dialog - 80% height */}
|
||||
<BottomDialog
|
||||
open={dialogOpen}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
>
|
||||
<View style={[tw`py-6`, { height: Dimensions.get('window').height * 0.8 }]}>
|
||||
<MyText style={tw`text-xl font-bold text-gray-900 mb-6 text-center`}>
|
||||
Select Delivery Options
|
||||
</MyText>
|
||||
|
||||
<View style={tw`flex-1`}>
|
||||
{/* Section 1: Delivery Time Selection */}
|
||||
<View style={tw`mb-6`}>
|
||||
<MyText style={tw`text-lg font-bold text-gray-900 mb-3`}>
|
||||
Select Delivery Time
|
||||
</MyText>
|
||||
|
||||
<ScrollView style={tw`max-h-40`} showsVerticalScrollIndicator={false}>
|
||||
{isForFlashDelivery ? (
|
||||
<View style={tw`p-3 bg-green-50 border border-green-200 rounded-lg mb-2`}>
|
||||
<View style={tw`flex-row items-center`}>
|
||||
<MaterialIcons name="bolt" size={20} color="#16a34a" />
|
||||
<MyText style={tw`text-green-800 font-medium ml-2`}>
|
||||
1 Hr Delivery - within 1 hour
|
||||
</MyText>
|
||||
</View>
|
||||
<MyText style={tw`text-green-700 text-xs mt-1`}>
|
||||
Selected automatically for flash delivery
|
||||
</MyText>
|
||||
</View>
|
||||
) : (
|
||||
slotOptions.map(slot => (
|
||||
<MyTouchableOpacity
|
||||
key={slot.id}
|
||||
style={tw`p-3 border border-gray-200 rounded-lg mb-2 ${
|
||||
slot.id === (slotId || earliestSlot?.id) ? 'bg-brand50 border-brand500' : 'bg-white'
|
||||
}`}
|
||||
onPress={() => {
|
||||
onSlotChange?.(slot.id);
|
||||
setDialogOpen(false);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MyText style={tw`font-medium text-gray-900`}>
|
||||
Delivery: {slot.deliveryTime}
|
||||
</MyText>
|
||||
<MyText style={tw`text-xs text-gray-500 mt-1`}>
|
||||
Orders Close at: {slot.closeTime}
|
||||
</MyText>
|
||||
</MyTouchableOpacity>
|
||||
))
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{/* Divider */}
|
||||
<View style={tw`h-px bg-gray-200 mb-6`} />
|
||||
|
||||
{/* Section 2: Address Selection */}
|
||||
<View style={tw`flex-1`}>
|
||||
<MyText style={tw`text-lg font-bold text-gray-900 mb-3`}>
|
||||
Select Delivery Address
|
||||
</MyText>
|
||||
|
||||
<ScrollView style={tw`flex-1`} showsVerticalScrollIndicator={false}>
|
||||
{!isAuthenticated ? (
|
||||
<View style={tw`p-4 bg-red-50 border border-red-200 rounded-lg`}>
|
||||
<View style={tw`flex-row items-center`}>
|
||||
<MaterialIcons name="error" size={20} color="#DC2626" />
|
||||
<MyText style={tw`text-red-800 font-medium ml-2`}>
|
||||
Authentication Required
|
||||
</MyText>
|
||||
</View>
|
||||
<MyText style={tw`text-red-700 text-sm mt-2`}>
|
||||
Please log in to select and manage delivery addresses.
|
||||
</MyText>
|
||||
</View>
|
||||
) : addressOptions.length === 0 ? (
|
||||
<View style={tw`p-4 bg-gray-50 border border-gray-200 rounded-lg`}>
|
||||
<MyText style={tw`text-gray-600 text-center`}>
|
||||
No delivery addresses available. Please add an address first.
|
||||
</MyText>
|
||||
</View>
|
||||
) : (
|
||||
addressOptions.map(address => (
|
||||
<MyTouchableOpacity
|
||||
key={address.id}
|
||||
style={tw`p-3 border border-gray-200 rounded-lg mb-2 ${
|
||||
address.id === (selectedAddressId || defaultAddress?.id) ? 'bg-brand50 border-brand500' : 'bg-white'
|
||||
}`}
|
||||
onPress={() => {
|
||||
setSelectedAddressId(address.id);
|
||||
setDialogOpen(false);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MyText style={tw`font-medium text-gray-900`}>
|
||||
{address.name}
|
||||
</MyText>
|
||||
<MyText style={tw`text-sm text-gray-600 mt-1`} numberOfLines={2}>
|
||||
{address.address}
|
||||
</MyText>
|
||||
<MyText style={tw`text-xs text-gray-500 mt-1`}>
|
||||
Phone: {address.phone}
|
||||
</MyText>
|
||||
</MyTouchableOpacity>
|
||||
))
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
{/* Address dropdown - temporarily hidden
|
||||
<MyTouchableOpacity
|
||||
onPress={() => setDialogOpen(true)}
|
||||
style={tw`flex-row items-center bg-brand50 border border-brand100 rounded-lg px-3 py-2`}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View style={tw`flex-1`}>
|
||||
<MyText style={tw`text-xs text-brand500 font-bold uppercase`}>Address</MyText>
|
||||
<MyText style={tw`text-sm font-bold text-brand900`} numberOfLines={1}>
|
||||
{getCurrentAddressDisplay()}
|
||||
</MyText>
|
||||
</View>
|
||||
<MaterialIcons name="keyboard-arrow-down" size={18} color={theme.colors.brand500} />
|
||||
</MyTouchableOpacity>
|
||||
*/}
|
||||
</View>
|
||||
</View>
|
||||
</BottomDialog>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<BottomDialog
|
||||
open={dialogOpen}
|
||||
onClose={() => setDialogOpen(false)}
|
||||
>
|
||||
<View style={[tw`py-6`, { height: Dimensions.get('window').height * 0.5 }]}>
|
||||
<MyText style={tw`text-xl font-bold text-gray-900 mb-4 text-center`}>
|
||||
Select Delivery Time
|
||||
</MyText>
|
||||
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{isForFlashDelivery ? (
|
||||
<View style={tw`p-3 bg-green-50 border border-green-200 rounded-lg mb-2`}>
|
||||
<View style={tw`flex-row items-center`}>
|
||||
<MaterialIcons name="bolt" size={20} color="#16a34a" />
|
||||
<MyText style={tw`text-green-800 font-medium ml-2`}>
|
||||
1 Hr Delivery - within 1 hour
|
||||
</MyText>
|
||||
</View>
|
||||
<MyText style={tw`text-green-700 text-xs mt-1`}>
|
||||
Selected automatically for flash delivery
|
||||
</MyText>
|
||||
</View>
|
||||
) : (
|
||||
slotOptions.map(slot => (
|
||||
<MyTouchableOpacity
|
||||
key={slot.id}
|
||||
style={tw`p-3 border border-gray-200 rounded-lg mb-2 ${
|
||||
slot.id === (slotId || earliestSlot?.id) ? 'bg-brand50 border-brand500' : 'bg-white'
|
||||
}`}
|
||||
onPress={() => {
|
||||
onSlotChange?.(slot.id);
|
||||
setDialogOpen(false);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MyText style={tw`font-medium text-gray-900`}>
|
||||
Delivery: {slot.deliveryTime}
|
||||
</MyText>
|
||||
<MyText style={tw`text-xs text-gray-500 mt-1`}>
|
||||
Orders Close at: {slot.closeTime}
|
||||
</MyText>
|
||||
</MyTouchableOpacity>
|
||||
))
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</BottomDialog>
|
||||
|
||||
{/* Address section - temporarily hidden
|
||||
<BottomDialog
|
||||
open={addressDialogOpen}
|
||||
onClose={() => setAddressDialogOpen(false)}
|
||||
>
|
||||
<View style={[tw`py-6`, { height: Dimensions.get('window').height * 0.5 }]}>
|
||||
<MyText style={tw`text-xl font-bold text-gray-900 mb-4 text-center`}>
|
||||
Select Delivery Address
|
||||
</MyText>
|
||||
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{!isAuthenticated ? (
|
||||
<View style={tw`p-4 bg-red-50 border border-red-200 rounded-lg`}>
|
||||
<View style={tw`flex-row items-center`}>
|
||||
<MaterialIcons name="error" size={20} color="#DC2626" />
|
||||
<MyText style={tw`text-red-800 font-medium ml-2`}>
|
||||
Authentication Required
|
||||
</MyText>
|
||||
</View>
|
||||
<MyText style={tw`text-red-700 text-sm mt-2`}>
|
||||
Please log in to select and manage delivery addresses.
|
||||
</MyText>
|
||||
</View>
|
||||
) : addressOptions.length === 0 ? (
|
||||
<View style={tw`p-4 bg-gray-50 border border-gray-200 rounded-lg`}>
|
||||
<MyText style={tw`text-gray-600 text-center`}>
|
||||
No delivery addresses available. Please add an address first.
|
||||
</MyText>
|
||||
</View>
|
||||
) : (
|
||||
addressOptions.map(address => (
|
||||
<MyTouchableOpacity
|
||||
key={address.id}
|
||||
style={tw`p-3 border border-gray-200 rounded-lg mb-2 ${
|
||||
address.id === (selectedAddressId || defaultAddress?.id) ? 'bg-brand50 border-brand500' : 'bg-white'
|
||||
}`}
|
||||
onPress={() => {
|
||||
setSelectedAddressId(address.id);
|
||||
setAddressDialogOpen(false);
|
||||
}}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<MyText style={tw`font-medium text-gray-900`}>
|
||||
{address.name}
|
||||
</MyText>
|
||||
<MyText style={tw`text-sm text-gray-600 mt-1`} numberOfLines={2}>
|
||||
{address.address}
|
||||
</MyText>
|
||||
<MyText style={tw`text-xs text-gray-500 mt-1`}>
|
||||
Phone: {address.phone}
|
||||
</MyText>
|
||||
</MyTouchableOpacity>
|
||||
))
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</BottomDialog>
|
||||
*/}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -162,8 +162,8 @@ useEffect(() => {
|
|||
<>
|
||||
<MyText style={tw`text-white font-black text-base`}>
|
||||
₹{totalCartValue}
|
||||
</MyText>
|
||||
{` • ${itemCount} ${itemCount === 1 ? "Item" : "Items"}`}
|
||||
</MyText>
|
||||
</>
|
||||
)}
|
||||
</MyText>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
const handleAddToCart = () => {
|
||||
const slotId = selectedSlotId ?? availableSlotIds[0] ?? 0;
|
||||
// Find cart item for this product
|
||||
const cartItem = cartData?.items?.find((item: any) => item.productId === product?.id);
|
||||
|
||||
addToCart.mutate(
|
||||
{ productId: product.id, quantity, slotId },
|
||||
{ onSuccess: () => clearAddedToCartProduct() }
|
||||
);
|
||||
// Determine if updating existing item (quantity > 1 means it's an update)
|
||||
const isUpdate = (cartItem?.quantity || 0) > 1;
|
||||
|
||||
const handleAddToCart = () => {
|
||||
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 (
|
||||
<BottomDialog open={isOpen} onClose={clearAddedToCartProduct}>
|
||||
<View style={tw`p-6 max-h-[500px]`}>
|
||||
<View style={tw`flex-row items-center mb-6`}>
|
||||
<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>
|
||||
<MyText style={tw`text-xl font-bold text-gray-900`}>Select Delivery Slot</MyText>
|
||||
<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}</MyText>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
|
|
@ -124,10 +152,10 @@ export default function AddToCartDialog() {
|
|||
<MyTouchableOpacity
|
||||
style={tw`flex-1 bg-brand500 py-3.5 rounded-xl items-center ${!selectedSlotId ? 'opacity-50' : ''}`}
|
||||
onPress={handleAddToCart}
|
||||
disabled={addToCart.isLoading || !selectedSlotId}
|
||||
disabled={(addToCart.isLoading || updateCartItem.isLoading) || !selectedSlotId}
|
||||
>
|
||||
<MyText style={tw`text-white font-bold`}>
|
||||
{addToCart.isLoading ? 'Adding...' : 'Add to Cart'}
|
||||
{addToCart.isLoading || updateCartItem.isLoading ? (isUpdate ? 'Updating...' : 'Adding...') : (isUpdate ? 'Update Item' : 'Add to Cart')}
|
||||
</MyText>
|
||||
</MyTouchableOpacity>
|
||||
<MyTouchableOpacity
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue