bezahlbrudi/js/index.js
Ricardo (XenGi) Band f9bcfcb7a6
things
2026-01-06 14:27:03 +01:00

351 lines
12 KiB
JavaScript

import { testIban } from "./iban.js";
// bulma hamburger menu
document.addEventListener('DOMContentLoaded', () => {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
// Add a click event on each of them
$navbarBurgers.forEach(el => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target;
const $target = document.getElementById(target);
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
});
});
});
// language selector
document.getElementById("current-language").addEventListener("click", (e) => {
e.preventDefault();
document.getElementById("language-dropdown").classList.toggle("is-active");
});
const i18n = JSON.parse(document.getElementById("i18n-data").textContent);
const i18nSupported = Object.keys(i18n._flags)
// build language selector
const langSelector = document.getElementById("language-selector")
for (const l of i18nSupported) {
const a = document.createElement("a");
a.classList.add("navbar-item");
a.innerHTML = `${i18n._flags[l]} ${l.toUpperCase()}`;
a.href = "#";
a.addEventListener("click", (e) => {
changeLanguage(e.target.textContent.slice(-2).toLowerCase());
document.getElementById("language-dropdown").classList.remove("is-active");
});
langSelector.appendChild(a);
}
const changeLanguage = (l) => {
const lang = i18nSupported.includes(l) ? l : "en";
console.log(`Switching language to ${lang}`);
document.querySelectorAll("[data-i18n]").forEach(el => {
el.innerHTML = i18n[el.dataset.i18n][lang];
});
document.getElementById("current-language").innerHTML = `${i18n._flags[lang]} ${lang.toUpperCase()}`;
document.getElementById("input-purpose").placeholder = i18n["purpose-example"][lang];
document.documentElement.lang = lang;
saveFormData();
};
const saveFormData = () => {
localStorage.setItem("bezahlbrudiFormData", JSON.stringify({
name: document.querySelector("#name input").value,
iban: document.querySelector("#iban input").value.toUpperCase(),
bic: document.querySelector("#bic input").value.toUpperCase(),
amount: parseFloat(document.querySelector("#amount input").value).toFixed(2),
purpose: document.querySelector("#purpose input").value,
lang: document.getElementById("current-language").textContent.slice(-2).toLowerCase()
}));
}
// Field data checks
const validateName = (target) => {
if (target.value.length > 0) {
if (target.value.length <= 70) {
target.classList.remove("is-danger")
document.querySelector("#name div span").innerHTML = "&#x2705;";
if (document.querySelector("#name p")) {
document.querySelector("#name p").remove();
}
} else {
target.classList.add("is-danger")
document.querySelector("#name div span").innerHTML = "&#x274C;";
if (!document.querySelector("#name p")) {
const help = document.createElement("p");
help.classList.add("help", "is-danger");
help.textContent = "Name can only have 70 characters.";
document.querySelector("#name").appendChild(help);
}
}
} else {
// empty field with no validation icon
target.classList.remove("is-danger");
document.querySelector("#name div span").innerHTML = "";
if (document.querySelector("#name p")) {
document.querySelector("#name p").remove();
}
}
}
const validateBic = (target) => {
if (target.value.length > 0) {
const re = /^[a-z]{6}[2-9a-z][0-9a-np-z]([a-z0-9]{3}|x{3})?$/i;
if (re.test(target.value)) {
target.classList.remove("is-danger")
document.querySelector("#bic div span").innerHTML = "&#x2705;";
if (document.querySelector("#bic p")) {
document.querySelector("#bic p").remove();
}
} else {
target.classList.add("is-danger")
document.querySelector("#bic div span").innerHTML = "&#x274C;";
if (!document.querySelector("#bic p")) {
const help = document.createElement("p");
help.classList.add("help", "is-danger");
help.textContent = "BIC is invalid";
document.querySelector("#bic").appendChild(help);
}
}
} else {
target.classList.remove("is-danger");
document.querySelector("#bic div span").innerHTML = "";
if (document.querySelector("#bic p")) {
document.querySelector("#bic p").remove();
}
}
}
const validateIban = (target) => {
// IBANs are fucking complicated
// If you want to know more look here:
// https://www.swift.com/standards/data-standards/iban-international-bank-account-number
// Validates ISO 13616 compliant countries (v99, Dec 2024)
// ISO 3166-1 country code + 2 check digits + up to 30 alphanumeric chars
if (target.value.length > 4) {
const message = testIban(target.value);
if (message === "") {
target.classList.remove("is-danger");
document.querySelector("#iban div span").innerHTML = "&#x2705;";
if (document.querySelector("#iban p")) {
document.querySelector("#iban p").remove();
}
} else {
target.classList.add("is-danger");
document.querySelector("#iban div span").innerHTML = "&#x274C;";
if (!document.querySelector("#iban p")) {
const help = document.createElement("p");
help.classList.add("help", "is-danger");
help.textContent = message;
document.querySelector("#iban").appendChild(help);
}
}
} else {
// empty field with no validation icon
target.classList.remove("is-danger");
document.querySelector("#iban div span").innerHTML = "";
if (document.querySelector("#iban p")) {
document.querySelector("#iban p").remove();
}
}
}
const validateAmount = (target) => {
console.log(`${target.value} = ${parseFloat(target.value.replace(",", ".")).toFixed(2)}`);
if (target.value.length > 0) {
const val = parseFloat(target.value.replace(",", "."))
if (val.toFixed(2) == val && val >= 0) {
target.classList.remove("is-danger");
document.querySelector("#amount div span.is-right").innerHTML = "&#x2705;";
} else {
target.classList.add("is-danger");
document.querySelector("#amount div span.is-right").innerHTML = "&#x274C;";
}
} else {
target.classList.remove("is-danger");
document.querySelector("#amount div span.is-right").innerHTML = "";
if (document.querySelector("#amount p")) {
document.querySelector("#amount p").remove();
}
}
}
const validatePurpose = (target) => {
if (target.value.length > 0) {
const re = /[a-z0-9\/?:().,'+\- ]{1,140}/i;
if (re.test(target.value)) {
target.classList.remove("is-danger")
document.querySelector("#purpose div span").innerHTML = "&#x2705;";
if (document.querySelector("#purpose p")) {
document.querySelector("#purpose p").remove();
}
} else {
target.classList.add("is-danger")
document.querySelector("#purpose div span").innerHTML = "&#x274C;";
if (!document.querySelector("#purpose p")) {
const help = document.createElement("p");
help.classList.add("help", "is-danger");
help.textContent = "Purpose can only have 140 characters and only include A-Z, a-z, 0-9, /?:().,'+- and spaces.";
document.querySelector("#purpose").appendChild(help);
}
}
} else {
target.classList.remove("is-danger");
document.querySelector("#purpose div span.is-right").innerHTML = "";
if (document.querySelector("#purpose p")) {
document.querySelector("#purpose p").remove();
}
}
}
document.querySelector("#name input").addEventListener("input", (event) => {
validateName(event.target);
regenerate();
});
document.querySelector("#iban input").addEventListener("input", (event) => {
validateIban(event.target);
regenerate();
});
document.querySelector("#bic input").addEventListener("input", (event) => {
validateBic(event.target);
regenerate();
});
document.querySelector("#amount input").addEventListener("input", (event) => {
// Remove invalid characters
//event.target.value = event.target.value.replace(/[^0-9.]/g, '');
if (event.target.value.length > 0 && !isNaN(parseFloat(event.target.value))) {
event.target.value = parseFloat(event.target.value).toFixed(2);
}
validateAmount(event.target);
regenerate();
});
document.querySelector("#purpose input").addEventListener("input", (event) => {
validatePurpose(event.target);
regenerate();
});
// Remember inputs
document.querySelector("#rememberme").addEventListener("change", (event) => {
if (event.target.checked) {
saveFormData();
} else {
localStorage.clear();
}
})
// Regenerate on color-scheme change
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {
regenerate()
});
// qr code generator
const regenerate = () => {
document.querySelector('#qr-code').innerHTML = "";
if (document.querySelector("#name input").classList.contains("is-danger")) {
console.error("Error in name");
}
else if (document.querySelector("#iban input").classList.contains("is-danger")) {
console.error("Error in IBAN");
}
else if (document.querySelector("#bic input").classList.contains("is-danger")) {
console.error("Error in BIC");
}
else if (document.querySelector("#amount input").classList.contains("is-danger")) {
console.error("Error in Amount");
}
else if (document.querySelector("#purpose input").classList.contains("is-danger")) {
console.error("Error in Purpose");
} else {
console.log("Generating new QR code...");
const data = `BCD
002
1
SCT
${document.querySelector("#bic input").value.toUpperCase()}
${document.querySelector("#name input").value}
${document.querySelector("#iban input").value.toUpperCase()}
EUR${document.querySelector("#amount input").value}
${document.querySelector("#purpose input").value}
`;
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
// dark mode
QrCreator.render({
text: data,
radius: 0.25,
ecLevel: "H",
fill: "#fff",
background: "#000",
size: 512
}, document.querySelector('#qr-code'));
} else {
// light mode
QrCreator.render({
text: data,
radius: 0.25,
ecLevel: "H",
fill: "#000",
background: "#fff",
size: 512
}, document.querySelector('#qr-code'));
}
if (document.querySelector("#rememberme").checked) {
saveFormData();
}
}
}
// Fill in form from local storage
const savedData = localStorage.getItem("bezahlbrudiFormData");
if (savedData) {
const formData = JSON.parse(savedData);
document.querySelector("#name input").value = formData["name"];
document.querySelector("#iban input").value = formData["iban"];
document.querySelector("#bic input").value = formData["bic"];
document.querySelector("#amount input").value = formData["amount"];
document.querySelector("#purpose input").value = formData["purpose"];
document.querySelector("#rememberme").checked = true;
changeLanguage(formData["lang"]);
} else {
// get browser language
let lang = "en"; // default fallback
for (const l of navigator.languages) {
const base = l.split("-")[0];
if (i18nSupported.includes(base)) {
lang = base;
break;
}
}
changeLanguage(lang);
}
// Validate all fields to set css properties
validateName(document.querySelector("#name input"));
validateIban(document.querySelector("#iban input"));
validateBic(document.querySelector("#bic input"));
validateAmount(document.querySelector("#amount input"));
validatePurpose(document.querySelector("#purpose input"));
// generate qr code on first visit
regenerate();
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/js/service-worker.js')
.catch(err => console.error('Service Worker registration failed:', err));
}