From 03573129b356c0683d5a8f6cf27cd743bf9f8bb0 Mon Sep 17 00:00:00 2001 From: Mark Moffat Date: Mon, 30 Dec 2019 13:40:57 +1030 Subject: [PATCH] Fixing cart storage and management --- lib/common.js | 21 +++- public/javascripts/expressCart.js | 116 +++++------------ public/javascripts/expressCart.min.js | 2 +- routes/index.js | 172 +++++++++++--------------- test/specs/products.js | 24 ++-- views/themes/Cloth/cart.hbs | 80 ++++++------ 6 files changed, 182 insertions(+), 233 deletions(-) diff --git a/lib/common.js b/lib/common.js index 46b3cea..5eef673 100755 --- a/lib/common.js +++ b/lib/common.js @@ -111,15 +111,24 @@ const clearSessionValue = (session, sessionVar) => { return temp; }; -const updateTotalCartAmount = (req, res) => { +const updateTotalCart = (req, res) => { const config = getConfig(); req.session.totalCartAmount = 0; + req.session.cartTotalItems = 0; - _(req.session.cart).forEach((item) => { - req.session.totalCartAmount = req.session.totalCartAmount + item.totalItemPrice; + // If cart is empty return zero values + if(!req.session.cart){ + return; + } + + Object.keys(req.session.cart).forEach((item) => { + req.session.totalCartAmount = req.session.totalCartAmount + req.session.cart[item].totalItemPrice; }); + // Update the total items in cart for the badge + req.session.cartTotalItems = Object.keys(req.session.cart).length; + // under the free shipping threshold if(req.session.totalCartAmount < config.freeShippingAmount){ req.session.totalCartAmount = req.session.totalCartAmount + parseInt(config.flatShipping); @@ -136,7 +145,7 @@ const updateSubscriptionCheck = (req, res) => { return; } - req.session.cart.forEach((item) => { + Object.keys(req.session.cart).forEach((item) => { if(item.productSubscription){ req.session.cartSubscription = item.productSubscription; }else{ @@ -279,7 +288,7 @@ const updateConfig = (fields) => { } }); - // delete settings + // delete any settings delete settingsFile.customCss_input; delete settingsFile.footerHtml_input; delete settingsFile.googleAnalytics_input; @@ -583,7 +592,7 @@ module.exports = { convertBool, addSitemapProducts, clearSessionValue, - updateTotalCartAmount, + updateTotalCart, updateSubscriptionCheck, checkDirectorySync, getThemes, diff --git a/public/javascripts/expressCart.js b/public/javascripts/expressCart.js index c1b3e97..ac1976d 100644 --- a/public/javascripts/expressCart.js +++ b/public/javascripts/expressCart.js @@ -19,6 +19,13 @@ $(document).ready(function (){ $('#offcanvasClose').hide(); } + // If cart was open before reload, open it again + var isCartOpen = (localStorage.getItem('cartOpen') === 'true'); + if(isCartOpen === true){ + localStorage.setItem('cartOpen', false); + $('body').addClass('pushy-open-right'); + } + $('#userSetupForm').validator().on('submit', function(e){ if(!e.isDefaultPrevented()){ e.preventDefault(); @@ -40,24 +47,6 @@ $(document).ready(function (){ } }); - // $('.shipping-form input').each(function(e){ - // $(this).wrap('
'); - // var tag = $(this).attr('placeholder'); - // $(this).after(''); - // }); - - // $('.shipping-form input').on('focus', function(){ - // $(this).next().addClass('floatLabel'); - // $(this).next().removeClass('hidden'); - // }); - - // $('.shipping-form input').on('blur', function(){ - // if($(this).val() === ''){ - // $(this).next().addClass('hidden'); - // $(this).next().removeClass('floatLabel'); - // } - // }); - $(document).on('click', '.menu-btn', function(e){ e.preventDefault(); $('body').addClass('pushy-open-right'); @@ -68,7 +57,9 @@ $(document).ready(function (){ $(this).addClass('table table-hover'); }); - $('#productTags').tokenfield(); + if($('#productTags').length){ + $('#productTags').tokenfield(); + } $(document).on('click', '.dashboard_list', function(e){ window.document.location = $(this).attr('href'); @@ -79,7 +70,6 @@ $(document).ready(function (){ $(document).on('click', '.btn-qty-minus', function(e){ e.preventDefault(); var qtyElement = $(e.target).parent().parent().find('.cart-product-quantity'); - // console.log('qtyElement', qtyElement); $(qtyElement).val(parseInt(qtyElement.val()) - 1); cartUpdate(qtyElement); }); @@ -91,11 +81,6 @@ $(document).ready(function (){ cartUpdate(qtyElement); }); - // $(document).on('change', '.cart-product-quantity', function (e){ - // console.log('test'); - // cartUpdate(e.target); - // }); - $(document).on('click', '.btn-delete-from-cart', function(e){ deleteFromCart($(e.target)); }); @@ -172,6 +157,7 @@ $(document).ready(function (){ }); $('#checkoutInformation').validator().on('click', function(e){ + console.log('here?'); e.preventDefault(); if($('#shipping-form').validator('validate').has('.has-error').length === 0){ // Change route if customer to be saved for later @@ -320,16 +306,15 @@ $(document).ready(function (){ }) .done(function(msg){ $('#cart-count').text(msg.totalCartItems); - updateCartDiv(); - showNotification(msg.message, 'success'); + showNotification(msg.message, 'success', true); }) .fail(function(msg){ showNotification(msg.responseJSON.message, 'danger'); }); }); - $('.cart-product-quantity').on('input', function(){ - cartUpdate(); + $('.cart-product-quantity').on('focusout', function(e){ + cartUpdate($(e.target)); }); $(document).on('click', '.pushy-link', function(e){ @@ -352,8 +337,7 @@ $(document).ready(function (){ }) .done(function(msg){ $('#cart-count').text(msg.totalCartItems); - updateCartDiv(); - showNotification(msg.message, 'success'); + showNotification(msg.message, 'success', true); }) .fail(function(msg){ showNotification(msg.responseJSON.message, 'danger'); @@ -368,7 +352,6 @@ $(document).ready(function (){ }) .done(function(msg){ $('#cart-count').text(msg.totalCartItems); - updateCartDiv(); showNotification(msg.message, 'success', true); }); }); @@ -420,25 +403,11 @@ function deleteFromCart(element){ $.ajax({ method: 'POST', url: '/product/removefromcart', - data: { cartId: element.attr('data-id') } + data: { productId: element.attr('data-id') } }) .done(function(msg){ - $('#cart-count').text(msg.totalCartItems); - if(msg.totalCartItems === 0){ - $(element).closest('.cart-row').hide('slow', function(){ - $(element).closest('.cart-row').remove(); - }); - $('.cart-contents-shipping').hide('slow', function(){ - $('.cart-contents-shipping').remove(); - }); - showNotification(msg.message, 'success'); - setTimeout(function(){ - window.location = '/'; - }, 3700); - }else{ - $(element).closest('.cart-row').hide('slow', function(){ $(element).closest('.cart-row').remove(); }); - showNotification(msg.message, 'success'); - } + setCartOpen(); + showNotification(msg.message, 'success', true); }) .fail(function(msg){ showNotification(msg.responseJSON.message, 'danger'); @@ -446,61 +415,42 @@ function deleteFromCart(element){ } function cartUpdate(element){ - console.log('element', element.val()); if($(element).val() > 0){ if($(element).val() !== ''){ - updateCart(); + updateCart(element); } }else{ $(element).val(1); } } -function updateCart(){ - // gather items of cart - var cartItems = []; - $('.cart-product-quantity').each(function(){ - cartItems.push({ - productId: $(this).attr('data-id'), - quantity: $(this).val() - }); - }); - - console.log('cartItems', cartItems) +function setCartOpen(){ + if($('body').hasClass('pushy-open-right') === true){ + localStorage.setItem('cartOpen', true); + }else{ + localStorage.setItem('cartOpen', false); + } +} +function updateCart(element){ // update cart on server $.ajax({ method: 'POST', url: '/product/updatecart', - data: { items: JSON.stringify(cartItems) } + data: { + productId: element.attr('data-id'), + quantity: element.val() + } }) .done(function(msg){ - // update cart items - updateCartDiv(); - $('#cart-count').text(msg.totalCartItems); + setCartOpen(); + showNotification(msg.message, 'success', true); }) .fail(function(msg){ showNotification(msg.responseJSON.message, 'danger', true); }); } -function updateCartDiv(){ - // get new cart render - var path = window.location.pathname.split('/').length > 0 ? window.location.pathname.split('/')[1] : ''; - $.ajax({ - method: 'GET', - url: '/cartPartial', - data: { path: path } - }) - .done(function(msg){ - // update cart div - $('#cart').html(msg); - }) - .fail(function(msg){ - showNotification(msg.responseJSON.message, 'danger'); - }); -} - function getSelectedOptions(){ var options = {}; $('.product-opt').each(function(){ diff --git a/public/javascripts/expressCart.min.js b/public/javascripts/expressCart.min.js index e90930b..fce4202 100644 --- a/public/javascripts/expressCart.min.js +++ b/public/javascripts/expressCart.min.js @@ -1 +1 @@ -function deleteFromCart(t){$.ajax({method:"POST",url:"/product/removefromcart",data:{cartId:t.attr("data-id")}}).done(function(a){$("#cart-count").text(a.totalCartItems),0===a.totalCartItems?($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),$(".cart-contents-shipping").hide("slow",function(){$(".cart-contents-shipping").remove()}),showNotification(a.message,"success"),setTimeout(function(){window.location="/"},3700)):($(t).closest(".cart-row").hide("slow",function(){$(t).closest(".cart-row").remove()}),showNotification(a.message,"success"))}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function cartUpdate(t){console.log("element",t.val()),$(t).val()>0?""!==$(t).val()&&updateCart():$(t).val(1)}function updateCart(){var t=[];$(".cart-product-quantity").each(function(){t.push({productId:$(this).attr("data-id"),quantity:$(this).val()})}),console.log("cartItems",t),$.ajax({method:"POST",url:"/product/updatecart",data:{items:JSON.stringify(t)}}).done(function(t){updateCartDiv(),$("#cart-count").text(t.totalCartItems)}).fail(function(t){showNotification(t.responseJSON.message,"danger",!0)})}function updateCartDiv(){var t=window.location.pathname.split("/").length>0?window.location.pathname.split("/")[1]:"";$.ajax({method:"GET",url:"/cartPartial",data:{path:t}}).done(function(t){$("#cart").html(t)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function getSelectedOptions(){var t={};return $(".product-opt").each(function(){if("opt-"!==$(this).attr("name")){var a=$(this).val().trim();"radio"===$(this).attr("type")&&(a=$('input[name="'+$(this).attr("name")+'"]:checked').val()),t[$(this).attr("name").substring(4,$(this).attr("name").length)]=a}else t[$(this).val().trim()]=$(this).prop("checked")}),t}$(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").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(),o=$("#paginateUrl").val(),n=$("#searchTerm").val();""!==n&&(n+="/");var i="/"+o+"/"+n+"{{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")})}}),$("#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,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=e+1===a.length||e+1<0?0:e+1),e++}),$("#product-title-image").attr("src",$(a).eq(o).attr("src"))}),$(document).on("click",".image-prev",function(t){var a=$(".thumbnail-image"),e=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=e-1===a.length||e-1<0?a.length-1:e-1),e++}),$("#product-title-image").attr("src",$(a).eq(o).attr("src"))}),$(document).on("click",".product-add-to-cart",function(t){var a=getSelectedOptions();parseInt($("#product_quantity").val())<0&&$("#product_quantity").val(0),$.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){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".cart-product-quantity").on("input",function(){cartUpdate()}),$(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){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#empty-cart",function(t){$.ajax({method:"POST",url:"/product/emptycart"}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success",!0)})}),$(".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 deleteFromCart(t){$.ajax({method:"POST",url:"/product/removefromcart",data:{productId:t.attr("data-id")}}).done(function(t){setCartOpen(),showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function cartUpdate(t){$(t).val()>0?""!==$(t).val()&&updateCart(t):$(t).val(1)}function setCartOpen(){!0===$("body").hasClass("pushy-open-right")?localStorage.setItem("cartOpen",!0):localStorage.setItem("cartOpen",!1)}function updateCart(t){$.ajax({method:"POST",url:"/product/updatecart",data:{productId:t.attr("data-id"),quantity:t.val()}}).done(function(t){setCartOpen(),showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger",!0)})}function getSelectedOptions(){var t={};return $(".product-opt").each(function(){if("opt-"!==$(this).attr("name")){var a=$(this).val().trim();"radio"===$(this).attr("type")&&(a=$('input[name="'+$(this).attr("name")+'"]:checked').val()),t[$(this).attr("name").substring(4,$(this).attr("name").length)]=a}else t[$(this).val().trim()]=$(this).prop("checked")}),t}$(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()),!0===("true"===localStorage.getItem("cartOpen"))&&(localStorage.setItem("cartOpen",!1),$("body").addClass("pushy-open-right")),$("#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(),o=$("#paginateUrl").val(),n=$("#searchTerm").val();""!==n&&(n+="/");var i="/"+o+"/"+n+"{{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(console.log("here?"),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")})}}),$("#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,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=e+1===a.length||e+1<0?0:e+1),e++}),$("#product-title-image").attr("src",$(a).eq(o).attr("src"))}),$(document).on("click",".image-prev",function(t){var a=$(".thumbnail-image"),e=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=e-1===a.length||e-1<0?a.length-1:e-1),e++}),$("#product-title-image").attr("src",$(a).eq(o).attr("src"))}),$(document).on("click",".product-add-to-cart",function(t){var a=getSelectedOptions();parseInt($("#product_quantity").val())<0&&$("#product_quantity").val(0),$.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){$("#cart-count").text(t.totalCartItems),showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".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){$("#cart-count").text(t.totalCartItems),showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#empty-cart",function(t){$.ajax({method:"POST",url:"/product/emptycart"}).done(function(t){$("#cart-count").text(t.totalCartItems),showNotification(t.message,"success",!0)})}),$(".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 s=$("#input_notify_message").val(),c=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(s,c||"danger",!1)}}); \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 2194533..2d86fb0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,7 +1,6 @@ const express = require('express'); const router = express.Router(); const colors = require('colors'); -const async = require('async'); const _ = require('lodash'); const { getId, @@ -11,7 +10,7 @@ const { getMenu, getPaymentConfig, getImages, - updateTotalCartAmount, + updateTotalCart, updateSubscriptionCheck, getData, addSitemapProducts, @@ -155,6 +154,10 @@ router.get('/checkout/cart', (req, res) => { }); }); +router.get('/checkout/cartdata', (req, res) => { + res.status(200).json(req.session.cart); +}); + router.get('/checkout/payment', (req, res) => { const config = req.app.config; @@ -244,14 +247,10 @@ router.get('/cart/retrieve', async (req, res, next) => { }); // Updates a single product quantity -router.post('/product/updatecart', (req, res, next) => { +router.post('/product/updatecart', async (req, res, next) => { const db = req.app.db; const config = req.app.config; - const cartItems = JSON.parse(req.body.items); - let hasError = false; - let stockError = false; - - console.log('cartItems', cartItems); + const cartItem = req.body; // Check cart exists if(!req.session.cart){ @@ -259,100 +258,84 @@ router.post('/product/updatecart', (req, res, next) => { return; } - console.log('req.session.cart', req.session.cart); + // Calculate the quantity to update + let productQuantity = cartItem.quantity ? cartItem.quantity : 1; + if(typeof productQuantity === 'string'){ + productQuantity = parseInt(productQuantity); + } - async.eachSeries(cartItems, async (cartItem, callback) => { - // Find index in cart - const cartIndex = _.findIndex(req.session.cart, { productId: cartItem.productId }); + if(productQuantity === 0){ + // quantity equals zero so we remove the item + delete req.session.cart[cartItem.productId]; + res.status(400).json({ message: 'There was an error updating the cart', totalCartItems: Object.keys(req.session.cart).length }); + return; + } - // Calculate the quantity to update - let productQuantity = cartItem.quantity ? cartItem.quantity : 1; - if(typeof productQuantity === 'string'){ - productQuantity = parseInt(productQuantity); - } + const product = await db.products.findOne({ _id: getId(cartItem.productId) }); + if(!product){ + res.status(400).json({ message: 'There was an error updating the cart', totalCartItems: Object.keys(req.session.cart).length }); + return; + } - console.log('productQuantity', productQuantity); - if(productQuantity === 0){ - // quantity equals zero so we remove the item - req.session.cart.splice(cartIndex, 1); - callback(null); + // If stock management on check there is sufficient stock for this product + if(config.trackStock){ + if(productQuantity > product.productStock){ + res.status(400).json({ message: 'There is insufficient stock of this product.', totalCartItems: Object.keys(req.session.cart).length }); return; } - const product = await db.products.findOne({ _id: getId(cartItem.productId) }); - if(product){ - // If stock management on check there is sufficient stock for this product - if(config.trackStock){ - if(productQuantity > product.productStock){ - hasError = true; - stockError = true; - callback(null); - return; - } - } + } - const productPrice = parseFloat(product.productPrice).toFixed(2); - if(req.session.cart[cartIndex]){ - req.session.cart[cartIndex].quantity = productQuantity; - req.session.cart[cartIndex].totalItemPrice = productPrice * productQuantity; - callback(null); - } - }else{ - hasError = true; - callback(null); - } - }, async () => { - // update total cart amount - updateTotalCartAmount(req, res); + const productPrice = parseFloat(product.productPrice).toFixed(2); + if(!req.session.cart[cartItem.productId]){ + res.status(400).json({ message: 'There was an error updating the cart', totalCartItems: Object.keys(req.session.cart).length }); + return; + } - // Update checking cart for subscription - updateSubscriptionCheck(req, res); + // Update the cart + req.session.cart[cartItem.productId].quantity = productQuantity; + req.session.cart[cartItem.productId].totalItemPrice = productPrice * productQuantity; - // Update cart to the DB - await db.cart.updateOne({ sessionId: req.session.id }, { - $set: { cart: req.session.cart } - }); + // update total cart amount + updateTotalCart(req, res); - // show response - if(hasError === false){ - res.status(200).json({ message: 'Cart successfully updated', totalCartItems: Object.keys(req.session.cart).length }); - }else{ - if(stockError){ - res.status(400).json({ message: 'There is insufficient stock of this product.', totalCartItems: Object.keys(req.session.cart).length }); - }else{ - res.status(400).json({ message: 'There was an error updating the cart', totalCartItems: Object.keys(req.session.cart).length }); - } - } + // Update checking cart for subscription + updateSubscriptionCheck(req, res); + + // Update cart to the DB + await db.cart.updateOne({ sessionId: req.session.id }, { + $set: { cart: req.session.cart } }); + + res.status(200).json({ message: 'Cart successfully updated', totalCartItems: Object.keys(req.session.cart).length }); }); // Remove single product from cart router.post('/product/removefromcart', async (req, res, next) => { const db = req.app.db; - let itemRemoved = false; + + // Check for item in cart + if(!req.session.cart[req.body.productId]){ + return res.status(400).json({ message: 'Product not found in cart' }); + } // remove item from cart - req.session.cart.forEach((item) => { - if(item){ - if(item.productId === req.body.cartId){ - itemRemoved = true; - req.session.cart = _.pull(req.session.cart, item); - } - } - }); + delete req.session.cart[req.body.productId]; + + // If not items in cart, empty it + if(Object.keys(req.session.cart).length === 0){ + return emptyCart(req, res, 'json'); + } // Update cart in DB await db.cart.updateOne({ sessionId: req.session.id }, { $set: { cart: req.session.cart } }); - // update total cart amount - updateTotalCartAmount(req, res); + // update total cart + updateTotalCart(req, res); // Update checking cart for subscription updateSubscriptionCheck(req, res); - if(itemRemoved === false){ - return res.status(400).json({ message: 'Product not found in cart' }); - } return res.status(200).json({ message: 'Product successfully removed', totalCartItems: Object.keys(req.session.cart).length }); }); @@ -372,8 +355,8 @@ const emptyCart = async (req, res, type, customMessage) => { // Remove cart from DB await db.cart.deleteOne({ sessionId: req.session.id }); - // update total cart amount - updateTotalCartAmount(req, res); + // update total cart + updateTotalCart(req, res); // Update checking cart for subscription updateSubscriptionCheck(req, res); @@ -409,7 +392,7 @@ router.post('/product/addtocart', async (req, res, next) => { // setup cart object if it doesn't exist if(!req.session.cart){ - req.session.cart = []; + req.session.cart = {}; } // Get the product from the DB @@ -425,14 +408,19 @@ router.post('/product/addtocart', async (req, res, next) => { } // If existing cart isn't empty check if product is a subscription - if(req.session.cart.length !== 0){ + if(Object.keys(req.session.cart).length !== 0){ if(product.productSubscription){ - return res.status(400).json({ message: 'You cannot combine scubscription products with existing in your cart. Empty your cart and try again.' }); + return res.status(400).json({ message: 'You cannot combine subscription products with existing in your cart. Empty your cart and try again.' }); } } // If stock management on check there is sufficient stock for this product if(config.trackStock && product.productStock){ + // If there is more stock than total (ignoring held) + if(productQuantity > product.productStock){ + return res.status(400).json({ message: 'There is insufficient stock of this product.' }); + } + const stockHeld = await db.cart.aggregate( { $match: { @@ -478,22 +466,14 @@ router.post('/product/addtocart', async (req, res, next) => { } }catch(ex){} } - const findDoc = { - productId: req.body.productId, - options: options - }; // if exists we add to the existing value - const cartIndex = _.findIndex(req.session.cart, findDoc); let cartQuantity = 0; - if(cartIndex > -1){ - cartQuantity = parseInt(req.session.cart[cartIndex].quantity) + productQuantity; - req.session.cart[cartIndex].quantity = cartQuantity; - req.session.cart[cartIndex].totalItemPrice = productPrice * parseInt(req.session.cart[cartIndex].quantity); + 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); }else{ - // Doesnt exist so we add to the cart session - req.session.cartTotalItems = req.session.cartTotalItems + productQuantity; - // Set the card quantity cartQuantity = productQuantity; @@ -514,7 +494,7 @@ router.post('/product/addtocart', async (req, res, next) => { } // merge into the current cart - req.session.cart.push(productObj); + req.session.cart[product._id] = productObj; } // Update cart to the DB @@ -523,7 +503,7 @@ router.post('/product/addtocart', async (req, res, next) => { }, { upsert: true }); // update total cart amount - updateTotalCartAmount(req, res); + updateTotalCart(req, res); // Update checking cart for subscription updateSubscriptionCheck(req, res); @@ -532,8 +512,6 @@ router.post('/product/addtocart', async (req, res, next) => { req.session.cartSubscription = product.productSubscription; } - // update how many products in the shopping cart - req.session.cartTotalItems = req.session.cart.reduce((a, b) => +a + +b.quantity, 0); return res.status(200).json({ message: 'Cart successfully updated', totalCartItems: req.session.cartTotalItems }); }); diff --git a/test/specs/products.js b/test/specs/products.js index 0722bd4..cb3b3ac 100644 --- a/test/specs/products.js +++ b/test/specs/products.js @@ -75,13 +75,13 @@ test('[Success] Update cart', async t => { .get('/cart/retrieve') .expect(200); - // Adjust the quantity of an item - cart.body.cart[0].quantity = 10; + const productId = g.products[0]._id; const res = await g.request .post('/product/updatecart') .send({ - items: JSON.stringify(cart.body.cart) + productId: productId, + quantity: 10 }) .expect(200); @@ -92,8 +92,8 @@ test('[Success] Update cart', async t => { .expect(200); // Check new quantity and total price has been updated - t.deepEqual(checkCart.body.cart[0].quantity, 10); - t.deepEqual(checkCart.body.cart[0].totalItemPrice, cart.body.cart[0].totalItemPrice * 10); + 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 => { @@ -105,7 +105,7 @@ test('[Fail] Cannot add subscripton when other product in cart', async t => { productOptions: {} }) .expect(400); - t.deepEqual(res.body.message, 'You cannot combine scubscription products with existing in your cart. Empty your cart and try again.'); + 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 => { @@ -132,10 +132,20 @@ test('[Fail] Add incorrect product to cart', async t => { }); 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({ - cartId: g.products[0]._id + productId: g.products[0]._id }) .expect(200); t.deepEqual(res.body.message, 'Product successfully removed'); diff --git a/views/themes/Cloth/cart.hbs b/views/themes/Cloth/cart.hbs index 571bb2a..dcfa09c 100644 --- a/views/themes/Cloth/cart.hbs +++ b/views/themes/Cloth/cart.hbs @@ -2,51 +2,53 @@
{{ @root.__ "Cart contents" }}
- {{#each @root.session.cart}} -
-
- {{#if productImage}} - {{this.title}} product image {{else}} - {{this.title}} product image {{/if}} -
-
-
- -
- {{#each this.options}} - {{#if @last}} - {{@key}}: {{this}} - {{else}} - {{@key}}: {{this}} / - {{/if}} - {{/each}} -
- {{#ifCond cartReadOnly '!=' true}} -
-
-
- -
- -
- +
+ {{#each @root.session.cart}} +
+
+ {{#if productImage}} + {{this.title}} product image {{else}} + {{this.title}} product image {{/if}} +
+
+
+ +
+ {{#each this.options}} + {{#if @last}} + {{@key}}: {{this}} + {{else}} + {{@key}}: {{this}} / + {{/if}} + {{/each}} +
+ {{#ifCond cartReadOnly '!=' true}} +
+
+
+ +
+ +
+ +
+
+ +
+ {{/ifCond}}
-
- -
- {{/ifCond}} +
+
+ {{currencySymbol @root.config.currencySymbol}}{{formatAmount this.totalItemPrice}}
-
- {{currencySymbol @root.config.currencySymbol}}{{formatAmount this.totalItemPrice}} -
+ {{/each}}
- {{/each}}
{{#if @root.session.cart}}