Compare commits

..

31 commits
main ... flask

Author SHA1 Message Date
Yarne Coppens
afae2f9605 Wait for umami events to finish 2024-10-04 09:33:25 +02:00
Yarne Coppens
bca3c65ead Placed umami tracking in JS 2024-10-04 09:29:12 +02:00
Yarne Coppens
d7976fa4b6 Added Umami events 2024-10-04 09:24:21 +02:00
Yarne Coppens
52e53330f6 Reformed to use new API icon structure 2024-09-29 16:52:19 +02:00
Yarne Coppens
4a2a52376f Added Umami tracking 2024-09-23 14:26:01 +02:00
Yarne Coppens
193703ff5a Made autofocus more consistent 2024-09-07 00:03:10 +02:00
Yarne Coppens
717624a922 Added favicon 2024-09-04 12:19:13 +02:00
Yarne Coppens
7f2e4bf848 Made barcode input invisible 2024-09-04 12:08:29 +02:00
Yarne Coppens
4ae196156b Maybe really fixed autofocus issue 2024-09-04 11:59:45 +02:00
Yarne Coppens
3bed47458f Fixed autofocus on barcode input 2024-09-04 11:52:18 +02:00
Yarne Coppens
1c056d85ef Added requirments.txt 2024-09-04 11:43:02 +02:00
Yarne Coppens
f817f5b56f Updated api_url 2024-09-04 11:35:18 +02:00
Yarne Coppens
dab46b862d Made it possible to switch payment type 2024-09-04 11:34:22 +02:00
Yarne Coppens
16a7f2ba79 Made it possible to remove products 2024-09-04 11:23:25 +02:00
Yarne Coppens
30e7d5a164 Can now redirect to pay with cash 2024-09-04 10:08:06 +02:00
Yarne Coppens
50a262e549 Made api_url a variable 2024-09-04 10:07:55 +02:00
Yarne Coppens
9287bb9a2b Added paycash template 2024-08-24 13:21:02 +02:00
Yarne Coppens
f6cf4e5695 Added price container styling 2024-08-24 12:49:53 +02:00
Yarne Coppens
1b71fdb0ce Made layout prettier 2024-08-24 12:28:56 +02:00
Yarne Coppens
01acc0617c Started work on Flask application 2024-08-24 12:14:18 +02:00
Yarne Coppens
aeef56e663 Removed console.log debug statements 2024-08-05 18:05:41 +02:00
Yarne Coppens
db06ec4504 Added stylesheet 2024-08-05 17:59:04 +02:00
Yarne Coppens
c478e40ade Implemented bootstrap grid 2024-08-05 17:51:55 +02:00
Yarne Coppens
3304a162ef Switched to Bootstrap 2024-08-05 17:03:32 +02:00
Yarne Coppens
b51a555392 Added the creditcard and cash icon buttons 2024-08-05 14:16:20 +02:00
Yarninator
aa921cc88d Added total price 2024-08-05 13:14:24 +02:00
Yarne Coppens
a47127b4fb Now uses svg's from API 2024-08-04 17:59:05 +02:00
Yarne Coppens
47dc83fd55 Catch barcode form submit for scanning 2024-08-04 11:01:52 +02:00
Yarne Coppens
d25bd44d15 Started barcode scanning basics 2024-08-04 10:35:58 +02:00
Yarne Coppens
26fb8073ed Load products on page load 2024-08-04 10:29:43 +02:00
Yarne Coppens
a50ba6f68a Enabled ability to make requests to API 2024-08-04 10:19:24 +02:00
9 changed files with 438 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.vscode/
__pycache__/
venv/

18
app.py Normal file
View file

@ -0,0 +1,18 @@
from flask import Flask, render_template
from markupsafe import escape
app = Flask(__name__)
api_url = "https://api.toddlershop.yarnecoppens.com"
@app.route("/")
def start():
return render_template('index.jinja', api_url=api_url)
@app.route("/pay_cash/<price>")
def pay_cash(price: int):
return render_template('paycash.jinja', price=escape(price), api_url=api_url)
@app.route("/pay_card/<price>")
def pay_card(price: int):
return render_template('paycard.jinja', price=escape(price), api_url=api_url)

