build: d805a69a-e437-4a58-a083-30e34be74fcd
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user