211 lines
5.9 KiB
TypeScript
211 lines
5.9 KiB
TypeScript
import React, { useEffect, useMemo, useState } from 'react';
|
|
import {
|
|
IonBadge,
|
|
IonButton,
|
|
IonButtons,
|
|
IonChip,
|
|
IonContent,
|
|
IonHeader,
|
|
IonIcon,
|
|
IonPage,
|
|
IonText,
|
|
IonTitle,
|
|
IonToolbar,
|
|
useIonViewWillEnter,
|
|
} from '@ionic/react';
|
|
import { addOutline, cartOutline, removeOutline, star } from 'ionicons/icons';
|
|
import { RouteComponentProps, useHistory } from 'react-router-dom';
|
|
import { CartItem, products, readCart, writeCart } from './Home';
|
|
import {
|
|
setStatusBarBackground,
|
|
setStatusBarStyle,
|
|
Style,
|
|
} from '../utils/statusBar';
|
|
|
|
type Props = RouteComponentProps<{ id: string }>;
|
|
|
|
const ProductDetail: React.FC<Props> = ({ match }) => {
|
|
const history = useHistory();
|
|
const [quantity, setQuantity] = useState(1);
|
|
const [message, setMessage] = useState('');
|
|
const [cartCount, setCartCount] = useState(0);
|
|
|
|
useIonViewWillEnter(() => {
|
|
setStatusBarStyle(Style.Dark);
|
|
setStatusBarBackground('#ffffff');
|
|
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 product = useMemo(
|
|
() => products.find((item) => item.id === match.params.id),
|
|
[match.params.id]
|
|
);
|
|
|
|
const showMessage = (text: string) => {
|
|
setMessage(text);
|
|
window.setTimeout(() => setMessage(''), 4000);
|
|
};
|
|
|
|
const addToCart = () => {
|
|
if (!product) return;
|
|
|
|
const cart: CartItem[] = readCart();
|
|
const existing = cart.find((item) => item.id === product.id);
|
|
|
|
const nextCart = existing
|
|
? cart.map((item) =>
|
|
item.id === product.id
|
|
? { ...item, quantity: item.quantity + quantity }
|
|
: item
|
|
)
|
|
: [
|
|
...cart,
|
|
{
|
|
id: product.id,
|
|
name: product.name,
|
|
price: product.price,
|
|
quantity,
|
|
},
|
|
];
|
|
|
|
writeCart(nextCart);
|
|
showMessage('Added to cart');
|
|
};
|
|
|
|
if (!product) {
|
|
return (
|
|
<IonPage>
|
|
<IonHeader className="ion-no-border">
|
|
<IonToolbar>
|
|
<IonButtons slot="start">
|
|
<IonButton fill="clear" onClick={() => history.replace('/home')}>
|
|
Back
|
|
</IonButton>
|
|
</IonButtons>
|
|
<IonTitle>Product</IonTitle>
|
|
</IonToolbar>
|
|
</IonHeader>
|
|
<IonContent className="ion-padding">
|
|
<div className="empty-state">
|
|
<h3>Product not found</h3>
|
|
<p className="muted-text">This product is no longer available.</p>
|
|
<IonButton onClick={() => history.replace('/home')}>
|
|
Browse products
|
|
</IonButton>
|
|
</div>
|
|
</IonContent>
|
|
</IonPage>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<IonPage>
|
|
<IonHeader className="ion-no-border">
|
|
<IonToolbar>
|
|
<IonButtons slot="start">
|
|
<IonButton fill="clear" onClick={() => history.replace('/home')}>
|
|
Back
|
|
</IonButton>
|
|
</IonButtons>
|
|
<IonTitle>{product.name}</IonTitle>
|
|
<IonButtons slot="end">
|
|
<IonButton
|
|
fill="clear"
|
|
className="cart-button pill-cart-button"
|
|
onClick={() => history.push('/cart')}
|
|
>
|
|
<IonIcon icon={cartOutline} />
|
|
<span>{cartCount}</span>
|
|
</IonButton>
|
|
</IonButtons>
|
|
</IonToolbar>
|
|
</IonHeader>
|
|
<IonContent fullscreen>
|
|
<div className="detail-hero ion-padding">
|
|
<div className="detail-image-wrap">
|
|
<img
|
|
src={product.image}
|
|
alt={product.imageAlt}
|
|
className="detail-image"
|
|
/>
|
|
</div>
|
|
<IonChip color="secondary">{product.category}</IonChip>
|
|
<h1>{product.name}</h1>
|
|
<div className="detail-meta">
|
|
<IonBadge color="light">
|
|
<IonIcon icon={star} /> {product.rating}
|
|
</IonBadge>
|
|
<IonBadge color="light">Ready in {product.prepTime}</IonBadge>
|
|
</div>
|
|
<p className="muted-text">{product.description}</p>
|
|
<div className="detail-price">${product.price.toFixed(2)}</div>
|
|
</div>
|
|
|
|
<div className="detail-panel ion-padding">
|
|
<h2>Choose quantity</h2>
|
|
<div className="quantity-row">
|
|
<IonButton
|
|
fill="outline"
|
|
onClick={() => setQuantity((value) => Math.max(1, value - 1))}
|
|
>
|
|
<IonIcon icon={removeOutline} />
|
|
</IonButton>
|
|
<span>{quantity}</span>
|
|
<IonButton
|
|
fill="outline"
|
|
onClick={() => setQuantity((value) => value + 1)}
|
|
>
|
|
<IonIcon icon={addOutline} />
|
|
</IonButton>
|
|
</div>
|
|
|
|
<div className="order-box">
|
|
<div>
|
|
<IonText color="medium">
|
|
<p>Total</p>
|
|
</IonText>
|
|
<strong>${(product.price * quantity).toFixed(2)}</strong>
|
|
</div>
|
|
<IonButton expand="block" onClick={addToCart}>
|
|
Add to cart
|
|
</IonButton>
|
|
</div>
|
|
|
|
{message && (
|
|
<IonText color="success">
|
|
<p>{message}</p>
|
|
</IonText>
|
|
)}
|
|
|
|
<IonButton
|
|
fill="clear"
|
|
expand="block"
|
|
onClick={() => history.push('/cart')}
|
|
>
|
|
Go to cart
|
|
</IonButton>
|
|
</div>
|
|
</IonContent>
|
|
</IonPage>
|
|
);
|
|
};
|
|
|
|
export default ProductDetail;
|