Skip to content

Commit

Permalink
WIP Kiosk confirmation with additional purchase option
Browse files Browse the repository at this point in the history
  • Loading branch information
megastary committed Mar 24, 2024
1 parent f746524 commit 30ca7cb
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 76 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

## ✨ Novinky

- 💳 Nově si může uživatel přidat kartu o minimální délce 6 znaků, která slouží jako bezpečnější způsob ověření
- 💳 Nově může administrátor uživatelům přidat kartu o minimální délce 6 znaků, která slouží jako bezpečnější způsob ověření
a rovněž slouží pro načtení fyzického čářového nebo QR kódu místo ručního zadávání
- 😱 Chybně zakoupený produkt nově dokáže administrátor do 15 minut stornovat
- 📱 Nová sada API volání umožňuje nakupovat pomocí nové mobilní aplikace sbf-scanner
- 🔢 Číslo klávesnice nyní může mít maximálně délku 5 znaků, pro delší bezpečnější způsob ověření slouží karta
- 🔐 Zákazník si může zakázat přihlašování pomocí čísla klávesnice, aby jeho snadno uhodnutelná identita nemohla být zneužita
- 🫅 Administrátorům konečně přibyla správa zákazníků, kde mohou nastavit práva, kartu a anonymizovat bývalé uživatele

## 🐞 Opravy chyb

- 🎨 Kategorie se v nabídce zobrazují ihned po vytvoření, není je potřeba ještě dodatečně upravit přes formulář Upravit kategorii
- 🚫 Zakázané kategorie se již nezobrazují ve filtru, u produktů a ani při vytváření a úpravě produktů
- 📑 S ohledem na rostoucí velikost databáze byly definovány indexy, které by měly rychlosti odezvy pomoci
- 🪪 Formuláře pro změnu údajů na stránce profilu nyní využívají CSRF token stejně jako ostatní části aplikace
104 changes: 59 additions & 45 deletions public/javascripts/kiosk_keypad.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,98 @@
function addMinutes(date, minutes) {
return new Date(date.getTime() + minutes * 60000)
return new Date(date.getTime() + minutes * 60000);
}

document.addEventListener('DOMContentLoaded', function () {
var interval
const kiosk_click_button = new Audio('/audio/kiosk_click_button.mp3')
const kiosk_click_reset = new Audio('/audio/kiosk_click_reset.mp3')
const kiosk_timeout_alert = new Audio('/audio/kiosk_timeout.mp3')
const timer_display = document.getElementById('timer')
function addTextToScreen(string) {
const screen = document.getElementById("customer_id");
screen.value = screen.value + string;
}

document.addEventListener("DOMContentLoaded", function () {
var interval;
const kiosk_click_button = new Audio("/audio/kiosk_click_button.mp3");
const kiosk_click_reset = new Audio("/audio/kiosk_click_reset.mp3");
const kiosk_timeout_alert = new Audio("/audio/kiosk_timeout.mp3");
const timer_display = document.getElementById("timer");

// Play sound on SUBMIT click
document.getElementById('submit').addEventListener('click', function () {
kiosk_click_button.currentTime = 0
kiosk_click_button.play()
})
document.getElementById("submit").addEventListener("click", function () {
kiosk_click_button.currentTime = 0;
kiosk_click_button.play();
});

// Play sound on RESET click and reset and stop timer
document.getElementById('reset').addEventListener('click', function () {
kiosk_timeout_alert.pause()
document.getElementById("reset").addEventListener("click", function () {
kiosk_timeout_alert.pause();

kiosk_click_reset.currentTime = 0
kiosk_click_reset.play()
kiosk_click_reset.currentTime = 0;
kiosk_click_reset.play();

clearInterval(interval)
timer_display.value = '⏱️'
})
clearInterval(interval);
timer_display.value = "⏱️";
});

// If navigated to this page with alert, play sound
const alertElement = document.getElementById('alert')
if (alertElement?.classList.contains('alert-danger')) {
new Audio('/audio/kiosk_user_not_found.mp3').play()
} else if (alertElement?.classList.contains('alert-success')) {
new Audio('/audio/kiosk_purchase_confirmed.mp3').play()
const alertElement = document.getElementById("alert");
if (alertElement?.classList.contains("alert-danger")) {
new Audio("/audio/kiosk_user_not_found.mp3").play();
} else if (alertElement?.classList.contains("alert-success")) {
new Audio("/audio/kiosk_purchase_confirmed.mp3").play();
} else {
// If navigating from context aware link, trigger timeout alert
const timeout = new URLSearchParams(location.search).get('timeout')
if (timeout) kiosk_timeout_alert.play()
const timeout = new URLSearchParams(location.search).get("timeout");
if (timeout) kiosk_timeout_alert.play();
// If navigating from context aware link, trigger timeout alert
const cancel = new URLSearchParams(location.search).get('cancel')
if (cancel) new Audio('/audio/kiosk_purchase_cancel.mp3').play()
const cancel = new URLSearchParams(location.search).get("cancel");
if (cancel) new Audio("/audio/kiosk_purchase_cancel.mp3").play();
}

// Register number input handling for all numeric buttons
const numberButtons = document.getElementsByClassName('numberBtn')
const numberButtons = document.getElementsByClassName("numberBtn");

const _numberButtonsEvent = [...numberButtons].forEach((element) => {
element.addEventListener('click', async function () {
element.addEventListener("click", async function () {
// Play sound on NUMBER click
kiosk_click_button.currentTime = 0
kiosk_click_button.play()
kiosk_click_button.currentTime = 0;
kiosk_click_button.play();

// Stop previous interval
clearInterval(interval)
clearInterval(interval);

// Set the date we're counting down to
var countDownDate = addMinutes(new Date(), 1)
var countDownDate = addMinutes(new Date(), 1);

// Update the count down every 1 second
interval = setInterval(function () {
// Get today's date and time
var now = new Date().getTime()
var now = new Date().getTime();

// Find the distance between now and the count down date
var distance = countDownDate - now
var distance = countDownDate - now;

var seconds = Math.floor((distance % (1000 * 60)) / 1000)
var seconds = Math.floor((distance % (1000 * 60)) / 1000);

// Display the result in the element with id="timer"
timer_display.value = seconds + 's'
timer_display.value = seconds + "s";

// If the count down is finished, redirect to itself to reset page state
if (distance < 0) {
clearInterval(interval)
window.location.href = '/kiosk_keypad?timeout=1'
clearInterval(interval);
window.location.href = "/kiosk_keypad?timeout=1";
}
}, 1000)
}, 1000);

// Display numbers on screen
const screen = document.getElementById('customer_id')
screen.value = screen.value + this.value
})
})
})
addTextToScreen(this.value);
});
});
});

