394 lines
16 KiB
JavaScript
394 lines
16 KiB
JavaScript
import { GetBasketItems, AddProductToBasket, RemoveProductFromBasket, GetBasketTotal, GetBasketTotalPrice } from './basket-popout.mjs';
|
|
import { RegisterComponent, Component } from './components.mjs';
|
|
|
|
class Basket extends Component {
|
|
static __IDENTIFY() { return 'basket'; }
|
|
|
|
constructor() {
|
|
super(Basket);
|
|
}
|
|
|
|
OnMount() {
|
|
const basket = localStorage.getItem('basket');
|
|
if (basket) {
|
|
const basketItems = JSON.parse(basket);
|
|
this.setState({
|
|
...basketItems,
|
|
}, false);
|
|
} else {
|
|
this.setState({
|
|
items: {},
|
|
total: 0,
|
|
}, false);
|
|
}
|
|
}
|
|
|
|
Render() {
|
|
return {
|
|
template: /* html */`
|
|
<div class="basket">
|
|
<div class="basket-header">
|
|
<div class="basket-header-title">
|
|
Your Basket
|
|
</div>
|
|
<div class="basket-header-total">
|
|
Total: <span class="basket-total">{this.state.total}</span> items
|
|
</div>
|
|
</div>
|
|
<div class="basket-items">
|
|
<div class="basket-items-list">
|
|
${Object.keys(this.state.items).map((key) => {
|
|
const item = this.state.items[key];
|
|
const modifier = key.includes('~');
|
|
return /* html */`
|
|
<div class="basket-item">
|
|
<super-compact-listing-component class="basket-item-listing"
|
|
id="${key.split('~')[0]}"
|
|
type="${item.type}"
|
|
bigimage="true"
|
|
${modifier ? `modifier="${key.split('~')[1]}"` : ''}>
|
|
</super-compact-listing-component>
|
|
<div class="basket-item-content">
|
|
<div class="product-quantity-selector">
|
|
<button class="product-quantity-button reduce-quantity" type="button">-</button>
|
|
<input class="quantity-input" type="number" value="${item.quantity}" min="0" max="{item.stock}">
|
|
<button class="product-quantity-button increase-quantity" type="button">+</button>
|
|
<span class="product-quantity"> <span class="stock-number">0</span> in stock</span>
|
|
</div>
|
|
<button class="product-quantity-button remove-quantity" type="button">Remove</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('')}
|
|
</div>
|
|
<div class="basket-footer">
|
|
<!-- subtotal -->
|
|
<div class="basket-footer-subtotal">
|
|
Subtotal: £<span class="basket-subtotal">0.00</span>
|
|
</div>
|
|
<!-- checkout button -->
|
|
<div class="basket-footer-checkout">
|
|
<a href="/checkout"><button class="basket-footer-button checkout-button" type="button">Checkout as ${localStorage.user}</button></a>
|
|
</div>
|
|
</div>
|
|
`,
|
|
style: `
|
|
.basket {
|
|
padding-top: 10px;
|
|
}
|
|
|
|
.basket-header {
|
|
display: flex;
|
|
font-size: 2em;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 10px;
|
|
border-bottom: 1px solid #ccc;
|
|
}
|
|
|
|
.basket-item {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
.basket-item-listing {
|
|
font-size: 1.3em;
|
|
flex-basis: 65%;
|
|
flex-grow: 4;
|
|
}
|
|
|
|
@media (pointer:none), (pointer:coarse), screen and (max-width: 900px) {
|
|
.basket-item {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.basket-item-listing {
|
|
font-size: 1.3em;
|
|
width: 100%;
|
|
flex-basis: 100%;
|
|
flex-grow: 4;
|
|
}
|
|
}
|
|
|
|
.basket-item-content {
|
|
flex-basis: 35%;
|
|
flex-grow: 1;
|
|
width: 100%;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-around;
|
|
align-items: center;
|
|
}
|
|
|
|
.product-quantity-selector {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: center;
|
|
align-items: center;
|
|
font-size: 1em;
|
|
}
|
|
|
|
.product-quantity-button {
|
|
cursor: pointer;
|
|
font-size: 1.7em;
|
|
border: #1A1A1A solid 1px;
|
|
background-color: #F5F6F6;
|
|
border-radius: 0.2em;
|
|
width: 1.5em;
|
|
height: 1.5em;
|
|
}
|
|
|
|
.remove-quantity {
|
|
font-size: 1em;
|
|
width: fit-content;
|
|
}
|
|
|
|
/* https://www.w3schools.com/howto/howto_css_hide_arrow_number.asp */
|
|
input::-webkit-outer-spin-button,
|
|
input::-webkit-inner-spin-button {
|
|
-webkit-appearance: none;
|
|
margin: 0;
|
|
}
|
|
input[type=number] {
|
|
-moz-appearance: textfield;
|
|
}
|
|
|
|
.quantity-input {
|
|
height: 2.2em;
|
|
width: 3.7em;
|
|
background-color: #F5F6F6;
|
|
border-top: #1A1A1A solid 1px;
|
|
border-bottom: #1A1A1A solid 1px;
|
|
border-right: none;
|
|
border-left: none;
|
|
text-align: center;
|
|
font-size: 1.2em;
|
|
}
|
|
|
|
.basket-footer {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 10px;
|
|
border-top: 1px solid #ccc;
|
|
}
|
|
|
|
.basket-footer-subtotal {
|
|
font-size: 1.5em;
|
|
}
|
|
|
|
.basket-footer-checkout {
|
|
flex-basis: 70%;
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: flex-end;
|
|
align-items: center;
|
|
}
|
|
|
|
.basket-footer-button {
|
|
cursor: pointer;
|
|
background-color: #222;
|
|
border: none;
|
|
color: #F5F6F6;
|
|
padding: 0.5em;
|
|
margin-left: 1em;
|
|
font-size: 1.4em;
|
|
transition: all 0.2s ease-in;
|
|
}
|
|
|
|
.basket-footer-button:hover {
|
|
background-color: #F5F6F6;
|
|
outline: 2px solid #222;
|
|
color: #222;
|
|
}
|
|
`,
|
|
};
|
|
}
|
|
|
|
async UpdateSubtotal() {
|
|
const subtotal = await GetBasketTotalPrice();
|
|
const basketSubtotal = this.root.querySelector('.basket-subtotal');
|
|
if (basketSubtotal) {
|
|
basketSubtotal.innerText = parseFloat(subtotal).toFixed(2);
|
|
}
|
|
}
|
|
|
|
OnRender() {
|
|
this.UpdateSubtotal();
|
|
this.root.querySelectorAll('.basket-item-listing').forEach((listing) => {
|
|
// listen to mutations on the attribute stock because the stock is updated once the
|
|
// super compact listing is loaded and the stock is updated
|
|
const observer = new MutationObserver((mutations) => {
|
|
mutations.forEach((mutation) => {
|
|
if (mutation.attributeName === 'stock') {
|
|
const stock = parseInt(mutation.target.getAttribute('stock'));
|
|
|
|
const itemCompositeId = mutation.target.getAttribute('id') + (mutation.target.getAttribute('modifier') ? '~' + mutation.target.getAttribute('modifier') : '');
|
|
const item = this.state.items[itemCompositeId];
|
|
if (item) {
|
|
item.stock = stock;
|
|
this.setState({
|
|
items: {
|
|
...this.state.items,
|
|
[itemCompositeId]: item,
|
|
},
|
|
...this.state,
|
|
}, false);
|
|
}
|
|
// update the stock number
|
|
const stockNumber = mutation.target.parentElement.querySelector('.stock-number');
|
|
stockNumber.innerText = stock;
|
|
const stockMax = mutation.target.parentElement.querySelector('.quantity-input');
|
|
stockMax.setAttribute('max', stock);
|
|
}
|
|
});
|
|
});
|
|
observer.observe(listing, {
|
|
attributes: true,
|
|
attributeFilter: ['stock'],
|
|
});
|
|
});
|
|
|
|
|
|
// set up each button to update the quantity and remove if it is zero
|
|
this.root.querySelectorAll('.product-quantity-button').forEach((button) => {
|
|
button.addEventListener('click', (event) => {
|
|
let clickedItem = event.target.parentElement;
|
|
let listing = clickedItem.querySelector('.basket-item-listing');
|
|
if (!listing) {
|
|
clickedItem = clickedItem.parentElement;
|
|
listing = clickedItem.querySelector('.basket-item-listing');
|
|
}
|
|
|
|
const id = listing.getAttribute('id');
|
|
const modifier = listing.getAttribute('modifier');
|
|
const compositeId = id + (modifier ? `~${modifier}` : '');
|
|
const item = this.state.items[compositeId];
|
|
|
|
// update the quantity
|
|
if (event.target.classList.contains('reduce-quantity')) {
|
|
if (item.quantity > 0) {
|
|
item.quantity--;
|
|
RemoveProductFromBasket(id, item.type, 1, modifier);
|
|
}
|
|
if (item.quantity === 0) {
|
|
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
|
|
this.UpdateSubtotal();
|
|
|
|
return this.setState({
|
|
...this.state,
|
|
total: GetBasketTotal(),
|
|
items: {
|
|
...GetBasketItems(),
|
|
},
|
|
});
|
|
}
|
|
} else if (event.target.classList.contains('increase-quantity')) {
|
|
if (item.quantity < item.stock) {
|
|
item.quantity++;
|
|
AddProductToBasket(id, item.type, 1, modifier);
|
|
}
|
|
} else if (event.target.classList.contains('remove-quantity')) {
|
|
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
|
|
this.UpdateSubtotal();
|
|
|
|
return this.setState({
|
|
...this.state,
|
|
total: GetBasketTotal(),
|
|
items: {
|
|
...GetBasketItems(),
|
|
},
|
|
});
|
|
}
|
|
|
|
// update the total
|
|
this.setState({
|
|
...this.state,
|
|
total: GetBasketTotal(),
|
|
items: {
|
|
...this.state.items,
|
|
[compositeId]: item,
|
|
},
|
|
}, false);
|
|
|
|
// update the total so it doesn't need to be rendered again
|
|
const total = this.root.querySelector('.basket-total');
|
|
total.innerText = this.state.total;
|
|
// and the same on the item
|
|
const itemTotal = clickedItem.querySelector('.quantity-input');
|
|
itemTotal.value = parseInt(item.quantity);
|
|
this.UpdateSubtotal();
|
|
});
|
|
});
|
|
|
|
|
|
// do the same but with the input field
|
|
this.root.querySelectorAll('.quantity-input').forEach((input) => {
|
|
input.addEventListener('change', (event) => {
|
|
let clickedItem = event.target.parentElement;
|
|
let listing = clickedItem.querySelector('.basket-item-listing');
|
|
if (!listing) {
|
|
clickedItem = clickedItem.parentElement;
|
|
listing = clickedItem.querySelector('.basket-item-listing');
|
|
}
|
|
|
|
const id = listing.getAttribute('id');
|
|
const modifier = listing.getAttribute('modifier');
|
|
const compositeId = id + (modifier ? `~${modifier}` : '');
|
|
const item = this.state.items[compositeId];
|
|
|
|
// update the quantity
|
|
if (event.target.value < 0) {
|
|
event.target.value = 0;
|
|
}
|
|
if (event.target.value > item.stock) {
|
|
event.target.value = item.stock;
|
|
}
|
|
|
|
if (parseInt(event.target.value) === 0) {
|
|
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
|
|
|
|
return this.setState({
|
|
...this.state,
|
|
total: GetBasketTotal(),
|
|
items: {
|
|
...GetBasketItems(),
|
|
},
|
|
});
|
|
}
|
|
|
|
// if has gone up in quantity then add it
|
|
if (item.quantity < event.target.value) {
|
|
AddProductToBasket(id, item.type, event.target.value - item.quantity, modifier);
|
|
item.quantity = parseInt(event.target.value);
|
|
} else if (item.quantity > event.target.value) {
|
|
RemoveProductFromBasket(id, item.type, item.quantity - event.target.value, modifier);
|
|
item.quantity = parseInt(event.target.value);
|
|
}
|
|
|
|
this.UpdateSubtotal();
|
|
|
|
// update the total
|
|
this.setState({
|
|
...this.state,
|
|
total: GetBasketTotal(),
|
|
items: {
|
|
...this.state.items,
|
|
[compositeId]: item,
|
|
},
|
|
}, false);
|
|
|
|
|
|
// update the total so it doesn't need to be rendered again
|
|
const total = this.root.querySelector('.basket-total');
|
|
total.innerText = this.state.total;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
RegisterComponent(Basket);
|