import React, { useState, useCallback, useMemo, memo } from "react"; import { View, Dimensions, Image, RefreshControl } from "react-native"; import { LinearGradient } from "expo-linear-gradient"; import { useRouter } from "expo-router"; import { theme, tw, useManualRefresh, useMarkDataFetchers, LoadingDialog, MyTouchableOpacity, MyText, SearchBar, useStatusBarStore, colors } from "common-ui"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import ProductCard from "@/components/ProductCard"; import AddToCartDialog from "@/src/components/AddToCartDialog"; import MyFlatList from "common-ui/src/components/flat-list"; import { trpc } from "@/src/trpc-client"; import { useAllProducts, useStores, useSlots, useGetEssentialConsts } from "@/src/hooks/prominent-api-hooks"; import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier"; import { useCentralSlotStore } from "@/src/store/centralSlotStore"; import { useCentralProductStore } from "@/src/store/centralProductStore"; import FloatingCartBar from "@/components/floating-cart-bar"; import BannerCarousel from "@/components/BannerCarousel"; import { useUserDetails } from "@/src/contexts/AuthContext"; import TabLayoutWrapper from "@/components/TabLayoutWrapper"; import { useNavigationStore } from "@/src/store/navigationStore"; import NextOrderGlimpse from "@/components/NextOrderGlimpse"; dayjs.extend(relativeTime); const { width: screenWidth } = Dimensions.get("window"); const itemWidth = screenWidth * 0.45; const gridItemWidth = (screenWidth - 48) / 2; const headerColor = colors.secondaryPink; const formatTimeRange = (deliveryTime: string) => { const time = dayjs(deliveryTime); const endTime = time.add(1, 'hour'); const startPeriod = time.format('A'); const endPeriod = endTime.format('A'); if (startPeriod === endPeriod) { return `${time.format('h')}-${endTime.format('h')} ${startPeriod}`; } else { return `${time.format('h:mm')} ${startPeriod} - ${endTime.format('h:mm')} ${endPeriod}`; } }; const staticStyles = { flatListContent: { gap: 16 }, columnWrapper: { gap: 16, paddingHorizontal: 16 }, popularListContent: { paddingBottom: 16 }, slotsListContent: { paddingBottom: 24 }, }; interface RenderStoreProps { item: any; } const RenderStore = memo(({ item }: RenderStoreProps) => { const router = useRouter(); const { setNavigatedFromHome, setSelectedStoreId } = useNavigationStore(); const handlePress = useCallback(() => { setNavigatedFromHome(true); setSelectedStoreId(item.id); router.push('/(drawer)/(tabs)/stores'); }, [item.id, router, setNavigatedFromHome, setSelectedStoreId]); return ( {item.signedImageUrl ? ( ) : ( )} {item.name.replace(/^The\s+/i, "")} ); }); RenderStore.displayName = 'RenderStore'; interface SlotCardProps { slot: any; } const SlotCard = memo(({ slot }: SlotCardProps) => { const router = useRouter(); const now = dayjs(); const freezeTime = dayjs(slot.freezeTime); const isClosingSoon = freezeTime.diff(now, "hour") < 4 && freezeTime.isAfter(now); const handlePress = useCallback(() => { router.push(`/(drawer)/(tabs)/home/slot-view?slotId=${slot.id}`); }, [router, slot.id]); return ( {isClosingSoon && ( CLOSING SOON )} Delivery At {formatTimeRange(slot.deliveryTime)} {dayjs(slot.deliveryTime).format("ddd, MMM DD")} Order By {dayjs(slot.freezeTime).format("h:mm A")} {dayjs(slot.freezeTime).format("ddd, MMM DD")} {slot.products.slice(0, 3).map((p: any, i: number) => ( 0 && tw`-ml-3`]} > {p.images?.[0] ? ( ) : ( )} ))} View all {slot.products.length} items ); }); interface PopularProductItemProps { item: any; onPress: (id: number) => void; } const PopularProductItem = memo(({ item, onPress }: PopularProductItemProps) => { const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]); return ( ); }); interface SlotItemProps { item: any; } const SlotItem = memo(({ item }: SlotItemProps) => ); interface ProductItemProps { item: any; onPress: (id: number) => void; } const ProductItem = memo(({ item, onPress }: ProductItemProps) => { const handlePress = useCallback(() => onPress(item.id), [item.id, onPress]); return ( ); }); interface ListHeaderProps { gradientHeight: number; onGradientLayout: (height: number) => void; storesData: any; popularProducts: any[]; sortedSlots: any[]; onProductPress: (id: number) => void; } const ListHeader = memo(({ gradientHeight, onGradientLayout, storesData, popularProducts, sortedSlots, onProductPress, }: ListHeaderProps) => { const handleLayout = useCallback((event: any) => { const { y, height } = event.nativeEvent.layout; onGradientLayout(y + height); }, [onGradientLayout]); const renderPopularItem = useCallback(({ item }: { item: any }) => ( ), [onProductPress]); const renderSlotItem = useCallback(({ item }: { item: any }) => ( ), []); const gradientStyle = useMemo(() => [ tw`absolute left-0 right-0 shadow-lg`, { height: gradientHeight + 32, zIndex: -1 } ], [gradientHeight]); return ( <> {storesData?.stores && storesData.stores.length > 0 && ( Our Stores Fresh from our locations {storesData.stores.map((store: any) => ( ))} )} Popular Items Trending fresh picks just for you item.id.toString()} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={staticStyles.popularListContent} renderItem={renderPopularItem} removeClippedSubviews={true} /> {sortedSlots.length > 0 && ( Upcoming Delivery Slots Plan your fresh deliveries ahead item.id.toString()} horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={staticStyles.slotsListContent} decelerationRate="fast" snapToInterval={280 + 16} renderItem={renderSlotItem} removeClippedSubviews={true} /> )} All Available Products Browse our complete selection ); }); export default function Dashboard() { const router = useRouter(); const userDetails = useUserDetails(); const [inputQuery, setInputQuery] = useState(""); const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false); const [gradientHeight, setGradientHeight] = useState(0); const [displayedProducts, setDisplayedProducts] = useState([]); const [hasMore, setHasMore] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); const { backgroundColor } = useStatusBarStore(); const { getQuickestSlot } = useProductSlotIdentifier(); const productSlotsMap = useCentralSlotStore((state) => state.productSlotsMap); const refetchProducts = useCentralProductStore((state) => state.refetchProducts); const refetchSlotsFromStore = useCentralSlotStore((state) => state.refetchSlots); const [isRefreshing, setIsRefreshing] = useState(false); const { data: productsData, isLoading, error, } = useAllProducts(); const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts(); const { data: storesData, refetch: refetchStores } = useStores(); const { data: slotsData } = useSlots(); const products = productsData?.products || []; React.useEffect(() => { if (products.length > 0 && displayedProducts.length === 0) { const initialBatch = products .filter(p => typeof p.id === "number") .sort((a, b) => { const slotA = getQuickestSlot(a.id); const slotB = getQuickestSlot(b.id); if (slotA && !slotB) return -1; if (!slotA && slotB) return 1; const aOutOfStock = productSlotsMap[a.id]?.isOutOfStock; const bOutOfStock = productSlotsMap[b.id]?.isOutOfStock; if (aOutOfStock && !bOutOfStock) return 1; if (!aOutOfStock && bOutOfStock) return -1; return 0; }); console.log('setting the displayed products') setDisplayedProducts(initialBatch); setHasMore(products.length > 10); } }, [productsData, productSlotsMap]); const popularItemIds = useMemo(() => { const popularItems = essentialConsts?.popularItems; if (!popularItems) return []; if (Array.isArray(popularItems)) { return popularItems.map((id: any) => parseInt(id)).filter((id: number) => !isNaN(id)); } else if (typeof popularItems === 'string') { return popularItems .split(',') .map((id: string) => parseInt(id.trim())) .filter((id: number) => !isNaN(id)); } return []; }, [essentialConsts?.popularItems]); const sortedSlots = useMemo(() => { if (!slotsData?.slots) return []; return [...slotsData.slots].sort((a, b) => { const deliveryDiff = dayjs(a.deliveryTime).diff(dayjs(b.deliveryTime)); if (deliveryDiff !== 0) return deliveryDiff; return dayjs(a.freezeTime).diff(dayjs(b.freezeTime)); }); }, [slotsData]); const popularProducts = useMemo(() => { return popularItemIds .map(id => products.find(product => product.id === id)) .filter((product): product is NonNullable => product != null); }, [popularItemIds, products]); const handleRefresh = useCallback(async () => { setIsRefreshing(true); try { const promises = []; if (refetchProducts) { promises.push(refetchProducts()); } if (refetchSlotsFromStore) { promises.push(refetchSlotsFromStore()); } promises.push(refetchStores()); promises.push(refetchConsts()); await Promise.all(promises); } finally { setIsRefreshing(false); } }, [refetchProducts, refetchSlotsFromStore, refetchStores, refetchConsts]); useManualRefresh(() => { handleRefresh(); }); useMarkDataFetchers(() => { handleRefresh(); }); const handleProductPress = useCallback((id: number) => { router.push(`/(drawer)/(tabs)/home/product-detail/${id}`); }, [router]); const handleGradientLayout = useCallback((height: number) => { setGradientHeight(height); }, []); const handleSearchPress = useCallback(() => { router.push("/(drawer)/(tabs)/home/search-results"); }, [router]); const renderProductItem = useCallback(({ item }: { item: any }) => ( ), [handleProductPress]); const listHeader = useMemo(() => ( ), [gradientHeight, handleGradientLayout, storesData, popularProducts, sortedSlots, handleProductPress]); const searchBarContainerStyle = useMemo(() => [ tw`w-full px-4 pt-4 pb-2`, { backgroundColor } ], [backgroundColor]); const listContentContainerStyle = useMemo(() => [ tw`pb-24`, staticStyles.flatListContent ], []); if (isLoading || isLoadingConsts) { return ( {isLoading ? 'Loading products...' : 'Loading app settings...'} ); } if (error || constsError) { return ( Oops! {error ? 'Failed to load products' : 'Failed to load app settings'} ); } let str = '' displayedProducts.forEach(product => str += `${product.id}-`) // console.log(str) return ( { }} onPress={handleSearchPress} editable={false} containerStyle={tw`bg-white`} onSubmitEditing={() => { if (inputQuery.trim()) { router.push(`/(drawer)/(tabs)/home/search-results?q=${encodeURIComponent(inputQuery.trim())}`); } }} returnKeyType="search" /> item.id.toString()} numColumns={2} contentContainerStyle={listContentContainerStyle} columnWrapperStyle={staticStyles.columnWrapper} renderItem={renderProductItem} ListHeaderComponent={listHeader} refreshControl={ } ListEmptyComponent={ No products available } removeClippedSubviews={true} maxToRenderPerBatch={10} windowSize={5} initialNumToRender={10} updateCellsBatchingPeriod={50} /> ); }