269 lines
8.2 KiB
TypeScript
269 lines
8.2 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 { ProviderCard, SearchBar } from '@components';
|
||
import { useAppTheme } from '@theme';
|
||
import { getProvidersApi } from '../../../api/deliveryApi';
|
||
import { Provider } from '../../../interfaces';
|
||
import { AppStackParamList } from '../../../navigation/appStack';
|
||
import { MainTabParamList } from '../../../navigation/mainTabNavigator';
|
||
|
||
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 [providers, setProviders] = useState<Provider[]>([]);
|
||
const [selectedCategory, setSelectedCategory] = useState('all');
|
||
const [activeBanner, setActiveBanner] = useState(0);
|
||
|
||
useEffect(() => {
|
||
getProvidersApi().then(setProviders);
|
||
}, []);
|
||
|
||
const filteredProviders =
|
||
selectedCategory === 'all'
|
||
? providers
|
||
: providers.filter(p => p.tag === 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}>
|
||
<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}>
|
||
{filteredProviders.length} place
|
||
{filteredProviders.length === 1 ? '' : 's'} delivering to you
|
||
</Text>
|
||
</View>
|
||
<TouchableOpacity activeOpacity={0.7}>
|
||
<Text style={styles.seeAllText}>See all</Text>
|
||
</TouchableOpacity>
|
||
</View>
|
||
|
||
{/* Provider list */}
|
||
<View style={styles.providerList}>
|
||
{filteredProviders.length === 0 && (
|
||
<View style={styles.emptyWrap}>
|
||
<Text style={styles.emptyEmoji}>🍽️</Text>
|
||
<Text style={styles.emptyText}>
|
||
No providers in this category yet
|
||
</Text>
|
||
</View>
|
||
)}
|
||
|
||
{filteredProviders.map(provider => (
|
||
<ProviderCard
|
||
key={provider.id}
|
||
imageUrl={provider.imageUrl}
|
||
name={provider.name}
|
||
rating={provider.rating}
|
||
deliveryTime={provider.deliveryTime}
|
||
tag={provider.tag}
|
||
discountText={provider.discountText}
|
||
onPress={() =>
|
||
navigation.navigate('ProviderDetailsScreen', {
|
||
providerId: provider.id,
|
||
})
|
||
}
|
||
/>
|
||
))}
|
||
</View>
|
||
|
||
<View style={{ height: 24 }} />
|
||
</ScrollView>
|
||
);
|
||
};
|