diff --git a/www/assets/css/app.css b/www/assets/css/app.css index 92b472e..6632927 100644 --- a/www/assets/css/app.css +++ b/www/assets/css/app.css @@ -109,6 +109,10 @@ Framework7 and FontAwesome both have a .fab class display: none; } +.homepage-card { + height: calc(100% - calc(var(--f7-card-margin-vertical) * 2)); +} + .no-animation * { -webkit-transition: 10ms !important; -moz-transition: 10ms !important; @@ -191,10 +195,30 @@ Framework7 and FontAwesome both have a .fab class border-radius: 10px; } -.card.pointercursor { +.pointercursor { cursor: pointer; } +.card-expandable .card-closed-display-none { + display: none; +} +.card-expandable.card-opened .card-opened-display-none { + display: none; +} +.card-expandable.card-opening .card-opened-display-none { + display: none; +} +.card-expandable.card-opened .card-closed-display-none { + display: initial; +} +.card-expandable.card-opening .card-closed-display-none { + display: initial; +} + +.card-expandable.card-opened .card-content, .card-expandable.card-opening .card-content { + padding-top: calc(var(--f7-navbar-height) * 1.5); +} + .maplibregl-popup-content { color: var(--f7-text-color); background-color: var(--f7-page-bg-color); diff --git a/www/assets/js/main.js b/www/assets/js/main.js index 16f69d7..bb5290d 100644 --- a/www/assets/js/main.js +++ b/www/assets/js/main.js @@ -14,7 +14,8 @@ var app = new Framework7({ id: "com.netsyms.helenaexpress.app", theme: "auto", card: { - swipeToClose: false + swipeToClose: false, + hideNavbarOnOpen: false, }, popup: { backdrop: true diff --git a/www/assets/js/shop.js b/www/assets/js/shop.js new file mode 100644 index 0000000..e7d745a --- /dev/null +++ b/www/assets/js/shop.js @@ -0,0 +1,217 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +var shopitems = []; +var shopitems_flattened = []; +var samedaydeliveryfee = 10.0; + +function loadShopPage( {resolve, reject}) { + app.dialog.preloader("Opening the shop..."); + apirequest(SETTINGS.apis.shopitems, [], function (resp) { + app.dialog.close(); + shopitems = resp.items; + samedaydeliveryfee = resp.samedaydeliveryfee; + for (var cat in shopitems) { + for (var i in shopitems[cat]["items"]) { + shopitems_flattened[i] = shopitems[cat]["items"][i]; + } + } + resolve({ + content: compiledPages.shop({ + items: shopitems + }) + }, {}); + }, function (error) { + app.dialog.close(); + app.dialog.alert("Couldn't open the shop right now. Try again later.", "Whoops!"); + reject(); + }); +} + +$("body").on("card:opened", ".shop-item-card", function () { + app.swiper.destroy("#swiper-" + $(this).data("sku")); + app.swiper.create("#swiper-" + $(this).data("sku"), { + pagination: { + el: "#swiper-pagination-" + $(this).data("sku"), + type: "bullets" + } + }); +}); + +var shoppingcart = {}; + +if (inStorage("shoppingcart")) { + shoppingcart = JSON.parse(getStorage("shoppingcart")); +} + +function addToCart(sku, qty) { + if (typeof qty == 'undefined') { + qty = 1; + } + if (shoppingcart[sku]) { + shoppingcart[sku] += qty; + } else { + shoppingcart[sku] = qty; + } + app.toast.show({ + icon: "", + text: "Added to cart!", + position: "center", + horizontalPosition: "center", + closeTimeout: 2000, + destroyOnClose: true + }); + updateCart(); +} + +function removeFromCart(sku, qty) { + // If no qty set, delete them all + if (typeof qty == "undefined") { + qty = 99999; + } + if (shoppingcart[sku]) { + shoppingcart[sku] -= qty; + } + if (shoppingcart[sku] <= 0) { + delete shoppingcart[sku]; + } + updateCart(); +} + +function emptyShoppingCart() { + shoppingcart = {}; + updateCart(); +} + +function updateCart() { + setStorage("shoppingcart", JSON.stringify(shoppingcart)); + var totalitems = 0; + for (var sku in shoppingcart) { + totalitems += shoppingcart[sku]; + } + $("#shopping-cart-items-chip-label").text(totalitems + ""); + + var cartitems = []; + for (var sku in shoppingcart) { + var item = shopitems_flattened[sku]; + item.qty = shoppingcart[sku]; + item.linetotal = (item.qty * item.price).toFixed(2); + cartitems.push(item); + } + + $("#shoppingCartContainer").html(compiledPages.shoppingcart_fragment({ + cartitems: cartitems + })); +} + +function openShopCheckout() { + // Check if order is mailable or not by checking each item for mailability + // Also check if all items are still in stock (cart could have sat around a while) + var ordermailable = true; + var orderinstock = true; + var ordertotal = 0.0; + var outofstockitems = []; + for (var sku in shoppingcart) { + if (shopitems_flattened[sku].mailable != true) { + ordermailable = false; + } + if (shopitems_flattened[sku].instock != true) { + orderinstock = false; + outofstockitems.push(shopitems_flattened[sku].name); + } + ordertotal += (shopitems_flattened[sku].price * shoppingcart[sku]); + } + + if (!orderinstock) { + if (outofstockitems.length == 1) { + app.dialog.alert("The following item is out of stock. Remove it from your cart to complete your order.

" + outofstockitems[0], "Out of Stock!"); + } else { + app.dialog.alert("The following items in your cart are out of stock. Remove them from your cart to complete your order.

" + outofstockitems.join("
"), "Out of Stock!"); + } + return; + } + + if (ordermailable) { + app.dialog.create({ + title: 'Checkout', + text: "Your order can be either mailed to you (1-5 days) or delivered same-day. Either way, it will be sent to your account's address. Your saved payment method will be charged for $" + ordertotal.toFixed(2) + " plus any delivery fee when your order ships.", + buttons: [ + { + text: 'Mail (free)' + }, + { + text: 'Same-day delivery ($' + samedaydeliveryfee.toFixed(2) + ')' + }, + { + text: 'Cancel Checkout', + color: "red" + } + ], + verticalButtons: true, + onClick: function (dialog, index) { + switch (index) { + case 0: + placeOrder("mail", ordertotal); + break; + case 1: + placeOrder("courier", ordertotal + samedaydeliveryfee); + break; + } + } + }).open(); + } else { + app.dialog.create({ + title: 'Checkout', + text: "Your order will be delivered to your account's address. Your saved payment method will be charged for $" + (ordertotal + samedaydeliveryfee).toFixed(2) + " (including delivery fee) when your order ships.", + buttons: [ + { + text: 'Confirm Order' + }, + { + text: 'Cancel Checkout', + color: "red" + } + ], + verticalButtons: true, + onClick: function (dialog, index) { + switch (index) { + case 0: + placeOrder("courier", ordertotal + samedaydeliveryfee); + break; + case 1: + break; + } + } + }).open(); + } +} + +function placeOrder(deliverymethod, ordertotal) { + app.dialog.preloader("Placing order..."); + apirequest(SETTINGS.apis.shopbuy, { + cart: JSON.stringify(shoppingcart), + shipmethod: deliverymethod, + total: (ordertotal * 1.0).toFixed(2), + accountnumber: getStorage("accountnumber"), + accountkey: getStorage("accountkey") + }, function (resp) { + app.dialog.close(); + if (resp.status == "ERROR") { + app.dialog.alert(resp.msg, "Error"); + return; + } else { + emptyShoppingCart(); + app.dialog.alert("Your order has been received.", "Order placed!"); + app.popup.close(); + return; + } + }, function (error) { + app.dialog.close(); + app.dialog.alert("Your order might not have gone through due to a network error. If you don't get a confirmation email, try again.", "Whoops!"); + }) + console.log(deliverymethod); + console.log(shoppingcart); +} \ No newline at end of file diff --git a/www/index.html b/www/index.html index 1c66926..5826f12 100644 --- a/www/index.html +++ b/www/index.html @@ -59,6 +59,7 @@ + diff --git a/www/pages/shop.html b/www/pages/shop.html new file mode 100644 index 0000000..1fa7839 --- /dev/null +++ b/www/pages/shop.html @@ -0,0 +1,117 @@ + + +
+ + + + +
+
+
+
+ + {{#each items}} +
+
{{category_name}}
+
+ {{#each items}} +
+
+
+
+
{{name}}
+ + {{#js_if "this.price == '0.00'"}}Free{{else}}${{price}}{{/js_if}} + {{#if instock}}{{else}} · Out of Stock{{/if}} + +
+ + + +
+ {{#js_if "this.images.length > 0"}} +
+ +
+ {{/js_if}} +
+ {{#if instock}} +
+ Add to Cart +
+ {{else}} +
+ Out of Stock +
+ {{/if}} + {{#js_if "this.images.length > 0"}} +
+
+ {{#each images}} +
+ +
+ {{/each}} +
+
+
+
+ {{/js_if}} + {{description}} +
+
+
+
+ {{/each}} + {{/each}} +
+
+
+
+ + +
\ No newline at end of file diff --git a/www/pages/shoppingcart_fragment.html b/www/pages/shoppingcart_fragment.html new file mode 100644 index 0000000..3c74a52 --- /dev/null +++ b/www/pages/shoppingcart_fragment.html @@ -0,0 +1,37 @@ +{{#js_if "this.cartitems.length > 0"}} +
+
+ Go to Checkout +
+
+
+ +
+{{else}} +
+ +

+ Your cart is empty! +
+{{/js_if}} \ No newline at end of file diff --git a/www/routes.js b/www/routes.js index a86f75b..9a58520 100644 --- a/www/routes.js +++ b/www/routes.js @@ -12,6 +12,8 @@ var pagesToCompile = [ "appointment", "dropandsend", "pickup", + "shop", + "shoppingcart_fragment", "rateresult", "account", "trackresult", @@ -71,22 +73,28 @@ var routes = [ }, { title: "Notarize a Document", - href: "/appointment/21", + href: "/appointment/notary", icon: "fad fa-file-signature", text: "Book a mobile notary visit." }, { - title: "My Account", - href: "/account", - icon: "fad fa-user-circle", - text: "Manage your Helena Express account." - }, - { - title: "Send a Telegram", + title: "Write a Telegram", href: "/telegram", icon: "fad fa-typewriter", text: "Send a hand-delivered telegram anywhere in the Helena area." - } + }, + { + title: "Shop for Supplies", + href: "/shop", + icon: "fad fa-shopping-cart", + text: "Get boxes, labels, and shipping supplies delivered to your door." + }, + { + title: "Manage My Account", + href: "/account", + icon: "fad fa-user-circle", + text: "Get account number, check rewards points, update payment method, and more." + }, ] }) }, {}); @@ -100,20 +108,20 @@ var routes = [ content: compiledPages.send({ pages: [ { - title: "Package Pickup", + title: "Use a Drop Box", + href: "/dropandsend", + icon: "fad fa-box-alt", + text: "Bring your package to a Drop and Send location and we'll ship it for you. No postage or appointment needed." + }, + { + title: "Request a Pickup", href: "/pickup", icon: "fad fa-home", text: "Leave your package on your porch and we'll pick it up and ship it for you. No postage or appointment needed." }, { - title: "Drop and Send", - href: "/dropandsend", - icon: "fad fa-box-alt", - text: "Bring your package to a secure drop location and we'll ship it for you. No postage or appointment needed." - }, - { - title: "Book Appointment", - href: "/appointment/19", + title: "Book an Appointment", + href: "/appointment/shipping", icon: "fad fa-calendar-alt", text: "A courier will come to you on your schedule. No account required." } @@ -133,13 +141,13 @@ var routes = [ title: "Package Pickup", text: "A courier will come to you and ship your mail, packages, etc.", icon: "fad fa-hand-holding-box", - serviceid: 19 + serviceid: "shipping" }, { title: "Mobile Notary", text: "A notary public will come to you and notarize your documents.", icon: "fad fa-file-signature", - serviceid: 21 + serviceid: "notary" } ] }) @@ -150,13 +158,13 @@ var routes = [ { path: "/:serviceId", async: function ( { resolve, reject, to }) { - var url = SETTINGS.appointmenturl; - if ($("#app").hasClass("theme-dark")) { - url = SETTINGS.appointmenturl_darkmode; + var url = SETTINGS.apis.appointmentredirect + "?service=" + to.params.serviceId; + if ($("body").hasClass("theme-dark")) { + url += "&darkmode=1"; } resolve({ content: compiledPages.appointment({ - url: url + "&service=" + to.params.serviceId + url: url }) }, { }); @@ -206,6 +214,23 @@ var routes = [ } } }, + { + path: '/shop', + name: 'shop', + async: loadShopPage, + on: { + pageBeforeIn: function () { + checkIfAccountGoodWithPaymentMethod(function (ok) { + if (!ok) { + $("#addPaymentMethodNag").css("display", ""); + } + }, function (error) { + $("#addPaymentMethodNag").css("display", ""); + }); + updateCart(); + } + } + }, { path: '/telegram', name: 'telegram', diff --git a/www/settings.js b/www/settings.js index 8c72284..94501d0 100644 --- a/www/settings.js +++ b/www/settings.js @@ -8,6 +8,7 @@ var SETTINGS = { apis: { track: "http://localhost/helena.express/apis/track/", rates: "http://localhost/helena.express/apis/rates/", + appointmentredirect: "http://localhost/helena.express/apis/appointmentredirect/", requestpickup: "http://localhost/helena.express/apis/requestpickup/", dropandsendlocations: "http://localhost/helena.express/apis/dropandsend/locations/", dropandsendpickup: "http://localhost/helena.express/apis/dropandsend/requestpickup/", @@ -17,11 +18,11 @@ var SETTINGS = { accountregister: "http://localhost/helena.express/apis/account/register/", redirecttopaymentsetup: "http://localhost/helena.express/apis/account/redirecttopaymentsetup/", finishpaymentsetup: "http://localhost/helena.express/apis/account/finishpaymentsetup/", - sendtelegram: "http://localhost/helena.express/apis/telegram" + sendtelegram: "http://localhost/helena.express/apis/telegram", + shopitems: "http://localhost/helena.express/apis/shop/items", + shopbuy: "http://localhost/helena.express/apis/shop/buy" }, stripe_pubkey: "pk_test_51J6qFXCa1Fboir5UzPO3LCiMsVNiFP2lq4wR0dEcjJJVzAaJ3uRggDekZPB3qeYpMD3ayIYHKyD5sSn0IFLlEXMW001LqrvGSH", - appointmenturl: "https://appointments.netsyms.com/index.php?hlnexp=1&embed=1&only=1&theme=darkly", - appointmenturl_darkmode: "https://appointments.netsyms.com/index.php?hlnexp=1&embed=1&only=1&theme=darkly", branding: { apptitle: "Helena Express", company: "Helena Express",