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
Mark Moffat 2020-03-17 16:56:04 +10:30 committed by GitHub
parent 6b9f4b2e6b
commit 78e06e511b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 411 additions and 32 deletions

View File

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

View File

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

View File

@ -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"] {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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">&nbsp;</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}}
&nbsp; > &nbsp;
<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>

View File

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