document.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
document.getElementById("submit").click();
} else if (e.key.length === 1) {
addTextToScreen(e.key);
}
console.log("IR scan textInput", e);
e.preventDefault();
});
155 changes: 131 additions & 24 deletions public/javascripts/kiosk_shop.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,151 @@
const myModal = new bootstrap.Modal("#confirm-modal");

const modalProductName = document.getElementById("product_name");
const modalProductPrice = document.getElementById("product_price");
const modalProductImage = document.getElementById("product_image");

function addMinutes(date, minutes) {
return new Date(date.getTime() + minutes * 60000)
return new Date(date.getTime() + minutes * 60000);
}

document.addEventListener('DOMContentLoaded', function () {
var interval
const backgroundMusic = new Audio('/audio/kiosk_shop_theme.mp3')
function addSeconds(date, seconds) {
return new Date(date.getTime() + seconds * 1000);
}

// Play background them
backgroundMusic.volume = 0.3
backgroundMusic.play()
document.addEventListener("DOMContentLoaded", function () {
var interval;
const backgroundMusic = new Audio("/audio/kiosk_shop_theme.mp3");

// Play background theme music
backgroundMusic.volume = 0.3;
backgroundMusic.play();

// Make whole product card clickable for touch screens
const _forms = document.querySelectorAll('form').forEach((element) => {
element.addEventListener('click', async function (event) {
event.preventDefault()
this.submit()
})
})
const _forms = document.querySelectorAll("form").forEach((element) => {
element.addEventListener("click", async function (event) {
event.preventDefault();
if (element.id === "back-button") this.submit();
showConfirm(
this.id,
this.dataset.productname,
this.dataset.productprice,
this.dataset.productimage
);
});
});

// Set the date we're counting down to
var countDownDate = addMinutes(new Date(), 3)
var countDownDate = addMinutes(new Date(), 3);

// Update the count down every 1 second
interval = setInterval(function () {
// Get today's date and time
var now = new Date().getTime()
var now = new Date().getTime();

// Find the distance between now and the count down date
var distance = countDownDate - now
var distance = countDownDate - now;

var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60))
var seconds = Math.floor((distance % (1000 * 60)) / 1000)
var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);

// Display the result in the element with id="timer"
document.getElementById('timer').value = minutes + 'm ' + seconds + 's'
document.getElementById("timer").value = minutes + "m " + seconds + "s";

// If the count down is finished, redirect to keypad to reset page state
if (distance < 0) {
clearInterval(interval)
window.location.href = '/kiosk_keypad?timeout=1'
if (distance < 1000) {
clearInterval(interval);
window.location.href = "/kiosk_keypad?timeout=1";
}
}, 1000);
});

