diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/components/CategorySlide.tsx b/03_source/mobile/src/pages/DemoFastFoodApp/components/CategorySlide.tsx new file mode 100644 index 0000000..0b189df --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/components/CategorySlide.tsx @@ -0,0 +1,15 @@ +import { Link } from 'react-router-dom'; + +export const CategorySlide = ({ name, path, image }) => ( + <>TODO: sorry but the ionic cannot provide ion-slide +); + +// import { IonSlide } from '@ionic/react'; +// export const CategorySlide = ({ name, path, image }) => ( +// +// +// category +//

{name}

+// +//
+// ); diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/components/ProductCard.module.css b/03_source/mobile/src/pages/DemoFastFoodApp/components/ProductCard.module.css new file mode 100644 index 0000000..1373e81 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/components/ProductCard.module.css @@ -0,0 +1,83 @@ +.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: 13rem; + margin: 0 !important; + padding: 0 !important; + padding: 1rem !important; +} + +.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; +} + +.food { + + text-align: center; + background-color: white; + border: 5px solid var(--ion-background-color); + border-radius: 30px; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/components/ProductCard.tsx b/03_source/mobile/src/pages/DemoFastFoodApp/components/ProductCard.tsx new file mode 100644 index 0000000..171be6e --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/components/ProductCard.tsx @@ -0,0 +1,97 @@ +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 { Link } from "react-router-dom"; +import { addToCart } from "../data/CartStore"; +import { addToFavourites, FavouritesStore } from "../data/FavouritesStore"; +import styles from "./ProductCard.module.css"; + +const ProductCard = props => { + + 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 ( + + + + {/* */} + +
+ addProductToFavourites(e, category.slug, product.id) } /> + + +
+ product pic +

{ product.name }

