diff --git a/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCard.module.css b/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCard.module.css new file mode 100644 index 0000000..41919a3 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCard.module.css @@ -0,0 +1,54 @@ +.coffeeCard { + + padding: 0.8rem; + border-radius: 20px; +} + +.coffeeCard img { + + border-radius: 20px; + height: 10rem; + width: 100%; +} + +.coffeeCard ion-card-title { + + margin-top: 1rem; + font-size: 1rem; +} + +.coffeePrice { + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + align-content: center; + margin-top: -0.2rem; +} + +.coffeeAddButton { + + color: var(--ion-color-main) !important; + margin-top: 0.5rem; +} + +.coffeeCardLong img { + + border-radius: 20px; + height: 5rem !important; + width: 100%; +} + +.coffeeCardLongDetails { + + margin-left: 1rem; + margin-top: -0.7rem; +} + +.coffeeCardLongDetails p { + + font-size: 0.8rem; + margin: 0; + margin-top: 0.2rem; +} diff --git a/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCard.tsx b/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCard.tsx new file mode 100644 index 0000000..e635094 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCard.tsx @@ -0,0 +1,28 @@ +import { IonCard, IonCardSubtitle, IonCardTitle, IonCol } from "@ionic/react"; +import { ArrowRightSquare } from "react-iconly"; + +import styles from "./CoffeeCard.module.css"; + +const CoffeeCard = props => { + + const { coffee } = props; + + return ( + + + + coffee + { coffee.name } + { coffee.summary } +
+

${ coffee.price }

+
+ +
+
+
+
+ ); +} + +export default CoffeeCard; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCardOffer.tsx b/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCardOffer.tsx new file mode 100644 index 0000000..d98c46a --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/components/CoffeeCardOffer.tsx @@ -0,0 +1,35 @@ +import { IonCard, IonCardSubtitle, IonCardTitle, IonCol, IonRow } from "@ionic/react"; +import { Plus } from "react-iconly"; + +import styles from "./CoffeeCard.module.css"; + +const CoffeeCardOffer = props => { + + const { offer } = props; + + return ( + + + + + + + + + coffee + + + +
+ { offer.title } +

{ offer.description }

+
+
+
+
+
+
+ ); +} + +export default CoffeeCardOffer; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/components/Tabs.tsx b/03_source/mobile/src/pages/DemoOrderingApp/components/Tabs.tsx new file mode 100644 index 0000000..2f8443e --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/components/Tabs.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { IonIcon, IonLabel, IonTabBar, IonTabButton, IonTabs, IonRouterOutlet } from '@ionic/react'; +import { Redirect, Route } from 'react-router-dom'; + +import { Home, Bag, Heart2, Notification } from 'react-iconly'; +import Homepage from '../pages/Home'; +import Favourites from '../pages/Favourites'; +import Cart from '../pages/Cart'; + +const Tabs = (props) => { + return ( + + + } + /> + } /> + } + /> + + + + + + + + + + + + + + + + + ); +}; + +export default Tabs; diff --git a/03_source/mobile/src/pages/DemoOrderingApp/components/ViewCoffeeCard.tsx b/03_source/mobile/src/pages/DemoOrderingApp/components/ViewCoffeeCard.tsx new file mode 100644 index 0000000..202e6e5 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/components/ViewCoffeeCard.tsx @@ -0,0 +1,71 @@ +import { IonButton, IonCard, IonCardSubtitle, IonCardTitle, IonCol, IonRow } from "@ionic/react"; + +import { useRef } from "react"; +import { Bag } from "react-iconly"; +import { addToCart } from "../store/CartStore"; +import '../pages/Home.css'; + +const ViewCoffeeCard = props => { + + const { coffee, cartRef } = props; + const coffeeCartRef = useRef(); + + const addCoffeeToCart = (e, coffeeID) => { + + e.preventDefault(); + e.stopPropagation(); + + coffeeCartRef.current.style.display = ""; + coffeeCartRef.current.classList.add("animate__fadeOutUp"); + + setTimeout(() => { + + cartRef.current.classList.add("animate__tada"); + addToCart(coffeeID); + + setTimeout(() => { + + cartRef.current.classList.remove("animate__tada"); + coffeeCartRef.current.style.display = "none"; + }, 500); + }, 500); + } + + return ( + + + + + + + coffee type + { coffee.name } + { coffee.summary } + + + + + Description +

{ coffee.description }

+ + + + View → + + + + addCoffeeToCart(e, coffee.id) }> + + + +
+ +
+
+
+
+
+ ); +} + +export default ViewCoffeeCard; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/index.tsx b/03_source/mobile/src/pages/DemoOrderingApp/index.tsx index a1ed13a..d5213c5 100644 --- a/03_source/mobile/src/pages/DemoOrderingApp/index.tsx +++ b/03_source/mobile/src/pages/DemoOrderingApp/index.tsx @@ -1,38 +1,39 @@ -import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react'; +import { IonRouterOutlet, IonTabs } from '@ionic/react'; -import { cloudOutline, searchOutline } from 'ionicons/icons'; import { Route, Redirect } from 'react-router'; -import Tab1 from './AppPages/Tab1'; -import Tab2 from './AppPages/Tab2'; +import './theme/variables.scss'; -import './style.scss'; +import Tabs from './components/Tabs'; +import Homepage from './pages/Home'; +import ViewCoffee from './pages/ViewCoffee'; +import ViewCoffees from './pages/ViewCoffees'; +import React from 'react'; -function DemoOrderingApp() { +function DemoOrderingApp(): React.JSX.Element { return ( - + - - - - - - + } /> + } + /> + } + /> + } + /> - + + - - {/* */} - - - - Dashboard - - - - Search - - ); } diff --git a/03_source/mobile/src/pages/DemoOrderingApp/module.d.ts b/03_source/mobile/src/pages/DemoOrderingApp/module.d.ts new file mode 100644 index 0000000..d774364 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/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/DemoOrderingApp/pages/Cart.module.css b/03_source/mobile/src/pages/DemoOrderingApp/pages/Cart.module.css new file mode 100644 index 0000000..427e2f6 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/pages/Cart.module.css @@ -0,0 +1,39 @@ +.cartCheckout { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + align-content: center; + margin: 1rem; +} + +.cartFooter { + border-top: 2px solid #141a22; + background-color: var(--ion-background-color); + padding-left: 1rem; + padding-right: 1rem; +} + +.cartCheckout ion-card-subtitle { + font-size: 1.3rem; + color: white !important; +} + +.cartItem { + padding: 1rem; +} + +.cartItem img { + height: 3rem; + width: 3rem; + border-radius: 10px; +} + +.cartSlider:not(:nth-child(1)) { + border-top: 2px solid var(--ion-background-color); +} + +.cartActions { + display: flex; + flex-direction: column; +} diff --git a/03_source/mobile/src/pages/DemoOrderingApp/pages/Cart.tsx b/03_source/mobile/src/pages/DemoOrderingApp/pages/Cart.tsx new file mode 100644 index 0000000..a9342fc --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/pages/Cart.tsx @@ -0,0 +1,129 @@ +import { + IonBadge, + IonButton, + IonCardSubtitle, + IonCol, + IonContent, + IonFooter, + IonHeader, + IonItem, + IonItemOption, + IonItemOptions, + IonItemSliding, + IonLabel, + IonList, + IonNote, + IonPage, + IonRow, + IonTitle, + IonToolbar, +} from '@ionic/react'; +import { Delete, TickSquare } from 'react-iconly'; +import { useEffect, useState } from 'react'; +import { CartStore, CoffeeStore } from '../store'; +import { removeFromCart } from '../store/CartStore'; +import { getCartCoffees, getCoffees } from '../store/Selectors'; + +import styles from './Cart.module.css'; + +const Cart = (): React.JSX.Element => { + const coffees = CoffeeStore.useState(getCoffees); + const cart = CartStore.useState(getCartCoffees); + + const [cartProducts, setCartProducts] = useState([]); + const [amountLoaded, setAmountLoaded] = useState(6); + const [total, setTotal] = useState(0); + + useEffect(() => { + const getCartProducts = () => { + setCartProducts([]); + setTotal(0); + + cart.forEach((coffee) => { + var coffeeID = coffee; + const tempCoffee = coffees.filter((p) => parseInt(p.id) === parseInt(coffeeID))[0]; + + setTotal((prevTotal) => prevTotal + parseFloat(tempCoffee.price)); + setCartProducts((prevSearchResults) => [...prevSearchResults, tempCoffee]); + }); + }; + + getCartProducts(); + }, [cart]); + + 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 ( + + + + Checkout + + + + + + + + {cartProducts && cartProducts.length}{' '} + {cartProducts.length > 1 || cartProducts.length === 0 ? ' coffees' : ' coffee'} found + + + + + + {cartProducts && + cartProducts.map((coffee, index) => { + if (index <= amountLoaded) { + return ( + + + cart coffee + +

