235 lines
No EOL
8.7 KiB
TypeScript
235 lines
No EOL
8.7 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
FlatList,
|
|
} from "react-native";
|
|
import { Image } from 'expo-image';
|
|
import { useRouter } from "expo-router";
|
|
import {
|
|
theme,
|
|
tw,
|
|
useManualRefresh,
|
|
useMarkDataFetchers,
|
|
MyFlatList,
|
|
MyText,
|
|
MyTouchableOpacity,
|
|
} from "common-ui";
|
|
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { trpc } from "@/src/trpc-client";
|
|
import { LinearGradient } from "expo-linear-gradient";
|
|
import TabLayoutWrapper from "@/components/TabLayoutWrapper";
|
|
import FloatingCartBar from "@/components/floating-cart-bar";
|
|
import { useFocusEffect } from "expo-router";
|
|
import { useNavigationStore } from "@/src/store/navigationStore";
|
|
|
|
const StoreCard = ({
|
|
item,
|
|
router,
|
|
}: {
|
|
item: any;
|
|
router: any;
|
|
}) => {
|
|
const sampleProducts = item.sampleProducts || [];
|
|
const remainingCount = item.productCount - sampleProducts.length;
|
|
|
|
const navigateToStore = () => router.push(`/(drawer)/(tabs)/stores/store-detail/${item.id}`);
|
|
|
|
return (
|
|
<View
|
|
style={tw`bg-white rounded-[24px] mb-4 shadow-lg shadow-slate-200 border border-slate-200 overflow-hidden`}
|
|
>
|
|
{/* Top Header Section - Touchable */}
|
|
<MyTouchableOpacity
|
|
onPress={navigateToStore}
|
|
activeOpacity={0.7}
|
|
style={tw`p-4 pb-0`}
|
|
>
|
|
<View style={tw`flex-row items-center mb-4`}>
|
|
<View style={tw`w-12 h-12 rounded-xl bg-slate-50 border border-slate-200 p-0.5 shadow-sm`}>
|
|
<Image
|
|
source={{ uri: item.signedImageUrl || undefined }}
|
|
style={tw`w-full h-full rounded-[10px]`}
|
|
contentFit="cover"
|
|
/>
|
|
{!item.signedImageUrl && (
|
|
<View style={tw`absolute inset-0 items-center justify-center`}>
|
|
<MaterialIcons name="storefront" size={20} color="#94A3B8" />
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
<View style={tw`flex-1 ml-3`}>
|
|
<MyText style={tw`text-slate-900 font-extrabold text-lg`} numberOfLines={1}>
|
|
{item.name}
|
|
</MyText>
|
|
</View>
|
|
|
|
<View style={tw`bg-brand50 px-2.5 py-1.5 rounded-xl border border-brand100 items-center justify-center`}>
|
|
<MyText style={tw`text-brand700 text-sm font-black`}>{item.productCount}</MyText>
|
|
<MyText style={tw`text-brand600 text-[8px] font-black uppercase tracking-tighter`}>Items</MyText>
|
|
</View>
|
|
</View>
|
|
</MyTouchableOpacity>
|
|
|
|
{/* Horizontal Scrollable Product Collection */}
|
|
<View style={tw`mb-5`}>
|
|
<FlatList
|
|
data={sampleProducts}
|
|
horizontal
|
|
showsHorizontalScrollIndicator={false}
|
|
contentContainerStyle={tw`px-4`}
|
|
keyExtractor={(product) => product.id.toString()}
|
|
renderItem={({ item: product }) => (
|
|
<MyTouchableOpacity
|
|
onPress={navigateToStore}
|
|
activeOpacity={0.85}
|
|
style={tw`mr-3 items-center w-24`}
|
|
>
|
|
<View style={tw`bg-slate-50 rounded-2xl p-1 border border-slate-200 w-24 h-24 mb-2 shadow-sm`}>
|
|
<Image
|
|
source={{ uri: product.signedImageUrl || undefined }}
|
|
style={tw`w-full h-full rounded-xl`}
|
|
contentFit="cover"
|
|
transition={200}
|
|
/>
|
|
</View>
|
|
<MyText style={tw`text-slate-900 text-[10px] font-black leading-tight text-center`} numberOfLines={2}>
|
|
{product.name}
|
|
</MyText>
|
|
</MyTouchableOpacity>
|
|
)}
|
|
ListFooterComponent={remainingCount > 0 ? (
|
|
<View style={tw`items-center justify-center`}>
|
|
<MyTouchableOpacity
|
|
onPress={navigateToStore}
|
|
style={tw`bg-slate-900 w-24 h-24 rounded-2xl items-center justify-center shadow-md`}
|
|
>
|
|
<MyText style={tw`text-white text-base font-black`}>+{remainingCount}</MyText>
|
|
<MyText style={tw`text-white/60 text-[8px] font-black uppercase tracking-widest`}>Discover</MyText>
|
|
<MaterialIcons name="arrow-forward" size={16} color="white" style={tw`mt-1`} />
|
|
</MyTouchableOpacity>
|
|
<View style={tw`h-8`} />
|
|
</View>
|
|
) : null}
|
|
/>
|
|
</View>
|
|
|
|
<View style={tw`px-4 pb-4`}>
|
|
<MyTouchableOpacity
|
|
onPress={navigateToStore}
|
|
activeOpacity={0.9}
|
|
style={tw`bg-brand600 py-3 rounded-[18px] flex-row items-center justify-center shadow-lg shadow-brand200`}
|
|
>
|
|
<MyText style={tw`text-white text-sm font-black uppercase tracking-wider mr-2`}>Explore Store</MyText>
|
|
<Ionicons name="arrow-forward" size={18} color="white" />
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
};
|
|
|
|
export default function Stores() {
|
|
const router = useRouter();
|
|
|
|
const {
|
|
data: storesData,
|
|
isLoading,
|
|
error,
|
|
refetch,
|
|
} = trpc.user.stores.getStores.useQuery();
|
|
|
|
const stores = storesData?.stores || [];
|
|
|
|
useManualRefresh(refetch);
|
|
useMarkDataFetchers(() => refetch());
|
|
|
|
const { isNavigatedFromHome, setNavigatedFromHome, selectedStoreId, setSelectedStoreId } = useNavigationStore();
|
|
|
|
useFocusEffect(() => {
|
|
if (isNavigatedFromHome && selectedStoreId) {
|
|
setNavigatedFromHome(false);
|
|
setSelectedStoreId(null);
|
|
router.push(`/(drawer)/(tabs)/stores/store-detail/${selectedStoreId}`);
|
|
}
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<View style={tw`flex-1 justify-center items-center bg-slate-50`}>
|
|
<View style={tw`w-20 h-20 items-center justify-center`}>
|
|
<MaterialIcons name="storefront" size={48} color={theme.colors.brand200} />
|
|
</View>
|
|
<MyText style={tw`text-slate-400 font-black uppercase tracking-widest text-[10px] mt-4`}>Opening Marketplace...</MyText>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<View style={tw`flex-1 justify-center items-center bg-slate-50 p-10`}>
|
|
<View style={tw`w-20 h-20 bg-rose-50 rounded-full items-center justify-center mb-6`}>
|
|
<MaterialIcons name="error-outline" size={32} color={theme.colors.red1} />
|
|
</View>
|
|
<MyText style={tw`text-slate-900 text-xl font-black text-center mb-2`}>Store Fetch Failed</MyText>
|
|
<MyText style={tw`text-slate-500 text-center font-medium mb-8 leading-5`}>
|
|
We couldn't reach our vendor network.
|
|
</MyText>
|
|
<MyTouchableOpacity
|
|
onPress={() => refetch()}
|
|
style={tw`bg-brand600 px-8 py-3 rounded-2xl shadow-lg shadow-brand200`}
|
|
>
|
|
<MyText style={tw`text-white font-black uppercase tracking-widest text-xs`}>Retry</MyText>
|
|
</MyTouchableOpacity>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<TabLayoutWrapper>
|
|
<View style={tw`flex-1 bg-slate-50 relative`}>
|
|
<MyFlatList
|
|
data={stores}
|
|
renderItem={({ item }) => <StoreCard item={item} router={router} />}
|
|
keyExtractor={(item) => item.id.toString()}
|
|
contentContainerStyle={tw`pb-32 px-3`}
|
|
showsVerticalScrollIndicator={false}
|
|
ListHeaderComponent={
|
|
<View style={tw`pt-6 pb-4`}>
|
|
<View style={tw`flex-row items-center mb-2`}>
|
|
<LinearGradient
|
|
colors={[theme.colors.brand500, theme.colors.brand700]}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 1 }}
|
|
style={tw`w-1 h-6 rounded-full mr-3`}
|
|
/>
|
|
<View>
|
|
<MyText style={tw`text-slate-400 text-[10px] font-black uppercase tracking-[0.2em]`}>
|
|
Our Outlets
|
|
</MyText>
|
|
<MyText style={tw`text-slate-900 text-3xl font-black tracking-tight`}>
|
|
Our Stores
|
|
</MyText>
|
|
</View>
|
|
</View>
|
|
<MyText style={tw`text-slate-500 text-sm font-medium leading-5 pr-4`}>
|
|
Experience the finest selection of premium meat, poultry, fresh fruits, vegetables, and dairy directly from our own stores.
|
|
</MyText>
|
|
</View>
|
|
}
|
|
ListEmptyComponent={
|
|
<View style={tw`flex-1 justify-center items-center py-20`}>
|
|
<View style={tw`w-24 h-24 bg-white rounded-full items-center justify-center mb-6 shadow-sm`}>
|
|
<Ionicons name="storefront-outline" size={48} color="#94A3B8" />
|
|
</View>
|
|
<MyText style={tw`text-slate-900 text-xl font-black tracking-tight`}>No Stores Available</MyText>
|
|
</View>
|
|
}
|
|
/>
|
|
<View style={tw`absolute bottom-2 left-4 right-4`}>
|
|
<FloatingCartBar />
|
|
</View>
|
|
</View>
|
|
</TabLayoutWrapper>
|
|
);
|
|
} |