ok epic
Former-commit-id: 8d565d0f3bae3df3c66f14186756fc16084004f1
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/basket.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-basket-list.mjs"></script>
|
||||
<script type="module" src="/components/accessability-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/tag.mjs"></script>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<script type="module" src="/components/navbar.mjs"></script>
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-basket-list.mjs"></script>
|
||||
<script type="module" src="/components/checkout.mjs"></script>
|
||||
<script type="module" src="/components/accessability-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
|
||||
@@ -180,22 +180,7 @@ class BasketPopout extends Component {
|
||||
{this.state.total} Items
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
${this.state.items
|
||||
? Object.keys(this.state.items).map((key) => {
|
||||
const item = this.state.items[key];
|
||||
return /* html */`
|
||||
<div class="popup-content-item">
|
||||
<span class="popup-content-item-quantity">x${item.quantity}</span>
|
||||
<super-compact-listing-component class="sc-listing"
|
||||
id="${key.split('~')[0]}"
|
||||
type="${item.type}"
|
||||
quantity="${item.quantity}"
|
||||
modifier="${key.split('~')[1] || ''}">
|
||||
</super-compact-listing-component>
|
||||
</div>
|
||||
`;
|
||||
}).join('')
|
||||
: ''}
|
||||
<immutable-basket-list-component h="400px" class="basket-list"></immutable-basket-list-component>
|
||||
</div>
|
||||
<div class="popup-footer">
|
||||
<span class="popup-footer-total">Subtotal: £${parseFloat(this.state.subtotal).toFixed(2)}</span>
|
||||
@@ -311,39 +296,12 @@ class BasketPopout extends Component {
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: left;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.popup-content-item {
|
||||
background-color: #F5F6F6;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popup-content-item-quantity {
|
||||
font-size: 2em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.sc-listing {
|
||||
flex-basis: 100%;
|
||||
flex-grow: 3;
|
||||
}
|
||||
|
||||
.popup-content-item-value {
|
||||
text-align: right;
|
||||
flex-grow: 1;
|
||||
.basket-list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.popup-footer {
|
||||
|
||||
@@ -258,7 +258,7 @@ class Basket extends Component {
|
||||
button.addEventListener('click', (event) => {
|
||||
let clickedItem = event.target.parentElement;
|
||||
let listing = clickedItem.querySelector('.basket-item-listing');
|
||||
if (!listing) {
|
||||
while (listing === null) {
|
||||
clickedItem = clickedItem.parentElement;
|
||||
listing = clickedItem.querySelector('.basket-item-listing');
|
||||
}
|
||||
@@ -330,7 +330,7 @@ class Basket extends Component {
|
||||
input.addEventListener('change', (event) => {
|
||||
let clickedItem = event.target.parentElement;
|
||||
let listing = clickedItem.querySelector('.basket-item-listing');
|
||||
if (!listing) {
|
||||
while (listing === null) {
|
||||
clickedItem = clickedItem.parentElement;
|
||||
listing = clickedItem.querySelector('.basket-item-listing');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { GetBasketTotalPrice } from './basket-popout.mjs';
|
||||
import { RegisterComponent, Component } from './components.mjs';
|
||||
import { RegisterComponent, Component, SideLoad } from './components.mjs';
|
||||
|
||||
class Checkout extends Component {
|
||||
static __IDENTIFY() { return 'checkout'; }
|
||||
@@ -17,53 +17,145 @@ class Checkout extends Component {
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
<div class="checkout-header">
|
||||
<span class="checkout-header-title">Checkout</span>
|
||||
</div>
|
||||
<div class="checkout">
|
||||
<div class="checkout-header">
|
||||
<span class="checkout-header-title">Checkout</span>
|
||||
<span class="checkout-header-total">Total: £{this.state.total}</span>
|
||||
</div>
|
||||
<div class="checkout-delivery-form">
|
||||
<div class="checkout-form-row">
|
||||
<input class="checkout-form-row-input" type="text" name="name" placeholder="name"/>
|
||||
<div class="checkout-body-left">
|
||||
<div class="checkout-delivery-form">
|
||||
<div class="checkout-delivery-form-title section-title">Shipping Details</div>
|
||||
<input class="checkout-form-row-input" type="text" autocomplete="address-line1" name="address" placeholder="Shipping Address"/>
|
||||
<input class="checkout-form-row-input" type="text" autocomplete="postal-code" name="postcode" placeholder="Postcode"/>
|
||||
</div>
|
||||
<div class="checkout-form-row">
|
||||
<input class="checkout-form-row-input" type="text" name="address" placeholder="address"/>
|
||||
</div>
|
||||
<div class="checkout-form-row">
|
||||
<input class="checkout-form-row-input" type="text" name="postcode" placeholder="postcode"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-payment-form">
|
||||
<div class="checkout-form-row">
|
||||
<input class="checkout-form-row-input" type="text" name="card_number" placeholder="Card Number"/>
|
||||
<input class="checkout-form-row-input" type="text" name="cvv" placeholder="CCV"/>
|
||||
<input class="checkout-form-row-input" type="text" name="exp" placeholder="MM/YY"/>
|
||||
|
||||
<div class="checkout-delivery-form-title section-title">Payment Details</div>
|
||||
<div class="checkout-payment-form">
|
||||
<div class="payment-row">
|
||||
<span class="form-item">
|
||||
<label class="checkout-form-row-label"> Card Number </label>
|
||||
<input class="checkout-form-row-input" type="text" autocomplete="cc-number" name="cc-number" placeholder="0000 0000 0000 0000"/>
|
||||
</span>
|
||||
<span class="form-item">
|
||||
<label class="checkout-form-row-label"> Cardholder Post Code </label>
|
||||
<input class="checkout-form-row-input" type="text" autocomplete="postal-code" name="postal-code" placeholder="e.g AB12 CD3"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="payment-row">
|
||||
<span class="form-item">
|
||||
<label class="checkout-form-row-label"> Expiry Date </label>
|
||||
<input class="checkout-form-row-input" type="text" autocomplete="cc-exp" name="cc-exp" placeholder="MM/YY"/>
|
||||
</span>
|
||||
<span class="form-item">
|
||||
<label class="checkout-form-row-label"> CVV / CSC </label>
|
||||
<input class="checkout-form-row-input" type="text" autocomplete="cc-csc" name="cc-csc" placeholder="CCV"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-place-order">
|
||||
<button class="checkout-place-order-button">Buy £${this.state.total}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkout-body-right">
|
||||
<div class="checkout-summary-title section-title">Your Order <a href="/basket"><span class="edit-basket">edit basket</span><a> </div>
|
||||
<div class="checkout-summary">
|
||||
<immutable-basket-list-component h="300px"></immutable-basket-list-component>
|
||||
<div class="checkout-summary-total">Subtotal ${this.state.total}</div>
|
||||
<div class="checkout-summary-total">Shipping (UK Only) ${this.state.total}</div>
|
||||
<div class="checkout-summary-total">Total ${this.state.total}</div>
|
||||
<input type="text" class="offer-text" placeholder="LEGO10"/><button class="offer-button">Apply Offer Code</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
style: `
|
||||
.checkout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.checkout-form-row-input {
|
||||
border: 1px solid #79747E;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
}
|
||||
`,
|
||||
style: SideLoad('/components/css/checkout.css'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
OnRender() {
|
||||
// card number
|
||||
let lastCardNumber = '';
|
||||
this.root.querySelector('input[name="cc-number"]').addEventListener('keyup', (e) => {
|
||||
if (e.target.value !== lastCardNumber) {
|
||||
// remove non-numeric characters
|
||||
e.target.value = e.target.value.replace(/[^0-9]/g, '');
|
||||
// space every 4 digits
|
||||
e.target.value = e.target.value.replace(/(\d{4})/g, '$1 ');
|
||||
// not longer than 4 sets of 4 numbers
|
||||
e.target.value = e.target.value.trim().substring(0, 19);
|
||||
lastCardNumber = e.target.value;
|
||||
|
||||
// perform validation on card number and determine if valid AND card type
|
||||
// please not this is NOT a checksum
|
||||
let cardType = 'unknown';
|
||||
this.root.querySelector('input[name="cc-number"]').classList.remove('visa', 'mastercard', 'amex', 'unknown');
|
||||
const cardNumber = e.target.value.replace(/\s/g, '');
|
||||
if (cardNumber.match(/^4[0-9]{12}(?:[0-9]{3})?$/)) {
|
||||
cardType = 'visa';
|
||||
} else if (cardNumber.match(/^5[1-5][0-9]{14}$/)) {
|
||||
cardType = 'mastercard';
|
||||
}
|
||||
// update card type
|
||||
this.root.querySelector('input[name="cc-number"]').classList.add(cardType);
|
||||
|
||||
if (e.target.value.length === 19) {
|
||||
this.root.querySelector('input[name="postal-code"]').focus();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.keyCode === 13) {
|
||||
this.root.querySelector('input[name="postal-code"]').focus();
|
||||
}
|
||||
});
|
||||
|
||||
// postal code
|
||||
let lastPostalCode = '';
|
||||
this.root.querySelector('input[name="postal-code"]').addEventListener('keyup', (e) => {
|
||||
if (e.target.value !== lastPostalCode) {
|
||||
// make uppercase
|
||||
e.target.value = e.target.value.toUpperCase();
|
||||
// not longer than 4 sets of 4 numbers
|
||||
lastPostalCode = e.target.value;
|
||||
}
|
||||
|
||||
if (e.keyCode === 13) {
|
||||
this.root.querySelector('input[name="cc-exp"]').focus();
|
||||
}
|
||||
});
|
||||
|
||||
// expiry date
|
||||
let lastExpiryDate = '';
|
||||
this.root.querySelector('input[name="cc-exp"]').addEventListener('keyup', (e) => {
|
||||
if (e.target.value !== lastExpiryDate) {
|
||||
// remove non-numeric characters
|
||||
e.target.value = e.target.value.replace(/[^0-9]/g, '');
|
||||
// space every 2 digits
|
||||
e.target.value = e.target.value.replace(/(\d{2})/g, '$1 / ');
|
||||
// not longer than 2 sets of 2 numbers
|
||||
e.target.value = e.target.value.trim().substring(0, 7);
|
||||
lastExpiryDate = e.target.value;
|
||||
|
||||
if (e.target.value.length === 7) {
|
||||
this.root.querySelector('input[name="cc-csc"]').focus();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.keyCode === 13) {
|
||||
this.root.querySelector('input[name="cc-csc"]').focus();
|
||||
}
|
||||
});
|
||||
|
||||
// cvv
|
||||
let lastCvv = '';
|
||||
this.root.querySelector('input[name="cc-csc"]').addEventListener('keyup', (e) => {
|
||||
if (e.target.value !== lastCvv) {
|
||||
// remove non-numeric characters
|
||||
e.target.value = e.target.value.replace(/[^0-9]/g, '');
|
||||
// not longer than 3 numbers
|
||||
e.target.value = e.target.value.trim().substring(0, 3);
|
||||
lastCvv = e.target.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
113
client/public/components/css/checkout.css
Normal file
113
client/public/components/css/checkout.css
Normal file
@@ -0,0 +1,113 @@
|
||||
.checkout-header {
|
||||
margin-top: 20px;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.checkout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 25px;
|
||||
margin-top: 25px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.checkout-body-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 45%;
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.checkout-body-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 45%;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
@media (pointer:none), (pointer:coarse), screen and (max-width: 900px) {
|
||||
.checkout {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.checkout-body-left {
|
||||
order: 1;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.checkout-body-right {
|
||||
order: 0;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.checkout-form-row-input, .checkout-form-row-label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.checkout-form-row-label {
|
||||
font-size: 0.9em;
|
||||
color: #888;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.checkout-form-row-input {
|
||||
margin-bottom: 10px;
|
||||
height: 3em;
|
||||
background-color: #F5F6F6;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 0.4em;
|
||||
transition: all 250ms ease-in-out;
|
||||
}
|
||||
|
||||
.checkout-form-row-input:focus {
|
||||
outline: 0;
|
||||
border: 2px solid transparent;
|
||||
border-bottom: 2px solid #222;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.checkout-payment-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.payment-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.visa {
|
||||
background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/260969/ic_card_visa.png) no-repeat;
|
||||
background-position: 95%;
|
||||
}
|
||||
|
||||
.mastercard {
|
||||
background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/260969/ic_card_master_card.png) no-repeat;
|
||||
background-position: 95%;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
width: 49%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.edit-basket {
|
||||
font-size: 0.7em;
|
||||
color: #888;
|
||||
float: right;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
116
client/public/components/immutable-basket-list.mjs
Normal file
116
client/public/components/immutable-basket-list.mjs
Normal file
@@ -0,0 +1,116 @@
|
||||
import { RegisterComponent, Component } from './components.mjs';
|
||||
import { GetBasketTotalPrice } from './basket-popout.mjs';
|
||||
import * as LocalStorageListener from '../localstorage-listener.mjs';
|
||||
|
||||
class ImmutableBasketList extends Component {
|
||||
static __IDENTIFY() { return 'immutable-basket-list'; }
|
||||
|
||||
constructor() {
|
||||
super(ImmutableBasketList);
|
||||
}
|
||||
|
||||
async OnLocalBasketUpdate() {
|
||||
const basket = localStorage.getItem('basket');
|
||||
|
||||
if (basket) {
|
||||
try {
|
||||
const basketJSON = JSON.parse(basket);
|
||||
const subtotal = await GetBasketTotalPrice();
|
||||
this.setState({
|
||||
...this.getState,
|
||||
items: basketJSON.items,
|
||||
total: basketJSON.total,
|
||||
subtotal,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
...this.getState,
|
||||
items: {},
|
||||
total: 0,
|
||||
subtotal: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OnMount() {
|
||||
LocalStorageListener.ListenOnKey('basket', () => {
|
||||
this.OnLocalBasketUpdate(Object.bind(this));
|
||||
});
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
items: {},
|
||||
total: 0,
|
||||
subtotal: 0,
|
||||
}, false);
|
||||
|
||||
this.OnLocalBasketUpdate(Object.bind(this));
|
||||
}
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
<div class="popup-content">
|
||||
${this.state.items
|
||||
? Object.keys(this.state.items).map((key) => {
|
||||
const item = this.state.items[key];
|
||||
return /* html */`
|
||||
<div class="popup-content-item">
|
||||
<span class="popup-content-item-quantity">x${item.quantity}</span>
|
||||
<super-compact-listing-component class="sc-listing"
|
||||
id="${key.split('~')[0]}"
|
||||
type="${item.type}"
|
||||
quantity="${item.quantity}"
|
||||
modifier="${key.split('~')[1] || ''}">
|
||||
</super-compact-listing-component>
|
||||
</div>
|
||||
`;
|
||||
}).join('')
|
||||
: ''}
|
||||
</div>
|
||||
`,
|
||||
style: `
|
||||
.popup-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: left;
|
||||
height: ${this.state.w || '100%'};
|
||||
height: ${this.state.h || 'auto'};
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.popup-content-item {
|
||||
background-color: #F5F6F6;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popup-content-item-quantity {
|
||||
font-size: 2em;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.sc-listing {
|
||||
flex-basis: 100%;
|
||||
flex-grow: 3;
|
||||
}
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
||||
OnRender() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RegisterComponent(ImmutableBasketList);
|
||||
@@ -6,7 +6,7 @@ import * as StorageListener from '../localstorage-listener.mjs';
|
||||
// we need to have this remember the state of the logged in user
|
||||
// so that we can display the correct navbar
|
||||
let navbarCallback = null;
|
||||
export function NotifyNavbar(type, user) {
|
||||
export function NotifyNavbar(type) {
|
||||
if (navbarCallback && type === 'login') {
|
||||
navbarCallback.OnLogin();
|
||||
}
|
||||
@@ -98,9 +98,11 @@ class NavBar extends Component {
|
||||
|
||||
// setup log in button
|
||||
const loginButton = this.root.querySelector('.account-button');
|
||||
loginButton.addEventListener('click', () => {
|
||||
LoginSignup(this);
|
||||
});
|
||||
if (loginButton) {
|
||||
loginButton.addEventListener('click', () => {
|
||||
LoginSignup(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<script type="module" src="/components/navbar.mjs"></script>
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-basket-list.mjs"></script>
|
||||
<script type="module" src="/components/accessability-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/tag.mjs"></script>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/basket.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-basket-list.mjs"></script>
|
||||
<script type="module" src="/components/accessability-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/storefront.mjs"></script>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<script type="module" src="/components/navbar.mjs"></script>
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-basket-list.mjs"></script>
|
||||
<script type="module" src="/components/accessability-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/storefront.mjs"></script>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/basket.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-basket-list.mjs"></script>
|
||||
<script type="module" src="/components/accessability-popout.mjs"></script>
|
||||
<script type="module" src="/components/notificationbar.mjs"></script>
|
||||
<script type="module" src="/components/storefront.mjs"></script>
|
||||
|
||||
Reference in New Issue
Block a user