Compare commits
No commits in common. "aa7035b77cf079d1f02cccf179f7b44c950c8063" and "b54bf65bcae40ea81ea1dee825919e5d7e07f844" have entirely different histories.
aa7035b77c
...
b54bf65bca
18 changed files with 415 additions and 527 deletions
|
|
@ -32,6 +32,4 @@ DELIVERY_CHARGE=20
|
||||||
|
|
||||||
# Telegram Configuration
|
# Telegram Configuration
|
||||||
TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
||||||
TELEGRAM_CHAT_IDS=
|
TELEGRAM_CHAT_IDS=-5075171894
|
||||||
# TELEGRAM_BOT_TOKEN=8410461852:AAGXQCwRPFbndqwTgLJh8kYxST4Z0vgh72U
|
|
||||||
# TELEGRAM_CHAT_IDS=-5075171894
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -824,7 +824,7 @@ export const orderRouter = router({
|
||||||
updateUserNotes: protectedProcedure
|
updateUserNotes: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.number(),
|
id: z.string(),
|
||||||
userNotes: z.string(),
|
userNotes: z.string(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
|
import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import ProductCard from "@/components/ProductCard";
|
import ProductCard from "@/components/ProductCard";
|
||||||
import AddToCartDialog from "@/src/components/AddToCartDialog";
|
|
||||||
import MyFlatList from "common-ui/src/components/flat-list";
|
import MyFlatList from "common-ui/src/components/flat-list";
|
||||||
|
|
||||||
import { trpc } from "@/src/trpc-client";
|
import { trpc } from "@/src/trpc-client";
|
||||||
|
|
@ -422,7 +421,6 @@ export default function Dashboard() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showDeliveryInfo={false}
|
showDeliveryInfo={false}
|
||||||
useAddToCartDialog={true}
|
|
||||||
miniView={true}
|
miniView={true}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
@ -625,20 +623,19 @@ export default function Dashboard() {
|
||||||
columnWrapperStyle={{gap: 16}}
|
columnWrapperStyle={{gap: 16}}
|
||||||
renderItem={({ item, index }) => (
|
renderItem={({ item, index }) => (
|
||||||
|
|
||||||
<ProductCard
|
<ProductCard
|
||||||
item={item}
|
item={item}
|
||||||
itemWidth={gridItemWidth}
|
itemWidth={gridItemWidth}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
router.push(
|
router.push(
|
||||||
`/(drawer)/(tabs)/home/product-detail/${item.id}`
|
`/(drawer)/(tabs)/home/product-detail/${item.id}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showDeliveryInfo={true}
|
showDeliveryInfo={true}
|
||||||
miniView={false}
|
miniView={false}
|
||||||
useAddToCartDialog={true}
|
|
||||||
|
|
||||||
key={item.id}
|
key={item.id}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
initialNumToRender={4}
|
initialNumToRender={4}
|
||||||
|
|
@ -668,7 +665,6 @@ export default function Dashboard() {
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
||||||
<AddToCartDialog />
|
|
||||||
<View style={tw`h-16`}></View>
|
<View style={tw`h-16`}></View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,34 @@
|
||||||
import React, { useState, useRef, useEffect } from "react";
|
import React, { useState, useRef, useEffect } from "react";
|
||||||
import { View, Dimensions } from "react-native";
|
import {
|
||||||
|
View,
|
||||||
|
Dimensions,
|
||||||
|
Image,
|
||||||
|
Alert,
|
||||||
|
Platform,
|
||||||
|
} from "react-native";
|
||||||
import { useRouter, useLocalSearchParams } from "expo-router";
|
import { useRouter, useLocalSearchParams } from "expo-router";
|
||||||
import {
|
import {
|
||||||
|
theme,
|
||||||
tw,
|
tw,
|
||||||
useManualRefresh,
|
useManualRefresh,
|
||||||
useMarkDataFetchers,
|
useMarkDataFetchers,
|
||||||
|
LoadingDialog,
|
||||||
|
AppContainer,
|
||||||
MyFlatList,
|
MyFlatList,
|
||||||
MyText,
|
MyText,
|
||||||
SearchBar,
|
MyTextInput,
|
||||||
} from "common-ui";
|
MyTouchableOpacity,
|
||||||
|
SearchBar } from "common-ui";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import { trpc } from "@/src/trpc-client";
|
import { trpc } from "@/src/trpc-client";
|
||||||
|
import { useGetCart, useAddToCart } from '@/hooks/cart-query-hooks';
|
||||||
import ProductCard from "@/components/ProductCard";
|
import ProductCard from "@/components/ProductCard";
|
||||||
import FloatingCartBar from "@/components/floating-cart-bar";
|
import FloatingCartBar from "@/components/floating-cart-bar";
|
||||||
|
|
||||||
const { width: screenWidth } = Dimensions.get("window");
|
const { width: screenWidth } = Dimensions.get("window");
|
||||||
const itemWidth = (screenWidth - 48) / 2;
|
const itemWidth = (screenWidth - 48) / 2; // 48 = padding horizontal (16*2) + gap (16)
|
||||||
|
|
||||||
|
|
||||||
export default function SearchResults() {
|
export default function SearchResults() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
@ -23,20 +36,29 @@ export default function SearchResults() {
|
||||||
const query = (q as string) || "";
|
const query = (q as string) || "";
|
||||||
const [inputQuery, setInputQuery] = useState(query);
|
const [inputQuery, setInputQuery] = useState(query);
|
||||||
const [searchQuery, setSearchQuery] = useState(query);
|
const [searchQuery, setSearchQuery] = useState(query);
|
||||||
|
const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false);
|
||||||
const searchInputRef = useRef<any>(null);
|
const searchInputRef = useRef<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Auto-focus search bar on mount
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
searchInputRef.current?.focus();
|
searchInputRef.current?.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { data: productsData, isLoading, error, refetch } =
|
const {
|
||||||
trpc.common.product.getAllProductsSummary.useQuery({
|
data: productsData,
|
||||||
searchQuery: searchQuery || undefined,
|
isLoading,
|
||||||
});
|
error,
|
||||||
|
refetch,
|
||||||
|
} = trpc.common.product.getAllProductsSummary.useQuery({
|
||||||
|
searchQuery: searchQuery || undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: cartData, refetch: refetchCart } = useGetCart();
|
||||||
|
|
||||||
const products = productsData?.products || [];
|
const products = productsData?.products || [];
|
||||||
|
const addToCart = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true });
|
||||||
|
|
||||||
useManualRefresh(() => {
|
useManualRefresh(() => {
|
||||||
refetch();
|
refetch();
|
||||||
|
|
@ -46,6 +68,43 @@ export default function SearchResults() {
|
||||||
refetch();
|
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 = () => {
|
const handleSearch = () => {
|
||||||
setSearchQuery(inputQuery);
|
setSearchQuery(inputQuery);
|
||||||
};
|
};
|
||||||
|
|
@ -76,19 +135,20 @@ export default function SearchResults() {
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<ProductCard
|
<ProductCard
|
||||||
item={item}
|
item={item}
|
||||||
|
handleAddToCart={handleAddToCart}
|
||||||
|
handleBuyNow={handleBuyNow}
|
||||||
itemWidth={itemWidth}
|
itemWidth={itemWidth}
|
||||||
onPress={() =>
|
onPress={() => router.push(`/(drawer)/(tabs)/home/product-detail/${item.id}`)}
|
||||||
router.push(`/(drawer)/(tabs)/home/product-detail/${item.id}`)
|
|
||||||
}
|
|
||||||
showDeliveryInfo={false}
|
showDeliveryInfo={false}
|
||||||
useAddToCartDialog={true}
|
iconType="flash"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
keyExtractor={(item, index) => index.toString()}
|
keyExtractor={(item, index) => index.toString()}
|
||||||
columnWrapperStyle={{ gap: 16, justifyContent: "center" }}
|
columnWrapperStyle={{ gap: 16, justifyContent: 'center' }}
|
||||||
contentContainerStyle={[tw`pb-24`, { gap: 16 }]}
|
contentContainerStyle={[tw`pb-24`, { gap: 16 }]}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<View style={tw`pt-4 pb-2 px-4`}>
|
<View style={tw`pt-4 pb-2 px-4`}>
|
||||||
|
{/* Search Bar */}
|
||||||
<SearchBar
|
<SearchBar
|
||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
value={inputQuery}
|
value={inputQuery}
|
||||||
|
|
@ -96,19 +156,21 @@ export default function SearchResults() {
|
||||||
onSubmitEditing={handleSearch}
|
onSubmitEditing={handleSearch}
|
||||||
returnKeyType="search"
|
returnKeyType="search"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Section Title */}
|
||||||
<View style={tw`flex-row justify-between items-center mb-2`}>
|
<View style={tw`flex-row justify-between items-center mb-2`}>
|
||||||
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
||||||
{searchQuery
|
{searchQuery ? `Search Results for "${searchQuery}"` : 'All Products'}
|
||||||
? `Search Results for "${searchQuery}"`
|
|
||||||
: "All Products"}
|
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
|
||||||
|
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
||||||
|
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
||||||
<FloatingCartBar />
|
<FloatingCartBar />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -236,7 +236,7 @@ export default function OrderDetails() {
|
||||||
</MyTouchableOpacity>
|
</MyTouchableOpacity>
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
updateNotesMutation.mutate({ id: order.id, userNotes: notesInput });
|
updateNotesMutation.mutate({ id: order.orderId, userNotes: notesInput });
|
||||||
}}
|
}}
|
||||||
disabled={updateNotesMutation.isPending}
|
disabled={updateNotesMutation.isPending}
|
||||||
style={tw`px-3 py-1 bg-brand500 rounded-lg`}
|
style={tw`px-3 py-1 bg-brand500 rounded-lg`}
|
||||||
|
|
@ -435,7 +435,7 @@ export default function OrderDetails() {
|
||||||
setComplaintDialogOpen(false);
|
setComplaintDialogOpen(false);
|
||||||
refetch();
|
refetch();
|
||||||
}}
|
}}
|
||||||
orderId={order.id}
|
orderId={order.orderId}
|
||||||
/>
|
/>
|
||||||
</BottomDialog>
|
</BottomDialog>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,54 @@
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { View, Dimensions, ScrollView } from "react-native";
|
import {
|
||||||
|
View,
|
||||||
|
Dimensions,
|
||||||
|
Image,
|
||||||
|
Platform,
|
||||||
|
Alert,
|
||||||
|
ScrollView,
|
||||||
|
} from "react-native";
|
||||||
import { LinearGradient } from "expo-linear-gradient";
|
import { LinearGradient } from "expo-linear-gradient";
|
||||||
import { useRouter } from "expo-router";
|
import { useRouter } from "expo-router";
|
||||||
import {
|
import {
|
||||||
|
theme,
|
||||||
tw,
|
tw,
|
||||||
useManualRefresh,
|
useManualRefresh,
|
||||||
useMarkDataFetchers,
|
useMarkDataFetchers,
|
||||||
|
LoadingDialog,
|
||||||
AppContainer,
|
AppContainer,
|
||||||
MyFlatList,
|
MyFlatList,
|
||||||
MyText,
|
MyText,
|
||||||
} from "common-ui";
|
} from "common-ui";
|
||||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
||||||
import ProductCard from "@/components/ProductCard";
|
import ProductCard from "@/components/ProductCard";
|
||||||
|
|
||||||
import { trpc } from "@/src/trpc-client";
|
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 FloatingCartBar from "@/components/floating-cart-bar";
|
||||||
import TabLayoutWrapper from "@/components/TabLayoutWrapper";
|
import TabLayoutWrapper from "@/components/TabLayoutWrapper";
|
||||||
|
|
||||||
const { width: screenWidth } = Dimensions.get("window");
|
const { width: screenWidth } = Dimensions.get("window");
|
||||||
const itemWidth = (screenWidth - 48) / 2;
|
const itemWidth = (screenWidth - 48) / 2; // 2 items per row with padding
|
||||||
|
|
||||||
export default function OrderAgain() {
|
export default function OrderAgain() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false);
|
||||||
|
|
||||||
const { data: recentProductsData, isLoading, error, refetch } =
|
const {
|
||||||
trpc.user.order.getRecentlyOrderedProducts.useQuery({
|
data: recentProductsData,
|
||||||
limit: 20,
|
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 recentProducts = recentProductsData?.products || [];
|
const recentProducts = recentProductsData?.products || [];
|
||||||
|
|
||||||
|
|
@ -37,14 +60,37 @@ export default function OrderAgain() {
|
||||||
refetch();
|
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) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||||
<MaterialIcons name="refresh" size={48} color="#3B82F6" />
|
<MaterialIcons name="refresh" size={48} color="#3B82F6" />
|
||||||
<MyText style={tw`text-gray-500 font-medium mt-4`}>
|
<MyText style={tw`text-gray-500 font-medium mt-4`}>Loading your recent orders...</MyText>
|
||||||
Loading your recent orders...
|
|
||||||
</MyText>
|
|
||||||
</View>
|
</View>
|
||||||
</AppContainer>
|
</AppContainer>
|
||||||
);
|
);
|
||||||
|
|
@ -56,9 +102,7 @@ export default function OrderAgain() {
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||||
<MaterialIcons name="error-outline" size={48} color="#EF4444" />
|
<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-900 text-lg font-bold mt-4`}>Oops!</MyText>
|
||||||
<MyText style={tw`text-gray-500 mt-2`}>
|
<MyText style={tw`text-gray-500 mt-2`}>Failed to load recent orders</MyText>
|
||||||
Failed to load recent orders
|
|
||||||
</MyText>
|
|
||||||
</View>
|
</View>
|
||||||
</AppContainer>
|
</AppContainer>
|
||||||
);
|
);
|
||||||
|
|
@ -68,7 +112,7 @@ export default function OrderAgain() {
|
||||||
<TabLayoutWrapper>
|
<TabLayoutWrapper>
|
||||||
<ScrollView style={tw`flex-1`} contentContainerStyle={tw`pb-20`}>
|
<ScrollView style={tw`flex-1`} contentContainerStyle={tw`pb-20`}>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={["#194185", "#1570EF"]}
|
colors={['#194185', '#1570EF']} // brand900 to brand600
|
||||||
start={{ x: 0, y: 0 }}
|
start={{ x: 0, y: 0 }}
|
||||||
end={{ x: 1, y: 1 }}
|
end={{ x: 1, y: 1 }}
|
||||||
style={tw`pb-8 pt-6 px-5 rounded-b-[32px] shadow-lg mb-4`}
|
style={tw`pb-8 pt-6 px-5 rounded-b-[32px] shadow-lg mb-4`}
|
||||||
|
|
@ -79,27 +123,22 @@ export default function OrderAgain() {
|
||||||
<View style={tw`bg-white/20 p-1 rounded-full mr-2`}>
|
<View style={tw`bg-white/20 p-1 rounded-full mr-2`}>
|
||||||
<MaterialIcons name="refresh" size={14} color="#FFF" />
|
<MaterialIcons name="refresh" size={14} color="#FFF" />
|
||||||
</View>
|
</View>
|
||||||
<MyText
|
<MyText style={tw`text-brand100 text-xs font-bold uppercase tracking-widest`}>
|
||||||
style={tw`text-brand100 text-xs font-bold uppercase tracking-widest`}
|
|
||||||
>
|
|
||||||
Order Again
|
Order Again
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
<MyText
|
<MyText style={tw`text-white text-sm font-medium opacity-90 ml-1`} numberOfLines={1}>
|
||||||
style={tw`text-white text-sm font-medium opacity-90 ml-1`}
|
|
||||||
numberOfLines={1}
|
|
||||||
>
|
|
||||||
Reorder your favorite items quickly
|
Reorder your favorite items quickly
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
|
|
||||||
|
{/* White Section */}
|
||||||
<View style={tw`bg-white`}>
|
<View style={tw`bg-white`}>
|
||||||
|
{/* Section Title */}
|
||||||
<View style={tw`flex-row items-center mb-2 px-4 pt-4`}>
|
<View style={tw`flex-row items-center mb-2 px-4 pt-4`}>
|
||||||
<MyText style={tw`text-lg font-bold text-gray-900`}>
|
<MyText style={tw`text-lg font-bold text-gray-900`}>Recently Ordered</MyText>
|
||||||
Recently Ordered
|
|
||||||
</MyText>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{recentProducts.length === 0 ? (
|
{recentProducts.length === 0 ? (
|
||||||
|
|
@ -113,27 +152,22 @@ export default function OrderAgain() {
|
||||||
No recent orders
|
No recent orders
|
||||||
</MyText>
|
</MyText>
|
||||||
<MyText style={tw`text-gray-500 mt-2 text-center px-8`}>
|
<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>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View style={tw`px-4 pb-4`}>
|
<View style={tw`px-4 pb-4`}>
|
||||||
<View style={tw`flex-row flex-wrap justify-between`}>
|
<View style={tw`flex-row flex-wrap justify-between`}>
|
||||||
{recentProducts.map((item, index) => (
|
{recentProducts.map((item, index) => (
|
||||||
<View
|
<View key={item.id} style={tw`mb-4 ${index % 2 === 0 ? 'mr-2' : ''}`}>
|
||||||
key={item.id}
|
|
||||||
style={tw`mb-4 ${index % 2 === 0 ? "mr-2" : ""}`}
|
|
||||||
>
|
|
||||||
<ProductCard
|
<ProductCard
|
||||||
item={item}
|
item={item}
|
||||||
|
// handleAddToCart={handleAddToCart}
|
||||||
|
// handleBuyNow={handleBuyNow}
|
||||||
itemWidth={itemWidth}
|
itemWidth={itemWidth}
|
||||||
onPress={() =>
|
onPress={() => router.push(`/(drawer)/(tabs)/order-again/product-detail/${item.id}`)}
|
||||||
router.push(
|
|
||||||
`/(drawer)/(tabs)/order-again/product-detail/${item.id}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
showDeliveryInfo={false}
|
showDeliveryInfo={false}
|
||||||
useAddToCartDialog={true}
|
// iconType="flash"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
|
@ -143,9 +177,10 @@ export default function OrderAgain() {
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
<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 />
|
<FloatingCartBar />
|
||||||
</View>
|
</View>
|
||||||
</TabLayoutWrapper>
|
</TabLayoutWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,44 +1,76 @@
|
||||||
import React from "react";
|
import React, { useState } from 'react';
|
||||||
import { View, Dimensions } from "react-native";
|
|
||||||
import { useRouter, useLocalSearchParams } from "expo-router";
|
|
||||||
import {
|
import {
|
||||||
theme,
|
View,
|
||||||
tw,
|
Dimensions,
|
||||||
useManualRefresh,
|
Image,
|
||||||
useMarkDataFetchers,
|
TouchableOpacity,
|
||||||
MyFlatList,
|
Alert,
|
||||||
useDrawerTitle,
|
Platform,
|
||||||
MyText,
|
} from 'react-native';
|
||||||
} from "common-ui";
|
import { useRouter, useLocalSearchParams } from 'expo-router';
|
||||||
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
import { theme, tw, useManualRefresh, MyFlatList, useDrawerTitle, useMarkDataFetchers, LoadingDialog, MyText, MyTouchableOpacity } from 'common-ui';
|
||||||
import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
|
import dayjs from 'dayjs';
|
||||||
import { trpc } from "@/src/trpc-client";
|
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
||||||
import ProductCard from "@/components/ProductCard";
|
import FontAwesome5 from '@expo/vector-icons/FontAwesome5';
|
||||||
import FloatingCartBar from "@/components/floating-cart-bar";
|
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)
|
||||||
|
|
||||||
const { width: screenWidth } = Dimensions.get("window");
|
|
||||||
const itemWidth = (screenWidth - 48) / 2;
|
|
||||||
|
|
||||||
export default function StoreDetail() {
|
export default function StoreDetail() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
// const { storeId } = useLocalSearchParams();
|
||||||
const { id: storeId } = useLocalSearchParams();
|
const { id: storeId } = useLocalSearchParams();
|
||||||
const storeIdNum = parseInt(storeId as string);
|
const storeIdNum = parseInt(storeId as string);
|
||||||
|
const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false);
|
||||||
|
|
||||||
const { data: storeData, isLoading, refetch, error } =
|
const { data: storeData, isLoading, refetch, error } = trpc.user.stores.getStoreWithProducts.useQuery(
|
||||||
trpc.user.stores.getStoreWithProducts.useQuery(
|
{ storeId: storeIdNum },
|
||||||
{ storeId: storeIdNum },
|
{ enabled: !!storeIdNum }
|
||||||
{ enabled: !!storeIdNum }
|
);
|
||||||
);
|
|
||||||
|
|
||||||
useManualRefresh(() => {
|
|
||||||
refetch();
|
|
||||||
});
|
|
||||||
|
|
||||||
useMarkDataFetchers(() => {
|
useMarkDataFetchers(() => {
|
||||||
refetch();
|
refetch();
|
||||||
});
|
});
|
||||||
|
|
||||||
useDrawerTitle(storeData?.store?.name || "Store", [storeData?.store?.name]);
|
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);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -53,75 +85,51 @@ export default function StoreDetail() {
|
||||||
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
||||||
<MaterialIcons name="error-outline" size={48} color="#EF4444" />
|
<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-900 text-lg font-bold mt-4`}>Oops!</MyText>
|
||||||
<MyText style={tw`text-gray-500 mt-2`}>
|
<MyText style={tw`text-gray-500 mt-2`}>Store not found or error loading</MyText>
|
||||||
Store not found or error loading
|
|
||||||
</MyText>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex-1 bg-gray-50 relative`}>
|
<View style={tw`flex-1 bg-gray-50 relative`}>
|
||||||
<MyFlatList
|
<MyFlatList
|
||||||
data={storeData?.products || []}
|
data={storeData?.products || []}
|
||||||
numColumns={2}
|
numColumns={2}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<ProductCard
|
<ProductCard
|
||||||
item={item}
|
item={item}
|
||||||
itemWidth={itemWidth}
|
itemWidth={itemWidth}
|
||||||
onPress={() =>
|
onPress={() => router.push(`/(drawer)/(tabs)/stores/store-detail/product-detail/${item.id}`)}
|
||||||
router.push(
|
showDeliveryInfo={false}
|
||||||
`/(drawer)/(tabs)/stores/store-detail/product-detail/${item.id}`
|
miniView={true}
|
||||||
)
|
/>
|
||||||
}
|
|
||||||
showDeliveryInfo={false}
|
|
||||||
miniView={true}
|
|
||||||
useAddToCartDialog={true}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
keyExtractor={(item, index) => index.toString()}
|
keyExtractor={(item, index) => index.toString()}
|
||||||
columnWrapperStyle={{ gap: 16 }}
|
columnWrapperStyle={{ gap: 16 }}
|
||||||
contentContainerStyle={[tw`px-4 pb-24`, { gap: 16 }]}
|
contentContainerStyle={[tw`px-4 pb-24`, { gap: 16 }]}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<View style={tw`pt-4 pb-6`}>
|
<View style={tw`pt-4 pb-6`}>
|
||||||
<View
|
<View style={tw`bg-white p-6 rounded-2xl shadow-sm border border-gray-100 items-center`}>
|
||||||
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`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>
|
</View>
|
||||||
<MyText
|
<MyText style={tw`text-2xl font-bold text-gray-900 text-center mb-2`}>{storeData?.store?.name}</MyText>
|
||||||
style={tw`text-2xl font-bold text-gray-900 text-center mb-2`}
|
|
||||||
>
|
|
||||||
{storeData?.store?.name}
|
|
||||||
</MyText>
|
|
||||||
{storeData?.store?.description && (
|
{storeData?.store?.description && (
|
||||||
<MyText
|
<MyText style={tw`text-gray-500 text-center leading-5 px-4`}>{storeData?.store?.description}</MyText>
|
||||||
style={tw`text-gray-500 text-center leading-5 px-4`}
|
|
||||||
>
|
|
||||||
{storeData?.store?.description}
|
|
||||||
</MyText>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={tw`flex-row items-center mt-6 mb-2`}>
|
<View style={tw`flex-row items-center mt-6 mb-2`}>
|
||||||
<MaterialIcons name="grid-view" size={20} color="#374151" />
|
<MaterialIcons name="grid-view" size={20} color="#374151" />
|
||||||
<MyText style={tw`text-lg font-bold text-gray-900 ml-2`}>
|
<MyText style={tw`text-lg font-bold text-gray-900 ml-2`}>Products from this Store</MyText>
|
||||||
Products from this Store
|
|
||||||
</MyText>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<LoadingDialog open={isLoadingDialogOpen} message="Processing..." />
|
||||||
|
|
||||||
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
||||||
<FloatingCartBar />
|
<FloatingCartBar />
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,6 @@ import UpdateChecker from "@/components/UpdateChecker";
|
||||||
import { RefreshProvider } from "../../../packages/ui/src/lib/refresh-context";
|
import { RefreshProvider } from "../../../packages/ui/src/lib/refresh-context";
|
||||||
import WebViewWrapper from "@/components/WebViewWrapper";
|
import WebViewWrapper from "@/components/WebViewWrapper";
|
||||||
import BackHandlerWrapper from "@/components/BackHandler";
|
import BackHandlerWrapper from "@/components/BackHandler";
|
||||||
import AddToCartDialog from "@/src/components/AddToCartDialog";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
|
|
@ -62,7 +61,6 @@ export default function RootLayout() {
|
||||||
<RefreshProvider queryClient={queryClient}>
|
<RefreshProvider queryClient={queryClient}>
|
||||||
<BackHandlerWrapper />
|
<BackHandlerWrapper />
|
||||||
<Stack screenOptions={{ headerShown: false }} />
|
<Stack screenOptions={{ headerShown: false }} />
|
||||||
<AddToCartDialog />
|
|
||||||
</RefreshProvider>
|
</RefreshProvider>
|
||||||
</LocationTestWrapper>
|
</LocationTestWrapper>
|
||||||
</PaperProvider>
|
</PaperProvider>
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,6 @@ interface LocationTestWrapperProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Feature flag to enable/disable location warning dialogs
|
|
||||||
const ENABLE_LOCATION_WARNINGS = false;
|
|
||||||
|
|
||||||
const LocationTestWrapper: React.FC<LocationTestWrapperProps> = ({ children }) => {
|
const LocationTestWrapper: React.FC<LocationTestWrapperProps> = ({ children }) => {
|
||||||
// Skip location checks entirely for emulators
|
// Skip location checks entirely for emulators
|
||||||
if (isEmulator()) {
|
if (isEmulator()) {
|
||||||
|
|
@ -65,7 +62,7 @@ const LocationTestWrapper: React.FC<LocationTestWrapperProps> = ({ children }) =
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ENABLE_LOCATION_WARNINGS && locationCheck && !locationCheck.isInside) {
|
if (locationCheck && !locationCheck.isInside) {
|
||||||
setLocationErrorDialogOpen(true);
|
setLocationErrorDialogOpen(true);
|
||||||
}
|
}
|
||||||
}, [locationCheck]);
|
}, [locationCheck]);
|
||||||
|
|
@ -76,7 +73,7 @@ const LocationTestWrapper: React.FC<LocationTestWrapperProps> = ({ children }) =
|
||||||
if (status === 'granted') {
|
if (status === 'granted') {
|
||||||
const location = await Location.getCurrentPositionAsync({});
|
const location = await Location.getCurrentPositionAsync({});
|
||||||
setUserLocation(location);
|
setUserLocation(location);
|
||||||
} else if (ENABLE_LOCATION_WARNINGS) {
|
} else {
|
||||||
setLocationDialogOpen(true);
|
setLocationDialogOpen(true);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { View, Alert, TouchableOpacity, Text } from 'react-native';
|
import { View, Alert, TouchableOpacity, Text } from 'react-native';
|
||||||
import { Image } from 'expo-image';
|
import { Image } from 'expo-image';
|
||||||
import { tw, theme, MyText, MyTouchableOpacity, Quantifier, MiniQuantifier } from 'common-ui';
|
import { tw, theme, MyText, MyTouchableOpacity, Quantifier, MiniQuantifier } from 'common-ui';
|
||||||
|
|
@ -13,8 +13,6 @@ import {
|
||||||
useAddToCart,
|
useAddToCart,
|
||||||
} from '@/hooks/cart-query-hooks';
|
} from '@/hooks/cart-query-hooks';
|
||||||
import { useProductSlotIdentifier } from '@/hooks/useProductSlotIdentifier';
|
import { useProductSlotIdentifier } from '@/hooks/useProductSlotIdentifier';
|
||||||
import { useCartStore } from '@/src/store/cartStore';
|
|
||||||
import { trpc } from '@/src/trpc-client';
|
|
||||||
|
|
||||||
|
|
||||||
interface ProductCardProps {
|
interface ProductCardProps {
|
||||||
|
|
@ -25,7 +23,6 @@ interface ProductCardProps {
|
||||||
miniView?: boolean;
|
miniView?: boolean;
|
||||||
nullIfNotAvailable?: boolean;
|
nullIfNotAvailable?: boolean;
|
||||||
containerComp?: React.ComponentType<any> | React.JSXElementConstructor<any>;
|
containerComp?: React.ComponentType<any> | React.JSXElementConstructor<any>;
|
||||||
useAddToCartDialog?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatQuantity = (quantity: number, unit: string): { value: string; display: string } => {
|
const formatQuantity = (quantity: number, unit: string): { value: string; display: string } => {
|
||||||
|
|
@ -43,11 +40,9 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
miniView = false,
|
miniView = false,
|
||||||
nullIfNotAvailable = false,
|
nullIfNotAvailable = false,
|
||||||
containerComp: ContainerComp = React.Fragment,
|
containerComp: ContainerComp = React.Fragment,
|
||||||
useAddToCartDialog = false,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { data: cartData } = useGetCart();
|
const { data: cartData } = useGetCart();
|
||||||
const { getQuickestSlot } = useProductSlotIdentifier();
|
const { getQuickestSlot } = useProductSlotIdentifier();
|
||||||
const { setAddedToCartProduct } = useCartStore();
|
|
||||||
const updateCartItem = useUpdateCartItem({
|
const updateCartItem = useUpdateCartItem({
|
||||||
showSuccessAlert: false,
|
showSuccessAlert: false,
|
||||||
showErrorAlert: false,
|
showErrorAlert: false,
|
||||||
|
|
@ -68,22 +63,6 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id);
|
const cartItem = cartData?.items?.find((cartItem: any) => cartItem.productId === item.id);
|
||||||
const quantity = cartItem?.quantity || 0;
|
const quantity = cartItem?.quantity || 0;
|
||||||
|
|
||||||
// Query all slots with products
|
|
||||||
const { data: slotsData } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
|
||||||
|
|
||||||
// Create slot lookup map
|
|
||||||
const slotMap = useMemo(() => {
|
|
||||||
const map: Record<number, any> = {};
|
|
||||||
slotsData?.slots?.forEach((slot: any) => {
|
|
||||||
map[slot.id] = slot;
|
|
||||||
});
|
|
||||||
return map;
|
|
||||||
}, [slotsData]);
|
|
||||||
|
|
||||||
// Get cart item's slot delivery time if item is in cart
|
|
||||||
const cartSlot = cartItem?.slotId ? slotMap[cartItem.slotId] : null;
|
|
||||||
const displayDeliveryDate = cartSlot?.deliveryTime || item.nextDeliveryDate;
|
|
||||||
|
|
||||||
// Precompute the next slot and determine display out of stock status
|
// Precompute the next slot and determine display out of stock status
|
||||||
const slotId = getQuickestSlot(item.id);
|
const slotId = getQuickestSlot(item.id);
|
||||||
const displayIsOutOfStock = item.isOutOfStock || !slotId;
|
const displayIsOutOfStock = item.isOutOfStock || !slotId;
|
||||||
|
|
@ -94,9 +73,7 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleQuantityChange = (newQuantity: number) => {
|
const handleQuantityChange = (newQuantity: number) => {
|
||||||
if (useAddToCartDialog) {
|
if (newQuantity === 0 && cartItem) {
|
||||||
setAddedToCartProduct({ productId: item.id, product: item });
|
|
||||||
} else if (newQuantity === 0 && cartItem) {
|
|
||||||
removeFromCart.mutate({ itemId: cartItem.id });
|
removeFromCart.mutate({ itemId: cartItem.id });
|
||||||
} else if (newQuantity === 1 && !cartItem) {
|
} else if (newQuantity === 1 && !cartItem) {
|
||||||
const slotId = getQuickestSlot(item.id);
|
const slotId = getQuickestSlot(item.id);
|
||||||
|
|
@ -164,11 +141,11 @@ const ProductCard: React.FC<ProductCardProps> = ({
|
||||||
<MyText style={tw`text-gray-500 text-xs font-medium`}>Quantity: <MyText style={tw`text-[#f81260] font-semibold`}>{formatQuantity(item.productQuantity || 1, item.unitNotation).display}</MyText></MyText>
|
<MyText style={tw`text-gray-500 text-xs font-medium`}>Quantity: <MyText style={tw`text-[#f81260] font-semibold`}>{formatQuantity(item.productQuantity || 1, item.unitNotation).display}</MyText></MyText>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{showDeliveryInfo && displayDeliveryDate && (
|
{showDeliveryInfo && item.nextDeliveryDate && (
|
||||||
<View style={tw`flex-row items-center bg-brand50 px-2 py-1.5 rounded-lg self-start mb-2 border border-brand100`}>
|
<View style={tw`flex-row items-center bg-brand50 px-2 py-1.5 rounded-lg self-start mb-2 border border-brand100`}>
|
||||||
<MaterialIcons name="local-shipping" size={12} color="#2E90FA" />
|
<MaterialIcons name="local-shipping" size={12} color="#2E90FA" />
|
||||||
<MyText style={tw`text-[10px] text-brand700 ml-1.5 font-bold`}>
|
<MyText style={tw`text-[10px] text-brand700 ml-1.5 font-bold`}>
|
||||||
{dayjs(displayDeliveryDate).format("ddd, DD MMM • h:mm A")}
|
{dayjs(item.nextDeliveryDate).format("ddd, DD MMM • h:mm A")}
|
||||||
</MyText>
|
</MyText>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -443,7 +443,7 @@ const ProductDetail: React.FC<ProductDetailProps> = ({ productId, isFlashDeliver
|
||||||
|
|
||||||
{/* Review Form - Moved above or keep below? Usually users want to read reviews first, but if few reviews, writing is good. The original had form then reviews. I will keep format but make it nicer. */}
|
{/* Review Form - Moved above or keep below? Usually users want to read reviews first, but if few reviews, writing is good. The original had form then reviews. I will keep format but make it nicer. */}
|
||||||
<View style={tw`mb-6`}>
|
<View style={tw`mb-6`}>
|
||||||
<ReviewForm productId={productDetail!.id} onReviewSubmitted={handleReviewSubmitted} />
|
<ReviewForm productId={productDetail.id} onReviewSubmitted={handleReviewSubmitted} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={tw`bg-white rounded-3xl shadow-sm border border-gray-100 overflow-hidden`}>
|
<View style={tw`bg-white rounded-3xl shadow-sm border border-gray-100 overflow-hidden`}>
|
||||||
|
|
|
||||||
|
|
@ -121,157 +121,174 @@ const QuickDeliveryAddressSelector: React.FC<QuickDeliveryAddressSelectorProps>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isForFlashDelivery && (
|
{!isForFlashDelivery && (
|
||||||
<>
|
<View style={tw`flex-1 mr-2`}>
|
||||||
<View style={tw`flex-1 flex-row items-center gap-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 */}
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => router.back()}
|
onPress={() => setDialogOpen(true)}
|
||||||
style={tw`p-1`}
|
style={tw`flex-row items-center justify-between mb-2`}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<MaterialIcons name="chevron-left" size={24} />
|
<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} />
|
||||||
</MyTouchableOpacity>
|
</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 dropdown - temporarily hidden
|
{/* Address Section */}
|
||||||
<MyTouchableOpacity
|
<MyTouchableOpacity
|
||||||
onPress={() => setDialogOpen(true)}
|
onPress={() => setDialogOpen(true)}
|
||||||
style={tw`flex-row items-center bg-brand50 border border-brand100 rounded-lg px-3 py-2`}
|
style={tw`flex-row items-center justify-between`}
|
||||||
activeOpacity={0.7}
|
activeOpacity={0.7}
|
||||||
>
|
>
|
||||||
<View style={tw`flex-1`}>
|
<View style={tw`flex-1`}>
|
||||||
<MyText style={tw`text-xs text-brand500 font-bold uppercase`}>Address</MyText>
|
{/* <MyText style={tw`text-xs text-brand500 font-bold uppercase tracking-wider leading-3`}>
|
||||||
<MyText style={tw`text-sm font-bold text-brand900`} numberOfLines={1}>
|
Delivery Address
|
||||||
{getCurrentAddressDisplay()}
|
</MyText> */}
|
||||||
</MyText>
|
<MyText style={tw`text-sm font-bold text-brand900 leading-4`} numberOfLines={1}>
|
||||||
</View>
|
TO: {getCurrentAddressDisplay()}
|
||||||
<MaterialIcons name="keyboard-arrow-down" size={18} color={theme.colors.brand500} />
|
</MyText>
|
||||||
</MyTouchableOpacity>
|
</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>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</BottomDialog>
|
||||||
<BottomDialog
|
</View>
|
||||||
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`}>
|
<MyText style={tw`text-white font-black text-base`}>
|
||||||
₹{totalCartValue}
|
₹{totalCartValue}
|
||||||
{` • ${itemCount} ${itemCount === 1 ? "Item" : "Items"}`}
|
|
||||||
</MyText>
|
</MyText>
|
||||||
|
{` • ${itemCount} ${itemCount === 1 ? "Item" : "Items"}`}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</MyText>
|
</MyText>
|
||||||
|
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
import React, { useState, useMemo, useEffect } from 'react';
|
|
||||||
import { View, ScrollView } from 'react-native';
|
|
||||||
import { tw, BottomDialog, MyText, MyTouchableOpacity, Quantifier } from 'common-ui';
|
|
||||||
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
|
|
||||||
import { useCartStore } from '@/src/store/cartStore';
|
|
||||||
import { trpc } from '@/src/trpc-client';
|
|
||||||
import { useAddToCart, useGetCart, useUpdateCartItem } from '@/hooks/cart-query-hooks';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
|
|
||||||
export default function AddToCartDialog() {
|
|
||||||
const { addedToCartProduct, clearAddedToCartProduct } = useCartStore();
|
|
||||||
const [quantity, setQuantity] = useState(1);
|
|
||||||
const [selectedSlotId, setSelectedSlotId] = useState<number | null>(null);
|
|
||||||
|
|
||||||
const { data: slotsData } = trpc.user.slots.getSlotsWithProducts.useQuery();
|
|
||||||
const { data: cartData } = useGetCart();
|
|
||||||
|
|
||||||
const addToCart = useAddToCart({
|
|
||||||
showSuccessAlert: false,
|
|
||||||
showErrorAlert: false,
|
|
||||||
refetchCart: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateCartItem = useUpdateCartItem({
|
|
||||||
showSuccessAlert: false,
|
|
||||||
showErrorAlert: false,
|
|
||||||
refetchCart: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isOpen = !!addedToCartProduct;
|
|
||||||
|
|
||||||
|
|
||||||
const product = addedToCartProduct?.product;
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
setSelectedSlotId(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [isOpen, cartData, product]);
|
|
||||||
|
|
||||||
const { slotMap, productSlotIdsMap } = useMemo(() => {
|
|
||||||
const slotMap: Record<number, any> = {};
|
|
||||||
const productSlotIdsMap: Record<number, number[]> = {};
|
|
||||||
|
|
||||||
if (slotsData?.slots) {
|
|
||||||
slotsData.slots.forEach((slot: any) => {
|
|
||||||
slotMap[slot.id] = slot;
|
|
||||||
|
|
||||||
slot.products?.forEach((p: any) => {
|
|
||||||
if (!productSlotIdsMap[p.id]) {
|
|
||||||
productSlotIdsMap[p.id] = [];
|
|
||||||
}
|
|
||||||
productSlotIdsMap[p.id].push(slot.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { slotMap, productSlotIdsMap };
|
|
||||||
}, [slotsData]);
|
|
||||||
|
|
||||||
const availableSlotIds = productSlotIdsMap[product?.id] || [];
|
|
||||||
|
|
||||||
const availableSlots = availableSlotIds
|
|
||||||
.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 = () => {
|
|
||||||
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;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BottomDialog open={isOpen} onClose={clearAddedToCartProduct}>
|
|
||||||
<View style={tw`p-6 max-h-[500px]`}>
|
|
||||||
<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>
|
|
||||||
<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}>
|
|
||||||
{availableSlots.map((slot: any) => (
|
|
||||||
<MyTouchableOpacity
|
|
||||||
key={slot.id}
|
|
||||||
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)}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
>
|
|
||||||
<MaterialIcons name="local-shipping" size={20} color="#3B82F6" style={tw`mt-0.5`} />
|
|
||||||
<View style={tw`ml-3 flex-1`}>
|
|
||||||
<MyText style={tw`text-gray-900 font-bold text-base`}>
|
|
||||||
{dayjs(slot.deliveryTime).format('ddd, DD MMM • h:mm A')}
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
{selectedSlotId === slot.id ? (
|
|
||||||
<MaterialIcons name="check-circle" size={24} color="#3B82F6" style={tw`mt-0.5`} />
|
|
||||||
) : (
|
|
||||||
<MaterialIcons name="check-box-outline-blank" size={24} color="#9CA3AF" style={tw`mt-0.5`} />
|
|
||||||
)}
|
|
||||||
</MyTouchableOpacity>
|
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<View style={tw`mt-4`}>
|
|
||||||
<MyText style={tw`text-sm font-bold text-gray-900 mb-2`}>Quantity</MyText>
|
|
||||||
<Quantifier
|
|
||||||
value={quantity}
|
|
||||||
setValue={setQuantity}
|
|
||||||
step={product.incrementStep}
|
|
||||||
unit={product.unitNotation}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<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' : ''}`}
|
|
||||||
onPress={handleAddToCart}
|
|
||||||
disabled={(addToCart.isLoading || updateCartItem.isLoading) || !selectedSlotId}
|
|
||||||
>
|
|
||||||
<MyText style={tw`text-white font-bold`}>
|
|
||||||
{addToCart.isLoading || updateCartItem.isLoading ? (isUpdate ? 'Updating...' : 'Adding...') : (isUpdate ? 'Update Item' : 'Add to Cart')}
|
|
||||||
</MyText>
|
|
||||||
</MyTouchableOpacity>
|
|
||||||
<MyTouchableOpacity
|
|
||||||
style={tw`flex-1 bg-gray-100 py-3.5 rounded-xl items-center`}
|
|
||||||
onPress={clearAddedToCartProduct}
|
|
||||||
>
|
|
||||||
<MyText style={tw`text-gray-700 font-bold`}>Cancel</MyText>
|
|
||||||
</MyTouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</BottomDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -111,17 +111,7 @@ const AddressForm: React.FC<AddressFormProps> = ({ onSuccess, initialValues, isE
|
||||||
keyboardDismissMode="on-drag"
|
keyboardDismissMode="on-drag"
|
||||||
>
|
>
|
||||||
<MyText style={tw`text-xl font-bold mb-4`}>{isEdit ? 'Edit Address' : 'Add Address'}</MyText>
|
<MyText style={tw`text-xl font-bold mb-4`}>{isEdit ? 'Edit Address' : 'Add Address'}</MyText>
|
||||||
|
|
||||||
{/* Service Area Notice */}
|
|
||||||
<View style={tw`bg-amber-50 border border-amber-200 rounded-lg p-3 mb-4`}>
|
|
||||||
<View style={tw`flex-row items-center`}>
|
|
||||||
<MyText style={tw`text-amber-600 mr-2`}>ℹ️</MyText>
|
|
||||||
<MyText style={tw`text-amber-800 text-sm flex-1`}>
|
|
||||||
We currently serve only in Mahabubnagar town
|
|
||||||
</MyText>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Submit Error Message */}
|
{/* Submit Error Message */}
|
||||||
{submitError && (
|
{submitError && (
|
||||||
<View style={tw`bg-red-50 border border-red-200 rounded-lg p-3 mb-4`}>
|
<View style={tw`bg-red-50 border border-red-200 rounded-lg p-3 mb-4`}>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { create } from 'zustand';
|
|
||||||
|
|
||||||
interface AddedToCartProduct {
|
|
||||||
productId: number;
|
|
||||||
product: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CartStore {
|
|
||||||
addedToCartProduct: AddedToCartProduct | null;
|
|
||||||
setAddedToCartProduct: (product: AddedToCartProduct | null) => void;
|
|
||||||
clearAddedToCartProduct: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCartStore = create<CartStore>((set) => ({
|
|
||||||
addedToCartProduct: null,
|
|
||||||
setAddedToCartProduct: (product) => set({ addedToCartProduct: product }),
|
|
||||||
clearAddedToCartProduct: () => set({ addedToCartProduct: null }),
|
|
||||||
}));
|
|
||||||
|
|
@ -4,8 +4,7 @@ const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
retry: 3,
|
retry: 3,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: true,
|
||||||
staleTime: 1200000,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue