Adding discount code support (#109)
Added a discount code module. Lots of work went into this. Please report any problems as issues to be fixedmaster
parent
83dc6f76ee
commit
799ed301f2
7
app.js
7
app.js
|
@ -20,7 +20,7 @@ const crypto = require('crypto');
|
||||||
const common = require('./lib/common');
|
const common = require('./lib/common');
|
||||||
const { runIndexing } = require('./lib/indexing');
|
const { runIndexing } = require('./lib/indexing');
|
||||||
const { addSchemas } = require('./lib/schema');
|
const { addSchemas } = require('./lib/schema');
|
||||||
const { initDb } = require('./lib/db');
|
const { initDb, getDbUri } = require('./lib/db');
|
||||||
let handlebars = require('express-handlebars');
|
let handlebars = require('express-handlebars');
|
||||||
const i18n = require('i18n');
|
const i18n = require('i18n');
|
||||||
|
|
||||||
|
@ -237,6 +237,9 @@ handlebars = handlebars.create({
|
||||||
formatDate: (date, format) => {
|
formatDate: (date, format) => {
|
||||||
return moment(date).format(format);
|
return moment(date).format(format);
|
||||||
},
|
},
|
||||||
|
discountExpiry: (start, end) => {
|
||||||
|
return moment().isBetween(moment(start), moment(end));
|
||||||
|
},
|
||||||
ifCond: (v1, operator, v2, options) => {
|
ifCond: (v1, operator, v2, options) => {
|
||||||
switch(operator){
|
switch(operator){
|
||||||
case'==':
|
case'==':
|
||||||
|
@ -317,7 +320,7 @@ handlebars = handlebars.create({
|
||||||
|
|
||||||
// session store
|
// session store
|
||||||
const store = new MongoStore({
|
const store = new MongoStore({
|
||||||
uri: config.databaseConnectionString,
|
uri: getDbUri(config.databaseConnectionString),
|
||||||
collection: 'sessions'
|
collection: 'sessions'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -246,6 +246,36 @@
|
||||||
"orderProducts": []
|
"orderProducts": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"discounts": [
|
||||||
|
{
|
||||||
|
"code": "valid_10_amount_code",
|
||||||
|
"type": "amount",
|
||||||
|
"value": 10,
|
||||||
|
"start": "",
|
||||||
|
"end": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "valid_10_percent_code",
|
||||||
|
"type": "percent",
|
||||||
|
"value": 10,
|
||||||
|
"start": "",
|
||||||
|
"end": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "expired_10_percent_code",
|
||||||
|
"type": "percent",
|
||||||
|
"value": 10,
|
||||||
|
"start": "",
|
||||||
|
"end": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "future_10_percent_code",
|
||||||
|
"type": "percent",
|
||||||
|
"value": 10,
|
||||||
|
"start": "",
|
||||||
|
"end": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
"menu": {
|
"menu": {
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -28,7 +28,8 @@
|
||||||
"maxQuantity": 25,
|
"maxQuantity": 25,
|
||||||
"modules": {
|
"modules": {
|
||||||
"enabled": {
|
"enabled": {
|
||||||
"shipping": "shipping-basic"
|
"shipping": "shipping-basic",
|
||||||
|
"discount": "discount-voucher"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ const restrictedRoutes = [
|
||||||
{ route: '/admin/product/edit/:id', response: 'redirect' },
|
{ route: '/admin/product/edit/:id', response: 'redirect' },
|
||||||
{ route: '/admin/product/update', response: 'redirect' },
|
{ route: '/admin/product/update', response: 'redirect' },
|
||||||
{ route: '/admin/product/delete/:id', response: 'redirect' },
|
{ route: '/admin/product/delete/:id', response: 'redirect' },
|
||||||
{ route: '/admin/product/published_state', response: 'json' },
|
{ route: '/admin/product/publishedState', response: 'json' },
|
||||||
{ route: '/admin/product/setasmainimage', response: 'json' },
|
{ route: '/admin/product/setasmainimage', response: 'json' },
|
||||||
{ route: '/admin/product/deleteimage', response: 'json' },
|
{ route: '/admin/product/deleteimage', response: 'json' },
|
||||||
{ route: '/admin/product/removeoption', response: 'json' },
|
{ route: '/admin/product/removeoption', response: 'json' },
|
||||||
|
@ -20,7 +20,7 @@ const restrictedRoutes = [
|
||||||
{ route: '/admin/settings/menu/new', response: 'json' },
|
{ route: '/admin/settings/menu/new', response: 'json' },
|
||||||
{ route: '/admin/settings/menu/update', response: 'json' },
|
{ route: '/admin/settings/menu/update', response: 'json' },
|
||||||
{ route: '/admin/settings/menu/delete', response: 'json' },
|
{ route: '/admin/settings/menu/delete', response: 'json' },
|
||||||
{ route: '/admin/settings/menu/save_order', response: 'json' },
|
{ route: '/admin/settings/menu/saveOrder', response: 'json' },
|
||||||
{ route: '/admin/file/upload', response: 'json' }
|
{ route: '/admin/file/upload', response: 'json' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -111,8 +111,9 @@ const clearSessionValue = (session, sessionVar) => {
|
||||||
return temp;
|
return temp;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateTotalCart = (req, res) => {
|
const updateTotalCart = async (req, res) => {
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
|
const db = req.app.db;
|
||||||
|
|
||||||
req.session.totalCartAmount = 0;
|
req.session.totalCartAmount = 0;
|
||||||
req.session.totalCartItems = 0;
|
req.session.totalCartItems = 0;
|
||||||
|
@ -131,15 +132,34 @@ const updateTotalCart = (req, res) => {
|
||||||
// Update the total items in cart for the badge
|
// Update the total items in cart for the badge
|
||||||
req.session.totalCartItems = Object.keys(req.session.cart).length;
|
req.session.totalCartItems = Object.keys(req.session.cart).length;
|
||||||
|
|
||||||
// Net cart amount
|
// Update the total amount not including shipping/discounts
|
||||||
const netCartAmount = req.session.totalCartAmount - req.session.totalCartShipping || 0;
|
req.session.totalCartNetAmount = req.session.totalCartAmount;
|
||||||
|
|
||||||
// Calculate shipping using the loaded module
|
// Calculate shipping using the loaded module
|
||||||
config.modules.loaded.shipping.calculateShipping(
|
config.modules.loaded.shipping.calculateShipping(
|
||||||
netCartAmount,
|
req.session.totalCartNetAmount,
|
||||||
config,
|
config,
|
||||||
req
|
req
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If discount module enabled
|
||||||
|
if(config.modules.loaded.discount){
|
||||||
|
// Recalculate discounts
|
||||||
|
const discount = await db.discounts.findOne({ code: req.session.discountCode });
|
||||||
|
if(discount){
|
||||||
|
config.modules.loaded.discount.calculateDiscount(
|
||||||
|
discount,
|
||||||
|
req
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
// If discount code is not found, remove it
|
||||||
|
delete req.session.discountCode;
|
||||||
|
req.session.totalCartDiscount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate our total amount removing discount and adding shipping
|
||||||
|
req.session.totalCartAmount = (req.session.totalCartNetAmount - req.session.totalCartDiscount) + req.session.totalCartShipping;
|
||||||
};
|
};
|
||||||
|
|
||||||
const emptyCart = async (req, res, type, customMessage) => {
|
const emptyCart = async (req, res, type, customMessage) => {
|
||||||
|
@ -150,12 +170,13 @@ const emptyCart = async (req, res, type, customMessage) => {
|
||||||
delete req.session.shippingAmount;
|
delete req.session.shippingAmount;
|
||||||
delete req.session.orderId;
|
delete req.session.orderId;
|
||||||
delete req.session.cartSubscription;
|
delete req.session.cartSubscription;
|
||||||
|
delete req.session.discountCode;
|
||||||
|
|
||||||
// Remove cart from DB
|
// Remove cart from DB
|
||||||
await db.cart.deleteOne({ sessionId: req.session.id });
|
await db.cart.deleteOne({ sessionId: req.session.id });
|
||||||
|
|
||||||
// update total cart
|
// update total cart
|
||||||
updateTotalCart(req, res);
|
await updateTotalCart(req, res);
|
||||||
|
|
||||||
// Update checking cart for subscription
|
// Update checking cart for subscription
|
||||||
updateSubscriptionCheck(req, res);
|
updateSubscriptionCheck(req, res);
|
||||||
|
|
26
lib/db.js
26
lib/db.js
|
@ -14,15 +14,14 @@ function initDb(dbUrl, callback){ // eslint-disable-line
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the DB url
|
||||||
|
dbUrl = getDbUri(dbUrl);
|
||||||
|
|
||||||
// select DB
|
// select DB
|
||||||
const dbUriObj = mongodbUri.parse(dbUrl);
|
const dbUriObj = mongodbUri.parse(dbUrl);
|
||||||
let db;
|
|
||||||
// if in testing, set the testing DB
|
// Set the DB depending on ENV
|
||||||
if(process.env.NODE_ENV === 'test'){
|
const db = client.db(dbUriObj.database);
|
||||||
db = client.db('testingdb');
|
|
||||||
}else{
|
|
||||||
db = client.db(dbUriObj.database);
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup the collections
|
// setup the collections
|
||||||
db.users = db.collection('users');
|
db.users = db.collection('users');
|
||||||
|
@ -33,17 +32,28 @@ function initDb(dbUrl, callback){ // eslint-disable-line
|
||||||
db.customers = db.collection('customers');
|
db.customers = db.collection('customers');
|
||||||
db.cart = db.collection('cart');
|
db.cart = db.collection('cart');
|
||||||
db.sessions = db.collection('sessions');
|
db.sessions = db.collection('sessions');
|
||||||
|
db.discounts = db.collection('discounts');
|
||||||
|
|
||||||
_db = db;
|
_db = db;
|
||||||
return callback(null, _db);
|
return callback(null, _db);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getDbUri(dbUrl){
|
||||||
|
const dbUriObj = mongodbUri.parse(dbUrl);
|
||||||
|
// if in testing, set the testing DB
|
||||||
|
if(process.env.NODE_ENV === 'test'){
|
||||||
|
dbUriObj.database = 'expresscart-test';
|
||||||
|
}
|
||||||
|
return mongodbUri.format(dbUriObj);
|
||||||
|
}
|
||||||
|
|
||||||
function getDb(){
|
function getDb(){
|
||||||
return _db;
|
return _db;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getDb,
|
getDb,
|
||||||
initDb
|
initDb,
|
||||||
|
getDbUri
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
const calculateDiscount = (discount, req) => {
|
||||||
|
let discountAmount = 0;
|
||||||
|
if(req.session.discountCode){
|
||||||
|
if(discount.type === 'amount'){
|
||||||
|
discountAmount = discount.value;
|
||||||
|
}
|
||||||
|
if(discount.type === 'percent'){
|
||||||
|
// Apply the discount on the net cart amount (eg: minus shipping)
|
||||||
|
discountAmount = (discount.value / 100) * req.session.totalCartNetAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.session.totalCartDiscount = discountAmount;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
calculateDiscount
|
||||||
|
};
|
|
@ -24,7 +24,7 @@ const calculateShipping = (amount, config, req) => {
|
||||||
if(!req.session.customerCountry){
|
if(!req.session.customerCountry){
|
||||||
req.session.shippingMessage = 'Estimated shipping';
|
req.session.shippingMessage = 'Estimated shipping';
|
||||||
req.session.totalCartShipping = domesticShippingAmount;
|
req.session.totalCartShipping = domesticShippingAmount;
|
||||||
req.session.totalCartAmount = req.session.totalCartAmount + domesticShippingAmount;
|
req.session.totalCartAmount = amount + domesticShippingAmount;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,14 +32,14 @@ const calculateShipping = (amount, config, req) => {
|
||||||
if(req.session.customerCountry.toLowerCase() !== shippingFromCountry.toLowerCase()){
|
if(req.session.customerCountry.toLowerCase() !== shippingFromCountry.toLowerCase()){
|
||||||
req.session.shippingMessage = 'International shipping';
|
req.session.shippingMessage = 'International shipping';
|
||||||
req.session.totalCartShipping = internationalShippingAmount;
|
req.session.totalCartShipping = internationalShippingAmount;
|
||||||
req.session.totalCartAmount = req.session.totalCartAmount + internationalShippingAmount;
|
req.session.totalCartAmount = amount + internationalShippingAmount;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Domestic shipping
|
// Domestic shipping
|
||||||
req.session.shippingMessage = 'Domestic shipping';
|
req.session.shippingMessage = 'Domestic shipping';
|
||||||
req.session.totalCartShipping = domesticShippingAmount;
|
req.session.totalCartShipping = domesticShippingAmount;
|
||||||
req.session.totalCartAmount = req.session.totalCartAmount + domesticShippingAmount;
|
req.session.totalCartAmount = amount + domesticShippingAmount;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const moment = require('moment');
|
||||||
const glob = require('glob');
|
const glob = require('glob');
|
||||||
const Ajv = require('ajv');
|
const Ajv = require('ajv');
|
||||||
const ajv = new Ajv();
|
const ajv = new Ajv();
|
||||||
|
@ -19,6 +20,13 @@ const addSchemas = () => {
|
||||||
const amountRegex = /^\d+\.\d\d$/;
|
const amountRegex = /^\d+\.\d\d$/;
|
||||||
ajv.addFormat('amount', amountRegex);
|
ajv.addFormat('amount', amountRegex);
|
||||||
|
|
||||||
|
// Datetime format
|
||||||
|
ajv.addFormat('datetime', {
|
||||||
|
validate: (dateTimeString) => {
|
||||||
|
return moment(dateTimeString, 'DD/MM/YYYY HH:mm').isValid();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ajv.addKeyword('isNotEmpty', {
|
ajv.addKeyword('isNotEmpty', {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
validate: (schema, data) => {
|
validate: (schema, data) => {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"$id": "editDiscount",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"amount",
|
||||||
|
"percent"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"type": "object",
|
||||||
|
"format" : "datetime"
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"type": "object",
|
||||||
|
"format" : "datetime"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"discountId"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"$id": "newDiscount",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"amount",
|
||||||
|
"percent"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"type": "object",
|
||||||
|
"format" : "datetime"
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"type": "object",
|
||||||
|
"format" : "datetime"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"code",
|
||||||
|
"type",
|
||||||
|
"value",
|
||||||
|
"start",
|
||||||
|
"end"
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
const { getConfig } = require('./common');
|
const { getConfig } = require('./common');
|
||||||
const { initDb } = require('./db');
|
const { initDb } = require('./db');
|
||||||
const { fixProductDates } = require('../test/helper');
|
const { fixProductDates, fixDiscountDates } = require('../test/helper');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ initDb(config.databaseConnectionString, (err, db) => {
|
||||||
db.users.deleteMany({}, {}),
|
db.users.deleteMany({}, {}),
|
||||||
db.customers.deleteMany({}, {}),
|
db.customers.deleteMany({}, {}),
|
||||||
db.products.deleteMany({}, {}),
|
db.products.deleteMany({}, {}),
|
||||||
|
db.discounts.deleteMany({}, {}),
|
||||||
db.menu.deleteMany({}, {})
|
db.menu.deleteMany({}, {})
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -22,6 +23,7 @@ initDb(config.databaseConnectionString, (err, db) => {
|
||||||
db.users.insertMany(jsonData.users),
|
db.users.insertMany(jsonData.users),
|
||||||
db.customers.insertMany(jsonData.customers),
|
db.customers.insertMany(jsonData.customers),
|
||||||
db.products.insertMany(fixProductDates(jsonData.products)),
|
db.products.insertMany(fixProductDates(jsonData.products)),
|
||||||
|
db.discounts.insertMany(fixDiscountDates(jsonData.discounts)),
|
||||||
db.menu.insertOne(jsonData.menu)
|
db.menu.insertOne(jsonData.menu)
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
@ -172,5 +172,24 @@
|
||||||
"Dashboard": "Dashboard",
|
"Dashboard": "Dashboard",
|
||||||
"Create order": "Create order",
|
"Create order": "Create order",
|
||||||
"Order shipping amount": "Order shipping amount",
|
"Order shipping amount": "Order shipping amount",
|
||||||
"Order net amount": "Order net amount"
|
"Order net amount": "Order net amount",
|
||||||
|
"Discount code": "Discount code",
|
||||||
|
"Apply": "Apply",
|
||||||
|
"Discount codes": "Discount codes",
|
||||||
|
"Code": "Code",
|
||||||
|
"Expiry": "Expiry",
|
||||||
|
"There are currently no discount codes setup.": "There are currently no discount codes setup.",
|
||||||
|
"New discount": "New discount",
|
||||||
|
"Amount": "Amount",
|
||||||
|
"Percent": "Percent",
|
||||||
|
"Discount type": "Discount type",
|
||||||
|
"Discount value": "Discount value",
|
||||||
|
"Discount": "Discount",
|
||||||
|
"Edit discount": "Edit discount",
|
||||||
|
"New Discount": "New Discount",
|
||||||
|
"Discount start": "Discount start",
|
||||||
|
"Discount end": "Discount end",
|
||||||
|
"Start": "Start",
|
||||||
|
"Running": "Running",
|
||||||
|
"Not running": "Not running"
|
||||||
}
|
}
|
|
@ -9255,71 +9255,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"superagent": {
|
|
||||||
"version": "3.8.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz",
|
|
||||||
"integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"component-emitter": "^1.2.0",
|
|
||||||
"cookiejar": "^2.1.0",
|
|
||||||
"debug": "^3.1.0",
|
|
||||||
"extend": "^3.0.0",
|
|
||||||
"form-data": "^2.3.1",
|
|
||||||
"formidable": "^1.2.0",
|
|
||||||
"methods": "^1.1.1",
|
|
||||||
"mime": "^1.4.1",
|
|
||||||
"qs": "^6.5.1",
|
|
||||||
"readable-stream": "^2.3.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"debug": {
|
|
||||||
"version": "3.2.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
|
||||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"ms": "^2.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ms": {
|
|
||||||
"version": "2.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
|
||||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"process-nextick-args": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"readable-stream": {
|
|
||||||
"version": "2.3.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
|
||||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"core-util-is": "~1.0.0",
|
|
||||||
"inherits": "~2.0.3",
|
|
||||||
"isarray": "~1.0.0",
|
|
||||||
"process-nextick-args": "~2.0.0",
|
|
||||||
"safe-buffer": "~5.1.1",
|
|
||||||
"string_decoder": "~1.1.1",
|
|
||||||
"util-deprecate": "~1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "~5.1.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"supertap": {
|
"supertap": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supertap/-/supertap-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supertap/-/supertap-1.0.0.tgz",
|
||||||
|
@ -9363,22 +9298,78 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"supertest": {
|
"supertest": {
|
||||||
"version": "3.4.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz",
|
||||||
"integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==",
|
"integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"methods": "^1.1.2",
|
"methods": "^1.1.2",
|
||||||
"superagent": "^3.8.3"
|
"superagent": "^3.8.3"
|
||||||
}
|
},
|
||||||
},
|
"dependencies": {
|
||||||
"supertest-session": {
|
"debug": {
|
||||||
"version": "4.0.0",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/supertest-session/-/supertest-session-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||||
"integrity": "sha512-9d7KAL+K9hnnicov7USv/Nu1tSl40qSrOsB8zZHOEtfEzHaAoID6tbl1NeCVUg7SJreJMlNn+KJ88V7FW8gD6Q==",
|
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"object-assign": "^4.0.1"
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"process-nextick-args": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"readable-stream": {
|
||||||
|
"version": "2.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||||
|
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"core-util-is": "~1.0.0",
|
||||||
|
"inherits": "~2.0.3",
|
||||||
|
"isarray": "~1.0.0",
|
||||||
|
"process-nextick-args": "~2.0.0",
|
||||||
|
"safe-buffer": "~5.1.1",
|
||||||
|
"string_decoder": "~1.1.1",
|
||||||
|
"util-deprecate": "~1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"string_decoder": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "~5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"superagent": {
|
||||||
|
"version": "3.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz",
|
||||||
|
"integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"component-emitter": "^1.2.0",
|
||||||
|
"cookiejar": "^2.1.0",
|
||||||
|
"debug": "^3.1.0",
|
||||||
|
"extend": "^3.0.0",
|
||||||
|
"form-data": "^2.3.1",
|
||||||
|
"formidable": "^1.2.0",
|
||||||
|
"methods": "^1.1.1",
|
||||||
|
"mime": "^1.4.1",
|
||||||
|
"qs": "^6.5.1",
|
||||||
|
"readable-stream": "^2.3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
|
|
|
@ -84,8 +84,7 @@
|
||||||
"gulp-minify": "^3.1.0",
|
"gulp-minify": "^3.1.0",
|
||||||
"gulp-rename": "^1.4.0",
|
"gulp-rename": "^1.4.0",
|
||||||
"less": "^3.10.3",
|
"less": "^3.10.3",
|
||||||
"supertest": "^3.4.2",
|
"supertest": "^4.0.2"
|
||||||
"supertest-session": "^4.0.0"
|
|
||||||
},
|
},
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -284,11 +284,11 @@ $(document).ready(function (){
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call to API to check if a permalink is available
|
// Call to API to check if a permalink is available
|
||||||
$(document).on('click', '#validate_permalink', function(e){
|
$(document).on('click', '#validatePermalink', function(e){
|
||||||
if($('#productPermalink').val() !== ''){
|
if($('#productPermalink').val() !== ''){
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/admin/api/validate_permalink',
|
url: '/admin/validatePermalink',
|
||||||
data: { permalink: $('#productPermalink').val(), docId: $('#productId').val() }
|
data: { permalink: $('#productPermalink').val(), docId: $('#productId').val() }
|
||||||
})
|
})
|
||||||
.done(function(msg){
|
.done(function(msg){
|
||||||
|
@ -404,10 +404,10 @@ $(document).ready(function (){
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call to API for a change to the published state of a product
|
// Call to API for a change to the published state of a product
|
||||||
$('input[class="published_state"]').change(function(){
|
$('input[class="publishedState"]').change(function(){
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/admin/product/published_state',
|
url: '/admin/product/publishedState',
|
||||||
data: { id: this.id, state: this.checked }
|
data: { id: this.id, state: this.checked }
|
||||||
})
|
})
|
||||||
.done(function(msg){
|
.done(function(msg){
|
||||||
|
@ -544,6 +544,86 @@ $(document).ready(function (){
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#discountNewForm').validator().on('submit', function(e){
|
||||||
|
if(!e.isDefaultPrevented()){
|
||||||
|
e.preventDefault();
|
||||||
|
$.ajax({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/admin/settings/discount/create',
|
||||||
|
data: {
|
||||||
|
code: $('#discountCode').val(),
|
||||||
|
type: $('#discountType').val(),
|
||||||
|
value: $('#discountValue').val(),
|
||||||
|
start: $('#discountStart').val(),
|
||||||
|
end: $('#discountEnd').val()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done(function(msg){
|
||||||
|
showNotification(msg.message, 'success', false, '/admin/settings/discount/edit/' + msg.discountId);
|
||||||
|
})
|
||||||
|
.fail(function(msg){
|
||||||
|
showNotification(msg.responseJSON.message, 'danger');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#discountEditForm').validator().on('submit', function(e){
|
||||||
|
if(!e.isDefaultPrevented()){
|
||||||
|
e.preventDefault();
|
||||||
|
$.ajax({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/admin/settings/discount/update',
|
||||||
|
data: {
|
||||||
|
discountId: $('#discountId').val(),
|
||||||
|
code: $('#discountCode').val(),
|
||||||
|
type: $('#discountType').val(),
|
||||||
|
value: $('#discountValue').val(),
|
||||||
|
start: $('#discountStart').val(),
|
||||||
|
end: $('#discountEnd').val()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done(function(msg){
|
||||||
|
showNotification(msg.message, 'success');
|
||||||
|
})
|
||||||
|
.fail(function(msg){
|
||||||
|
showNotification(msg.responseJSON.message, 'danger');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#discountStart').datetimepicker({
|
||||||
|
uiLibrary: 'bootstrap4',
|
||||||
|
footer: true,
|
||||||
|
modal: true,
|
||||||
|
format: 'dd/mm/yyyy HH:MM',
|
||||||
|
showOtherMonths: true
|
||||||
|
});
|
||||||
|
$('#discountEnd').datetimepicker({
|
||||||
|
uiLibrary: 'bootstrap4',
|
||||||
|
footer: true,
|
||||||
|
modal: true,
|
||||||
|
format: 'dd/mm/yyyy HH:MM'
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '#btnDiscountDelete', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
if(confirm('Are you sure?')){
|
||||||
|
$.ajax({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/admin/settings/discount/delete',
|
||||||
|
data: {
|
||||||
|
discountId: $(this).attr('data-id')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done(function(msg){
|
||||||
|
showNotification(msg.message, 'success', true);
|
||||||
|
})
|
||||||
|
.fail(function(msg){
|
||||||
|
showNotification(msg.message, 'danger', true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$(document).on('click', '#settings-menu-new', function(e){
|
$(document).on('click', '#settings-menu-new', function(e){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
@ -613,7 +693,7 @@ $(document).ready(function (){
|
||||||
$.ajax({
|
$.ajax({
|
||||||
data: { order: menuOrder },
|
data: { order: menuOrder },
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: '/admin/settings/menu/save_order'
|
url: '/admin/settings/menu/saveOrder'
|
||||||
})
|
})
|
||||||
.done(function(){
|
.done(function(){
|
||||||
showNotification('Menu order saved', 'success', true);
|
showNotification('Menu order saved', 'success', true);
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -183,6 +183,38 @@ $(document).ready(function (){
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#addDiscountCode').on('click', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
$.ajax({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/checkout/adddiscountcode',
|
||||||
|
data: {
|
||||||
|
discountCode: $('#discountCode').val()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.done(function(msg){
|
||||||
|
showNotification(msg.message, 'success', true);
|
||||||
|
})
|
||||||
|
.fail(function(msg){
|
||||||
|
showNotification(msg.responseJSON.message, 'danger');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#removeDiscountCode').on('click', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
$.ajax({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/checkout/removediscountcode',
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
.done(function(msg){
|
||||||
|
showNotification(msg.message, 'success', true);
|
||||||
|
})
|
||||||
|
.fail(function(msg){
|
||||||
|
showNotification(msg.responseJSON.message, 'danger');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$('#loginForm').on('click', function(e){
|
$('#loginForm').on('click', function(e){
|
||||||
if(!e.isDefaultPrevented()){
|
if(!e.isDefaultPrevented()){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -513,6 +545,15 @@ function updateCartDiv(){
|
||||||
shippingTotal = `<span id="shipping-amount">${session.shippingMessage}</span>`;
|
shippingTotal = `<span id="shipping-amount">${session.shippingMessage}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var discountTotalAmt = numeral(session.totalCartDiscount).format('0.00');
|
||||||
|
var discountTotal = '';
|
||||||
|
if(session.totalCartDiscount > 0){
|
||||||
|
discountTotal = `
|
||||||
|
<div class="text-right">
|
||||||
|
Discount: <strong id="discount-amount">${result.currencySymbol}${discountTotalAmt}</strong>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
// If the cart has contents
|
// If the cart has contents
|
||||||
if(cart){
|
if(cart){
|
||||||
$('#cart-empty').empty();
|
$('#cart-empty').empty();
|
||||||
|
@ -553,16 +594,16 @@ function updateCartDiv(){
|
||||||
<div class="col-12 col-md-6 no-pad-left mb-2">
|
<div class="col-12 col-md-6 no-pad-left mb-2">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<button class="btn btn-outline-primary btn-qty-minus" type="button">-</button>
|
<button class="btn btn-primary btn-qty-minus" type="button">-</button>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" class="form-control cart-product-quantity text-center" id="${productId}-qty" data-id="${productId}" maxlength="2" value="${item.quantity}">
|
<input type="number" class="form-control cart-product-quantity text-center" id="${productId}-qty" data-id="${productId}" maxlength="2" value="${item.quantity}">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button class="btn btn-outline-primary btn-qty-add" type="button">+</button>
|
<button class="btn btn-primary btn-qty-add" type="button">+</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4 col-md-2 no-pad-left">
|
<div class="col-4 col-md-2 no-pad-left">
|
||||||
<button class="btn btn-outline-danger btn-delete-from-cart" data-id="${productId}" type="button"><i class="far fa-trash-alt" data-id="${productId}" aria-hidden="true"></i></button>
|
<button class="btn btn-danger btn-delete-from-cart" data-id="${productId}" type="button"><i class="far fa-trash-alt" data-id="${productId}" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8 col-md-4 align-self-center text-right">
|
<div class="col-8 col-md-4 align-self-center text-right">
|
||||||
<strong class="my-auto">${result.currencySymbol}${productTotalAmount}</strong>
|
<strong class="my-auto">${result.currencySymbol}${productTotalAmount}</strong>
|
||||||
|
@ -588,6 +629,7 @@ function updateCartDiv(){
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
${shippingTotal}
|
${shippingTotal}
|
||||||
</div>
|
</div>
|
||||||
|
${discountTotal}
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
Total:
|
Total:
|
||||||
<strong id="total-cart-amount">${result.currencySymbol}${totalAmount}</strong>
|
<strong id="total-cart-amount">${result.currencySymbol}${totalAmount}</strong>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4,11 +4,6 @@
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
border-color: #000000;
|
border-color: #000000;
|
||||||
}
|
}
|
||||||
.btn-outline-danger {
|
|
||||||
color: #ffffff !important;
|
|
||||||
background-color: #cc4135;
|
|
||||||
border-color: #cc4135;
|
|
||||||
}
|
|
||||||
.has-error input,
|
.has-error input,
|
||||||
.has-error textarea,
|
.has-error textarea,
|
||||||
.has-error div {
|
.has-error div {
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
.btn-outline-primary,.btn-warning{color:#fff!important;background-color:#000;border-color:#000}.btn-outline-danger{color:#fff!important;background-color:#cc4135;border-color:#cc4135}.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}.productsWrapper{padding-right:10px;padding-left:10px}.searchBarWrapper{padding-right:0;padding-left:0}.footer{padding-top:20px}.product-price{padding-bottom:0}.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}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}@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}}
|
.btn-outline-primary,.btn-warning{color:#fff!important;background-color:#000;border-color:#000}.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}.productsWrapper{padding-right:10px;padding-left:10px}.searchBarWrapper{padding-right:0;padding-left:0}.footer{padding-top:20px}.product-price{padding-bottom:0}.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}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}@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}}
|
|
@ -10,12 +10,6 @@
|
||||||
border-color: @btn-danger-color-border;
|
border-color: @btn-danger-color-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-danger {
|
|
||||||
color: @btn-danger-color-txt !important;
|
|
||||||
background-color: @accent-color;
|
|
||||||
border-color: @accent-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.has-error input, .has-error textarea, .has-error div {
|
.has-error input, .has-error textarea, .has-error div {
|
||||||
border-color: @accent-color;
|
border-color: @accent-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,26 +490,29 @@ input.form-control.customerDetails{
|
||||||
color: @primary-btn-color;
|
color: @primary-btn-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-warning, .btn-outline-primary {
|
.btn-warning, .btn-primary {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: @primary-btn-color;
|
background-color: @primary-btn-color;
|
||||||
border-color: @primary-btn-color;
|
border-color: @primary-btn-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-warning:hover, .btn-outline-primary:hover {
|
.btn-primary:hover,
|
||||||
|
.btn-primary:active,
|
||||||
|
.btn-primary.focus,
|
||||||
|
.btn-primary:focus {
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
background-color: @primary-btn-color !important;
|
background-color: @primary-btn-color !important;
|
||||||
border-color: @primary-btn-color !important;
|
border-color: @primary-btn-color !important;
|
||||||
opacity: 0.65;
|
opacity: 0.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-danger{
|
.btn-danger{
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: @secondary-btn-color;
|
background-color: @secondary-btn-color;
|
||||||
border-color: @secondary-btn-color;
|
border-color: @secondary-btn-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-danger:hover{
|
.btn-danger:hover{
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
background-color: @secondary-btn-color !important;
|
background-color: @secondary-btn-color !important;
|
||||||
border-color: @secondary-btn-color !important;
|
border-color: @secondary-btn-color !important;
|
||||||
|
|
|
@ -391,24 +391,26 @@ input.form-control.customerDetails {
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
.btn-warning,
|
.btn-warning,
|
||||||
.btn-outline-primary {
|
.btn-primary {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
border-color: #000000;
|
border-color: #000000;
|
||||||
}
|
}
|
||||||
.btn-warning:hover,
|
.btn-primary:hover,
|
||||||
.btn-outline-primary:hover {
|
.btn-primary:active,
|
||||||
|
.btn-primary.focus,
|
||||||
|
.btn-primary:focus {
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
background-color: #000000 !important;
|
background-color: #000000 !important;
|
||||||
border-color: #000000 !important;
|
border-color: #000000 !important;
|
||||||
opacity: 0.65;
|
opacity: 0.65;
|
||||||
}
|
}
|
||||||
.btn-outline-danger {
|
.btn-danger {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #cc3a2c;
|
background-color: #cc3a2c;
|
||||||
border-color: #cc3a2c;
|
border-color: #cc3a2c;
|
||||||
}
|
}
|
||||||
.btn-outline-danger:hover {
|
.btn-danger:hover {
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
background-color: #cc3a2c !important;
|
background-color: #cc3a2c !important;
|
||||||
border-color: #cc3a2c !important;
|
border-color: #cc3a2c !important;
|
||||||
|
|
File diff suppressed because one or more lines are too long
168
routes/admin.js
168
routes/admin.js
|
@ -4,10 +4,12 @@ const { restrict, checkAccess } = require('../lib/auth');
|
||||||
const escape = require('html-entities').AllHtmlEntities;
|
const escape = require('html-entities').AllHtmlEntities;
|
||||||
const colors = require('colors');
|
const colors = require('colors');
|
||||||
const bcrypt = require('bcryptjs');
|
const bcrypt = require('bcryptjs');
|
||||||
|
const moment = require('moment');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const mime = require('mime-type/with-db');
|
const mime = require('mime-type/with-db');
|
||||||
|
const { validateJson } = require('../lib/schema');
|
||||||
const ObjectId = require('mongodb').ObjectID;
|
const ObjectId = require('mongodb').ObjectID;
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
@ -401,7 +403,7 @@ router.post('/admin/settings/menu/delete', restrict, checkAccess, (req, res) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
// We call this via a Ajax call to save the order from the sortable list
|
// We call this via a Ajax call to save the order from the sortable list
|
||||||
router.post('/admin/settings/menu/save_order', restrict, checkAccess, (req, res) => {
|
router.post('/admin/settings/menu/saveOrder', restrict, checkAccess, (req, res) => {
|
||||||
const result = common.orderMenu(req, res);
|
const result = common.orderMenu(req, res);
|
||||||
if(result === false){
|
if(result === false){
|
||||||
res.status(400).json({ message: 'Failed saving menu order' });
|
res.status(400).json({ message: 'Failed saving menu order' });
|
||||||
|
@ -411,7 +413,7 @@ router.post('/admin/settings/menu/save_order', restrict, checkAccess, (req, res)
|
||||||
});
|
});
|
||||||
|
|
||||||
// validate the permalink
|
// validate the permalink
|
||||||
router.post('/admin/api/validate_permalink', async (req, res) => {
|
router.post('/admin/validatePermalink', async (req, res) => {
|
||||||
// if doc id is provided it checks for permalink in any products other that one provided,
|
// 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
|
// else it just checks for any products with that permalink
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
@ -431,6 +433,168 @@ router.post('/admin/api/validate_permalink', async (req, res) => {
|
||||||
res.status(200).json({ message: 'Permalink validated successfully' });
|
res.status(200).json({ message: 'Permalink validated successfully' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Discount codes
|
||||||
|
router.get('/admin/settings/discounts', restrict, checkAccess, async (req, res) => {
|
||||||
|
const db = req.app.db;
|
||||||
|
|
||||||
|
const discounts = await db.discounts.find({}).toArray();
|
||||||
|
|
||||||
|
res.render('settings-discounts', {
|
||||||
|
title: 'Discount code',
|
||||||
|
config: req.app.config,
|
||||||
|
session: req.session,
|
||||||
|
discounts,
|
||||||
|
admin: true,
|
||||||
|
message: common.clearSessionValue(req.session, 'message'),
|
||||||
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
|
helpers: req.handlebars.helpers
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Edit a discount code
|
||||||
|
router.get('/admin/settings/discount/edit/:id', restrict, checkAccess, async (req, res) => {
|
||||||
|
const db = req.app.db;
|
||||||
|
|
||||||
|
const discount = await db.discounts.findOne({ _id: common.getId(req.params.id) });
|
||||||
|
|
||||||
|
res.render('settings-discount-edit', {
|
||||||
|
title: 'Discount code edit',
|
||||||
|
session: req.session,
|
||||||
|
admin: true,
|
||||||
|
discount,
|
||||||
|
message: common.clearSessionValue(req.session, 'message'),
|
||||||
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
|
helpers: req.handlebars.helpers,
|
||||||
|
config: req.app.config
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update discount code
|
||||||
|
router.post('/admin/settings/discount/update', restrict, checkAccess, async (req, res) => {
|
||||||
|
const db = req.app.db;
|
||||||
|
|
||||||
|
// Doc to insert
|
||||||
|
const discountDoc = {
|
||||||
|
discountId: req.body.discountId,
|
||||||
|
code: req.body.code,
|
||||||
|
type: req.body.type,
|
||||||
|
value: parseInt(req.body.value),
|
||||||
|
start: moment(req.body.start, 'DD/MM/YYYY HH:mm').toDate(),
|
||||||
|
end: moment(req.body.end, 'DD/MM/YYYY HH:mm').toDate()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate the body again schema
|
||||||
|
const schemaValidate = validateJson('editDiscount', discountDoc);
|
||||||
|
if(!schemaValidate.result){
|
||||||
|
res.status(400).json(schemaValidate.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check start is after today
|
||||||
|
if(moment(discountDoc.start).isBefore(moment())){
|
||||||
|
res.status(400).json({ message: 'Discount start date needs to be after today' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check end is after the start
|
||||||
|
if(!moment(discountDoc.end).isAfter(moment(discountDoc.start))){
|
||||||
|
res.status(400).json({ message: 'Discount end date needs to be after start date' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if code exists
|
||||||
|
const checkCode = await db.discounts.countDocuments({
|
||||||
|
code: discountDoc.code,
|
||||||
|
_id: { $ne: common.getId(discountDoc.discountId) }
|
||||||
|
});
|
||||||
|
if(checkCode){
|
||||||
|
res.status(400).json({ message: 'Discount code already exists' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove discountID
|
||||||
|
delete discountDoc.discountId;
|
||||||
|
|
||||||
|
try{
|
||||||
|
await db.discounts.updateOne({ _id: common.getId(req.body.discountId) }, { $set: discountDoc }, {});
|
||||||
|
res.status(200).json({ message: 'Successfully saved', discount: discountDoc });
|
||||||
|
}catch(ex){
|
||||||
|
res.status(400).json({ message: 'Failed to save. Please try again' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a discount code
|
||||||
|
router.get('/admin/settings/discount/new', restrict, checkAccess, async (req, res) => {
|
||||||
|
res.render('settings-discount-new', {
|
||||||
|
title: 'Discount code create',
|
||||||
|
session: req.session,
|
||||||
|
admin: true,
|
||||||
|
message: common.clearSessionValue(req.session, 'message'),
|
||||||
|
messageType: common.clearSessionValue(req.session, 'messageType'),
|
||||||
|
helpers: req.handlebars.helpers,
|
||||||
|
config: req.app.config
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a discount code
|
||||||
|
router.post('/admin/settings/discount/create', restrict, checkAccess, async (req, res) => {
|
||||||
|
const db = req.app.db;
|
||||||
|
|
||||||
|
// Doc to insert
|
||||||
|
const discountDoc = {
|
||||||
|
code: req.body.code,
|
||||||
|
type: req.body.type,
|
||||||
|
value: parseInt(req.body.value),
|
||||||
|
start: moment(req.body.start, 'DD/MM/YYYY HH:mm').toDate(),
|
||||||
|
end: moment(req.body.end, 'DD/MM/YYYY HH:mm').toDate()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate the body again schema
|
||||||
|
const schemaValidate = validateJson('newDiscount', discountDoc);
|
||||||
|
if(!schemaValidate.result){
|
||||||
|
res.status(400).json(schemaValidate.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if code exists
|
||||||
|
const checkCode = await db.discounts.countDocuments({
|
||||||
|
code: discountDoc.code
|
||||||
|
});
|
||||||
|
if(checkCode){
|
||||||
|
res.status(400).json({ message: 'Discount code already exists' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check start is after today
|
||||||
|
if(moment(discountDoc.start).isBefore(moment())){
|
||||||
|
res.status(400).json({ message: 'Discount start date needs to be after today' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check end is after the start
|
||||||
|
if(!moment(discountDoc.end).isAfter(moment(discountDoc.start))){
|
||||||
|
res.status(400).json({ message: 'Discount end date needs to be after start date' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert discount code
|
||||||
|
const discount = await db.discounts.insertOne(discountDoc);
|
||||||
|
res.status(200).json({ message: 'Discount code created successfully', discountId: discount.insertedId });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete discount code
|
||||||
|
router.delete('/admin/settings/discount/delete', restrict, checkAccess, async (req, res) => {
|
||||||
|
const db = req.app.db;
|
||||||
|
|
||||||
|
try{
|
||||||
|
await db.discounts.deleteOne({ _id: common.getId(req.body.discountId) }, {});
|
||||||
|
res.status(200).json({ message: 'Discount code successfully deleted' });
|
||||||
|
return;
|
||||||
|
}catch(ex){
|
||||||
|
res.status(400).json({ message: 'Error deleting discount code. Please try again.' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// upload the file
|
// upload the file
|
||||||
const upload = multer({ dest: 'public/uploads/' });
|
const upload = multer({ dest: 'public/uploads/' });
|
||||||
router.post('/admin/file/upload', restrict, checkAccess, upload.single('uploadFile'), async (req, res) => {
|
router.post('/admin/file/upload', restrict, checkAccess, upload.single('uploadFile'), async (req, res) => {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const colors = require('colors');
|
const colors = require('colors');
|
||||||
|
const moment = require('moment');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const {
|
const {
|
||||||
getId,
|
getId,
|
||||||
|
@ -173,7 +174,7 @@ router.get('/checkout/cartdata', (req, res) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/checkout/payment', (req, res) => {
|
router.get('/checkout/payment', async (req, res) => {
|
||||||
const config = req.app.config;
|
const config = req.app.config;
|
||||||
|
|
||||||
// if there is no items in the cart then render a failure
|
// if there is no items in the cart then render a failure
|
||||||
|
@ -189,6 +190,9 @@ router.get('/checkout/payment', (req, res) => {
|
||||||
paymentType = '_subscription';
|
paymentType = '_subscription';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update total cart amount one last time before payment
|
||||||
|
await updateTotalCart(req, res);
|
||||||
|
|
||||||
res.render(`${config.themeViews}checkout-payment`, {
|
res.render(`${config.themeViews}checkout-payment`, {
|
||||||
title: 'Checkout',
|
title: 'Checkout',
|
||||||
config: req.app.config,
|
config: req.app.config,
|
||||||
|
@ -207,6 +211,84 @@ router.get('/checkout/payment', (req, res) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/checkout/adddiscountcode', async (req, res) => {
|
||||||
|
const config = req.app.config;
|
||||||
|
const db = req.app.db;
|
||||||
|
|
||||||
|
// if there is no items in the cart return a failure
|
||||||
|
if(!req.session.cart){
|
||||||
|
res.status(400).json({
|
||||||
|
message: 'The are no items in your cart.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the discount module is loaded
|
||||||
|
if(!config.modules.loaded.discount){
|
||||||
|
res.status(400).json({
|
||||||
|
message: 'Access denied.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check defined or null
|
||||||
|
if(!req.body.discountCode || req.body.discountCode === ''){
|
||||||
|
res.status(400).json({
|
||||||
|
message: 'Discount code is invalid or expired'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate discount code
|
||||||
|
const discount = await db.discounts.findOne({ code: req.body.discountCode });
|
||||||
|
if(!discount){
|
||||||
|
res.status(400).json({
|
||||||
|
message: 'Discount code is invalid or expired'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate date validity
|
||||||
|
if(!moment().isBetween(moment(discount.start), moment(discount.end))){
|
||||||
|
res.status(400).json({
|
||||||
|
message: 'Discount is expired'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the discount code
|
||||||
|
req.session.discountCode = discount.code;
|
||||||
|
|
||||||
|
// Update the cart amount
|
||||||
|
await updateTotalCart(req, res);
|
||||||
|
|
||||||
|
// Return the message
|
||||||
|
res.status(200).json({
|
||||||
|
message: 'Discount code applied'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/checkout/removediscountcode', async (req, res) => {
|
||||||
|
// if there is no items in the cart return a failure
|
||||||
|
if(!req.session.cart){
|
||||||
|
res.status(400).json({
|
||||||
|
message: 'The are no items in your cart.'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the discount code
|
||||||
|
delete req.session.discountCode;
|
||||||
|
|
||||||
|
// update total cart amount
|
||||||
|
await updateTotalCart(req, res);
|
||||||
|
|
||||||
|
// Return the message
|
||||||
|
res.status(200).json({
|
||||||
|
message: 'Discount code removed'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// show an individual product
|
// show an individual product
|
||||||
router.get('/product/:id', async (req, res) => {
|
router.get('/product/:id', async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
@ -310,7 +392,7 @@ router.post('/product/updatecart', async (req, res, next) => {
|
||||||
req.session.cart[cartItem.productId].totalItemPrice = productPrice * productQuantity;
|
req.session.cart[cartItem.productId].totalItemPrice = productPrice * productQuantity;
|
||||||
|
|
||||||
// update total cart amount
|
// update total cart amount
|
||||||
updateTotalCart(req, res);
|
await updateTotalCart(req, res);
|
||||||
|
|
||||||
// Update checking cart for subscription
|
// Update checking cart for subscription
|
||||||
updateSubscriptionCheck(req, res);
|
updateSubscriptionCheck(req, res);
|
||||||
|
@ -345,7 +427,7 @@ router.post('/product/removefromcart', async (req, res, next) => {
|
||||||
$set: { cart: req.session.cart }
|
$set: { cart: req.session.cart }
|
||||||
});
|
});
|
||||||
// update total cart
|
// update total cart
|
||||||
updateTotalCart(req, res);
|
await updateTotalCart(req, res);
|
||||||
|
|
||||||
// Update checking cart for subscription
|
// Update checking cart for subscription
|
||||||
updateSubscriptionCheck(req, res);
|
updateSubscriptionCheck(req, res);
|
||||||
|
@ -489,7 +571,7 @@ router.post('/product/addtocart', async (req, res, next) => {
|
||||||
}, { upsert: true });
|
}, { upsert: true });
|
||||||
|
|
||||||
// update total cart amount
|
// update total cart amount
|
||||||
updateTotalCart(req, res);
|
await updateTotalCart(req, res);
|
||||||
|
|
||||||
// Update checking cart for subscription
|
// Update checking cart for subscription
|
||||||
updateSubscriptionCheck(req, res);
|
updateSubscriptionCheck(req, res);
|
||||||
|
|
|
@ -305,7 +305,7 @@ router.post('/admin/product/delete', restrict, checkAccess, async (req, res) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
// update the published state based on an ajax call from the frontend
|
// update the published state based on an ajax call from the frontend
|
||||||
router.post('/admin/product/published_state', restrict, checkAccess, async (req, res) => {
|
router.post('/admin/product/publishedState', restrict, checkAccess, async (req, res) => {
|
||||||
const db = req.app.db;
|
const db = req.app.db;
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const session = require('supertest-session');
|
const moment = require('moment');
|
||||||
|
const supertest = require('supertest');
|
||||||
const app = require('../app.js');
|
const app = require('../app.js');
|
||||||
const { newId } = require('../lib/common');
|
const { newId } = require('../lib/common');
|
||||||
const { runIndexing } = require('../lib/indexing');
|
const { runIndexing } = require('../lib/indexing');
|
||||||
|
@ -14,6 +15,7 @@ const g = {
|
||||||
db: {},
|
db: {},
|
||||||
config: {},
|
config: {},
|
||||||
products: {},
|
products: {},
|
||||||
|
discounts: {},
|
||||||
customers: {},
|
customers: {},
|
||||||
users: {},
|
users: {},
|
||||||
request: null,
|
request: null,
|
||||||
|
@ -26,20 +28,23 @@ const setup = (db) => {
|
||||||
db.users.deleteMany({}, {}),
|
db.users.deleteMany({}, {}),
|
||||||
db.customers.deleteMany({}, {}),
|
db.customers.deleteMany({}, {}),
|
||||||
db.products.deleteMany({}, {}),
|
db.products.deleteMany({}, {}),
|
||||||
db.orders.deleteMany({}, {})
|
db.discounts.deleteMany({}, {}),
|
||||||
|
db.orders.deleteMany({}, {}),
|
||||||
|
db.sessions.deleteMany({}, {})
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
db.users.insertMany(addApiKey(jsonData.users)),
|
db.users.insertMany(addApiKey(jsonData.users)),
|
||||||
db.customers.insertMany(jsonData.customers),
|
db.customers.insertMany(jsonData.customers),
|
||||||
db.products.insertMany(fixProductDates(jsonData.products))
|
db.products.insertMany(fixProductDates(jsonData.products)),
|
||||||
|
db.discounts.insertMany(fixDiscountDates(jsonData.discounts))
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const runBefore = async () => {
|
const runBefore = async () => {
|
||||||
// Create a session
|
// Create a session
|
||||||
g.request = session(app);
|
g.request = supertest.agent(app);
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
app.on('appStarted', async () => {
|
app.on('appStarted', async () => {
|
||||||
// Set some stuff now we have the app started
|
// Set some stuff now we have the app started
|
||||||
|
@ -51,6 +56,7 @@ const runBefore = async () => {
|
||||||
// Get some data from DB to use in compares
|
// Get some data from DB to use in compares
|
||||||
g.products = await g.db.products.find({}).toArray();
|
g.products = await g.db.products.find({}).toArray();
|
||||||
g.customers = await g.db.customers.find({}).toArray();
|
g.customers = await g.db.customers.find({}).toArray();
|
||||||
|
g.discounts = await g.db.discounts.find({}).toArray();
|
||||||
g.users = await g.db.users.find({}).toArray();
|
g.users = await g.db.users.find({}).toArray();
|
||||||
|
|
||||||
// Insert orders using product ID's
|
// Insert orders using product ID's
|
||||||
|
@ -87,6 +93,36 @@ const fixProductDates = (products) => {
|
||||||
return products;
|
return products;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fixDiscountDates = (discounts) => {
|
||||||
|
let index = 0;
|
||||||
|
discounts.forEach(() => {
|
||||||
|
let startDate = moment().subtract(1, 'days').toDate();
|
||||||
|
let endDate = moment().add(7, 'days').toDate();
|
||||||
|
const expiredStart = moment().subtract(14, 'days').toDate();
|
||||||
|
const expiredEnd = moment().subtract(7, 'days').toDate();
|
||||||
|
const futureStart = moment().add(7, 'days').toDate();
|
||||||
|
const futureEnd = moment().add(14, 'days').toDate();
|
||||||
|
|
||||||
|
// If code is expired, make sure the dates are correct
|
||||||
|
if(discounts[index].code.substring(0, 7) === 'expired'){
|
||||||
|
startDate = expiredStart;
|
||||||
|
endDate = expiredEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If code is future, make sure the dates are correct
|
||||||
|
if(discounts[index].code.substring(0, 6) === 'future'){
|
||||||
|
startDate = futureStart;
|
||||||
|
endDate = futureEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the expiry dates
|
||||||
|
discounts[index].start = startDate;
|
||||||
|
discounts[index].end = endDate;
|
||||||
|
index++;
|
||||||
|
});
|
||||||
|
return discounts;
|
||||||
|
};
|
||||||
|
|
||||||
const addApiKey = (users) => {
|
const addApiKey = (users) => {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
users.forEach(() => {
|
users.forEach(() => {
|
||||||
|
@ -100,5 +136,6 @@ module.exports = {
|
||||||
runBefore,
|
runBefore,
|
||||||
setup,
|
setup,
|
||||||
g,
|
g,
|
||||||
fixProductDates
|
fixProductDates,
|
||||||
|
fixDiscountDates
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,233 @@
|
||||||
|
import{ serial as test }from'ava';
|
||||||
|
const {
|
||||||
|
runBefore,
|
||||||
|
g
|
||||||
|
} = require('../helper');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
test.before(async () => {
|
||||||
|
await runBefore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Success] Add valid amount discount', async t => {
|
||||||
|
// Remove any sessions
|
||||||
|
await g.db.sessions.deleteMany({}, {});
|
||||||
|
|
||||||
|
await g.request
|
||||||
|
.post('/product/addtocart')
|
||||||
|
.send({
|
||||||
|
productId: g.products[0]._id,
|
||||||
|
productQuantity: 1,
|
||||||
|
productOptions: JSON.stringify(g.products[0].productOptions)
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const res = await g.request
|
||||||
|
.post('/checkout/adddiscountcode')
|
||||||
|
.send({
|
||||||
|
discountCode: g.discounts[0].code
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.message, 'Discount code applied');
|
||||||
|
|
||||||
|
// Get our session
|
||||||
|
const sessions = await g.db.sessions.find({}).toArray();
|
||||||
|
if(!sessions || sessions.length === 0){
|
||||||
|
t.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate what we expect
|
||||||
|
const totalCartAmount = g.products[0].productPrice * 1;
|
||||||
|
|
||||||
|
const session = sessions[0].session;
|
||||||
|
t.deepEqual(session.discountCode, g.discounts[0].code);
|
||||||
|
t.deepEqual(session.totalCartDiscount, g.discounts[0].value);
|
||||||
|
t.deepEqual(session.totalCartAmount, totalCartAmount - g.discounts[0].value);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Success] Add valid percent discount', async t => {
|
||||||
|
// Remove any sessions
|
||||||
|
await g.db.sessions.deleteMany({}, {});
|
||||||
|
|
||||||
|
await g.request
|
||||||
|
.post('/product/addtocart')
|
||||||
|
.send({
|
||||||
|
productId: g.products[0]._id,
|
||||||
|
productQuantity: 1,
|
||||||
|
productOptions: JSON.stringify(g.products[0].productOptions)
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const res = await g.request
|
||||||
|
.post('/checkout/adddiscountcode')
|
||||||
|
.send({
|
||||||
|
discountCode: g.discounts[1].code
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.message, 'Discount code applied');
|
||||||
|
|
||||||
|
// Get our session
|
||||||
|
const sessions = await g.db.sessions.find({}).toArray();
|
||||||
|
if(!sessions || sessions.length === 0){
|
||||||
|
t.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate what we expect - percent
|
||||||
|
const totalCartAmount = g.products[0].productPrice * 1;
|
||||||
|
const expectedDiscount = (g.discounts[1].value / 100) * totalCartAmount;
|
||||||
|
|
||||||
|
const session = sessions[0].session;
|
||||||
|
t.deepEqual(session.discountCode, g.discounts[1].code);
|
||||||
|
t.deepEqual(session.totalCartAmount, totalCartAmount - expectedDiscount);
|
||||||
|
t.deepEqual(session.totalCartDiscount, expectedDiscount);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Fail] Add an expired discount code', async t => {
|
||||||
|
await g.request
|
||||||
|
.post('/product/addtocart')
|
||||||
|
.send({
|
||||||
|
productId: g.products[0]._id,
|
||||||
|
productQuantity: 1,
|
||||||
|
productOptions: JSON.stringify(g.products[0].productOptions)
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const res = await g.request
|
||||||
|
.post('/checkout/adddiscountcode')
|
||||||
|
.send({
|
||||||
|
discountCode: g.discounts[2].code
|
||||||
|
})
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.message, 'Discount is expired');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Fail] Add a future discount code', async t => {
|
||||||
|
await g.request
|
||||||
|
.post('/product/addtocart')
|
||||||
|
.send({
|
||||||
|
productId: g.products[0]._id,
|
||||||
|
productQuantity: 1,
|
||||||
|
productOptions: JSON.stringify(g.products[0].productOptions)
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const res = await g.request
|
||||||
|
.post('/checkout/adddiscountcode')
|
||||||
|
.send({
|
||||||
|
discountCode: g.discounts[3].code
|
||||||
|
})
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.message, 'Discount is expired');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Fail] Add a bogus code', async t => {
|
||||||
|
await g.request
|
||||||
|
.post('/product/addtocart')
|
||||||
|
.send({
|
||||||
|
productId: g.products[0]._id,
|
||||||
|
productQuantity: 1,
|
||||||
|
productOptions: JSON.stringify(g.products[0].productOptions)
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const res = await g.request
|
||||||
|
.post('/checkout/adddiscountcode')
|
||||||
|
.send({
|
||||||
|
discountCode: 'some_bogus_code'
|
||||||
|
})
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.message, 'Discount code is invalid or expired');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Success] Create a new discount code', async t => {
|
||||||
|
// Add a discount code
|
||||||
|
const res = await g.request
|
||||||
|
.post('/admin/settings/discount/create')
|
||||||
|
.send({
|
||||||
|
code: 'TEST_CODE_5',
|
||||||
|
type: 'amount',
|
||||||
|
value: 10,
|
||||||
|
start: moment().add(1, 'days').format('DD/MM/YYYY HH:mm'),
|
||||||
|
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
||||||
|
})
|
||||||
|
.set('apiKey', g.users[0].apiKey)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.message, 'Discount code created successfully');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Fail] Create a new discount code with invalid type', async t => {
|
||||||
|
// Add a discount code
|
||||||
|
const res = await g.request
|
||||||
|
.post('/admin/settings/discount/create')
|
||||||
|
.send({
|
||||||
|
code: 'TEST_CODE_1',
|
||||||
|
type: 'bogus_type',
|
||||||
|
value: 10,
|
||||||
|
start: moment().add(1, 'days').format('DD/MM/YYYY HH:mm'),
|
||||||
|
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
||||||
|
})
|
||||||
|
.set('apiKey', g.users[0].apiKey)
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
t.deepEqual(res.body[0].message, 'should be equal to one of the allowed values');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Fail] Create a new discount code with existing code', async t => {
|
||||||
|
// Add a discount code
|
||||||
|
const res = await g.request
|
||||||
|
.post('/admin/settings/discount/create')
|
||||||
|
.send({
|
||||||
|
code: 'valid_10_amount_code',
|
||||||
|
type: 'amount',
|
||||||
|
value: 10,
|
||||||
|
start: moment().add(1, 'days').format('DD/MM/YYYY HH:mm'),
|
||||||
|
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
||||||
|
})
|
||||||
|
.set('apiKey', g.users[0].apiKey)
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.message, 'Discount code already exists');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Success] Update a discount code', async t => {
|
||||||
|
// Add a discount code
|
||||||
|
const res = await g.request
|
||||||
|
.post('/admin/settings/discount/update')
|
||||||
|
.send({
|
||||||
|
discountId: g.discounts[0]._id,
|
||||||
|
code: 'TEST_CODE_99',
|
||||||
|
type: 'amount',
|
||||||
|
value: 20,
|
||||||
|
start: moment().add(1, 'days').format('DD/MM/YYYY HH:mm'),
|
||||||
|
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
||||||
|
})
|
||||||
|
.set('apiKey', g.users[0].apiKey)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.discount.value, 20);
|
||||||
|
t.deepEqual(res.body.message, 'Successfully saved');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[Fail] Update a discount with same code as existing', async t => {
|
||||||
|
// Add a discount code
|
||||||
|
const res = await g.request
|
||||||
|
.post('/admin/settings/discount/update')
|
||||||
|
.send({
|
||||||
|
discountId: g.discounts[1]._id,
|
||||||
|
code: 'TEST_CODE_99',
|
||||||
|
type: 'amount',
|
||||||
|
value: 20,
|
||||||
|
start: moment().add(1, 'days').format('DD/MM/YYYY HH:mm'),
|
||||||
|
end: moment().add(7, 'days').format('DD/MM/YYYY HH:mm')
|
||||||
|
})
|
||||||
|
.set('apiKey', g.users[0].apiKey)
|
||||||
|
.expect(400);
|
||||||
|
|
||||||
|
t.deepEqual(res.body.message, 'Discount code already exists');
|
||||||
|
});
|
|
@ -22,7 +22,8 @@
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha256-L/W5Wfqfa0sdBNIKN9cG6QA5F2qx4qICmU2VgLruv9Y=" crossorigin="anonymous" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha256-L/W5Wfqfa0sdBNIKN9cG6QA5F2qx4qICmU2VgLruv9Y=" crossorigin="anonymous" />
|
||||||
<link rel="stylesheet" href="/stylesheets/pushy{{config.env}}.css">
|
<link rel="stylesheet" href="/stylesheets/pushy{{config.env}}.css">
|
||||||
{{#if admin}}
|
{{#if admin}}
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.css" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/gijgo/1.9.13/combined/css/gijgo.min.css" integrity="sha256-bH0WSMuCFoG/dxeox/5aOWmaZl729yDg4ylckwSRTfU=" crossorigin="anonymous" />
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.css" integrity="sha256-MdzaXfGXzZdeHw/XEV2LNNycipsLk4uZ0FYzO3hbuvI=" crossorigin="anonymous" />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" integrity="sha256-+N4/V/SbAFiW1MPBCXnfnP9QSN3+Keu+NlB+0ev/YKQ=" crossorigin="anonymous" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" integrity="sha256-+N4/V/SbAFiW1MPBCXnfnP9QSN3+Keu+NlB+0ev/YKQ=" crossorigin="anonymous" />
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tokenfield/0.12.0/css/bootstrap-tokenfield.min.css" integrity="sha256-4qBzeX420hElp9/FzsuqUNqVobcClz1BjnXoxUDSYQ0=" crossorigin="anonymous" />
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tokenfield/0.12.0/css/bootstrap-tokenfield.min.css" integrity="sha256-4qBzeX420hElp9/FzsuqUNqVobcClz1BjnXoxUDSYQ0=" crossorigin="anonymous" />
|
||||||
|
@ -36,6 +37,8 @@
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js" integrity="sha256-x3YZWtRjM8bJqf48dFAv/qmgL68SI4jqNWeSLMZaMGA=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js" integrity="sha256-x3YZWtRjM8bJqf48dFAv/qmgL68SI4jqNWeSLMZaMGA=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha256-WqU1JavFxSAMcLP2WIOI+GB2zWmShMI82mTpLDcqFUg=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha256-WqU1JavFxSAMcLP2WIOI+GB2zWmShMI82mTpLDcqFUg=" crossorigin="anonymous"></script>
|
||||||
{{#if admin}}
|
{{#if admin}}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js" integrity="sha256-4iQZ6BVL4qNKlQ27TExEhBN1HFPvAvAMbFavKKosSWQ=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gijgo/1.9.13/combined/js/gijgo.min.js" integrity="sha256-YZhUu69bCn9uTqQyKwwQ3GyRypS7eaxp/wmVS282sDI=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.js" integrity="sha256-K1exjHe1X4MP24jRizgBaSbUDUrNhFDRSwGoEYGmtJE=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/codemirror.min.js" integrity="sha256-K1exjHe1X4MP24jRizgBaSbUDUrNhFDRSwGoEYGmtJE=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/css/css.min.js" integrity="sha256-D5oJ11cOmRhXSYWELwG2U/XYH3YveZJr9taRYLZ2DSM=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/css/css.min.js" integrity="sha256-D5oJ11cOmRhXSYWELwG2U/XYH3YveZJr9taRYLZ2DSM=" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/xml/xml.min.js" integrity="sha256-ERFGS58tayDq5kkyNwd/89iZZ+HglMH7eYXxG1hxTvA=" crossorigin="anonymous"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/xml/xml.min.js" integrity="sha256-ERFGS58tayDq5kkyNwd/89iZZ+HglMH7eYXxG1hxTvA=" crossorigin="anonymous"></script>
|
||||||
|
@ -100,7 +103,7 @@
|
||||||
{{#unless admin}}
|
{{#unless admin}}
|
||||||
{{#ifCond @root.config.enableLanguages "!=" false}}
|
{{#ifCond @root.config.enableLanguages "!=" false}}
|
||||||
<div class="dropdown d-none d-sm-block">
|
<div class="dropdown d-none d-sm-block">
|
||||||
<button class="btn btn-outline-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button class="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<i class="fas fa-globe-americas"></i>
|
<i class="fas fa-globe-americas"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||||
|
@ -134,7 +137,7 @@
|
||||||
<div id="cart" class="col-md-12 pad-left-12 top-pad-10 pushy pushy-right">
|
<div id="cart" class="col-md-12 pad-left-12 top-pad-10 pushy pushy-right">
|
||||||
<div class="row {{checkout}}">
|
<div class="row {{checkout}}">
|
||||||
<div class="col-sm-12 text-right">
|
<div class="col-sm-12 text-right">
|
||||||
<button class="pushy-link btn btn-outline-primary" type="button">X</button>
|
<button class="pushy-link btn btn-primary" type="button">X</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -142,8 +145,8 @@
|
||||||
{{> (getTheme 'cart')}}
|
{{> (getTheme 'cart')}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 {{showCartButtons @root.session.cart}} cart-buttons">
|
<div class="col-sm-12 {{showCartButtons @root.session.cart}} cart-buttons">
|
||||||
<button class="btn btn-outline-danger float-left" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
|
<button class="btn btn-danger float-left" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
|
||||||
<a href="/checkout/information" class="btn btn-outline-primary float-right">Checkout</a>
|
<a href="/checkout/information" class="btn btn-primary float-right">Checkout</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,5 +22,8 @@
|
||||||
<li class="list-group-item"><i class="fas fa-cog fa-icon"></i> <a href="/admin/settings">{{ @root.__ "General settings" }}</a></li>
|
<li class="list-group-item"><i class="fas fa-cog fa-icon"></i> <a href="/admin/settings">{{ @root.__ "General settings" }}</a></li>
|
||||||
<li class="list-group-item"><i class="fas fa-bars fa-icon"></i> <a href="/admin/settings/menu">Menu</a></li>
|
<li class="list-group-item"><i class="fas fa-bars fa-icon"></i> <a href="/admin/settings/menu">Menu</a></li>
|
||||||
<li class="list-group-item"><i class="far fa-file fa-icon"></i> <a href="/admin/settings/pages">{{ @root.__ "Static pages" }}</a></li>
|
<li class="list-group-item"><i class="far fa-file fa-icon"></i> <a href="/admin/settings/pages">{{ @root.__ "Static pages" }}</a></li>
|
||||||
|
{{#ifCond session.isAdmin '===' true}}
|
||||||
|
<li class="list-group-item"><i class="fas fa-tag"></i> <a href="/admin/settings/discounts">{{ @root.__ "Discount codes" }}</a></li>
|
||||||
|
{{/ifCond}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
|
@ -56,7 +56,7 @@
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" class="form-control" id="productPermalink" placeholder="Permalink for the article" value={{result.productPermalink}}>
|
<input type="text" class="form-control" id="productPermalink" placeholder="Permalink for the article" value={{result.productPermalink}}>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button class="btn btn-outline-success" id="validate_permalink" type="button">{{ @root.__ "Validate" }}</button>
|
<button class="btn btn-outline-success" id="validatePermalink" type="button">{{ @root.__ "Validate" }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="help-block">{{ @root.__ "This sets a readable URL for the product" }}</p>
|
<p class="help-block">{{ @root.__ "This sets a readable URL for the product" }}</p>
|
||||||
|
|
|
@ -1,139 +1,138 @@
|
||||||
{{> partials/menu}}
|
{{> partials/menu}}
|
||||||
<form class="form-horizontal" id="productNewForm" data-toggle="validator">
|
<form class="form-horizontal" id="productNewForm" data-toggle="validator">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<button id="frm_edit_product_save" class="btn btn-outline-success" type="submit">Add product <i class="far fa-plus-square"></i></button>
|
<button id="frm_edit_product_save" class="btn btn-outline-success" type="submit">Add product <i class="far fa-plus-square"></i></button>
|
||||||
|
</div>
|
||||||
|
<h2>{{ @root.__ "New product" }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="productTitle" class="control-label">{{ @root.__ "Product title" }} *</label>
|
||||||
|
<input type="text" id="productTitle" class="form-control" minlength="5" maxlength="200" value="{{productTitle}}" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="productPrice" class="control-label">{{ @root.__ "Product price" }} *</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<label class="input-group-text">{{currencySymbol config.currencySymbol}}</label>
|
||||||
</div>
|
</div>
|
||||||
<h2>{{ @root.__ "New product" }}</h2>
|
<input type="number" id="productPrice" class="form-control" step="any" value="{{productPrice}}" required/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-10">
|
</div>
|
||||||
<div class="form-group">
|
<div class="col-sm-6">
|
||||||
<label for="productTitle" class="control-label">{{ @root.__ "Product title" }} *</label>
|
<div class="form-group">
|
||||||
<input type="text" id="productTitle" class="form-control" minlength="5" maxlength="200" value="{{productTitle}}" required/>
|
<label for="productPublished" class="control-label">{{ @root.__ "Status" }}</label>
|
||||||
</div>
|
<select class="form-control" id="productPublished">
|
||||||
|
<option value="true" selected>{{ @root.__ "Published" }}</option>
|
||||||
|
<option value="false">{{ @root.__ "Draft" }}</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6">
|
</div>
|
||||||
<div class="form-group">
|
{{#if config.trackStock}}
|
||||||
<label for="productPrice" class="control-label">{{ @root.__ "Product price" }} *</label>
|
<div class="col-sm-6">
|
||||||
<div class="input-group">
|
<div class="form-group">
|
||||||
<div class="input-group-prepend">
|
<label for="productStock" class="control-label">{{ @root.__ "Stock level" }}</label>
|
||||||
<label class="input-group-text">{{currencySymbol config.currencySymbol}}</label>
|
<input type="number" id="productStock" class="form-control" value="{{productStock}}" step="any" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group" id="editor-wrapper">
|
||||||
|
<label for="editor" class="control-label">{{ @root.__ "Product description" }} *</label>
|
||||||
|
<textarea minlength="5" rows="10" id="productDescription" class="form-control" required>{{productDescription}}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label">Permalink</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="productPermalink" placeholder="Permalink for the article" value={{productPermalink}}>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-success" id="validatePermalink" type="button">Validate</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="help-block">{{ @root.__ "This sets a readable URL for the product" }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label">{{ @root.__ "Product options" }}</label>
|
||||||
|
<input type="hidden" id="productOptions" value="{{result.productOptions}}" />
|
||||||
|
<ul class="list-group" id="product_opt_wrapper">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<strong>{{ @root.__ "Name" }}:</strong>
|
||||||
|
<input type="text" id="product_optName" class="form-control" placeholder="Size" />
|
||||||
</div>
|
</div>
|
||||||
<input type="number" id="productPrice" class="form-control" step="any" value="{{productPrice}}" required/>
|
<div class="col-sm-2">
|
||||||
</div>
|
<strong>{{ @root.__ "Label" }}:</strong>
|
||||||
</div>
|
<input type="text" id="product_optLabel" class="form-control" placeholder="Select size"/>
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="productPublished" class="control-label">{{ @root.__ "Status" }}</label>
|
|
||||||
<select class="form-control" id="productPublished">
|
|
||||||
<option value="true" selected>{{ @root.__ "Published" }}</option>
|
|
||||||
<option value="false">{{ @root.__ "Draft" }}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{#if config.trackStock}}
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="productStock" class="control-label">{{ @root.__ "Stock level" }}</label>
|
|
||||||
<input type="number" id="productStock" class="form-control" value="{{productStock}}" step="any" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<div class="form-group" id="editor-wrapper">
|
|
||||||
<label for="editor" class="control-label">{{ @root.__ "Product description" }} *</label>
|
|
||||||
<textarea minlength="5" rows="10" id="productDescription" class="form-control" required>{{productDescription}}</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="control-label">Permalink</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" class="form-control" id="productPermalink" placeholder="Permalink for the article" value={{productPermalink}}>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<button class="btn btn-outline-success" id="validate_permalink" type="button">Validate</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-sm-2">
|
||||||
<p class="help-block">{{ @root.__ "This sets a readable URL for the product" }}</p>
|
<strong>{{ @root.__ "Type" }}:</strong>
|
||||||
</div>
|
<select id="product_optType" class="form-control">
|
||||||
</div>
|
<option value="select">Select</option>
|
||||||
<div class="col-sm-10">
|
<option value="radio">Radio</option>
|
||||||
<div class="form-group">
|
<option value="checkbox">Checkbox</option>
|
||||||
<label class="control-label">{{ @root.__ "Product options" }}</label>
|
</select>
|
||||||
<input type="hidden" id="productOptions" value="{{result.productOptions}}" />
|
</div>
|
||||||
<ul class="list-group" id="product_opt_wrapper">
|
<div class="col-sm-4">
|
||||||
|
<strong>{{ @root.__ "Options" }}:</strong>
|
||||||
|
<input type="text" id="product_optOptions" class="form-control" placeholder="comma, seporated, list"/>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 text-right"></br>
|
||||||
|
<button id="product_opt_add" class="btn btn-outline-success">{{ @root.__ "Add" }}</button>
|
||||||
|
</div></div>
|
||||||
|
</li>
|
||||||
|
{{#each options}}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-2">
|
<div class='col-sm-2 opt-name'>{{this.optName}}</div>
|
||||||
<strong>{{ @root.__ "Name" }}:</strong>
|
<div class='col-sm-2'>{{this.optLabel}}</div>
|
||||||
<input type="text" id="product_optName" class="form-control" placeholder="Size" />
|
<div class='col-sm-2'>{{this.optType}}</div>
|
||||||
</div>
|
<div class='col-sm-4'>{{{this.optOptions}}}</div>
|
||||||
<div class="col-sm-2">
|
<div class='col-sm-2 text-right'>
|
||||||
<strong>{{ @root.__ "Label" }}:</strong>
|
<button class='product_opt_remove btn btn-outline-danger'>{{ @root.__ "Remove" }}</button>
|
||||||
<input type="text" id="product_optLabel" class="form-control" placeholder="Select size"/>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-2">
|
|
||||||
<strong>{{ @root.__ "Type" }}:</strong>
|
|
||||||
<select id="product_optType" class="form-control">
|
|
||||||
<option value="select">Select</option>
|
|
||||||
<option value="radio">Radio</option>
|
|
||||||
<option value="checkbox">Checkbox</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<strong>{{ @root.__ "Options" }}:</strong>
|
|
||||||
<input type="text" id="product_optOptions" class="form-control" placeholder="comma, seporated, list"/>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-2 text-right"></br>
|
|
||||||
<button id="product_opt_add" class="btn btn-outline-success">{{ @root.__ "Add" }}</button>
|
|
||||||
</div></div>
|
|
||||||
</li>
|
|
||||||
{{#each options}}
|
|
||||||
<li class="list-group-item">
|
|
||||||
<div class="row">
|
|
||||||
<div class='col-sm-2 opt-name'>{{this.optName}}</div>
|
|
||||||
<div class='col-sm-2'>{{this.optLabel}}</div>
|
|
||||||
<div class='col-sm-2'>{{this.optType}}</div>
|
|
||||||
<div class='col-sm-4'>{{{this.optOptions}}}</div>
|
|
||||||
<div class='col-sm-2 text-right'>
|
|
||||||
<button class='product_opt_remove btn btn-outline-danger'>{{ @root.__ "Remove" }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
{{/each}}
|
</li>
|
||||||
</ul>
|
{{/each}}
|
||||||
<p class="help-block">{{ @root.__ "Here you can set options for your product. Eg: Size, color, style" }}</p>
|
</ul>
|
||||||
</div>
|
<p class="help-block">{{ @root.__ "Here you can set options for your product. Eg: Size, color, style" }}</p>
|
||||||
</div>
|
</div>
|
||||||
{{#ifCond config.paymentGateway '==' 'stripe'}}
|
</div>
|
||||||
<div class="col-sm-10">
|
{{#ifCond config.paymentGateway '==' 'stripe'}}
|
||||||
<div class="form-group">
|
<div class="col-sm-10">
|
||||||
<label class="control-label">Subscription plan</label>
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" id="productSubscription" placeholder="plan_XXXXXXXXXXXXXX">
|
<label class="control-label">Subscription plan</label>
|
||||||
<p class="help-block">First setup the plan in <strong>Stripe</strong> dashboard and enter the Plan ID. Format: plan_XXXXXXXXXXXXXX</p>
|
<input type="text" class="form-control" id="productSubscription" placeholder="plan_XXXXXXXXXXXXXX">
|
||||||
</div>
|
<p class="help-block">First setup the plan in <strong>Stripe</strong> dashboard and enter the Plan ID. Format: plan_XXXXXXXXXXXXXX</p>
|
||||||
</div>
|
</div>
|
||||||
{{/ifCond}}
|
</div>
|
||||||
<div class="col-sm-10">
|
{{/ifCond}}
|
||||||
<div class="form-group">
|
<div class="col-sm-10">
|
||||||
<label for="productComment" class="control-label">{{ @root.__ "Allow comment" }}</label>
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<label for="productComment" class="control-label">{{ @root.__ "Allow comment" }}</label>
|
||||||
<label>
|
<div class="checkbox">
|
||||||
<input class="productComment" type="checkbox" {{checkedState result.productComment}} id="productComment">
|
<label>
|
||||||
</label>
|
<input class="productComment" type="checkbox" {{checkedState result.productComment}} id="productComment">
|
||||||
</div>
|
</label>
|
||||||
<p class="help-block">{{ @root.__ "Allow free form comments when adding products to cart" }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="help-block">{{ @root.__ "Allow free form comments when adding products to cart" }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-10">
|
</div>
|
||||||
<div class="form-group">
|
<div class="col-sm-10">
|
||||||
<label for="productTags" class="control-label">Product tag words</label>
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" id="productTags">
|
<label for="productTags" class="control-label">Product tag words</label>
|
||||||
<p class="help-block">{{ @root.__ "Tag words used to indexed products, making them easier to find and filter." }}</p>
|
<input type="text" class="form-control" id="productTags">
|
||||||
</div>
|
<p class="help-block">{{ @root.__ "Tag words used to indexed products, making them easier to find and filter." }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
{{#each results}}
|
{{#each results}}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<button class="float-right btn text-danger btn-delete-product" data-id="{{this._id}}"> <i class="far fa-trash-alt"></i></button>
|
<button class="float-right btn text-danger btn-delete-product" data-id="{{this._id}}"> <i class="far fa-trash-alt"></i></button>
|
||||||
<h4 class="float-right"><input id="{{this._id}}" class="published_state" type="checkbox" {{checkedState this.productPublished}}></h4>
|
<h4 class="float-right"><input id="{{this._id}}" class="publishedState" type="checkbox" {{checkedState this.productPublished}}></h4>
|
||||||
<div class="top-pad-8"><a href="/admin/product/edit/{{this._id}}">{{this.productTitle}}</a></div>
|
<div class="top-pad-8"><a href="/admin/product/edit/{{this._id}}">{{this.productTitle}}</a></div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
{{> partials/menu}}
|
||||||
|
<form class="form-horizontal col-sm-7" id="discountEditForm" data-toggle="validator">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="float-right">
|
||||||
|
<button id="" class="btn btn-outline-success" type="submit">Update discount</button>
|
||||||
|
</div>
|
||||||
|
<h2>{{ @root.__ "Edit discount" }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountCode" class="control-label">{{ @root.__ "Discount code" }} *</label>
|
||||||
|
<input type="text" id="discountCode" class="form-control" minlength="1" maxlength="50" value="{{discount.code}}" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountType" class="control-label">{{ @root.__ "Discount type" }} *</label>
|
||||||
|
<select class="form-control" id="discountType">
|
||||||
|
<option value="amount" {{selectState discount.type "amount"}}>{{ @root.__ "Amount" }}</option>
|
||||||
|
<option value="percent" {{selectState discount.type "percent"}}>{{ @root.__ "Percent" }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountValue" class="control-label">{{ @root.__ "Discount value" }} *</label>
|
||||||
|
<input type="number" id="discountValue" class="form-control" minlength="1" maxlength="50" value="{{discount.value}}" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountStart" class="control-label">{{ @root.__ "Discount start" }} *</label>
|
||||||
|
<input id="discountStart" value="{{formatDate discount.start 'DD/MM/YYYY kk:mm'}}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountEnd" class="control-label">{{ @root.__ "Discount end" }} *</label>
|
||||||
|
<input id="discountEnd" value="{{formatDate discount.end 'DD/MM/YYYY kk:mm'}}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="discountId" value="{{discount._id}}">
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -0,0 +1,46 @@
|
||||||
|
{{> partials/menu}}
|
||||||
|
<form class="form-horizontal col-sm-7" id="discountNewForm" data-toggle="validator">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="float-right">
|
||||||
|
<button id="" class="btn btn-outline-success" type="submit">Add discount</button>
|
||||||
|
</div>
|
||||||
|
<h2>{{ @root.__ "New discount" }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountCode" class="control-label">{{ @root.__ "Discount code" }} *</label>
|
||||||
|
<input type="text" id="discountCode" class="form-control" minlength="1" maxlength="50" placeholder="CODE20" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountType" class="control-label">{{ @root.__ "Discount type" }} *</label>
|
||||||
|
<select class="form-control" id="discountType">
|
||||||
|
<option value="amount" selected>{{ @root.__ "Amount" }}</option>
|
||||||
|
<option value="percent">{{ @root.__ "Percent" }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountValue" class="control-label">{{ @root.__ "Discount value" }} *</label>
|
||||||
|
<input type="number" id="discountValue" class="form-control" minlength="1" maxlength="50" placeholder="20" required/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountStart" class="control-label">{{ @root.__ "Discount start" }} *</label>
|
||||||
|
<input id="discountStart" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="discountEnd" class="control-label">{{ @root.__ "Discount end" }} *</label>
|
||||||
|
<input id="discountEnd" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -0,0 +1,39 @@
|
||||||
|
{{> partials/menu}}
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<h2 class="clearfix">{{ @root.__ "Discount codes" }} <div class="float-right"><a href="/admin/settings/discount/new" class="btn btn-outline-success">{{ @root.__ "New Discount" }}</a></div></h2>
|
||||||
|
{{#if discounts}}
|
||||||
|
<ul class="list-group">
|
||||||
|
{{#each discounts}}
|
||||||
|
<li class="list-group-item">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 mt-2">
|
||||||
|
<span><strong>{{ @root.__ "Code" }}:</strong> {{this.code}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 mt-2">
|
||||||
|
<span><strong>{{ @root.__ "Type" }}:</strong> {{this.type}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 mt-2">
|
||||||
|
<span><strong>{{ @root.__ "Status" }}:</strong>
|
||||||
|
{{#ifCond (discountExpiry this.start this.end) '===' true}}
|
||||||
|
<span class="text-success">{{ @root.__ "Running" }}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="text-danger">{{ @root.__ "Not running" }}</span>
|
||||||
|
{{/ifCond}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-3 text-right">
|
||||||
|
<a class="btn btn-outline-success" href="/admin/settings/discount/edit/{{this._id}}">{{ @root.__ "Edit" }}</a>
|
||||||
|
<button class="btn btn-outline-danger" id="btnDiscountDelete" data-id="{{this._id}}">{{ @root.__ "Delete" }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<h4 class="text-warning text-center">{{ @root.__ "There are currently no discount codes setup." }}</h4>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -29,16 +29,16 @@
|
||||||
<div class="col-12 col-md-6 no-pad-left mb-2">
|
<div class="col-12 col-md-6 no-pad-left mb-2">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<button class="btn btn-outline-primary btn-qty-minus" type="button">-</button>
|
<button class="btn btn-primary btn-qty-minus" type="button">-</button>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" class="form-control cart-product-quantity text-center" id="{{../this.productId}}-qty" data-id="{{../this.productId}}" maxlength="2" value="{{../this.quantity}}">
|
<input type="number" class="form-control cart-product-quantity text-center" id="{{../this.productId}}-qty" data-id="{{../this.productId}}" maxlength="2" value="{{../this.quantity}}">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button class="btn btn-outline-primary btn-qty-add" type="button">+</button>
|
<button class="btn btn-primary btn-qty-add" type="button">+</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4 col-md-2 no-pad-left">
|
<div class="col-4 col-md-2 no-pad-left">
|
||||||
<button class="btn btn-outline-danger btn-delete-from-cart" data-id="{{../this.productId}}" type="button"><i class="far fa-trash-alt" data-id="{{../this.productId}}" aria-hidden="true"></i></button>
|
<button class="btn btn-danger btn-delete-from-cart" data-id="{{../this.productId}}" type="button"><i class="far fa-trash-alt" data-id="{{../this.productId}}" aria-hidden="true"></i></button>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="col-12 col-md-8 no-pad-left mb-2"></div>
|
<div class="col-12 col-md-8 no-pad-left mb-2"></div>
|
||||||
|
@ -66,6 +66,11 @@
|
||||||
<span id="shipping-amount">{{@root.session.shippingMessage}}</span>
|
<span id="shipping-amount">{{@root.session.shippingMessage}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{/ifCond}}
|
{{/ifCond}}
|
||||||
|
{{#ifCond @root.session.totalCartDiscount '>' 0}}
|
||||||
|
<div class="text-right">
|
||||||
|
Discount: <strong id="discount-amount">{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartDiscount}}</strong>
|
||||||
|
</div>
|
||||||
|
{{/ifCond}}
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
Total:
|
Total:
|
||||||
<strong id="total-cart-amount">{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartAmount}}</strong>
|
<strong id="total-cart-amount">{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartAmount}}</strong>
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
{{> (getTheme 'cart')}}
|
{{> (getTheme 'cart')}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 {{showCartButtons @root.session.cart}} cart-buttons">
|
<div class="col-sm-12 {{showCartButtons @root.session.cart}} cart-buttons">
|
||||||
<button class="btn btn-outline-danger float-left" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
|
<button class="btn btn-danger float-left" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
|
||||||
<a href="/checkout/information" class="btn btn-outline-danger float-right">Checkout</a>
|
<a href="/checkout/information" class="btn btn-danger float-right">Checkout</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,10 +36,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<a href="/customer/forgotten"
|
<a href="/customer/forgotten"
|
||||||
class="btn btn-outline-primary float-left">{{ @root.__ "Forgotten" }}</a>
|
class="btn btn-primary float-left">{{ @root.__ "Forgotten" }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button id="customerLogin" class="btn btn-outline-primary float-right"
|
<button id="customerLogin" class="btn btn-primary float-right"
|
||||||
type="submit">Login</button>
|
type="submit">Login</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
<div class="row bottom-marg-15">
|
<div class="row bottom-marg-15">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button id="customerLogout"
|
<button id="customerLogout"
|
||||||
class="btn btn-outline-primary float-right">{{ @root.__ "Change customer" }}</button>
|
class="btn btn-primary float-right">{{ @root.__ "Change customer" }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -76,8 +76,8 @@
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<a href="/checkout/cart" class="btn btn-outline-primary float-left">Return to cart</a>
|
<a href="/checkout/cart" class="btn btn-primary float-left">Return to cart</a>
|
||||||
<a href="/checkout/shipping" id="checkoutInformation" class="btn btn-outline-primary float-right">Continue to shipping</a>
|
<a href="/checkout/shipping" id="checkoutInformation" class="btn btn-primary float-right">Continue to shipping</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
{{#if @root.session.cart}}
|
{{#if @root.session.cart}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 {{showCartButtons @root.session.cart}} cart-buttons">
|
<div class="col-sm-12 {{showCartButtons @root.session.cart}} cart-buttons">
|
||||||
<button class="btn btn-outline-danger float-right" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
|
<button class="btn btn-danger float-right" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -39,6 +39,17 @@
|
||||||
<li class="list-group-item">FREE shipping <span class="float-right"><a href="/checkout/shipping">Change</a></span></li>
|
<li class="list-group-item">FREE shipping <span class="float-right"><a href="/checkout/shipping">Change</a></span></li>
|
||||||
{{/ifCond}}
|
{{/ifCond}}
|
||||||
</ul>
|
</ul>
|
||||||
|
{{#if @root.config.modules.loaded.discount}}
|
||||||
|
<div class="input-group bottom-pad-15">
|
||||||
|
<input class="form-control" id="discountCode" type="search" placeholder="{{ @root.__ "Discount code" }}" value="{{@root.session.discountCode}}">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-success" id="addDiscountCode">{{ @root.__ "Apply" }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-outline-danger" id="removeDiscountCode"><i class="fa fa-times" aria-hidden="true"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action{{@root.paymentType}}" method="post" role="form" data-toggle="validator" novalidate="false">
|
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action{{@root.paymentType}}" method="post" role="form" data-toggle="validator" novalidate="false">
|
||||||
{{#if session.customerPresent}}
|
{{#if session.customerPresent}}
|
||||||
{{#ifCond config.paymentGateway '==' 'paypal'}}
|
{{#ifCond config.paymentGateway '==' 'paypal'}}
|
||||||
|
|
|
@ -23,14 +23,14 @@
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/checkout/information" class="btn btn-outline-primary float-left">{{ @root.__ "Return to information" }}</a>
|
<a href="/checkout/information" class="btn btn-primary float-left">{{ @root.__ "Return to information" }}</a>
|
||||||
<a href="/checkout/payment" class="btn btn-outline-primary float-right">{{ @root.__ "Proceed to payment" }}</a>
|
<a href="/checkout/payment" class="btn btn-primary float-right">{{ @root.__ "Proceed to payment" }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="cart" class="col-md-7 d-none d-sm-block">
|
<div id="cart" class="col-md-7 d-none d-sm-block">
|
||||||
{{> (getTheme 'cart')}}
|
{{> (getTheme 'cart')}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 {{showCartButtons @root.session.cart}} cart-buttons">
|
<div class="col-sm-12 {{showCartButtons @root.session.cart}} cart-buttons">
|
||||||
<button class="btn btn-outline-danger float-right" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
|
<button class="btn btn-danger float-right" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
{{currencySymbol ../config.currencySymbol}}{{formatAmount productPrice}}
|
{{currencySymbol ../config.currencySymbol}}{{formatAmount productPrice}}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
<a class="btn btn-outline-primary add-to-cart" data-id="{{this._id}}" data-link="{{this.productPermalink}}" data-has-options="{{checkProductOptions this.productOptions}}" role="button">{{ @root.__ "Add to cart" }}</a>
|
<a class="btn btn-primary add-to-cart" data-id="{{this._id}}" data-link="{{this.productPermalink}}" data-has-options="{{checkProductOptions this.productOptions}}" role="button">{{ @root.__ "Add to cart" }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -74,11 +74,11 @@
|
||||||
<p class="product-option-text">{{ @root.__ "Quantity" }}</p>
|
<p class="product-option-text">{{ @root.__ "Quantity" }}</p>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<button class="btn btn-outline-primary qty-btn-minus" type="button">-</button>
|
<button class="btn btn-primary qty-btn-minus" type="button">-</button>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" class="form-control add-color text-center" id="product_quantity" maxlength="3" value="1">
|
<input type="number" class="form-control add-color text-center" id="product_quantity" maxlength="3" value="1">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button class="btn btn-outline-primary qty-btn-plus" type="button">+</button>
|
<button class="btn btn-primary qty-btn-plus" type="button">+</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="col-md-10 btnAddToCart">
|
<div class="col-md-10 btnAddToCart">
|
||||||
<button class="btn btn-outline-primary btn-block product-add-to-cart" type="button">{{ @root.__ "Add to cart" }}</button>
|
<button class="btn btn-primary btn-block product-add-to-cart" type="button">{{ @root.__ "Add to cart" }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-10 body_text">
|
<div class="col-md-10 body_text">
|
||||||
{{{productDescription}}}
|
{{{productDescription}}}
|
||||||
|
|
Loading…
Reference in New Issue