Files
appcakes-builds/src/pages/Home.tsx
T
2026-05-27 17:01:31 +00:00

275 lines
7.6 KiB
TypeScript

import React, { useEffect, useMemo, useState } from 'react';
import {
IonButton,
IonCard,
IonCardContent,
IonChip,
IonContent,
IonHeader,
IonIcon,
IonPage,
IonSearchbar,
IonText,
IonTitle,
IonToolbar,
useIonViewWillEnter,
} from '@ionic/react';
import {
bagHandleOutline,
cartOutline,
receiptOutline,
searchOutline,
star,
} from 'ionicons/icons';
import { useHistory } from 'react-router-dom';
import {
setStatusBarBackground,
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';
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: 'Ready to ship',
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: 'Baked today',
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: 'Picked fresh',
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: 'Stocked daily',
image: farmEggsImage,
imageAlt: 'Carton of free range farm eggs on a clean 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 Home: React.FC = () => {
const history = useHistory();
const [query, setQuery] = useState('');
const [cartCount, setCartCount] = useState(0);
useIonViewWillEnter(() => {
setStatusBarStyle(Style.Dark);
setStatusBarBackground('#fff8f2');
const count = readCart().reduce((sum, item) => sum + item.quantity, 0);
setCartCount(count);
});
useEffect(() => {
const syncCount = () => {
const count = readCart().reduce((sum, item) => sum + item.quantity, 0);
setCartCount(count);
};
syncCount();
window.addEventListener('storage', syncCount);
window.addEventListener('cart:updated', syncCount as EventListener);
return () => {
window.removeEventListener('storage', syncCount);
window.removeEventListener('cart:updated', syncCount as EventListener);
};
}, []);
const filteredProducts = useMemo(() => {
const term = query.toLowerCase().trim();
return products.filter((product) => {
if (!term) return true;
return [product.name, product.category, product.description]
.join(' ')
.toLowerCase()
.includes(term);
});
}, [query]);
return (
<IonPage>
<IonHeader className="shop-header ion-no-border">
<IonToolbar>
<IonTitle>QuickCart</IonTitle>
<IonButton
slot="end"
fill="clear"
className="header-icon-button"
onClick={() => history.push('/orders')}
aria-label="Track orders"
>
<IonIcon icon={receiptOutline} />
</IonButton>
<IonButton
slot="end"
fill="clear"
className="cart-button pill-cart-button"
onClick={() => history.push('/cart')}
aria-label="View cart"
>
<IonIcon icon={cartOutline} />
<span>{cartCount}</span>
</IonButton>
</IonToolbar>
</IonHeader>
<IonContent fullscreen className="shop-content">
<div className="hero-card">
<IonChip color="primary">Fast delivery</IonChip>
<h1>Find your next favorite meal.</h1>
<p>
Browse simple picks, add them to your cart, and place your order in
seconds.
</p>
</div>
<div className="catalog-section ion-padding">
<IonSearchbar
value={query}
onIonInput={(event) => setQuery(event.detail.value ?? '')}
placeholder="Search products"
searchIcon={searchOutline}
/>
<div className="section-heading">
<div>
<h2>Popular products</h2>
<IonText color="medium">
<p>{filteredProducts.length} items available</p>
</IonText>
</div>
</div>
{filteredProducts.length === 0 ? (
<div className="empty-state">
<IonIcon icon={bagHandleOutline} />
<h3>No products found</h3>
<p>Try a different search term to browse what is available.</p>
<IonButton onClick={() => setQuery('')}>Clear search</IonButton>
</div>
) : (
<div className="product-grid">
{filteredProducts.map((product) => (
<IonCard
key={product.id}
button
className="product-card"
onClick={() => history.push(`/product/${product.id}`)}
>
<img
src={product.image}
alt={product.imageAlt}
className="product-card-image"
/>
<IonCardContent>
<div className="product-card-top">
<IonChip color="secondary">{product.category}</IonChip>
<div className="rating-pill">
<IonIcon icon={star} />
<span>{product.rating}</span>
</div>
</div>
<h3>{product.name}</h3>
<p>{product.description}</p>
<div className="product-card-footer">
<strong>${product.price.toFixed(2)}</strong>
<span>{product.prepTime}</span>
</div>
</IonCardContent>
</IonCard>
))}
</div>
)}
</div>
</IonContent>
</IonPage>
);
};
export default Home;