discounts as well as fixing a dumbb fucking bug

Former-commit-id: 4c082340cebde8ff3434734797e08e8046a63764
This commit is contained in:
Ben
2022-04-27 23:23:52 +01:00
parent 676ff3b926
commit 3f2037f1fc
15 changed files with 485 additions and 178 deletions

109
client/public/basket.mjs Normal file
View File

@@ -0,0 +1,109 @@
// Basket is stored locally only and is not persisted to the server.
// It is used to store the current basket and is used to calculate the total price of the basket.
// It is also used to store the current user's basket.
// The structure of the basket is in local storage and is as follows:
// {
// "basket": {
// "items": {
// "item1~modifier": { quantity, type },
// "item2": { quantity, type },
// ...
// },
// "total": total
// },
// }
// TODO: Does the localstorage have a problem with mutual exclusion?
// TODO: Should the basket be persisted to the server?
export function GetBasketItems() {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return;
}
return JSON.parse(localStorage.getItem('basket')).items;
}
export function AddProductToBasket(product, type, amount, brickModifier = 'none') {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
localStorage.setItem('basket', JSON.stringify({
items: {},
total: 0,
}));
}
const basket = JSON.parse(localStorage.getItem('basket'));
if (type === 'brick') {
product += '~' + brickModifier;
}
if (basket.items[product]) {
basket.items[product].quantity += amount;
} else {
basket.items[product] = {
quantity: amount,
type,
};
}
basket.total += amount;
localStorage.setItem('basket', JSON.stringify(basket));
}
export function RemoveProductFromBasket(product, type, amount, brickModifier = 'none') {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return;
}
const basket = JSON.parse(localStorage.getItem('basket'));
if (type === 'brick') {
product += '~' + brickModifier;
}
if (basket.items[product]) {
if (basket.items[product].quantity > amount) {
basket.items[product].quantity -= amount;
} else {
delete basket.items[product];
}
}
basket.total -= amount;
localStorage.setItem('basket', JSON.stringify(basket));
}
export function GetBasketTotal() {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return 0;
}
const basket = JSON.parse(localStorage.getItem('basket'));
return basket.total;
}
export async function GetBasketTotalPrice(discount = 0, type = '£', entity_type = undefined) {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return 0;
}
const basket = JSON.parse(localStorage.getItem('basket'));
const res = await fetch('/api/basket/price', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(basket),
}).then(res => res.json());
if (res.error) {
return 0;
}
return res.data.subtotal;
}
export async function GetAbsoluteBasketDiscount(discount = 0, type = '£', entity_type = undefined) {
}

View File

