291 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect, useCallback, useRef } from 'react';
import {
View,
Text,
ScrollView,
TouchableOpacity,
FlatList,
Image,
NativeSyntheticEvent,
NativeScrollEvent,
Dimensions,
} from 'react-native';
import {
useNavigation,
CompositeNavigationProp,
useFocusEffect,
} from '@react-navigation/native';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { StackNavigationProp } from '@react-navigation/stack';
import { getStyles } from './homeScreen.styles';
import { ProductCard, SearchBar } from '@components';
import { useAppTheme } from '@theme';
import { Product } from '../../../interfaces';
import { AppStackParamList } from '../../../navigation/appStack';
import { MainTabParamList } from '../../../navigation/mainTabNavigator';
import {
fetchCustomerDetails,
getAllOffersThunk,
useAppDispatch,
useAppSelector,
} from '@store';
import { getAllCategoriesThunk, getAllProductsThunk } from './thunk';
type NavProp = CompositeNavigationProp<
BottomTabNavigationProp<MainTabParamList, 'HomeScreen'>,
StackNavigationProp<AppStackParamList>
>;
const { width } = Dimensions.get('window');
const BANNER_STEP = width - 32 + 12; // card width + margin
const CATEGORIES = [
{ key: 'all', icon: '🏠', label: 'All' },
{ key: 'Food', icon: '🍔', label: 'Food' },
{ key: 'Groceries', icon: '🛒', label: 'Grocery' },
{ key: 'Pharmacy', icon: '💊', label: 'Pharmacy' },
{ key: 'Meat', icon: '🥩', label: 'Meat' },
{ key: 'Flowers', icon: '💐', label: 'Flowers' },
{ key: 'More', icon: '📦', label: 'More' },
];
const PROMOS = [
{
key: 'promo1',
colors: ['#05824C', '#0AA25E'],
eyebrow: 'Limited time',
title: '50% OFF',
subtitle: 'On your first order, up to ₹150',
cta: 'Order now',
},
{
key: 'promo2',
colors: ['#7B3FF2', '#9B6BF5'],
eyebrow: 'Free delivery',
title: '₹0 Delivery',
subtitle: 'On orders above ₹299 today',
cta: 'Explore',
},
{
key: 'promo3',
colors: ['#E85D3D', '#F0805F'],
eyebrow: 'Pharmacy',
title: 'Meds in 20 min',
subtitle: 'Genuine medicines, fast delivery',
cta: 'Shop now',
},
];
export const HomeScreen: React.FC = () => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NavProp>();
const dispatch = useAppDispatch();
const { products, isLoading, categories } = useAppSelector(
state => state.home,
);
const [selectedCategory, setSelectedCategory] = useState('all');
const [activeBanner, setActiveBanner] = useState(0);
useEffect(() => {
dispatch(getAllProductsThunk());
dispatch(fetchCustomerDetails());
dispatch(getAllCategoriesThunk());
}, [dispatch]);
useFocusEffect(
useCallback(() => {
dispatch(getAllOffersThunk());
}, [dispatch]),
);
const filteredProducts =
selectedCategory === 'all'
? products
: products.filter(p => p.category?.name === selectedCategory);
const handleSearchFocus = useCallback(() => {
navigation.navigate('SearchScreen');
}, [navigation]);
const handleBannerScroll = useCallback(
(e: NativeSyntheticEvent<NativeScrollEvent>) => {
const index = Math.round(e.nativeEvent.contentOffset.x / BANNER_STEP);
setActiveBanner(index);
},
[],
);
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
{/* Top bar */}
<View style={styles.topBar}>
<TouchableOpacity style={styles.addressBar} activeOpacity={0.7}>
<View style={styles.pinBadge}>
<Text style={styles.pinEmoji}>📍</Text>
</View>
<View style={styles.addressTextWrap}>
<Text style={styles.addressLabel}>Deliver to</Text>
<View style={styles.addressRow}>
<Text style={styles.addressText} numberOfLines={1}>
Koramangala 4th Block
</Text>
<Text style={styles.dropdownArrow}></Text>
</View>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.avatarButton}
activeOpacity={0.7}
onPress={() => navigation.navigate('CartScreen')}
>
<Text style={styles.avatarEmoji}>🧺</Text>
<View style={styles.notifDot} />
</TouchableOpacity>
</View>
{/* Search */}
<View style={styles.searchWrap}>
<SearchBar
value=""
onChangeText={() => {}}
placeholder="Search providers or items..."
onFocus={handleSearchFocus}
/>
</View>
{/* Promo carousel */}
<FlatList
data={PROMOS}
horizontal
keyExtractor={item => item.key}
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.bannerListContent}
snapToInterval={BANNER_STEP}
decelerationRate="fast"
onScroll={handleBannerScroll}
scrollEventThrottle={16}
renderItem={({ item }) => (
<View
style={[styles.bannerCard, { backgroundColor: item.colors[0] }]}
>
<View style={styles.bannerDecoCircleLg} />
<View style={styles.bannerDecoCircleSm} />
<View>
<Text style={styles.bannerEyebrow}>{item.eyebrow}</Text>
<Text style={styles.bannerText}>{item.title}</Text>
<Text style={styles.bannerSubtext}>{item.subtitle}</Text>
</View>
<View style={styles.bannerCta}>
<Text style={styles.bannerCtaText}>{item.cta}</Text>
</View>
</View>
)}
/>
<View style={styles.dotsRow}>
{PROMOS.map((_, i) => (
<View
key={i}
style={[styles.dot, i === activeBanner && styles.dotActive]}
/>
))}
</View>
{/* Categories */}
<View style={styles.categorySection}>
<FlatList
horizontal
data={CATEGORIES}
keyExtractor={item => item.key}
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.chipRow}
renderItem={({ item }) => {
const isSelected = selectedCategory === item.key;
return (
<TouchableOpacity
style={styles.categoryItem}
activeOpacity={0.75}
onPress={() => {
setSelectedCategory(item.key);
if (item.key !== 'all') {
navigation.navigate('ProviderListScreen', {
category: item.key,
});
}
}}
>
<View
style={[
styles.categoryCircle,
isSelected && styles.categoryCircleSelected,
]}
>
<Text style={styles.categoryIcon}>{item.icon}</Text>
</View>
<Text
style={[
styles.categoryLabel,
isSelected && styles.categoryLabelSelected,
]}
numberOfLines={1}
>
{item.label}
</Text>
</TouchableOpacity>
);
}}
/>
</View>
{/* Section header */}
<View style={styles.sectionHeaderRow}>
<View>
<Text style={styles.sectionTitle}>
{selectedCategory === 'all' ? 'Popular near you' : selectedCategory}
</Text>
<Text style={styles.sectionSubtitle}>
{filteredProducts.length} product
{filteredProducts.length === 1 ? '' : 's'} available
</Text>
</View>
<TouchableOpacity activeOpacity={0.7}>
<Text style={styles.seeAllText}>See all</Text>
</TouchableOpacity>
</View>
{/* Product list */}
<View style={styles.providerList}>
{filteredProducts.length === 0 && (
<View style={[styles.emptyWrap, { width: '100%' }]}>
<Text style={styles.emptyEmoji}>🛍</Text>
<Text style={styles.emptyText}>No products found</Text>
</View>
)}
{filteredProducts.map(product => (
<ProductCard
key={product.id}
id={product.id}
imageUrl={product.imageUrl}
name={product.name}
price={product.price}
compareAtPrice={product.compareAtPrice}
brand={product.brand || product.merchant?.name}
currency={product.currency === 'INR' ? '₹' : product.currency}
onPress={() => {
navigation.navigate('ProviderDetailsScreen', {
providerId: product.id,
providerName: product.name,
});
}}
/>
))}
</View>
<View style={{ height: 24 }} />
</ScrollView>
);
};