276 lines
8.5 KiB
TypeScript
276 lines
8.5 KiB
TypeScript
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||
import {
|
||
View,
|
||
Text,
|
||
ScrollView,
|
||
TouchableOpacity,
|
||
FlatList,
|
||
Image,
|
||
NativeSyntheticEvent,
|
||
NativeScrollEvent,
|
||
Dimensions,
|
||
} from 'react-native';
|
||
import {
|
||
useNavigation,
|
||
CompositeNavigationProp,
|
||
} 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 { useAppDispatch, useAppSelector } from '@store';
|
||
import { 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 } = useAppSelector(state => state.home);
|
||
|
||
const [selectedCategory, setSelectedCategory] = useState('all');
|
||
const [activeBanner, setActiveBanner] = useState(0);
|
||
|
||
useEffect(() => {
|
||
dispatch(getAllProductsThunk());
|
||
}, [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>
|
||
);
|
||
};
|