import React, { useState } from "react"; import { View, Dimensions, Image, Alert, ScrollView, StatusBar as RNStatusBar, RefreshControl } from "react-native"; import { StatusBar as ExpoStatusBar } from 'expo-status-bar'; import { LinearGradient } from "expo-linear-gradient"; import { useRouter } from "expo-router"; import { theme, tw, useManualRefresh, useMarkDataFetchers, LoadingDialog, AppContainer, MyTouchableOpacity, MyText, MyTextInput, SearchBar, useStatusBarStore, colors } from "common-ui"; import dayjs from "dayjs"; import relativeTime from "dayjs/plugin/relativeTime"; import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import FontAwesome5 from "@expo/vector-icons/FontAwesome5"; import { Ionicons } from "@expo/vector-icons"; import ProductCard from "@/components/ProductCard"; import MyFlatList from "common-ui/src/components/flat-list"; import { trpc } from "@/src/trpc-client"; import { useGetCart } from "@/hooks/cart-query-hooks"; import { useProductSlotIdentifier } from "@/hooks/useProductSlotIdentifier"; import FloatingCartBar from "@/components/floating-cart-bar"; import AddressSelector from "@/components/AddressSelector"; import BannerCarousel from "@/components/BannerCarousel"; import TestingPhaseNote from "@/components/TestingPhaseNote"; import { useUserDetails } from "@/src/contexts/AuthContext"; import TabLayoutWrapper from "@/components/TabLayoutWrapper"; import { useNavigationStore } from "@/src/store/navigationStore"; import { useGetEssentialConsts } from "@/src/api-hooks/essential-consts.api"; import NextOrderGlimpse from "@/components/NextOrderGlimpse"; dayjs.extend(relativeTime); // import { StatusBar } from "expo-status-bar"; const { width: screenWidth } = Dimensions.get("window"); const itemWidth = screenWidth * 0.45; // 45% of screen width const gridItemWidth = (screenWidth - 48) / 2; // Half of screen width minus padding const RenderStore = ({ item, }: { item: any; }) => { const router = useRouter(); const { setNavigatedFromHome, setSelectedStoreId } = useNavigationStore(); const handlePress = () => { setNavigatedFromHome(true); setSelectedStoreId(item.id); router.push('/(drawer)/(tabs)/stores'); }; return ( {item.signedImageUrl ? ( ) : ( )} {item.name.replace(/^The\s+/i, "")} ); }; const headerColor = colors.secondaryPink; // Format time range helper 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}`; } }; export default function Dashboard() { const router = useRouter(); const userDetails = useUserDetails(); const [inputQuery, setInputQuery] = useState(""); const [searchQuery, setSearchQuery] = useState(""); const [selectedTagId, setSelectedTagId] = useState(null); const [isLoadingDialogOpen, setIsLoadingDialogOpen] = useState(false); const [gradientHeight, setGradientHeight] = useState(0); const [stickyBarLayout, setStickyBarLayout] = useState({ y: 0, height: 0 }); const [whiteSectionLayout, setWhiteSectionLayout] = useState({ y: 0 }); const [displayedProducts, setDisplayedProducts] = useState([]); const [endIndex, setEndIndex] = useState(10); const [hasMore, setHasMore] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); const { backgroundColor } = useStatusBarStore(); const { getQuickestSlot } = useProductSlotIdentifier(); const [isRefreshing, setIsRefreshing] = useState(false); const { data: productsData, isLoading, error, refetch, } = trpc.common.product.getAllProductsSummary.useQuery({ searchQuery: searchQuery || undefined, tagId: selectedTagId || undefined, }); const { data: essentialConsts, isLoading: isLoadingConsts, error: constsError, refetch: refetchConsts } = useGetEssentialConsts(); const { data: storesData, refetch: refetchStores } = trpc.user.stores.getStores.useQuery(); const { data: slotsData, refetch: refetchSlots } = trpc.user.slots.getSlotsWithProducts.useQuery(); const products = productsData?.products || []; // Initialize with the first batch of products (only those with available slots) React.useEffect(() => { if (products.length > 0 && displayedProducts.length === 0) { const initialBatchRaw = products; // Filter to include only products with available slots // const initialBatch = initialBatchRaw.filter(product => { // const slot = getQuickestSlot(product.id); // return !product.isOutOfStock; // }); const initialBatch = initialBatchRaw .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; if(a.isOutOfStock && !b.isOutOfStock) return 1; if(!a.isOutOfStock && b.isOutOfStock) return -1; return 0; }) setDisplayedProducts(initialBatch); setHasMore(products.length > 10); setEndIndex(10); } }, [productsData]); // Extract popular items IDs as an array to preserve order const popularItemIds = (() => { 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 []; })(); const sortedSlots = React.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]); // Filter products to only include those whose ID exists in popularItemIds, preserving order // Only filter when both products and essentialConsts are loaded const popularProducts = popularItemIds .map(id => products.find(product => product.id === id)) .filter((product): product is NonNullable => product != null); const handleRefresh = async () => { setIsRefreshing(true); try { await Promise.all([ refetch(), refetchStores(), refetchSlots(), refetchConsts(), ]); } finally { setIsRefreshing(false); } }; useManualRefresh(() => { handleRefresh(); }); useMarkDataFetchers(() => { handleRefresh(); }); const handleScroll = (event: any) => { }; 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'} ); } return ( {/* */} } onScroll={(e) => { handleScroll(e); // Check if we're near the end of the scroll for vertical loading const { layoutMeasurement, contentOffset, contentSize } = e.nativeEvent; const paddingToBottom = 40; if (layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom) { // Load more products when reaching near the end if (!isLoadingMore && hasMore) { // loadMoreProducts(); } } }} scrollEventThrottle={16} > router.push("/(drawer)/(tabs)/me")} style={tw`bg-white/10 rounded-full border border-white/10`} > {userDetails?.profileImage ? ( ) : ( )} { const { y, height } = event.nativeEvent.layout; setStickyBarLayout({ y, height }); }} > { }} onPress={() => router.push("/(drawer)/(tabs)/home/search-results")} editable={false} containerStyle={tw` bg-white`} onSubmitEditing={() => { if (inputQuery.trim()) { router.push( `/(drawer)/(tabs)/home/search-results?q=${encodeURIComponent( inputQuery.trim() )}` ); } }} returnKeyType="search" /> { const { y, height } = event.nativeEvent.layout; setGradientHeight(y + height); }} > {/* Stores Section */} {storesData?.stores && storesData.stores.length > 0 && ( Our Stores Fresh from our locations {storesData.stores.map((store, index) => ( ))} )} {/* Banner Carousel */} {/* White Section */} { const { y } = event.nativeEvent.layout; setWhiteSectionLayout({ y }); }} > {/* Section Title */} Popular Items Trending fresh picks just for you {popularProducts.map((item, index: number) => ( router.push( `/(drawer)/(tabs)/home/product-detail/${item.id}` ) } showDeliveryInfo={false} miniView={true} /> ))} {/* Upcoming Deliveries Section */} {sortedSlots.length > 0 && ( Upcoming Delivery Slots Plan your fresh deliveries ahead {sortedSlots.slice(0, 5).map((slot) => { const now = dayjs(); const freezeTime = dayjs(slot.freezeTime); const isClosingSoon = freezeTime.diff(now, "hour") < 4 && freezeTime.isAfter(now); return ( router.push( `/(drawer)/(tabs)/home/slot-view?slotId=${slot.id}` ) } activeOpacity={0.9} > {/* Status Badge */} {isClosingSoon && ( CLOSING SOON )} {/* Main Time Grid */} 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")} {/* Product Teaser */} {slot.products.slice(0, 3).map((p, i) => ( 0 && tw`-ml-3`, ]} > {p.images?.[0] ? ( ) : ( )} ))} View all {slot.products.length} items ); })} )} {/* All Products Section - Vertical Infinite Scroll */} All Available Products Browse our complete selection {/* Product List */} item.id} numColumns={2} // contentContainerStyle={tw`pb-8`} contentContainerStyle={[tw` pb-24`, { gap: 16 }]} columnWrapperStyle={{gap: 16}} renderItem={({ item, index }) => ( router.push( `/(drawer)/(tabs)/home/product-detail/${item.id}` ) } showDeliveryInfo={true} miniView={false} key={item.id} /> )} initialNumToRender={4} maxToRenderPerBatch={4} windowSize={4} // onEndReached={() => { // if (!isLoadingMore && hasMore) { // loadMoreProducts(); // } // }} onEndReachedThreshold={0.5} ListFooterComponent={ isLoadingMore ? ( Loading more... ) : null } ListEmptyComponent={ No products available } /> ); }