{coffee.name}

+
+ +
+ ${coffee.price} +
+
+ + + removeProductFromCart(index)} + > + + + +
+ ); + } + })} +
+
+ + +
+ ${total.toFixed(2)} + + + +  Checkout + +
+
+
+ ); +}; + +export default Cart; diff --git a/03_source/mobile/src/pages/DemoOrderingApp/pages/Favourites.tsx b/03_source/mobile/src/pages/DemoOrderingApp/pages/Favourites.tsx new file mode 100644 index 0000000..0995d1f --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/pages/Favourites.tsx @@ -0,0 +1,113 @@ +import { + IonButtons, + IonCol, + IonContent, + IonGrid, + IonHeader, + IonInfiniteScroll, + IonInfiniteScrollContent, + IonNote, + IonPage, + IonRow, + IonTitle, + IonToolbar, + useIonRouter, +} from '@ionic/react'; +import { Bag } from 'react-iconly'; +import { useEffect, useRef, useState } from 'react'; +import ViewCoffeeCard from '../components/ViewCoffeeCard'; +import { CartStore, CoffeeStore, FavouriteStore } from '../store'; +import { getCartCoffees, getCoffees, getFavouriteCoffees } from '../store/Selectors'; +import './Home.css'; + +const Favourites = (): React.JSX.Element => { + const cartRef = useRef(null); + const router = useIonRouter(); + + const coffees = CoffeeStore.useState(getCoffees); + const favourites = FavouriteStore.useState(getFavouriteCoffees); + const cart = CartStore.useState(getCartCoffees); + + const [searchResults, setSearchResults] = useState([]); + const [amountLoaded, setAmountLoaded] = useState(6); + + useEffect(() => { + const getFavourites = () => { + setSearchResults([]); + + favourites.forEach((favourite) => { + var coffeeID = favourite; + const tempCoffee = coffees.filter((c) => parseInt(c.id) === parseInt(coffeeID))[0]; + setSearchResults((prevSearchResults) => [...prevSearchResults, tempCoffee]); + }); + }; + + getFavourites(); + }, [favourites]); + + const fetchMore = async (e) => { + // Increment the amount loaded by 6 for the next iteration + setAmountLoaded((prevAmount) => prevAmount + 6); + e.target.complete(); + }; + + return ( + + + + Favourites + + +
router.goBack()} + > + 0 ? 'yellow-icon' : 'gray-icon'} /> +
+
+
+
+ + + + + + + {searchResults && searchResults.length}{' '} + {searchResults.length > 1 || searchResults.length === 0 + ? ' favourites' + : ' favourite'}{' '} + found + + + + + + {searchResults && + searchResults.map((coffee, index) => { + if (index <= amountLoaded) { + return ( + + ); + } + })} + + + + + + + +
+ ); +}; + +export default Favourites; diff --git a/03_source/mobile/src/pages/DemoOrderingApp/pages/Home.scss b/03_source/mobile/src/pages/DemoOrderingApp/pages/Home.scss new file mode 100644 index 0000000..a9fa354 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/pages/Home.scss @@ -0,0 +1,11 @@ +.demo-ordering-app { + .main-heading { + font-size: 2.3rem; + font-weight: 700; + } + + .heading { + letter-spacing: -0.05rem; + margin-left: 1rem; + } +} diff --git a/03_source/mobile/src/pages/DemoOrderingApp/pages/Home.tsx b/03_source/mobile/src/pages/DemoOrderingApp/pages/Home.tsx new file mode 100644 index 0000000..c5c018b --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/pages/Home.tsx @@ -0,0 +1,115 @@ +import { + IonButton, + IonButtons, + IonCol, + IonContent, + IonGrid, + IonHeader, + IonIcon, + IonPage, + IonRouterLink, + IonRow, + IonSearchbar, + IonTitle, + IonToolbar, + useIonRouter, +} from '@ionic/react'; +import { chevronBackOutline, searchSharp } from 'ionicons/icons'; +import { Category, Filter } from 'react-iconly'; +import CoffeeCard from '../components/CoffeeCard'; +import CoffeeCardOffer from '../components/CoffeeCardOffer'; + +import { CoffeeStore, CoffeeOfferStore } from '../store'; +import { getCoffees, getOffers } from '../store/Selectors'; +import './Home.scss'; + +const Homepage = (): React.JSX.Element => { + const router = useIonRouter(); + const coffees = CoffeeStore.useState(getCoffees); + const offers = CoffeeOfferStore.useState(getOffers); + + function handleBackClick() { + router.goBack(); + } + + return ( + + + + + handleBackClick()}> + + + +
+ +
+
+ + Ionic Coffee + + +
+ avatar +
+
+
+
+ + + + + + +

Find the best coffee near you

+
+
+
+
+ + + + + router.push('/coffees/true')} + searchIcon={searchSharp} + placeholder="Try 'Caramel Latte'" + /> + + + + +

