mirror of
https://github.com/Fluffy-Bean/TastyBites.git
synced 2024-12-29 10:56:15 +00:00
Move to key-value basket, to keep track of information easier
Clean up code, add more types Clean up error handling
This commit is contained in:
parent
ec2ef95cca
commit
7066cc492b
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { link } from 'svelte-spa-router';
|
import { link } from 'svelte-spa-router';
|
||||||
import { Plus, Minus, Trash } from 'phosphor-svelte';
|
import { Plus, Minus, Trash, Acorn, Fish, GrainsSlash, Leaf, Pepper } from "phosphor-svelte";
|
||||||
|
|
||||||
import type { CartItem } from "../lib/types";
|
import { type CartItem, Labels } from "../lib/types";
|
||||||
import Cart from "../lib/cart";
|
import Cart from "../lib/cart";
|
||||||
|
|
||||||
export let item: CartItem;
|
export let item: CartItem;
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<img src="/MenuItemLoadingAlt.svg" alt="Item" class="basket-item-image">
|
<img src="/MenuItemLoadingAlt.svg" alt="Item" class="basket-item-image">
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<ul>
|
<ul class="basket-item-data">
|
||||||
<li class="basket-item-name"><a href="/item/{item.uuid}" use:link>{item.data.name}</a></li>
|
<li class="basket-item-name"><a href="/item/{item.uuid}" use:link>{item.data.name}</a></li>
|
||||||
<li class="basket-item-controls">
|
<li class="basket-item-controls">
|
||||||
<button class="button " on:click={() => { Cart.addToCart(item.uuid, -1) }}><Minus /></button>
|
<button class="button " on:click={() => { Cart.addToCart(item.uuid, -1) }}><Minus /></button>
|
||||||
|
@ -26,6 +26,22 @@
|
||||||
</li>
|
</li>
|
||||||
<li class="basket-item-price">£{item.data.price * item.amount} (£{item.data.price})</li>
|
<li class="basket-item-price">£{item.data.price * item.amount} (£{item.data.price})</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<ul class="basket-item-labels">
|
||||||
|
{#each item.data.labels as label}
|
||||||
|
{#if label === Labels.vegan}
|
||||||
|
<li class="vegan"><Leaf weight="fill" /></li>
|
||||||
|
{:else if label === Labels.fish}
|
||||||
|
<li class="fish"><Fish weight="fill" /></li>
|
||||||
|
{:else if label === Labels.nut}
|
||||||
|
<li class="nut"><Acorn weight="fill" /></li>
|
||||||
|
{:else if label === Labels.gluten}
|
||||||
|
<li class="gluten"><GrainsSlash weight="fill" /></li>
|
||||||
|
{:else if label === Labels.spicy}
|
||||||
|
<li class="spicy"><Pepper weight="fill" /></li>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -39,21 +55,7 @@
|
||||||
background-position: 135px -43px;
|
background-position: 135px -43px;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
ul {
|
|
||||||
margin: $spacing-small;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
li {
|
|
||||||
padding-bottom: $spacing-small;
|
|
||||||
list-style: none;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.basket-item-image {
|
.basket-item-image {
|
||||||
margin: $spacing-small;
|
margin: $spacing-small;
|
||||||
|
|
||||||
|
@ -64,6 +66,58 @@
|
||||||
|
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
.basket-item-labels {
|
||||||
|
padding: $spacing-normal;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
margin: 0 0 0 -15px;
|
||||||
|
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
border-radius: $border-radius-circle;
|
||||||
|
background-color: $color-dark;
|
||||||
|
color: $color-on-dark;
|
||||||
|
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
&.vegan {
|
||||||
|
background-color: $color-vegan;
|
||||||
|
}
|
||||||
|
&.fish {
|
||||||
|
background-color: $color-fish;
|
||||||
|
}
|
||||||
|
&.nut {
|
||||||
|
background-color: $color-nut;
|
||||||
|
}
|
||||||
|
&.gluten {
|
||||||
|
background-color: $color-gluten;
|
||||||
|
}
|
||||||
|
&.spicy {
|
||||||
|
background-color: $color-spicy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.basket-item-data {
|
||||||
|
margin: $spacing-small;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
> li {
|
||||||
|
padding-bottom: $spacing-small;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.basket-item-name a {
|
.basket-item-name a {
|
||||||
font-size: $font-size-h2;
|
font-size: $font-size-h2;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|
|
@ -1,86 +1,84 @@
|
||||||
|
import type { Writable } from "svelte/store";
|
||||||
import { get, writable } from "svelte/store";
|
import { get, writable } from "svelte/store";
|
||||||
|
|
||||||
import type { Item, CartItem } from './types';
|
import { type CartItem } from './types';
|
||||||
import { getItemByUUID, postVerifyCart } from "./test-api";
|
import { getItemByUUID, postVerifyCart } from "./test-api";
|
||||||
|
|
||||||
|
|
||||||
// Load content from localstorage
|
// Load content from localstorage
|
||||||
let local: CartItem[] = [];
|
let local: Record<string, CartItem> = {};
|
||||||
try {
|
try {
|
||||||
let verified = await postVerifyCart(
|
await postVerifyCart(JSON.parse(localStorage.getItem("basket")))
|
||||||
JSON.parse(localStorage.getItem("basket")) || []
|
.then((data: Record<string, CartItem>) => {
|
||||||
);
|
local = data;
|
||||||
|
console.log(data);
|
||||||
if (verified instanceof Error) {
|
})
|
||||||
throw new Error("Bruh");
|
.catch((error) => {
|
||||||
}
|
throw error; // Hot potato style
|
||||||
|
});
|
||||||
local = <CartItem[]>verified;
|
|
||||||
} catch {
|
} catch {
|
||||||
console.error("Failed to load cart")
|
console.error("Failed to load cart")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store function
|
// Store function
|
||||||
function createCartStore() {
|
function createCartStore() {
|
||||||
const cart = writable(local);
|
const cart: Writable<Record<string, CartItem>> = writable(local);
|
||||||
|
|
||||||
async function addToCart(uuid: string, amount: number) {
|
async function addToCart(uuid: string, amount: number) {
|
||||||
let found = false;
|
if (get(cart)[uuid] !== undefined) {
|
||||||
|
cart.update((cart: Record<string, CartItem>) => {
|
||||||
get(cart).forEach((item: CartItem) => {
|
cart[uuid].amount += amount;
|
||||||
if (item.uuid === uuid) {
|
return cart;
|
||||||
item.amount += amount;
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
if (!found) {
|
await getItemByUUID(uuid)
|
||||||
let cartData: Item;
|
.then((data) => {
|
||||||
|
cart.update((cart: Record<string, CartItem>) =>
|
||||||
try {
|
Object.assign({}, cart, {[uuid]: {
|
||||||
let data: Item | Error = await getItemByUUID(uuid);
|
|
||||||
if (data instanceof Error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cartData = <Item>data;
|
|
||||||
} finally {
|
|
||||||
const newItem: CartItem = {
|
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
data: cartData,
|
data: data,
|
||||||
};
|
}})
|
||||||
cart.update((cart: CartItem[]) => [...cart, newItem]);
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove items that have an amount of 0 or lower
|
function getEntries(): [string, CartItem][] {
|
||||||
cart.update((cart) => cart.filter((item) => item.amount > 0))
|
return Object.entries(get(cart));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUniqueLength() {
|
function getUniqueLength(): number {
|
||||||
return get(cart).length;
|
return Object.keys(get(cart)).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTotalLength() {
|
function getTotalLength(): number {
|
||||||
let amounts = get(cart).map((item) => item.amount);
|
let totalCartSize: number = 0;
|
||||||
return amounts.reduce((a, b) => a + b, 0);
|
Object.values(get(cart)).forEach((item: CartItem) => {
|
||||||
|
totalCartSize += item.amount;
|
||||||
|
});
|
||||||
|
return totalCartSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTotalPrice() {
|
function getTotalPrice(): number {
|
||||||
let price = 0;
|
let totalCartPrice: number = 0;
|
||||||
get(cart).forEach((item) => {
|
Object.values(get(cart)).forEach((item: CartItem) => {
|
||||||
price += item.amount * item.data.price;
|
totalCartPrice += (item.amount * item.data.price);
|
||||||
})
|
});
|
||||||
return price;
|
return totalCartPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeByUUID(uuid: string) {
|
function removeByUUID(uuid: string) {
|
||||||
cart.update((cart) => cart.filter((item) => item.uuid !== uuid))
|
cart.update((cart) => {
|
||||||
|
delete cart[uuid];
|
||||||
|
return cart;
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...cart,
|
...cart,
|
||||||
addToCart,
|
addToCart,
|
||||||
|
getEntries,
|
||||||
getUniqueLength,
|
getUniqueLength,
|
||||||
getTotalLength,
|
getTotalLength,
|
||||||
getTotalPrice,
|
getTotalPrice,
|
||||||
|
@ -96,4 +94,9 @@ Cart.subscribe((value) => {
|
||||||
localStorage.setItem("basket", JSON.stringify(value));
|
localStorage.setItem("basket", JSON.stringify(value));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
Cart.subscribe((value) => {
|
||||||
|
console.log(value);
|
||||||
|
});
|
||||||
|
|
||||||
export default Cart;
|
export default Cart;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type {CartItem, Item} from './types';
|
import { type CartItem, type Item } from './types';
|
||||||
import TestData from './test-data';
|
import TestData from './test-data';
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,13 +19,13 @@ export async function getAnnouncements(): Promise<{image: string}> {
|
||||||
if (cache.announcement_banner) {
|
if (cache.announcement_banner) {
|
||||||
return cache.announcement_banner;
|
return cache.announcement_banner;
|
||||||
}
|
}
|
||||||
|
await fakeDelay(200)
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
image: "/BannerExampleImage.jpg",
|
image: "/BannerExampleImage.jpg",
|
||||||
};
|
};
|
||||||
cache.announcement_banner = data;
|
cache.announcement_banner = data;
|
||||||
|
|
||||||
await fakeDelay(200)
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,17 +34,18 @@ export async function getPopularToday(): Promise<Item[]> {
|
||||||
if (cache.popular_today) {
|
if (cache.popular_today) {
|
||||||
return cache.popular_today;
|
return cache.popular_today;
|
||||||
}
|
}
|
||||||
|
await fakeDelay(200)
|
||||||
|
|
||||||
const data: Item[] = TestData;
|
const data: Item[] = TestData;
|
||||||
cache.popular_today = data;
|
cache.popular_today = data;
|
||||||
|
|
||||||
await fakeDelay(200)
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getMenuItems() {
|
export async function getMenuItems(): Promise<{name: string, items: Item[]}[]> {
|
||||||
const data = [
|
await fakeDelay(20);
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
name: "Main Menu",
|
name: "Main Menu",
|
||||||
items: TestData,
|
items: TestData,
|
||||||
|
@ -58,12 +59,12 @@ export async function getMenuItems() {
|
||||||
items: TestData,
|
items: TestData,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
await fakeDelay(20)
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getItemsByUUID(items: string[]): Promise<Item[] | Error> {
|
export async function getItemsByUUID(items: string[]): Promise<Item[]> {
|
||||||
|
await fakeDelay(200)
|
||||||
|
|
||||||
let data: Item[] = [];
|
let data: Item[] = [];
|
||||||
|
|
||||||
TestData.forEach((itemInDatabase: Item) => {
|
TestData.forEach((itemInDatabase: Item) => {
|
||||||
|
@ -74,8 +75,6 @@ export async function getItemsByUUID(items: string[]): Promise<Item[] | Error> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await fakeDelay(200)
|
|
||||||
|
|
||||||
if (data.length < 0) {
|
if (data.length < 0) {
|
||||||
throw new Error("Resource could not be found");
|
throw new Error("Resource could not be found");
|
||||||
}
|
}
|
||||||
|
@ -84,21 +83,26 @@ export async function getItemsByUUID(items: string[]): Promise<Item[] | Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function getItemByUUID(uuid: string): Promise<Item | Error> {
|
export async function getItemByUUID(uuid: string): Promise<Item> {
|
||||||
let data: Item[] | Error = await getItemsByUUID([uuid]);
|
let data: Item[];
|
||||||
|
|
||||||
if (data instanceof Error) {
|
await getItemsByUUID([uuid])
|
||||||
throw new Error("Resource could not be found");
|
.then((result) => {
|
||||||
}
|
if (result.length != 1) {
|
||||||
if (data.length != 1) {
|
|
||||||
throw new Error("Resource could not be found");
|
throw new Error("Resource could not be found");
|
||||||
|
} else {
|
||||||
|
data = result;
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
return data[0];
|
return data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function postContactEmail(name: string, email: string, message: string): Promise<string | Error> {
|
export async function postContactEmail(name: string, email: string, message: string): Promise<string> {
|
||||||
await fakeDelay(200)
|
await fakeDelay(200)
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
|
@ -116,35 +120,29 @@ export async function postContactEmail(name: string, email: string, message: str
|
||||||
return "Check your email to confirm the message!";
|
return "Check your email to confirm the message!";
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postVerifyCart(currentCartData: CartItem[]): Promise<CartItem[] | Error> {
|
export async function postVerifyCart(currentCartData: Record<string, CartItem>): Promise<Record<string, CartItem>> {
|
||||||
if (currentCartData.length <= 0) {
|
let verifiedItems: Item[] = []
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let itemUUIDs: string[] = currentCartData.map((item) => item.uuid);
|
await getItemsByUUID(Object.keys(currentCartData))
|
||||||
let verifiedItems: Item[] | Error = await getItemsByUUID(itemUUIDs);
|
.then((data) => {
|
||||||
|
verifiedItems = data
|
||||||
if (verifiedItems instanceof Error) {
|
|
||||||
return new Error("Could not collect new cart information");
|
|
||||||
}
|
|
||||||
|
|
||||||
let newCartData: CartItem[] = [];
|
|
||||||
currentCartData.forEach((currentItem) => {
|
|
||||||
let data: Item;
|
|
||||||
verifiedItems.forEach((verifiedItem) => {
|
|
||||||
if (verifiedItem.uuid === currentItem.uuid) {
|
|
||||||
data = verifiedItem;
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
.catch(() => {
|
||||||
if (data) {
|
return new Error("Could not collect new cart information")
|
||||||
newCartData.push({
|
|
||||||
uuid: currentItem.uuid,
|
|
||||||
amount: currentItem.amount,
|
|
||||||
data: data,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let newCartData: Record<string, CartItem> = {};
|
||||||
|
Object.entries(currentCartData).forEach(([key, value]) => {
|
||||||
|
verifiedItems.forEach((verifiedItem) => {
|
||||||
|
if (verifiedItem.uuid === key) {
|
||||||
|
newCartData[key] = {
|
||||||
|
uuid: value.uuid,
|
||||||
|
amount: value.amount,
|
||||||
|
data: verifiedItem,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return newCartData;
|
return newCartData;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,13 +44,13 @@ const TestData: Item[] = [
|
||||||
labels: [Labels.nut],
|
labels: [Labels.nut],
|
||||||
detail: "Example",
|
detail: "Example",
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// uuid: "gwagwa",
|
uuid: "gwagwa",
|
||||||
// name: "GwaGwa",
|
name: "GwaGwa",
|
||||||
// price: 69,
|
price: 69,
|
||||||
// labels: [Labels.nut],
|
labels: [Labels.nut],
|
||||||
// image: "/dab.jpg",
|
image: "/dab.jpg",
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
uuid: "hogmelon",
|
uuid: "hogmelon",
|
||||||
name: "Hogermellon",
|
name: "Hogermellon",
|
||||||
|
@ -59,14 +59,14 @@ const TestData: Item[] = [
|
||||||
image: "/wathog.jpg",
|
image: "/wathog.jpg",
|
||||||
detail: "Example",
|
detail: "Example",
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// uuid: "bluhog",
|
uuid: "bluhog",
|
||||||
// name: "Blue HOGGGGG",
|
name: "Blue HOGGGGG",
|
||||||
// price: 0,
|
price: 0,
|
||||||
// labels: [Labels.nut, Labels.gluten, Labels.spicy],
|
labels: [Labels.nut, Labels.gluten, Labels.spicy],
|
||||||
// image: "/sonichog.jpg",
|
image: "/sonichog.jpg",
|
||||||
// detail: "Example",
|
detail: "Example",
|
||||||
// },
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default TestData;
|
export default TestData;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { link } from 'svelte-spa-router';
|
import { link } from 'svelte-spa-router';
|
||||||
import { Basket } from "phosphor-svelte";
|
import { Basket } from "phosphor-svelte";
|
||||||
|
|
||||||
import type { CartItem } from "../lib/types";
|
import { type CartItem } from "../lib/types";
|
||||||
import { getPopularToday } from "../lib/test-api";
|
import { getPopularToday } from "../lib/test-api";
|
||||||
import Cart from "../lib/cart";
|
import Cart from "../lib/cart";
|
||||||
import MenuList from "../components/MenuList.svelte";
|
import MenuList from "../components/MenuList.svelte";
|
||||||
|
@ -10,23 +10,23 @@
|
||||||
|
|
||||||
let popularToday = getPopularToday();
|
let popularToday = getPopularToday();
|
||||||
|
|
||||||
let items: CartItem[];
|
let items: [string, CartItem][];
|
||||||
let totalPrice: number;
|
let totalPrice: number;
|
||||||
|
|
||||||
Cart.subscribe(() => {
|
Cart.subscribe(() => {
|
||||||
items = $Cart;
|
items = Cart.getEntries();
|
||||||
totalPrice = Cart.getTotalPrice();
|
totalPrice = Cart.getTotalPrice();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
{#if items.length > 0}
|
{#if items.entries}
|
||||||
<h1>Cart</h1>
|
<h1>Cart</h1>
|
||||||
|
|
||||||
<button id="checkout-button">Checkout</button>
|
<button id="checkout-button">Checkout</button>
|
||||||
<h2>Order total: £{totalPrice}</h2>
|
<h2>Order total: £{totalPrice}</h2>
|
||||||
|
|
||||||
{#each items as item}
|
{#each items as [key, item]}
|
||||||
<div class="basket-item">
|
<div class="basket-item">
|
||||||
<BasketItem item={item}/>
|
<BasketItem item={item}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,8 +10,7 @@
|
||||||
export let params;
|
export let params;
|
||||||
|
|
||||||
$: item = getItemByUUID(params.uuid)
|
$: item = getItemByUUID(params.uuid)
|
||||||
|
$: popularToday = getPopularToday();
|
||||||
let popularToday = getPopularToday();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="main">
|
<div class="main">
|
||||||
|
|
|
@ -78,6 +78,10 @@ hr {
|
||||||
background-color: rgba($color-dark, 0.1);
|
background-color: rgba($color-dark, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: $font-family;
|
||||||
|
}
|
||||||
|
|
||||||
.spacer {
|
.spacer {
|
||||||
height: $spacing-large;
|
height: $spacing-large;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue