orders
Former-commit-id: df0cca4571f50a90d7da755bab48e06c4051bce7
This commit is contained in:
@@ -7,11 +7,13 @@ const AUTH0CONFIG = {
|
||||
};
|
||||
|
||||
let auth0 = null;
|
||||
let ready = false;
|
||||
|
||||
async function CheckRedirect() {
|
||||
const isAuthenticated = await auth0.isAuthenticated();
|
||||
if (isAuthenticated) {
|
||||
localStorage.setItem('loggedIn', true);
|
||||
ready = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -23,6 +25,7 @@ async function CheckRedirect() {
|
||||
} catch (e) {
|
||||
window.alert(e.message || 'authentication error, sorry');
|
||||
localStorage.setItem('loggedIn', false);
|
||||
ready = false;
|
||||
Signout();
|
||||
}
|
||||
|
||||
@@ -32,9 +35,7 @@ async function CheckRedirect() {
|
||||
}
|
||||
|
||||
export async function InitAuth0() {
|
||||
// localStorage.setItem('loggedIn', false);
|
||||
// localStorage.setItem('user', 'Guest');
|
||||
// localStorage.setItem('admin', false);
|
||||
ready = false;
|
||||
|
||||
auth0 = await window.createAuth0Client({
|
||||
domain: AUTH0CONFIG.domain,
|
||||
@@ -50,6 +51,7 @@ export async function InitAuth0() {
|
||||
localStorage.setItem('user', user.given_name || user.nickname);
|
||||
NotifyNavbar('login', user);
|
||||
localStorage.setItem('loggedIn', true);
|
||||
ready = true;
|
||||
|
||||
// tell the server about the logon, so that it can make the proper
|
||||
// entry in the database, if there is for example an address
|
||||
@@ -69,6 +71,11 @@ export async function InitAuth0() {
|
||||
}
|
||||
|
||||
export async function GetToken() {
|
||||
/* eslint-disable-next-line */
|
||||
while (!ready) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
const token = await auth0.getTokenSilently();
|
||||
return token;
|
||||
}
|
||||
@@ -90,6 +97,7 @@ export async function LoginSignup() {
|
||||
|
||||
export async function Signout() {
|
||||
localStorage.setItem('loggedIn', false);
|
||||
ready = false;
|
||||
localStorage.setItem('user', 'Guest');
|
||||
localStorage.setItem('admin', false);
|
||||
await auth0.logout({
|
||||
|
||||
@@ -20,7 +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/immutable-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,7 +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/immutable-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>
|
||||
|
||||
@@ -66,7 +66,7 @@ class BasketPopout extends Component {
|
||||
{this.state.total} Items
|
||||
</div>
|
||||
<div class="popup-content">
|
||||
<immutable-basket-list-component h="400px" class="basket-list"></immutable-basket-list-component>
|
||||
<immutable-list-component source="basket" h="400px" class="basket-list"></immutable-list-component>
|
||||
</div>
|
||||
<div class="popup-footer">
|
||||
<span class="popup-footer-total">Subtotal: £${parseFloat(this.state.subtotal).toFixed(2)}</span>
|
||||
|
||||
@@ -79,7 +79,7 @@ class Checkout extends Component {
|
||||
<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>
|
||||
<immutable-list-component source="basket" h="300px"></immutable-list-component>
|
||||
|
||||
<div class="checkout-summary-prices">
|
||||
<div class="checkout-summary-prices-row">
|
||||
@@ -363,10 +363,10 @@ class Checkout extends Component {
|
||||
}
|
||||
|
||||
// clear basket
|
||||
await Basket.ClearBasket();
|
||||
Basket.ClearBasket();
|
||||
|
||||
// redirect to receipt
|
||||
window.location.href = `/order/${req.data.receipt_id}`;
|
||||
window.location.href = `/orders/order?id=${req.data.receipt_id}`;
|
||||
// we're done !
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ export class Component extends HTMLElement {
|
||||
Update() { }
|
||||
Render() { Component.__WARN('Render'); }
|
||||
OnRender() { }
|
||||
OnUnMount() { }
|
||||
static __IDENTIFY() { Component.__WARN('identify'); }
|
||||
|
||||
async connectedCallback() {
|
||||
@@ -60,6 +61,7 @@ export class Component extends HTMLElement {
|
||||
|
||||
disconnectedCallback() {
|
||||
this.root.innerHTML = '';
|
||||
this.OnUnMount();
|
||||
}
|
||||
|
||||
watchAttributeChange(callback) {
|
||||
|
||||
233
client/public/components/css/order.css
Normal file
233
client/public/components/css/order.css
Normal file
@@ -0,0 +1,233 @@
|
||||
.order-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 20px;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.monospace {
|
||||
font-family: monospace;
|
||||
background-color: rgb(209, 209, 209);
|
||||
font-size: 0.9em;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.order-header-subtitle {
|
||||
font-size: 0.6em;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.order-body {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.order-body-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.order-breakdown-table {
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.order-breakdown-table-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 30%;
|
||||
height: 7em;
|
||||
border: #ccc 1px solid;
|
||||
/* background-color: #FAFBFB; */
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 0 5px rgba(0,0,0,0.75);
|
||||
clip-path: inset(0px -5px 0px 0px);
|
||||
}
|
||||
|
||||
.order-breakdown-table-header {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.order-breakdown-table-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.order-breakdown-table-row-value {
|
||||
font-size: 1em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.order-price-table-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;
|
||||
}
|
||||
|
||||
@media (pointer:none), (pointer:coarse), screen and (max-width: 900px) {
|
||||
.order-breakdown-table {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.order-breakdown-table-item {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* loosely based on https://codepen.io/luisar/pen/JjoOZav */
|
||||
|
||||
.order-body-item-table {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.order-body-item-left {
|
||||
width: 49%;
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.order-body-item-right {
|
||||
width: 49%;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.order-body-header {
|
||||
margin-top: 20px;
|
||||
font-size: 1.3em;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.order-track-step {
|
||||
display: flex;
|
||||
height: 7rem;
|
||||
}
|
||||
|
||||
.order-track-step:last-child {
|
||||
overflow: hidden;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.order-track-step:last-child .order-track-status div:last-of-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.order-track-status {
|
||||
margin-right: 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.order-track-step-icon {
|
||||
display: block;
|
||||
width: 1.8em;
|
||||
height: 1.8em;
|
||||
border-radius: 50%;
|
||||
background: #F2CA52;
|
||||
}
|
||||
|
||||
.order-track-step-line {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 4px;
|
||||
height: 7rem;
|
||||
background: #F2CA52;
|
||||
}
|
||||
|
||||
.completed {
|
||||
background: #0EAD69;
|
||||
}
|
||||
|
||||
.order-body-status-title {
|
||||
transform: translateY(-0.6em);
|
||||
font-size: 1.2em;
|
||||
font-weight: 500;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.when {
|
||||
font-size: 1em;
|
||||
color: #444;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
.order-track {
|
||||
margin-top: 20px;
|
||||
transition: all .3s height 0.3s;
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.orders-list-body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.orders-list-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
order: 0;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.order-list-item-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.order-list-item-header-subtitle {
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
.order-list-item-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
order: 0;
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
import { RegisterComponent, Component } from './components.mjs';
|
||||
import { GetBasketTotalPrice } from '../basket.mjs';
|
||||
import * as LocalStorageListener from '../localstorage-listener.mjs';
|
||||
|
||||
class ImmutableBasketList extends Component {
|
||||
static __IDENTIFY() { return 'immutable-basket-list'; }
|
||||
// This was changed to be generic from the original: ImmutableBasketList
|
||||
// so it acts now on a local storage "source" instead of just basket
|
||||
// works i guess /shrug
|
||||
class ImmutableList extends Component {
|
||||
static __IDENTIFY() { return 'immutable-list'; }
|
||||
|
||||
constructor() {
|
||||
super(ImmutableBasketList);
|
||||
super(ImmutableList);
|
||||
}
|
||||
|
||||
async OnLocalBasketUpdate() {
|
||||
const basket = localStorage.getItem('basket');
|
||||
OnLocalStorageListener() {
|
||||
const itemsList = localStorage.getItem(this.state.source);
|
||||
|
||||
if (basket) {
|
||||
if (itemsList) {
|
||||
try {
|
||||
const basketJSON = JSON.parse(basket);
|
||||
const subtotal = await GetBasketTotalPrice();
|
||||
const itemsJson = JSON.parse(itemsList);
|
||||
this.setState({
|
||||
...this.getState,
|
||||
items: basketJSON.items,
|
||||
total: basketJSON.total,
|
||||
subtotal,
|
||||
items: itemsJson.items,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
@@ -29,26 +28,17 @@ class ImmutableBasketList extends Component {
|
||||
this.setState({
|
||||
...this.getState,
|
||||
items: {},
|
||||
total: 0,
|
||||
subtotal: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OnMount() {
|
||||
LocalStorageListener.ListenOnKey('basket', () => {
|
||||
this.OnLocalBasketUpdate(Object.bind(this));
|
||||
LocalStorageListener.ListenOnKey(this.state.source, () => {
|
||||
this.OnLocalStorageListener(Object.bind(this));
|
||||
});
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
items: {},
|
||||
total: 0,
|
||||
subtotal: 0,
|
||||
}, false);
|
||||
|
||||
this.OnLocalBasketUpdate(Object.bind(this));
|
||||
this.OnLocalStorageListener(Object.bind(this));
|
||||
}
|
||||
|
||||
Render() {
|
||||
@@ -115,4 +105,4 @@ class ImmutableBasketList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
RegisterComponent(ImmutableBasketList);
|
||||
RegisterComponent(ImmutableList);
|
||||
@@ -46,22 +46,29 @@ class NavBar extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
if (localStorage.admin === 'true' || localStorage.admin === true) {
|
||||
this.root.querySelector('.stock-mode').style.display = 'flex';
|
||||
} else {
|
||||
this.root.querySelector('.stock-mode').style.display = 'none';
|
||||
}
|
||||
|
||||
const account = this.root.querySelector('.account-item');
|
||||
|
||||
// doing this with proper dom manipulation wasn't working
|
||||
account.innerHTML = `
|
||||
<a class="nav-link" href="#">${localStorage.user}▾</a>
|
||||
<ul class="sub-nav" >
|
||||
<li><a class="sub-nav-link" href="#">My Orders</a></li>
|
||||
<li><a class="sub-nav-link logout-button" href="#">Log Out</a></li>
|
||||
</ul>
|
||||
`;
|
||||
if (localStorage.admin === 'true' || localStorage.admin === true) {
|
||||
this.root.querySelector('.stock-mode').style.display = 'flex';
|
||||
account.innerHTML = `
|
||||
<a class="nav-link" href="#">${localStorage.user}▾</a>
|
||||
<ul class="sub-nav" >
|
||||
<li><a class="sub-nav-link" href="/orders">My Orders</a></li>
|
||||
<li><a class="sub-nav-link" href="">Add or Remove Stock</a></li>
|
||||
<li><a class="sub-nav-link" href="">Review Open Orders</a></li>
|
||||
<li><a class="sub-nav-link logout-button" href="#">Log Out</a></li>
|
||||
</ul>
|
||||
`;
|
||||
} else {
|
||||
this.root.querySelector('.stock-mode').style.display = 'none';
|
||||
account.innerHTML = `
|
||||
<a class="nav-link" href="#">${localStorage.user}▾</a>
|
||||
<ul class="sub-nav" >
|
||||
<li><a class="sub-nav-link" href="/orders">My Orders</a></li>
|
||||
<li><a class="sub-nav-link logout-button" href="#">Log Out</a></li>
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
const logoutButton = account.querySelector('.logout-button');
|
||||
logoutButton.addEventListener('click', () => {
|
||||
|
||||
60
client/public/components/order-list.mjs
Normal file
60
client/public/components/order-list.mjs
Normal file
@@ -0,0 +1,60 @@
|
||||
import { RegisterComponent, Component, SideLoad } from './components.mjs';
|
||||
import * as Auth from '../auth.mjs';
|
||||
|
||||
class OrderList extends Component {
|
||||
static __IDENTIFY() { return 'order-list'; }
|
||||
|
||||
constructor() {
|
||||
super(OrderList);
|
||||
}
|
||||
|
||||
async OnMount() {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${await Auth.GetToken()}` },
|
||||
};
|
||||
|
||||
const res = await fetch('/api/auth/orders', options).then(res => res.json());
|
||||
|
||||
console.log(res);
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
orders: res.data,
|
||||
}, false);
|
||||
console.log(this.state);
|
||||
}
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
<div class="order-header">
|
||||
<span class="order-header-title">Your Orders</span>
|
||||
</div>
|
||||
|
||||
<div class="orders-list-body">
|
||||
${this.state.orders.map(order => /* html */`
|
||||
<div class="orders-list-item">
|
||||
<a href="/orders/order?id=${order.id}"><div class="order-list-item">
|
||||
<div class="order-list-item-header">
|
||||
<span class="order-list-item-header-title">Order #${order.id}</span>
|
||||
<span class="order-list-item-header-subtitle">Placed on ${new Date(order.date_placed).toDateString()}</span>
|
||||
</div>
|
||||
<div class="order-list-item-body">
|
||||
<span class="order-list-item-body-item-title">Paid: £${parseFloat(order.subtotal_paid).toFixed(2)}</span>
|
||||
<span class="order-list-item-body-item-title">Shipped? ${order.shipped ? 'Yes' : 'No'}</span>
|
||||
</div>
|
||||
</div></a>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`,
|
||||
style: SideLoad('/components/css/order.css'),
|
||||
};
|
||||
}
|
||||
|
||||
OnRender() {
|
||||
}
|
||||
}
|
||||
|
||||
RegisterComponent(OrderList);
|
||||
129
client/public/components/order.mjs
Normal file
129
client/public/components/order.mjs
Normal file
@@ -0,0 +1,129 @@
|
||||
import { RegisterComponent, Component, SideLoad } from './components.mjs';
|
||||
|
||||
class Order extends Component {
|
||||
static __IDENTIFY() { return 'order'; }
|
||||
|
||||
constructor() {
|
||||
super(Order);
|
||||
}
|
||||
|
||||
async OnMount() {
|
||||
// get order id from search param
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
const id = query.get('id');
|
||||
|
||||
const res = (await fetch(`/api/order/${id}`).then(res => res.json())).data;
|
||||
|
||||
this.setState({
|
||||
...this.getState,
|
||||
id,
|
||||
...res,
|
||||
}, false);
|
||||
|
||||
console.log(this.state);
|
||||
localStorage.setItem('viewing-order', JSON.stringify({ items: res.items }));
|
||||
}
|
||||
|
||||
Render() {
|
||||
return {
|
||||
template: /* html */`
|
||||
<div class="order-header">
|
||||
<span class="order-header-title">Thank You For Your Order!</span>
|
||||
<span class="order-header-subtitle">Your order number is <span class="monospace">{this.state.id}</span></span>
|
||||
</div>
|
||||
|
||||
<div class="order-body">
|
||||
<div class="order-body-item">
|
||||
<span class="order-body-date-placed">Placed on ${new Date(this.state.date_placed).toDateString()} at ${new Date(this.state.date_placed).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
<div class="order-body-item">
|
||||
<div class="order-breakdown-table">
|
||||
<div class="order-breakdown-table-item">
|
||||
<div class="order-breakdown-table-header">Address</div>
|
||||
<div class="order-breakdown-table-row">
|
||||
<span class="order-breakdown-table-row-value">John Doe</span>
|
||||
<span class="order-breakdown-table-row-value">123 Example Av,</span>
|
||||
<span class="order-breakdown-table-row-value">Portsmouth,</span>
|
||||
<span class="order-breakdown-table-row-value">PO1 1EA</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-breakdown-table-item">
|
||||
<div class="order-breakdown-table-header">Payment Card</div>
|
||||
<div class="order-breakdown-table-row">
|
||||
<span class="order-breakdown-table-row-value">**** **** **** 1111</span>
|
||||
<span class="order-breakdown-table-row-value">Expires: 01/20</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-breakdown-table-item">
|
||||
<div class="order-price-table">
|
||||
<div class="order-breakdown-table-header">Payment Breakdown</div>
|
||||
<div class="order-price-table-row">
|
||||
<span class="order-price-table-row-title">Discount Applied</span>
|
||||
<span class="order-price-table-row-price">£${parseFloat(this.state.discount).toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="order-price-table-row">
|
||||
<span class="order-price-table-row-title">Total Paid</span>
|
||||
<span class="order-price-table-row-price">£${parseFloat(this.state.subtotal_paid).toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-body-item">
|
||||
<div class="order-body-item-table">
|
||||
<div class="order-body-item-left">
|
||||
<div class="order-body-header">Order Status</div>
|
||||
<div class="order-track">
|
||||
<div class="order-track-step">
|
||||
<span class="order-track-status">
|
||||
<div class="order-track-step-icon completed"></div>
|
||||
<div class="order-track-step-line completed"></div>
|
||||
</span>
|
||||
<div class="order-track-text">
|
||||
<span class="order-body-status-title">Ordered</span>
|
||||
<span class="when">${new Date(this.state.date_placed).toDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-track-step">
|
||||
<span class="order-track-status">
|
||||
<div class="order-track-step-icon"></div>
|
||||
<div class="order-track-step-line"></div>
|
||||
</span>
|
||||
<div class="order-track-text">
|
||||
<span class="order-body-status-title">Posted</span>
|
||||
<span class="when"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-track-step">
|
||||
<span class="order-track-status">
|
||||
<div class="order-track-step-icon"></div>
|
||||
<div class="order-track-step-line"></div>
|
||||
</span>
|
||||
<div class="order-track-text">
|
||||
<span class="order-body-status-title">Delivered</span>
|
||||
<span class="when"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-body-item-right">
|
||||
<div class="order-body-header">Your LegoLog Order</div>
|
||||
<immutable-list-component source="viewing-order" h="400px" class="order-list"></immutable-list-component>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
style: SideLoad('/components/css/order.css'),
|
||||
};
|
||||
}
|
||||
|
||||
OnRender() {
|
||||
// todo: add order tracking, the data is already there
|
||||
}
|
||||
|
||||
OnUnMount() {
|
||||
localStorage.removeItem('viewing-order');
|
||||
}
|
||||
}
|
||||
|
||||
RegisterComponent(Order);
|
||||
@@ -23,7 +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/immutable-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,7 +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/immutable-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>
|
||||
|
||||
37
client/public/orders/index.html
Normal file
37
client/public/orders/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LegoLog Basket</title>
|
||||
<meta name="viewport">
|
||||
<link rel="icon" type="image/svg+xml" href="/res/favicon.svg">
|
||||
<link rel="stylesheet" type="text/css" href="/global.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Londrina+Solid&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Josefin+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Auth0 - a library for authentication -->
|
||||
<script src="https://cdn.auth0.com/js/auth0-spa-js/1.13/auth0-spa-js.production.js"></script>
|
||||
|
||||
<!-- Components used on this page - they must be included to work -->
|
||||
<script type="module" src="/components/components.mjs"></script>
|
||||
<script type="module" src="/components/navbar.mjs"></script>
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/order-list.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-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>
|
||||
<script type="module" src="/components/super-compact-listing.mjs"></script>
|
||||
|
||||
<script type="module" src="/index.mjs"></script>
|
||||
</head>
|
||||
<body>
|
||||
<notificationbar-component></notificationbar-component>
|
||||
<navbar-component></navbar-component>
|
||||
<limited-margin>
|
||||
<order-list-component></order-list-component>
|
||||
</limited-margin>
|
||||
</body>
|
||||
37
client/public/orders/order/index.html
Normal file
37
client/public/orders/order/index.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>LegoLog Basket</title>
|
||||
<meta name="viewport">
|
||||
<link rel="icon" type="image/svg+xml" href="/res/favicon.svg">
|
||||
<link rel="stylesheet" type="text/css" href="/global.css">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Londrina+Solid&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Josefin+Sans&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Auth0 - a library for authentication -->
|
||||
<script src="https://cdn.auth0.com/js/auth0-spa-js/1.13/auth0-spa-js.production.js"></script>
|
||||
|
||||
<!-- Components used on this page - they must be included to work -->
|
||||
<script type="module" src="/components/components.mjs"></script>
|
||||
<script type="module" src="/components/navbar.mjs"></script>
|
||||
<script type="module" src="/components/search.mjs"></script>
|
||||
<script type="module" src="/components/order.mjs"></script>
|
||||
<script type="module" src="/components/basket-popout.mjs"></script>
|
||||
<script type="module" src="/components/immutable-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>
|
||||
<script type="module" src="/components/super-compact-listing.mjs"></script>
|
||||
|
||||
<script type="module" src="/index.mjs"></script>
|
||||
</head>
|
||||
<body>
|
||||
<notificationbar-component></notificationbar-component>
|
||||
<navbar-component></navbar-component>
|
||||
<limited-margin>
|
||||
<order-component></order-component>
|
||||
</limited-margin>
|
||||
</body>
|
||||
@@ -19,7 +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/immutable-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,7 +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/immutable-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>
|
||||
|
||||
@@ -90,15 +90,29 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
date_updated TIMESTAMP WITHOUT TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS offer_code (
|
||||
code TEXT NOT NULL PRIMARY KEY,
|
||||
discount DECIMAL NOT NULL, -- percentage or fixed amount
|
||||
discount_type INT NOT NULL, -- 0 = percentage, 1 = fixed amount
|
||||
min_order_value DECIMAL NOT NULL,
|
||||
type TEXT NOT NULL, -- set or brick
|
||||
expiry_date TIMESTAMP WITHOUT TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS order_log (
|
||||
id VARCHAR (50) NOT NULL PRIMARY KEY,
|
||||
user_id VARCHAR (50), -- null if guest
|
||||
offer_code SERIAL,
|
||||
subtotal_paid DECIMAL NOT NULL,
|
||||
offer_code TEXT,
|
||||
discount DECIMAL,
|
||||
date_placed TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
shipped BOOLEAN NOT NULL,
|
||||
date_shipped TIMESTAMP WITHOUT TIME ZONE,
|
||||
recieved BOOLEAN NOT NULL,
|
||||
date_recieved TIMESTAMP WITHOUT TIME ZONE,
|
||||
FOREIGN KEY ( user_id ) REFERENCES users( id ),
|
||||
FOREIGN KEY ( offer_code ) REFERENCES offer_code( id )
|
||||
FOREIGN KEY ( offer_code ) REFERENCES offer_code( code )
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS order_item (
|
||||
@@ -108,19 +122,8 @@ CREATE TABLE IF NOT EXISTS order_item (
|
||||
brick_colour INT,
|
||||
set_id VARCHAR (50),
|
||||
amount INT NOT NULL,
|
||||
price_paid DECIMAL NOT NULL,
|
||||
FOREIGN KEY ( order_id ) REFERENCES order_log( id ),
|
||||
FOREIGN KEY ( brick_id ) REFERENCES lego_brick( id ),
|
||||
FOREIGN KEY ( brick_colour ) REFERENCES lego_brick_colour( id ),
|
||||
FOREIGN KEY ( set_id ) REFERENCES lego_set( id )
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS offer_code (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
code TEXT NOT NULL,
|
||||
discount DECIMAL NOT NULL, -- percentage or fixed amount
|
||||
discount_type INT NOT NULL, -- 0 = percentage, 1 = fixed amount
|
||||
min_order_value DECIMAL NOT NULL,
|
||||
type TEXT NOT NULL, -- set or brick
|
||||
expiry_date TIMESTAMP WITHOUT TIME ZONE NOT NULL
|
||||
);
|
||||
|
||||
@@ -1,14 +1,154 @@
|
||||
const Database = require('../database/database.js');
|
||||
const Logger = require('../logger.js');
|
||||
const Crypto = require('crypto');
|
||||
|
||||
// C
|
||||
|
||||
async function NewOrder(userId, total, items, discountId = null, discountApplied = null) {
|
||||
// generate unique hex order id xxxx-xxxx-xxxx-xxxx "somewhat securely"
|
||||
const orderId = (Crypto.randomBytes(2).toString('hex') + '-' +
|
||||
Crypto.randomBytes(2).toString('hex') + '-' +
|
||||
Crypto.randomBytes(2).toString('hex') + '-' +
|
||||
Crypto.randomBytes(2).toString('hex')).toUpperCase();
|
||||
|
||||
// user_id and discount_id are optional and can be null
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
const dbres = await Database.Query(`
|
||||
INSERT INTO order_log (id, user_id, subtotal_paid, offer_code, discount, date_placed, shipped, recieved)
|
||||
VALUES ($1, $2, $3, $4, $5, NOW(), FALSE, FALSE)
|
||||
`, [orderId, userId, total, discountId, discountApplied]).catch(() => {
|
||||
return {
|
||||
error: 'Database error',
|
||||
};
|
||||
});
|
||||
|
||||
let dbresOrderItemError = false;
|
||||
|
||||
// this worked first time, it MUST be wrong
|
||||
for (const [key, item] of Object.entries(items)) {
|
||||
const brickId = item.type === 'brick' ? key.split('~')[0] : null;
|
||||
const brickModifier = key.split('~')[1] || null;
|
||||
const setId = item.type === 'set' ? key : null;
|
||||
|
||||
await Database.Query(`
|
||||
INSERT INTO order_item (order_id, brick_id, brick_colour, set_id, amount)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
`, [orderId, brickId, brickModifier, setId, item.quantity]).catch(() => {
|
||||
dbresOrderItemError = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (dbres.error || dbresOrderItemError) {
|
||||
Database.Query('ROLLBACK TRANSACTION;');
|
||||
Logger.Error('Something went wrong inserting an order into the database');
|
||||
return {
|
||||
error: 'Database error',
|
||||
};
|
||||
}
|
||||
Database.Query('COMMIT TRANSACTION;');
|
||||
|
||||
return orderId;
|
||||
}
|
||||
|
||||
// R
|
||||
|
||||
async function GetOrderById(orderid) {
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
const dbres = await Database.Query(`
|
||||
SELECT order_log.id, order_log.user_id, subtotal_paid, offer_code,
|
||||
discount, date_placed, shipped, date_shipped, recieved,
|
||||
date_recieved, item.brick_id, item.brick_colour, item.set_id, item.amount
|
||||
FROM order_log
|
||||
LEFT JOIN order_item AS item ON item.order_id = order_log.id
|
||||
WHERE order_log.id = $1
|
||||
`, [orderid]).catch(() => {
|
||||
return {
|
||||
error: 'Database error',
|
||||
};
|
||||
});
|
||||
|
||||
if (dbres.error) {
|
||||
Database.Query('ROLLBACK TRANSACTION;');
|
||||
Logger.Error(dbres.error);
|
||||
return {
|
||||
error: 'Database error',
|
||||
};
|
||||
}
|
||||
Database.Query('COMMIT TRANSACTION;');
|
||||
|
||||
const result = dbres.rows;
|
||||
|
||||
const items = {};
|
||||
for (const item of result) {
|
||||
if (item.brick_id) {
|
||||
items[`${item.brick_id}~${item.brick_colour}`] = {
|
||||
type: 'brick',
|
||||
quantity: item.amount,
|
||||
};
|
||||
} else if (item.set_id) {
|
||||
items[item.set_id] = {
|
||||
type: 'set',
|
||||
quantity: item.amount,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: result[0].id,
|
||||
user_id: result[0].user_id,
|
||||
subtotal_paid: result[0].subtotal_paid,
|
||||
offer_code: result[0].offer_code,
|
||||
discount: result[0].discount,
|
||||
date_placed: result[0].date_placed,
|
||||
shipped: result[0].shipped,
|
||||
date_shipped: result[0].date_shipped,
|
||||
recieved: result[0].recieved,
|
||||
date_recieved: result[0].date_recieved,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
async function GetOrdersByUser(userId) {
|
||||
await Database.Query('BEGIN TRANSACTION;');
|
||||
const dbres = await Database.Query(`
|
||||
SELECT order_log.id, order_log.user_id, subtotal_paid,
|
||||
discount, date_placed, shipped
|
||||
FROM order_log
|
||||
WHERE order_log.user_id = $1
|
||||
`, [userId]).catch(() => {
|
||||
return {
|
||||
error: 'Database error',
|
||||
};
|
||||
});
|
||||
if (dbres.error) {
|
||||
Database.Query('ROLLBACK TRANSACTION;');
|
||||
Logger.Error(dbres.error);
|
||||
return {
|
||||
error: 'Database error',
|
||||
};
|
||||
}
|
||||
Database.Query('COMMIT TRANSACTION;');
|
||||
|
||||
const result = dbres.rows;
|
||||
return result;
|
||||
}
|
||||
|
||||
// U
|
||||
|
||||
async function OrderShipped(orderid) {
|
||||
|
||||
}
|
||||
|
||||
async function OrderRecieved(orderid) {
|
||||
|
||||
}
|
||||
|
||||
// D
|
||||
|
||||
module.exports = {
|
||||
|
||||
NewOrder,
|
||||
GetOrderById,
|
||||
GetOrdersByUser,
|
||||
OrderShipped,
|
||||
OrderRecieved,
|
||||
};
|
||||
|
||||
@@ -25,11 +25,11 @@ function Init() {
|
||||
Server.App.post('/api/basket/price/', Helpers.CalculateBasketPrice);
|
||||
Server.App.get('/api/discount/', Helpers.DiscountCode);
|
||||
Server.App.post('/api/order/', Order.ProcessNew);
|
||||
Server.App.get('/api/order:id');
|
||||
Server.App.get('/api/order/:id', Order.GetOrder);
|
||||
|
||||
Server.App.get('/api/auth/login/', Auth0.JWTMiddleware, Auth0.Login);
|
||||
Server.App.post('/api/auth/order/', Auth0.JWTMiddleware, Order.ProcessNew);
|
||||
Server.App.get('/api/auth/orders/');
|
||||
Server.App.get('/api/auth/orders/', Auth0.JWTMiddleware, Order.GetOrders);
|
||||
|
||||
Logger.Module('API', 'API Routes Initialized');
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ const ControllerMaster = require('../controllers/controller-master.js');
|
||||
const MiscController = require('../controllers/misc-controller.js');
|
||||
const BrickController = require('../controllers/brick-controller.js');
|
||||
const SetController = require('../controllers/set-controller.js');
|
||||
const OrderController = require('../controllers/order-controller.js');
|
||||
const AuthRouter = require('./auth0-router.js');
|
||||
|
||||
async function ProcessNew(req, res) {
|
||||
console.log(req.body);
|
||||
|
||||
// as it's optional auth, 0 is guest
|
||||
let userID = null;
|
||||
if (req.auth) {
|
||||
@@ -16,8 +15,6 @@ async function ProcessNew(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(userID);
|
||||
|
||||
// validate the request
|
||||
if (!req.body.basket) {
|
||||
return res.send({
|
||||
@@ -26,7 +23,7 @@ async function ProcessNew(req, res) {
|
||||
}
|
||||
|
||||
const basket = req.body.basket;
|
||||
const discountCode = req.body.discountCode || '';
|
||||
const discountCode = req.body.discountCode || null;
|
||||
|
||||
// validate the basket
|
||||
|
||||
@@ -114,11 +111,13 @@ async function ProcessNew(req, res) {
|
||||
// now we need to calculate the discount (if applicable)
|
||||
// again, this could do with some consolidation
|
||||
|
||||
let discount = 0;
|
||||
if (discountCode !== '') {
|
||||
let discount = {
|
||||
discount: 0,
|
||||
};
|
||||
if (discountCode !== null) {
|
||||
const sanatisedCode = ControllerMaster.SanatiseQuery(req.query.code);
|
||||
|
||||
const discount = await MiscController.GetDiscount(sanatisedCode);
|
||||
discount = await MiscController.GetDiscount(sanatisedCode);
|
||||
|
||||
if (discount.error) {
|
||||
return res.send({
|
||||
@@ -133,9 +132,64 @@ async function ProcessNew(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
const total = basketSubtotal - discount.discount;
|
||||
|
||||
const order = await OrderController.NewOrder(userID, total, basket, discountCode, discount.discount);
|
||||
|
||||
if (order.error) {
|
||||
return res.send({
|
||||
error: order.error,
|
||||
});
|
||||
}
|
||||
|
||||
return res.send({
|
||||
data: {
|
||||
receipt_id: order,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function GetOrder(req, res) {
|
||||
const orderId = req.params.id;
|
||||
|
||||
if (!orderId) {
|
||||
return res.send({
|
||||
error: 'No order id in request',
|
||||
});
|
||||
}
|
||||
|
||||
const order = await OrderController.GetOrderById(orderId);
|
||||
|
||||
if (order.error) {
|
||||
return res.send({
|
||||
error: order.error,
|
||||
});
|
||||
}
|
||||
|
||||
return res.send({
|
||||
data: order,
|
||||
});
|
||||
}
|
||||
|
||||
async function GetOrders(req, res) {
|
||||
const user = await AuthRouter.Auth0GetUser(req);
|
||||
const userId = user.sub.split('|')[1];
|
||||
|
||||
const orders = await OrderController.GetOrdersByUser(userId);
|
||||
|
||||
if (orders.error) {
|
||||
return res.send({
|
||||
error: orders.error,
|
||||
});
|
||||
}
|
||||
|
||||
return res.send({
|
||||
data: orders,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ProcessNew,
|
||||
GetOrder,
|
||||
GetOrders,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user