7
requirements.txt Normal file
View file

@ -0,0 +1,7 @@
blinker==1.8.2
click==8.1.7
Flask==3.0.3
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
Werkzeug==3.0.4

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 549 B

161
static/scripts/main.js Normal file
View file

@ -0,0 +1,161 @@
var all_products
let barcodeForm
const api_url = "https://api.toddlershop.yarnecoppens.com"
var to_fill_product_index = 0
var total_price = 0
async function makeAPIRequest(request) {
try {
const response = await fetch(request);
const result = await response.json();
return result
} catch (error) {
console.error("Error:", error);
}
}
async function loadCash(price) {
const cashAmountRequest = new Request(api_url + '/price_to_cash/' + price)
const cashAmount = await makeAPIRequest(cashAmountRequest)
const cash_bills_row = document.getElementById('cash_bills')
for (var bill_type in cashAmount){
bill_amount = cashAmount[bill_type]
console.log(bill_type, bill_amount)
for (var x = 0; x < bill_amount; x++){
const new_column = document.createElement('div')
new_column.classList.add('col')
const bill_image = document.createElement('img')
bill_image.classList.add('cash_image')
bill_image.src = api_url + '/icons/cash/' + bill_type
new_column.appendChild(bill_image)
cash_bills_row.appendChild(new_column)
}
}
}
async function indexOnLoad(){
// document.body.addEventListener('click', focusBarcodeInput, true);
loadProducts();
const bardcodeInputField = document.getElementById('barcode_form')
bardcodeInputField.focus()
bardcodeInputField.addEventListener('blur', function() {
setTimeout(() => {
bardcodeInputField.focus();
}, 0)
})
}
function focusBarcodeInput(){
document.getElementById('barcode_input').focus()
}
async function loadProducts() {
const loadProductRequest = new Request(api_url + "/products")
all_products = await makeAPIRequest(loadProductRequest)
console.log("Loaded products:", all_products)
barcodeForm = document.getElementById("barcode_form");
barcodeForm.addEventListener("submit", (e) => {
e.preventDefault();
if (to_fill_product_index < 5) {
const barcode = document.getElementById('barcode_input').value
var chosen_product
for (index = 0; index < all_products.length; index++) {
if (all_products[index].barcode == barcode) {
chosen_product = all_products[index]
}
}
barcode_input.value = ""
const product_placeholders = document.getElementsByClassName('product_col')
const chosen_product_placeholder = product_placeholders[to_fill_product_index]
const image_product = chosen_product_placeholder.getElementsByClassName('product_image')[0]
const price_product = chosen_product_placeholder.getElementsByClassName('product_price')[0]
const total_price_holder = document.getElementById("totalprice")
total_price += chosen_product.price
image_product.setAttribute('src', api_url + "/icons/" + chosen_product.image_filename);
image_product.style.visibility = 'visible';
price_product.textContent = "\u20AC " + chosen_product.price
total_price_holder.textContent = "\u20AC " + total_price
to_fill_product_index += 1
}else{
barcode_input.value = ""
}
});
}
async function removeProduct(element_to_remove){
await umami.track('Remove Product');
const product_row = document.getElementById('product_row')
const products = product_row.children
let found_removed_product = false
for (var i = 0; i < products.length; i++) {
var product = products[i];
if (product.getElementsByClassName('product_image')[0] == element_to_remove){
found_removed_product = true
const total_price_holder = document.getElementById("totalprice")
total_price -= product.getElementsByClassName('product_price')[0].textContent.replace('€ ','')
console.log(product.getElementsByClassName('product_price')[0].textContent.replace('€ ',''))
total_price_holder.textContent = "\u20AC " + total_price
const price_holder = element_to_remove.parentNode.getElementsByClassName('product_price')[0]
price_holder.innerHTML = ''
}
if (found_removed_product){
if (i < to_fill_product_index - 1){
const next_product = products[i+1]
const current_price_holder = product.getElementsByClassName('product_price')[0]
const next_price_holder = next_product.getElementsByClassName('product_price')[0]
product.getElementsByClassName('product_image')[0].src = next_product.getElementsByClassName('product_image')[0].src
current_price_holder.innerHTML = next_price_holder.innerHTML
}else{
product.getElementsByClassName('product_image')[0].style.visibility = 'hidden';
const current_price_holder = product.getElementsByClassName('product_price')[0]
current_price_holder.innerHTML = ''
}
}
}
to_fill_product_index -= 1
}
async function payCash() {
await umami.track('Pay Cash button');
const total_cost = document.getElementById('totalprice').innerHTML.replace('€ ','')
window.location.href = "/pay_cash/" + total_cost
}
async function payCard() {
await umami.track('Pay Card button');
const total_cost = document.getElementById('totalprice').innerHTML.replace('€ ','')
window.location.href = "/pay_card/" + total_cost
}
async function toMain() {
await umami.track('To Main button');
window.location.href = "/";
}

