mirror of
https://github.com/Fluffy-Bean/TastyBites.git
synced 2025-01-29 17:48:28 +00:00
Create checkout forms
Move section out of container because its more useful that way give section more styling options Move basket item out into its own div, as to not override containers styling for no reason Change formMessage type from sting | Error to just string Make the booking progress code more readable
This commit is contained in:
parent
488503d3ff
commit
5afe4386d6
|
@ -23,61 +23,67 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#if !item.data.availability}
|
||||
<div class="basket-item-notice">
|
||||
<SealWarning weight="fill" /> <span>Item is no-longer for sale</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if item.data.images && item.data.images[0]}
|
||||
<img src="{item.data.images[0]}" alt="Item" class="basket-item-image">
|
||||
{:else}
|
||||
<img src={ImageLoading} alt="Item" class="basket-item-image">
|
||||
{/if}
|
||||
<div class="container" >
|
||||
<div class="basket-item" class:basket-item-availability={!item.data.availability}>
|
||||
{#if !item.data.availability}
|
||||
<div class="basket-item-notice">
|
||||
<SealWarning weight="fill" /> <span>Item is no-longer for sale</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<ul class="basket-item-data">
|
||||
<li class="basket-item-name"><a href="/item/{item.data.uuid}" use:link>{item.data.name}</a></li>
|
||||
<li class="basket-item-controls">
|
||||
<button class="button" class:disabled={item.amount <= 1} on:click={reduce}><Minus /></button>
|
||||
<p>{item.amount}</p>
|
||||
<button class="button" class:disabled={item.amount >= 99} on:click={add}><Plus /></button>
|
||||
<hr>
|
||||
<button class="button evil" on:click={yeet}><Trash /></button>
|
||||
</li>
|
||||
<li class="basket-item-price">£{item.data.price * item.amount} (£{item.data.price})</li>
|
||||
</ul>
|
||||
{#if item.data.images && item.data.images[0]}
|
||||
<img src="{item.data.images[0]}" alt="Item" class="basket-item-image">
|
||||
{:else}
|
||||
<img src={ImageLoading} alt="Item" class="basket-item-image">
|
||||
{/if}
|
||||
|
||||
<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>
|
||||
<ul class="basket-item-data">
|
||||
<li class="basket-item-name"><a href="/item/{item.data.uuid}" use:link>{item.data.name}</a></li>
|
||||
<li class="basket-item-controls">
|
||||
<button class="button" class:disabled={item.amount <= 1} on:click={reduce}><Minus /></button>
|
||||
<p>{item.amount}</p>
|
||||
<button class="button" class:disabled={item.amount >= 99} on:click={add}><Plus /></button>
|
||||
<hr>
|
||||
<button class="button evil" on:click={yeet}><Trash /></button>
|
||||
</li>
|
||||
<li class="basket-item-price">£{item.data.price * item.amount} (£{item.data.price})</li>
|
||||
</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">
|
||||
@import "../styles/vars";
|
||||
|
||||
.container {
|
||||
.basket-item {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
// Move background out of way of the image
|
||||
background-position: 135px -43px;
|
||||
border-radius: $border-radius-normal;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
&.basket-item-availability {
|
||||
padding-top: 30px;
|
||||
}
|
||||
}
|
||||
.basket-item-notice {
|
||||
width: 100%;
|
||||
|
@ -148,8 +154,8 @@
|
|||
}
|
||||
}
|
||||
.basket-item-data {
|
||||
margin: $spacing-small;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: $spacing-small;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { link } from 'svelte-spa-router';
|
||||
import { Acorn, Fish, Leaf, Pepper, ArrowUpRight, GrainsSlash, SealWarning } from 'phosphor-svelte';
|
||||
import { Acorn, Fish, Leaf, Pepper, ArrowUpRight, GrainsSlash } from 'phosphor-svelte';
|
||||
|
||||
import { type Item, Labels} from "../lib/types";
|
||||
import LoadingImage from '/assets/MenuItemLoadingAlt.svg';
|
||||
|
@ -10,21 +10,19 @@
|
|||
|
||||
let element: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
keepSquare();
|
||||
})
|
||||
|
||||
function keepSquare() {
|
||||
element.style.height = "";
|
||||
element.style.height = element.offsetWidth + "px";
|
||||
}
|
||||
|
||||
onMount(keepSquare)
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={keepSquare}></svelte:window>
|
||||
|
||||
<div class="menu-item" bind:this={element}>
|
||||
{#if !item.availability}
|
||||
<!-- <div class="menu-item-notice"><span>Item is no-longer for sale</span></div>-->
|
||||
<!--<div class="menu-item-notice"><span>Item is no-longer for sale</span></div>-->
|
||||
{/if}
|
||||
|
||||
{#if item.images && item.images[0]}
|
||||
|
@ -51,13 +49,7 @@
|
|||
{/if}
|
||||
</ul>
|
||||
|
||||
<a class="menu-item-link" href="/item/{item.uuid}" use:link>
|
||||
View <ArrowUpRight />
|
||||
</a>
|
||||
|
||||
<ul class="menu-item-detail">
|
||||
<li>{item.name}</li>
|
||||
<li>£{item.price}</li>
|
||||
</ul>
|
||||
<a class="menu-item-link" href="/item/{item.uuid}" use:link>View <ArrowUpRight /></a>
|
||||
<ul class="menu-item-detail"><li>£{item.price} | {item.name}</li></ul>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,16 +8,14 @@
|
|||
let progress = 1;
|
||||
querystring.subscribe((params) => {
|
||||
const url = new URLSearchParams(params);
|
||||
const parsed = parseInt(url.get("progress"));
|
||||
|
||||
progress = 1;
|
||||
if (
|
||||
url.has("progress") &&
|
||||
!isNaN(parseInt(url.get("progress")))
|
||||
) {
|
||||
progress = parseInt(url.get("progress"));
|
||||
}
|
||||
|
||||
if ([1, 2, 3].indexOf(progress) == -1) {
|
||||
if (url.has("progress") && !isNaN(parsed)) {
|
||||
progress = parsed;
|
||||
}
|
||||
if ([1, 2, 3].indexOf(progress) === -1) {
|
||||
progress = 1;
|
||||
push("/booking?progress=1");
|
||||
}
|
||||
|
@ -226,7 +224,6 @@
|
|||
margin: 0 $spacing-normal;
|
||||
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
height: 5px;
|
||||
|
||||
position: relative;
|
||||
|
|
|
@ -1,20 +1,87 @@
|
|||
<script lang="ts">
|
||||
import { link } from "svelte-spa-router";
|
||||
import { ArrowLeft } from "phosphor-svelte";
|
||||
import { ArrowLeft, ArrowRight, Basket, SealWarning } from "phosphor-svelte";
|
||||
|
||||
import Cart from "../lib/cart";
|
||||
import PaymentForm from "./Checkout/PaymentForm.svelte";
|
||||
import LeFinal from "./Checkout/LeFinal.svelte";
|
||||
|
||||
export let params: {
|
||||
progress?: string;
|
||||
};
|
||||
|
||||
let unavailableItems = false;
|
||||
Cart.getEntries().forEach(([_, item]) => {
|
||||
if (!item.data.availability) unavailableItems = true;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{#if unavailableItems}
|
||||
<div class="notice error">
|
||||
<SealWarning weight="fill" /> <span>Order contains an item that is no-longer available</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<a href="/cart" use:link id="back-button"><ArrowLeft /> Back</a>
|
||||
<h1>Cart</h1>
|
||||
|
||||
<h2>Order total: £Balls</h2>
|
||||
<div id="checkoutHeader">
|
||||
<a href="/cart" use:link id="cancel-button"><ArrowLeft /> Cancel</a>
|
||||
</div>
|
||||
|
||||
{#if params.progress === "1"}
|
||||
<PaymentForm />
|
||||
<div id="checkoutFooter">
|
||||
<div />
|
||||
<a href="/cart/checkout/2" use:link id="navigation-button">Order <ArrowRight /></a>
|
||||
</div>
|
||||
{:else if params.progress === "2"}
|
||||
<LeFinal />
|
||||
<div id="checkoutFooter">
|
||||
<a href="/cart/checkout/1" use:link id="navigation-button"><ArrowLeft /> Payment</a>
|
||||
<a href="/cart/checkout/3" use:link id="navigation-button">Confirm Purchase <ArrowRight /></a>
|
||||
</div>
|
||||
{:else}
|
||||
<div id="checkoutLost">
|
||||
<h1>Something went wrong! <Basket weight="fill" /></h1>
|
||||
<p>Return <a href="/cart/checkout/1" use:link>to checkout</a>.</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
@import "../styles/vars";
|
||||
|
||||
#back-button {
|
||||
margin-top: 8px;
|
||||
#checkoutHeader {
|
||||
padding-bottom: $spacing-normal;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
> p {
|
||||
padding: 0 $spacing-small;
|
||||
|
||||
width: max-content;
|
||||
height: 30px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
font-size: $font-size-p;
|
||||
}
|
||||
}
|
||||
|
||||
#checkoutFooter {
|
||||
padding-top: $spacing-normal;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#cancel-button {
|
||||
padding: 0 $spacing-small;
|
||||
|
||||
width: max-content;
|
||||
|
@ -33,8 +100,85 @@
|
|||
color: $color-on-background;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-light;
|
||||
color: $color-on-light;
|
||||
background-color: $color-error;
|
||||
color: $color-on-error;
|
||||
}
|
||||
}
|
||||
#navigation-button {
|
||||
padding: 0 $spacing-small;
|
||||
|
||||
width: max-content;
|
||||
height: 30px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
font-size: $font-size-p;
|
||||
text-decoration: none;
|
||||
|
||||
border-radius: 9999px;
|
||||
background-color: $color-light;
|
||||
color: $color-on-light;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-dark;
|
||||
color: $color-on-dark;
|
||||
}
|
||||
}
|
||||
|
||||
#checkoutLost {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
max-width: $sizing-default-width;
|
||||
height: 400px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> h1 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-size: $font-size-very-fucking-big;
|
||||
text-align: center;
|
||||
}
|
||||
> p {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin-right: auto;
|
||||
margin-bottom: $spacing-large;
|
||||
margin-left: auto;
|
||||
|
||||
max-width: $sizing-default-width;
|
||||
height: 40px;
|
||||
|
||||
position: sticky;
|
||||
top: calc($spacing-small + $sizing-navigation-height);
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-size: $font-size-p;
|
||||
|
||||
border-radius: $border-radius-large;
|
||||
background: $color-dark;
|
||||
color: $color-on-dark;
|
||||
|
||||
box-shadow: 0 1px 0.5px rgba(#000, 0.3);
|
||||
|
||||
&.error {
|
||||
background: $color-error;
|
||||
color: $color-on-error;
|
||||
}
|
||||
}
|
||||
</style>
|
74
front/src/pages/Checkout/LeFinal.svelte
Normal file
74
front/src/pages/Checkout/LeFinal.svelte
Normal file
|
@ -0,0 +1,74 @@
|
|||
<script lang="ts">
|
||||
import { type CartItem } from "../../lib/types";
|
||||
import Cart from "../../lib/cart";
|
||||
|
||||
let items: [string, CartItem][];
|
||||
let totalPrice: number;
|
||||
|
||||
Cart.subscribe(() => {
|
||||
items = Cart.getEntries();
|
||||
totalPrice = Cart.getTotalPrice();
|
||||
});
|
||||
</script>
|
||||
|
||||
<h1>Checkout > Order</h1>
|
||||
<div class="container">
|
||||
<div class="section small">
|
||||
<div class="table">
|
||||
<table>
|
||||
<tr><th>Price (each)</th><th>Item Name</th><th>Amount</th></tr>
|
||||
{#each items as [_, item]}
|
||||
<tr>
|
||||
<td>£{item.data.price * item.amount} (£{item.data.price})</td>
|
||||
<td>{item.data.name}</td>
|
||||
<td>{item.amount}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</div>
|
||||
<div class="spacer half" />
|
||||
|
||||
<p>Total: ${totalPrice}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../styles/vars";
|
||||
|
||||
.table {
|
||||
border-radius: $border-radius-normal;
|
||||
border: 1px solid rgba($color-dark, 0.3);
|
||||
background-color: $color-light;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid rgba($color-dark, 0.3);
|
||||
|
||||
&:last-of-type {
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: $spacing-xsmall $spacing-small;
|
||||
border-right: 1px solid rgba($color-dark, 0.3);
|
||||
|
||||
&:last-of-type {
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: $font-weight-bolder;
|
||||
}
|
||||
td {
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
66
front/src/pages/Checkout/PaymentForm.svelte
Normal file
66
front/src/pages/Checkout/PaymentForm.svelte
Normal file
|
@ -0,0 +1,66 @@
|
|||
<script lang="ts">
|
||||
import { SealCheck } from "phosphor-svelte";
|
||||
|
||||
let name = "";
|
||||
let email = "";
|
||||
|
||||
let nameValid = true;
|
||||
let emailValid = true;
|
||||
|
||||
function validateName() { nameValid = name.length > 1 }
|
||||
function validateEmail() { emailValid = email.length > 1 }
|
||||
|
||||
function onSubmit() {
|
||||
validateName();
|
||||
validateEmail();
|
||||
}
|
||||
</script>
|
||||
|
||||
<h1>Checkout > Payment</h1>
|
||||
<form on:submit|preventDefault={onSubmit}>
|
||||
<div class="form-element">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input
|
||||
bind:value={name}
|
||||
on:blur={validateName}
|
||||
on:input={validateName}
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
class="form-input"
|
||||
/>
|
||||
<span class="form-notice error">
|
||||
{#if !nameValid}
|
||||
Enter a name
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="spacer half" />
|
||||
|
||||
<div class="form-element">
|
||||
<label class="form-label" for="email">Email</label>
|
||||
<input
|
||||
bind:value={email}
|
||||
on:blur={validateEmail}
|
||||
on:input={validateEmail}
|
||||
type="text"
|
||||
id="email"
|
||||
name="email"
|
||||
class="form-input"
|
||||
/>
|
||||
<span class="form-notice error">
|
||||
{#if !emailValid}
|
||||
Email not valid
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="spacer half" />
|
||||
|
||||
<p class="form-message success"><SealCheck weight="fill" /> GwaGwa</p>
|
||||
</form>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../styles/vars";
|
||||
</style>
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
const minMessageLength = 150;
|
||||
|
||||
let formMessage: Promise<string | Error>;
|
||||
let formMessage: Promise<string>;
|
||||
|
||||
let name = "";
|
||||
let email = "";
|
||||
|
|
|
@ -40,11 +40,11 @@ const routes = {
|
|||
conditions: [],
|
||||
userData: { showNavBar: true, fullWidth: false },
|
||||
}),
|
||||
"/cart/checkout": wrap({
|
||||
"/cart/checkout/:progress?": wrap({
|
||||
asyncComponent: () => import("./pages/Checkout.svelte"),
|
||||
loadingComponent: PageLoading,
|
||||
conditions: [],
|
||||
userData: { showNavBar: true, fullWidth: true },
|
||||
userData: { showNavBar: true, fullWidth: false },
|
||||
}),
|
||||
"/booking": wrap({
|
||||
asyncComponent: () => import("./pages/Booking.svelte"),
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
box-shadow: 0 1px 0.5px rgba(#000, 0.3);
|
||||
|
||||
.header {
|
||||
> .header {
|
||||
padding: $spacing-normal;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -20,8 +20,4 @@
|
|||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: $spacing-normal;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-size: $font-size-p;
|
||||
font-size: $font-size-small;
|
||||
text-align: center;
|
||||
|
||||
background: $color-error;
|
||||
|
@ -120,7 +120,7 @@
|
|||
}
|
||||
|
||||
.menu-item-detail {
|
||||
padding: $spacing-normal;
|
||||
padding: $spacing-small;
|
||||
|
||||
width: 100%;
|
||||
|
||||
|
@ -131,9 +131,12 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
font-weight: 800;
|
||||
font-size: $font-size-xsmall;
|
||||
font-weight: $font-weight-bolder;
|
||||
|
||||
background-image: linear-gradient(0deg, $color-dark, rgba($color-dark, 0));
|
||||
//border-radius: $border-radius-large $border-radius-large 0 0;
|
||||
//background-image: linear-gradient(0deg, rgba($color-dark, 1), rgba($color-dark, 0.6));
|
||||
background-color: rgba($color-dark, 0.7);
|
||||
color: $color-light;
|
||||
|
||||
z-index: 2;
|
||||
|
|
|
@ -107,6 +107,20 @@ button {
|
|||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: $spacing-normal;
|
||||
|
||||
&.large {
|
||||
padding: $spacing-large;
|
||||
}
|
||||
&.small {
|
||||
padding: $spacing-small;
|
||||
}
|
||||
&.xsmall {
|
||||
padding: $spacing-xsmall;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
|
Loading…
Reference in a new issue