diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-06-01 21:13:43 +0200 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-06-01 21:13:43 +0200 |
| commit | 9383a2fb09ffb60cfe63683106945bd688affa59 (patch) | |
| tree | 65b3f4b48841583e355887db5de5a16e7005fc87 /src/wwwroot/scripts/front | |
| download | vinjesvingenhandel.no-9383a2fb09ffb60cfe63683106945bd688affa59.tar.xz vinjesvingenhandel.no-9383a2fb09ffb60cfe63683106945bd688affa59.zip | |
feat: Initial commit after clean slate
Diffstat (limited to 'src/wwwroot/scripts/front')
| -rw-r--r-- | src/wwwroot/scripts/front/handlekurv.js | 256 | ||||
| -rw-r--r-- | src/wwwroot/scripts/front/logginn.js | 92 | ||||
| -rw-r--r-- | src/wwwroot/scripts/front/produkter.js | 341 | ||||
| -rw-r--r-- | src/wwwroot/scripts/front/status.js | 10 |
4 files changed, 699 insertions, 0 deletions
diff --git a/src/wwwroot/scripts/front/handlekurv.js b/src/wwwroot/scripts/front/handlekurv.js new file mode 100644 index 0000000..bb80b3e --- /dev/null +++ b/src/wwwroot/scripts/front/handlekurv.js @@ -0,0 +1,256 @@ +import {utilites} from "../utilities"; +import {$, doc, pageInit} from "../base"; +import { + cartDispose, + getCart, + priceTax, + priceTotal, + removeProductFromCart, + setProductCount, + updatePricesFromApi, +} from "../cart-core"; + +import {getCounterControl} from "../components"; +import {strings} from "../i10n.ts"; +import {submitOrder} from "../api/order-api"; +import "bootstrap/js/dist/tab"; + +const inputName = $("#input-name"); +const inputEmail = $("#input-email"); +const inputPhone = $("#input-phone"); +const inputComment = $("#input-comment"); +const invoiceMail = $("#invoice-mail"); + +const orderSummaryWrapper = $("#order-summary-wrapper"); +const submitOrderWrapper = $("#submit-order-wrapper"); +const orderSummaryLoader = $("#order-summary-spinner"); +const total = $("#total-wrapper"); + +function setOrderSummaryLoaderVisibility(show = false) { + if (show) { + orderSummaryWrapper.classList.add("d-none"); + orderSummaryLoader.classList.remove("d-none"); + } else { + orderSummaryWrapper.classList.remove("d-none"); + orderSummaryLoader.classList.add("d-none"); + } +} + +function renderOverview() { + setOrderSummaryLoaderVisibility(true); + const cartData = getCart(); + if (utilites.array.isEmpty(cartData)) { + const noItemsAlert = doc.createElement("div"); + noItemsAlert.className = "alert alert-primary"; + noItemsAlert.innerHTML = `${strings.languageSpesific.the_shopping_bag_is_empty}, ${strings.languageSpesific.go_to} <a href='/produktar'>/produktar</a> ${strings.languageSpesific.to_add_LC}`; + orderSummaryWrapper.appendChild(noItemsAlert); + setOrderSummaryLoaderVisibility(false); + orderSummaryWrapper.querySelector("table").classList.add("d-none"); + total.classList.add("d-none"); + submitOrderWrapper.classList.add("d-none"); + return; + } + const tbody = orderSummaryWrapper.querySelector("tbody"); + tbody.innerHTML = ""; + for (const product of cartData) + tbody.appendChild(getProductTableRow(product)); + updateTotal(); + setOrderSummaryLoaderVisibility(false); +} + +function updateTotal() { + total.querySelector("#total-sum").innerText = priceTotal(); + total.querySelector("#total-tax").innerText = priceTax(); +} + +function getProductTableRow(product) { + const row = doc.createElement("tr"); + + const productCell = doc.createElement("td"); + productCell.innerText = product.name; + + const priceTotalCell = doc.createElement("td"); + priceTotalCell.innerText = (product.price * product.count).toFixed(2) + ",-"; + + const countCell = doc.createElement("td"); + + const counterControl = getCounterControl({ + initialCount: product.count, + min: "0", + max: product.maxCount, + onChange: (e) => { + const newCount = parseInt(e.target.value); + if (newCount === 0) { + removeProductFromCart(product.id, (updatedCart) => { + if (updatedCart.length >= 1) { + row.remove(); + updateTotal(); + } else { + renderOverview(); + } + }); + return; + } + setProductCount(product.id, newCount); + priceTotalCell.innerText = (product.price * newCount).toFixed(2) + ",-"; + updateTotal(); + }, + }); + counterControl.style.height = "30px"; + countCell.appendChild(counterControl); + + const priceCell = doc.createElement("td"); + priceCell.innerText = product.readablePrice; + + row.appendChild(productCell); + row.appendChild(countCell); + row.appendChild(priceCell); + row.appendChild(priceTotalCell); + + return row; +} + +function getOrderPayload() { + const payload = { + comment: inputComment.value, + contactInfo: { + name: inputName.value, + emailAddress: inputEmail.value, + phoneNumber: inputPhone.value, + }, + products: [], + }; + const cartData = getCart(); + if (utilites.array.isEmpty(cartData)) return undefined; + for (const item of cartData) { + if (payload.products.findIndex(c => c.id === item.id) !== -1) continue; + payload.products.push({ + id: item.id, + numberOfItems: item.count, + }); + } + return payload; +} + +function isSubmitOrderFormValid() { + return ( + inputName.value && + inputEmail.value && + inputPhone.value && + $("#vipps-terms-confirm").checked || $("#invoice-terms-confirm").checked + ); +} + +function submitOrderForm(type) { + if (isSubmitOrderFormValid()) { + const payload = getOrderPayload(); + if (payload === undefined) return; + payload.paymentType = type; + $("#submit-order-form").classList.add("loading"); + $("#form-errors").classList.add("d-none"); + $("#form-errors").innerHTML = ""; + submitOrder(payload).then(res => { + if (res.ok) { + res.text().then(continueTo => { + window.location.replace(continueTo.replaceAll("\"", "")); + }); + } else { + console.log(res.headers.get("Content-Type")); + if (res.headers.get("Content-Type")?.startsWith("application/json")) { + res.json().then(errorJson => { + if (errorJson.isValid === false) { + errorJson = utilites.resolveReferences(errorJson); + for (const error of errorJson.errors) { + if (error.id == null) { + const listItem = doc.createElement("li"); + if (error.errors.length === 1) { + listItem.innerText = error.errors[0]; + } else { + let html; + for (const itemError of error.errors) { + html += itemError + "<br>"; + } + listItem.innerHTML = html; + } + $("#form-errors").appendChild(listItem); + } else { + const cartproducts = getCart(); + const errorProduct = cartproducts.find(c => c.id === error.id); + const listItem = doc.createElement("li"); + if (error.errors.length === 1) { + listItem.innerHTML = `<b>${errorProduct.name}:</b> ${error.errors[0]}`; + } else { + let html = ""; + for (const itemError of error.errors) { + html += `<b>${errorProduct.name}:</b> ${itemError} <br>`; + } + listItem.innerHTML = html; + } + $("#form-errors").appendChild(listItem); + } + } + $("#form-errors").classList.remove("d-none"); + $("#submit-order-form").classList.remove("loading"); + } else { + utilites.handleError(res, { + title: strings.languageSpesific.an_unknown_error_occured, + message: strings.languageSpesific.try_again_soon, + }); + $("#submit-order-form").classList.remove("loading"); + } + }); + } else { + utilites.handleError(res, { + title: strings.languageSpesific.an_unknown_error_occured, + message: strings.languageSpesific.try_again_soon, + }); + $("#submit-order-form").classList.remove("loading"); + } + } + }) + .catch(err => { + console.error(err); + }); + } +} + +window.disposeCart = () => { + cartDispose(); + location.href = "/"; +}; + +function handleCallbackError() { + const urlParams = new URLSearchParams(window.location.search); + const error = urlParams.get("error"); + console.log(error); + switch (error) { + case "cancelled": + $("#order-alert").classList.remove("d-none"); + $("#order-alert").innerHTML = "Din bestilling er kansellert! <span class='btn btn-link' onclick='disposeCart()' title='Slett handlekorgen'>Klikk her for å slette handlekorgen</span>"; + break; + case "failed": + $("#order-alert").classList.remove("d-none"); + $("#order-alert").innerHTML = "Bestillingen feilet, venlegst prøv igjen eller <a href='/#kontakt'>ta kontakt</a> med oss hvis problemet vedvarer!"; + break; + } + urlParams.delete("error"); +} + +if (location.pathname.startsWith("/handlekorg")) { + pageInit(() => { + updatePricesFromApi().then(() => { + $(".submit-vipps").addEventListener("click", () => submitOrderForm(0)); + $(".submit-invoice").addEventListener("click", () => submitOrderForm(1)); + inputEmail.addEventListener("keyup", (e) => { + if (utilites.validators.isEmail(e.target.value)) { + invoiceMail.innerText = e.target.value; + } else { + invoiceMail.innerText = "din e-postadresse"; + } + }); + + renderOverview(); + }); + handleCallbackError(); + }); +} diff --git a/src/wwwroot/scripts/front/logginn.js b/src/wwwroot/scripts/front/logginn.js new file mode 100644 index 0000000..f15a323 --- /dev/null +++ b/src/wwwroot/scripts/front/logginn.js @@ -0,0 +1,92 @@ +import {$, pageInit} from "../base"; +import {utilites} from "../utilities"; +import {login} from "../api/account-api"; +import {strings} from "../i10n.ts"; + +const submitButton = $("#submit"); +const submitButtonSpinner = $("#submit .spinner-border"); +const errorContainer = $("#error"); +const errorMessage = $("#error-message"); +const errorTitle = $("#error-title"); +const continueTo = new URL(location.href).searchParams.get("ReturnUrl") + ? new URL(location.href).searchParams.get("ReturnUrl") + : "/kontoret"; +const email = $("#input-email"); +const password = $("#input-password"); +const persist = $("#persist-session"); +const loginForm = $("#login-form"); + +const loading = { + show() { + submitButton.classList.add("disabled"); + submitButtonSpinner.classList.remove("d-none"); + }, + hide() { + submitButton.classList.remove("disabled"); + submitButtonSpinner.classList.add("d-none"); + }, +}; + +const error = { + show(title = strings.languageSpesific.an_error_occured, message = strings.languageSpesific.try_again_soon) { + errorMessage.innerText = message; + errorTitle.innerText = title; + errorContainer.classList.remove("d-none"); + }, + hide() { + errorMessage.innerText = ""; + errorTitle.innerText = ""; + errorContainer.classList.add("d-none"); + }, +}; + +const form = { + submit() { + loading.show(); + error.hide(); + let payload = { + username: email.value, + password: password.value, + persist: persist.checked, + }; + login(payload, $("input[name=\"xsrf\"]").value).then((response) => { + if (response.status === 200) { + error.hide(); + location.href = continueTo; + } else { + utilites.handleError(response, { + title: strings.languageSpesific.an_unknown_error_occured, + message: strings.languageSpesific.try_again_soon, + }); + loading.hide(); + } + }).catch((err) => console.log(err)); + }, + isValid() { + email.removeAttribute("aria-invalid"); + password.removeAttribute("aria-invalid"); + if (!email.value || !utilites.validators.isEmail(email.value)) { + error.show(undefined, strings.languageSpesific.the_email_address + " " + strings.languageSpesific.is_invalid_LC); + email.setAttribute("aria-invalid", "true"); + return false; + } + if (!password.value) { + error.show(undefined, strings.languageSpesific.the_password + " " + strings.languageSpesific.is_invalid_LC); + password.setAttribute("aria-invalid", "true"); + return false; + } + return true; + }, +}; + +if (location.pathname.startsWith("/logginn")) { + pageInit(() => { + loginForm.addEventListener("submit", (e) => { + e.preventDefault(); + e.stopPropagation(); + if (form.isValid()) { + form.submit(); + } + }); + }); +} diff --git a/src/wwwroot/scripts/front/produkter.js b/src/wwwroot/scripts/front/produkter.js new file mode 100644 index 0000000..86a2afb --- /dev/null +++ b/src/wwwroot/scripts/front/produkter.js @@ -0,0 +1,341 @@ +import {validateOrderProducts} from "../api/order-api"; +import {$, $$, doc, pageInit, toaster} from "../base"; +import Modal from "bootstrap/js/dist/modal"; +import Carousel from "bootstrap/js/dist/carousel"; +import {utilites} from "../utilities"; +import { + addOrUpdateProduct, + cartDispose, + getCart, + priceTotal, + removeProductFromCart, + setProductCount, +} from "../cart-core"; +import {getCounterControl} from "../components"; +import {messages} from "../messages"; +import {strings} from "../i10n.ts"; + +const headerCartButton = $("#header-cart-button"); +const headerCartButtonCount = $("#header-cart-button #item-count"); +const cartViewRoot = $("#cart-modal #product-list"); +const submitCartButton = $("#cart-modal .submit-cart"); +const cartModalElement = $("#cart-modal"); +const cartModal = new Modal(cartModalElement, { + keyboard: false, +}); + +const isProductPage = location.pathname.startsWith("/produktar") && location.pathname.match(/\/./g).length === 3; +const isProductsGridPage = location.pathname.startsWith("/produktar") && location.pathname.match(/\/./g).length <= 2; + +if (!location.pathname.startsWith("/handlekorg")) { + pageInit(() => { + renderHeaderIcon(false); + renderProductButtons(); + cartModalElement.addEventListener("show.bs.modal", () => { + renderCartView(); + renderTotalView(); + }); + headerCartButton.addEventListener("click", () => cartModal.toggle()); + submitCartButton.addEventListener("click", () => submitCart()); + }); +} else { + headerCartButton.classList.add("d-none"); +} + +if (location.pathname.match("(\/produktar\/.*\/.*)") !== null && $("#carousel-navigator") !== null) { + const carousel = new Carousel($("#product-carousel")); + $$(".thumb-button").forEach(el => { + const index = el.dataset.thumbIndex; + el.onclick = () => carousel.to(index); + }); +} + +function dispose() { + cartDispose(); + renderHeaderIcon(); + renderCartView(); + renderTotalView(); + renderProductButtons(); +} + +function renderProductButtons() { + const cartData = getCart(); + if (isProductPage) { + const productWrapper = $("#single-product-wrapper"); + const productId = productWrapper.dataset.id; + const productIndex = utilites.array.isEmpty(cartData) ? -1 : cartData.findIndex(c => c.id === productId); + const bagButton = productWrapper.querySelector(".buttons .bag-button") ?? doc.createElement("dummy"); + const counterWrapper = productWrapper.querySelector(".buttons .counter-wrapper") ?? doc.createElement("dummy"); + bagButton.classList.remove("disabled"); + counterWrapper.innerHTML = ""; + + if (productIndex !== -1) { + const maxProductCount = bagButton.dataset.productMaxCount === "-1" ? 99999 : bagButton.dataset.productMaxCount; + const minProductCount = "0"; + const product = cartData[productIndex]; + bagButton.classList.add("d-none"); + counterWrapper.appendChild(getCounterControl({ + initialCount: product.count, + min: minProductCount, + max: maxProductCount, + onChange: (e) => { + let newCount = e.target.value; + if (newCount <= minProductCount) { + removeProduct(product.id); + bagButton.classList.remove("d-none"); + return; + } + + if (newCount > maxProductCount) newCount = maxProductCount; + setProductCount(product.id, parseInt(newCount)); + renderHeaderIcon(); + $$("[data-id='" + product.id + "'] .counter-wrapper").forEach(el => { + el.querySelector(".number-input input").value = parseInt(newCount); + }); + }, + })); + } else { + bagButton.innerHTML = "Legg i handlekorgen"; + bagButton.onclick = () => addProduct(productId); + bagButton.classList.remove("d-none"); + } + } else if (isProductsGridPage) { + $$(".product-card").forEach(productCard => { + const productId = productCard.dataset.id; + const bagButton = productCard.querySelector(".buttons .bag-button") ?? doc.createElement("dummy"); + const counterWrapper = productCard.querySelector(".buttons .counter-wrapper") ?? doc.createElement("dummy"); + const productIndex = utilites.array.isEmpty(cartData) ? -1 : cartData.findIndex(c => c.id === productId); + bagButton.classList.remove("disabled"); + counterWrapper.innerHTML = ""; + + if (productIndex !== -1) { + const maxProductCount = bagButton.dataset.productMaxCount === "-1" ? 99999 : bagButton.dataset.productMaxCount; + const minProductCount = "0"; + const product = cartData[productIndex]; + bagButton.innerHTML = "Legg i handlekorgen"; + bagButton.classList.add("d-none"); + counterWrapper.appendChild(getCounterControl({ + initialCount: product.count, + min: minProductCount, + max: maxProductCount, + onChange: (e) => { + let newCount = e.target.value; + if (newCount <= minProductCount) { + removeProduct(product.id); + bagButton.classList.remove("d-none"); + return; + } + if (newCount > maxProductCount) newCount = maxProductCount; + setProductCount(product.id, parseInt(newCount)); + renderHeaderIcon(); + $$("[data-id='" + product.id + "'] .counter-wrapper").forEach(el => { + el.querySelector(".number-input input").value = parseInt(newCount); + }); + + }, + })); + } else { + bagButton.innerHTML = "Legg i handlekorgen"; + bagButton.onclick = () => addProduct(productId); + bagButton.classList.remove("d-none"); + } + }); + } +} + +function getProductDataFromHtml(id) { + if (isProductsGridPage) { + const card = $(".product-card[data-id='" + id + "']"); + if (card === undefined) return card; + const bagButton = card.querySelector(".bag-button"); + return { + id: id, + name: card.querySelector(".card-title").innerText, + price: card.querySelector(".product-price").dataset.price, + readablePrice: card.querySelector(".product-price").innerText, + maxCount: bagButton.dataset.productMaxCount === "-1" ? 99999 : bagButton.dataset.productMaxCount, + }; + } else if (isProductPage) { + const productWrapper = $("#single-product-wrapper[data-id='" + id + "']"); + if (productWrapper === undefined) return productWrapper; + const bagButton = productWrapper.querySelector(".bag-button"); + return { + id: id, + name: productWrapper.querySelector(".title").innerText, + description: productWrapper.querySelector(".description").innerText, + price: productWrapper.querySelector(".product-price").dataset.price, + readablePrice: productWrapper.querySelector(".product-price").innerText, + maxCount: bagButton.dataset.productMaxCount === "-1" ? 99999 : bagButton.dataset.productMaxCount, + }; + } +} + +function addProduct(id) { + addOrUpdateProduct(getProductDataFromHtml(id), () => { + renderHeaderIcon(); + renderProductButtons(); + }); +} + +function removeProduct(id) { + removeProductFromCart(id, () => { + renderHeaderIcon(); + renderTotalView(); + renderCartView(); + renderProductButtons(); + }); +} + +function renderHeaderIcon() { + const cartData = getCart(); + if (utilites.array.isEmpty(cartData)) { + headerCartButtonCount.innerText = "0"; + return; + } + const currentCount = parseInt(headerCartButtonCount.innerText); + let count = 0; + for (const product of cartData) + count += product.count; + + if (currentCount !== count) { + headerCartButtonCount.innerText = count >= 1 ? count : "0"; + } +} + +function getProductListItemHTML(product) { + console.log(product); + const wrapper = doc.createElement("div"); + wrapper.dataset.id = product.id; + wrapper.className = "mb-3"; + + const card = doc.createElement("div"); + card.className = "card"; + + const cardHeaderWrapper = doc.createElement("div"); + cardHeaderWrapper.className = "card-header d-flex justify-content-between align-items-center"; + + const cardHeader = doc.createElement("h5"); + cardHeader.className = "mb-0"; + cardHeader.innerText = product.name; + cardHeaderWrapper.appendChild(cardHeader); + const removeProductButton = doc.createElement("button"); + removeProductButton.className = "btn btn-link shadow-none text-danger pe-0"; + removeProductButton.innerText = "Slett"; + removeProductButton.onclick = () => removeProduct(product.id); + cardHeaderWrapper.appendChild(removeProductButton); + + card.appendChild(cardHeaderWrapper); + const cardBody = doc.createElement("div"); + cardBody.className = "card-body"; + + const itemCountWrapper = doc.createElement("div"); + itemCountWrapper.className = "d-flex justify-content-between align-items-center"; + + itemCountWrapper.appendChild(getCounterControl({ + initialCount: product.count, + min: "0", + max: product.maxCount, + onChange: (e) => { + let newCount = e.target.value; + if (newCount <= "0") { + removeProduct(product.id); + return; + } + if (newCount > product.maxCount) newCount = product.maxCount; + setProductCount(product.id, parseInt(newCount)); + renderHeaderIcon(); + $$("[data-id='" + product.id + "'] .counter-wrapper").forEach(el => { + el.querySelector(".number-input input").value = newCount; + }); + renderTotalView(); + }, + })); + + const price = doc.createElement("div"); + price.className = "h3"; + price.innerText = product.readablePrice; + itemCountWrapper.appendChild(price); + + + cardBody.append(itemCountWrapper); + card.appendChild(cardBody); + wrapper.appendChild(card); + return wrapper; +} + +function renderTotalView() { + const cartTotal = priceTotal(); + if (cartTotal == null) { + $("#cart-modal .modal-footer").classList.add("d-none"); + $("#total").innerText = ""; + } else { + $("#cart-modal .modal-footer").classList.remove("d-none"); + $("#total").innerText = cartTotal; + } +} + +function renderCartView() { + const cartData = getCart(); + cartViewRoot.innerHTML = ""; + if (utilites.array.isEmpty(cartData)) { + const noItemsAlert = doc.createElement("div"); + noItemsAlert.className = "alert alert-primary"; + noItemsAlert.role = "alert"; + noItemsAlert.innerHTML = `${strings.languageSpesific.the_shopping_bag_is_empty}, ${strings.languageSpesific.go_to_LC} <a href='/produktar'>/produktar</a> ${strings.languageSpesific.to_add_LC}`; + cartViewRoot.appendChild(noItemsAlert); + } else { + for (const product of cartData) + cartViewRoot.appendChild(getProductListItemHTML(product)); + } +} + +function validateCartModal() { + return new Promise((greatSuccess, graveFailure) => { + const payload = { + products: [], + }; + + const cartData = getCart(); + if (utilites.array.isEmpty(cartData)) { + toaster.error(strings.languageSpesific.the_shopping_bag_is_empty); + return; + } + + for (const product of cartData) { + payload.products.push({ + id: product.id, + count: product.count, + }); + } + + validateOrderProducts(payload).then(res => { + if (res.ok) { + res.json().then(json => { + if (json.isValid) { + return greatSuccess(true); + } else { + toaster.error(strings.languageSpesific.invalid_form); + //TODO: show validation errors + return greatSuccess(false); + } + }); + } else { + utilites.handleError(res, { + title: strings.languageSpesific.could_not_validate_your_order, + message: strings.languageSpesific.try_again_soon, + }); + } + }).catch(err => { + console.error(err); + toaster.errorObj(messages.unknownError); + }); + }); +} + +function submitCart() { + validateCartModal().then(isValid => { + if (isValid === true) { + location.href = "/handlekorg"; + } + }); +} diff --git a/src/wwwroot/scripts/front/status.js b/src/wwwroot/scripts/front/status.js new file mode 100644 index 0000000..3addfac --- /dev/null +++ b/src/wwwroot/scripts/front/status.js @@ -0,0 +1,10 @@ +import {cartDispose} from "../cart-core"; +import {pageInit} from "../base"; + +if (location.pathname.startsWith("/status")) { + pageInit(() => { + const urlParams = new URLSearchParams(window.location.search); + const clearCart = urlParams.get("clearCart"); + if (clearCart) cartDispose(); + }); +} |