+
+ + + +
+ + { product.price } + + addProductToCart(e, category.slug, product.id) }> + + + + +
+
+ + {/*
*/} +
+ ); +} + +export default ProductCard; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/data/CartStore.js b/03_source/mobile/src/pages/DemoFastFoodApp/data/CartStore.js new file mode 100644 index 0000000..59eb7ac --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/data/CartStore.js @@ -0,0 +1,17 @@ +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) }); +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/data/FavouritesStore.js b/03_source/mobile/src/pages/DemoFastFoodApp/data/FavouritesStore.js new file mode 100644 index 0000000..53d6d22 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/data/FavouritesStore.js @@ -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) }` ]; + } + }); +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/data/ProductStore.js b/03_source/mobile/src/pages/DemoFastFoodApp/data/ProductStore.js new file mode 100644 index 0000000..86dbaf4 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/data/ProductStore.js @@ -0,0 +1,6 @@ +import { Store } from "pullstate"; + +export const ProductStore = new Store({ + + products: [] +}); \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/data/fetcher.js b/03_source/mobile/src/pages/DemoFastFoodApp/data/fetcher.js new file mode 100644 index 0000000..358c2b7 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/data/fetcher.js @@ -0,0 +1,58 @@ +import { ProductStore } from "./ProductStore"; + +export const fetchData = async () => { + + const json = ["new.json", "chicken.json", "veggie.json", "burgers.json", "sides.json", "drinks.json", "kids.json"]; + + var products = []; + + json.forEach( async category => { + + products = await fetchProducts(category); + + products.forEach(product => { + + product.price = `£${ (Math.floor(Math.random() * (10 - 4 + 1))).toFixed(2) }`; + }); + + let categoryName = category.replace(".json", ""); + categoryName = categoryName.replaceAll("_", " "); + categoryName = uppercaseWords(categoryName); + + const foodCategory = { + + name: categoryName, + slug: category.replace(".json", ""), + cover: products[1].image, + products + }; + + ProductStore.update(s => { s.products = [ ...s.products, foodCategory ]; }); + }); + + return products; +} + +const fetchProducts = async category => { + + const response = await fetch(`categories/${ category }`); + const data = await response.json(); + + // Set a category 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; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/index.tsx b/03_source/mobile/src/pages/DemoFastFoodApp/index.tsx index 26759f1..31b2b8b 100644 --- a/03_source/mobile/src/pages/DemoFastFoodApp/index.tsx +++ b/03_source/mobile/src/pages/DemoFastFoodApp/index.tsx @@ -7,32 +7,47 @@ import Tab1 from './AppPages/Tab1'; import Tab2 from './AppPages/Tab2'; import './style.scss'; +import Home from './pages/Home'; +import FavouriteProducts from './pages/FavouriteProducts'; +import CartProducts from './pages/CartProducts'; +import CategoryProducts from './pages/CategoryProducts'; +import Product from './pages/Product'; function DemoFastFoodApp() { return ( + {/* + */} - + + + + + + + + + + + + + + + + + + + + + - - {/* */} - - - - Dashboard - - - - Search - - ); } diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/module.d.ts b/03_source/mobile/src/pages/DemoFastFoodApp/module.d.ts new file mode 100644 index 0000000..d774364 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/module.d.ts @@ -0,0 +1,4 @@ +declare module '*.module.css' { + const classes: { readonly [key: string]: string }; + export default classes; +} diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/CartProducts.module.css b/03_source/mobile/src/pages/DemoFastFoodApp/pages/CartProducts.module.css new file mode 100644 index 0000000..851dbe1 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/CartProducts.module.css @@ -0,0 +1,37 @@ +.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; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/CartProducts.tsx b/03_source/mobile/src/pages/DemoFastFoodApp/pages/CartProducts.tsx new file mode 100644 index 0000000..c73a636 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/CartProducts.tsx @@ -0,0 +1,144 @@ +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 { useEffect, useRef, useState } from "react"; +import { CartStore, removeFromCart } from "../data/CartStore"; +import { ProductStore } from "../data/ProductStore"; + +import styles from "./CartProducts.module.css"; + +const CartProducts = () => { + + const cartRef = useRef(); + 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 ( + + + + + + +  Categories + + + Cart + + + + { shopCart.length } + + + + + + + + + + + + + cart + + + + + + { cartProducts && cartProducts.length } { (cartProducts.length > 1 || cartProducts.length === 0) ? " foods" : " food" } found + + + + + { cartProducts && cartProducts.map((product, index) => { + + if ((index <= amountLoaded)) { + return ( + + + + + + + +

{ product.category.name }

+

{ product.product.name }

+
+ +
+ { product.product.price } +
+
+ + + removeProductFromCart(index) }> + + + +
+ ); + } + })} +
+
+ + +
+ £{ total.toFixed(2) } + + +  Checkout + +
+
+
+ ); +} + +export default CartProducts; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/CategoryProducts.module.css b/03_source/mobile/src/pages/DemoFastFoodApp/pages/CategoryProducts.module.css new file mode 100644 index 0000000..c0d5021 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/CategoryProducts.module.css @@ -0,0 +1,69 @@ +.categoryPage ion-toolbar { + + --border-style: none; +} + +.foodResults { + + color: black; +} + +.chickenResults { + + color: white; +} + +.burgers { + + --ion-background-color: #7dd5ff; + --ion-toolbar-background: #7dd5ff; + --ion-text-color: black; + --ion-item-background: white !important; +} + +.sides { + + --ion-background-color: #ffd87d; + --ion-toolbar-background: #ffd87d; + --ion-text-color: black !important; + --ion-item-background: white !important; +} + +.chicken { + + --ion-background-color: #6477fe; + --ion-toolbar-background: #6477fe; + /* --ion-text-color: white; */ + --ion-item-background: white !important; + --ion-toolbar-color: white; +} + +.drinks { + + --ion-background-color: #fda9f3; + --ion-toolbar-background: #fda9f3; + /* --ion-text-color: white; */ + --ion-item-background: white !important; +} + +.veggie { + + --ion-background-color: #9fef79; + --ion-toolbar-background: #9fef79; + /* --ion-text-color: white !important; */ + --ion-item-background: white !important; +} + +.kids { + + --ion-background-color: #dc9afe; + --ion-toolbar-background: #dc9afe; + /* --ion-text-color: white; */ + --ion-item-background: white !important; +} + +.search { + + --background: rgb(240, 240, 240); + --color: black; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/CategoryProducts.tsx b/03_source/mobile/src/pages/DemoFastFoodApp/pages/CategoryProducts.tsx new file mode 100644 index 0000000..af4bf51 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/CategoryProducts.tsx @@ -0,0 +1,107 @@ +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 ( + + + + + + +  Categories + + + { category && category.name } + + + + { shopCart.length } + + + + + + + + + + + + + + + + + { searchResults && searchResults.length } { (searchResults.length > 1 || searchResults.length === 0) ? " foods" : " food" } found + + + + + { searchResults && searchResults.map((product, index) => { + + if ((index <= amountLoaded) && product.image) { + return ( + + ); + } + })} + + + + + + + + + + ); +} + +export default CategoryProducts; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/FavouriteProducts.tsx b/03_source/mobile/src/pages/DemoFastFoodApp/pages/FavouriteProducts.tsx new file mode 100644 index 0000000..f6c2a1e --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/FavouriteProducts.tsx @@ -0,0 +1,108 @@ +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(); + 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 ( + + + + + + +  Categories + + + Favourites + + + + { shopCart.length } + + + + + + + + + + + + + + { searchResults && searchResults.length } { (searchResults.length > 1 || searchResults.length === 0) ? " favourites" : " favourite" } found + + + + + { searchResults && searchResults.map((product, index) => { + + if ((index <= amountLoaded)) { + return ( + + ); + } + })} + + + + + + + + + + ); +} + +export default FavouriteProducts; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/Home.module.scss b/03_source/mobile/src/pages/DemoFastFoodApp/pages/Home.module.scss new file mode 100644 index 0000000..24a4522 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/Home.module.scss @@ -0,0 +1,137 @@ +.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; +} + +.categorySlider { + + margin-top: 1rem; + + ion-slide { + + width: 60%; + margin-right: 10px; + margin-left: 10px; + + display: flex; + flex-direction: column; + justify-content: center; + + img { + + border-radius: 22px; + } + } +} + +.categorySquares { + + ion-row { + + .categorySquare { + + height: 4.5rem; + text-align: center; + display: flex; + justify-content: center; + align-content: center; + align-items: center; + border-radius: 22px; + + h4 { + + text-align: center; + } + } + + .categorySquare:nth-child(1) { + + background-color: rgb(105, 62, 5); + color: white; + } + + .categorySquare:nth-child(2) { + + background-color: rgb(83, 185, 0); + color: white; + } + + .categorySquare:nth-child(3) { + + background-color: rgb(255, 240, 24); + } + } +} + +.orderSection { + + padding: 2rem; + background-color: rgb(206, 41, 0); + color: white; + border-radius: 22px; + margin: 5px; + + h4 { + + margin-left: 1.5rem; + } +} + +.offerSection { + + padding: 2rem; + background-color: #ffd146; + color: black; + border-radius: 22px; + margin: 5px; + + h4, + ion-card-subtitle { + + margin-left: 1.5rem; + } + + ion-card-subtitle { + + color: rgb(255, 255, 255); + font-weight: 800; + } +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/Home.tsx b/03_source/mobile/src/pages/DemoFastFoodApp/pages/Home.tsx new file mode 100644 index 0000000..a4f448b --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/Home.tsx @@ -0,0 +1,134 @@ +import { useState } from 'react'; +import { + IonBadge, + IonButton, + IonButtons, + IonCard, + IonCardContent, + IonCardSubtitle, + IonCol, + IonContent, + IonGrid, + IonHeader, + IonIcon, + IonPage, + IonRow, + // IonSlide, + // IonSlides, + IonTitle, + IonToolbar, + useIonRouter, + useIonViewDidEnter, + useIonViewWillLeave, +} from '@ionic/react'; + +import styles from './Home.module.scss'; +import { cart, chevronBackOutline, heart } from 'ionicons/icons'; + +import { ProductStore } from '../data/ProductStore'; +import { FavouritesStore } from '../data/FavouritesStore'; +import { CartStore } from '../data/CartStore'; +import { Link } from 'react-router-dom'; +import { CategorySlide } from '../components/CategorySlide'; + +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); + + useIonViewWillLeave(() => { + document.querySelector('#slider').stopAutoplay(); + }); + + useIonViewDidEnter(() => { + document.querySelector('#slider') && document.querySelector('#slider').startAutoplay(); + document.querySelector('#slider') && document.querySelector('#slider').update(); + }); + + const router = useIonRouter(); + function handleBackClick() { + router.goBack(); + } + + return ( + + + + Popular + + + handleBackClick()}> + + + + + + Ionic Food + + + + {favourites.length} + + + + + {shopCart.length} + + + + + + + + + + + + order method + + + +

Kids eat free on any orders over £20.00

+ Valid until July → +
+
+
+ + + + Let's eat + + + + {/* + + + + + + + + + */} + + + + + order method + + + +

Order for a collection or get a local delivery

+
+
+
+
+
+ ); +}; + +export default Home; diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/Product.module.css b/03_source/mobile/src/pages/DemoFastFoodApp/pages/Product.module.css new file mode 100644 index 0000000..253b7ed --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/Product.module.css @@ -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; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/pages/Product.tsx b/03_source/mobile/src/pages/DemoFastFoodApp/pages/Product.tsx new file mode 100644 index 0000000..18eeda6 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/pages/Product.tsx @@ -0,0 +1,153 @@ +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(); + 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 ( + + + + + + +  { category.name } + + + + View Food + + + + { shopCart.length } + + + + + + + + + + + + + + + +
+ addProductToFavourites(e, category.slug, product.id) } /> + + +
+ product pic +

{ product.name }

+ { product.nutrition } +
+ + + +
+ + { product.price } + + addProductToCart(e, category.slug, product.id) }> +   Add to Cart + + + +
+
+
+
+
+ + + + Similar foods... + + + + + { (category && category.products) && category.products.map((similar, index) => { + + if ((similar.id !== product.id) && product.image && index < 4) { + + return ( + + + ); + } + })} + +
+
+
+ ); +} + +export default Product; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoFastFoodApp/theme/variables.css b/03_source/mobile/src/pages/DemoFastFoodApp/theme/variables.css new file mode 100644 index 0000000..86f1b29 --- /dev/null +++ b/03_source/mobile/src/pages/DemoFastFoodApp/theme/variables.css @@ -0,0 +1,87 @@ +/* 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); */ +} + +.non-link { + + text-decoration: none; + color: unset; +} \ No newline at end of file