176 lines
No EOL
4.9 KiB
TypeScript
176 lines
No EOL
4.9 KiB
TypeScript
import React, { useState, useRef, useEffect } from "react";
|
|
import {
|
|
View,
|
|
Dimensions,
|
|
Image,
|
|
Alert,
|
|
Platform,
|
|
} 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";
|
|
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)
|
|
|
|
|
|
export default function SearchResults() {
|
|
const router = useRouter();
|
|
const { q } = useLocalSearchParams();
|
|
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 products = productsData?.products || [];
|
|
const addToCart = useAddToCart({ showSuccessAlert: false, showErrorAlert: false, refetchCart: true });
|
|
|
|
useManualRefresh(() => {
|
|
refetch();
|
|
});
|
|
|
|
useMarkDataFetchers(() => {
|
|
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);
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<View style={tw`flex-1 justify-center items-center bg-gray-50`}>
|
|
<MyText style={tw`text-gray-500 font-medium`}>Loading products...</MyText>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<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 products</MyText>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<View style={tw`flex-1 bg-gray-50`}>
|
|
<MyFlatList
|
|
data={products}
|
|
numColumns={2}
|
|
renderItem={({ item }) => (
|
|
<ProductCard
|
|
item={item}
|
|
handleAddToCart={handleAddToCart}
|
|
handleBuyNow={handleBuyNow}
|
|
itemWidth={itemWidth}
|
|
onPress={() => router.push(`/(drawer)/(tabs)/home/product-detail/${item.id}`)}
|
|
showDeliveryInfo={false}
|
|
iconType="flash"
|
|
/>
|
|
)}
|
|
keyExtractor={(item, index) => index.toString()}
|
|
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}
|
|
onChangeText={setInputQuery}
|
|
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'}
|
|
</MyText>
|
|
</View>
|
|
</View>
|
|
}
|
|
/>
|
|
|
|
<LoadingDialog open={isLoadingDialogOpen} message="Adding to cart..." />
|
|
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
|
<FloatingCartBar />
|
|
</View>
|
|
</View>
|
|
);
|
|
} |