From 6076455d06e557941cc02b18d26222980cb5d0a6 Mon Sep 17 00:00:00 2001 From: Mark Moffat Date: Sun, 21 Jan 2018 22:20:33 +0100 Subject: [PATCH] Added support for returning customers --- app.js | 1 + package.json | 1 + public/javascripts/expressCart.js | 96 ++++++++++-- public/javascripts/expressCart.min.js | 2 +- public/themes/Cloth/pay.hbs | 54 +++++-- routes/admin.js | 138 +++++++++++++---- routes/index.js | 206 ++++++++++++++++++++++++++ views/customer.hbs | 49 ++++++ views/customers.hbs | 64 ++++++++ views/forgotten.hbs | 9 ++ views/partials/menu.hbs | 2 + views/pay.hbs | 58 ++++++-- views/reset.hbs | 15 ++ 13 files changed, 627 insertions(+), 68 deletions(-) create mode 100644 views/customer.hbs create mode 100644 views/customers.hbs create mode 100644 views/forgotten.hbs create mode 100644 views/reset.hbs diff --git a/app.js b/app.js index 21337d8..4a5e632 100644 --- a/app.js +++ b/app.js @@ -288,6 +288,7 @@ MongoClient.connect(config.databaseConnectionString, {}, (err, client) => { db.orders = db.collection('orders'); db.pages = db.collection('pages'); db.menu = db.collection('menu'); + db.customers = db.collection('customers'); // add db to app for routes app.db = db; diff --git a/package.json b/package.json index 8362c1d..37e8fce 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "nodemailer": "^4.4.1", "numeral": "^2.0.6", "paypal-rest-sdk": "^1.6.9", + "rand-token": "^0.4.0", "rimraf": "^2.6.2", "sitemap": "^1.6.0", "stripe": "^5.4.0", diff --git a/public/javascripts/expressCart.js b/public/javascripts/expressCart.js index 19777f0..9c737cb 100644 --- a/public/javascripts/expressCart.js +++ b/public/javascripts/expressCart.js @@ -294,6 +294,79 @@ $(document).ready(function (){ } }); + $('#customerLogout').on('click', function(e){ + $.ajax({ + method: 'POST', + url: '/customer/logout', + data: {} + }) + .done(function(msg){ + location.reload(); + }); + }); + + $('#createCustomerAccount').validator().on('click', function(e){ + e.preventDefault(); + if($('#shipping-form').validator('validate').has('.has-error').length === 0){ + $.ajax({ + method: 'POST', + url: '/customer/create', + 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() + } + }) + .done(function(msg){ + // Just reload to fill in the form from session + location.reload(); + }) + .fail(function(msg){ + showNotification(msg.responseJSON.err, 'danger'); + }); + } + }); + + // call update settings API + $('#customerLogin').on('click', function(e){ + if(!e.isDefaultPrevented()){ + e.preventDefault(); + $.ajax({ + method: 'POST', + url: '/customer/login_action', + data: { + loginEmail: $('#customerLoginEmail').val(), + loginPassword: $('#customerLoginPassword').val() + } + }) + .done(function(msg){ + var customer = msg.customer; + // Fill in customer form + $('#shipEmail').val(customer.email); + $('#shipFirstname').val(customer.firstName); + $('#shipLastname').val(customer.lastName); + $('#shipAddr1').val(customer.address1); + $('#shipAddr2').val(customer.address2); + $('#shipCountry').val(customer.country); + $('#shipState').val(customer.state); + $('#shipPostcode').val(customer.postcode); + $('#shipPhoneNumber').val(customer.phone); + location.reload(); + }) + .fail(function(msg){ + showNotification(msg.responseJSON.err, 'danger'); + }); + } + e.preventDefault(); + }); + $(document).on('click', '.image-next', function(e){ var thumbnails = $('.thumbnail-image'); var index = 0; @@ -473,7 +546,7 @@ $(document).ready(function (){ } }); - // applies an product filter + // applies an product filter $(document).on('click', '#btn_product_filter', function(e){ if($('#product_filter').val() !== ''){ window.location.href = '/admin/products/filter/' + $('#product_filter').val(); @@ -491,22 +564,21 @@ $(document).ready(function (){ } }); + // applies an product filter + $(document).on('click', '#btn_customer_filter', function(e){ + if($('#customer_filter').val() !== ''){ + window.location.href = '/admin/customers/filter/' + $('#customer_filter').val(); + }else{ + showNotification('Please enter a keyword to filter', 'danger'); + } + }); + // resets the order filter $(document).on('click', '#btn_search_reset', function(e){ window.location.replace('/'); }); - // resets the product filter - $(document).on('click', '#btn_product_reset', function(e){ - window.location.href = '/admin/products'; - }); - - // resets the order filter - $(document).on('click', '#btn_order_reset', function(e){ - window.location.href = '/admin/orders'; - }); - - // search button click event + // search button click event $(document).on('click', '#btn_search', function(e){ e.preventDefault(); if($('#frm_search').val().trim() === ''){ diff --git a/public/javascripts/expressCart.min.js b/public/javascripts/expressCart.min.js index c9bce16..c679684 100644 --- a/public/javascripts/expressCart.min.js +++ b/public/javascripts/expressCart.min.js @@ -1 +1 @@ -$(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()),$(".shipping-form input").each(function(t){$(this).wrap("
");var e=$(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(){""===$(this).val()&&($(this).next().addClass("hidden"),$(this).next().removeClass("floatLabel"))}),$(".menu-btn").on("click",function(t){t.preventDefault()}),$("#sendTestEmail").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/testEmail"}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseText,"danger")})}),$("#footerHtml").length){var t=window.CodeMirror.fromTextArea(document.getElementById("footerHtml"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1});t.setValue(t.getValue())}if($("#googleAnalytics").length&&window.CodeMirror.fromTextArea(document.getElementById("googleAnalytics"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1}),$("#customCss").length){var e=window.CodeMirror.fromTextArea(document.getElementById("customCss"),{mode:"text/css",tabMode:"indent",theme:"flatly",lineNumbers:!0}),a=window.cssbeautify(e.getValue(),{indent:" ",autosemicolon:!0});e.setValue(a)}if($("table").each(function(){$(this).addClass("table table-hover")}),$("#frmProductTags").tokenfield(),$(document).on("click",".dashboard_list",function(t){window.document.location=$(this).attr("href")}).hover(function(){$(this).toggleClass("hover")}),$(".product-title").dotdotdot({ellipsis:"..."}),$("input[class='published_state']").change(function(){$.ajax({method:"POST",url:"/admin/product/published_state",data:{id:this.id,state:this.checked}}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseText,"danger")})}),$(document).on("click",".btn-qty-minus",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())-1),cartUpdate(e)}),$(document).on("click",".btn-qty-add",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())+1),cartUpdate(e)}),$(document).on("click",".orderFilterByStatus",function(t){t.preventDefault(),window.location="/admin/orders/bystatus/"+$("#orderStatusFilter").val()}),$("#pager").length){var o=$("#pageNum").val(),i=$("#productsPerPage").val(),n=$("#totalProductCount").val(),r=$("#paginateUrl").val(),c=$("#searchTerm").val();""!==c&&(c+="/");var s="/"+r+"/"+c+"{{number}}",d=Math.ceil(n/i);parseInt(n)>parseInt(i)&&$("#pager").bootpag({total:d,page:o,maxVisible:5,href:s})}if($(document).on("click","#btnPageUpdate",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/settings/pages/update",data:{page_id:$("#page_id").val(),pageName:$("#pageName").val(),pageSlug:$("#pageSlug").val(),pageEnabled:$("#pageEnabled").is(":checked"),pageContent:$("#pageContent").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product_opt_remove",function(t){t.preventDefault();var e=$(this).closest("li").find(".opt-name").html();$.ajax({method:"POST",url:"/admin/settings/option/remove/",data:{productId:$("#frmProductId").val(),optName:e}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#product_opt_add",function(t){t.preventDefault();var e=$("#product_optName").val(),a=$("#product_optLabel").val(),o=$("#product_optType").val(),i=$("#product_optOptions").val(),n={};""!==$("#productOptJson").val()&&(n=JSON.parse($("#productOptJson").val()));var r='
  • ';r+='
    ',r+='
    '+e+"
    ",r+='
    '+a+"
    ",r+='
    '+o+"
    ",r+='
    '+i+"
    ",r+='
    ',r+='',r+="
  • ",$("#product_opt_wrapper").append(r),n[e]={optName:e,optLabel:a,optType:o,optOptions:$.grep(i.split(","),function(t){return 0===t||t})},$("#productOptJson").val(JSON.stringify(n)),$("#product_optName").val(""),$("#product_optLabel").val(""),$("#product_optOptions").val("")}),$("#stripeButton").validator().on("click",function(t){if(t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length){window.StripeCheckout.configure({key:$("#stripeButton").data("key"),image:$("#stripeButton").data("image"),locale:"auto",token:function(t){$("#shipping-form").append(''),$("#shipping-form").submit()}}).open({name:$("#stripeButton").data("name"),description:$("#stripeButton").data("description"),zipCode:$("#stripeButton").data("zipCode"),amount:$("#stripeButton").data("amount"),currency:$("#stripeButton").data("currency")})}}),$("#settingsForm").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$("#footerHtml_input").val($(".CodeMirror")[0].CodeMirror.getValue()),$("#googleAnalytics_input").val($(".CodeMirror")[1].CodeMirror.getValue()),$("#customCss_input").val($(".CodeMirror")[2].CodeMirror.getValue()),$.ajax({method:"POST",url:"/admin/settings/update",data:$("#settingsForm").serialize()}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}))}),$(document).on("click",".image-next",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a+1===e.length||a+1<0?0:a+1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click",".image-prev",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a-1===e.length||a-1<0?e.length-1:a-1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click","#orderStatusUpdate",function(t){$.ajax({method:"POST",url:"/admin/order/statusupdate",data:{order_id:$("#order_id").val(),status:$("#orderStatus").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product-add-to-cart",function(t){var e=getSelectedOptions();$.ajax({method:"POST",url:"/admin/product/addtocart",data:{productId:$("#productId").val(),productQuantity:$("#product_quantity").val(),productOptions:JSON.stringify(e)}}).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 e="/product/"+$(this).attr("data-id");$(this).attr("data-link")&&(e="/product/"+$(this).attr("data-link")),"true"===$(this).attr("data-has-options")?window.location=e:$.ajax({method:"POST",url:"/admin/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:"/admin/product/emptycart"}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success",!0)})}),$(".qty-btn-minus").on("click",function(){$(this).parent().siblings("input").val(parseInt($(this).parent().siblings("input").val())-1)}),$(".qty-btn-plus").on("click",function(){$(this).parent().siblings("input").val(parseInt($(this).parent().siblings("input").val())+1)}),$(".thumbnail-image").on("click",function(){$("#product-title-image").attr("src",$(this).attr("src"))}),$(".set-as-main-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/setasmainimage",data:{product_id:$("#frmProductId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".btn-delete-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/deleteimage",data:{product_id:$("#frmProductId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#validate_permalink",function(t){""!==$("#frmProductPermalink").val()?$.ajax({method:"POST",url:"/admin/api/validate_permalink",data:{permalink:$("#frmProductPermalink").val(),docId:$("#frmProductId").val()}}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseText,"danger")}):showNotification("Please enter a permalink to validate","danger")}),$(document).on("click","#btn_product_filter",function(t){""!==$("#product_filter").val()?window.location.href="/admin/products/filter/"+$("#product_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_order_filter",function(t){""!==$("#order_filter").val()?window.location.href="/admin/orders/filter/"+$("#order_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_search_reset",function(t){window.location.replace("/")}),$(document).on("click","#btn_product_reset",function(t){window.location.href="/admin/products"}),$(document).on("click","#btn_order_reset",function(t){window.location.href="/admin/orders"}),$(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()}),$(document).on("click","#frm_edit_product_save",function(t){""===$("#frmProductPermalink").val()&&""!==$("#frmProductTitle").val()&&$("#frmProductPermalink").val(slugify($("#frmProductTitle").val()))}),""!==$("#input_notify_message").val()){var l=$("#input_notify_message").val(),u=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(l,u,!1)}});function deleteFromCart(t){$.ajax({method:"POST",url:"/admin/product/removefromcart",data:{cart_index:t}}).done(function(t){$("#cart-count").text(t.totalCartItems),0===t.totalCartItems?(showNotification(t.message,"success"),setTimeout(function(){window.location="/"},3700)):showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function slugify(t){return $.trim(t).replace(/[^a-z0-9-æøå]/gi,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").replace(/æ/gi,"ae").replace(/ø/gi,"oe").replace(/å/gi,"a").toLowerCase()}function cartUpdate(t){0===$(t).val()?deleteFromCart($(t).attr("data-id")):""!==$(t).val()&&updateCart()}function updateCart(){var t=[];$(".cart-product-quantity").each(function(){var e={cartIndex:$(this).attr("id"),itemQuantity:$(this).val(),productId:$(this).attr("data-id")};"0"===$(this).val()?deleteFromCart($(this).attr("data-id")):t.push(e)}),$.ajax({method:"POST",url:"/admin/product/updatecart",data:{items:JSON.stringify(t)}}).done(function(t){updateCartDiv(),$("#cart-count").text(t.totalCartItems)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}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(){t[$(this).attr("name")]=$(this).val()}),t}function showNotification(t,e,a){a=a||!1,$("#notify_message").removeClass(),$("#notify_message").addClass("alert-"+e),$("#notify_message").html(t),$("#notify_message").slideDown(600).delay(2500).slideUp(600,function(){!0===a&&location.reload()})}function searchForm(t){$("form#"+t).submit()} \ No newline at end of file +function deleteFromCart(t){$.ajax({method:"POST",url:"/admin/product/removefromcart",data:{cart_index:t}}).done(function(t){$("#cart-count").text(t.totalCartItems),0===t.totalCartItems?(showNotification(t.message,"success"),setTimeout(function(){window.location="/"},3700)):showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}function slugify(t){return $.trim(t).replace(/[^a-z0-9-æøå]/gi,"-").replace(/-+/g,"-").replace(/^-|-$/g,"").replace(/æ/gi,"ae").replace(/ø/gi,"oe").replace(/å/gi,"a").toLowerCase()}function cartUpdate(t){0===$(t).val()?deleteFromCart($(t).attr("data-id")):""!==$(t).val()&&updateCart()}function updateCart(){var t=[];$(".cart-product-quantity").each(function(){var e={cartIndex:$(this).attr("id"),itemQuantity:$(this).val(),productId:$(this).attr("data-id")};"0"===$(this).val()?deleteFromCart($(this).attr("data-id")):t.push(e)}),$.ajax({method:"POST",url:"/admin/product/updatecart",data:{items:JSON.stringify(t)}}).done(function(t){updateCartDiv(),$("#cart-count").text(t.totalCartItems)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}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(){t[$(this).attr("name")]=$(this).val()}),t}function showNotification(t,e,a){a=a||!1,$("#notify_message").removeClass(),$("#notify_message").addClass("alert-"+e),$("#notify_message").html(t),$("#notify_message").slideDown(600).delay(2500).slideUp(600,function(){!0===a&&location.reload()})}function searchForm(t){$("form#"+t).submit()}$(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()),$(".shipping-form input").each(function(t){$(this).wrap("
    ");var e=$(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(){""===$(this).val()&&($(this).next().addClass("hidden"),$(this).next().removeClass("floatLabel"))}),$(".menu-btn").on("click",function(t){t.preventDefault()}),$("#sendTestEmail").on("click",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/testEmail"}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseText,"danger")})}),$("#footerHtml").length){var t=window.CodeMirror.fromTextArea(document.getElementById("footerHtml"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1});t.setValue(t.getValue())}if($("#googleAnalytics").length&&window.CodeMirror.fromTextArea(document.getElementById("googleAnalytics"),{mode:"xml",tabMode:"indent",theme:"flatly",lineNumbers:!0,htmlMode:!0,fixedGutter:!1}),$("#customCss").length){var e=window.CodeMirror.fromTextArea(document.getElementById("customCss"),{mode:"text/css",tabMode:"indent",theme:"flatly",lineNumbers:!0}),a=window.cssbeautify(e.getValue(),{indent:" ",autosemicolon:!0});e.setValue(a)}if($("table").each(function(){$(this).addClass("table table-hover")}),$("#frmProductTags").tokenfield(),$(document).on("click",".dashboard_list",function(t){window.document.location=$(this).attr("href")}).hover(function(){$(this).toggleClass("hover")}),$(".product-title").dotdotdot({ellipsis:"..."}),$("input[class='published_state']").change(function(){$.ajax({method:"POST",url:"/admin/product/published_state",data:{id:this.id,state:this.checked}}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseText,"danger")})}),$(document).on("click",".btn-qty-minus",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())-1),cartUpdate(e)}),$(document).on("click",".btn-qty-add",function(t){var e=$(t.target).parent().parent().find(".cart-product-quantity");$(e).val(parseInt(e.val())+1),cartUpdate(e)}),$(document).on("click",".orderFilterByStatus",function(t){t.preventDefault(),window.location="/admin/orders/bystatus/"+$("#orderStatusFilter").val()}),$("#pager").length){var o=$("#pageNum").val(),i=$("#productsPerPage").val(),n=$("#totalProductCount").val(),r=$("#paginateUrl").val(),s=$("#searchTerm").val();""!==s&&(s+="/");var c="/"+r+"/"+s+"{{number}}",d=Math.ceil(n/i);parseInt(n)>parseInt(i)&&$("#pager").bootpag({total:d,page:o,maxVisible:5,href:c})}if($(document).on("click","#btnPageUpdate",function(t){t.preventDefault(),$.ajax({method:"POST",url:"/admin/settings/pages/update",data:{page_id:$("#page_id").val(),pageName:$("#pageName").val(),pageSlug:$("#pageSlug").val(),pageEnabled:$("#pageEnabled").is(":checked"),pageContent:$("#pageContent").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product_opt_remove",function(t){t.preventDefault();var e=$(this).closest("li").find(".opt-name").html();$.ajax({method:"POST",url:"/admin/settings/option/remove/",data:{productId:$("#frmProductId").val(),optName:e}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#product_opt_add",function(t){t.preventDefault();var e=$("#product_optName").val(),a=$("#product_optLabel").val(),o=$("#product_optType").val(),i=$("#product_optOptions").val(),n={};""!==$("#productOptJson").val()&&(n=JSON.parse($("#productOptJson").val()));var r='
  • ';r+='
    ',r+='
    '+e+"
    ",r+='
    '+a+"
    ",r+='
    '+o+"
    ",r+='
    '+i+"
    ",r+='
    ',r+='',r+="
  • ",$("#product_opt_wrapper").append(r),n[e]={optName:e,optLabel:a,optType:o,optOptions:$.grep(i.split(","),function(t){return 0===t||t})},$("#productOptJson").val(JSON.stringify(n)),$("#product_optName").val(""),$("#product_optLabel").val(""),$("#product_optOptions").val("")}),$("#stripeButton").validator().on("click",function(t){(t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length)&&window.StripeCheckout.configure({key:$("#stripeButton").data("key"),image:$("#stripeButton").data("image"),locale:"auto",token:function(t){$("#shipping-form").append(''),$("#shipping-form").submit()}}).open({name:$("#stripeButton").data("name"),description:$("#stripeButton").data("description"),zipCode:$("#stripeButton").data("zipCode"),amount:$("#stripeButton").data("amount"),currency:$("#stripeButton").data("currency")})}),$("#settingsForm").validator().on("submit",function(t){t.isDefaultPrevented()||(t.preventDefault(),$("#footerHtml_input").val($(".CodeMirror")[0].CodeMirror.getValue()),$("#googleAnalytics_input").val($(".CodeMirror")[1].CodeMirror.getValue()),$("#customCss_input").val($(".CodeMirror")[2].CodeMirror.getValue()),$.ajax({method:"POST",url:"/admin/settings/update",data:$("#settingsForm").serialize()}).done(function(t){showNotification(t.message,"success")}).fail(function(t){showNotification(t.responseJSON.message,"danger")}))}),$("#customerLogout").on("click",function(t){$.ajax({method:"POST",url:"/customer/logout",data:{}}).done(function(t){location.reload()})}),$("#createCustomerAccount").validator().on("click",function(t){t.preventDefault(),0===$("#shipping-form").validator("validate").has(".has-error").length&&$.ajax({method:"POST",url:"/customer/create",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()}}).done(function(t){location.reload()}).fail(function(t){showNotification(t.responseJSON.err,"danger")})}),$("#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 e=t.customer;$("#shipEmail").val(e.email),$("#shipFirstname").val(e.firstName),$("#shipLastname").val(e.lastName),$("#shipAddr1").val(e.address1),$("#shipAddr2").val(e.address2),$("#shipCountry").val(e.country),$("#shipState").val(e.state),$("#shipPostcode").val(e.postcode),$("#shipPhoneNumber").val(e.phone),location.reload()}).fail(function(t){showNotification(t.responseJSON.err,"danger")})),t.preventDefault()}),$(document).on("click",".image-next",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a+1===e.length||a+1<0?0:a+1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click",".image-prev",function(t){var e=$(".thumbnail-image"),a=0,o=0;$(".thumbnail-image").each(function(){$("#product-title-image").attr("src")===$(this).attr("src")&&(o=a-1===e.length||a-1<0?e.length-1:a-1),a++}),$("#product-title-image").attr("src",$(e).eq(o).attr("src"))}),$(document).on("click","#orderStatusUpdate",function(t){$.ajax({method:"POST",url:"/admin/order/statusupdate",data:{order_id:$("#order_id").val(),status:$("#orderStatus").val()}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click",".product-add-to-cart",function(t){var e=getSelectedOptions();$.ajax({method:"POST",url:"/admin/product/addtocart",data:{productId:$("#productId").val(),productQuantity:$("#product_quantity").val(),productOptions:JSON.stringify(e)}}).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 e="/product/"+$(this).attr("data-id");$(this).attr("data-link")&&(e="/product/"+$(this).attr("data-link")),"true"===$(this).attr("data-has-options")?window.location=e:$.ajax({method:"POST",url:"/admin/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:"/admin/product/emptycart"}).done(function(t){$("#cart-count").text(t.totalCartItems),updateCartDiv(),showNotification(t.message,"success",!0)})}),$(".qty-btn-minus").on("click",function(){$(this).parent().siblings("input").val(parseInt($(this).parent().siblings("input").val())-1)}),$(".qty-btn-plus").on("click",function(){$(this).parent().siblings("input").val(parseInt($(this).parent().siblings("input").val())+1)}),$(".thumbnail-image").on("click",function(){$("#product-title-image").attr("src",$(this).attr("src"))}),$(".set-as-main-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/setasmainimage",data:{product_id:$("#frmProductId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(".btn-delete-image").on("click",function(){$.ajax({method:"POST",url:"/admin/product/deleteimage",data:{product_id:$("#frmProductId").val(),productImage:$(this).attr("data-id")}}).done(function(t){showNotification(t.message,"success",!0)}).fail(function(t){showNotification(t.responseJSON.message,"danger")})}),$(document).on("click","#validate_permalink",function(t){""!==$("#frmProductPermalink").val()?$.ajax({method:"POST",url:"/admin/api/validate_permalink",data:{permalink:$("#frmProductPermalink").val(),docId:$("#frmProductId").val()}}).done(function(t){showNotification(t,"success")}).fail(function(t){showNotification(t.responseText,"danger")}):showNotification("Please enter a permalink to validate","danger")}),$(document).on("click","#btn_product_filter",function(t){""!==$("#product_filter").val()?window.location.href="/admin/products/filter/"+$("#product_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_order_filter",function(t){""!==$("#order_filter").val()?window.location.href="/admin/orders/filter/"+$("#order_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(document).on("click","#btn_customer_filter",function(t){""!==$("#customer_filter").val()?window.location.href="/admin/customers/filter/"+$("#customer_filter").val():showNotification("Please enter a keyword to filter","danger")}),$(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()}),$(document).on("click","#frm_edit_product_save",function(t){""===$("#frmProductPermalink").val()&&""!==$("#frmProductTitle").val()&&$("#frmProductPermalink").val(slugify($("#frmProductTitle").val()))}),""!==$("#input_notify_message").val()){var l=$("#input_notify_message").val(),u=$("#input_notify_messageType").val();$("#input_notify_message").val(""),$("#input_notify_messageType").val(""),showNotification(l,u,!1)}}); \ No newline at end of file diff --git a/public/themes/Cloth/pay.hbs b/public/themes/Cloth/pay.hbs index 8185c2a..28eddae 100644 --- a/public/themes/Cloth/pay.hbs +++ b/public/themes/Cloth/pay.hbs @@ -6,59 +6,93 @@
    Customer details
    + {{#unless session.customer}} + + {{/unless}}
    + {{#if session.customer}} +
    + +
    + {{/if}}
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    + {{#if session.customer}} {{#ifCond config.paymentGateway '==' 'paypal'}}
    {{/ifCond}} + {{/if}} + {{#unless session.customer}} +
    +

    Enter a password to create an account for next time

    +
    + +
    + Create account +
    + {{/unless}}
    + {{#if session.customer}} {{#ifCond config.paymentGateway '==' 'stripe'}}
    @@ -82,6 +116,8 @@
    {{/ifCond}} + {{/if}} +
    diff --git a/routes/admin.js b/routes/admin.js index 9e8a2cf..54a94c7 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -13,7 +13,7 @@ router.get('/', common.restrict, (req, res, next) => { // Admin section router.get('/orders', common.restrict, (req, res, next) => { - let db = req.app.db; + const db = req.app.db; // Top 10 products common.dbQuery(db.orders, {}, {'orderDate': -1}, 10, (err, orders) => { @@ -35,7 +35,7 @@ router.get('/orders', common.restrict, (req, res, next) => { // Admin section router.get('/orders/bystatus/:orderstatus', common.restrict, (req, res, next) => { - let db = req.app.db; + const db = req.app.db; if(typeof req.params.orderstatus === 'undefined'){ res.redirect('/admin/orders'); @@ -65,7 +65,7 @@ router.get('/orders/bystatus/:orderstatus', common.restrict, (req, res, next) => // render the editor router.get('/order/view/:id', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.orders.findOne({_id: common.getId(req.params.id)}, (err, result) => { if(err){ console.info(err.stack); @@ -91,7 +91,7 @@ router.get('/order/view/:id', common.restrict, (req, res) => { // Admin section router.get('/orders/filter/:search', common.restrict, (req, res, next) => { - let db = req.app.db; + const db = req.app.db; let searchTerm = req.params.search; let ordersIndex = req.app.ordersIndex; @@ -121,7 +121,7 @@ router.get('/orders/filter/:search', common.restrict, (req, res, next) => { // order product router.get('/order/delete/:id', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; // remove the article db.orders.remove({_id: common.getId(req.params.id)}, {}, (err, numRemoved) => { @@ -141,7 +141,7 @@ router.get('/order/delete/:id', common.restrict, (req, res) => { // update order status router.post('/order/statusupdate', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.orders.update({_id: common.getId(req.body.order_id)}, {$set: {orderStatus: req.body.status}}, {multi: false}, (err, numReplaced) => { if(err){ console.info(err.stack); @@ -152,7 +152,7 @@ router.post('/order/statusupdate', common.restrict, (req, res) => { // Admin section router.get('/products', common.restrict, (req, res, next) => { - let db = req.app.db; + const db = req.app.db; // get the top results common.dbQuery(db.products, {}, {'productAddedDate': -1}, 10, (err, topResults) => { if(err){ @@ -173,7 +173,7 @@ router.get('/products', common.restrict, (req, res, next) => { // Admin section router.post('/product/addtocart', (req, res, next) => { - let db = req.app.db; + const db = req.app.db; let productQuantity = req.body.productQuantity ? parseInt(req.body.productQuantity) : 1; // setup cart object if it doesn't exist @@ -240,7 +240,7 @@ router.post('/product/addtocart', (req, res, next) => { // Updates a single product quantity router.post('/product/updatecart', (req, res, next) => { - let db = req.app.db; + const db = req.app.db; let cartItems = JSON.parse(req.body.items); let hasError = false; @@ -310,7 +310,7 @@ router.post('/product/emptycart', (req, res, next) => { // Admin section router.get('/products/filter/:search', common.restrict, (req, res, next) => { - let db = req.app.db; + const db = req.app.db; let searchTerm = req.params.search; let productsIndex = req.app.productsIndex; @@ -358,7 +358,7 @@ router.get('/product/new', common.restrict, (req, res) => { // insert new product form action router.post('/product/insert', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; let doc = { productPermalink: req.body.frmProductPermalink, @@ -429,7 +429,7 @@ router.post('/product/insert', common.restrict, (req, res) => { // render the editor router.get('/product/edit/:id', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; common.getImages(req.params.id, req, res, (images) => { db.products.findOne({_id: common.getId(req.params.id)}, (err, result) => { @@ -460,7 +460,7 @@ router.get('/product/edit/:id', common.restrict, (req, res) => { // Update an existing product form action router.post('/product/update', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.products.findOne({_id: common.getId(req.body.frmProductId)}, (err, product) => { if(err){ @@ -540,7 +540,7 @@ router.post('/product/update', common.restrict, (req, res) => { // delete product router.get('/product/delete/:id', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; let rimraf = require('rimraf'); // remove the article @@ -568,7 +568,7 @@ router.get('/product/delete/:id', common.restrict, (req, res) => { // users router.get('/users', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.users.find({}).toArray((err, users) => { if(err){ console.info(err.stack); @@ -589,7 +589,7 @@ router.get('/users', common.restrict, (req, res) => { // edit user router.get('/user/edit/:id', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.users.findOne({_id: common.getId(req.params.id)}, (err, user) => { if(err){ console.info(err.stack); @@ -618,7 +618,7 @@ router.get('/user/edit/:id', common.restrict, (req, res) => { // update a user router.post('/user/update', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; let bcrypt = req.bcrypt; let isAdmin = req.body.user_admin === 'on' ? 'true' : 'false'; @@ -666,7 +666,7 @@ router.post('/user/update', common.restrict, (req, res) => { // insert a user router.post('/setup_action', (req, res) => { - let db = req.app.db; + const db = req.app.db; let bcrypt = req.bcrypt; let doc = { @@ -704,7 +704,7 @@ router.post('/setup_action', (req, res) => { // insert a user router.post('/user/insert', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; let bcrypt = req.bcrypt; let url = require('url'); @@ -765,6 +765,79 @@ router.post('/user/insert', common.restrict, (req, res) => { }); }); +// render the customer view +router.get('/customer/view/:id?', common.restrict, (req, res) => { + const db = req.app.db; + + console.log('here'); + + db.customers.findOne({_id: common.getId(req.params.id)}, (err, result) => { + if(err){ + console.info(err.stack); + } + + res.render('customer', { + title: 'View customer', + result: result, + admin: true, + session: req.session, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + config: common.getConfig(), + editor: true, + helpers: req.handlebars.helpers + }); + }); +}); + +// customers list +router.get('/customers', common.restrict, (req, res) => { + const db = req.app.db; + + db.customers.find({}).limit(20).sort({created: -1}).toArray((err, customers) => { + res.render('customers', { + title: 'Customers - List', + admin: true, + customers: customers, + session: req.session, + helpers: req.handlebars.helpers, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + config: common.getConfig() + }); + }); +}); + +// Filtered customers list +router.get('/customers/filter/:search', common.restrict, (req, res, next) => { + const db = req.app.db; + let searchTerm = req.params.search; + let customersIndex = req.app.customersIndex; + + let lunrIdArray = []; + customersIndex.search(searchTerm).forEach((id) => { + lunrIdArray.push(common.getId(id.ref)); + }); + + // we search on the lunr indexes + db.customers.find({_id: {$in: lunrIdArray}}).sort({created: -1}).toArray((err, customers) => { + if(err){ + console.error(colors.red('Error searching', err)); + } + res.render('customers', { + title: 'Customer results', + customers: customers, + admin: true, + config: common.getConfig(), + session: req.session, + searchTerm: searchTerm, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + helpers: req.handlebars.helpers + }); + }); +}); + // users new router.get('/user/new', common.restrict, (req, res) => { res.render('user_new', { @@ -780,7 +853,7 @@ router.get('/user/new', common.restrict, (req, res) => { // delete user router.get('/user/delete/:id', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; if(req.session.isAdmin === 'true'){ db.users.remove({_id: common.getId(req.params.id)}, {}, (err, numRemoved) => { if(err){ @@ -825,7 +898,7 @@ router.post('/settings/update', common.restrict, (req, res) => { // settings update router.post('/settings/option/remove', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.products.findOne({_id: common.getId(req.body.productId)}, (err, product) => { if(err){ console.info(err.stack); @@ -852,7 +925,7 @@ router.post('/settings/option/remove', common.restrict, (req, res) => { // settings update router.get('/settings/menu', common.restrict, async (req, res) => { - let db = req.app.db; + const db = req.app.db; res.render('settings_menu', { title: 'Cart menu', session: req.session, @@ -867,7 +940,7 @@ router.get('/settings/menu', common.restrict, async (req, res) => { // settings page list router.get('/settings/pages', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; common.dbQuery(db.pages, {}, null, null, async (err, pages) => { if(err){ console.info(err.stack); @@ -889,7 +962,7 @@ router.get('/settings/pages', common.restrict, (req, res) => { // settings pages new router.get('/settings/pages/new', common.restrict, async (req, res) => { - let db = req.app.db; + const db = req.app.db; res.render('settings_page_edit', { title: 'Static pages', @@ -906,7 +979,7 @@ router.get('/settings/pages/new', common.restrict, async (req, res) => { // settings pages editor router.get('/settings/pages/edit/:page', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.pages.findOne({_id: common.getId(req.params.page)}, async (err, page) => { if(err){ console.info(err.stack); @@ -942,7 +1015,7 @@ router.get('/settings/pages/edit/:page', common.restrict, (req, res) => { // settings update page router.post('/settings/pages/update', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; let doc = { pageName: req.body.pageName, @@ -982,7 +1055,7 @@ router.post('/settings/pages/update', common.restrict, (req, res) => { // settings delete page router.get('/settings/pages/delete/:page', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.pages.remove({_id: common.getId(req.params.page)}, {}, (err, numRemoved) => { if(err){ req.session.message = 'Error deleting page. Please try again.'; @@ -1040,7 +1113,7 @@ router.post('/settings/menu/save_order', common.restrict, (req, res) => { router.post('/api/validate_permalink', (req, res) => { // if doc id is provided it checks for permalink in any products other that one provided, // else it just checks for any products with that permalink - let db = req.app.db; + const db = req.app.db; let query = {}; if(typeof req.body.docId === 'undefined' || req.body.docId === ''){ @@ -1065,7 +1138,7 @@ router.post('/api/validate_permalink', (req, res) => { // update the published state based on an ajax call from the frontend router.post('/product/published_state', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; db.products.update({_id: common.getId(req.body.id)}, {$set: {productPublished: req.body.state}}, {multi: false}, (err, numReplaced) => { if(err){ @@ -1081,7 +1154,7 @@ router.post('/product/published_state', common.restrict, (req, res) => { // set as main product image router.post('/product/setasmainimage', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; // update the productImage to the db db.products.update({_id: common.getId(req.body.product_id)}, {$set: {productImage: req.body.productImage}}, {multi: false}, (err, numReplaced) => { @@ -1095,7 +1168,7 @@ router.post('/product/setasmainimage', common.restrict, (req, res) => { // deletes a product image router.post('/product/deleteimage', common.restrict, (req, res) => { - let db = req.app.db; + const db = req.app.db; let fs = require('fs'); let path = require('path'); @@ -1136,7 +1209,7 @@ router.post('/product/deleteimage', common.restrict, (req, res) => { let multer = require('multer'); let upload = multer({dest: 'public/uploads/'}); router.post('/file/upload', common.restrict, upload.single('upload_file'), (req, res, next) => { - let db = req.app.db; + const db = req.app.db; let fs = require('fs'); let path = require('path'); @@ -1195,6 +1268,7 @@ router.post('/file/upload', common.restrict, upload.single('upload_file'), (req, // delete a file via ajax request router.post('/testEmail', common.restrict, (req, res) => { let config = common.getConfig(); + // TODO: Should fix this to properly handle result common.sendEmail(config.emailAddress, 'expressCart test email', 'Your email settings are working'); res.status(200).json('Test email sent'); }); diff --git a/routes/index.js b/routes/index.js index 9abbea2..b440786 100644 --- a/routes/index.js +++ b/routes/index.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const colors = require('colors'); const _ = require('lodash'); +const randtoken = require('rand-token'); const common = require('./common'); router.get('/payment/:orderId', async (req, res, next) => { @@ -236,6 +237,211 @@ router.post('/login_action', (req, res) => { }); }); +// insert a customer +router.post('/customer/create', (req, res) => { + const db = req.app.db; + const bcrypt = req.bcrypt; + + let doc = { + email: req.body.email, + firstName: req.body.firstName, + lastName: req.body.lastName, + address1: req.body.address1, + address2: req.body.address2, + country: req.body.country, + state: req.body.state, + postcode: req.body.postcode, + phone: req.body.phone, + password: bcrypt.hashSync(req.body.password), + created: new Date() + }; + + // check for existing customer + db.customers.findOne({email: req.body.email}, (err, customer) => { + if(customer){ + res.status(404).json({ + err: 'A customer already exists with that email address' + }); + return; + } + // email is ok to be used. + db.customers.insertOne(doc, (err, newCustomer) => { + if(err){ + if(newCustomer){ + console.error(colors.red('Failed to insert customer: ' + err)); + res.status(400).json({ + err: 'A customer already exists with that email address' + }); + return; + } + console.error(colors.red('Failed to insert customer: ' + err)); + res.status(400).json({ + err: 'Customer creation failed.' + }); + return; + } + + // Customer creation successful + req.session.customer = newCustomer.ops[0]; + res.status(200).json({ + message: 'Successfully logged in', + customer: newCustomer + }); + }); + }); +}); + +// login the customer and check the password +router.post('/customer/login_action', (req, res) => { + let db = req.app.db; + let bcrypt = req.bcrypt; + + db.customers.findOne({email: req.body.loginEmail}, (err, customer) => { + if(err){ + // An error accurred + return res.status(400).json({ + err: 'Access denied. Check password and try again.' + }); + } + + // check if customer exists with that email + if(customer === undefined || customer === null){ + return res.status(400).json({ + err: 'A customer with that email does not exist.' + }); + } + // we have a customer under that email so we compare the password + if(bcrypt.compareSync(req.body.loginPassword, customer.password) === false){ + // password is not correct + return res.status(400).json({ + err: 'Access denied. Check password and try again.' + }); + } + + // Customer login successful + req.session.customer = customer; + return res.status(200).json({ + message: 'Successfully logged in', + customer: customer + }); + }); +}); + +// customer forgotten password +router.get('/customer/forgotten', (req, res) => { + res.render('forgotten', { + title: 'Forgotten', + route: 'customer', + forgotType: 'customer', + config: common.getConfig(), + helpers: req.handlebars.helpers, + message: common.clearSessionValue(req.session, 'message'), + messageType: common.clearSessionValue(req.session, 'messageType'), + showFooter: 'showFooter' + }); +}); + +// forgotten password +router.post('/customer/forgotten_action', (req, res) => { + const db = req.app.db; + const config = common.getConfig(); + let passwordToken = randtoken.generate(30); + + // find the user + db.customers.findOne({email: req.body.email}, (err, customer) => { + // if we have a customer, set a token, expiry and email it + if(customer){ + let tokenExpiry = Date.now() + 3600000; + db.customers.update({email: req.body.email}, {$set: {resetToken: passwordToken, resetTokenExpiry: tokenExpiry}}, {multi: false}, (err, numReplaced) => { + // send forgotten password email + let mailOpts = { + to: req.body.email, + subject: 'Forgotten password request', + body: `You are receiving this because you (or someone else) have requested the reset of the password for your user account.\n\n + Please click on the following link, or paste this into your browser to complete the process:\n\n + ${config.baseUrl}/customer/reset/${passwordToken}\n\n + If you did not request this, please ignore this email and your password will remain unchanged.\n` + }; + + // send the email with token to the user + // TODO: Should fix this to properly handle result + common.sendEmail(mailOpts.to, mailOpts.subject, mailOpts.body); + req.session.message = 'An email has been sent to ' + req.body.email + ' with further instructions'; + req.session.message_type = 'success'; + return res.redirect('/customer/forgotten'); + }); + }else{ + req.session.message = 'Account does not exist'; + res.redirect('/customer/forgotten'); + } + }); +}); + +// reset password form +router.get('/customer/reset/:token', (req, res) => { + const db = req.app.db; + + // Find the customer using the token + db.customers.findOne({resetToken: req.params.token, resetTokenExpiry: {$gt: Date.now()}}, (err, customer) => { + if(!customer){ + req.session.message = 'Password reset token is invalid or has expired'; + req.session.message_type = 'danger'; + res.redirect('/forgot'); + return; + } + + // show the password reset form + res.render('reset', { + title: 'Reset password', + token: req.params.token, + route: 'customer', + config: common.getConfig(), + message: common.clearSessionValue(req.session, 'message'), + message_type: common.clearSessionValue(req.session, 'message_type'), + show_footer: 'show_footer', + helpers: req.handlebars.helpers + }); + }); +}); + +// reset password action +router.post('/customer/reset/:token', (req, res) => { + const db = req.app.db; + let bcrypt = req.bcrypt; + + // get the customer + db.customers.findOne({resetToken: req.params.token, resetTokenExpiry: {$gt: Date.now()}}, (err, customer) => { + if(!customer){ + req.session.message = 'Password reset token is invalid or has expired'; + req.session.message_type = 'danger'; + return res.redirect('/forgot'); + } + + // update the password and remove the token + let newPassword = bcrypt.hashSync(req.body.password); + db.customers.update({email: customer.email}, {$set: {password: newPassword, resetToken: undefined, resetTokenExpiry: undefined}}, {multi: false}, (err, numReplaced) => { + let mailOpts = { + to: customer.email, + subject: 'Password successfully reset', + body: 'This is a confirmation that the password for your account ' + customer.email + ' has just been changed successfully.\n' + }; + + // TODO: Should fix this to properly handle result + common.sendEmail(mailOpts.to, mailOpts.subject, mailOpts.body); + req.session.message = 'Password successfully updated'; + req.session.message_type = 'success'; + return res.redirect('/pay'); + }); + return''; + }); +}); + +// logout the customer +router.post('/customer/logout', (req, res) => { + req.session.customer = null; + res.status(200).json({}); +}); + // search products router.get('/search/:searchTerm/:pageNum?', (req, res) => { let db = req.app.db; diff --git a/views/customer.hbs b/views/customer.hbs new file mode 100644 index 0000000..56a3dae --- /dev/null +++ b/views/customer.hbs @@ -0,0 +1,49 @@ +{{> menu}} +
    +
    +
    +

    Customer

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Email:{{result.email}}
    Name:{{result.firstName}} {{result.lastName}}
    Address 1:{{result.address1}}
    Address 2:{{result.address2}}
    Country:{{result.country}}
    State:{{result.state}}
    Postcode:{{result.postcode}}
    Phone number:{{result.phone}}
    Creation date:{{formatDate result.created "DD/MM/YYYY hh:mmA"}}
    +
    +
    + +
    \ No newline at end of file diff --git a/views/customers.hbs b/views/customers.hbs new file mode 100644 index 0000000..9924864 --- /dev/null +++ b/views/customers.hbs @@ -0,0 +1,64 @@ +{{> menu}} +
    +
    +

    Customers

    +
    + + {{#if customers}} +
    + +
    + {{else}} +

    + No orders found +

    + {{/if}} +
    \ No newline at end of file diff --git a/views/forgotten.hbs b/views/forgotten.hbs new file mode 100644 index 0000000..dc6d462 --- /dev/null +++ b/views/forgotten.hbs @@ -0,0 +1,9 @@ +
    + +
    diff --git a/views/partials/menu.hbs b/views/partials/menu.hbs index dd21a61..36b4c82 100644 --- a/views/partials/menu.hbs +++ b/views/partials/menu.hbs @@ -6,6 +6,8 @@
  •   List
  • Orders
  •   List
  • +
  • Customers
  • +
  •   List
  • Users
  •   New
  •   Edit
  • diff --git a/views/pay.hbs b/views/pay.hbs index 885f382..aedcdb1 100644 --- a/views/pay.hbs +++ b/views/pay.hbs @@ -6,64 +6,93 @@
    Customer details
    + {{#unless session.customer}} + + {{/unless}}
    + {{#if session.customer}} +
    + +
    + {{/if}}
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - +
    - -
    -
    -
    -
    - +
    + {{#if session.customer}} {{#ifCond config.paymentGateway '==' 'paypal'}}
    {{/ifCond}} + {{/if}} + {{#unless session.customer}} +
    +

    Enter a password to create an account for next time

    +
    + +
    + Create account +
    + {{/unless}}
    + {{#if session.customer}} {{#ifCond config.paymentGateway '==' 'stripe'}}
    @@ -87,6 +116,7 @@
    {{/ifCond}} + {{/if}}
    diff --git a/views/reset.hbs b/views/reset.hbs new file mode 100644 index 0000000..27fe59d --- /dev/null +++ b/views/reset.hbs @@ -0,0 +1,15 @@ +
    + +