31
static/style/main.css Normal file
View file

@ -0,0 +1,31 @@
html, body {
margin: 0;
height: 100%;
}
body{
background-color: #D9F2D0;
}
div.card{
background-color: #afda8e;
}
#price_container {
background-color: white;
margin-top: auto;
margin-bottom: auto;
margin-left: auto;
margin-right: auto;
border-style: solid;
}
#cash_bills{
background-color: #afda8e;
}
.cash_image{
float: left;
width: 300px;
height: 300px;
}

98
templates/index.jinja Normal file
View file

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html data-theme="light">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<title>
Toddler Shop
</title>
<script src="{{url_for('static', filename='scripts/main.js')}}"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='style/main.css')}}">
<script defer src="https://umami.yarnecoppens.com/script.js" data-website-id="1d8c47c1-acee-47ff-a067-f5363a0514a2"></script>
</head>
<body onload="indexOnLoad()" onclick="focusBarcodeInput()">
<form action="" id="barcode_form" style="opacity: 0;">
<input id="barcode_input" type="text" autofocus>
<input type="submit" value="Submit">
</form>
<img class="product_image">
<div class="text-center" id="product_placeholder">
<div class="row" style="margin-bottom: 5rem;" id="product_row">
<div class="col product_col">
<div class="card h-100">
<input type="image" onclick="removeProduct(this)" class="card-img-top product_image" alt="">
<div class="card-body">
<h5 class="card-title product_price"></h5>
</div>
</div>
</div>
<div class="col product_col">
<div class="card h-100">
<input type="image" onclick="removeProduct(this)" class="card-img-top product_image" alt="">
<div class="card-body">
<h5 class="card-title product_price"></h5>
</div>
</div>
</div>
<div class="col product_col">
<div class="card h-100">
<input type="image" onclick="removeProduct(this)" class="card-img-top product_image" alt="">
<div class="card-body">
<h5 class="card-title product_price"></h5>
</div>
</div>
</div>
<div class="col product_col">
<div class="card h-100">
<input type="image" onclick="removeProduct(this)" class="card-img-top product_image" alt="">
<div class="card-body">
<h5 class="card-title product_price"></h5>
</div>
</div>
</div>
<div class="col product_col">
<div class="card h-100">
<input type="image" onclick="removeProduct(this)" class="card-img-top product_image" alt="">
<div class="card-body">
<h5 class="card-title product_price"></h5>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-8">
<div class="card">
<div class="container text-center">
<div class="row" style="width: 70%;margin-left: auto;margin-right: auto;">
<div class="col">
<img src="{{api_url}}/icons/cart" height="100rem" width="100rem" class="card-img-top" alt="">
</div>
<div class="col" id="price_container">
<h2 class="card-title" id="totalprice">&euro; 0</h2>
</div>
</div>
</div>
</div>
</div>
<div class="col-2">
<div class="card">
<input type="image" onclick="payCash()" src="{{api_url}}/icons/cash" height="100rem" width="100rem" class="card-img-top" alt="" />
</div>
</div>
<div class="col-2">
<div class="card">
<input type="image" onclick="payCard()" src="{{api_url}}/icons/creditcard" height="100rem" width="100rem" class="card-img-top" alt="" />
</div>
</div>
</div>
</div>
</body>
</html>