@@ -1,120 +1,6 @@
import { RegisterComponent, Component } from './components.mjs';
// Basket is stored locally only and is not persisted to the server.
// It is used to store the current basket and is used to calculate the total price of the basket.
// It is also used to store the current user's basket.
// The structure of the basket is in local storage and is as follows:
// {
// "basket": {
// "items": {
// "item1~modifier": { quantity, type },
// "item2": { quantity, type },
// ...
// },
// "total": total
// },
// }
let basketCallback = null;
// TODO: Does the localstorage have a problem with mutual exclusion?
// TODO: Should the basket be persisted to the server?
export function GetBasketItems() {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return;
}
return JSON.parse(localStorage.getItem('basket')).items;
}
export function AddProductToBasket(product, type, amount, brickModifier = 'none') {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
localStorage.setItem('basket', JSON.stringify({
items: {},
total: 0,
}));
}
const basket = JSON.parse(localStorage.getItem('basket'));
if (type === 'brick') {
product += '~' + brickModifier;
}
if (basket.items[product]) {
basket.items[product].quantity += amount;
} else {
basket.items[product] = {
quantity: amount,
type,
};
}
basket.total += amount;
localStorage.setItem('basket', JSON.stringify(basket));
if (basketCallback) {
basketCallback();
}
}
export function RemoveProductFromBasket(product, type, amount, brickModifier = 'none') {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return;
}
const basket = JSON.parse(localStorage.getItem('basket'));
if (type === 'brick') {
product += '~' + brickModifier;
}
if (basket.items[product]) {
if (basket.items[product].quantity > amount) {
basket.items[product].quantity -= amount;
} else {
delete basket.items[product];
}
}
basket.total -= amount;
localStorage.setItem('basket', JSON.stringify(basket));
if (basketCallback) {
basketCallback();
}
}
export function GetBasketTotal() {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return 0;
}
const basket = JSON.parse(localStorage.getItem('basket'));
return basket.total;
}
export async function GetBasketTotalPrice() {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
return 0;
}
const basket = JSON.parse(localStorage.getItem('basket'));
const res = await fetch('/api/basket/price', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(basket),
}).then(res => res.json());
if (res.error) {
return 0;
}
return res.data.subtotal;
}
import * as Basket from '../basket.mjs';
import * as LocalStorageListener from '../localstorage-listener.mjs';
class BasketPopout extends Component {
static __IDENTIFY() { return 'basket-popout'; }
@@ -129,7 +15,7 @@ class BasketPopout extends Component {
if (basket) {
try {
const basketJSON = JSON.parse(basket);
const subtotal = await GetBasketTotalPrice();
const subtotal = await Basket.GetBasketTotalPrice();
this.setState({
items: basketJSON.items,
total: basketJSON.total,
@@ -156,7 +42,7 @@ class BasketPopout extends Component {
this.OnLocalBasketUpdate(Object.bind(this));
basketCallback = this.OnLocalBasketUpdate.bind(this);
LocalStorageListener.ListenOnKey('basket', this.OnLocalBasketUpdate.bind(this));
}
Render() {

View File

@@ -1,5 +1,5 @@
import { GetBasketItems, AddProductToBasket, RemoveProductFromBasket, GetBasketTotal, GetBasketTotalPrice } from './basket-popout.mjs';
import { RegisterComponent, Component } from './components.mjs';
import * as BasketMaster from '../basket.mjs';
class Basket extends Component {
static __IDENTIFY() { return 'basket'; }
@@ -93,7 +93,7 @@ class Basket extends Component {
}
.basket-item-listing {
font-size: 1.3em;
font-size: 1.2em;
flex-basis: 65%;
flex-grow: 4;
}
@@ -209,7 +209,7 @@ class Basket extends Component {
}
async UpdateSubtotal() {
const subtotal = await GetBasketTotalPrice();
const subtotal = await BasketMaster.GetBasketTotalPrice();
const basketSubtotal = this.root.querySelector('.basket-subtotal');
if (basketSubtotal) {
basketSubtotal.innerText = parseFloat(subtotal).toFixed(2);
@@ -272,34 +272,34 @@ class Basket extends Component {
if (event.target.classList.contains('reduce-quantity')) {
if (item.quantity > 0) {
item.quantity--;
RemoveProductFromBasket(id, item.type, 1, modifier);
BasketMaster.RemoveProductFromBasket(id, item.type, 1, modifier);
}
if (item.quantity === 0) {
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
BasketMaster.RemoveProductFromBasket(id, item.type, item.quantity, modifier);
this.UpdateSubtotal();
return this.setState({
...this.state,
total: GetBasketTotal(),
total: BasketMaster.GetBasketTotal(),
items: {
...GetBasketItems(),
...BasketMaster.GetBasketItems(),
},
});
}
} else if (event.target.classList.contains('increase-quantity')) {
if (item.quantity < item.stock) {
item.quantity++;
AddProductToBasket(id, item.type, 1, modifier);
BasketMaster.AddProductToBasket(id, item.type, 1, modifier);
}
} else if (event.target.classList.contains('remove-quantity')) {
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
BasketMaster.RemoveProductFromBasket(id, item.type, item.quantity, modifier);
this.UpdateSubtotal();
return this.setState({
...this.state,
total: GetBasketTotal(),
total: BasketMaster.GetBasketTotal(),
items: {
...GetBasketItems(),
...BasketMaster.GetBasketItems(),
},
});
}
@@ -307,7 +307,7 @@ class Basket extends Component {
// update the total
this.setState({
...this.state,
total: GetBasketTotal(),
total: BasketMaster.GetBasketTotal(),
items: {
...this.state.items,
[compositeId]: item,
@@ -349,23 +349,23 @@ class Basket extends Component {
}
if (parseInt(event.target.value) === 0) {
RemoveProductFromBasket(id, item.type, item.quantity, modifier);
BasketMaster.RemoveProductFromBasket(id, item.type, item.quantity, modifier);
return this.setState({
...this.state,
total: GetBasketTotal(),
total: BasketMaster.GetBasketTotal(),
items: {
...GetBasketItems(),
...BasketMaster.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);
BasketMaster.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);
BasketMaster.RemoveProductFromBasket(id, item.type, item.quantity - event.target.value, modifier);
item.quantity = parseInt(event.target.value);
}
@@ -374,7 +374,7 @@ class Basket extends Component {
// update the total
this.setState({
...this.state,
total: GetBasketTotal(),
total: BasketMaster.GetBasketTotal(),
items: {
...this.state.items,
[compositeId]: item,

View File

@@ -1,4 +1,4 @@
import { GetBasketTotalPrice } from './basket-popout.mjs';
import { GetBasketTotalPrice } from '../basket.mjs';
import { RegisterComponent, Component, SideLoad } from './components.mjs';
class Checkout extends Component {
@@ -10,7 +10,9 @@ class Checkout extends Component {
async OnMount() {
this.setState({
subtotal: parseFloat(await GetBasketTotalPrice()).toFixed(2),
total: parseFloat(await GetBasketTotalPrice()).toFixed(2),
discount: 0,
});
}
@@ -22,10 +24,20 @@ class Checkout extends Component {
</div>
<div class="checkout">
<div class="checkout-body-left">
<div class="checkout-delivery-form-title section-title">Shipping Details</div>
<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"/>
<span class="form-item full-width">
<label class="checkout-form-row-label">Email</label>
<input class="checkout-form-row-input" type="text" autocomplete="email" name="email" placeholder="Email"/>
</span>
<span class="form-item full-width">
<label class="checkout-form-row-label">Address Line 1</label>
<input class="checkout-form-row-input" type="text" autocomplete="address-line1" name="address" placeholder="House Name or Number"/>
</span>
<span class="form-item full-width">
<label class="checkout-form-row-label">Post Code</label>
<input class="checkout-form-row-input" type="text" autocomplete="postal-code" name="postcode" placeholder="e.g AB12 CD3"/>
</span>
</div>
<div class="checkout-delivery-form-title section-title">Payment Details</div>
@@ -43,7 +55,7 @@ class Checkout extends Component {
<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"/>
<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>
@@ -53,17 +65,36 @@ class Checkout extends Component {
</div>
<div class="checkout-place-order">
<button class="checkout-place-order-button">Buy £${this.state.total}</button>
<button class="checkout-place-order-button">Buy £${this.state.subtotal - this.state.discount}</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 class="checkout-summary-prices">
<div class="checkout-summary-prices-row">
<span class="checkout-summary-prices-row-label">Subtotal</span>
<span class="checkout-summary-prices-row-value">£${this.state.subtotal}</span>
</div>
<div class="checkout-summary-prices-row">
<span class="checkout-summary-prices-row-label">Delivery</span>
<span class="checkout-summary-prices-row-value">£0.00</span>
</div>
<div class="checkout-summary-prices-row discount-row" style="display: ${this.state.discount > 0 ? 'flex;' : 'none;'}">
<span class="checkout-summary-prices-row-label">Discount</span>
<span class="checkout-summary-prices-row-value">£-${parseFloat(this.state.discount).toFixed(2)}</span>
</div>
<div class="checkout-summary-prices-row">
<span class="checkout-summary-prices-row-label">Total</span>
<span class="checkout-summary-prices-row-value">£${parseFloat(this.state.subtotal - this.state.discount).toFixed(2)}</span>
</div>
</div>
<div><label class="checkout-form-row-label"> Discount Code </label></div>
<div class="checkout-summary-discount-code">
<input type="text" class="offer-text" name="offer-text" placeholder="LEGO10"/><button class="offer-button">Apply</button>
</div>
</div>
</div>
</div>
@@ -156,6 +187,58 @@ class Checkout extends Component {
lastCvv = e.target.value;
}
});
// discount code
this.root.querySelector('.offer-button').addEventListener('click', async () => {
// get discount code
const offerText = this.root.querySelector('input[name="offer-text"]').value;
const offerTextBox = this.root.querySelector('.offer-text');
// check if valid
if (offerText.length === 0 || offerTextBox.classList.contains('code-applied')) {
// show error
offerTextBox.classList.add('error');
setTimeout(() => {
offerTextBox.classList.remove('error');
}, 1000);
return;
}
// ask server for discount
const req = await fetch(`/api/discount?code=${encodeURIComponent(offerText)}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res.json());
if (req.error) {
// show error
offerTextBox.classList.add('error');
setTimeout(() => {
offerTextBox.classList.remove('error');
}, 1000);
return;
}
offerTextBox.classList.add('code-applied');
offerTextBox.disabled = true;
if (await GetBasketTotalPrice() < req.discount.min_value) {
// show error
offerTextBox.classList.add('error');
setTimeout(() => {
offerTextBox.classList.remove('error');
}, 1000);
return;
}
this.setState({
subtotal: parseFloat(await GetBasketTotalPrice()).toFixed(2),
total: parseFloat(await GetBasketTotalPrice(req.discount, req.type, req.entity_type)).toFixed(2),
discount: await GetAbsoluteBasketDiscount(req.discount, req.type, req.entity_type),
});
});
}
}

View File

@@ -38,14 +38,16 @@
flex-direction: column;
}
/* we want the "your order" on top here */
.checkout-body-left {
width: 100%;
order: 1;
flex-basis: 100%;
}
.checkout-body-right {
width: 100%;
margin-top: 50px;
order: 0;
flex-basis: 100%;
}
}
@@ -104,10 +106,128 @@
align-items: flex-start;
}
.full-width {
width: 100%;
}
.checkout-place-order {
width: 100%;
}
.checkout-place-order-button {
width: 100%;
box-shadow: #222 0px 0px 2px;
background: #222;
color: #fff;
border: none;
padding: 10px;
font-size: 1.2em;
cursor: pointer;
transition: all 250ms ease-in-out;
}
.edit-basket {
font-size: 0.7em;
color: #888;
float: right;
text-transform: uppercase;
}
.checkout-summary-prices {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
margin-top: 20px;
margin-bottom: 20px;
}
.checkout-summary-prices-row {
width: 100%;
margin-top: 10px;
font-size: 1em;
color: #444;
font-weight: lighter;
border-bottom: 1px solid #ccc;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
}
.discount-row {
color: #E55744;
}
.checkout-summary-discount-code {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
margin-top: 10px;
margin-bottom: 10px;
}
.offer-text {
margin-bottom: 10px;
height: 3em;
width: 79%;
background-color: #F5F6F6;
border: 1px solid #ccc;
border-radius: 0.4em;
transition: all 250ms ease-in-out;
}
.offer-text:focus {
outline: 0;
border: 2px solid transparent;
border-bottom: 2px solid #222;
border-radius: 0;
}
.offer-button {
width: 14%;
background: #222;
color: #fff;
border: none;
padding: 10px;
font-size: 1em;
cursor: pointer;
transition: all 250ms ease-in-out;
}
.offer-button:hover {
background-color: #F5F6F6;
outline: 2px solid #222;
color: #222;
}
.error {
color: #E55744;
font-size: 0.8em;
animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
}
@keyframes shake {
10%, 90% {
transform: translate3d(-1px, 0, 0);
}
20%, 80% {
transform: translate3d(2px, 0, 0);
}
30%, 50%, 70% {
transform: translate3d(-4px, 0, 0);
}
40%, 60% {
transform: translate3d(4px, 0, 0);
}
}
.code-applied {
color: #F2CA52;
font-weight: bold;
pointer-events: none;
}

View File

@@ -94,7 +94,7 @@
.product-listing-price-new {
font-weight: bold;
color: red;
color: #E55744;
font-size: 1.5em;
}

View File

@@ -1,5 +1,5 @@
import { RegisterComponent, Component } from './components.mjs';
import { GetBasketTotalPrice } from './basket-popout.mjs';
import { GetBasketTotalPrice } from '../basket.mjs';
import * as LocalStorageListener from '../localstorage-listener.mjs';
class ImmutableBasketList extends Component {
@@ -79,8 +79,10 @@ class ImmutableBasketList extends Component {
flex-direction: column;
flex-wrap: nowrap;
justify-content: left;
height: ${this.state.w || '100%'};
height: ${this.state.h || 'auto'};
height: 100%;
width: 100%;
max-width: ${this.state.w || '100%'};
max-height: ${this.state.h || 'auto'};
overflow-y: scroll;
overflow-x: hidden;
justify-content: space-between;

View File

@@ -1,5 +1,5 @@
import { RegisterComponent, Component, SideLoad } from './components.mjs';
import { AddProductToBasket } from './basket-popout.mjs';
import { AddProductToBasket } from '../basket.mjs';
class ProductListing extends Component {
static __IDENTIFY() { return 'product-listing'; }

View File

@@ -109,8 +109,8 @@ class SuperCompactProductListing extends Component {
}
.product-image {
max-height: 150px;
max-width: 150px;
max-height: 110px;
max-width: 110px;
object-fit: scale-down;
object-position: center;
}

View File

@@ -9,21 +9,22 @@ automatically every request
## Routes
| Type | Route | Queries | Auth? | Notes |
| --- | --- | --- | -- | --- |
| GET | /api/special/ | | no | |
| GET | /api/type/:id | | no | |
| GET | /api/search/ | query (q), page | no | Query endpoint |
| GET | /api/bricks/ | query (q), page | no | Query endpoint |
| GET | /api/sets/ | query (q), page | no | Query endpoint |
| GET | /api/sets/featured | page | no | Query endpoint |
| GET | /api/brick/:id | | no | |
| POST | /api/bulk/brick | array | no | POST due to bulk nature |
| GET | /api/set/:id | | no | |
| GET | /api/cdn/:id | | no | |
| GET | /api/basket/price/ | | no | |
| PUT | /api/auth/login/ | | yes | |
| POST | /api/auth/signup/ | | yes | |
| GET | /api/auth/orders/ | | yes | |
| --- | --- | --- | - | --- |
| GET | /api/special/ | | | |
| GET | /api/search/ | query (q), page | ❌ | Query endpoint |
| GET | /api/bricks/ | query (q), page | | Query endpoint |
| GET | /api/sets/ | query (q), page | | Query endpoint |
| GET | /api/sets/featured | page | ❌ | Query endpoint |
| GET | /api/brick/:id | | ❌ | |
| POST | /api/bulk/brick | array | ❌ | POST due to bulk nature |
| GET | /api/set/:id | | ❌ | |
| GET | /api/cdn/:id | | | |
| GET | /api/basket/price/ | | | |
| GET | /api/discount/ | offer code | | |
| GET | /api/auth/login/ | | ✔️ | |
| POST | /api/auth/order/ | | ✔️❌ | |
| GET | /api/auth/order/:id | | ✔️❌ | |
| GET | /api/auth/orders/ | | ✔️ | |
Query endpoints do not return the full data on a brick/set, they return
a subset for product listing pages
@@ -52,11 +53,11 @@ set: brick to search for (absolute, fuzzy string)
```js
{
error: false
data: {
// defined in the response description for each route
}
// other important data, or metadata for the data can be added here
// other important data, or metadata for the data
// (such as pagination data) can be added here
}
```

View File

@@ -91,6 +91,8 @@ async function SumPrices(bricksArr, quantityArray) {
}
Database.Query('COMMIT TRANSACTION;');
console.log(dbres.rows)
// validate database response
if (dbres.rows.length === 0) {
return {

View File

@@ -57,7 +57,6 @@ function SanatiseQuery(query) {
query = query.trim();
query = query.replace(/[^a-zA-Z0-9,&/\s]/g, '');
query = escape(query);
query = query.toLowerCase();
return query;
}

View File

@@ -0,0 +1,40 @@
const Database = require('../database/database.js');
const Logger = require('../logger.js');
async function GetDiscount(code) {
await Database.Query('BEGIN TRANSACTION;');
const dbres = await Database.Query(`
SELECT discount, discount_type, min_order_value, type
FROM offer_code
WHERE code = $1;
`, [code]).catch(() => {
return {
error: 'Database error',
};
});
if (dbres.error) {
Database.Query('ROLLBACK TRANSACTION;');
Logger.Error(dbres.error);
return dbres.error;
}
// validate database response
if (dbres.rows.length === 0) {
return {
error: 'Discount code not found',
long: 'The discount code you are looking for does not exist',
};
}
return {
discount: dbres.rows[0].discount,
type: dbres.rows[0].discount_type === '0' ? '%' : '£',
min_value: dbres.rows[0].min_order_value,
entity_type: dbres.rows[0].type,
end_date: dbres.rows[0].expiry_date,
};
}
module.exports = {
GetDiscount,
};

View File

@@ -13,6 +13,7 @@ function Init() {
Server.App.get('/api/special/', Helpers.Special);
Server.App.get('/api/search/', Query.Search);
Server.App.get('/api/bricks/', Bricks.Query);
Server.App.get('/api/sets/');
Server.App.get('/api/sets/featured/', Sets.Featured);
@@ -22,12 +23,13 @@ function Init() {
Server.App.get('/api/cdn/:id', CDN.Get);
Server.App.get('/api/auth/login', Auth0.JWTMiddleware, Auth0.Login);
Server.App.post('/api/basket/price/', Helpers.CalculateBasketPrice);
Server.App.get('/api/discount/', Helpers.DiscountCode);
Server.App.get('/api/auth/login/', Auth0.JWTMiddleware, Auth0.Login);
Server.App.get('/api/auth/orders/');
Server.App.get('/api/auth/order/:id');
Server.App.post('/api/basket/price', Helpers.CalculateBasketPrice);
Logger.Module('API', 'API Routes Initialized');
}

View File

@@ -1,7 +1,11 @@
const ControllerMaster = require('../controllers/controller-master.js');
const BrickController = require('../controllers/brick-controller.js');
const SetController = require('../controllers/set-controller.js');
const MiscController = require('../controllers/misc-controller.js');
const Logger = require('../logger.js');
const Delay = (ms) => new Promise((r) => setTimeout(r, ms));
const EndDate = new Date('2022-06-10T00:00:00.000Z');
function Special(req, res) {
@@ -39,11 +43,26 @@ async function CalculateBasketPrice(req, res) {
}
}
// combine bricks by id and quantity into a single array
// this annoyingly happens as the brick ids are not unique
// when it comes to composite IDs based on modifiers.
// As modifiers do not change the price of a brick this is fine.
const newBrickList = [];
const newBrickQuantities = [];
for (let i = 0; i < brickList.length; i++) {
if (!newBrickList.includes(brickList[i])) {
newBrickList[i] = brickList[i];
newBrickQuantities[i] = brickQuantities[i];
} else {
newBrickQuantities[newBrickList.indexOf(brickList[i])] += brickQuantities[i];
}
}
let setSubtotal = setList.length > 0
? await SetController.SumPrices(setList, setQuantities)
: 0;
let brickSubtotal = brickList.length > 0
? await BrickController.SumPrices(brickList, brickQuantities)
? await BrickController.SumPrices(newBrickList, newBrickQuantities)
: 0;
if (setSubtotal.error) setSubtotal = 0;
@@ -59,7 +78,51 @@ async function CalculateBasketPrice(req, res) {
}
async function DiscountCode(req, res) {
// // artificial delay to simulate a lots of maths
// await Delay(1000);
if (!req.query.code) {
res.send({
error: 'No code provided',
});
return;
}
const sanatisedCode = ControllerMaster.SanatiseQuery(req.query.code);
const discount = await MiscController.GetDiscount(sanatisedCode);
Logger.Debug(JSON.stringify(discount));
if (discount.error) {
res.send({
error: discount.error,
});
return;
}
if (discount.end_date < new Date()) {
res.send({
error: 'Discount code expired',
});
return;
}
res.send({
data: {
discount: discount.discount,
type: discount.type,
min_value: discount.min_value,
entity_type: discount.entity_type,
end_date: discount.end,
},
});
}
module.exports = {
Special,
CalculateBasketPrice,
DiscountCode,
};