Popular

+ + + + +
+ + + {coffees.map((coffee) => { + if (coffee.id <= 2) { + return ; + } + })} + + + + +

Special Offers

+
+
+ + {offers.map((offer) => { + return ; + })} +
+
+
+ ); +}; + +export default Homepage; diff --git a/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffee.module.css b/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffee.module.css new file mode 100644 index 0000000..f472b46 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffee.module.css @@ -0,0 +1,50 @@ +.checkout { + + border-top: 2px solid #141a22; + background-color: var(--ion-background-color); + padding-bottom: 1rem; +} + +.checkoutDetails { + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + align-content: center; + margin-left: 2rem; + margin-right: 1rem; + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.checkoutDetails ion-button { + + width: 60%; + --border-radius: 10px; + height: 3.5rem; +} + +.priceDetails { + + /* margin-left: 2rem; */ + display: flex; + flex-direction: column; + justify-content: center; + align-content: center; + align-items: center; +} + +.priceDetails h4 { + + margin: 0; + padding: 0; + color: white !important; +} + +.extra { + + margin-top: 0.3rem; + padding: 0.75rem; + color: #475464; +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffee.tsx b/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffee.tsx new file mode 100644 index 0000000..3d4fc19 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffee.tsx @@ -0,0 +1,208 @@ +import { + IonBadge, + IonButton, + IonButtons, + IonCard, + IonCardSubtitle, + IonCol, + IonContent, + IonFooter, + IonGrid, + IonHeader, + IonPage, + IonRow, + IonTitle, + IonToolbar, + useIonRouter, +} from '@ionic/react'; +import { useEffect, useRef, useState } from 'react'; +import { Bag, Heart, CaretLeft, InfoSquare } from 'react-iconly'; +import { useParams } from 'react-router'; +import { CoffeeSizeStore, CoffeeStore, FavouriteStore } from '../store'; +import { addToCart } from '../store/CartStore'; +import { addToFavourites } from '../store/FavouriteStore'; +import { getCoffee, getCoffeeSizes, getFavouriteCoffees } from '../store/Selectors'; +import './Home.css'; + +import styles from './ViewCoffee.module.css'; + +const ViewCoffee = (props): React.JSX.Element => { + const router = useIonRouter(); + const params = useParams(); + const coffee = CoffeeStore.useState(getCoffee(params.id)); + const favourites = FavouriteStore.useState(getFavouriteCoffees); + const coffeeSizes = CoffeeSizeStore.useState(getCoffeeSizes); + const [selectedSize, setSelectedSize] = useState(false); + + const favouriteRef = useRef(); + const coffeeCartRef = useRef(); + const [isFavourite, setIsFavourite] = useState(false); + + const getPrice = () => + coffee.prices.filter((p) => parseInt(p.size_id) === parseInt(selectedSize))[0].price; + + useEffect(() => { + const coffeeID = params.id; + const tempIsFavourite = favourites.find((f) => parseInt(f) === parseInt(coffeeID)); + + setIsFavourite(tempIsFavourite); + }, [params.id, favourites]); + + const addCoffeeToFavourites = (e, coffeeID) => { + e.preventDefault(); + addToFavourites(coffeeID); + + favouriteRef.current.classList.add('animate__tada'); + + setTimeout(() => { + favouriteRef.current.classList.remove('animate__tada'); + }, 700); + }; + + const addCoffeeToCart = (e, coffeeID) => { + e.preventDefault(); + e.stopPropagation(); + + coffeeCartRef.current.style.display = ''; + coffeeCartRef.current.classList.add('animate__fadeOutUp'); + + setTimeout(() => { + addToCart(coffeeID); + + setTimeout(() => { + coffeeCartRef.current.style.display = 'none'; + }, 500); + }, 500); + }; + + return ( + + + + +
router.goBack()}> + +
+
+ + {coffee.name} + + +
addCoffeeToFavourites(e, coffee.id)} + > + +
+
+
+
+ + + + + + +

