Customer account page (#124)
* Adding customer login & account page support * Added account link * Sorting orders on account page * Small UI fixes * Removed debugs * Added tests Co-authored-by: Mark Moffat <mark@Marks-MacBook-Pro-2.local>master
							parent
							
								
									6b9f4b2e6b
								
							
						
					
					
						commit
						78e06e511b
					
				|  | @ -2,6 +2,9 @@ | |||
|     "$id": "editCustomer", | ||||
|     "type": "object", | ||||
|     "properties": { | ||||
|         "company": { | ||||
|             "type": "string" | ||||
|         }, | ||||
|         "email": { | ||||
|             "type": "string", | ||||
|             "format": "emailAddress" | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -237,6 +237,27 @@ $(document).ready(function (){ | |||
|         e.preventDefault(); | ||||
|     }); | ||||
| 
 | ||||
|     $('#customerloginForm').on('click', function(e){ | ||||
|         if(!e.isDefaultPrevented()){ | ||||
|             e.preventDefault(); | ||||
|             $.ajax({ | ||||
|                 method: 'POST', | ||||
|                 url: '/customer/login_action', | ||||
|                 data: { | ||||
|                     loginEmail: $('#email').val(), | ||||
|                     loginPassword: $('#password').val() | ||||
|                 } | ||||
|             }) | ||||
|             .done(function(msg){ | ||||
|                 window.location = '/customer/account'; | ||||
|             }) | ||||
|             .fail(function(msg){ | ||||
|                 showNotification(msg.responseJSON.message, 'danger'); | ||||
|             }); | ||||
|         } | ||||
|         e.preventDefault(); | ||||
|     }); | ||||
| 
 | ||||
|     // call update settings API
 | ||||
|     $('#customerLogin').on('click', function(e){ | ||||
|         if(!e.isDefaultPrevented()){ | ||||
|  | @ -270,6 +291,37 @@ $(document).ready(function (){ | |||
|         e.preventDefault(); | ||||
|     }); | ||||
| 
 | ||||
|     // Customer saving own details
 | ||||
|     $('#customerSave').validator().on('click', function(e){ | ||||
|         e.preventDefault(); | ||||
|         if($('#customer-form').validator('validate').has('.has-error').length === 0){ | ||||
|             $.ajax({ | ||||
|                 method: 'POST', | ||||
|                 url: '/customer/update', | ||||
|                 data: { | ||||
|                     email: $('#shipEmail').val(), | ||||
|                     company: $('#shipCompany').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(){ | ||||
|                 showNotification('Customer saved', 'success'); | ||||
|             }) | ||||
|             .fail(function(msg){ | ||||
|                 showNotification(msg.responseJSON.message, 'danger'); | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     $(document).on('click', '.image-next', function(e){ | ||||
|         var thumbnails = $('.thumbnail-image'); | ||||
|         var index = 0; | ||||
|  |  | |||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -244,8 +244,6 @@ body .popover { | |||
| [role="main"] { | ||||
|   padding-top: 20px; | ||||
|   margin-bottom: 20px; | ||||
|   min-height: 100%; | ||||
|   height: 100%; | ||||
| } | ||||
| @media (min-width: 768px) { | ||||
|   [role="main"] { | ||||
|  |  | |||
|  | @ -1 +1 @@ | |||
| body,html{height:100%;background-color:#f8f9fa!important}.content-body{margin-bottom:0}.feather{width:16px;height:16px}.btn-outline-danger,.btn-outline-info,.btn-outline-primary,.btn-outline-success,.btn-outline-warning{background-color:#fff}.btn-outline-primary{color:#fff!important;background-color:#000;border-color:#000}.btn-outline-danger{color:#dc3545!important}.btn-outline-danger:hover{color:#fff!important}.has-error div,.has-error input,.has-error textarea{border-color:#cc4135}#frm_search,.search-bar-input,.search-bar-input .btn{padding-top:10px;height:45px}.footer{padding-top:20px}.admin-menu{padding-left:0}.admin-menu .list-group-item{border-bottom:none}.navbarMenuWrapper{background-color:#f5f5f5}.navbarMenu>ul>li>a:hover{color:#cc4135!important}.navbarMenu{padding-right:0;padding-left:0}.product-wrapper>a:hover{color:#cc4135!important}#navbar,#navbar>.navbar-nav,#navbar>.navbar-nav>li>a,.navbar-header,.navbar-static-top{margin-bottom:0;height:100px!important}#navbar>.navbar-nav>li>a{padding-top:35px}.pagination>li>a{background-color:#cc4135!important}.admin-card{background-color:#fff!important;border-radius:5px;padding:15px;margin:10px;box-shadow:0 0 1px rgba(0,0,0,.12),0 1px 6px rgba(0,0,0,.03),0 6px 10px -8px rgba(0,0,0,.1)}.admin-card-body{padding:5px}body .popover{display:none!important}.navbar-brand{color:#cc4135!important;letter-spacing:4px;padding-left:20px!important;padding-top:0!important;height:80px!important;font-size:55px!important}.navbar-brand,.navbar-brand-image{height:80px;padding-left:10px;padding-top:10px}.navbar-default .badge{background-color:#cc4135}#empty-cart:active,#empty-cart:active:hover,#empty-cart:focus,#empty-cart:hover,.pushy-link:active,.pushy-link:active:hover,.pushy-link:focus,.pushy-link:hover{border-color:#cc4135;background-color:#cc4135}.navActive>a{margin-bottom:0;padding-top:15px;border-bottom:5px solid #cc4135}#navbar,#navbar>.navbar-nav,#navbar>.navbar-nav>li>a,.navbar-header,.navbar-static-top{background-color:#fff}.navbar-default .navbar-nav>li>a{color:#838b8f;font-size:20px}.global-result-type{color:#8d8d8d}.global-result:hover{background-color:#007bff}.global-result:hover .global-result-detail,.global-result:hover .global-result-type,.global-result:hover .global-result-type .fal{color:#fff!important}.global-result a{text-decoration:none!important}.global-result:hover{cursor:pointer}.global-result:first-child{border-top-left-radius:0;border-top-right-radius:0}.global-result{border-left:0;border-right:0}.global-search-modal-content,.global-search-modal-header{background-color:transparent;border:none}#global-search-results{padding-right:0;border-bottom-left-radius:.3rem;border-bottom-right-radius:.3rem}.global-search-form{margin-bottom:0}#global-search-value{border-bottom-right-radius:0}.search-input-addon{border-bottom-left-radius:0}.sidebar{position:fixed;top:0;bottom:0;left:0;z-index:100;padding:0;background-color:#fff;box-shadow:inset -1px 0 0 rgba(0,0,0,.1)}.sidebar-sticky{position:relative;top:0;height:100vh;padding-top:15px;overflow-x:hidden;overflow-y:auto}@supports ((position:-webkit-sticky) or (position:sticky)){.sidebar-sticky{position:-webkit-sticky;position:sticky}}.sidebar .nav-link{font-weight:400;color:#333}.sidebar .nav-link .feather{margin-right:4px;color:#999}.sidebar .nav-link.active .feather,.sidebar .nav-link:hover .feather{color:inherit}.sidebar-heading{font-size:.75rem;text-transform:uppercase}.sidebar-brand{width:80%}.sidebar-search{text-align:center;width:20%}.sidebar-addon{height:40px}.sidebar-link{display:inline-block;width:80%}.sidebar-link-addon{display:inline-block;width:10%}[role=main]{padding-top:20px;margin-bottom:20px;min-height:100%;height:100%}@media (min-width:768px){[role=main]{padding-top:20px;padding-bottom:20px}}@media only screen and (max-width:768px){.navbar-default .navbar-brand{padding-top:10px}.navbar-default .navbar-nav>li>a{font-size:16px}.searchBarWrapper{padding-top:10px}.navbarMenuWrapper{padding-left:0;padding-right:0}.navbarMenuOuter{padding-left:0;padding-right:0}.navActive>a{color:#fff!important}.navbarMenu{padding-right:7.5px;padding-left:7.5px}.navActive>a{color:#fff!important;background-color:#cc4135;border-bottom:none}.footer{padding-top:10px}} | ||||
| body,html{height:100%;background-color:#f8f9fa!important}.content-body{margin-bottom:0}.feather{width:16px;height:16px}.btn-outline-danger,.btn-outline-info,.btn-outline-primary,.btn-outline-success,.btn-outline-warning{background-color:#fff}.btn-outline-primary{color:#fff!important;background-color:#000;border-color:#000}.btn-outline-danger{color:#dc3545!important}.btn-outline-danger:hover{color:#fff!important}.has-error div,.has-error input,.has-error textarea{border-color:#cc4135}#frm_search,.search-bar-input,.search-bar-input .btn{padding-top:10px;height:45px}.footer{padding-top:20px}.admin-menu{padding-left:0}.admin-menu .list-group-item{border-bottom:none}.navbarMenuWrapper{background-color:#f5f5f5}.navbarMenu>ul>li>a:hover{color:#cc4135!important}.navbarMenu{padding-right:0;padding-left:0}.product-wrapper>a:hover{color:#cc4135!important}#navbar,#navbar>.navbar-nav,#navbar>.navbar-nav>li>a,.navbar-header,.navbar-static-top{margin-bottom:0;height:100px!important}#navbar>.navbar-nav>li>a{padding-top:35px}.pagination>li>a{background-color:#cc4135!important}.admin-card{background-color:#fff!important;border-radius:5px;padding:15px;margin:10px;box-shadow:0 0 1px rgba(0,0,0,.12),0 1px 6px rgba(0,0,0,.03),0 6px 10px -8px rgba(0,0,0,.1)}.admin-card-body{padding:5px}body .popover{display:none!important}.navbar-brand{color:#cc4135!important;letter-spacing:4px;padding-left:20px!important;padding-top:0!important;height:80px!important;font-size:55px!important}.navbar-brand,.navbar-brand-image{height:80px;padding-left:10px;padding-top:10px}.navbar-default .badge{background-color:#cc4135}#empty-cart:active,#empty-cart:active:hover,#empty-cart:focus,#empty-cart:hover,.pushy-link:active,.pushy-link:active:hover,.pushy-link:focus,.pushy-link:hover{border-color:#cc4135;background-color:#cc4135}.navActive>a{margin-bottom:0;padding-top:15px;border-bottom:5px solid #cc4135}#navbar,#navbar>.navbar-nav,#navbar>.navbar-nav>li>a,.navbar-header,.navbar-static-top{background-color:#fff}.navbar-default .navbar-nav>li>a{color:#838b8f;font-size:20px}.global-result-type{color:#8d8d8d}.global-result:hover{background-color:#007bff}.global-result:hover .global-result-detail,.global-result:hover .global-result-type,.global-result:hover .global-result-type .fal{color:#fff!important}.global-result a{text-decoration:none!important}.global-result:hover{cursor:pointer}.global-result:first-child{border-top-left-radius:0;border-top-right-radius:0}.global-result{border-left:0;border-right:0}.global-search-modal-content,.global-search-modal-header{background-color:transparent;border:none}#global-search-results{padding-right:0;border-bottom-left-radius:.3rem;border-bottom-right-radius:.3rem}.global-search-form{margin-bottom:0}#global-search-value{border-bottom-right-radius:0}.search-input-addon{border-bottom-left-radius:0}.sidebar{position:fixed;top:0;bottom:0;left:0;z-index:100;padding:0;background-color:#fff;box-shadow:inset -1px 0 0 rgba(0,0,0,.1)}.sidebar-sticky{position:relative;top:0;height:100vh;padding-top:15px;overflow-x:hidden;overflow-y:auto}@supports ((position:-webkit-sticky) or (position:sticky)){.sidebar-sticky{position:-webkit-sticky;position:sticky}}.sidebar .nav-link{font-weight:400;color:#333}.sidebar .nav-link .feather{margin-right:4px;color:#999}.sidebar .nav-link.active .feather,.sidebar .nav-link:hover .feather{color:inherit}.sidebar-heading{font-size:.75rem;text-transform:uppercase}.sidebar-brand{width:80%}.sidebar-search{text-align:center;width:20%}.sidebar-addon{height:40px}.sidebar-link{display:inline-block;width:80%}.sidebar-link-addon{display:inline-block;width:10%}[role=main]{padding-top:20px;margin-bottom:20px}@media (min-width:768px){[role=main]{padding-top:20px;padding-bottom:20px}}@media only screen and (max-width:768px){.navbar-default .navbar-brand{padding-top:10px}.navbar-default .navbar-nav>li>a{font-size:16px}.searchBarWrapper{padding-top:10px}.navbarMenuWrapper{padding-left:0;padding-right:0}.navbarMenuOuter{padding-left:0;padding-right:0}.navActive>a{color:#fff!important}.navbarMenu{padding-right:7.5px;padding-left:7.5px}.navActive>a{color:#fff!important;background-color:#cc4135;border-bottom:none}.footer{padding-top:10px}} | ||||
|  | @ -283,8 +283,6 @@ body .popover{display:none !important; } | |||
| [role="main"] { | ||||
|     padding-top: 20px; | ||||
|     margin-bottom: 20px; | ||||
|     min-height: 100%; | ||||
|     height: 100%; | ||||
| } | ||||
|    | ||||
| @media (min-width: 768px) { | ||||
|  |  | |||
|  | @ -3,7 +3,14 @@ const router = express.Router(); | |||
| const colors = require('colors'); | ||||
| const randtoken = require('rand-token'); | ||||
| const bcrypt = require('bcryptjs'); | ||||
| const common = require('../lib/common'); | ||||
| const { | ||||
|     getId, | ||||
|     clearSessionValue, | ||||
|     getCountryList, | ||||
|     mongoSanitize, | ||||
|     sendEmail, | ||||
|     clearCustomer | ||||
| } = require('../lib/common'); | ||||
| const rateLimit = require('express-rate-limit'); | ||||
| const { indexCustomers } = require('../lib/indexing'); | ||||
| const { validateJson } = require('../lib/schema'); | ||||
|  | @ -58,6 +65,7 @@ router.post('/customer/create', async (req, res) => { | |||
| 
 | ||||
|             // Set the customer into the session
 | ||||
|             req.session.customerPresent = true; | ||||
|             req.session.customerId = customerReturn._id; | ||||
|             req.session.customerEmail = customerReturn.email; | ||||
|             req.session.customerCompany = customerReturn.company; | ||||
|             req.session.customerFirstname = customerReturn.firstName; | ||||
|  | @ -118,11 +126,107 @@ router.post('/customer/save', async (req, res) => { | |||
|     res.status(200).json(customerObj); | ||||
| }); | ||||
| 
 | ||||
| // Get customer orders
 | ||||
| router.get('/customer/account', async (req, res) => { | ||||
|     const db = req.app.db; | ||||
|     const config = req.app.config; | ||||
| 
 | ||||
|     if(!req.session.customerPresent){ | ||||
|         res.redirect('/customer/login'); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const orders = await db.orders.find({ | ||||
|         orderCustomer: getId(req.session.customerId) | ||||
|     }) | ||||
|     .sort({ orderDate: -1 }) | ||||
|     .toArray(); | ||||
|     res.render(`${config.themeViews}customer-account`, { | ||||
|         title: 'Orders', | ||||
|         session: req.session, | ||||
|         orders, | ||||
|         message: clearSessionValue(req.session, 'message'), | ||||
|         messageType: clearSessionValue(req.session, 'messageType'), | ||||
|         countryList: getCountryList(), | ||||
|         config: req.app.config, | ||||
|         helpers: req.handlebars.helpers | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| // Update a customer
 | ||||
| router.post('/customer/update', async (req, res) => { | ||||
|     const db = req.app.db; | ||||
| 
 | ||||
|     if(!req.session.customerPresent){ | ||||
|         res.redirect('/customer/login'); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     const customerObj = { | ||||
|         company: req.body.company, | ||||
|         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 | ||||
|     }; | ||||
| 
 | ||||
|     const schemaResult = validateJson('editCustomer', customerObj); | ||||
|     if(!schemaResult.result){ | ||||
|         console.log('errors', schemaResult.errors); | ||||
|         res.status(400).json(schemaResult.errors); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // check for existing customer
 | ||||
|     const customer = await db.customers.findOne({ _id: getId(req.session.customerId) }); | ||||
|     if(!customer){ | ||||
|         res.status(400).json({ | ||||
|             message: 'Customer not found' | ||||
|         }); | ||||
|         return; | ||||
|     } | ||||
|     // Update customer
 | ||||
|     try{ | ||||
|         const updatedCustomer = await db.customers.findOneAndUpdate( | ||||
|             { _id: getId(req.session.customerId) }, | ||||
|             { | ||||
|                 $set: customerObj | ||||
|             }, { multi: false, returnOriginal: false } | ||||
|         ); | ||||
|         indexCustomers(req.app) | ||||
|         .then(() => { | ||||
|             // Set the customer into the session
 | ||||
|             req.session.customerEmail = customerObj.email; | ||||
|             req.session.customerCompany = customerObj.company; | ||||
|             req.session.customerFirstname = customerObj.firstName; | ||||
|             req.session.customerLastname = customerObj.lastName; | ||||
|             req.session.customerAddress1 = customerObj.address1; | ||||
|             req.session.customerAddress2 = customerObj.address2; | ||||
|             req.session.customerCountry = customerObj.country; | ||||
|             req.session.customerState = customerObj.state; | ||||
|             req.session.customerPostcode = customerObj.postcode; | ||||
|             req.session.customerPhone = customerObj.phone; | ||||
|             req.session.orderComment = req.body.orderComment; | ||||
| 
 | ||||
|             res.status(200).json({ message: 'Customer updated', customer: updatedCustomer.value }); | ||||
|         }); | ||||
|     }catch(ex){ | ||||
|         console.error(colors.red('Failed updating customer: ' + ex)); | ||||
|         res.status(400).json({ message: 'Failed to update customer' }); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| // Update a customer
 | ||||
| router.post('/admin/customer/update', restrict, async (req, res) => { | ||||
|     const db = req.app.db; | ||||
| 
 | ||||
|     const customerObj = { | ||||
|         company: req.body.company, | ||||
|         email: req.body.email, | ||||
|         firstName: req.body.firstName, | ||||
|         lastName: req.body.lastName, | ||||
|  | @ -145,7 +249,7 @@ router.post('/admin/customer/update', restrict, async (req, res) => { | |||
|     } | ||||
| 
 | ||||
|     // check for existing customer
 | ||||
|     const customer = await db.customers.findOne({ _id: common.getId(req.body.customerId) }); | ||||
|     const customer = await db.customers.findOne({ _id: getId(req.body.customerId) }); | ||||
|     if(!customer){ | ||||
|         res.status(400).json({ | ||||
|             message: 'Customer not found' | ||||
|  | @ -155,7 +259,7 @@ router.post('/admin/customer/update', restrict, async (req, res) => { | |||
|     // Update customer
 | ||||
|     try{ | ||||
|         const updatedCustomer = await db.customers.findOneAndUpdate( | ||||
|             { _id: common.getId(req.body.customerId) }, | ||||
|             { _id: getId(req.body.customerId) }, | ||||
|             { | ||||
|                 $set: customerObj | ||||
|             }, { multi: false, returnOriginal: false } | ||||
|  | @ -177,7 +281,7 @@ router.delete('/admin/customer', restrict, async (req, res) => { | |||
|     const db = req.app.db; | ||||
| 
 | ||||
|     // check for existing customer
 | ||||
|     const customer = await db.customers.findOne({ _id: common.getId(req.body.customerId) }); | ||||
|     const customer = await db.customers.findOne({ _id: getId(req.body.customerId) }); | ||||
|     if(!customer){ | ||||
|         res.status(400).json({ | ||||
|             message: 'Failed to delete customer. Customer not found' | ||||
|  | @ -186,7 +290,7 @@ router.delete('/admin/customer', restrict, async (req, res) => { | |||
|     } | ||||
|     // Update customer
 | ||||
|     try{ | ||||
|         await db.customers.deleteOne({ _id: common.getId(req.body.customerId) }); | ||||
|         await db.customers.deleteOne({ _id: getId(req.body.customerId) }); | ||||
|         indexCustomers(req.app) | ||||
|         .then(() => { | ||||
|             res.status(200).json({ message: 'Customer deleted' }); | ||||
|  | @ -201,7 +305,7 @@ router.delete('/admin/customer', restrict, async (req, res) => { | |||
| router.get('/admin/customer/view/:id?', restrict, async (req, res) => { | ||||
|     const db = req.app.db; | ||||
| 
 | ||||
|     const customer = await db.customers.findOne({ _id: common.getId(req.params.id) }); | ||||
|     const customer = await db.customers.findOne({ _id: getId(req.params.id) }); | ||||
| 
 | ||||
|     if(!customer){ | ||||
|          // If API request, return json
 | ||||
|  | @ -223,9 +327,9 @@ router.get('/admin/customer/view/:id?', restrict, async (req, res) => { | |||
|         result: customer, | ||||
|         admin: true, | ||||
|         session: req.session, | ||||
|         message: common.clearSessionValue(req.session, 'message'), | ||||
|         messageType: common.clearSessionValue(req.session, 'messageType'), | ||||
|         countryList: common.getCountryList(), | ||||
|         message: clearSessionValue(req.session, 'message'), | ||||
|         messageType: clearSessionValue(req.session, 'messageType'), | ||||
|         countryList: getCountryList(), | ||||
|         config: req.app.config, | ||||
|         editor: true, | ||||
|         helpers: req.handlebars.helpers | ||||
|  | @ -249,8 +353,8 @@ router.get('/admin/customers', restrict, async (req, res) => { | |||
|         customers: customers, | ||||
|         session: req.session, | ||||
|         helpers: req.handlebars.helpers, | ||||
|         message: common.clearSessionValue(req.session, 'message'), | ||||
|         messageType: common.clearSessionValue(req.session, 'messageType'), | ||||
|         message: clearSessionValue(req.session, 'message'), | ||||
|         messageType: clearSessionValue(req.session, 'messageType'), | ||||
|         config: req.app.config | ||||
|     }); | ||||
| }); | ||||
|  | @ -263,7 +367,7 @@ router.get('/admin/customers/filter/:search', restrict, async (req, res, next) = | |||
| 
 | ||||
|     const lunrIdArray = []; | ||||
|     customersIndex.search(searchTerm).forEach((id) => { | ||||
|         lunrIdArray.push(common.getId(id.ref)); | ||||
|         lunrIdArray.push(getId(id.ref)); | ||||
|     }); | ||||
| 
 | ||||
|     // we search on the lunr indexes
 | ||||
|  | @ -283,8 +387,8 @@ router.get('/admin/customers/filter/:search', restrict, async (req, res, next) = | |||
|         config: req.app.config, | ||||
|         session: req.session, | ||||
|         searchTerm: searchTerm, | ||||
|         message: common.clearSessionValue(req.session, 'message'), | ||||
|         messageType: common.clearSessionValue(req.session, 'messageType'), | ||||
|         message: clearSessionValue(req.session, 'message'), | ||||
|         messageType: clearSessionValue(req.session, 'messageType'), | ||||
|         helpers: req.handlebars.helpers | ||||
|     }); | ||||
| }); | ||||
|  | @ -298,6 +402,7 @@ router.post('/admin/customer/lookup', restrict, async (req, res, next) => { | |||
| 
 | ||||
|     if(customer){ | ||||
|         req.session.customerPresent = true; | ||||
|         req.session.customerId = customer._id; | ||||
|         req.session.customerEmail = customer.email; | ||||
|         req.session.customerCompany = customer.company; | ||||
|         req.session.customerFirstname = customer.firstName; | ||||
|  | @ -319,11 +424,24 @@ router.post('/admin/customer/lookup', restrict, async (req, res, next) => { | |||
|     }); | ||||
| }); | ||||
| 
 | ||||
| router.get('/customer/login', async (req, res, next) => { | ||||
|     const config = req.app.config; | ||||
| 
 | ||||
|     res.render(`${config.themeViews}customer-login`, { | ||||
|         title: 'Customer login', | ||||
|         config: req.app.config, | ||||
|         session: req.session, | ||||
|         message: clearSessionValue(req.session, 'message'), | ||||
|         messageType: clearSessionValue(req.session, 'messageType'), | ||||
|         helpers: req.handlebars.helpers | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
| // login the customer and check the password
 | ||||
| router.post('/customer/login_action', async (req, res) => { | ||||
|     const db = req.app.db; | ||||
| 
 | ||||
|     const customer = await db.customers.findOne({ email: common.mongoSanitize(req.body.loginEmail) }); | ||||
|     const customer = await db.customers.findOne({ email: mongoSanitize(req.body.loginEmail) }); | ||||
|     // check if customer exists with that email
 | ||||
|     if(customer === undefined || customer === null){ | ||||
|         res.status(400).json({ | ||||
|  | @ -344,6 +462,7 @@ router.post('/customer/login_action', async (req, res) => { | |||
| 
 | ||||
|         // Customer login successful
 | ||||
|         req.session.customerPresent = true; | ||||
|         req.session.customerId = customer._id; | ||||
|         req.session.customerEmail = customer.email; | ||||
|         req.session.customerCompany = customer.company; | ||||
|         req.session.customerFirstname = customer.firstName; | ||||
|  | @ -375,8 +494,8 @@ router.get('/customer/forgotten', (req, res) => { | |||
|         forgotType: 'customer', | ||||
|         config: req.app.config, | ||||
|         helpers: req.handlebars.helpers, | ||||
|         message: common.clearSessionValue(req.session, 'message'), | ||||
|         messageType: common.clearSessionValue(req.session, 'messageType'), | ||||
|         message: clearSessionValue(req.session, 'message'), | ||||
|         messageType: clearSessionValue(req.session, 'messageType'), | ||||
|         showFooter: 'showFooter' | ||||
|     }); | ||||
| }); | ||||
|  | @ -411,7 +530,7 @@ router.post('/customer/forgotten_action', apiLimiter, async (req, res) => { | |||
| 
 | ||||
|         // send the email with token to the user
 | ||||
|         // TODO: Should fix this to properly handle result
 | ||||
|         common.sendEmail(mailOpts.to, mailOpts.subject, mailOpts.body); | ||||
|         sendEmail(mailOpts.to, mailOpts.subject, mailOpts.body); | ||||
|         res.status(200).json({ | ||||
|             message: 'If your account exists, a password reset has been sent to your email' | ||||
|         }); | ||||
|  | @ -441,8 +560,8 @@ router.get('/customer/reset/:token', async (req, res) => { | |||
|         token: req.params.token, | ||||
|         route: 'customer', | ||||
|         config: req.app.config, | ||||
|         message: common.clearSessionValue(req.session, 'message'), | ||||
|         message_type: common.clearSessionValue(req.session, 'message_type'), | ||||
|         message: clearSessionValue(req.session, 'message'), | ||||
|         message_type: clearSessionValue(req.session, 'message_type'), | ||||
|         show_footer: 'show_footer', | ||||
|         helpers: req.handlebars.helpers | ||||
|     }); | ||||
|  | @ -471,7 +590,7 @@ router.post('/customer/reset/:token', async (req, res) => { | |||
|         }; | ||||
| 
 | ||||
|         // TODO: Should fix this to properly handle result
 | ||||
|         common.sendEmail(mailOpts.to, mailOpts.subject, mailOpts.body); | ||||
|         sendEmail(mailOpts.to, mailOpts.subject, mailOpts.body); | ||||
|         req.session.message = 'Password successfully updated'; | ||||
|         req.session.message_type = 'success'; | ||||
|         return res.redirect('/checkout/payment'); | ||||
|  | @ -486,8 +605,15 @@ router.post('/customer/reset/:token', async (req, res) => { | |||
| // logout the customer
 | ||||
| router.post('/customer/logout', (req, res) => { | ||||
|     // Clear our session
 | ||||
|     common.clearCustomer(req); | ||||
|     clearCustomer(req); | ||||
|     res.status(200).json({}); | ||||
| }); | ||||
| 
 | ||||
| // logout the customer
 | ||||
| router.get('/customer/logout', (req, res) => { | ||||
|     // Clear our session
 | ||||
|     clearCustomer(req); | ||||
|     res.redirect('/customer/login'); | ||||
| }); | ||||
| 
 | ||||
| module.exports = router; | ||||
|  |  | |||
|  | @ -80,6 +80,7 @@ router.post('/checkout_action', async (req, res, next) => { | |||
|         orderShipping: req.session.totalCartShipping, | ||||
|         orderItemCount: req.session.totalCartItems, | ||||
|         orderProductCount: req.session.totalCartProducts, | ||||
|         orderCustomer: common.getId(req.session.customerId), | ||||
|         orderEmail: req.session.customerEmail, | ||||
|         orderCompany: req.session.customerCompany, | ||||
|         orderFirstname: req.session.customerFirstname, | ||||
|  |  | |||
|  | @ -63,6 +63,7 @@ router.post('/checkout_action', (req, res, next) => { | |||
|             orderShipping: req.session.totalCartShipping, | ||||
|             orderItemCount: req.session.totalCartItems, | ||||
|             orderProductCount: req.session.totalCartProducts, | ||||
|             orderCustomer: common.getId(req.session.customerId), | ||||
|             orderEmail: req.session.customerEmail, | ||||
|             orderCompany: req.session.customerCompany, | ||||
|             orderFirstname: req.session.customerFirstname, | ||||
|  |  | |||
|  | @ -94,6 +94,7 @@ router.post('/checkout_action', (req, res, next) => { | |||
|                 orderShipping: req.session.totalCartShipping, | ||||
|                 orderItemCount: req.session.totalCartItems, | ||||
|                 orderProductCount: req.session.totalCartProducts, | ||||
|                 orderCustomer: common.getId(req.session.customerId), | ||||
|                 orderEmail: req.session.customerEmail, | ||||
|                 orderCompany: req.session.customerCompany, | ||||
|                 orderFirstname: req.session.customerFirstname, | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ router.post('/checkout_action', async (req, res, next) => { | |||
|         orderShipping: 0, | ||||
|         orderItemCount: req.session.totalCartItems, | ||||
|         orderProductCount: req.session.totalCartProducts, | ||||
|         orderCustomer: common.getId(req.session.customerId), | ||||
|         orderEmail: req.session.customerEmail, | ||||
|         orderCompany: req.session.customerCompany, | ||||
|         orderFirstname: req.session.customerFirstname, | ||||
|  |  | |||
|  | @ -166,6 +166,7 @@ router.post('/checkout_action', (req, res, next) => { | |||
|                 orderShipping: req.session.totalCartShipping, | ||||
|                 orderItemCount: req.session.totalCartItems, | ||||
|                 orderProductCount: req.session.totalCartProducts, | ||||
|                 orderCustomer: common.getId(req.session.customerId), | ||||
|                 orderEmail: req.session.customerEmail, | ||||
|                 orderCompany: req.session.customerCompany, | ||||
|                 orderFirstname: req.session.customerFirstname, | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ router.post('/checkout_action', (req, res, next) => { | |||
|             orderShipping: req.session.totalCartShipping, | ||||
|             orderItemCount: req.session.totalCartItems, | ||||
|             orderProductCount: req.session.totalCartProducts, | ||||
|             orderCustomer: common.getId(req.session.customerId), | ||||
|             orderEmail: req.session.customerEmail, | ||||
|             orderCompany: req.session.customerCompany, | ||||
|             orderFirstname: req.session.customerFirstname, | ||||
|  |  | |||
|  | @ -78,9 +78,10 @@ test('[Fail] Create with invalid email address', async t => { | |||
|     t.deepEqual(res.body[0].message, 'should match format "emailAddress"'); | ||||
| }); | ||||
| 
 | ||||
| test('[Success] Update existing customer', async t => { | ||||
| test('[Success] Update existing customer from dashboard', async t => { | ||||
|     const customer = { | ||||
|         customerId: g.customers[1]._id, | ||||
|         company: 'Acme Co', | ||||
|         email: 'sarah.jones@test.com', | ||||
|         firstName: 'Sarah', | ||||
|         lastName: 'Jones', | ||||
|  | @ -99,6 +100,47 @@ test('[Success] Update existing customer', async t => { | |||
|         .expect(200); | ||||
| 
 | ||||
|     t.deepEqual(res.body.message, 'Customer updated'); | ||||
|     t.deepEqual(res.body.customer.company, customer.company); | ||||
|     t.deepEqual(res.body.customer.email, customer.email); | ||||
|     t.deepEqual(res.body.customer.firstName, customer.firstName); | ||||
|     t.deepEqual(res.body.customer.lastName, customer.lastName); | ||||
|     t.deepEqual(res.body.customer.address1, customer.address1); | ||||
|     t.deepEqual(res.body.customer.country, customer.country); | ||||
|     t.deepEqual(res.body.customer.state, customer.state); | ||||
|     t.deepEqual(res.body.customer.postcode, customer.postcode); | ||||
|     t.deepEqual(res.body.customer.phone, customer.phone); | ||||
| }); | ||||
| 
 | ||||
| test('[Success] Update existing customer from customer page', async t => { | ||||
|     const customer = { | ||||
|         customerId: g.customers[1]._id, | ||||
|         company: 'Acme Company', | ||||
|         email: 'sarah.jones@test.com', | ||||
|         firstName: 'Tina', | ||||
|         lastName: 'Smith', | ||||
|         address1: '2 Sydney Street', | ||||
|         address2: '', | ||||
|         country: 'Australia', | ||||
|         state: 'NSW', | ||||
|         postcode: '2000', | ||||
|         phone: '0444444444' | ||||
|     }; | ||||
| 
 | ||||
|     await g.request | ||||
|         .post('/customer/login_action') | ||||
|         .send({ | ||||
|             loginEmail: 'sarah.jones@test.com', | ||||
|             loginPassword: 'test' | ||||
|         }) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     const res = await g.request | ||||
|         .post('/customer/update') | ||||
|         .send(customer) | ||||
|         .expect(200); | ||||
| 
 | ||||
|     t.deepEqual(res.body.message, 'Customer updated'); | ||||
|     t.deepEqual(res.body.customer.company, customer.company); | ||||
|     t.deepEqual(res.body.customer.email, customer.email); | ||||
|     t.deepEqual(res.body.customer.firstName, customer.firstName); | ||||
|     t.deepEqual(res.body.customer.lastName, customer.lastName); | ||||
|  |  | |||
|  | @ -185,6 +185,19 @@ | |||
|                         </div> | ||||
|                     </div> | ||||
|                     {{/ifCond}} | ||||
|                     {{#if @root.session.customerPresent}} | ||||
|                     <div class="dropdown d-none d-sm-block"> | ||||
|                         <button class="btn dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> | ||||
|                             {{{feather 'user'}}} | ||||
|                         </button> | ||||
|                         <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"> | ||||
|                             <li><a class="dropdown-item" href="/customer/account">Account</a></li> | ||||
|                             <li><a class="dropdown-item" href="/customer/logout">Logout</a></li> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     {{else}} | ||||
|                         <li class="nav-item ml-3"><a href="/customer/account" class="btn">{{{feather 'user'}}}</a></li> | ||||
|                     {{/if}} | ||||
|                     {{#if @root.session.cart}} | ||||
|                         <li class="nav-item"><a href="/checkout/cart" class="btn menu-btn">{{{feather 'shopping-cart'}}} {{ @root.__ "Cart" }} <span class="badge badge-danger" id="cart-count">{{@root.session.totalCartItems}}</span></a></li> | ||||
|                         {{else}} | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ | |||
|         </div>  | ||||
|         <div id="draggable_list"> | ||||
|             {{#each menu.items}} | ||||
|                 <div class="row drag-row" id="menuId-{{@key}}"> | ||||
|                 <div class="row drag-row mb-2" id="menuId-{{@key}}"> | ||||
|                     <input type="hidden" class="navId" value="{{title}}"> | ||||
|                     <div class="col-sm-1 dragable_item">{{{feather 'move'}}}</div> | ||||
|                     <div class="col-sm-2 dragable_item"> | ||||
|  | @ -42,7 +42,7 @@ | |||
|                 </button> | ||||
|             </div> | ||||
|         </div> | ||||
|         <p class="text-muted"> | ||||
|         <p class="text-muted mt-4"> | ||||
|             {{ @root.__ "Setting_menu_explain" }} | ||||
|         </p> | ||||
|     </div> | ||||
|  |  | |||
|  | @ -0,0 +1,129 @@ | |||
| <div class="col-md-10 offset-md-1 col-sm-12"> | ||||
|     <nav aria-label="breadcrumb"> | ||||
|         <ol class="breadcrumb"> | ||||
|             <li class="breadcrumb-item"><a href="/">Home</a></li> | ||||
|             <li class="breadcrumb-item active" aria-current="page"><a href="/customer/account">Orders</a></li> | ||||
|         </ol> | ||||
|     </nav> | ||||
|     <div class="row"> | ||||
|         <div class="col-12 col-md-5 bottom-pad-15"> | ||||
|             <div class="card top-marg-15"> | ||||
|                 <div class="card-body customer-details-login"> | ||||
|                     <h5 class="card-heading">{{ @root.__ "Customer details" }}</h5> | ||||
|                     {{#unless @root.session.customerPresent}} | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12 bottom-marg-15"> | ||||
|                             <p>{{ @root.__ "Existing customer" }}</p> | ||||
|                             <div class="form-group"> | ||||
|                                 <input type="email" class="form-control" id="customerLoginEmail" name="loginEmail" | ||||
|                                     minlength="5" placeholder="Email address" required> | ||||
|                             </div> | ||||
|                             <div class="form-group"> | ||||
|                                 <input type="password" class="form-control" id="customerLoginPassword" | ||||
|                                     name="loginPassword" minlength="5" placeholder="Password" required> | ||||
|                             </div> | ||||
|                             <div class="form-group"> | ||||
|                                 <a href="/customer/forgotten" | ||||
|                                     class="btn btn-primary float-left">{{ @root.__ "Forgotten" }}</a> | ||||
|                             </div> | ||||
|                             <div class="form-group"> | ||||
|                                 <button id="customerLogin" class="btn btn-primary float-right" | ||||
|                                     type="submit">Login</button> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     {{/unless}} | ||||
|                     <form id="customer-form" role="form" data-toggle="validator" novalidate="false"> | ||||
|                         {{> themes/Cloth/shipping-form}} | ||||
|                         <div class="row"> | ||||
|                             <div class="col-sm-12"> | ||||
|                                 <a href="/checkout/cart" id="customerSave" class="btn btn-primary float-left">Save details</a> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </form> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="col-md-7"> | ||||
|             <div class="col-12 bottom-pad-15 no-pad-right"> | ||||
|                 <div class="card top-marg-15"> | ||||
|                     <div class="card-body"> | ||||
|                         <h5 class="card-heading">{{ @root.__ "Orders" }}</h5> | ||||
|                         <div id="accordion"> | ||||
|                             {{#each orders}} | ||||
|                             <div class="card mb-3"> | ||||
|                                 <div class="card-header" id="headingOne"> | ||||
|                                     <p class="mb-0"> | ||||
|                                         OrderId: {{this._id}} -  | ||||
|                                         Date: {{formatDate this.orderDate "DD/MM/YYYY hh:mmA"}} | ||||
|                                         <button class="btn btn-sm btn-outline-success float-right" data-toggle="collapse" data-target="#collapse{{this._id}}" aria-expanded="true" aria-controls="collapse{{this._id}}"> | ||||
|                                             View | ||||
|                                         </button> | ||||
|                                     </p> | ||||
|                                 </div> | ||||
|                                 <div id="collapse{{this._id}}" class="collapse" aria-labelledby="headingOne" data-parent="#accordion"> | ||||
|                                     <div class="card-body"> | ||||
|                                         <ul class="list-group"> | ||||
|                                             <li class="list-group-item list-group-input-pad"> | ||||
|                                                 <strong> Order status: </strong><span class="text-{{getStatusColor this.orderStatus}} float-right">{{this.orderStatus}}</span> | ||||
|                                             </li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Order date" }}: </strong><span class="float-right">{{formatDate result.orderDate "DD/MM/YYYY hh:mmA"}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Order ID" }}: </strong><span class="float-right">{{this._id}}</span></li> | ||||
|                                             {{#if result.orderExpectedBtc }} | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Order Expected BTC" }}: </strong><span class="float-right">{{this.orderExpectedBtc}}</span></li> | ||||
|                                             {{/if}} | ||||
|                                             {{#if this.orderReceivedBtc }} | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Order Received BTC" }}: </strong><span class="float-right">{{this.orderReceivedBtc}}</span></li> | ||||
|                                             {{/if}} | ||||
|                                             {{#if this.orderBlockonomicsTxid }} | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Order Blockonomics Txid" }}: </strong><span class="float-right">{{this.orderBlockonomicsTxid}}</span></li> | ||||
|                                             {{/if}} | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Order net amount" }}: </strong><span class="float-right">{{currencySymbol config.currencySymbol}}{{formatAmount (math this.orderTotal '-' this.orderShipping)}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Order shipping amount" }}: </strong><span class="float-right">{{currencySymbol config.currencySymbol}}{{formatAmount this.orderShipping}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Order total amount" }}: </strong><span class="float-right">{{currencySymbol config.currencySymbol}}{{formatAmount this.orderTotal}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Email address" }}: </strong><span class="float-right">{{this.orderEmail}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Company" }}: </strong><span class="float-right">{{this.orderCompany}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "First name" }}: </strong><span class="float-right">{{this.orderFirstname}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Last name" }}: </strong><span class="float-right">{{this.orderLastname}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Address 1" }}: </strong><span class="float-right">{{this.orderAddr1}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Address 2" }}: </strong><span class="float-right">{{this.orderAddr2}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Country" }}: </strong><span class="float-right">{{this.orderCountry}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "State" }}: </strong><span class="float-right">{{this.orderState}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Postcode" }}: </strong><span class="float-right">{{this.orderPostcode}}</span></li> | ||||
|                                             <li class="list-group-item"><strong> {{ @root.__ "Phone number" }}: </strong><span class="float-right">{{this.orderPhoneNumber}}</span></li> | ||||
|                                             <li class="list-group-item"> </li> | ||||
|                                             <li class="list-group-item"><strong class="text-info">{{ @root.__ "Products ordered" }}</strong></li> | ||||
|                                             {{#each this.orderProducts}} | ||||
|                                                 <li class="list-group-item"> | ||||
|                                                     {{this.quantity}} x {{this.title}} | ||||
|                                                     {{#if this.options}} | ||||
|                                                           >   | ||||
|                                                         <span class="text-warning"> {{ @root.__ "Options" }}: </span> | ||||
|                                                         ( | ||||
|                                                         {{#each this.options}}  | ||||
|                                                             {{#if @last}} | ||||
|                                                                 <strong>{{#upperFirst this.name}}{{/upperFirst}}</strong>: {{this.value}} | ||||
|                                                                 {{else}}  | ||||
|                                                                 <strong>{{#upperFirst this.name}}{{/upperFirst}}:</strong> {{this.value}} / | ||||
|                                                             {{/if}}  | ||||
|                                                         {{/each}} | ||||
|                                                         ) | ||||
|                                                     {{/if}} | ||||
|                                                     <div class="float-right">{{currencySymbol @root.config.currencySymbol}}{{formatAmount this.totalItemPrice}}</div> | ||||
|                                                     {{#if productComment}} | ||||
|                                                     <h4><span class="text-danger">Comment:</span> {{this.productComment}}</h4> | ||||
|                                                     {{/if}} | ||||
|                                                 </li> | ||||
|                                             {{/each}} | ||||
|                                         </ul> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                             {{/each}} | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | @ -0,0 +1,12 @@ | |||
| <div class="col-md-4 offset-md-4 top-pad-100"> | ||||
| 	<form class="form-signin"  method="post" role="form" data-toggle="validator"> | ||||
| 		<h2 class="form-signin-heading">{{ @root.__ "Please sign in" }}</h2> | ||||
| 		<div class="form-group"> | ||||
| 			<input type="email" id="email" name="email" class="form-control" placeholder="email address" required autofocus> | ||||
| 		</div> | ||||
| 		<div class="form-group"> | ||||
| 			<input type="password" id="password" name="password" class="form-control" placeholder="Password" required> | ||||
| 		</div> | ||||
| 		<button class="btn btn-outline-primary btn-block" id="customerloginForm" type="submit">{{ @root.__ "Sign in" }}</button> | ||||
| 	</form> | ||||
| </div> | ||||
		Loading…
	
		Reference in New Issue