import React, { useEffect, useMemo, useState } from 'react'; import { IonButton, IonChip, IonContent, IonHeader, IonIcon, IonPage, IonSearchbar, IonText, IonTitle, IonToolbar, IonToast, useIonViewWillEnter, } from '@ionic/react'; import { bagHandleOutline, cartOutline, chevronForwardOutline, homeOutline, locationOutline, micOutline, personCircleOutline, receiptOutline, searchOutline, storefrontOutline, timeOutline, } from 'ionicons/icons'; import { useHistory } from 'react-router-dom'; import ProductFilters, { SortOption } from '../components/ProductFilters'; import { useAuth } from '../context/AuthContext'; import { setStatusBarStyle, Style } from '../utils/statusBar'; import avocadoOilImage from '../assets/organic-avocado-oil.png'; import sourdoughBreadImage from '../assets/artisan-sourdough-bread.png'; import strawberriesPackImage from '../assets/fresh-strawberries-pack.png'; import farmEggsImage from '../assets/farm-eggs-carton.png'; import bananasImage from '../assets/bananas-bunch.png'; import wholeMilkImage from '../assets/whole-milk-carton.png'; import brownRiceImage from '../assets/brown-rice-bag.png'; import chickenBreastImage from '../assets/chicken-breast-pack.png'; import orangeJuiceImage from '../assets/orange-juice-bottle.png'; import rolledOatsImage from '../assets/rolled-oats-pack.png'; import greekYogurtImage from '../assets/greek-yogurt-cup.png'; import peanutButterImage from '../assets/peanut-butter-jar.png'; import spaghettiImage from '../assets/pasta-spaghetti-pack.png'; import tomatoSauceImage from '../assets/tomato-sauce-jar.png'; import cheddarCheeseImage from '../assets/cheddar-cheese-block.png'; import lettuceImage from '../assets/lettuce-head.png'; import carrotsImage from '../assets/carrots-bag.png'; import potatoesImage from '../assets/potatoes-bag.png'; import onionsImage from '../assets/onions-net-bag.png'; import salmonImage from '../assets/salmon-fillet-pack.png'; import sparklingWaterImage from '../assets/sparkling-water-pack.png'; import coffeeBeansImage from '../assets/coffee-beans-bag.png'; import greenTeaImage from '../assets/green-tea-box.png'; import oliveOilImage from '../assets/olive-oil-bottle.png'; export type Product = { id: string; name: string; category: string; description: string; price: number; rating: number; prepTime: string; image: string; imageAlt: string; }; export type CartItem = { id: string; name: string; price: number; quantity: number; }; export const CART_STORAGE_KEY = 'simple-shop-cart'; export const LAST_ORDER_STORAGE_KEY = 'simple-shop-last-order'; export const products: Product[] = [ { id: '1', name: 'Organic Avocado Oil', category: 'Pantry', description: 'Cold-pressed avocado oil for cooking, roasting, and healthy everyday meals.', price: 8.99, rating: 4.8, prepTime: '60 MIN', image: avocadoOilImage, imageAlt: 'Bottle of organic avocado oil with fresh avocados', }, { id: '2', name: 'Artisan Sourdough Bread', category: 'Bakery', description: 'Freshly baked sourdough loaf with a crisp crust and soft, airy center.', price: 7.49, rating: 4.6, prepTime: '45 MIN', image: sourdoughBreadImage, imageAlt: 'Fresh artisan sourdough bread loaf on a wooden board', }, { id: '3', name: 'Fresh Strawberries Pack', category: 'Fruit', description: 'Sweet, bright strawberries packed fresh for snacks, desserts, and smoothies.', price: 9.25, rating: 4.9, prepTime: '35 MIN', image: strawberriesPackImage, imageAlt: 'Pack of fresh strawberries with berries beside it', }, { id: '4', name: 'Free Range Farm Eggs', category: 'Dairy & Eggs', description: 'Farm-fresh eggs with rich yolks, ideal for breakfast, baking, and meal prep.', price: 4.5, rating: 4.7, prepTime: '55 MIN', image: farmEggsImage, imageAlt: 'Carton of free range farm eggs on a clean background', }, { id: '5', name: 'Bananas Bunch', category: 'Fruit', description: 'Naturally sweet ripe bananas for breakfast bowls, snacks, and smoothies.', price: 3.2, rating: 4.7, prepTime: '40 MIN', image: bananasImage, imageAlt: 'Fresh bunch of ripe yellow bananas', }, { id: '6', name: 'Whole Milk Carton', category: 'Dairy & Eggs', description: 'Creamy whole milk for cereal, coffee, cooking, and family breakfasts.', price: 3.99, rating: 4.5, prepTime: '30 MIN', image: wholeMilkImage, imageAlt: 'Carton of whole milk standing upright', }, { id: '7', name: 'Brown Rice Bag', category: 'Pantry', description: 'Nutty wholegrain brown rice for bowls, sides, and meal prep dinners.', price: 5.75, rating: 4.6, prepTime: '50 MIN', image: brownRiceImage, imageAlt: 'Sealed bag of brown rice on a white background', }, { id: '8', name: 'Chicken Breast Pack', category: 'Meat & Seafood', description: 'Lean chicken breast fillets for grilling, roasting, and protein-packed meals.', price: 11.9, rating: 4.8, prepTime: '70 MIN', image: chickenBreastImage, imageAlt: 'Pack of fresh chicken breast fillets', }, { id: '9', name: 'Orange Juice Bottle', category: 'Beverages', description: 'Bright citrus orange juice for a refreshing breakfast or midday boost.', price: 4.8, rating: 4.6, prepTime: '25 MIN', image: orangeJuiceImage, imageAlt: 'Bottle of orange juice on a clean background', }, { id: '10', name: 'Rolled Oats Pack', category: 'Breakfast', description: 'Hearty rolled oats for porridge, overnight oats, and healthy baking.', price: 4.25, rating: 4.7, prepTime: '35 MIN', image: rolledOatsImage, imageAlt: 'Pack of rolled oats on a white background', }, { id: '11', name: 'Greek Yogurt Cup', category: 'Dairy & Eggs', description: 'Thick plain Greek yogurt with a rich texture for breakfast and snacks.', price: 2.65, rating: 4.5, prepTime: '20 MIN', image: greekYogurtImage, imageAlt: 'Single cup of plain Greek yogurt', }, { id: '12', name: 'Peanut Butter Jar', category: 'Pantry', description: 'Smooth peanut butter for toast, sandwiches, smoothies, and baking.', price: 5.2, rating: 4.8, prepTime: '45 MIN', image: peanutButterImage, imageAlt: 'Jar of peanut butter on a white background', }, { id: '13', name: 'Spaghetti Pasta Pack', category: 'Pantry', description: 'Classic spaghetti pasta for quick weeknight dinners and family meals.', price: 3.45, rating: 4.6, prepTime: '50 MIN', image: spaghettiImage, imageAlt: 'Pack of spaghetti pasta on a clean background', }, { id: '14', name: 'Tomato Sauce Jar', category: 'Pantry', description: 'Rich tomato sauce for pasta, pizza bases, and simmered home cooking.', price: 3.95, rating: 4.7, prepTime: '55 MIN', image: tomatoSauceImage, imageAlt: 'Glass jar of tomato sauce', }, { id: '15', name: 'Cheddar Cheese Block', category: 'Dairy & Eggs', description: 'Sharp cheddar cheese block for sandwiches, snacks, sauces, and grating.', price: 6.15, rating: 4.7, prepTime: '35 MIN', image: cheddarCheeseImage, imageAlt: 'Block of cheddar cheese on a white background', }, { id: '16', name: 'Fresh Lettuce Head', category: 'Vegetables', description: 'Crisp green lettuce for salads, wraps, burgers, and light lunches.', price: 2.8, rating: 4.4, prepTime: '30 MIN', image: lettuceImage, imageAlt: 'Fresh green head of lettuce', }, { id: '17', name: 'Carrots Bag', category: 'Vegetables', description: 'Sweet crunchy carrots for roasting, soups, stir-fries, and lunch boxes.', price: 3.1, rating: 4.5, prepTime: '45 MIN', image: carrotsImage, imageAlt: 'Bag of fresh carrots on a clean background', }, { id: '18', name: 'Potatoes Bag', category: 'Vegetables', description: 'Versatile potatoes for frying, mashing, baking, and hearty dinners.', price: 4.75, rating: 4.6, prepTime: '55 MIN', image: potatoesImage, imageAlt: 'Mesh bag of potatoes', }, { id: '19', name: 'Onions Net Bag', category: 'Vegetables', description: 'Flavorful onions for stews, sauces, sautés, and everyday cooking.', price: 3.35, rating: 4.4, prepTime: '50 MIN', image: onionsImage, imageAlt: 'Net bag of onions on a white background', }, { id: '20', name: 'Salmon Fillet Pack', category: 'Meat & Seafood', description: 'Fresh salmon fillet packed for quick oven dinners and healthy lunches.', price: 13.99, rating: 4.8, prepTime: '70 MIN', image: salmonImage, imageAlt: 'Packaged salmon fillet on a clean background', }, { id: '21', name: 'Sparkling Water Pack', category: 'Beverages', description: 'Refreshing sparkling water multipack for hydration and light entertaining.', price: 6.8, rating: 4.5, prepTime: '25 MIN', image: sparklingWaterImage, imageAlt: 'Multipack of sparkling water cans', }, { id: '22', name: 'Coffee Beans Bag', category: 'Beverages', description: 'Roasted coffee beans with a rich aroma for espresso and filter brews.', price: 9.6, rating: 4.8, prepTime: '40 MIN', image: coffeeBeansImage, imageAlt: 'Bag of roasted coffee beans on a white background', }, { id: '23', name: 'Green Tea Box', category: 'Beverages', description: 'Soothing green tea for a light daily ritual and calming warm drink.', price: 4.4, rating: 4.5, prepTime: '30 MIN', image: greenTeaImage, imageAlt: 'Box of green tea on a clean background', }, { id: '24', name: 'Extra Virgin Olive Oil', category: 'Pantry', description: 'Fragrant olive oil for salads, drizzling, roasting, and Mediterranean meals.', price: 10.5, rating: 4.9, prepTime: '60 MIN', image: oliveOilImage, imageAlt: 'Bottle of extra virgin olive oil on a white background', }, ]; export const readCart = (): CartItem[] => { if (typeof window === 'undefined') return []; try { const savedCart = window.localStorage.getItem(CART_STORAGE_KEY); if (!savedCart) return []; const parsed = JSON.parse(savedCart) as CartItem[]; return Array.isArray(parsed) ? parsed : []; } catch { return []; } }; export const writeCart = (cart: CartItem[]) => { if (typeof window === 'undefined') return; window.localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(cart)); window.dispatchEvent(new CustomEvent('cart:updated')); }; const featuredSectionIds = ['12', '11', '23', '1', '24']; const homeLifestyleSectionIds = ['6', '9', '21', '22', '10']; const STORE_OPEN_HOUR = 8; const STORE_CLOSE_HOUR = 20; const formatPlacedTime = (date: Date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false, }); const formatHourSlot = (hour24: number) => { const normalized = ((hour24 % 24) + 24) % 24; const hour12 = normalized % 12 === 0 ? 12 : normalized % 12; const suffix = normalized >= 12 ? 'PM' : 'AM'; return `${hour12} ${suffix}`; }; const formatDeliveryWindow = (startHour24: number, endHour24: number) => `${formatHourSlot(startHour24)}-${formatHourSlot(endHour24)}`; const getDeliveryTiming = (now: Date) => { const currentHour = now.getHours(); let slotStart = STORE_OPEN_HOUR; let slotEnd = STORE_OPEN_HOUR + 1; if (currentHour < STORE_OPEN_HOUR) { slotStart = STORE_OPEN_HOUR; slotEnd = STORE_OPEN_HOUR + 1; } else if (currentHour >= STORE_CLOSE_HOUR - 1) { slotStart = STORE_OPEN_HOUR; slotEnd = STORE_OPEN_HOUR + 1; } else { slotStart = currentHour + 1; slotEnd = Math.min(slotStart + 1, STORE_CLOSE_HOUR); } const etaHour = slotStart; const etaMinute = now.getMinutes() < 30 ? 15 : 45; const etaDate = new Date(now); etaDate.setHours(etaHour, etaMinute, 0, 0); if (currentHour >= STORE_CLOSE_HOUR - 1) { etaDate.setDate(etaDate.getDate() + 1); } return { placedLabel: formatPlacedTime(now), windowLabel: formatDeliveryWindow(slotStart, slotEnd), etaLabel: etaDate.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', hour12: true, }), }; }; const ProductShelf: React.FC<{ title: string; products: Product[]; onOpenProduct: (id: string) => void; onQuickAdd?: (product: Product) => void; }> = ({ title, products: shelfProducts, onOpenProduct, onQuickAdd }) => ( {title} View All {shelfProducts.map((product) => ( onOpenProduct(product.id)} > { event.stopPropagation(); onQuickAdd?.(product); }} aria-label={`Add ${product.name} to cart`} > + {product.category} {product.name} ${product.price.toFixed(2)} {product.description} {product.prepTime} ))} ); const Home: React.FC = () => { const history = useHistory(); const { user } = useAuth(); const [query, setQuery] = useState(''); const [now, setNow] = useState(() => new Date()); const [cartCount, setCartCount] = useState(0); const [selectedCategory, setSelectedCategory] = useState('All'); const [sortOption, setSortOption] = useState('popular'); const [isFilterOpen, setIsFilterOpen] = useState(false); const [toastMessage, setToastMessage] = useState(''); useIonViewWillEnter(() => { setStatusBarStyle(Style.Dark); const count = readCart().reduce((sum, item) => sum + item.quantity, 0); setCartCount(count); }); useEffect(() => { const syncCount = () => setCartCount(readCart().reduce((sum, item) => sum + item.quantity, 0)); syncCount(); window.addEventListener('storage', syncCount); window.addEventListener('cart:updated', syncCount as EventListener); return () => { window.removeEventListener('storage', syncCount); window.removeEventListener('cart:updated', syncCount as EventListener); }; }, []); useEffect(() => { const interval = window.setInterval(() => { setNow(new Date()); }, 60000); return () => window.clearInterval(interval); }, []); const categories = useMemo( () => Array.from(new Set(products.map((product) => product.category))), [] ); const filteredProducts = useMemo(() => { const term = query.toLowerCase().trim(); const nextProducts = products.filter((product) => { const matchesCategory = selectedCategory === 'All' || product.category === selectedCategory; const matchesQuery = !term || [product.name, product.category, product.description] .join(' ') .toLowerCase() .includes(term); return matchesCategory && matchesQuery; }); const sorted = [...nextProducts]; if (sortOption === 'price-asc') sorted.sort((a, b) => a.price - b.price); if (sortOption === 'price-desc') sorted.sort((a, b) => b.price - a.price); if (sortOption === 'name-asc') { sorted.sort((a, b) => a.name.localeCompare(b.name)); } if (sortOption === 'popular') sorted.sort((a, b) => b.rating - a.rating); return sorted; }, [query, selectedCategory, sortOption]); const featuredProducts = useMemo( () => products.filter((product) => featuredSectionIds.includes(product.id)), [] ); const lifestyleProducts = useMemo( () => products.filter((product) => homeLifestyleSectionIds.includes(product.id) ), [] ); const addressLabel = '22 Acacia St'; const { placedLabel, windowLabel, etaLabel } = getDeliveryTiming(now); const handleQuickAdd = (product: Product) => { const cart = readCart(); const existingItem = cart.find((item) => item.id === product.id); const nextCart = existingItem ? cart.map((item) => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item ) : [ ...cart, { id: product.id, name: product.name, price: product.price, quantity: 1, }, ]; writeCart(nextCart); setCartCount(nextCart.reduce((sum, item) => sum + item.quantity, 0)); setToastMessage(`${product.name} added to basket`); }; return ( Deliver to {addressLabel} Open history.push('/cart')} aria-label="Open basket" > {cartCount} Fresh groceries, fast Shop today's picks Curated essentials delivered in your next available slot. Placed {placedLabel} Delivery {windowLabel} setQuery(event.detail.value ?? '')} placeholder="Search products and brands" searchIcon={searchOutline} /> history.push('/orders')} > Order tracking Order placed ETA {etaLabel} history.push(`/product/${id}`)} onQuickAdd={handleQuickAdd} /> history.push(`/product/${id}`)} onQuickAdd={handleQuickAdd} /> Browse everything {filteredProducts.length} products available setIsFilterOpen((value) => !value)} /> {filteredProducts.length === 0 ? ( No products found Try a different search term or category. { setQuery(''); setSelectedCategory('All'); }} > Clear filters ) : ( {filteredProducts.map((product) => ( history.push(`/product/${product.id}`)} > { event.stopPropagation(); handleQuickAdd(product); }} aria-label={`Add ${product.name} to cart`} > + {product.category} {product.name} ${product.price.toFixed(2)} {product.prepTime} {product.description} ))} )} setToastMessage('')} /> Home setIsFilterOpen(true)} > Discover history.push('/orders')} > My Shop history.push(user ? '/profile' : '/auth')} > My Profile history.push('/cart')} > Basket ); }; export default Home;
{product.description}
Curated essentials delivered in your next available slot.
ETA {etaLabel}
{filteredProducts.length} products available
Try a different search term or category.