{coffee.name}

+ {coffee.summary} +
+
+
+
+ + + + + + coffee type + + + + + Description +

{coffee.description}

+ +
+
+ + + + Extras included + + + + {coffee.extras.map((extra, index) => { + return ( + + {extra} + + ); + })} + + + + + + + + Pick your size + + + {coffeeSizes.map((size) => { + return ( + + setSelectedSize(size.id)} + expand="block" + color={size.id === selectedSize ? 'main' : 'custom-light'} + fill={size.id === selectedSize ? 'outline' : 'solid'} + > + {size.name} + + + ); + })} + + + +
+
+ + +
+
+ Price +

${selectedSize ? getPrice() : '0.00'}

+
+ addCoffeeToCart(e, coffee.id)} + disabled={!selectedSize} + expand="block" + color="main" + > + Add to cart + + +
+ +
+
+
+
+ ); +}; + +export default ViewCoffee; diff --git a/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffees.tsx b/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffees.tsx new file mode 100644 index 0000000..336befe --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/pages/ViewCoffees.tsx @@ -0,0 +1,117 @@ +import { + IonButtons, + IonCardSubtitle, + IonCol, + IonContent, + IonGrid, + IonHeader, + IonPage, + IonRow, + IonSearchbar, + IonTitle, + IonToolbar, + useIonRouter, + useIonViewDidEnter, +} from '@ionic/react'; +import { searchSharp } from 'ionicons/icons'; +import { useRef, useState } from 'react'; +import { Bag, CaretLeft } from 'react-iconly'; +import { useParams } from 'react-router'; +import ViewCoffeeCard from '../components/ViewCoffeeCard'; +import { CartStore, CoffeeStore } from '../store'; +import { getCoffees, getCartCoffees } from '../store/Selectors'; +import './Home.css'; + +const ViewCoffees = (props): React.JSX.Element => { + const router = useIonRouter(); + const params = useParams(); + const coffees = CoffeeStore.useState(getCoffees); + const cart = CartStore.useState(getCartCoffees); + const [results, setResults] = useState(coffees); + + const cartRef = useRef(); + const searchRef = useRef(); + + useIonViewDidEnter(() => { + if (params.from_search) { + setTimeout(() => { + searchRef.current.setFocus(); + }, 500); + } + }); + + const search = (e) => { + const searchTerm = e.currentTarget.value; + + if (searchTerm !== '') { + const searchTermLower = searchTerm.toLowerCase(); + + const newResults = coffees.filter((e) => e.name.toLowerCase().includes(searchTermLower)); + setResults(newResults); + } else { + setResults(coffees); + } + }; + + return ( + + + + +
router.goBack()}> + +
+
+ + Full Range + + +
router.push('/tabs/cart')} + > + 0 ? 'yellow-icon' : 'gray-icon'} /> +
+
+
+
+ + + + + + +

