Former-commit-id: 717ed994ad894a878e3ae21be920b5e71fb3c53a
This commit is contained in:
Ben
2022-04-15 19:22:47 +01:00
parent 3ed429f0f4
commit d3fd17cc8a
13 changed files with 274 additions and 45 deletions

View File

@@ -7,8 +7,8 @@ import { RegisterComponent, Component } from './components.mjs';
// {
// "basket": {
// "items": {
// "item1": amount,
// "item2": amount,
// "item1~modifier": { quantity, type },
// "item2": { quantity, type },
// ...
// },
// "total": total
@@ -20,7 +20,7 @@ let basketCallback = null;
// TODO: Does the localstorage have a problem with mutual exclusion?
// TODO: Should the basket be persisted to the server?
export function AddProductToBasket(product, type, amount) {
export function AddProductToBasket(product, type, amount, brickModifier = 'none') {
if (localStorage.getItem('basket') === null || !localStorage.getItem('basket')) {
localStorage.setItem('basket', JSON.stringify({
items: {},
@@ -30,6 +30,10 @@ export function AddProductToBasket(product, type, amount) {
const basket = JSON.parse(localStorage.getItem('basket'));
if (type === 'brick') {
product += '~' + brickModifier;
}
if (basket.items[product]) {
basket.items[product].quantity += amount;
} else {
@@ -48,12 +52,16 @@ export function AddProductToBasket(product, type, amount) {
}
}
export function RemoveProductFromBasket(product, amount) {
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] > amount) {
basket.items[product] -= amount;
} else {

View File

@@ -26,13 +26,15 @@
.product-image-container {
flex-grow: 1;
aspect-ratio: 1;
display: flex;
justify-content: center;
max-width: 600px;
}
.active-image {
display: block;
width: 100%;
max-width: 100%;
height: 100%;
}
.product-info {
@@ -102,6 +104,22 @@
margin-block-end: 0.83em;
}
.brick-colour-selector {
display: flex;
flex-direction: row;
font-size: 1.3em;
justify-content: flex-start;
align-items: center;
margin-block-start: 0.83em;
margin-block-end: 0.83em;
}
.brick-colour-selector-select {
font-size: 1em;
font-family: inherit;
transform: translateY(-1px);
}
.product-quantity-selector {
display: flex;
flex-direction: row;
@@ -254,3 +272,21 @@ input[type=number] {
font-size: 2.3em;
font-weight: bold;
}
.brick-colour-container {
width: 100%;
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.brick-colour-demonstrator {
font-family: inherit;
font-size: 1.5em;
font-weight: bold;
text-shadow: 1px 1px 1px #fff;
width: 40px;
height: 40px;
margin-right: 0.5em;
border: #1A1A1A solid 1px;
}

View File

@@ -50,6 +50,7 @@ class ProductListing extends Component {
}
Render() {
// set contents for sets
let setContents = '';
if (this.state.type === 'set') {
setContents = /* html */`
@@ -66,7 +67,7 @@ class ProductListing extends Component {
name="${brick.name}"
tag="${brick.tag}"
type="brick"
price="${brick.price || brick.discount}">
price="${brick.discount || brick.price}">
</super-compact-listing-component>
</div>
`).join('')}
@@ -75,6 +76,44 @@ class ProductListing extends Component {
`;
}
console.log(this.state)
// brick colour availability for bricks
let brickColourAvailability = '';
let brickColourSelector = '';
if (this.state.type === 'brick') {
brickColourAvailability = /* html */`
<div class="collapsible-menu">
<div class="menu-header">
<span class="menu-header-text">Colour Availability</span>
<img class="menu-header-arrow" src="/res/back-arrow.svg" height="30em" alt="down-arrow">
</div>
<div class="menu-content scrollable-container">
${this.state.colours.map(colour => /* html */`
<div class="brick-colour-container">
<span class="brick-colour-demonstrator" style="background-color: #${colour.hexrgb}"></span>
<span class="brick-colour-name">${colour.name}</span>
<span class="brick-colour-types">&nbsp;In: ${colour.type}</span>
</span>
</div>
`).join('')}
</div>
</div>
`;
brickColourSelector = /* html */`
<div class="brick-colour-selector">
Select Brick Colour&nbsp;
<select class="brick-colour-selector-select">
${this.state.colours.map(colour => /* html */`
<option value="${colour.name}">
${colour.type} ${colour.name}
</option>
`).join('')}
</select>
</div>
`;
}
return {
template: /* html */`
<div class="product-page">
@@ -101,6 +140,8 @@ class ProductListing extends Component {
: `<span class="product-listing-price">£${parseFloat(this.state.price).toFixed(2)}</span>`}
<div class="product-description">${this.state.description || this.state.name + ' ' + this.state.tag}</div>
${brickColourSelector}
<div class="product-quantity-selector">
<button class="product-quantity-button reduce-quantity" type="button">-</button>
<input class="quantity-input" type="number" value="1" min="1" max="{this.state.stock}">
@@ -128,6 +169,7 @@ class ProductListing extends Component {
</div>
${setContents}
${brickColourAvailability}
</div>
</div>
@@ -152,9 +194,8 @@ class ProductListing extends Component {
quantityInput.value = 1;
quantityInput.addEventListener('change', () => {
if (typeof quantityInput.value !== 'number') {
quantityInput.value = 1;
}
quantityInput.value = parseInt(quantityInput.value);
if (quantityInput.value > this.state.stock) {
quantityInput.value = this.state.stock;
}

View File

@@ -11,6 +11,7 @@ class Search extends Component {
return {
template: /* html */`
<input id="search-bar" class="menu-item" type="text" placeholder="search..."/>
<div class="search-results"></div>
`,
style: `
/* Modified version of https://codepen.io/mihaeltomic/pen/vmwMdm */
@@ -56,9 +57,87 @@ class Search extends Component {
margin: 0;
}
}
.search-results {
display: none;
background-color: #AB8FFF;
flex-wrap: wrap;
font-size: 0.6em;
position: fixed;
width: 65%;
}
#search-bar:focus + .search-results {
display: flex;
}
.search-results:hover {
display: flex;
}
.sc-listing {
margin: 0;
padding: 0;
width: 100%;
}
`,
};
}
AppendSearchResults(results, empty = false) {
const searchResults = this.root.querySelector('.search-results');
searchResults.innerHTML = '';
if (empty) {
return;
}
for (const result of results) {
const res = /* html */`
<super-compact-listing-component class="sc-listing" id="${result.id}"
name="${result.name}"
tag="${result.tag}"
type="${result.type}"
price="${result.discount || result.price}">
</super-compact-listing-component>
`;
searchResults.innerHTML += res;
}
}
OnRender() {
const searchBar = this.root.querySelector('#search-bar');
searchBar.value = localStorage.getItem('search-bar');
const route = `/api/search?q=${searchBar.value}&per_page=10`;
fetch(route).then((response) => {
return response.json();
}).then((data) => {
this.AppendSearchResults(data.data);
});
searchBar.addEventListener('keyup', (e) => {
localStorage.setItem('search-bar', e.target.value);
if (e.target.value === '') {
this.AppendSearchResults([], true);
return;
}
// we want this to happen async
const route = `/api/search?q=${e.target.value}&per_page=10`;
fetch(route).then((response) => {
return response.json();
}).then((data) => {
this.AppendSearchResults(data.data);
});
if (e.keyCode === 13) {
const searchTerm = searchBar.value;
if (searchTerm.length > 0) {
window.location.href = `/search?q=${searchTerm}`;
}
}
});
}
}
RegisterComponent(Search);

View File

@@ -36,7 +36,7 @@ class StoreFront extends Component {
<img class="carousel-image" src="res/builder.png" alt="">
<div class="carousel-caption">
<h1>Our Featured Bonsai Tree Set</h1>
<button>Get It Now</button>
<a href="/product/?type=set&id=1010&name=Lego%20Bonsai%20Tree"><button>Get It Now</button></a>
</div>
</div>
</div>

View File

@@ -31,10 +31,13 @@ class SuperCompactProductListing extends Component {
style: `
.product-listing {
width: 95%;
background-color: #F5F6F6;
position: relative;
display: flex;
flex-direction: row;
align-items: center;
padding-left: 10px;
padding-right: 15px;
margin: 7px;
z-index: 0;
cursor: pointer;

View File

@@ -14,12 +14,12 @@ class Tag extends Component {
`,
style: `
.tag {
padding: 0.3em 1em;
margin-right: 0.3em;
margin-top: 0.3em;
margin-bottom: 0.3em;
line-height: 1.5em;
font-size: 0.8em;
padding: 0.2em 0.5em;
margin-right: 0.3em;
margin-top: 0.2em;
margin-bottom: 0.2em;
line-height: 1.3em;
font-weight: bold;
background-color: #F2CA52;
cursor: pointer;

View File

@@ -16,15 +16,12 @@
<!-- TODO: Going to need to dynamically generate this -->
<ul class = "sub-nav">
<li><a class="sub-nav-link" href="#">Trending now</a></li>
<li><a class="sub-nav-link" href="#">For you</a></li>
<li><a class="sub-nav-link" href="#">Sets by theme</a></li>
<li><a class="sub-nav-link" href="#">Sets by ages</a></li>
<li><a class="sub-nav-link" href="#">Sets by price</a></li>
</ul>
</li>
<li class="menu-item dropdown"><a class="nav-link" href="#">Bricks▾</a>
<ul class="sub-nav" >
<li><a class="sub-nav-link" href="#">For you</a></li>
<li><a class="sub-nav-link" href="#">Bricks by catagory</a></li>
<li><a class="sub-nav-link" href="#">Bricks by price</a></li>
</ul>

View File

@@ -12,9 +12,9 @@ automatically every request
| --- | --- | --- | -- | --- |
| GET | /api/special/ | | no | |
| GET | /api/type/:id | | no | |
| GET | /api/search/ | query, page | no | Query endpoint |
| GET | /api/bricks/ | query, page | no | Query endpoint |
| GET | /api/sets/ | query, page | no | Query endpoint |
| 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 |
@@ -40,6 +40,8 @@ For all endpoints that query, the following parameters are supported:
tags: tags to include in search
per_page: results to include per page
page: starting page
pages: pages to return starting from page

View File

@@ -59,8 +59,18 @@ async function GetBrick(brickId) {
};
}
const colours = [];
for (const colour of colDbres.rows) {
colours[colour.name] = {
id: colour.id,
name: colour.name,
hexrgb: colour.hexrgb,
type: colour.colour_type,
};
}
const brick = dbres.rows[0];
brick.colours = colDbres.rows;
brick.colours = Object.values(colours);
brick.tags = brick.tag.split(',');
brick.type = 'brick';

View File

@@ -12,7 +12,7 @@ const Auth0 = require('./auth0-router.js');
function Init() {
Server.App.get('/api/special/', Helpers.Special);
Server.App.get('/api/search/', []);
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);

View File

@@ -16,13 +16,13 @@ function Get(req, res) {
id = id.replace('-thumb', '');
}
// this very randomly fails sometimes
try {
// work out hash from id
const hash = md5(id.split('.png')[0]);
const bucket = hash.substring(0, 4);
const file = `${process.cwd()}\\db\\img\\${bucket[0]}\\${bucket[1]}\\${bucket[2]}\\${bucket[3]}\\${id}`;
// this very randomly fails sometimes
try {
if (fs.existsSync(file)) {
if (thumbnail) {
// generate thumbnail
@@ -34,13 +34,17 @@ function Get(req, res) {
.then(data => {
res.set('Content-Type', 'image/png');
res.send(data);
})
.catch(err => {
Logger.Error(err);
res.sendStatus(404);
});
return;
}
res.sendFile(file);
} else {
if (thumbnail) {
sharp(`${process.cwd()}\\res\\default.png`)
sharp(`${process.cwd()}\\db\\img\\default.png`)
.resize({
height: 50,
}) // keep aspect ratio
@@ -48,6 +52,10 @@ function Get(req, res) {
.then(data => {
res.set('Content-Type', 'image/png');
res.send(data);
})
.catch(err => {
Logger.Error(err);
res.sendStatus(404);
});
return;
}

View File

@@ -0,0 +1,45 @@
async function Search(req, res) {
const q = req.query.q;
console.log(q);
res.send(JSON.stringify({
data: [
{
id: '1010',
type: 'set',
name: q,
price: '1',
discount: '1',
tag: '1',
},
{
id: '1',
type: 'brick',
name: q,
price: '1',
discount: '1',
tag: '1',
},
{
id: '1',
type: 'brick',
name: q,
price: '1',
discount: '1',
tag: '1',
},
{
id: '1',
type: 'brick',
name: q,
price: '1',
discount: '1',
tag: '1',
},
],
}));
}
module.exports = {
Search,
};