62
templates/paycard.jinja Normal file
View file

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html data-theme="light">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<title>
Toddler Shop
</title>
<script src="{{url_for('static', filename='scripts/main.js')}}"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='style/main.css')}}">
<script defer src="https://umami.yarnecoppens.com/script.js" data-website-id="1d8c47c1-acee-47ff-a067-f5363a0514a2"></script>
</head>
<body>
<div class="row m-5">
<div class="col-2">
<div class="card">
<input type="image" onclick="payCash()" src="{{api_url}}/icons/cash" height="100rem" width="100rem" class="card-img-top" alt="">
</div>
</div>
<div class="col-8">
<div class="card">
<div class="container text-center product_placeholder">
<div class="row" style="width: 70%;margin-left: auto;margin-right: auto;">
<div class="col">
<img src="{{api_url}}/icons/cart" height="100rem" width="100rem" class="card-img-top" alt="">
</div>
<div class="col" id="price_container">
<h2 class="card-title" id="totalprice">&euro; {{price}}</h2>
</div>
</div>
</div>
</div>
</div>
<div class="col-2">
<div class="card">
<input type="image" onclick="payCard()" src="{{api_url}}/icons/creditcard" height="100rem" width="100rem" class="card-img-top" alt="">
</div>
</div>
</div>
<div class="row justify-content-center m-5">
<div class="col-1">
<img src="{{api_url}}/icons/paycard" />
</div>
</div>
<div class="row justify-content-end m-5">
<div class="col-2">
<div class="card">
<input type="image" onclick="toMain()" src="{{api_url}}/icons/shop" height="100rem" width="100rem" class="card-img-top" alt="" />
</div>
</div>
</div>
</body>
</html>

58
templates/paycash.jinja Normal file
View file

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html data-theme="light">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<title>
Toddler Shop
</title>
<script src="{{url_for('static', filename='scripts/main.js')}}"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<link rel="stylesheet" href="{{url_for('static', filename='style/main.css')}}">
<script defer src="https://umami.yarnecoppens.com/script.js" data-website-id="1d8c47c1-acee-47ff-a067-f5363a0514a2"></script>
</head>
<body onload="loadCash({{price}})">
<div class="row m-5">
<div class="col-2">
<div class="card">
<input type="image" onclick="payCash()" src="{{api_url}}/icons/cash" height="100rem" width="100rem" class="card-img-top" alt="">
</div>
</div>
<div class="col-8">
<div class="card">
<div class="container text-center product_placeholder">
<div class="row" style="width: 70%;margin-left: auto;margin-right: auto;">
<div class="col">
<img src="{{api_url}}/icons/cart" height="100rem" width="100rem" class="card-img-top" alt="">
</div>
<div class="col" id="price_container">
<h2 class="card-title" id="totalprice">&euro; {{price}}</h2>
</div>
</div>
</div>
</div>
</div>
<div class="col-2">
<div class="card">
<input type="image" onclick="payCard()" src="{{api_url}}/icons/creditcard" height="100rem" width="100rem" class="card-img-top" alt="">
</div>
</div>
</div>
<div class="row m-5" id="cash_bills">
</div>
<div class="row justify-content-end m-5">
<div class="col-2">
<div class="card">
<input type="image" onclick="toMain()" src="{{api_url}}/icons/shop" height="100rem" width="100rem" class="card-img-top" alt="" />
</div>
</div>
</div>
</body>
</html>