diff --git a/package-lock.json b/package-lock.json index b812e3d..42f0c4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7244,6 +7244,11 @@ } } }, + "object-hash": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.1.tgz", + "integrity": "sha512-HgcGMooY4JC2PBt9sdUdJ6PMzpin+YtY3r/7wg0uTifP+HJWW8rammseSEHuyt0UeShI183UGssCJqm1bJR7QA==" + }, "object-is": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", diff --git a/package.json b/package.json index db76a4a..29cf0ff 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "node-cron": "^2.0.3", "nodemailer": "^4.7.0", "numeral": "^2.0.6", + "object-hash": "^2.0.1", "paypal-rest-sdk": "^1.6.9", "rand-token": "^0.4.0", "rimraf": "^2.7.1", diff --git a/public/javascripts/expressCart.js b/public/javascripts/expressCart.js index 1844a63..821e68a 100644 --- a/public/javascripts/expressCart.js +++ b/public/javascripts/expressCart.js @@ -377,15 +377,17 @@ $(document).ready(function (){ } }); + // On empty cart click $(document).on('click', '#empty-cart', function(e){ - $.ajax({ - method: 'POST', - url: '/product/emptycart' - }) - .done(function(msg){ - showNotification(msg.message, 'success', true); - updateCartDiv(); - }); + $('#confirmModal').modal('show'); + $('#buttonConfirm').attr('data-func', 'emptyCart'); + }); + + $(document).on('click', '#buttonConfirm', function(e){ + // Get the function and run it + var func = $(e.target).attr('data-func'); + window[func](); + $('#confirmModal').modal('hide'); }); $('.qty-btn-minus').on('click', function(){ @@ -449,7 +451,9 @@ function deleteFromCart(element){ $.ajax({ method: 'POST', url: '/product/removefromcart', - data: { productId: element.attr('data-id') } + data: { + cartId: element.attr('data-cartid') + } }) .done(function(msg){ showNotification(msg.message, 'success'); @@ -476,6 +480,7 @@ function updateCart(element){ method: 'POST', url: '/product/updatecart', data: { + cartId: element.attr('data-cartid'), productId: element.attr('data-id'), quantity: element.val() } @@ -596,14 +601,14 @@ function updateCartDiv(){
- +
- +
${result.currencySymbol}${productTotalAmount} @@ -663,3 +668,15 @@ function upperFirst(value){ return chr.toUpperCase(); }); } + +// eslint-disable-next-line no-unused-vars +function emptyCart(){ + $.ajax({ + method: 'POST', + url: '/product/emptycart' + }) + .done(function(msg){ + showNotification(msg.message, 'success', true); + updateCartDiv(); + }); +} diff --git a/public/javascripts/expressCart.min.js b/public/javascripts/expressCart.min.js index 68710bf..6b3e62f 100644 --- a/public/javascripts/expressCart.min.js +++ b/public/javascripts/expressCart.min.js @@ -1 +1 @@ -function checkMaxQuantity(t,a){if($("#maxQuantity").length){if(46===t.keyCode||8===t.keyCode)return;if(parseInt($(t.target).val())>parseInt($("#maxQuantity").val())){const e=a.val();t.preventDefault(),a.val(e.slice(0,-1)),showNotification(`Exceeds maximum quantity: ${$("#maxQuantity").val()}`,"warning",!1)}}}function deleteFromCart(t){$.ajax({method:"POST",url:"/product/removefromcart",data:{productId:t.attr("data-id")}}).done(function(t){showNotification(t.message,"success"),updateCartDiv()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function cartUpdate(t){$(t).val()>0?""!==$(t).val()&&updateCart(t):$(t).val(1)}function updateCart(t){$.ajax({method:"POST",url:"/product/updatecart",data:{productId:t.attr("data-id"),quantity:t.val()}}).done(function(t){updateCartDiv()}).fail(function(t){showNotification(t.responseJSON.message,"danger",!0)})}function getSelectedOptions(){var t={};return $(".product-opt").each(function(){var a=$(this).val().trim(),e=$(this).attr("data-label"),n=$(this).attr("name"),o=$(this).attr("type");o||(t[n]={label:e,name:n,value:a}),"radio"===o&&(t[n]={label:e,name:n,value:$('input[name="'+n+'"]:checked').val()}),"checkbox"===o&&(t[n]={label:e,name:n,value:$('input[name="'+$(this).attr("name")+'"]').is(":checked")})}),t}function updateCartDiv(){$.ajax({method:"GET",url:"/checkout/cartdata"}).done(function(t){var a=t.cart,e=t.session,n="",o=numeral(e.totalCartAmount).format("0.00"),i=numeral(e.totalCartShipping).format("0.00"),r=`${e.shippingMessage} :${t.currencySymbol}${i}`;0===e.totalCartShipping&&(r=`${e.shippingMessage}`);var c=numeral(e.totalCartDiscount).format("0.00"),s="";e.totalCartDiscount>0&&(s=`\n
\n Discount: ${t.currencySymbol}${c}\n
`),a?($("#cart-empty").empty(),Object.keys(a).forEach(function(e){var o=a[e],i=numeral(o.totalItemPrice).format("0.00"),r="",c=1;Object.keys(o.options).forEach(function(t){var a=o.options[t];c===Object.keys(o.options).length?r+=`${upperFirst(a.name)}: ${a.value}`:r+=`${upperFirst(a.name)}: ${a.value} / `,c++});var s=`${o.title} product image`;o.productImage&&(s=`${o.title} product image`),n+=`\n
\n
\n
\n
\n ${s}\n
\n
\n
\n
\n
${o.title}
\n ${r}\n
\n
\n
\n
\n \n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n ${t.currencySymbol}${i}\n
\n
\n
\n
\n
\n
`}),$(".cartBodyWrapper").html(n)):$(".cartBodyWrapper").html(""),$("#cart-count").text(e.totalCartItems);var d=`\n
\n
\n
\n ${r}\n
\n ${s}\n
\n Total:\n ${t.currencySymbol}${o}\n
\n
\n
`;a?($(".cartTotalsWrapper").html(d),$(".cart-buttons").removeClass("d-none")):($(".cartTotalsWrapper").html('\n
\n
t.toUpperCase())}$(document).ready(function(){if($(window).width()<768&&($(".menu-side").on("click",function(t){t.preventDefault(),$('.menu-side li:not(".active")').slideToggle()}),$('.menu-side li:not(".active")').hide(),$(".menu-side>.active").html(''),$(".menu-side>.active").addClass("menu-side-mobile"),0===$("#navbar ul li").length&&$("#navbar").hide(),$("#offcanvasClose").hide()),$("#userSetupForm").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/admin/setup_action",data:{usersName:$("#usersName").val(),userEmail:$("#userEmail").val(),userPassword:$("#userPassword").val()}}).done(function(t){showNotification(t.message,"success",!1,"/admin/login")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}))}),$(document).on("click",".menu-btn",function(t){t.preventDefault(),$("body").addClass("pushy-open-right")}),$("table").each(function(){$(this).addClass("table table-hover")}),$("#productTags").length&&$("#productTags").tokenfield(),$(document).on("click",".dashboard_list",function(t){window.document.location=$(this).attr("href")}).hover(function(){$(this).toggleClass("hover")}),$(document).on("click",".btn-qty-minus",function(t){t.preventDefault();var a=$(t.target).parent().parent().find(".cart-product-quantity");$(a).val(parseInt(a.val())-1),cartUpdate(a)}),$(document).on("click",".btn-qty-add",function(t){t.preventDefault();var a=$(t.target).parent().parent().find(".cart-product-quantity");$(a).val(parseInt(a.val())+1),cartUpdate(a)}),$(document).on("click",".btn-delete-from-cart",function(t){deleteFromCart($(t.target))}),$("#pager").length){var t=$("#pageNum").val(),a=$("#productsPerPage").val(),e=$("#totalProductCount").val(),n=$("#paginateUrl").val(),o=$("#searchTerm").val();""!==o&&(o+="/");var i="/"+n+"/"+o+"{{number}}",r=Math.ceil(e/a);parseInt(e)>parseInt(a)&&($("#pager").bootpag({total:r,page:t,maxVisible:5,href:i,wrapClass:"pagination",prevClass:"page-item previous",nextClass:"page-item next",activeClass:"page-item active"}),$("#pager a").each(function(){$(this).addClass("page-link")}))}if($("#customerLogout").on("click",function(t){$.ajax({method:"POST",url:"/customer/logout",data:{}}).done(function(t){location.reload()})}),$("#customerForgotten").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/customer/forgotten_action",data:{email:$("#email").val()}}).done(function(t){showNotification(t.message,"success")}).fail(function(t){t.message?showNotification(t.responseJSON.message,"danger"):showNotification(t.responseText,"danger")}))}),$(document).on("click","#createAccountCheckbox",function(t){$("#newCustomerPassword").prop("required",$("#createAccountCheckbox").prop("checked"))}),$("#checkoutInformation").validator().on("click",function(t){if(t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length){var a="/customer/save";$("#createAccountCheckbox").prop("checked")&&(a="/customer/create"),$.ajax({method:"POST",url:a,data:{email:$("#shipEmail").val(),firstName:$("#shipFirstname").val(),lastName:$("#shipLastname").val(),address1:$("#shipAddr1").val(),address2:$("#shipAddr2").val(),country:$("#shipCountry").val(),state:$("#shipState").val(),postcode:$("#shipPostcode").val(),phone:$("#shipPhoneNumber").val(),password:$("#newCustomerPassword").val(),orderComment:$("#orderComment").val()}}).done(function(){window.location="/checkout/shipping"}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}}),$("#addDiscountCode").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/checkout/adddiscountcode",data:{discountCode:$("#discountCode").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#removeDiscountCode").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/checkout/removediscountcode",data:{}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#loginForm").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/admin/login_action",data:{email:$("#email").val(),password:$("#password").val()}}).done(function(t){window.location="/admin"}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$("#customerLogin").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/customer/login_action",data:{loginEmail:$("#customerLoginEmail").val(),loginPassword:$("#customerLoginPassword").val()}}).done(function(t){var a=t.customer;$("#shipEmail").val(a.email),$("#shipFirstname").val(a.firstName),$("#shipLastname").val(a.lastName),$("#shipAddr1").val(a.address1),$("#shipAddr2").val(a.address2),$("#shipCountry").val(a.country),$("#shipState").val(a.state),$("#shipPostcode").val(a.postcode),$("#shipPhoneNumber").val(a.phone),location.reload()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$(document).on("click",".image-next",function(t){var a=$(".thumbnail-image"),e=0,n=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(n=e+1===a.length||e+1<0?0:e+1),e++}),$("#product-title-image").attr("src",$(a).eq(n).attr("src"))}),$(document).on("click",".image-prev",function(t){var a=$(".thumbnail-image"),e=0,n=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(n=e-1===a.length||e-1<0?a.length-1:e-1),e++}),$("#product-title-image").attr("src",$(a).eq(n).attr("src"))}),$(document).on("click",".product-add-to-cart",function(t){var a=getSelectedOptions();parseInt($("#product_quantity").val())<1&&$("#product_quantity").val(1),$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$("#productId").val(),productQuantity:$("#product_quantity").val(),productOptions:JSON.stringify(a),productComment:$("#product_comment").val()}}).done(function(t){showNotification(t.message,"success"),updateCartDiv()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#product_quantity").on("keyup",function(t){checkMaxQuantity(t,$("#product_quantity"))}),$(".cart-product-quantity").on("keyup",function(t){checkMaxQuantity(t,$(".cart-product-quantity"))}),$(".cart-product-quantity").on("focusout",function(t){cartUpdate($(t.target))}),$(document).on("click",".pushy-link",function(t){$("body").removeClass("pushy-open-right")}),$(document).on("click",".add-to-cart",function(t){var a="/product/"+$(this).attr("data-id");$(this).attr("data-link")&&(a="/product/"+$(this).attr("data-link")),"true"===$(this).attr("data-has-options")?window.location=a:$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success"),updateCartDiv()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#empty-cart",function(t){$.ajax({method:"POST",url:"/product/emptycart"}).done(function(t){showNotification(t.message,"success",!0),updateCartDiv()})}),$(".qty-btn-minus").on("click",function(){var t=parseInt($("#product_quantity").val())-1;$("#product_quantity").val(t>0?t:1)}),$(".qty-btn-plus").on("click",function(){$("#product_quantity").val(parseInt($("#product_quantity").val())+1)}),$(".thumbnail-image").on("click",function(){$("#product-title-image").attr("src",$(this).attr("src"))}),$(document).on("click","#btn_search_reset",function(t){window.location.replace("/")}),$(document).on("click","#btn_search",function(t){t.preventDefault(),""===$("#frm_search").val().trim()?showNotification("Please enter a search value","danger"):window.location.href="/search/"+$("#frm_search").val()}),""!==$("#input_notify_message").val()){var c=$("#input_notify_message").val(),s=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(c,s||"danger",!1)}}); \ No newline at end of file +function checkMaxQuantity(t,a){if($("#maxQuantity").length){if(46===t.keyCode||8===t.keyCode)return;if(parseInt($(t.target).val())>parseInt($("#maxQuantity").val())){const n=a.val();t.preventDefault(),a.val(n.slice(0,-1)),showNotification(`Exceeds maximum quantity: ${$("#maxQuantity").val()}`,"warning",!1)}}}function deleteFromCart(t){$.ajax({method:"POST",url:"/product/removefromcart",data:{cartId:t.attr("data-cartid")}}).done(function(t){showNotification(t.message,"success"),updateCartDiv()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function cartUpdate(t){$(t).val()>0?""!==$(t).val()&&updateCart(t):$(t).val(1)}function updateCart(t){$.ajax({method:"POST",url:"/product/updatecart",data:{cartId:t.attr("data-cartid"),productId:t.attr("data-id"),quantity:t.val()}}).done(function(t){updateCartDiv()}).fail(function(t){showNotification(t.responseJSON.message,"danger",!0)})}function getSelectedOptions(){var t={};return $(".product-opt").each(function(){var a=$(this).val().trim(),n=$(this).attr("data-label"),e=$(this).attr("name"),o=$(this).attr("type");o||(t[e]={label:n,name:e,value:a}),"radio"===o&&(t[e]={label:n,name:e,value:$('input[name="'+e+'"]:checked').val()}),"checkbox"===o&&(t[e]={label:n,name:e,value:$('input[name="'+$(this).attr("name")+'"]').is(":checked")})}),t}function updateCartDiv(){$.ajax({method:"GET",url:"/checkout/cartdata"}).done(function(t){var a=t.cart,n=t.session,e="",o=numeral(n.totalCartAmount).format("0.00"),i=numeral(n.totalCartShipping).format("0.00"),r=`${n.shippingMessage} :${t.currencySymbol}${i}`;0===n.totalCartShipping&&(r=`${n.shippingMessage}`);var c=numeral(n.totalCartDiscount).format("0.00"),s="";n.totalCartDiscount>0&&(s=`\n
\n Discount: ${t.currencySymbol}${c}\n
`),a?($("#cart-empty").empty(),Object.keys(a).forEach(function(n){var o=a[n],i=numeral(o.totalItemPrice).format("0.00"),r="",c=1;Object.keys(o.options).forEach(function(t){var a=o.options[t];c===Object.keys(o.options).length?r+=`${upperFirst(a.name)}: ${a.value}`:r+=`${upperFirst(a.name)}: ${a.value} / `,c++});var s=`${o.title} product image`;o.productImage&&(s=`${o.title} product image`),e+=`\n
\n
\n
\n
\n ${s}\n
\n
\n
\n
\n
${o.title}
\n ${r}\n
\n
\n
\n
\n \n
\n \n
\n \n
\n
\n
\n
\n \n
\n
\n ${t.currencySymbol}${i}\n
\n
\n
\n
\n
\n
`}),$(".cartBodyWrapper").html(e)):$(".cartBodyWrapper").html(""),$("#cart-count").text(n.totalCartItems);var d=`\n
\n
\n
\n ${r}\n
\n ${s}\n
\n Total:\n ${t.currencySymbol}${o}\n
\n
\n
`;a?($(".cartTotalsWrapper").html(d),$(".cart-buttons").removeClass("d-none")):($(".cartTotalsWrapper").html('\n
\n
t.toUpperCase())}function emptyCart(){$.ajax({method:"POST",url:"/product/emptycart"}).done(function(t){showNotification(t.message,"success",!0),updateCartDiv()})}$(document).ready(function(){if($(window).width()<768&&($(".menu-side").on("click",function(t){t.preventDefault(),$('.menu-side li:not(".active")').slideToggle()}),$('.menu-side li:not(".active")').hide(),$(".menu-side>.active").html(''),$(".menu-side>.active").addClass("menu-side-mobile"),0===$("#navbar ul li").length&&$("#navbar").hide(),$("#offcanvasClose").hide()),$("#userSetupForm").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/admin/setup_action",data:{usersName:$("#usersName").val(),userEmail:$("#userEmail").val(),userPassword:$("#userPassword").val()}}).done(function(t){showNotification(t.message,"success",!1,"/admin/login")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}))}),$(document).on("click",".menu-btn",function(t){t.preventDefault(),$("body").addClass("pushy-open-right")}),$("table").each(function(){$(this).addClass("table table-hover")}),$("#productTags").length&&$("#productTags").tokenfield(),$(document).on("click",".dashboard_list",function(t){window.document.location=$(this).attr("href")}).hover(function(){$(this).toggleClass("hover")}),$(document).on("click",".btn-qty-minus",function(t){t.preventDefault();var a=$(t.target).parent().parent().find(".cart-product-quantity");$(a).val(parseInt(a.val())-1),cartUpdate(a)}),$(document).on("click",".btn-qty-add",function(t){t.preventDefault();var a=$(t.target).parent().parent().find(".cart-product-quantity");$(a).val(parseInt(a.val())+1),cartUpdate(a)}),$(document).on("click",".btn-delete-from-cart",function(t){deleteFromCart($(t.target))}),$("#pager").length){var t=$("#pageNum").val(),a=$("#productsPerPage").val(),n=$("#totalProductCount").val(),e=$("#paginateUrl").val(),o=$("#searchTerm").val();""!==o&&(o+="/");var i="/"+e+"/"+o+"{{number}}",r=Math.ceil(n/a);parseInt(n)>parseInt(a)&&($("#pager").bootpag({total:r,page:t,maxVisible:5,href:i,wrapClass:"pagination",prevClass:"page-item previous",nextClass:"page-item next",activeClass:"page-item active"}),$("#pager a").each(function(){$(this).addClass("page-link")}))}if($("#customerLogout").on("click",function(t){$.ajax({method:"POST",url:"/customer/logout",data:{}}).done(function(t){location.reload()})}),$("#customerForgotten").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/customer/forgotten_action",data:{email:$("#email").val()}}).done(function(t){showNotification(t.message,"success")}).fail(function(t){t.message?showNotification(t.responseJSON.message,"danger"):showNotification(t.responseText,"danger")}))}),$(document).on("click","#createAccountCheckbox",function(t){$("#newCustomerPassword").prop("required",$("#createAccountCheckbox").prop("checked"))}),$("#checkoutInformation").validator().on("click",function(t){if(t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length){var a="/customer/save";$("#createAccountCheckbox").prop("checked")&&(a="/customer/create"),$.ajax({method:"POST",url:a,data:{email:$("#shipEmail").val(),firstName:$("#shipFirstname").val(),lastName:$("#shipLastname").val(),address1:$("#shipAddr1").val(),address2:$("#shipAddr2").val(),country:$("#shipCountry").val(),state:$("#shipState").val(),postcode:$("#shipPostcode").val(),phone:$("#shipPhoneNumber").val(),password:$("#newCustomerPassword").val(),orderComment:$("#orderComment").val()}}).done(function(){window.location="/checkout/shipping"}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}}),$("#addDiscountCode").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/checkout/adddiscountcode",data:{discountCode:$("#discountCode").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#removeDiscountCode").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/checkout/removediscountcode",data:{}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#loginForm").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/admin/login_action",data:{email:$("#email").val(),password:$("#password").val()}}).done(function(t){window.location="/admin"}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$("#customerLogin").on("click",function(t){t.isDefaultPrevented()||(t.preventDefault(),$.ajax({method:"POST",url:"/customer/login_action",data:{loginEmail:$("#customerLoginEmail").val(),loginPassword:$("#customerLoginPassword").val()}}).done(function(t){var a=t.customer;$("#shipEmail").val(a.email),$("#shipFirstname").val(a.firstName),$("#shipLastname").val(a.lastName),$("#shipAddr1").val(a.address1),$("#shipAddr2").val(a.address2),$("#shipCountry").val(a.country),$("#shipState").val(a.state),$("#shipPostcode").val(a.postcode),$("#shipPhoneNumber").val(a.phone),location.reload()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})),t.preventDefault()}),$(document).on("click",".image-next",function(t){var a=$(".thumbnail-image"),n=0,e=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(e=n+1===a.length||n+1<0?0:n+1),n++}),$("#product-title-image").attr("src",$(a).eq(e).attr("src"))}),$(document).on("click",".image-prev",function(t){var a=$(".thumbnail-image"),n=0,e=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(e=n-1===a.length||n-1<0?a.length-1:n-1),n++}),$("#product-title-image").attr("src",$(a).eq(e).attr("src"))}),$(document).on("click",".product-add-to-cart",function(t){var a=getSelectedOptions();parseInt($("#product_quantity").val())<1&&$("#product_quantity").val(1),$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$("#productId").val(),productQuantity:$("#product_quantity").val(),productOptions:JSON.stringify(a),productComment:$("#product_comment").val()}}).done(function(t){showNotification(t.message,"success"),updateCartDiv()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$("#product_quantity").on("keyup",function(t){checkMaxQuantity(t,$("#product_quantity"))}),$(".cart-product-quantity").on("keyup",function(t){checkMaxQuantity(t,$(".cart-product-quantity"))}),$(".cart-product-quantity").on("focusout",function(t){cartUpdate($(t.target))}),$(document).on("click",".pushy-link",function(t){$("body").removeClass("pushy-open-right")}),$(document).on("click",".add-to-cart",function(t){var a="/product/"+$(this).attr("data-id");$(this).attr("data-link")&&(a="/product/"+$(this).attr("data-link")),"true"===$(this).attr("data-has-options")?window.location=a:$.ajax({method:"POST",url:"/product/addtocart",data:{productId:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success"),updateCartDiv()}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#empty-cart",function(t){$("#confirmModal").modal("show"),$("#buttonConfirm").attr("data-func","emptyCart")}),$(document).on("click","#buttonConfirm",function(t){var a=$(t.target).attr("data-func");window[a](),$("#confirmModal").modal("hide")}),$(".qty-btn-minus").on("click",function(){var t=parseInt($("#product_quantity").val())-1;$("#product_quantity").val(t>0?t:1)}),$(".qty-btn-plus").on("click",function(){$("#product_quantity").val(parseInt($("#product_quantity").val())+1)}),$(".thumbnail-image").on("click",function(){$("#product-title-image").attr("src",$(this).attr("src"))}),$(document).on("click","#btn_search_reset",function(t){window.location.replace("/")}),$(document).on("click","#btn_search",function(t){t.preventDefault(),""===$("#frm_search").val().trim()?showNotification("Please enter a search value","danger"):window.location.href="/search/"+$("#frm_search").val()}),""!==$("#input_notify_message").val()){var c=$("#input_notify_message").val(),s=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(c,s||"danger",!1)}}); \ No newline at end of file diff --git a/public/stylesheets/pushy.css b/public/stylesheets/pushy.css index 60e4309..7e64904 100644 --- a/public/stylesheets/pushy.css +++ b/public/stylesheets/pushy.css @@ -8,7 +8,7 @@ width: 500px; height: 100%; top: 0; - z-index: 9999; + z-index: 999; background: #fff; overflow: auto; visibility: hidden; @@ -80,7 +80,7 @@ right: 0; bottom: 0; left: 0; - z-index: 9998; + z-index: 998; background-color: rgba(0, 0, 0, 0.5); -webkit-animation: fade 500ms; animation: fade 500ms; diff --git a/public/stylesheets/pushy.min.css b/public/stylesheets/pushy.min.css index ad287e3..af85f46 100644 --- a/public/stylesheets/pushy.min.css +++ b/public/stylesheets/pushy.min.css @@ -1,4 +1,4 @@ /*! Pushy - v1.0.0 - 2016-3-1 * Pushy is a responsive off-canvas navigation menu using CSS transforms & transitions. * https://github.com/christophery/pushy/ -* by Christopher Yee */.pushy{position:fixed;width:500px;height:100%;top:0;z-index:9999;background:#fff;overflow:auto;visibility:hidden;-webkit-overflow-scrolling:touch}.pushy ul:first-child{margin-top:10px}.pushy.pushy-left{left:0}.pushy.pushy-right{right:0}.pushy-left{-webkit-transform:translate3d(-500px,0,0);-ms-transform:translate3d(-500px,0,0);transform:translate3d(-500px,0,0)}.pushy-open-left #container,.pushy-open-left .push{-webkit-transform:translate3d(500px,0,0);-ms-transform:translate3d(500px,0,0);transform:translate3d(500px,0,0)}.pushy-right{-webkit-transform:translate3d(500px,0,0);-ms-transform:translate3d(500px,0,0);transform:translate3d(500px,0,0)}.pushy-open-right #container,.pushy-open-right .push{-webkit-transform:translate3d(-500px,0,0);-ms-transform:translate3d(-500px,0,0);transform:translate3d(-500px,0,0)}.pushy-open-left .pushy,.pushy-open-right .pushy{-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#container,.push,.pushy{transition:transform .2s cubic-bezier(.16,.68,.43,.99)}.site-overlay{display:none}.pushy-open-left .site-overlay,.pushy-open-right .site-overlay{display:block;position:fixed;top:0;right:0;bottom:0;left:0;z-index:9998;background-color:rgba(0,0,0,.5);-webkit-animation:fade .5s;animation:fade .5s}@keyframes fade{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes fade{0%{opacity:0}100%{opacity:1}}.pushy-submenu ul{padding-left:15px;transition:max-height .2s ease-in-out}.pushy-submenu ul .pushy-link{transition:opacity .2s ease-in-out}.pushy-submenu>a{position:relative}.pushy-submenu>a::after{content:'';display:block;height:11px;width:8px;position:absolute;top:50%;right:15px;background:url(../img/arrow.svg) no-repeat;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);transition:transform .2s}.pushy-submenu-closed ul{max-height:0;overflow:hidden}.pushy-submenu-closed .pushy-link{opacity:0}.pushy-submenu-open ul{max-height:1000px}.pushy-submenu-open .pushy-link{opacity:1}.pushy-submenu-open a::after{-webkit-transform:translateY(-50%) rotate(90deg);-ms-transform:translateY(-50%) rotate(90deg);transform:translateY(-50%) rotate(90deg)}.no-csstransforms3d .pushy-submenu-closed ul{max-height:none;display:none} \ No newline at end of file +* by Christopher Yee */.pushy{position:fixed;width:500px;height:100%;top:0;z-index:999;background:#fff;overflow:auto;visibility:hidden;-webkit-overflow-scrolling:touch}.pushy ul:first-child{margin-top:10px}.pushy.pushy-left{left:0}.pushy.pushy-right{right:0}.pushy-left{-webkit-transform:translate3d(-500px,0,0);-ms-transform:translate3d(-500px,0,0);transform:translate3d(-500px,0,0)}.pushy-open-left #container,.pushy-open-left .push{-webkit-transform:translate3d(500px,0,0);-ms-transform:translate3d(500px,0,0);transform:translate3d(500px,0,0)}.pushy-right{-webkit-transform:translate3d(500px,0,0);-ms-transform:translate3d(500px,0,0);transform:translate3d(500px,0,0)}.pushy-open-right #container,.pushy-open-right .push{-webkit-transform:translate3d(-500px,0,0);-ms-transform:translate3d(-500px,0,0);transform:translate3d(-500px,0,0)}.pushy-open-left .pushy,.pushy-open-right .pushy{-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#container,.push,.pushy{transition:transform .2s cubic-bezier(.16,.68,.43,.99)}.site-overlay{display:none}.pushy-open-left .site-overlay,.pushy-open-right .site-overlay{display:block;position:fixed;top:0;right:0;bottom:0;left:0;z-index:998;background-color:rgba(0,0,0,.5);-webkit-animation:fade .5s;animation:fade .5s}@keyframes fade{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes fade{0%{opacity:0}100%{opacity:1}}.pushy-submenu ul{padding-left:15px;transition:max-height .2s ease-in-out}.pushy-submenu ul .pushy-link{transition:opacity .2s ease-in-out}.pushy-submenu>a{position:relative}.pushy-submenu>a::after{content:'';display:block;height:11px;width:8px;position:absolute;top:50%;right:15px;background:url(../img/arrow.svg) no-repeat;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);transition:transform .2s}.pushy-submenu-closed ul{max-height:0;overflow:hidden}.pushy-submenu-closed .pushy-link{opacity:0}.pushy-submenu-open ul{max-height:1000px}.pushy-submenu-open .pushy-link{opacity:1}.pushy-submenu-open a::after{-webkit-transform:translateY(-50%) rotate(90deg);-ms-transform:translateY(-50%) rotate(90deg);transform:translateY(-50%) rotate(90deg)}.no-csstransforms3d .pushy-submenu-closed ul{max-height:none;display:none} \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 76ba220..429772e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,6 +1,7 @@ const express = require('express'); const router = express.Router(); const colors = require('colors'); +const hash = require('object-hash'); const moment = require('moment'); const _ = require('lodash'); const { @@ -362,12 +363,12 @@ router.post('/product/updatecart', async (req, res, next) => { if(productQuantity === 0){ // quantity equals zero so we remove the item - delete req.session.cart[cartItem.productId]; + delete req.session.cart[cartItem.cartId]; res.status(400).json({ message: 'There was an error updating the cart', totalCartItems: Object.keys(req.session.cart).length }); return; } - const product = await db.products.findOne({ _id: getId(cartItem.productId) }); + const product = await db.products.findOne({ _id: getId(req.session.cart[cartItem.cartId].productId) }); if(!product){ res.status(400).json({ message: 'There was an error updating the cart', totalCartItems: Object.keys(req.session.cart).length }); return; @@ -382,14 +383,14 @@ router.post('/product/updatecart', async (req, res, next) => { } const productPrice = parseFloat(product.productPrice).toFixed(2); - if(!req.session.cart[cartItem.productId]){ + if(!req.session.cart[cartItem.cartId]){ res.status(400).json({ message: 'There was an error updating the cart', totalCartItems: Object.keys(req.session.cart).length }); return; } // Update the cart - req.session.cart[cartItem.productId].quantity = productQuantity; - req.session.cart[cartItem.productId].totalItemPrice = productPrice * productQuantity; + req.session.cart[cartItem.cartId].quantity = productQuantity; + req.session.cart[cartItem.cartId].totalItemPrice = productPrice * productQuantity; // update total cart amount await updateTotalCart(req, res); @@ -410,12 +411,12 @@ router.post('/product/removefromcart', async (req, res, next) => { const db = req.app.db; // Check for item in cart - if(!req.session.cart[req.body.productId]){ + if(!req.session.cart[req.body.cartId]){ return res.status(400).json({ message: 'Product not found in cart' }); } // remove item from cart - delete req.session.cart[req.body.productId]; + delete req.session.cart[req.body.cartId]; // If not items in cart, empty it if(Object.keys(req.session.cart).length === 0){ @@ -535,19 +536,25 @@ router.post('/product/addtocart', async (req, res, next) => { }catch(ex){} } + // Product with options hash + const productHash = hash({ + productId: product._id.toString(), + options + }); + // if exists we add to the existing value let cartQuantity = 0; - if(req.session.cart[product._id]){ - cartQuantity = parseInt(req.session.cart[product._id].quantity) + productQuantity; - req.session.cart[product._id].quantity = cartQuantity; - req.session.cart[product._id].totalItemPrice = productPrice * parseInt(req.session.cart[product._id].quantity); + if(req.session.cart[productHash]){ + cartQuantity = parseInt(req.session.cart[productHash].quantity) + productQuantity; + req.session.cart[productHash].quantity = cartQuantity; + req.session.cart[productHash].totalItemPrice = productPrice * parseInt(req.session.cart[productHash].quantity); }else{ // Set the card quantity cartQuantity = productQuantity; // new product deets const productObj = {}; - productObj.productId = req.body.productId; + productObj.productId = product._id; productObj.title = product.productTitle; productObj.quantity = productQuantity; productObj.totalItemPrice = productPrice * productQuantity; @@ -562,7 +569,7 @@ router.post('/product/addtocart', async (req, res, next) => { } // merge into the current cart - req.session.cart[product._id] = productObj; + req.session.cart[productHash] = productObj; } // Update cart to the DB @@ -580,7 +587,11 @@ router.post('/product/addtocart', async (req, res, next) => { req.session.cartSubscription = product.productSubscription; } - return res.status(200).json({ message: 'Cart successfully updated', totalCartItems: req.session.totalCartItems }); + return res.status(200).json({ + message: 'Cart successfully updated', + cartId: productHash, + totalCartItems: req.session.totalCartItems + }); }); // search products diff --git a/test/specs/cart.js b/test/specs/cart.js new file mode 100644 index 0000000..b77126f --- /dev/null +++ b/test/specs/cart.js @@ -0,0 +1,166 @@ +import{ serial as test }from'ava'; +const { + runBefore, + g +} = require('../helper'); + +test.before(async () => { + await runBefore(); +}); + +test('[Success] Add subscripton product to cart', async t => { + const res = await g.request + .post('/product/addtocart') + .send({ + productId: g.products[7]._id, + productQuantity: 1, + productOptions: {} + }) + .expect(200); + const sessions = await g.db.cart.find({}).toArray(); + if(!sessions || sessions.length === 0){ + t.fail(); + } + t.deepEqual(res.body.message, 'Cart successfully updated'); +}); + +test('[Fail] Add product to cart when subscription already added', async t => { + const res = await g.request + .post('/product/addtocart') + .send({ + productId: g.products[1]._id, + productQuantity: 1, + productOptions: JSON.stringify(g.products[1].productOptions) + }) + .expect(400); + t.deepEqual(res.body.message, 'Subscription already existing in cart. You cannot add more.'); +}); + +test('[Fail] Add quantity which exceeds the maxQuantity', async t => { + const res = await g.request + .post('/product/addtocart') + .send({ + productId: g.products[4]._id, + productQuantity: 75, + productOptions: {} + }) + .expect(400); + t.deepEqual(res.body.message, 'The quantity exceeds the max amount. Please contact us for larger orders.'); +}); + +test('[Success] Empty cart', async t => { + const res = await g.request + .post('/product/emptycart') + .expect(200); + t.deepEqual(res.body.message, 'Cart successfully emptied'); +}); + +test('[Success] Add product to cart', async t => { + const res = await g.request + .post('/product/addtocart') + .send({ + productId: g.products[0]._id, + productQuantity: 1, + productOptions: JSON.stringify(g.products[0].productOptions) + }) + .expect(200); + const sessions = await g.db.cart.find({}).toArray(); + if(!sessions || sessions.length === 0){ + t.fail(); + } + t.deepEqual(res.body.message, 'Cart successfully updated'); +}); + +test('[Success] Update cart', async t => { + const cart = await g.request + .get('/cart/retrieve') + .expect(200); + + const cartId = Object.keys(cart.body.cart)[0]; + const productId = cart.body.cart[cartId].id; + + const res = await g.request + .post('/product/updatecart') + .send({ + productId: productId, + cartId: cartId, + quantity: 10 + }) + .expect(200); + + t.deepEqual(res.body.message, 'Cart successfully updated'); + + const checkCart = await g.request + .get('/cart/retrieve') + .expect(200); + + // Check new quantity and total price has been updated + t.deepEqual(checkCart.body.cart[cartId].quantity, 10); + t.deepEqual(checkCart.body.cart[cartId].totalItemPrice, cart.body.cart[cartId].totalItemPrice * 10); +}); + +test('[Fail] Cannot add subscripton when other product in cart', async t => { + const res = await g.request + .post('/product/addtocart') + .send({ + productId: g.products[7]._id, + productQuantity: 1, + productOptions: {} + }) + .expect(400); + t.deepEqual(res.body.message, 'You cannot combine subscription products with existing in your cart. Empty your cart and try again.'); +}); + +test('[Fail] Add product to cart with not enough stock', async t => { + const res = await g.request + .post('/product/addtocart') + .send({ + productId: g.products[0]._id, + productQuantity: 20, + productOptions: JSON.stringify(g.products[0].productOptions) + }) + .expect(400); + t.deepEqual(res.body.message, 'There is insufficient stock of this product.'); +}); + +test('[Fail] Add incorrect product to cart', async t => { + const res = await g.request + .post('/product/addtocart') + .send({ + productId: 'fake_product_id', + productQuantity: 20, + productOptions: JSON.stringify(g.products[0].productOptions) + }) + .expect(400); + t.deepEqual(res.body.message, 'Error updating cart. Please try again.'); +}); + +test('[Success] Remove item previously added to cart', async t => { + // Add a second product to cart + const cart = await g.request + .post('/product/addtocart') + .send({ + productId: g.products[1]._id, + productQuantity: 1, + productOptions: JSON.stringify(g.products[1].productOptions) + }) + .expect(200); + + const res = await g.request + .post('/product/removefromcart') + .send({ + cartId: cart.body.cartId + }) + .expect(200); + t.deepEqual(res.body.message, 'Product successfully removed'); +}); + +test('[Fail] Try remove an item which is not in the cart', async t => { + const res = await g.request + .post('/product/removefromcart') + .send({ + cartId: 'bogus_product_id' + }) + .expect(400); + t.deepEqual(res.body.message, 'Product not found in cart'); +}); diff --git a/test/specs/products.js b/test/specs/products.js index 43e801d..30be9d4 100644 --- a/test/specs/products.js +++ b/test/specs/products.js @@ -19,160 +19,6 @@ test('[Success] Get products JSON', async t => { } }); -test('[Success] Add subscripton product to cart', async t => { - const res = await g.request - .post('/product/addtocart') - .send({ - productId: g.products[7]._id, - productQuantity: 1, - productOptions: {} - }) - .expect(200); - const sessions = await g.db.cart.find({}).toArray(); - if(!sessions || sessions.length === 0){ - t.fail(); - } - t.deepEqual(res.body.message, 'Cart successfully updated'); -}); - -test('[Fail] Add product to cart when subscription already added', async t => { - const res = await g.request - .post('/product/addtocart') - .send({ - productId: g.products[1]._id, - productQuantity: 1, - productOptions: JSON.stringify(g.products[1].productOptions) - }) - .expect(400); - t.deepEqual(res.body.message, 'Subscription already existing in cart. You cannot add more.'); -}); - -test('[Fail] Add quantity which exceeds the maxQuantity', async t => { - const res = await g.request - .post('/product/addtocart') - .send({ - productId: g.products[4]._id, - productQuantity: 75, - productOptions: {} - }) - .expect(400); - t.deepEqual(res.body.message, 'The quantity exceeds the max amount. Please contact us for larger orders.'); -}); - -test('[Success] Empty cart', async t => { - const res = await g.request - .post('/product/emptycart') - .expect(200); - t.deepEqual(res.body.message, 'Cart successfully emptied'); -}); - -test('[Success] Add product to cart', async t => { - const res = await g.request - .post('/product/addtocart') - .send({ - productId: g.products[0]._id, - productQuantity: 1, - productOptions: JSON.stringify(g.products[0].productOptions) - }) - .expect(200); - const sessions = await g.db.cart.find({}).toArray(); - if(!sessions || sessions.length === 0){ - t.fail(); - } - t.deepEqual(res.body.message, 'Cart successfully updated'); -}); - -test('[Success] Update cart', async t => { - const cart = await g.request - .get('/cart/retrieve') - .expect(200); - - const productId = g.products[0]._id; - - const res = await g.request - .post('/product/updatecart') - .send({ - productId: productId, - quantity: 10 - }) - .expect(200); - - t.deepEqual(res.body.message, 'Cart successfully updated'); - - const checkCart = await g.request - .get('/cart/retrieve') - .expect(200); - - // Check new quantity and total price has been updated - t.deepEqual(checkCart.body.cart[productId].quantity, 10); - t.deepEqual(checkCart.body.cart[productId].totalItemPrice, cart.body.cart[productId].totalItemPrice * 10); -}); - -test('[Fail] Cannot add subscripton when other product in cart', async t => { - const res = await g.request - .post('/product/addtocart') - .send({ - productId: g.products[7]._id, - productQuantity: 1, - productOptions: {} - }) - .expect(400); - t.deepEqual(res.body.message, 'You cannot combine subscription products with existing in your cart. Empty your cart and try again.'); -}); - -test('[Fail] Add product to cart with not enough stock', async t => { - const res = await g.request - .post('/product/addtocart') - .send({ - productId: g.products[0]._id, - productQuantity: 20, - productOptions: JSON.stringify(g.products[0].productOptions) - }) - .expect(400); - t.deepEqual(res.body.message, 'There is insufficient stock of this product.'); -}); - -test('[Fail] Add incorrect product to cart', async t => { - const res = await g.request - .post('/product/addtocart') - .send({ - id: 'fake_product_id', - state: false - }) - .expect(400); - t.deepEqual(res.body.message, 'Error updating cart. Please try again.'); -}); - -test('[Success] Remove item previously added to cart', async t => { - // Add a second product to cart - await g.request - .post('/product/addtocart') - .send({ - productId: g.products[1]._id, - productQuantity: 1, - productOptions: JSON.stringify(g.products[1].productOptions) - }) - .expect(200); - - const res = await g.request - .post('/product/removefromcart') - .send({ - productId: g.products[0]._id - }) - .expect(200); - t.deepEqual(res.body.message, 'Product successfully removed'); -}); - -test('[Fail] Try remove an item which is not in the cart', async t => { - const res = await g.request - .post('/product/removefromcart') - .send({ - cartId: 'bogus_product_id' - }) - .expect(400); - t.deepEqual(res.body.message, 'Product not found in cart'); -}); - test('[Success] Search products', async t => { const res = await g.request .get('/category/backpack?json=true') diff --git a/views/layouts/layout.hbs b/views/layouts/layout.hbs index f20020a..b9a3a1f 100644 --- a/views/layouts/layout.hbs +++ b/views/layouts/layout.hbs @@ -208,5 +208,6 @@
{{/if}} + {{> partials/confirmModal}} diff --git a/views/partials/confirmModal.hbs b/views/partials/confirmModal.hbs new file mode 100644 index 0000000..0525710 --- /dev/null +++ b/views/partials/confirmModal.hbs @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/views/themes/Cloth/cart.hbs b/views/themes/Cloth/cart.hbs index df61d38..d6e2f4b 100644 --- a/views/themes/Cloth/cart.hbs +++ b/views/themes/Cloth/cart.hbs @@ -31,14 +31,14 @@
- +
- +
{{else}}