From 3f2037f1fca7d24c30d89de118926ecfff43f57c Mon Sep 17 00:00:00 2001
From: Ben <36240171+benkyd@users.noreply.github.com>
Date: Wed, 27 Apr 2022 23:23:52 +0100
Subject: [PATCH] discounts as well as fixing a dumbb fucking bug
Former-commit-id: 4c082340cebde8ff3434734797e08e8046a63764
---
client/public/basket.mjs | 109 +++++++++++++++
client/public/components/basket-popout.mjs | 122 +----------------
client/public/components/basket.mjs | 36 ++---
client/public/components/checkout.mjs | 103 ++++++++++++--
client/public/components/css/checkout.css | 126 +++++++++++++++++-
.../public/components/css/product-listing.css | 2 +-
.../components/immutable-basket-list.mjs | 8 +-
client/public/components/product-listing.mjs | 2 +-
.../components/super-compact-listing.mjs | 4 +-
docs/API.md | 35 ++---
src/controllers/brick-controller.js | 2 +
src/controllers/controller-master.js | 1 -
src/controllers/misc-controller.js | 40 ++++++
src/routes/api.js | 8 +-
src/routes/helpers.js | 65 ++++++++-
15 files changed, 485 insertions(+), 178 deletions(-)
create mode 100644 client/public/basket.mjs
create mode 100644 src/controllers/misc-controller.js
diff --git a/client/public/basket.mjs b/client/public/basket.mjs
new file mode 100644
index 0000000..fe80d8f
--- /dev/null
+++ b/client/public/basket.mjs
@@ -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) {
+
+}
diff --git a/client/public/components/basket-popout.mjs b/client/public/components/basket-popout.mjs
index bd7fbcf..c17c69b 100644
--- a/client/public/components/basket-popout.mjs
+++ b/client/public/components/basket-popout.mjs
@@ -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() {
diff --git a/client/public/components/basket.mjs b/client/public/components/basket.mjs
index c093272..cec5d10 100644
--- a/client/public/components/basket.mjs
+++ b/client/public/components/basket.mjs
@@ -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,
diff --git a/client/public/components/checkout.mjs b/client/public/components/checkout.mjs
index 24433be..da0ad4d 100644
--- a/client/public/components/checkout.mjs
+++ b/client/public/components/checkout.mjs
@@ -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 {
+
Shipping Details
Payment Details
@@ -43,7 +55,7 @@ class Checkout extends Component {
-
+
@@ -53,17 +65,36 @@ class Checkout extends Component {
-
+
-
Subtotal ${this.state.total}
-
Shipping (UK Only) ${this.state.total}
-
Total ${this.state.total}
-
+
+
+
+ Subtotal
+ £${this.state.subtotal}
+
+
+ Delivery
+ £0.00
+
+
+ Discount
+ £-${parseFloat(this.state.discount).toFixed(2)}
+
+
+ Total
+ £${parseFloat(this.state.subtotal - this.state.discount).toFixed(2)}
+
+
+
+
+
+
@@ -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),
+ });
+ });
}
}
diff --git a/client/public/components/css/checkout.css b/client/public/components/css/checkout.css
index db196a7..e803b4f 100644
--- a/client/public/components/css/checkout.css
+++ b/client/public/components/css/checkout.css
@@ -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;
}
-
\ No newline at end of file
+
+.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;
+}
diff --git a/client/public/components/css/product-listing.css b/client/public/components/css/product-listing.css
index 62a3d5b..ca30972 100644
--- a/client/public/components/css/product-listing.css
+++ b/client/public/components/css/product-listing.css
@@ -94,7 +94,7 @@
.product-listing-price-new {
font-weight: bold;
- color: red;
+ color: #E55744;
font-size: 1.5em;
}
diff --git a/client/public/components/immutable-basket-list.mjs b/client/public/components/immutable-basket-list.mjs
index 216e8cb..33d84fb 100644
--- a/client/public/components/immutable-basket-list.mjs
+++ b/client/public/components/immutable-basket-list.mjs
@@ -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;
diff --git a/client/public/components/product-listing.mjs b/client/public/components/product-listing.mjs
index 5b8e46d..60e8da6 100644
--- a/client/public/components/product-listing.mjs
+++ b/client/public/components/product-listing.mjs
@@ -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'; }
diff --git a/client/public/components/super-compact-listing.mjs b/client/public/components/super-compact-listing.mjs
index cf5b5fb..76fe783 100644
--- a/client/public/components/super-compact-listing.mjs
+++ b/client/public/components/super-compact-listing.mjs
@@ -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;
}
diff --git a/docs/API.md b/docs/API.md
index fde99e6..781d65b 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -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
}
```
diff --git a/src/controllers/brick-controller.js b/src/controllers/brick-controller.js
index 4b788fb..316d486 100644
--- a/src/controllers/brick-controller.js
+++ b/src/controllers/brick-controller.js
@@ -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 {
diff --git a/src/controllers/controller-master.js b/src/controllers/controller-master.js
index 36515dc..6e8b10d 100644
--- a/src/controllers/controller-master.js
+++ b/src/controllers/controller-master.js
@@ -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;
}
diff --git a/src/controllers/misc-controller.js b/src/controllers/misc-controller.js
new file mode 100644
index 0000000..ba73e31
--- /dev/null
+++ b/src/controllers/misc-controller.js
@@ -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,
+};
diff --git a/src/routes/api.js b/src/routes/api.js
index 512865e..01089f5 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -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');
}
diff --git a/src/routes/helpers.js b/src/routes/helpers.js
index a2e65fd..d95488d 100644
--- a/src/routes/helpers.js
+++ b/src/routes/helpers.js
@@ -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,
};