View Full Range

+ Our range of succulent coffee +
+
+
+
+ + + + + search(e)} + id="searchbar" + ref={searchRef} + searchIcon={searchSharp} + placeholder="Try 'Cappuccino'" + /> + + + + {results.map((coffee) => { + return ; + })} + +
+
+ ); +}; + +export default ViewCoffees; diff --git a/03_source/mobile/src/pages/DemoOrderingApp/store/CartStore.tsx b/03_source/mobile/src/pages/DemoOrderingApp/store/CartStore.tsx new file mode 100644 index 0000000..0cf4601 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/store/CartStore.tsx @@ -0,0 +1,19 @@ +import { Store } from "pullstate"; + +const CartStore = new Store({ + + total: 0, + coffee_ids: [] +}); + +export default CartStore; + +export const addToCart = (coffeeID) => { + + CartStore.update(s => { s.coffee_ids = [ ...s.coffee_ids, `${ parseInt(coffeeID) }` ]; }); +} + +export const removeFromCart = coffeeIndex => { + + CartStore.update(s => { s.coffee_ids.splice(coffeeIndex, 1) }); +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeOfferStore.tsx b/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeOfferStore.tsx new file mode 100644 index 0000000..9132461 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeOfferStore.tsx @@ -0,0 +1,15 @@ +import { Store } from 'pullstate'; + +const CoffeeOfferStore = new Store({ + + offers: [ + { + id: 1, + title: "Buy one get one free!", + description: "Any time you buy a coffee using your loyalty card scheme, you can get one free", + image: "https://images.pexels.com/photos/861090/pexels-photo-861090.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" + } + ] +}); + +export default CoffeeOfferStore; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeSizeStore.tsx b/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeSizeStore.tsx new file mode 100644 index 0000000..d965a27 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeSizeStore.tsx @@ -0,0 +1,21 @@ +import { Store } from 'pullstate'; + +const CoffeeSizeStore = new Store({ + + sizes: [ + { + id: 1, + name: "Small" + }, + { + id: 2, + name: "Medium", + }, + { + id: 3, + name: "Large" + } + ] +}); + +export default CoffeeSizeStore; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeStore.tsx b/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeStore.tsx new file mode 100644 index 0000000..3b8cd3e --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/store/CoffeeStore.tsx @@ -0,0 +1,101 @@ +import { Store } from 'pullstate'; + +const CoffeeStore = new Store({ + + coffees: [ + { + id: 1, + name: "Cappuccino", + summary: "With Milk", + extras: [ "milk" ], + description: "This is a beautiful cup of cappuccino, complimented with semi-skimmed milk. Comes in three different sizes.", + price: "3.20", + prices: [ + { + size_id: 1, + price: "3.20" + }, + { + size_id: 2, + price: "3.90" + }, + { + size_id: 3, + price: "4.20" + } + ], + image: "https://images.pexels.com/photos/1170659/pexels-photo-1170659.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" + }, + { + id: 2, + name: "Lattè", + summary: "With Caramel", + extras: [ "caramel" ], + description: "This is a beautiful cup of lattè, complimented with sweet caramel. Comes in three different sizes.", + price: "5.10", + prices: [ + { + size_id: 1, + price: "4.35" + }, + { + size_id: 2, + price: "4.85" + }, + { + size_id: 3, + price: "5.10" + } + ], + image: "https://images.pexels.com/photos/2067399/pexels-photo-2067399.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" + }, + { + id: 3, + name: "Espresso", + summary: "With 2 shots", + extras: [ "2 shots" ], + description: "This is a beautiful cup of espresso, complimented with 2 shots. Comes in three different sizes.", + price: "6.20", + prices: [ + { + size_id: 1, + price: "6.20" + }, + { + size_id: 2, + price: "6.80" + }, + { + size_id: 3, + price: "7.10" + } + ], + image: "https://images.pexels.com/photos/302894/pexels-photo-302894.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" + }, + { + id: 4, + name: "Americano", + summary: "With Milk", + extras: [ "Milk" ], + description: "This is a beautiful cup of Americano, complimented with full fat milk. Comes in three different sizes.", + price: "5.35", + prices: [ + { + size_id: 1, + price: "5.35" + }, + { + size_id: 2, + price: "5.70" + }, + { + size_id: 3, + price: "6.50" + } + ], + image: "https://images.pexels.com/photos/6207297/pexels-photo-6207297.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" + } + ] +}); + +export default CoffeeStore; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/store/FavouriteStore.tsx b/03_source/mobile/src/pages/DemoOrderingApp/store/FavouriteStore.tsx new file mode 100644 index 0000000..177a0bc --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/store/FavouriteStore.tsx @@ -0,0 +1,21 @@ +import { Store } from "pullstate"; + +const FavouriteStore = new Store({ + + total: 0, + coffee_ids: [] +}); + +export default FavouriteStore; + +export const addToFavourites = (coffeeID) => { + + FavouriteStore.update(s => { + + if (s.coffee_ids.find(id => id === parseInt(coffeeID))) { + s.coffee_ids = s.coffee_ids.filter(id => id !== parseInt(coffeeID)); + } else { + s.coffee_ids = [ ...s.coffee_ids, parseInt(coffeeID) ]; + } + }); +} \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/store/Selectors.tsx b/03_source/mobile/src/pages/DemoOrderingApp/store/Selectors.tsx new file mode 100644 index 0000000..ef89efe --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/store/Selectors.tsx @@ -0,0 +1,17 @@ +import { createSelector } from 'reselect'; + +const getState = (state) => state; + +// General getters +export const getCoffees = createSelector(getState, (state) => state.coffees); +export const getOffers = createSelector(getState, (state) => state.offers); +export const getCoffeeSizes = createSelector(getState, (state) => state.sizes); +export const getCartCoffees = createSelector(getState, (state) => state.coffee_ids); +export const getFavouriteCoffees = createSelector(getState, (state) => state.coffee_ids); + +// More specific getters +export const getCoffee = (id) => + createSelector( + getState, + (state) => state.coffees.filter((c) => parseInt(c.id) === parseInt(id))[0] + ); diff --git a/03_source/mobile/src/pages/DemoOrderingApp/store/index.tsx b/03_source/mobile/src/pages/DemoOrderingApp/store/index.tsx new file mode 100644 index 0000000..d52e3f5 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/store/index.tsx @@ -0,0 +1,5 @@ +export { default as CoffeeStore } from "./CoffeeStore"; +export { default as CoffeeSizeStore } from "./CoffeeSizeStore"; +export { default as CoffeeOfferStore } from "./CoffeeOfferStore"; +export { default as CartStore } from "./CartStore"; +export { default as FavouriteStore } from "./FavouriteStore"; \ No newline at end of file diff --git a/03_source/mobile/src/pages/DemoOrderingApp/style.scss b/03_source/mobile/src/pages/DemoOrderingApp/style.scss deleted file mode 100644 index 37c1e1a..0000000 --- a/03_source/mobile/src/pages/DemoOrderingApp/style.scss +++ /dev/null @@ -1,103 +0,0 @@ -#about-page { - ion-toolbar { - position: absolute; - - top: 0; - left: 0; - right: 0; - - --background: transparent; - --color: white; - } - - ion-toolbar ion-back-button, - ion-toolbar ion-button, - ion-toolbar ion-menu-button { - --color: white; - } - - .about-header { - position: relative; - - width: 100%; - height: 30%; - } - - .about-header .about-image { - position: absolute; - - top: 0; - left: 0; - bottom: 0; - right: 0; - - background-position: center; - background-size: cover; - background-repeat: no-repeat; - - opacity: 0; - - transition: opacity 500ms ease-in-out; - } - - .about-header .madison { - background-image: url('/assets/WeatherDemo/img/about/madison.jpg'); - } - - .about-header .austin { - background-image: url('/assets/WeatherDemo/img/about/austin.jpg'); - } - - .about-header .chicago { - background-image: url('/assets/WeatherDemo/img/about/chicago.jpg'); - } - - .about-header .seattle { - background-image: url('/assets/WeatherDemo/img/about/seattle.jpg'); - } - - .about-info { - position: relative; - margin-top: -10px; - border-radius: 10px; - background: var(--ion-background-color, #fff); - z-index: 2; // display rounded border above header image - } - - .about-info h3 { - margin-top: 0; - } - - .about-info ion-list { - padding-top: 0; - } - - .about-info p { - line-height: 130%; - - color: var(--ion-color-dark); - } - - .about-info ion-icon { - margin-inline-end: 32px; - } - - /* - * iOS Only - */ - - .ios .about-info { - --ion-padding: 19px; - } - - .ios .about-info h3 { - font-weight: 700; - } -} - -#date-input-popover { - --offset-y: -var(--ion-safe-area-bottom); - - --max-width: 90%; - --width: 336px; -} diff --git a/03_source/mobile/src/pages/DemoOrderingApp/theme/variables.scss b/03_source/mobile/src/pages/DemoOrderingApp/theme/variables.scss new file mode 100644 index 0000000..1325221 --- /dev/null +++ b/03_source/mobile/src/pages/DemoOrderingApp/theme/variables.scss @@ -0,0 +1,278 @@ +.demo-ordering-app { + /* 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; + + /** custom **/ + --ion-color-main: rgb(221, 174, 21); + --ion-color-main-rgb: 221, 174, 21; + --ion-color-main-contrast: rgb(0, 0, 0); + --ion-color-main-contrast-rgb: 0, 0, 0; + --ion-color-main-shade: rgb(179, 142, 22); + --ion-color-main-tint: rgb(233, 195, 71); + + /** custom light **/ + --ion-color-custom-light: #141a22; + --ion-color-custom-light-contrast: #ffffff; + --ion-color-custom-light-shade: #12171d; + --ion-color-custom-light-tint: #232c38; + } + + .ion-color-main { + --ion-color-base: var(--ion-color-main); + --ion-color-base-rgb: var(--ion-color-main-rgb); + --ion-color-contrast: var(--ion-color-main-contrast); + --ion-color-contrast-rgb: var(--ion-color-main-contrast-rgb); + --ion-color-shade: var(--ion-color-main-shade); + --ion-color-tint: var(--ion-color-main-tint); + } + + .ion-color-custom-light { + --ion-color-base: var(--ion-color-custom-light); + --ion-color-contrast: var(--ion-color-custom-light-contrast); + --ion-color-shade: var(--ion-color-custom-light-shade); + --ion-color-tint: var(--ion-color-custom-light-tint); + } + + :root { + --main-orange-color: #d17842; + + --ion-background-color: #0e1016; + --ion-background-color-rgb: 0, 0, 0; + + --ion-text-color: #ffffff; + --ion-text-color-rgb: 255, 255, 255; + --ion-item-background: #141a22; + + --ion-card-background: #141a22; + --ion-tab-bar-color: #4d5053; + --ion-tab-bar-background: #0e1016; + --ion-tab-bar-color-selected: rgb(221, 174, 21); + + /* --ion-toolbar-color: white; */ + --ion-toolbar-background: #0e1016; + --ion-toolbar-border-color: #0e1016; + --ion-tab-bar-border-color: #0e1016; + --ion-grid-column-padding: 0; + } + + .app-icon { + color: rgb(221, 174, 21); + } + + ion-toolbar { + --padding-start: 1rem; + --padding-end: 1rem; + --padding-top: 1rem; + --padding-bottom: 1rem; + } + + .inner-toolbar { + --padding-top: 0rem !important; + --padding-bottom: 0rem !important; + } + + .button-container-img img { + height: 2.3rem; + border-radius: 10px; + } + + .gray-icon { + color: var(--ion-tab-bar-color); + background-color: #1b2025; + } + + .yellow-icon { + color: var(--ion-color-main); + background-color: #1b2025; + } + + .button-container { + background-color: #1b2025; + border: 2px solid #1d232a; + padding: 0.5rem; + border-radius: 13px; + + display: flex; + flex-direction: row; + align-content: center; + align-items: center; + justify-content: center; + } + + .button-container-img { + background-color: #1b2025; + border: 2px solid #1d232a; + padding: 0.2rem; + border-radius: 13px; + + display: flex; + flex-direction: row; + align-content: center; + align-items: center; + justify-content: center; + } + + ion-card { + padding: 0.8rem; + border-radius: 20px; + } + + .coffee-card img { + border-radius: 20px; + height: 10rem; + width: 100%; + } + + .coffee-card-long img { + border-radius: 20px; + height: 5rem !important; + width: 100%; + } + + .coffee-card-long-details { + margin-left: 1rem; + margin-top: -0.7rem; + } + + .coffee-card-long-details p { + font-size: 0.8rem; + margin: 0; + margin-top: 0.2rem; + } + + .coffee-card ion-card-title { + margin-top: 1rem; + font-size: 1rem; + } + + .coffee-card .coffee-price { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + align-content: center; + margin-top: -0.2rem; + } + + .coffee-card .coffee-price .add-button { + color: var(--main-orange-color) !important; + margin-top: 0.5rem; + } + + .coffee-card .coffee-price .add-button svg { + color: white !important; + } + + .outer-heading { + margin-bottom: -1.3rem; + margin-left: 0.5rem; + margin-right: 1.3rem; + } + /* +.searchbar-input { + + padding: 1.5rem !important; +} + +.searchbar-search-icon { + + margin-top: 0.3rem; + margin-right: 3rem; + padding-right: 3rem; +} */ + /* +ion-tab-bar { + + bottom: 20px; + position: relative; + box-shadow: 0px 0px 1.5px rgba(255, 255, 255, 0.2); + border-radius: 16px; +width: 92%; +border-top: none; +margin: 0 auto; +height: 55px; +} + +ion-tab-button { + --padding-bottom: 8px; + --padding-top: 8px; +} */ + + .custom-margin-left { + margin-left: 0.2rem; + } +}