378 lines
11 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, { useEffect, useMemo, useState } from 'react';
import {
View,
Text,
TouchableOpacity,
ScrollView,
Image,
ImageBackground,
} from 'react-native';
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { getStyles } from './providerDetailsScreen.styles';
import { CatalogItemRow } from '@components';
import { useAppTheme } from '@theme';
import { useAppDispatch, useAppSelector } from '../../../hooks/useAppDispatch';
import { addItem } from '../../../store/commonreducers/cart';
import { getProviderCatalogApi, getProvidersApi } from '../../../api/deliveryApi';
import { CatalogItem, Provider } from '../../../interfaces';
import { AppStackParamList } from '../../../navigation/appStack';
type ProviderDetailsNavProp = StackNavigationProp<
AppStackParamList,
'ProviderDetailsScreen'
>;
type ProviderDetailsRouteProp = RouteProp<
AppStackParamList,
'ProviderDetailsScreen'
>;
const FALLBACK_CATEGORIES = ['Pizza', 'Sides', 'Beverages'];
const OFFERS = [
{ key: 'o1', icon: '🏷️', label: '50% OFF up to ₹100' },
{ key: 'o2', icon: '🚚', label: 'Free delivery above ₹299' },
{ key: 'o3', icon: '🎁', label: 'Buy 1 Get 1 on combos' },
];
// ---------------------------------------------------------------------------
// Presentational subcomponents
// Kept local to this screen since none are reused elsewhere yet. Promote to
// @components if a second screen needs them.
// ---------------------------------------------------------------------------
interface HeroSectionProps {
styles: ReturnType<typeof getStyles>;
imageUrl?: string;
onBack: () => void;
}
const HeroSection: React.FC<HeroSectionProps> = ({
styles,
imageUrl,
onBack,
}) => (
<View style={styles.heroWrap}>
{imageUrl ? (
<ImageBackground source={{ uri: imageUrl }} style={styles.heroImage}>
<View style={styles.heroOverlay} />
</ImageBackground>
) : (
<View style={styles.heroFallback}>
<Text style={styles.heroFallbackEmoji}>🍽</Text>
<View style={styles.heroOverlay} />
</View>
)}
<View style={styles.topIconRow}>
<TouchableOpacity
style={styles.iconButton}
activeOpacity={0.75}
onPress={onBack}
>
<Text style={styles.iconButtonText}></Text>
</TouchableOpacity>
<View style={styles.iconButtonGroup}>
<TouchableOpacity
style={[styles.iconButton, { marginRight: 10 }]}
activeOpacity={0.75}
>
<Text style={styles.iconButtonText}>🔗</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.iconButton} activeOpacity={0.75}>
<Text style={styles.iconButtonText}>🤍</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
interface InfoCardProps {
styles: ReturnType<typeof getStyles>;
name: string;
rating: number;
deliveryTime: string;
tag: string;
}
const InfoCard: React.FC<InfoCardProps> = ({
styles,
name,
rating,
deliveryTime,
tag,
}) => (
<View style={styles.infoCard}>
<View style={styles.infoTopRow}>
<Text style={styles.heroName} numberOfLines={1}>
{name}
</Text>
<View style={styles.ratingBadge}>
<Text style={{ fontSize: 11 }}></Text>
<Text style={styles.ratingBadgeText}>{rating.toFixed(1)}</Text>
</View>
</View>
<Text style={styles.cuisineText}>{tag} Multi-cuisine</Text>
<View style={styles.metaRow}>
<View style={styles.metaItem}>
<Text style={styles.metaIcon}>🕐</Text>
<Text style={styles.metaText}>{deliveryTime}</Text>
</View>
<View style={styles.metaDivider} />
<View style={styles.metaItem}>
<Text style={styles.metaIcon}>📍</Text>
<Text style={styles.metaText}>2.4 km away</Text>
</View>
</View>
<View style={styles.statusRow}>
<View style={styles.statusDot} />
<Text style={styles.statusText}>Open now</Text>
<Text style={styles.statusTextMuted}> Closes 11:30 PM</Text>
</View>
</View>
);
interface OffersRowProps {
styles: ReturnType<typeof getStyles>;
}
const OffersRow: React.FC<OffersRowProps> = ({ styles }) => (
<View style={styles.offersSection}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.offersScrollContent}
>
{OFFERS.map(offer => (
<View key={offer.key} style={styles.offerChip}>
<Text style={styles.offerIcon}>{offer.icon}</Text>
<Text style={styles.offerText}>{offer.label}</Text>
</View>
))}
</ScrollView>
</View>
);
interface CategoryTabsProps {
styles: ReturnType<typeof getStyles>;
categories: string[];
activeCategory: string;
onSelect: (category: string) => void;
}
const CategoryTabs: React.FC<CategoryTabsProps> = ({
styles,
categories,
activeCategory,
onSelect,
}) => (
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.categoryRow}
>
{categories.map(cat => {
const isActive = activeCategory === cat;
return (
<TouchableOpacity
key={cat}
style={[styles.categoryTab, isActive && styles.categoryTabActive]}
onPress={() => onSelect(cat)}
activeOpacity={0.75}
>
<Text
style={[
styles.categoryTabText,
isActive && styles.categoryTabTextActive,
]}
>
{cat}
</Text>
</TouchableOpacity>
);
})}
</ScrollView>
);
interface CartStripProps {
styles: ReturnType<typeof getStyles>;
totalItems: number;
totalPrice: number;
onPress: () => void;
}
const CartStrip: React.FC<CartStripProps> = ({
styles,
totalItems,
totalPrice,
onPress,
}) => (
<View style={styles.cartStripWrap}>
<TouchableOpacity
style={styles.cartStrip}
activeOpacity={0.85}
onPress={onPress}
>
<View style={styles.cartStripLeft}>
<Text style={styles.cartBagIcon}>🛍</Text>
<View style={styles.cartStripTextWrap}>
<Text style={styles.cartStripCount}>
{totalItems} item{totalItems === 1 ? '' : 's'}
</Text>
<Text style={styles.cartStripPrice}>{totalPrice}</Text>
</View>
</View>
<View style={styles.viewCartButton}>
<Text style={styles.viewCartText}>View Cart</Text>
<Text style={styles.viewCartArrow}></Text>
</View>
</TouchableOpacity>
</View>
);
// ---------------------------------------------------------------------------
// Screen
// ---------------------------------------------------------------------------
export const ProviderDetailsScreen: React.FC = () => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const dispatch = useAppDispatch();
const navigation = useNavigation<ProviderDetailsNavProp>();
const route = useRoute<ProviderDetailsRouteProp>();
const { providerId, providerName } = route.params;
const cartItems = useAppSelector(state => state.cart.items);
const [provider, setProvider] = useState<Provider | null>(null);
const [catalog, setCatalog] = useState<CatalogItem[]>([]);
const [activeCategory, setActiveCategory] = useState(FALLBACK_CATEGORIES[0]);
const resolvedProviderId = providerId || 'p1';
const resolvedProviderName = providerName || 'Pizza Planet';
useEffect(() => {
getProviderCatalogApi(resolvedProviderId).then(setCatalog);
}, [resolvedProviderId]);
useEffect(() => {
getProvidersApi().then(providers => {
const match = providers.find(p => p.id === resolvedProviderId);
if (match) setProvider(match);
});
}, [resolvedProviderId]);
const categories = useMemo(() => {
const fromCatalog = Array.from(new Set(catalog.map(item => item.category)));
return fromCatalog.length > 0 ? fromCatalog : FALLBACK_CATEGORIES;
}, [catalog]);
useEffect(() => {
if (!categories.includes(activeCategory)) {
setActiveCategory(categories[0]);
}
}, [categories, activeCategory]);
const filteredItems = useMemo(
() => catalog.filter(item => item.category === activeCategory),
[catalog, activeCategory],
);
const getItemQuantity = (itemId: string) => {
const match = cartItems.find(i => i.item.id === itemId);
return match ? match.quantity : 0;
};
const totalItems = cartItems.reduce((sum, i) => sum + i.quantity, 0);
const totalPrice = cartItems.reduce(
(sum, i) => sum + i.item.price * i.quantity,
0,
);
const handleAddItem = (item: CatalogItem) => {
dispatch(
addItem({
providerId: resolvedProviderId,
providerName: resolvedProviderName,
item,
}),
);
};
return (
<View style={styles.container}>
<ScrollView
style={styles.content}
contentContainerStyle={styles.contentBody}
showsVerticalScrollIndicator={false}
>
<HeroSection
styles={styles}
imageUrl={provider?.imageUrl}
onBack={() => navigation.goBack()}
/>
<InfoCard
styles={styles}
name={resolvedProviderName}
rating={provider?.rating ?? 4.5}
deliveryTime={provider?.deliveryTime ?? '25 min'}
tag={provider?.tag ?? 'Italian'}
/>
<OffersRow styles={styles} />
<View style={styles.sectionDivider} />
<View style={styles.menuHeaderRow}>
<Text style={styles.menuTitle}>Menu</Text>
<Text style={styles.menuCount}>{catalog.length} items</Text>
</View>
<CategoryTabs
styles={styles}
categories={categories}
activeCategory={activeCategory}
onSelect={setActiveCategory}
/>
<View style={styles.menuList}>
{filteredItems.length === 0 && (
<View style={styles.emptyMenuWrap}>
<Text style={styles.emptyMenuEmoji}>🍽</Text>
<Text style={styles.emptyMenuText}>
No items in this category yet
</Text>
</View>
)}
{filteredItems.map(item => (
<CatalogItemRow
key={item.id}
imageUrl={item.imageUrl}
title={item.title}
description={item.description}
price={item.price}
quantity={getItemQuantity(item.id)}
onAdd={() => handleAddItem(item)}
onRemove={() => {}}
/>
))}
</View>
</ScrollView>
{totalItems > 0 && (
<CartStrip
styles={styles}
totalItems={totalItems}
totalPrice={totalPrice}
onPress={() => navigation.navigate('CartScreen')}
/>
)}
</View>
);
};