update demo-ecommerce-example,
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
.categoryPage ion-toolbar {
|
||||||
|
|
||||||
|
--border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCard {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
/* min-height: 20rem !important; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCardActions {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCardAction {
|
||||||
|
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCardHeader {
|
||||||
|
|
||||||
|
min-height: 17rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCardHeader p {
|
||||||
|
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent ion-button {
|
||||||
|
|
||||||
|
height: 1.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent p {
|
||||||
|
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCard img {
|
||||||
|
|
||||||
|
/* border-radius: 5px; */
|
||||||
|
/* padding: 1rem; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.productPrice {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
@@ -0,0 +1,105 @@
|
|||||||
|
import { IonButton, IonCard, IonCardContent, IonCardHeader, IonCol, IonIcon } from '@ionic/react';
|
||||||
|
import { arrowRedoOutline, cart, cartOutline, heart, heartOutline } from 'ionicons/icons';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { addToCart } from '../data/CartStore';
|
||||||
|
import { addToFavourites, FavouritesStore } from '../data/FavouritesStore';
|
||||||
|
import styles from './ProductCard.module.css';
|
||||||
|
|
||||||
|
const ProductCard = (props): React.FC => {
|
||||||
|
const { product, category, index, cartRef } = props;
|
||||||
|
const favourites = FavouritesStore.useState((s) => s.product_ids);
|
||||||
|
|
||||||
|
const productCartRef = useRef();
|
||||||
|
const productFavouriteRef = useRef();
|
||||||
|
const [isFavourite, setIsFavourite] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tempIsFavourite = favourites.find((f) => f === `${category.slug}/${product.id}`);
|
||||||
|
setIsFavourite(tempIsFavourite ? true : false);
|
||||||
|
}, [props.product, favourites]);
|
||||||
|
|
||||||
|
const addProductToFavourites = (e, categorySlug, productID) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
addToFavourites(categorySlug, productID);
|
||||||
|
|
||||||
|
productFavouriteRef.current.style.display = '';
|
||||||
|
productFavouriteRef.current.classList.add('animate__fadeOutTopRight');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (productCartRef.current) {
|
||||||
|
productFavouriteRef.current.classList.remove('animate__fadeOutTopRight');
|
||||||
|
productFavouriteRef.current.style.display = 'none';
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addProductToCart = (e, categorySlug, productID) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
productCartRef.current.style.display = '';
|
||||||
|
productCartRef.current.classList.add('animate__fadeOutUp');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
cartRef.current.classList.add('animate__tada');
|
||||||
|
addToCart(categorySlug, productID);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
cartRef.current.classList.remove('animate__tada');
|
||||||
|
productCartRef.current.style.display = 'none';
|
||||||
|
}, 500);
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonCol size="6" key={`category_product_list_${index}`}>
|
||||||
|
<IonCard
|
||||||
|
routerLink={`/category/${category.slug}/${product.id}`}
|
||||||
|
className={styles.categoryCard}
|
||||||
|
>
|
||||||
|
<IonCardHeader className={styles.productCardHeader}>
|
||||||
|
<div className={styles.productCardActions}>
|
||||||
|
<IonIcon
|
||||||
|
className={styles.productCardAction}
|
||||||
|
color={isFavourite ? 'danger' : 'medium'}
|
||||||
|
icon={isFavourite ? heart : heartOutline}
|
||||||
|
onClick={(e) => addProductToFavourites(e, category.slug, product.id)}
|
||||||
|
/>
|
||||||
|
<IonIcon
|
||||||
|
ref={productFavouriteRef}
|
||||||
|
style={{ position: 'absolute', display: 'none' }}
|
||||||
|
className={`${styles.productCardAction} animate__animated`}
|
||||||
|
color="danger"
|
||||||
|
icon={heart}
|
||||||
|
/>
|
||||||
|
<IonIcon className={styles.productCardAction} size="medium" icon={arrowRedoOutline} />
|
||||||
|
</div>
|
||||||
|
<img src={product.image} alt="product pic" />
|
||||||
|
<p className="ion-text-wrap">{product.name}</p>
|
||||||
|
</IonCardHeader>
|
||||||
|
|
||||||
|
<IonCardContent className={styles.categoryCardContent}>
|
||||||
|
<div className={styles.productPrice}>
|
||||||
|
<IonButton style={{ width: '100%' }} color="light">
|
||||||
|
{product.price}
|
||||||
|
</IonButton>
|
||||||
|
<IonButton color="dark" onClick={(e) => addProductToCart(e, category.slug, product.id)}>
|
||||||
|
<IonIcon icon={cartOutline} />
|
||||||
|
</IonButton>
|
||||||
|
|
||||||
|
<IonIcon
|
||||||
|
ref={productCartRef}
|
||||||
|
icon={cart}
|
||||||
|
color="dark"
|
||||||
|
style={{ position: 'absolute', display: 'none', fontSize: '3rem' }}
|
||||||
|
className="animate__animated"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</IonCardContent>
|
||||||
|
</IonCard>
|
||||||
|
</IonCol>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductCard;
|
@@ -0,0 +1,18 @@
|
|||||||
|
import { Store } from 'pullstate';
|
||||||
|
|
||||||
|
export const CartStore = new Store({
|
||||||
|
total: 0,
|
||||||
|
product_ids: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addToCart = (categorySlug, productID) => {
|
||||||
|
CartStore.update((s) => {
|
||||||
|
s.product_ids = [...s.product_ids, `${categorySlug}/${parseInt(productID)}`];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeFromCart = (productIndex) => {
|
||||||
|
CartStore.update((s) => {
|
||||||
|
s.product_ids.splice(productIndex, 1);
|
||||||
|
});
|
||||||
|
};
|
@@ -0,0 +1,17 @@
|
|||||||
|
import { Store } from "pullstate";
|
||||||
|
|
||||||
|
export const FavouritesStore = new Store({
|
||||||
|
|
||||||
|
total: 0,
|
||||||
|
product_ids: []
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addToFavourites = (categorySlug, productID) => {
|
||||||
|
FavouritesStore.update(s => {
|
||||||
|
if (s.product_ids.find(id => id === `${ categorySlug }/${ parseInt(productID) }`)) {
|
||||||
|
s.product_ids = s.product_ids.filter(id => id !== `${ categorySlug }/${ parseInt(productID) }`);
|
||||||
|
} else {
|
||||||
|
s.product_ids = [ ...s.product_ids, `${ categorySlug }/${ parseInt(productID) }` ];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
import { Store } from "pullstate";
|
||||||
|
|
||||||
|
export const ProductStore = new Store({
|
||||||
|
|
||||||
|
products: []
|
||||||
|
});
|
@@ -0,0 +1,57 @@
|
|||||||
|
import { ProductStore } from './ProductStore';
|
||||||
|
|
||||||
|
export const fetchData = async () => {
|
||||||
|
const json = [
|
||||||
|
'beds.json',
|
||||||
|
'armchairs.json',
|
||||||
|
'coffee_tables.json',
|
||||||
|
'cushions.json',
|
||||||
|
'floor_lamps.json',
|
||||||
|
'office_chairs.json',
|
||||||
|
];
|
||||||
|
|
||||||
|
var products = [];
|
||||||
|
|
||||||
|
json.forEach(async (category) => {
|
||||||
|
const products = await fetchProducts(category);
|
||||||
|
|
||||||
|
let categoryName = category.replace('.json', '');
|
||||||
|
categoryName = categoryName.replace('_', ' ');
|
||||||
|
categoryName = uppercaseWords(categoryName);
|
||||||
|
|
||||||
|
const productCategory = {
|
||||||
|
name: categoryName,
|
||||||
|
slug: category.replace('.json', ''),
|
||||||
|
cover: products[6].image,
|
||||||
|
products,
|
||||||
|
};
|
||||||
|
|
||||||
|
ProductStore.update((s) => {
|
||||||
|
s.products = [...s.products, productCategory];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return products;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchProducts = async (category) => {
|
||||||
|
const response = await fetch(`products/${category}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Set a product id
|
||||||
|
await data.forEach((d, i) => {
|
||||||
|
d.id = i + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const uppercaseWords = (words) => {
|
||||||
|
words = words
|
||||||
|
.toLowerCase()
|
||||||
|
.split(' ')
|
||||||
|
.map((s) => s.charAt(0).toUpperCase() + s.substring(1))
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
return words;
|
||||||
|
};
|
@@ -3,23 +3,48 @@ import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs }
|
|||||||
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
import { cloudOutline, searchOutline } from 'ionicons/icons';
|
||||||
import { Route, Redirect } from 'react-router';
|
import { Route, Redirect } from 'react-router';
|
||||||
|
|
||||||
import Tab1 from './AppPages/Tab1';
|
// import Tab1 from './AppPages/Tab1';
|
||||||
import Tab2 from './AppPages/Tab2';
|
// import Tab2 from './AppPages/Tab2';
|
||||||
|
|
||||||
|
import Home from './pages/Home';
|
||||||
|
import { fetchData } from './data/fetcher';
|
||||||
|
import CategoryProducts from './pages/CategoryProducts';
|
||||||
|
import Product from './pages/Product';
|
||||||
|
import FavouriteProducts from './pages/FavouriteProducts';
|
||||||
|
import CartProducts from './pages/CartProducts';
|
||||||
|
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
|
function DemoEcommerceExample(): React.JSX.Element {
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
function DemoEcommerceExample() {
|
|
||||||
return (
|
return (
|
||||||
<IonTabs>
|
<IonTabs>
|
||||||
<IonRouterOutlet>
|
<IonRouterOutlet>
|
||||||
<Route exact path="/demo-ecommerce-example/tab1">
|
<Route path="/demo-ecommerce-example/home" exact={true}>
|
||||||
<Tab1 />
|
<Home />
|
||||||
</Route>
|
|
||||||
<Route exact path="/demo-ecommerce-example/tab2">
|
|
||||||
<Tab2 />
|
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Redirect exact path="/demo-ecommerce-example" to="/demo-ecommerce-example/tab1" />
|
<Route path="/demo-ecommerce-example/favourites" exact>
|
||||||
|
<FavouriteProducts />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/demo-ecommerce-example/cart" exact>
|
||||||
|
<CartProducts />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/demo-ecommerce-example/category/:slug" exact>
|
||||||
|
<CategoryProducts />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route path="/demo-ecommerce-example/category/:slug/:id" exact>
|
||||||
|
<Product />
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Redirect exact path="/demo-ecommerce-example" to="/demo-ecommerce-example/home" />
|
||||||
</IonRouterOutlet>
|
</IonRouterOutlet>
|
||||||
|
|
||||||
{/* */}
|
{/* */}
|
||||||
|
4
03_source/mobile/src/pages/DemoEcommerceExample/module.d.ts
vendored
Normal file
4
03_source/mobile/src/pages/DemoEcommerceExample/module.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.module.css' {
|
||||||
|
const classes: { readonly [key: string]: string };
|
||||||
|
export default classes;
|
||||||
|
}
|
@@ -0,0 +1,31 @@
|
|||||||
|
.cartCheckout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartFooter {
|
||||||
|
border-top: 2px solid rgb(200, 200, 200);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartCheckout ion-card-subtitle {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartItem ion-avatar {
|
||||||
|
height: 4rem;
|
||||||
|
width: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartSlider:not(:nth-child(1)) {
|
||||||
|
border-top: 2px solid rgb(236, 236, 236);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cartActions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
@@ -0,0 +1,161 @@
|
|||||||
|
import {
|
||||||
|
IonAvatar,
|
||||||
|
IonBadge,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonFooter,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonImg,
|
||||||
|
IonItem,
|
||||||
|
IonItemOption,
|
||||||
|
IonItemOptions,
|
||||||
|
IonItemSliding,
|
||||||
|
IonLabel,
|
||||||
|
IonList,
|
||||||
|
IonNote,
|
||||||
|
IonPage,
|
||||||
|
IonRow,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { cart, checkmarkSharp, chevronBackOutline, trashOutline } from 'ionicons/icons';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { CartStore, removeFromCart } from '../data/CartStore';
|
||||||
|
import { ProductStore } from '../data/ProductStore';
|
||||||
|
|
||||||
|
import styles from './CartProducts.module.css';
|
||||||
|
|
||||||
|
const CartProducts = (): React.JSX.Element => {
|
||||||
|
const cartRef = useRef(null);
|
||||||
|
const products = ProductStore.useState((s) => s.products);
|
||||||
|
const shopCart = CartStore.useState((s) => s.product_ids);
|
||||||
|
const [cartProducts, setCartProducts] = useState([]);
|
||||||
|
const [amountLoaded, setAmountLoaded] = useState(6);
|
||||||
|
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getCartProducts = () => {
|
||||||
|
setCartProducts([]);
|
||||||
|
setTotal(0);
|
||||||
|
|
||||||
|
shopCart.forEach((product) => {
|
||||||
|
var favouriteParts = product.split('/');
|
||||||
|
var categorySlug = favouriteParts[0];
|
||||||
|
var productID = favouriteParts[1];
|
||||||
|
|
||||||
|
const tempCategory = products.filter((p) => p.slug === categorySlug)[0];
|
||||||
|
const tempProduct = tempCategory.products.filter(
|
||||||
|
(p) => parseInt(p.id) === parseInt(productID)
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
const tempCartProduct = {
|
||||||
|
category: tempCategory,
|
||||||
|
product: tempProduct,
|
||||||
|
};
|
||||||
|
|
||||||
|
setTotal((prevTotal) => prevTotal + parseInt(tempProduct.price.replace('£', '')));
|
||||||
|
setCartProducts((prevSearchResults) => [...prevSearchResults, tempCartProduct]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getCartProducts();
|
||||||
|
}, [shopCart]);
|
||||||
|
|
||||||
|
const fetchMore = async (e) => {
|
||||||
|
// Increment the amount loaded by 6 for the next iteration
|
||||||
|
setAmountLoaded((prevAmount) => prevAmount + 6);
|
||||||
|
e.target.complete();
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeProductFromCart = async (index) => {
|
||||||
|
removeFromCart(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage id="category-page" className={styles.categoryPage}>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonButton color="dark" routerLink="/" routerDirection="back">
|
||||||
|
<IonIcon color="dark" icon={chevronBackOutline} />
|
||||||
|
Categories
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
<IonTitle>Cart</IonTitle>
|
||||||
|
|
||||||
|
<IonButtons slot="end">
|
||||||
|
<IonBadge color="dark">{shopCart.length}</IonBadge>
|
||||||
|
<IonButton color="dark">
|
||||||
|
<IonIcon ref={cartRef} className="animate__animated" icon={cart} />
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonRow className="ion-text-center ion-margin-top">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonNote>
|
||||||
|
{cartProducts && cartProducts.length}{' '}
|
||||||
|
{cartProducts.length > 1 || cartProducts.length === 0 ? ' products' : ' product'}{' '}
|
||||||
|
found
|
||||||
|
</IonNote>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonList>
|
||||||
|
{cartProducts &&
|
||||||
|
cartProducts.map((product, index) => {
|
||||||
|
if (index <= amountLoaded) {
|
||||||
|
return (
|
||||||
|
<IonItemSliding className={styles.cartSlider}>
|
||||||
|
<IonItem key={index} lines="none" detail={false} className={styles.cartItem}>
|
||||||
|
<IonAvatar>
|
||||||
|
<IonImg src={product.product.image} />
|
||||||
|
</IonAvatar>
|
||||||
|
<IonLabel className="ion-padding-start ion-text-wrap">
|
||||||
|
<p>{product.category.name}</p>
|
||||||
|
<h4>{product.product.name}</h4>
|
||||||
|
</IonLabel>
|
||||||
|
|
||||||
|
<div className={styles.cartActions}>
|
||||||
|
<IonBadge color="dark">{product.product.price}</IonBadge>
|
||||||
|
</div>
|
||||||
|
</IonItem>
|
||||||
|
|
||||||
|
<IonItemOptions side="end">
|
||||||
|
<IonItemOption
|
||||||
|
color="danger"
|
||||||
|
style={{ paddingLeft: '1rem', paddingRight: '1rem' }}
|
||||||
|
onClick={() => removeProductFromCart(index)}
|
||||||
|
>
|
||||||
|
<IonIcon icon={trashOutline} />
|
||||||
|
</IonItemOption>
|
||||||
|
</IonItemOptions>
|
||||||
|
</IonItemSliding>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</IonList>
|
||||||
|
</IonContent>
|
||||||
|
|
||||||
|
<IonFooter className={styles.cartFooter}>
|
||||||
|
<div className={styles.cartCheckout}>
|
||||||
|
<IonCardSubtitle>£{total.toFixed(2)}</IonCardSubtitle>
|
||||||
|
|
||||||
|
<IonButton color="dark">
|
||||||
|
<IonIcon icon={checkmarkSharp} />
|
||||||
|
Checkout
|
||||||
|
</IonButton>
|
||||||
|
</div>
|
||||||
|
</IonFooter>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CartProducts;
|
@@ -0,0 +1,10 @@
|
|||||||
|
.categoryPage ion-toolbar {
|
||||||
|
|
||||||
|
--border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
|
||||||
|
--background: rgb(240, 240, 240);
|
||||||
|
--color: black;
|
||||||
|
}
|
@@ -0,0 +1,134 @@
|
|||||||
|
import {
|
||||||
|
IonBadge,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonGrid,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonInfiniteScroll,
|
||||||
|
IonInfiniteScrollContent,
|
||||||
|
IonNote,
|
||||||
|
IonPage,
|
||||||
|
IonRow,
|
||||||
|
IonSearchbar,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { cart, chevronBackOutline, searchOutline } from 'ionicons/icons';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
import ProductCard from '../components/ProductCard';
|
||||||
|
|
||||||
|
import { CartStore } from '../data/CartStore';
|
||||||
|
import { ProductStore } from '../data/ProductStore';
|
||||||
|
|
||||||
|
import styles from './CategoryProducts.module.css';
|
||||||
|
|
||||||
|
const CategoryProducts = () => {
|
||||||
|
const params = useParams();
|
||||||
|
const cartRef = useRef();
|
||||||
|
const products = ProductStore.useState((s) => s.products);
|
||||||
|
const shopCart = CartStore.useState((s) => s.product_ids);
|
||||||
|
const [category, setCategory] = useState({});
|
||||||
|
const [searchResults, setsearchResults] = useState([]);
|
||||||
|
const [amountLoaded, setAmountLoaded] = useState(6);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const categorySlug = params.slug;
|
||||||
|
const tempCategory = products.filter((p) => p.slug === categorySlug)[0];
|
||||||
|
setCategory(tempCategory);
|
||||||
|
setsearchResults(tempCategory.products);
|
||||||
|
}, [params.slug]);
|
||||||
|
|
||||||
|
const fetchMore = async (e) => {
|
||||||
|
// Increment the amount loaded by 6 for the next iteration
|
||||||
|
setAmountLoaded((prevAmount) => prevAmount + 6);
|
||||||
|
e.target.complete();
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = async (e) => {
|
||||||
|
const searchVal = e.target.value;
|
||||||
|
|
||||||
|
if (searchVal !== '') {
|
||||||
|
const tempResults = category.products.filter((p) =>
|
||||||
|
p.name.toLowerCase().includes(searchVal.toLowerCase())
|
||||||
|
);
|
||||||
|
setsearchResults(tempResults);
|
||||||
|
} else {
|
||||||
|
setsearchResults(category.products);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage id="category-page" className={styles.categoryPage}>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonButton color="dark" text={category.name} routerLink="/" routerDirection="back">
|
||||||
|
<IonIcon color="dark" icon={chevronBackOutline} />
|
||||||
|
Categories
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
<IonTitle>{category && category.name}</IonTitle>
|
||||||
|
|
||||||
|
<IonButtons slot="end">
|
||||||
|
<IonBadge color="dark">{shopCart.length}</IonBadge>
|
||||||
|
<IonButton color="dark" routerLink="/cart">
|
||||||
|
<IonIcon ref={cartRef} className="animate__animated" icon={cart} />
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonSearchbar
|
||||||
|
className={styles.search}
|
||||||
|
onKeyUp={search}
|
||||||
|
placeholder="Try 'high back'"
|
||||||
|
searchIcon={searchOutline}
|
||||||
|
animated={true}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonNote>
|
||||||
|
{searchResults && searchResults.length}{' '}
|
||||||
|
{searchResults.length > 1 || searchResults.length === 0 ? ' products' : ' product'}{' '}
|
||||||
|
found
|
||||||
|
</IonNote>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow>
|
||||||
|
{searchResults &&
|
||||||
|
searchResults.map((product, index) => {
|
||||||
|
if (index <= amountLoaded && product.image) {
|
||||||
|
return (
|
||||||
|
<ProductCard
|
||||||
|
key={`category_product_${index}`}
|
||||||
|
product={product}
|
||||||
|
index={index}
|
||||||
|
cartRef={cartRef}
|
||||||
|
category={category}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</IonRow>
|
||||||
|
</IonGrid>
|
||||||
|
|
||||||
|
<IonInfiniteScroll threshold="100px" onIonInfinite={fetchMore}>
|
||||||
|
<IonInfiniteScrollContent
|
||||||
|
loadingSpinner="bubbles"
|
||||||
|
loadingText="Fetching more..."
|
||||||
|
></IonInfiniteScrollContent>
|
||||||
|
</IonInfiniteScroll>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CategoryProducts;
|
@@ -0,0 +1,131 @@
|
|||||||
|
import {
|
||||||
|
IonBadge,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonGrid,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonInfiniteScroll,
|
||||||
|
IonInfiniteScrollContent,
|
||||||
|
IonNote,
|
||||||
|
IonPage,
|
||||||
|
IonRow,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { cart, chevronBackOutline } from 'ionicons/icons';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import ProductCard from '../components/ProductCard';
|
||||||
|
import { CartStore } from '../data/CartStore';
|
||||||
|
import { FavouritesStore } from '../data/FavouritesStore';
|
||||||
|
import { ProductStore } from '../data/ProductStore';
|
||||||
|
|
||||||
|
import styles from './CategoryProducts.module.css';
|
||||||
|
|
||||||
|
const FavouriteProducts = () => {
|
||||||
|
const cartRef = useRef(null);
|
||||||
|
const products = ProductStore.useState((s) => s.products);
|
||||||
|
const favourites = FavouritesStore.useState((s) => s.product_ids);
|
||||||
|
const shopCart = CartStore.useState((s) => s.product_ids);
|
||||||
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
|
const [amountLoaded, setAmountLoaded] = useState(6);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getFavourites = () => {
|
||||||
|
setSearchResults([]);
|
||||||
|
|
||||||
|
favourites.forEach((favourite) => {
|
||||||
|
var favouriteParts = favourite.split('/');
|
||||||
|
var categorySlug = favouriteParts[0];
|
||||||
|
var productID = favouriteParts[1];
|
||||||
|
|
||||||
|
const tempCategory = products.filter((p) => p.slug === categorySlug)[0];
|
||||||
|
const tempProduct = tempCategory.products.filter(
|
||||||
|
(p) => parseInt(p.id) === parseInt(productID)
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
const tempFavourite = {
|
||||||
|
category: tempCategory,
|
||||||
|
product: tempProduct,
|
||||||
|
};
|
||||||
|
|
||||||
|
setSearchResults((prevSearchResults) => [...prevSearchResults, tempFavourite]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getFavourites();
|
||||||
|
}, [favourites]);
|
||||||
|
|
||||||
|
const fetchMore = async (e) => {
|
||||||
|
// Increment the amount loaded by 6 for the next iteration
|
||||||
|
setAmountLoaded((prevAmount) => prevAmount + 6);
|
||||||
|
e.target.complete();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage id="category-page" className={styles.categoryPage}>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonButton color="dark" routerLink="/" routerDirection="back">
|
||||||
|
<IonIcon color="dark" icon={chevronBackOutline} />
|
||||||
|
Categories
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
<IonTitle>Favourites</IonTitle>
|
||||||
|
|
||||||
|
<IonButtons slot="end">
|
||||||
|
<IonBadge color="dark">{shopCart.length}</IonBadge>
|
||||||
|
<IonButton color="dark">
|
||||||
|
<IonIcon ref={cartRef} className="animate__animated" icon={cart} />
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonNote>
|
||||||
|
{searchResults && searchResults.length}{' '}
|
||||||
|
{searchResults.length > 1 || searchResults.length === 0
|
||||||
|
? ' favourites'
|
||||||
|
: ' favourite'}{' '}
|
||||||
|
found
|
||||||
|
</IonNote>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow>
|
||||||
|
{searchResults &&
|
||||||
|
searchResults.map((product, index) => {
|
||||||
|
if (index <= amountLoaded) {
|
||||||
|
return (
|
||||||
|
<ProductCard
|
||||||
|
key={`category_product_${index}`}
|
||||||
|
product={product.product}
|
||||||
|
index={index}
|
||||||
|
cartRef={cartRef}
|
||||||
|
category={product.category}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</IonRow>
|
||||||
|
</IonGrid>
|
||||||
|
|
||||||
|
<IonInfiniteScroll threshold="100px" onIonInfinite={fetchMore}>
|
||||||
|
<IonInfiniteScrollContent
|
||||||
|
loadingSpinner="bubbles"
|
||||||
|
loadingText="Fetching more..."
|
||||||
|
></IonInfiniteScrollContent>
|
||||||
|
</IonInfiniteScroll>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FavouriteProducts;
|
@@ -0,0 +1,42 @@
|
|||||||
|
.homePage ion-toolbar {
|
||||||
|
|
||||||
|
--border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
color: var(--ion-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCard,
|
||||||
|
.categoryCardContent {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent ion-button {
|
||||||
|
|
||||||
|
height: 1.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent {
|
||||||
|
|
||||||
|
background-color: rgb(238, 238, 238);
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent ion-card-subtitle {
|
||||||
|
|
||||||
|
/* color: rgb(78, 78, 78); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCard img {
|
||||||
|
|
||||||
|
/* border-radius: 5px; */
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
@@ -0,0 +1,88 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
IonBadge,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonCard,
|
||||||
|
IonCardContent,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonGrid,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonPage,
|
||||||
|
IonRow,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
|
||||||
|
import styles from './Home.module.css';
|
||||||
|
import { cart, heart } from 'ionicons/icons';
|
||||||
|
|
||||||
|
import { ProductStore } from '../data/ProductStore';
|
||||||
|
import { FavouritesStore } from '../data/FavouritesStore';
|
||||||
|
import { CartStore } from '../data/CartStore';
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
const products = ProductStore.useState((s) => s.products);
|
||||||
|
const favourites = FavouritesStore.useState((s) => s.product_ids);
|
||||||
|
const shopCart = CartStore.useState((s) => s.product_ids);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage id="home-page" className={styles.homePage}>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Categories</IonTitle>
|
||||||
|
|
||||||
|
<IonButtons slot="start" className="ion-padding-start">
|
||||||
|
<IonCardSubtitle className={styles.logo}>Ionic Furniture</IonCardSubtitle>
|
||||||
|
</IonButtons>
|
||||||
|
|
||||||
|
<IonButtons slot="end">
|
||||||
|
<IonBadge color="danger">{favourites.length}</IonBadge>
|
||||||
|
<IonButton color="danger" routerLink="/favourites">
|
||||||
|
<IonIcon icon={heart} />
|
||||||
|
</IonButton>
|
||||||
|
|
||||||
|
<IonBadge color="dark">{shopCart.length}</IonBadge>
|
||||||
|
<IonButton color="dark" routerLink="/cart">
|
||||||
|
<IonIcon icon={cart} />
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonHeader collapse="condense">
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle size="large">Categories</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow>
|
||||||
|
{products.map((category, index) => {
|
||||||
|
return (
|
||||||
|
<IonCol size="6" key={`category_list_${index}`}>
|
||||||
|
<IonCard
|
||||||
|
routerLink={`/category/${category.slug}`}
|
||||||
|
className={styles.categoryCard}
|
||||||
|
>
|
||||||
|
<img src={category.cover} alt="category cover" />
|
||||||
|
|
||||||
|
<IonCardContent className={styles.categoryCardContent}>
|
||||||
|
<IonCardSubtitle>{category.name}</IonCardSubtitle>
|
||||||
|
</IonCardContent>
|
||||||
|
</IonCard>
|
||||||
|
</IonCol>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</IonRow>
|
||||||
|
</IonGrid>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
@@ -0,0 +1,66 @@
|
|||||||
|
.categoryPage ion-toolbar {
|
||||||
|
|
||||||
|
--border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCard {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCardActions {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCardAction {
|
||||||
|
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCardHeader {
|
||||||
|
|
||||||
|
min-height: 17rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productCardHeader p {
|
||||||
|
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent ion-button {
|
||||||
|
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categoryCardContent p {
|
||||||
|
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.productPrice {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
@@ -0,0 +1,210 @@
|
|||||||
|
import {
|
||||||
|
IonBadge,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonCard,
|
||||||
|
IonCardContent,
|
||||||
|
IonCardHeader,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonGrid,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonPage,
|
||||||
|
IonRow,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import {
|
||||||
|
arrowRedoOutline,
|
||||||
|
cart,
|
||||||
|
cartOutline,
|
||||||
|
chevronBackOutline,
|
||||||
|
heart,
|
||||||
|
heartOutline,
|
||||||
|
} from 'ionicons/icons';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { useParams } from 'react-router';
|
||||||
|
import ProductCard from '../components/ProductCard';
|
||||||
|
import { addToCart, CartStore } from '../data/CartStore';
|
||||||
|
import { addToFavourites, FavouritesStore } from '../data/FavouritesStore';
|
||||||
|
import { ProductStore } from '../data/ProductStore';
|
||||||
|
|
||||||
|
import styles from './Product.module.css';
|
||||||
|
|
||||||
|
const Product = () => {
|
||||||
|
const params = useParams();
|
||||||
|
const cartRef = useRef(null);
|
||||||
|
const products = ProductStore.useState((s) => s.products);
|
||||||
|
const favourites = FavouritesStore.useState((s) => s.product_ids);
|
||||||
|
const [isFavourite, setIsFavourite] = useState(false);
|
||||||
|
const shopCart = CartStore.useState((s) => s.product_ids);
|
||||||
|
const [product, setProduct] = useState({});
|
||||||
|
const [category, setCategory] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const categorySlug = params.slug;
|
||||||
|
const productID = params.id;
|
||||||
|
const tempCategory = products.filter((p) => p.slug === categorySlug)[0];
|
||||||
|
const tempProduct = tempCategory.products.filter(
|
||||||
|
(p) => parseInt(p.id) === parseInt(productID)
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
const tempIsFavourite = favourites.find((f) => f === `${categorySlug}/${productID}`);
|
||||||
|
|
||||||
|
setIsFavourite(tempIsFavourite);
|
||||||
|
setCategory(tempCategory);
|
||||||
|
setProduct(tempProduct);
|
||||||
|
}, [params.slug, params.id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const tempIsFavourite = favourites.find((f) => f === `${category.slug}/${product.id}`);
|
||||||
|
setIsFavourite(tempIsFavourite ? true : false);
|
||||||
|
}, [favourites, product]);
|
||||||
|
|
||||||
|
const addProductToFavourites = (e, categorySlug, productID) => {
|
||||||
|
e.preventDefault();
|
||||||
|
addToFavourites(categorySlug, productID);
|
||||||
|
|
||||||
|
document.getElementById(
|
||||||
|
`placeholder_favourite_product_${categorySlug}_${productID}`
|
||||||
|
).style.display = '';
|
||||||
|
document
|
||||||
|
.getElementById(`placeholder_favourite_product_${categorySlug}_${productID}`)
|
||||||
|
.classList.add('animate__fadeOutTopRight');
|
||||||
|
};
|
||||||
|
|
||||||
|
const addProductToCart = (e, categorySlug, productID) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
document.getElementById(`placeholder_cart_${categorySlug}_${productID}`).style.display = '';
|
||||||
|
document
|
||||||
|
.getElementById(`placeholder_cart_${categorySlug}_${productID}`)
|
||||||
|
.classList.add('animate__fadeOutUp');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
cartRef.current.classList.add('animate__tada');
|
||||||
|
addToCart(categorySlug, productID);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
cartRef.current.classList.remove('animate__tada');
|
||||||
|
}, 500);
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage id="category-page" className={styles.categoryPage}>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonButton
|
||||||
|
color="dark"
|
||||||
|
text={category.name}
|
||||||
|
routerLink={`/category/${category.slug}`}
|
||||||
|
routerDirection="back"
|
||||||
|
>
|
||||||
|
<IonIcon color="dark" icon={chevronBackOutline} />
|
||||||
|
{category.name}
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
|
||||||
|
<IonTitle>View Product</IonTitle>
|
||||||
|
|
||||||
|
<IonButtons slot="end">
|
||||||
|
<IonBadge color="dark">{shopCart.length}</IonBadge>
|
||||||
|
<IonButton color="dark" routerLink="/cart">
|
||||||
|
<IonIcon ref={cartRef} className="animate__animated" icon={cart} />
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow>
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCard className={styles.categoryCard}>
|
||||||
|
<IonCardHeader className={styles.productCardHeader}>
|
||||||
|
<div className={styles.productCardActions}>
|
||||||
|
<IonIcon
|
||||||
|
className={styles.productCardAction}
|
||||||
|
color={isFavourite ? 'danger' : 'medium'}
|
||||||
|
icon={isFavourite ? heart : heartOutline}
|
||||||
|
onClick={(e) => addProductToFavourites(e, category.slug, product.id)}
|
||||||
|
/>
|
||||||
|
<IonIcon
|
||||||
|
style={{ position: 'absolute', display: 'none' }}
|
||||||
|
id={`placeholder_favourite_product_${category.slug}_${product.id}`}
|
||||||
|
className={`${styles.productCardAction} animate__animated`}
|
||||||
|
color="danger"
|
||||||
|
icon={heart}
|
||||||
|
/>
|
||||||
|
<IonIcon
|
||||||
|
className={styles.productCardAction}
|
||||||
|
size="medium"
|
||||||
|
icon={arrowRedoOutline}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<img src={product.image} alt="product pic" />
|
||||||
|
<p className="ion-text-wrap">{product.name}</p>
|
||||||
|
</IonCardHeader>
|
||||||
|
|
||||||
|
<IonCardContent className={styles.categoryCardContent}>
|
||||||
|
<div className={styles.productPrice}>
|
||||||
|
<IonButton color="light" size="large">
|
||||||
|
{product.price}
|
||||||
|
</IonButton>
|
||||||
|
<IonButton
|
||||||
|
size="large"
|
||||||
|
color="dark"
|
||||||
|
onClick={(e) => addProductToCart(e, category.slug, product.id)}
|
||||||
|
>
|
||||||
|
<IonIcon icon={cartOutline} />
|
||||||
|
Add to Cart
|
||||||
|
</IonButton>
|
||||||
|
|
||||||
|
<IonIcon
|
||||||
|
icon={cart}
|
||||||
|
color="dark"
|
||||||
|
style={{ position: 'absolute', display: 'none', fontSize: '3rem' }}
|
||||||
|
id={`placeholder_cart_${category.slug}_${product.id}`}
|
||||||
|
className="animate__animated"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</IonCardContent>
|
||||||
|
</IonCard>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow className="ion-text-center">
|
||||||
|
<IonCol size="12">
|
||||||
|
<IonCardSubtitle>Similar products...</IonCardSubtitle>
|
||||||
|
</IonCol>
|
||||||
|
</IonRow>
|
||||||
|
|
||||||
|
<IonRow>
|
||||||
|
{category &&
|
||||||
|
category.products &&
|
||||||
|
category.products.map((similar, index) => {
|
||||||
|
if (similar.id !== product.id && product.image && index < 4) {
|
||||||
|
return (
|
||||||
|
<ProductCard
|
||||||
|
key={`similar_product_${index}`}
|
||||||
|
product={similar}
|
||||||
|
index={index}
|
||||||
|
isFavourite={false}
|
||||||
|
cartRef={cartRef}
|
||||||
|
category={category}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</IonRow>
|
||||||
|
</IonGrid>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Product;
|
@@ -0,0 +1,81 @@
|
|||||||
|
/* Ionic Variables and Theming. For more info, please see:
|
||||||
|
http://ionicframework.com/docs/theming/ */
|
||||||
|
|
||||||
|
/** Ionic CSS Variables **/
|
||||||
|
:root {
|
||||||
|
/** primary **/
|
||||||
|
--ion-color-primary: #3880ff;
|
||||||
|
--ion-color-primary-rgb: 56, 128, 255;
|
||||||
|
--ion-color-primary-contrast: #ffffff;
|
||||||
|
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-primary-shade: #3171e0;
|
||||||
|
--ion-color-primary-tint: #4c8dff;
|
||||||
|
|
||||||
|
/** secondary **/
|
||||||
|
--ion-color-secondary: #3dc2ff;
|
||||||
|
--ion-color-secondary-rgb: 61, 194, 255;
|
||||||
|
--ion-color-secondary-contrast: #ffffff;
|
||||||
|
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-secondary-shade: #36abe0;
|
||||||
|
--ion-color-secondary-tint: #50c8ff;
|
||||||
|
|
||||||
|
/** tertiary **/
|
||||||
|
--ion-color-tertiary: #5260ff;
|
||||||
|
--ion-color-tertiary-rgb: 82, 96, 255;
|
||||||
|
--ion-color-tertiary-contrast: #ffffff;
|
||||||
|
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-tertiary-shade: #4854e0;
|
||||||
|
--ion-color-tertiary-tint: #6370ff;
|
||||||
|
|
||||||
|
/** success **/
|
||||||
|
--ion-color-success: #2dd36f;
|
||||||
|
--ion-color-success-rgb: 45, 211, 111;
|
||||||
|
--ion-color-success-contrast: #ffffff;
|
||||||
|
--ion-color-success-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-success-shade: #28ba62;
|
||||||
|
--ion-color-success-tint: #42d77d;
|
||||||
|
|
||||||
|
/** warning **/
|
||||||
|
--ion-color-warning: #ffc409;
|
||||||
|
--ion-color-warning-rgb: 255, 196, 9;
|
||||||
|
--ion-color-warning-contrast: #000000;
|
||||||
|
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
||||||
|
--ion-color-warning-shade: #e0ac08;
|
||||||
|
--ion-color-warning-tint: #ffca22;
|
||||||
|
|
||||||
|
/** danger **/
|
||||||
|
--ion-color-danger: #eb445a;
|
||||||
|
--ion-color-danger-rgb: 235, 68, 90;
|
||||||
|
--ion-color-danger-contrast: #ffffff;
|
||||||
|
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-danger-shade: #cf3c4f;
|
||||||
|
--ion-color-danger-tint: #ed576b;
|
||||||
|
|
||||||
|
/** dark **/
|
||||||
|
--ion-color-dark: #222428;
|
||||||
|
--ion-color-dark-rgb: 34, 36, 40;
|
||||||
|
--ion-color-dark-contrast: #ffffff;
|
||||||
|
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-dark-shade: #1e2023;
|
||||||
|
--ion-color-dark-tint: #383a3e;
|
||||||
|
|
||||||
|
/** medium **/
|
||||||
|
--ion-color-medium: #92949c;
|
||||||
|
--ion-color-medium-rgb: 146, 148, 156;
|
||||||
|
--ion-color-medium-contrast: #ffffff;
|
||||||
|
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||||
|
--ion-color-medium-shade: #808289;
|
||||||
|
--ion-color-medium-tint: #9d9fa6;
|
||||||
|
|
||||||
|
/** light **/
|
||||||
|
--ion-color-light: #f4f5f8;
|
||||||
|
--ion-color-light-rgb: 244, 245, 248;
|
||||||
|
--ion-color-light-contrast: #000000;
|
||||||
|
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||||
|
--ion-color-light-shade: #d7d8da;
|
||||||
|
--ion-color-light-tint: #f5f6f9;
|
||||||
|
|
||||||
|
--ion-toolbar-color: black;
|
||||||
|
--ion-grid-column-padding: 0rem;
|
||||||
|
/* --ion-toolbar-background: var(--ion-color-warning); */
|
||||||
|
}
|
Reference in New Issue
Block a user