master
Skylar Ittner 2 years ago
parent d4309871e3
commit c35de6ff64

@ -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);

@ -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

@ -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: "<i class='far fa-cart-plus fa-2x'></i>",
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.<br><br>" + 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.<br><br>" + outofstockitems.join("<br>"), "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);
}

@ -59,6 +59,7 @@
<script src="assets/js/pickup.js"></script>
<script src="assets/js/rates.js"></script>
<script src="assets/js/account.js"></script>
<script src="assets/js/shop.js"></script>
<script src="routes.js"></script>
<script src="assets/js/main.js"></script>

@ -0,0 +1,117 @@
<!-- 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/. -->
<div class="page" data-name="shop">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="left">
<a class="link back hapticbtn" href="#">
<i class="icon icon-back"></i>
<span class="if-not-md">Back</span>
</a>
</div>
<div class="title">Shop</div>
<div class="right">
<a href="#" class="link hapticbtn popup-open" data-popup="#shoppingCartPopup">
<span class="chip"><span class="chip-label" id="shopping-cart-items-chip-label">0</span></span>
<i class="far fa-shopping-cart"></i>
<span>Cart</span>
</a>
</div>
</div>
</div>
<div class="page-content noselect">
<div class="row justify-content-center margin-top">
<div class="col-100 medium-90 xlarge-75">
<div class="row justify-content-left">
<div class="col-100 no-margin" id="addPaymentMethodNag" style="display: none;">
<div class="card">
<div class="card-content text-align-center padding-half">
<i class="fad fa-exclamation-circle fa-2x text-color-orange"></i><br>
You need an account with a linked credit card to use the shop. <span class="taptext">Tap</span><span class="clicktext">Click</span> the button to update your account.
<a class="button hapticbtn button-fill margin" href="/account"><i class="fas fa-user-circle"></i> My Account</a>
</div>
</div>
</div>
{{#each items}}
<div class="col-100">
<div class="block-title no-margin-bottom margin-half-top">{{category_name}}</div>
</div>
{{#each items}}
<div class="col-100 small-50 large-33 no-margin">
<div class="card card-expandable card-expandable-animate-width margin-half hapticbtn shop-item-card" id="shop-item-card-{{sku}}" data-sku="{{sku}}">
<div class="card-content card-content-padding">
<div class="pointercursor card-close">
<div class="pointercursor" style="max-width: calc(100% - 2.5em);">{{name}}<br />
<small style="opacity: 0.7">
{{#js_if "this.price == '0.00'"}}Free{{else}}${{price}}{{/js_if}}
{{#if instock}}{{else}} &middot; Out of Stock{{/if}}
</small>
</div>
<a href="#" class="link card-opened-fade-in pointercursor"
style="position: absolute; right: 2em; top: calc(var(--f7-navbar-height) * 1.5);">
<i class="fas fa-times"></i>
</a>
</div>
{{#js_if "this.images.length > 0"}}
<div style="text-align: center;" class="card-opened-fade-out card-opened-display-none">
<img src="{{js 'this.images[0]'}}" style="max-width: 100%; max-height: 200px;"/>
</div>
{{/js_if}}
<div class="card-opened-fade-in card-closed-display-none">
{{#if instock}}
<div class="button button-fill margin-top card-close" onclick="addToCart('{{sku}}', 1)">
<i class="far fa-cart-plus"></i> Add to Cart
</div>
{{else}}
<div class="button button-fill margin-top color-gray color-theme-gray">
Out of Stock
</div>
{{/if}}
{{#js_if "this.images.length > 0"}}
<div class="swiper-container shop-images-swiper" data-sku="{{sku}}" id="swiper-{{sku}}">
<div class="swiper-wrapper">
{{#each images}}
<div class="swiper-slide" style="display: flex; justify-content: center; align-items: center;">
<img src="{{this}}" style="max-height: 400px; max-width: 90%;"/>
</div>
{{/each}}
</div>
<div id="swiper-pagination-{{sku}}" class="swiper-pagination"></div>
</div>
<br />
{{/js_if}}
{{description}}
</div>
</div>
</div>
</div>
{{/each}}
{{/each}}
</div>
</div>
</div>
</div>
<div class="popup" id="shoppingCartPopup">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="left">
<a class="link popup-close" href="#">
<i class="icon icon-back"></i>
<span class="if-not-md">Close</span>
</a>
</div>
<div class="title">Cart</div>
</div>
</div>
<div id="shoppingCartContainer">
</div>
</div>
</div>

@ -0,0 +1,37 @@
{{#js_if "this.cartitems.length > 0"}}
<div class="block">
<div class="button button-fill" onclick="openShopCheckout()">
<i class="far fa-cash-register"></i> Go to Checkout
</div>
</div>
<div class="list media-list">
<ul>
{{#each cartitems}}
<li>
<div class="item-content">
<div class="item-media popup-close" onclick="app.card.open('#shop-item-card-{{sku}}');">
{{#js_if "this.images.length > 0"}}
<img src="{{js 'this.images[0]'}}" style="width: 80px;" />
{{else}}
<div style="width: 80px; height: 60px;"></div>
{{/js_if}}</div>
<div class="item-inner">
<div class="item-title-row">
<div class="item-title">{{name}}</div>
<div class="item-after">{{#js_if "this.linetotal == '0.00'"}}Free{{else}}${{linetotal}}{{/js_if}}</div>
</div>
<div class="item-subtitle">{{#js_if "this.linetotal == '0.00'"}}Free{{else}}${{price}}{{/js_if}} x {{qty}} <div class="button button-small button-outline display-inline-block" onclick='removeFromCart("{{sku}}")'><i class="fas fa-minus-circle"></i> Remove</div></div>
<div class="item-text">{{description}}</div>
</div>
</div>
</li>
{{/each}}
</ul>
</div>
{{else}}
<div class="block text-align-center">
<i class="far fa-shopping-cart fa-4x"></i>
<br /><br />
Your cart is empty!
</div>
{{/js_if}}

@ -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',

@ -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",

Loading…
Cancel
Save