let backgroundInput = "";
document.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
const form = document.querySelector(
"form[data-code='" + backgroundInput + "']"
);
if (form) {
// TODO: Show modal instead
form.action = form.action + "?return=kiosk_shop";
form.submit();
} else {
console.log("Invalid code", backgroundInput);
let backgroundInput = "";
}
}, 1000)
})
} else if (e.key.length === 1) {
backgroundInput = backgroundInput + e.key;
}
console.log("Key pressed", e);
e.preventDefault();
});

// Display confirmation dialog
async function showConfirm(
form_id,
product_name,
product_price,
product_image
) {
const submit = document.getElementById("modal_confirm");
const submitContinue = document.getElementById("modal_confirm_continue");
submit.dataset.submit_id = form_id;
submitContinue.dataset.submit_id = form_id;
modalProductName.innerText = product_name;
modalProductPrice.innerText = product_price;
modalProductImage.src = product_image;
myModal.show();
}

// Submit form from modal to confirm purchase - optionally keep shopping
function submitFromModal(ctx, keepShopping) {
const form = document.getElementById(ctx.dataset.submit_id);
if (keepShopping) {
form.action += "?return=kiosk_shop";
}
form.submit();
}

// TODO: Clarify varabile names
var modalCountDownDate;
var modalInterval;

document
.getElementById("confirm-modal")
.addEventListener("shown.bs.modal", function () {
console.log("Modal shown");

document.getElementById("modal_confirm").innerHTML = "Dokončit nákup (3s)";

// Set the date we're counting down to
modalCountDownDate = addSeconds(new Date(), 4);

// Update the count down every 1 second
modalInterval = setInterval(function () {
// Get today's date and time
var now = new Date().getTime();

// Find the distance between now and the count down date
var distance = modalCountDownDate - now;

// var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
var seconds = Math.floor((distance % (1000 * 60)) / 1000);

// If the count down is finished, redirect to keypad to reset page state
if (distance < 1000) {
clearInterval(modalInterval);
document.getElementById("modal_confirm").click();
}

// Display the result in the element with id="timer"
document.getElementById("modal_confirm").innerHTML =
"Dokončit nákup (" + seconds + "s)";
}, 1000);
});

document
.getElementById("confirm-modal")
.addEventListener("hidden.bs.modal", function () {
console.log("Modal hidden");
clearInterval(modalInterval);
});
17 changes: 15 additions & 2 deletions routes/kiosk_shop.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function renderPage(req, res, alert, customer) {
from: "categories",
localField: "category",
foreignField: "_id",
pipeline: [{ $match: { disabled: { $in: [null, false] } } }],
as: "category",
},
},
Expand All @@ -47,6 +48,7 @@ function renderPage(req, res, alert, customer) {
description: "$description",
imagePath: "$imagePath",
category: "$category",
code: "$code",
stock: {
$filter: {
// We filter only the stock object from array where ammount left is greater than 0
Expand Down Expand Up @@ -210,6 +212,11 @@ router.get("/", ensureAuthenticated, function (req, res) {
alert = req.session.alert;
delete req.session.alert;
}
if (req.query.customer_id.length.toString() < 6) {
customer.form_identifier = customer.keypadId;
} else {
customer.form_identifier = customer.card;
}
renderPage(req, res, alert, customer);
})
.catch((err) => {
Expand Down Expand Up @@ -273,7 +280,7 @@ router.post("/", ensureAuthenticated, function (req, res) {
.then((user) => {
if (!user) {
logger.error(
`server.routes.kioskshop.post__Failed to find user by keypadId ${req.body.customer_id}.`,
`server.routes.kioskshop.post__Failed to find user by keypadId or card ${req.body.customer_id}.`,
{
metadata: {
error: req.body.customer_id,
Expand Down Expand Up @@ -335,7 +342,13 @@ router.post("/", ensureAuthenticated, function (req, res) {
req.body.image_path
);

res.redirect("/kiosk_keypad");
if (req.query.return === "kiosk_shop") {
res.redirect(
"/kiosk_shop?customer_id=" + req.body.customer_id
);
} else {
res.redirect("/kiosk_keypad");
}
})
.catch((err) => {
logger.error(
Expand Down
1 change: 1 addition & 0 deletions routes/shop.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function renderPage(req, res, alert) {
from: "categories",
localField: "category",
foreignField: "_id",
pipeline: [{ $match: { disabled: { $in: [null, false] } } }],
as: "category",
},
},
Expand Down
Loading

0 comments on commit 30ca7cb

Please sign in to comment.