Added support for returning customers

react_convert
Mark Moffat 2018-01-21 22:20:33 +01:00
parent 3730f84796
commit 6076455d06
13 changed files with 627 additions and 68 deletions

1
app.js
View File

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

View File

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

View File

@ -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() === ''){

File diff suppressed because one or more lines are too long

View File

@ -6,59 +6,93 @@
<div class="col-md-5">
<div class="panel panel-default" style="margin-top: 30px;">
<div class="panel-heading">Customer details</div>
{{#unless session.customer}}
<div class="panel-body customer-details-login">
<p>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-default pull-left">Forgotten</a>
</div>
<div class="form-group">
<button id="customerLogin" class="btn btn-success pull-right" type="submit">Login</button>
</div>
</div>
{{/unless}}
<div class="panel-body customer-details">
{{#if session.customer}}
<div class="col-xs-12 col-md-12">
<button id="customerLogout" class="btn btn-sm btn-success pull-right">Change customer</button>
</div>
{{/if}}
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action" method="post" role="form" data-toggle="validator" novalidate="false">
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="email" class="form-control customerDetails" id="shipEmail" name="shipEmail" minlength="5" placeholder="Email address" required>
<input type="email" class="form-control customerDetails" id="shipEmail" name="shipEmail" minlength="5" placeholder="Email address" value="{{session.customer.email}}" required>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipFirstname" name="shipFirstname" placeholder="First name" required>
<input type="text" class="form-control customerDetails" id="shipFirstname" name="shipFirstname" placeholder="First name" value="{{session.customer.firstName}}" required>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipLastname" name="shipLastname" placeholder="Last name" required>
<input type="text" class="form-control customerDetails" id="shipLastname" name="shipLastname" placeholder="Last name" value="{{session.customer.lastName}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipAddr1" name="shipAddr1" placeholder="Address 1" required>
<input type="text" class="form-control customerDetails" id="shipAddr1" name="shipAddr1" placeholder="Address 1" value="{{session.customer.address1}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipAddr2" name="shipAddr2" placeholder="Address 2 (optional)">
<input type="text" class="form-control customerDetails" id="shipAddr2" name="shipAddr2" placeholder="Address 2 (optional)" value="{{session.customer.address1}}">
</div>
</div>
<div class="col-xs-12 col-md-4">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipCountry" name="shipCountry" placeholder="Country" required>
<input type="text" class="form-control customerDetails" id="shipCountry" name="shipCountry" placeholder="Country" value="{{session.customer.country}}" required>
</div>
</div>
<div class="col-xs-12 col-md-4">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipState" name="shipState" placeholder="State" required>
<input type="text" class="form-control customerDetails" id="shipState" name="shipState" placeholder="State" value="{{session.customer.state}}" required>
</div>
</div>
<div class="col-xs-12 col-md-4">
<div class="form-group">
<input type="number" class="form-control customerDetails" id="shipPostcode" name="shipPostcode" placeholder="Post code" required>
<input type="text" class="form-control customerDetails" id="shipPostcode" name="shipPostcode" placeholder="Post code" value="{{session.customer.postcode}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="number" class="form-control customerDetails" id="shipPhoneNumber" name="shipPhoneNumber" placeholder="Phone number (optional)">
<input type="number" class="form-control customerDetails" id="shipPhoneNumber" name="shipPhoneNumber" placeholder="Phone number" value="{{session.customer.phone}}" required>
</div>
</div>
{{#if session.customer}}
{{#ifCond config.paymentGateway '==' 'paypal'}}
<div class="paypal_button col-xs-12 col-md-12 text-center">
<button id="checkout_paypal" class="btn btn-success" type="submit"><i class="fa fa-cc-paypal fa-lg" aria-hidden="true"></i> Pay with PayPal <i class="fa fa-cc-paypal fa-lg" aria-hidden="true"></i></button>
</div>
{{/ifCond}}
{{/if}}
{{#unless session.customer}}
<div class="col-xs-12 col-md-12">
<p class="text-muted">Enter a password to create an account for next time</p>
<div class="form-group">
<input type="password" class="form-control customerDetails" id="newCustomerPassword" name="newCustomerPassword" placeholder="Password" required>
</div>
<a id="createCustomerAccount" class="btn btn-success pull-right">Create account</a>
</div>
{{/unless}}
</form>
{{#if session.customer}}
{{#ifCond config.paymentGateway '==' 'stripe'}}
<div class="col-xs-12 col-md-12 text-center">
<form method="POST" id="payment-form">
@ -82,6 +116,8 @@
</form>
</div>
{{/ifCond}}
{{/if}}
</div>
</div>
</div>

View File

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

View File

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

49
views/customer.hbs Normal file
View File

@ -0,0 +1,49 @@
{{> menu}}
<div class="col-lg-9">
<div class="row">
<div class="col-lg-12">
<h2>Customer</h2>
</div>
<div class="col-xs-12 col-md-12">
<table class="table-bordered">
<tr>
<td><span class="text-info">Email:</span></td>
<td>{{result.email}}</td>
</tr>
<tr>
<td><span class="text-info">Name:</span></td>
<td>{{result.firstName}} {{result.lastName}}</td>
</tr>
<tr>
<td><span class="text-info">Address 1:</span></td>
<td>{{result.address1}}</td>
</tr>
<tr>
<td><span class="text-info">Address 2:</span></td>
<td>{{result.address2}}</td>
</tr>
<tr>
<td><span class="text-info">Country:</span></td>
<td>{{result.country}}</td>
</tr>
<tr>
<td><span class="text-info">State:</span></td>
<td>{{result.state}}</td>
</tr>
<tr>
<td><span class="text-info">Postcode:</span></td>
<td>{{result.postcode}}</td>
</tr>
<tr>
<td><span class="text-info">Phone number:</span></td>
<td>{{result.phone}}</td>
</tr>
<tr>
<td><span class="text-info">Creation date:</span></td>
<td>{{formatDate result.created "DD/MM/YYYY hh:mmA"}}</td>
</tr>
</table>
</div>
</div>
<input type="hidden" id="productId" value="{{result._id}}">
</div>

64
views/customers.hbs Normal file
View File

@ -0,0 +1,64 @@
{{> menu}}
<div class="col-lg-9">
<div class="col-lg-12">
<h2>Customers</h2>
</div>
<div class="col-lg-12 search_bar">
<div class="input-group">
<input type="text" name="customer_filter" id="customer_filter" class="form-control input-lg" placeholder="Filter customers">
<span class="input-group-btn">
<button class="btn btn-success btn-lg" id="btn_customer_filter">Filter</button>
<a href="/admin/customers" class="hidden-xs btn btn-warning btn-lg"><i class="fa fa-times" aria-hidden="true"></i></a>
</span>
</div>
<p class="text-warning top-pad-10">Customers can be filtered by: email, name or phone numnber</p>
</div>
{{#if customers}}
<div class="col-lg-12">
<ul class="list-group">
<li class="list-group-item">
<strong>
Customers
{{#if searchTerm}}
- <span class="text-danger">Filtered term: {{searchTerm}} </span>
{{/if}}
</strong>
</li>
<li class="list-group-item">
<div class="row">
<div class="col-md-4">
<h5 class="text-info"><strong>Email address</strong></h5>
</div>
<div class="col-md-4">
<h5 class="text-info"><strong>Name</strong></h5>
</div>
<div class="col-md-4">
<h5 class="text-info"><strong>Phone number</strong></h5>
</div>
</div>
</li>
{{#each customers}}
<li class="list-group-item">
<div class="row">
<a href="/admin/customer/view/{{this._id}}">
<div class="col-md-4">
<h5 class="">{{this.email}}</h5>
</div>
<div class="col-md-4">
<h5 class="">{{this.firstName}} {{this.lastName}}</h5>
</div>
<div class="col-md-4">
<h5 class="">{{this.phone}}</h5>
</div>
</a>
</div>
</li>
{{/each}}
</ul>
</div>
{{else}}
<h4 class="text-center">
No orders found
</h4>
{{/if}}
</div>

9
views/forgotten.hbs Normal file
View File

@ -0,0 +1,9 @@
<div class="col-md-offset-4 col-md-4 col-lg-offset-4 col-lg-4" style="padding-top: 100px" >
<form class="form-signin" action="/{{route}}/forgotten_action" method="post" role="form" data-toggle="validator">
<h2 class="form-signin-heading">Please enter your email address</h2>
<div class="form-group">
<input type="email" name="email" class="form-control" placeholder="email address" required autofocus>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Reset</button>
</form>
</div>

View File

@ -6,6 +6,8 @@
<li class="list-group-item"><i class="fa fa-list fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/products">List</a></li>
<li class="list-group-item"><strong>Orders</strong></li>
<li class="list-group-item"><i class="fa fa-cube fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/orders">List</a></li>
<li class="list-group-item"><strong>Customers</strong></li>
<li class="list-group-item"><i class="fa fa-users fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/customers">List</a></li>
<li class="list-group-item"><strong>Users</strong></li>
<li class="list-group-item"><i class="fa fa-user-plus fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/user/new">New</a></li>
<li class="list-group-item"><i class="fa fa-user fa-icon" aria-hidden="true"></i> &nbsp; <a href="/admin/users">Edit</a></li>

View File

@ -6,64 +6,93 @@
<div class="col-md-5">
<div class="panel panel-default" style="margin-top: 30px;">
<div class="panel-heading">Customer details</div>
{{#unless session.customer}}
<div class="panel-body customer-details-login">
<p>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-default pull-left">Forgotten</a>
</div>
<div class="form-group">
<button id="customerLogin" class="btn btn-success pull-right" type="submit">Login</button>
</div>
</div>
{{/unless}}
<div class="panel-body customer-details">
{{#if session.customer}}
<div class="col-xs-12 col-md-12">
<button id="customerLogout" class="btn btn-sm btn-success pull-right">Change customer</button>
</div>
{{/if}}
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action" method="post" role="form" data-toggle="validator" novalidate="false">
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="email" class="form-control customerDetails" id="shipEmail" name="shipEmail" minlength="5" placeholder="Email address" required>
<input type="email" class="form-control customerDetails" id="shipEmail" name="shipEmail" minlength="5" placeholder="Email address" value="{{session.customer.email}}" required>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipFirstname" name="shipFirstname" placeholder="First name" required>
<input type="text" class="form-control customerDetails" id="shipFirstname" name="shipFirstname" placeholder="First name" value="{{session.customer.firstName}}" required>
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipLastname" name="shipLastname" placeholder="Last name" required>
<input type="text" class="form-control customerDetails" id="shipLastname" name="shipLastname" placeholder="Last name" value="{{session.customer.lastName}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipAddr1" name="shipAddr1" placeholder="Address 1" required>
<input type="text" class="form-control customerDetails" id="shipAddr1" name="shipAddr1" placeholder="Address 1" value="{{session.customer.address1}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipAddr2" name="shipAddr2" placeholder="Address 2 (optional)">
<input type="text" class="form-control customerDetails" id="shipAddr2" name="shipAddr2" placeholder="Address 2 (optional)" value="{{session.customer.address1}}">
</div>
</div>
<div class="col-xs-12 col-md-4">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipCountry" name="shipCountry" placeholder="Country" required>
<input type="text" class="form-control customerDetails" id="shipCountry" name="shipCountry" placeholder="Country" value="{{session.customer.country}}" required>
</div>
</div>
<div class="col-xs-12 col-md-4">
<div class="form-group">
<input type="text" class="form-control customerDetails" id="shipState" name="shipState" placeholder="State" required>
<input type="text" class="form-control customerDetails" id="shipState" name="shipState" placeholder="State" value="{{session.customer.state}}" required>
</div>
</div>
<div class="col-xs-12 col-md-4">
<div class="form-group">
<input type="number" class="form-control customerDetails" id="shipPostcode" name="shipPostcode" placeholder="Post code" required>
<input type="text" class="form-control customerDetails" id="shipPostcode" name="shipPostcode" placeholder="Post code" value="{{session.customer.postcode}}" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="number" class="form-control customerDetails" id="shipPostcode" name="shipPostcode" placeholder="Post code" required>
</div>
</div>
<div class="col-xs-12 col-md-12">
<div class="form-group">
<input type="number" class="form-control customerDetails" id="shipPhoneNumber" name="shipPhoneNumber" placeholder="Phone number (optional)">
<input type="number" class="form-control customerDetails" id="shipPhoneNumber" name="shipPhoneNumber" placeholder="Phone number" value="{{session.customer.phone}}" required>
</div>
</div>
{{#if session.customer}}
{{#ifCond config.paymentGateway '==' 'paypal'}}
<div class="paypal_button col-xs-12 col-md-12 text-center">
<button id="checkout_paypal" class="btn btn-success" type="submit"><i class="fa fa-cc-paypal fa-lg" aria-hidden="true"></i> Pay with PayPal <i class="fa fa-cc-paypal fa-lg" aria-hidden="true"></i></button>
</div>
{{/ifCond}}
{{/if}}
{{#unless session.customer}}
<div class="col-xs-12 col-md-12">
<p class="text-muted">Enter a password to create an account for next time</p>
<div class="form-group">
<input type="password" class="form-control customerDetails" id="newCustomerPassword" name="newCustomerPassword" placeholder="Password" required>
</div>
<a id="createCustomerAccount" class="btn btn-success pull-right">Create account</a>
</div>
{{/unless}}
</form>
{{#if session.customer}}
{{#ifCond config.paymentGateway '==' 'stripe'}}
<div class="col-xs-12 col-md-12 text-center">
<form method="POST" id="payment-form">
@ -87,6 +116,7 @@
</form>
</div>
{{/ifCond}}
{{/if}}
</div>
</div>
</div>

15
views/reset.hbs Normal file
View File

@ -0,0 +1,15 @@
<div class="col-md-offset-4 col-md-4 col-lg-offset-4 col-lg-4">
<form class="form-signin" action="/{{route}}/reset/{{token}}" method="post" role="form" data-toggle="validator">
<h2 class="form-signin-heading">Password reset</h2>
<div class="form-group">
<label for="user_password">Password *</label>
<input type="password" class="form-control" id="user_password" name="password" placeholder="Password" required>
</div>
<div class="form-group">
<label for="frm_user_password_confirm">Confirm password *</label>
<input type="password" data-match="#user_password" placeholder="Confirm password" class="form-control" name="frm_user_password_confirm" required>
</div>
<button class="btn btn-lg btn-success btn-block" type="submit">Update password</button>
</br>
</form>
</div>