Răsfoiți Sursa

Merge branch 'development'

Lee Morgan 5 ani în urmă
părinte
comite
d27af7f963
56 a modificat fișierele cu 1183 adăugiri și 1948 ștergeri
  1. 29 11
      app.js
  2. 3 4
      controllers/emailVerification.js
  3. 32 68
      controllers/ingredientData.js
  4. 15 160
      controllers/merchantData.js
  5. 24 63
      controllers/orderData.js
  6. 3 106
      controllers/otherData.js
  7. 3 3
      controllers/passwordReset.js
  8. 20 227
      controllers/recipeData.js
  9. 27 72
      controllers/renderer.js
  10. 48 138
      controllers/transactionData.js
  11. 60 0
      middleware.js
  12. 0 16
      models/activity.js
  13. 2 1
      models/archivedRecipe.js
  14. 2 1
      models/inventoryAdjustment.js
  15. 9 1
      models/merchant.js
  16. 4 1
      models/order.js
  17. 4 1
      models/transaction.js
  18. 7 20
      package-lock.json
  19. 1 1
      package.json
  20. 35 41
      routes.js
  21. 0 0
      views/dashboardPage/bundle.js
  22. 5 2
      views/dashboardPage/dashboard.css
  23. 1 0
      views/dashboardPage/dashboard.ejs
  24. 13 15
      views/dashboardPage/ejs/sidebars/orderCalculator.ejs
  25. 1 1
      views/dashboardPage/ejs/strands/orders.ejs
  26. 12 63
      views/dashboardPage/js/classes/Merchant.js
  27. 0 11
      views/dashboardPage/js/classes/Order.js
  28. 0 42
      views/dashboardPage/js/classes/Recipe.js
  29. 0 12
      views/dashboardPage/js/classes/Transaction.js
  30. 2 8
      views/dashboardPage/js/dashboard.js
  31. 2 2
      views/dashboardPage/js/sidebars/editIngredient.js
  32. 118 39
      views/dashboardPage/js/sidebars/orderCalculator.js
  33. 4 2
      views/dashboardPage/js/strands/analytics.js
  34. 6 4
      views/dashboardPage/js/strands/home.js
  35. 43 35
      views/dashboardPage/js/strands/orders.js
  36. 8 33
      views/dashboardPage/sidebars.css
  37. 14 6
      views/informationPages/help.ejs
  38. 14 6
      views/informationPages/privacyPolicy.ejs
  39. 14 6
      views/informationPages/terms.ejs
  40. 0 53
      views/landingPage/controller.js
  41. 0 256
      views/landingPage/landing.css
  42. 0 170
      views/landingPage/landing.ejs
  43. 0 14
      views/landingPage/login.js
  44. 0 6
      views/landingPage/public.js
  45. 0 48
      views/landingPage/register.js
  46. 79 0
      views/otherPages/landing.ejs
  47. 62 0
      views/otherPages/login.ejs
  48. 77 0
      views/otherPages/register.ejs
  49. 229 0
      views/otherPages/style.css
  50. 14 7
      views/passwordResetPages/email.ejs
  51. 28 4
      views/passwordResetPages/password.ejs
  52. 30 34
      views/shared/banner.ejs
  53. 0 18
      views/shared/header.ejs
  54. 56 98
      views/shared/shared.css
  55. 23 5
      views/verifyPage/verify.ejs
  56. 0 13
      workspace.html

+ 29 - 11
app.js

@@ -2,33 +2,51 @@ const express = require("express");
 const session = require("cookie-session");
 const mongoose = require("mongoose");
 const compression = require("compression");
+const https = require("https");
+const fs = require("fs");
 
 const app = express();
 
-mongoose.connect(process.env.SUBLINE_DB, {useNewUrlParser: true, useUnifiedTopology: true});
+mongoose.connect(`${process.env.DB}/inventory-management`, {
+    useNewUrlParser: true,
+    useUnifiedTopology: true,
+    useFindAndModify: false,
+    useCreateIndex: true
+});
 
 app.set("view engine", "ejs");
-app.set("subdomain offset", 1);
 
-function requireHTTPS(req, res, next) {
-    if (!req.secure && req.get('x-forwarded-proto') !== 'https' && process.env.NODE_ENV !== "development") {
-      return res.redirect('https://' + req.get('host') + req.url);
-    }
-    next();
+app.use(express.static(__dirname + "/views"));
+let httpsServer = {};
+if(process.env.NODE_ENV === "production"){
+    httpsServer = https.createServer({
+        key: fs.readFileSync("/etc/letsencrypt/live/www.thesubline.com/privkey.pem", "utf8"),
+        cert: fs.readFileSync("/etc/letsencrypt/live/www.thesubline.com/fullchain.pem", "utf8")
+    }, app);
+
+    app.use((req, res, next)=>{
+        if(req.secure === true){
+            next();
+        }else{
+            res.redirect(`https://${req.headers.host}${req.url}`);
+        }
+    });
 }
 
-app.use(requireHTTPS);
 app.use(compression());
 app.use(session({
-    secret: "Super Secret Subline Subliminally Saving Secrets So Sneaky Snakes Stay Sullen",
-    cookie: {secure: false},
+    secret: "Super Secret Subline Subliminally Saving Secrets So Sneaky Snakes Stay Sullen. Simply Superb.",
+    cookie: {secure: true},
     saveUninitialized: true,
     resave: false
 }));
-app.use(express.static(__dirname + "/views"));
 app.use(express.urlencoded({extended: true}));
 app.use(express.json());
 
 require("./routes")(app);
 
+if(process.env.NODE_ENV === "production"){
+    httpsServer.listen(process.env.HTTPS_PORT, ()=>{});
+}
+
 app.listen(process.env.PORT, ()=>{});

+ 3 - 4
controllers/emailVerification.js

@@ -2,7 +2,6 @@ const Merchant = require("../models/merchant.js");
 
 const mailgun = require("mailgun-js")({apiKey: process.env.MG_SUBLINE_APIKEY, domain: "mail.thesubline.net"});
 const verifyEmail = require("../emails/verifyEmail.js");
-const { db } = require("../models/merchant.js");
 
 module.exports = {
     sendVerifyEmail: function(req, res){
@@ -20,7 +19,7 @@ module.exports = {
                 mailgun.messages().send(mailgunData, (err, body)=>{});
 
 
-                return res.render(`verifyPage/verify`, {id: merchant._id, email: merchant.email});
+                return res.render(`verifyPage/verify`, {id: merchant._id, email: merchant.email, banner: res.locals.merchant});
             })
             .catch((err)=>{
                 req.session.error = "ERROR: UNABLE TO SEND VERIFICATION EMAIL";
@@ -79,9 +78,9 @@ module.exports = {
                 return merchant.save();
             })
             .then((merchant)=>{
-                req.session.user = merchant._id;
+                req.session.success = "EMAIL VERIFIED.  PLEASE LOG IN";
 
-                return res.redirect("/dashboard");
+                return res.redirect("/login");
             })
             .catch((err)=>{
                 if(typeof(err) === "string"){

+ 32 - 68
controllers/ingredientData.js

@@ -1,4 +1,3 @@
-const Merchant = require("../models/merchant");
 const Ingredient = require("../models/ingredient");
 const InventoryAdjustment = require("../models/inventoryAdjustment.js");
 
@@ -23,33 +22,26 @@ module.exports = {
         Same as above, with the _id
     */
     createIngredient: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         let newIngredient = {...req.body};
         if(req.body.defaultUnit === "bottle"){
             newIngredient.ingredient.unitSize = helper.convertQuantityToBaseUnit(newIngredient.ingredient.unitSize, newIngredient.ingredient.unitType);
         }
 
+        
         newIngredient = new Ingredient(newIngredient.ingredient);
-
-        let ingredientPromise = newIngredient.save();
-        let merchantPromise = Merchant.findOne({_id: req.session.user});
-
-        Promise.all([ingredientPromise, merchantPromise])
-            .then((response)=>{
+        
+        newIngredient.save()
+            .then((ingredient)=>{
                 newIngredient = {
-                    ingredient: response[0],
+                    ingredient: ingredient,
                     defaultUnit: req.body.defaultUnit
                 }
 
                 newIngredient.quantity = helper.convertQuantityToBaseUnit(req.body.quantity, req.body.defaultUnit);
 
-                response[1].inventory.push(newIngredient);
+                res.locals.merchant.inventory.push(newIngredient);
 
-                return response[1].save();
+                return res.locals.merchant.save();
             })
             .then((response)=>{
                 return res.json(newIngredient);
@@ -76,11 +68,6 @@ module.exports = {
     }
     */
     updateIngredient: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-        let updatedIngredient = {};
         Ingredient.findOne({_id: req.body.id})
             .then((ingredient)=>{
                 ingredient.name = req.body.name,
@@ -89,35 +76,33 @@ module.exports = {
                 return ingredient.save();
             })
             .then((ingredient)=>{
-                updatedIngredient.ingredient = ingredient;
-                return Merchant.findOne({_id: req.session.user});
-            })
-            .then((merchant)=>{
-                for(let i = 0; i < merchant.inventory.length; i++){
-                    if(merchant.inventory[i].ingredient.toString() === req.body.id){
-                        merchant.inventory[i].defaultUnit = req.body.unit;
+                let updatedIngredient = {};
+                for(let i = 0; i < res.locals.merchant.inventory.length; i++){
+                    if(res.locals.merchant.inventory[i].ingredient.toString() === req.body.id){
+                        res.locals.merchant.inventory[i].defaultUnit = req.body.unit;
 
-                        if(merchant.inventory[i].quantity !== req.body.quantity){
+                        if(res.locals.merchant.inventory[i].quantity !== req.body.quantity){
                             new InventoryAdjustment({
                                 date: new Date(),
                                 merchant: req.session.user,
                                 ingredient: req.body.id,
-                                quantity: req.body.quantity - merchant.inventory[i].quantity
+                                quantity: req.body.quantity - res.locals.merchant.inventory[i].quantity
                             }).save().catch(()=>{});
 
-                            merchant.inventory[i].quantity = req.body.quantity;
+                            res.locals.merchant.inventory[i].quantity = req.body.quantity;
                         }
 
-                        updatedIngredient.quantity = helper.convertQuantityToBaseUnit(req.body.quantity, req.body.unit);
-                        updatedIngredient.unit = req.body.unit;
+                        updatedIngredient = {
+                            ingredient: ingredient,
+                            quantity: helper.convertQuantityToBaseUnit(req.body.quantity, req.body.unit),
+                            unit: req.body.unit
+                        }
                         
                         break;
                     }
                 }
 
-                return merchant.save();
-            })
-            .then((merchant)=>{
+                res.locals.merchant.save().catch((err)=>{throw err});
                 return res.json(updatedIngredient);
             })
             .catch((err)=>{
@@ -127,16 +112,11 @@ module.exports = {
                 if(err.name === "ValidationError"){
                     return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
                 }
-                return res.json("ERROR: UNABLE TO UDATE THE INGREDIENT");
+                return res.json("ERROR: UNABLE TO UPDATE THE INGREDIENT");
             });
     },
 
     createFromSpreadsheet: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         //read file, get the correct sheet, create array from sheet
         let workbook = xlsx.readFile(req.file.path);
         fs.unlink(req.file.path, ()=>{});
@@ -191,15 +171,12 @@ module.exports = {
             ingredients.push(ingredient);
         }
 
-        //Update the database
-        Merchant.findOne({_id: req.session.user})
-            .then((merchant)=>{
-                for(let i = 0; i < merchantData.length; i++){
-                    merchant.inventory.push(merchantData[i]);
-                }
+        for(let i = 0; i < merchantData.length; i++){
+            res.locals.merchant.inventory.push(merchantData[i]);
+        }
 
-                return Promise.all([Ingredient.create(ingredients), merchant.save()]);
-            })
+        //Update the database
+        Promise.all([Ingredient.create(ingredients), res.locals.merchant.save()])
             .then((response)=>{
                 return res.json(merchantData);
             })
@@ -215,11 +192,6 @@ module.exports = {
     },
 
     spreadsheetTemplate: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         let workbook = xlsx.utils.book_new();
         workbook.SheetNames.push("Ingredients");
         let workbookData = [];
@@ -238,22 +210,14 @@ module.exports = {
 
     //DELETE - Removes an ingredient from the merchant's inventory
     removeIngredient: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
+        for(let i = 0; i < res.locals.merchant.inventory.length; i++){
+            if(req.params.id === res.locals.merchant.inventory[i].ingredient._id.toString()){
+                res.locals.merchant.inventory.splice(i, 1);
+                break;
+            }
         }
 
-        Merchant.findOne({_id: req.session.user})
-            .then((merchant)=>{
-                for(let i = 0; i < merchant.inventory.length; i++){
-                    if(req.params.id === merchant.inventory[i].ingredient._id.toString()){
-                        merchant.inventory.splice(i, 1);
-                        break;
-                    }
-                }
-
-                return Promise.all([merchant.save(), Ingredient.deleteOne({_id: req.params.id})]);
-            })
+        Promise.all([res.locals.merchant.save(), Ingredient.deleteOne({_id: req.params.id})])
             .then((response)=>{
                 return res.json({});
             })

+ 15 - 160
controllers/merchantData.js

@@ -1,10 +1,8 @@
 const Merchant = require("../models/merchant");
-const Recipe = require("../models/recipe");
 const InventoryAdjustment = require("../models/inventoryAdjustment");
 
 const helper = require("./helper.js");
 
-const axios = require("axios");
 const bcrypt = require("bcryptjs");
 
 module.exports = {
@@ -21,23 +19,26 @@ module.exports = {
     createMerchantNone: async function(req, res){
         if(req.body.password.length < 10){
             req.session.error = "PASSWORD MUST CONTAIN AT LEAST 10 CHARACTERS";
-            return res.redirect("/");
+            return res.redirect("/register");
         }
 
         if(req.body.password !== req.body.confirmPassword){
             req.session.error = "PASSWORDS DO NOT MATCH";
-            return res.redirect("/");
+            return res.redirect("/register");
         }
 
         const merchantFind = await Merchant.findOne({email: req.body.email.toLowerCase()});
         if(merchantFind !== null){
             req.session.error = "USER WITH THIS EMAIL ADDRESS ALREADY EXISTS";
-            return res.redirect("/");
+            return res.redirect("/register");
         }
 
         let salt = bcrypt.genSaltSync(10);
         let hash = bcrypt.hashSync(req.body.password, salt);
 
+        let expirationDate = new Date();
+        expirationDate.setDate(expirationDate.getDate() + 90);
+
         let merchant = new Merchant({
             name: req.body.name,
             email: req.body.email.toLowerCase(),
@@ -48,7 +49,11 @@ module.exports = {
             status: ["unverified"],
             inventory: [],
             recipes: [],
-            verifyId: helper.generateId(15)
+            verifyId: helper.generateId(15),
+            session: {
+                sessionId: helper.generateId(25),
+                expiration: expirationDate
+            }
         });
 
         merchant.save()
@@ -68,152 +73,6 @@ module.exports = {
             });
     },
 
-    /*
-    POST - Creates new Clover merchant
-    Redirects to /dashboard
-    */
-    createMerchantClover: async function(req, res){
-        let merchant = {}
-        axios.get(`${process.env.CLOVER_ADDRESS}/v3/merchants/${req.session.merchantId}?access_token=${req.session.accessToken}`)
-            .then((response)=>{
-                merchant = new Merchant({
-                    name: response.data.name,
-                    pos: "clover",
-                    posId: req.session.merchantId,
-                    posAccessToken: req.session.accessToken,
-                    lastUpdatedTime: Date.now(),
-                    createdAt: Date.now(),
-                    inventory: [],
-                    recipes: []
-                });
-
-                return axios.get(`${process.env.CLOVER_ADDRESS}/v3/merchants/${req.session.merchantId}/items?access_token=${req.session.accessToken}`);
-            })
-            .then((response)=>{
-                let recipes = [];
-                for(let i = 0; i < response.data.elements.length; i++){
-                    let recipe = new Recipe({
-                        posId: response.data.elements[i].id,
-                        merchant: merchant,
-                        name: response.data.elements[i].name,
-                        price: response.data.elements[i].price,
-                        ingredients: []
-                    });
-
-                    recipes.push(recipe);
-                    merchant.recipes.push(recipe);                                
-                }
-
-                Recipe.create(recipes).catch((err)=>{});
-
-                return merchant.save();
-            })
-            .then((newMerchant)=>{
-                req.session.accessToken = undefined;
-                req.session.user = newMerchant._id;
-
-                return res.redirect("/dashboard");
-            })
-            .catch((err)=>{
-                if(typeof(err) === "string"){
-                    req.session.error = err;
-                }else if(err.name === "ValidationError"){
-                    req.session.error = err.errors[Object.keys(err.errors)[0]].properties.message;
-                }else{
-                    req.session.error = "ERROR: UNABLE TO RETRIEVE DATA FROM CLOVER";
-                }
-                
-                return res.redirect("/");
-            });
-    },
-
-    createMerchantSquare: function(req, res){
-        let merchant = {}
-
-        axios.get(`${process.env.SQUARE_ADDRESS}/v2/merchants/${req.session.merchantId}`, {
-            headers: {
-                Authorization: `Bearer ${req.session.accessToken}`
-            }
-        })
-            .then((response)=>{
-                req.session.merchantId = undefined;
-
-                return new Merchant({
-                    name: response.data.merchant.business_name,
-                    pos: "square",
-                    posId: response.data.merchant.id,
-                    posAccessToken: req.session.accessToken,
-                    lastUpdatedTime: new Date(),
-                    createdAt: new Date(),
-                    squareLocation: response.data.merchant.main_location_id,
-                    inventory: [],
-                    recipes: []
-                });
-            })
-            .then((newMerchant)=>{
-                req.session.accessToken = undefined;
-                merchant = newMerchant;
-                
-                return axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search`, {
-                    object_types: ["ITEM"]
-                }, {
-                    headers: {
-                        Authorization: `Bearer ${merchant.posAccessToken}`
-                    }
-                });
-            })
-            .then((response)=>{
-                let recipes = [];
-                
-                for(let i = 0; i < response.data.objects.length; i++){
-                    if(response.data.objects[i].item_data.variations.length > 1){
-                        for(let j = 0; j < response.data.objects[i].item_data.variations.length; j++){
-                            let recipe = new Recipe({
-                                posId: response.data.objects[i].item_data.variations[j].id,
-                                merchant: merchant._id,
-                                name: `${response.data.objects[i].item_data.name} '${response.data.objects[i].item_data.variations[j].item_variation_data.name}'`,
-                                price: response.data.objects[i].item_data.variations[j].item_variation_data.price_money.amount
-                            });
-
-                            recipes.push(recipe);
-                            merchant.recipes.push(recipe);
-                        }
-                    }else{
-                        let recipe = new Recipe({
-                            posId: response.data.objects[i].item_data.variations[0].id,
-                            merchant: merchant._id,
-                            name: response.data.objects[i].item_data.name,
-                            price: response.data.objects[i].item_data.variations[0].item_variation_data.price_money.amount,
-                            ingredients: []
-                        });
-
-                        recipes.push(recipe);
-                        merchant.recipes.push(recipe);
-                    }
-                }
-
-                return Recipe.create(recipes);
-            })
-            .then((recipes)=>{
-                return merchant.save();
-            })
-            .then((merchant)=>{
-                req.session.user = merchant._id;
-
-                return res.redirect("/dashboard");
-            })
-            .catch((err)=>{
-                if(typeof(err) === "string"){
-                    req.session.error = err;
-                }else if(err.name === "ValidationError"){
-                    req.session.error = err.errors[Object.keys(err.errors)[0]].properties.message;
-                }else{
-                    req.session.error = "ERROR: UNABLE TO CREATE NEW USER";
-                }
-                return res.redirect("/");
-            });
-    },
-
     /*
     POST - Update the quantity for a merchant inventory item
     req.body = [{
@@ -221,16 +80,12 @@ module.exports = {
         quantity: change in quantity
     }]
     */
-    updateMerchantIngredient: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
+    updateIngredientQuantities: function(req, res){
         let adjustments = [];
         let changedIngredients = [];
-        Merchant.findOne({_id: req.session.user})
+        res.locals.merchant
             .populate("inventory.ingredient")
+            .execPopulate()
             .then((merchant)=>{
                 for(let i = 0; i < req.body.length; i++){
                     let updateIngredient;
@@ -303,7 +158,7 @@ module.exports = {
             })
             .then((merchant)=>{
                 req.session.success = "PASSWORD SUCCESSFULLY RESET. PLEASE LOG IN";
-                return res.redirect("/");
+                return res.redirect("/login");
             })
             .catch((err)=>{
                 if(typeof(err) === "string"){

+ 24 - 63
controllers/orderData.js

@@ -1,5 +1,4 @@
 const Order = require("../models/order.js");
-const Merchant = require("../models/merchant.js");
 
 const helper = require("./helper.js");
 
@@ -19,17 +18,13 @@ module.exports = {
     }
     */
     getOrders: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
         let from = new Date(req.body.from);
         let to = new Date(req.body.to);
 
         let match = {};
         let objectifiedIngredients = [];
         if(req.body.ingredients.length === 0){
-            match = {$exists: true};
+            match = {$ne: false};
         }else{  
             for(let i = 0; i < req.body.ingredients.length; i++){
                 objectifiedIngredients.push(new ObjectId(req.body.ingredients[i]));
@@ -46,7 +41,7 @@ module.exports = {
 
         Order.aggregate([
             {$match:{
-                merchant: new ObjectId(req.session.user),
+                merchant: new ObjectId(res.locals.merchant._id),
                 date: {
                     $gte: from,
                     $lt: to
@@ -76,13 +71,8 @@ module.exports = {
     } 
     */ 
     createOrder: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         let newOrder = new Order(req.body);
-        newOrder.merchant = req.session.user;
+        newOrder.merchant = res.locals.merchant._id;
         newOrder.save()
             .then((response)=>{
                 res.json(response);
@@ -97,30 +87,19 @@ module.exports = {
                 return res.json("ERROR: UNABLE TO SAVE ORDER");
             });
 
-        Merchant.findOne({_id: req.session.user})
-            .then((merchant)=>{
-                for(let i = 0; i < req.body.ingredients.length; i++){
-                    for(let j = 0; j < merchant.inventory.length; j++){
-                        if(req.body.ingredients[i].ingredient === merchant.inventory[j].ingredient.toString()){
-                            merchant.inventory[j].quantity += parseFloat(req.body.ingredients[i].quantity);
-                        }
+        
+            for(let i = 0; i < req.body.ingredients.length; i++){
+                for(let j = 0; j < res.locals.merchant.inventory.length; j++){
+                    if(req.body.ingredients[i].ingredient === res.locals.merchant.inventory[j].ingredient.toString()){
+                        res.locals.merchant.inventory[j].quantity += parseFloat(req.body.ingredients[i].quantity);
                     }
                 }
+            }
 
-                return merchant.save();
-            })
-            .then((merchant)=>{
-                return;
-            })
-            .catch(()=>{});
+            res.locals.merchant.save().catch((err)=>{});
     },
 
     createFromSpreadsheet: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         //read file, get the correct sheet, create array from sheet
         let workbook = xlsx.readFile(req.file.path);
         fs.unlink(req.file.path, ()=>{});
@@ -170,13 +149,14 @@ module.exports = {
         }
 
         let merchant = {};
-        Merchant.findOne({_id: req.session.user})
+        res.locals.merchant
             .populate("inventory.ingredient")
+            .execPopulate()
             .then((response)=>{
                 merchant = response;
 
                 let order = new Order({
-                    merchant: req.session.user,
+                    merchant: res.locals.merchant._id,
                     name: array[1][locations.name],
                     date: spreadsheetDate,
                     taxes: parseInt(array[1][locations.taxes] * 100),
@@ -231,13 +211,9 @@ module.exports = {
     GET - Creates and sends a template xlsx for uploading orders
     */
     spreadsheetTemplate: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
-        Merchant.findOne({_id: req.session.user})
+        res.locals.merchant
             .populate("inventory.ingredient")
+            .execPopulate()
             .then((merchant)=>{
                 let workbook = xlsx.utils.book_new();
                 workbook.SheetNames.push("Order");
@@ -272,36 +248,21 @@ module.exports = {
     DELETE - Remove an order from the database
     */
     removeOrder: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
-        let merchant = {};
-        let order = {}
-        Merchant.findOne({_id: req.session.user})
-            .then((response)=>{
-                merchant = response;
-                return Order.findOne({_id: req.params.id});
-            })
-            .then((response)=>{
-                order = response;
-
-                return Order.deleteOne({_id: req.params.id})
-            })
-            .then((response)=>{
-                res.json({});
-
+        Order.findOne({_id: req.params.id})
+            .then((order)=>{
                 for(let i = 0; i < order.ingredients.length; i++){
-                    for(let j = 0; j < merchant.inventory.length; j++){
-                        if(order.ingredients[i].ingredient.toString() === merchant.inventory[j].ingredient.toString()){
-                            merchant.inventory[j].quantity -= order.ingredients[i].quantity;
+                    for(let j = 0; j < res.locals.merchant.inventory.length; j++){
+                        if(order.ingredients[i].ingredient.toString() === res.locals.merchant.inventory[j].ingredient.toString()){
+                            res.locals.merchant.inventory[j].quantity -= order.ingredients[i].quantity;
                             break;
                         }
                     }
                 }
 
-                return merchant.save();
+                return Promise.all([Order.deleteOne({_id: req.params.id}), res.locals.merchant.save()]);
+            })
+            .then((response)=>{
+                res.json({});
             })
             .catch((err)=>{
                 if(typeof(err) === "string"){

+ 3 - 106
controllers/otherData.js

@@ -1,8 +1,6 @@
 const Merchant = require("../models/merchant");
 
 const bcrypt = require("bcryptjs");
-const axios = require("axios");
-const path = require("path");
 
 module.exports = {
     /*
@@ -19,16 +17,16 @@ module.exports = {
                 if(merchant){
                     bcrypt.compare(req.body.password, merchant.password, (err, result)=>{
                         if(result){
-                            req.session.user = merchant._id;
+                            req.session.user = merchant.session.sessionId;
                             return res.redirect("/dashboard");
                         }else{
                             req.session.error = "INVALID EMAIL OR PASSWORD";
-                            return res.redirect("/");
+                            return res.redirect("/login");
                         }
                     });
                 }else{
                     req.session.error = "INVALID EMAIL OR PASSWORD";
-                    return res.redirect("/");
+                    return res.redirect("/login");
                 }
             })
             .catch((err)=>{
@@ -46,106 +44,5 @@ module.exports = {
         req.session.user = undefined;
 
         return res.redirect("/");
-    },
-
-    //GET - Redirects user to Clover OAuth page
-    cloverRedirect: function(req, res){
-        return res.redirect(`${process.env.CLOVER_ADDRESS}/oauth/authorize?client_id=${process.env.SUBLINE_CLOVER_APPID}&redirect_uri=${process.env.SUBLINE_CLOVER_URI}`);
-    },
-
-    //GET - Redirects user to Square OAuth page
-    squareRedirect: function(req, res){
-        return res.redirect(`${process.env.SQUARE_ADDRESS}/oauth2/authorize?client_id=${process.env.SUBLINE_SQUARE_APPID}&scope=INVENTORY_READ+ITEMS_READ+MERCHANT_PROFILE_READ+ORDERS_READ+PAYMENTS_READ`);
-    },
-
-    //GET - Get access token from clover and  redirect to merchant creation
-    cloverAuth: function(req, res){
-        let dataArr = req.url.slice(req.url.indexOf("?") + 1).split("&");
-        let authorizationCode = "";
-        let merchantId = "";
-
-        for(let i = 0; i < dataArr.length; i++){
-            if(dataArr[i].slice(0, dataArr[i].indexOf("=")) === "merchant_id"){
-                merchantId = dataArr[i].slice(dataArr[i].indexOf("=") + 1);
-            }else if(dataArr[i].slice(0, dataArr[i].indexOf("=")) === "code"){
-                authorizationCode = dataArr[i].slice(dataArr[i].indexOf("=") + 1);
-            }
-        }
-
-        let response = {}
-        axios.get(`${process.env.CLOVER_ADDRESS}/oauth/token?client_id=${process.env.SUBLINE_CLOVER_APPID}&client_secret=${process.env.SUBLINE_CLOVER_APPSECRET}&code=${authorizationCode}`)
-            .then((data)=>{
-                response = data;
-                
-                return Merchant.findOne({posId: merchantId});
-            })
-            .then((merchant)=>{
-                if(merchant){
-                    merchant.posAccessToken = response.data.access_token;
-
-                    merchant.save()
-                        .then((updatedMerchant)=>{
-                            req.session.user = updatedMerchant._id;
-                            return res.redirect("/dashboard");
-                        })
-                        .catch((err)=>{
-                            req.session.error = "ERROR: UNABLE TO CREATE MERCHANT";
-                            return res.redirect("/")
-                        });
-                }else{
-                    req.session.merchantId = merchantId;
-                    req.session.accessToken = response.data.access_token;
-                    return res.redirect("/merchant/create/clover");
-                }
-            })
-            .catch((err)=>{
-                req.session.error = "ERROR: UNABLE TO RETRIEVE DATA FROM CLOVER";
-                return res.redirect("/");
-            });
-    },
-
-    squareAuth: function(req, res){
-        const code = req.url.slice(req.url.indexOf("code=") + 5, req.url.indexOf("&"));
-        const url = `${process.env.SQUARE_ADDRESS}/oauth2/token?`;
-        let data = {
-            client_id: process.env.SUBLINE_SQUARE_APPID,
-            client_secret: process.env.SUBLINE_SQUARE_APPSECRET,
-            grant_type: "authorization_code",
-            code: code
-        };
-
-        axios.post(url, data)
-            .then((response)=>{
-                data = response.data;
-                return Merchant.findOne({posId: data.merchant_id});
-            })
-            .then((merchant)=>{
-                if(merchant){
-                    merchant.posAccessToken = data.access_token;
-
-                    return merchant.save()
-                        .then((merchant)=>{
-                            req.session.user = merchant._id;
-                            return res.redirect("/dashboard");
-                        })
-                        .catch((err)=>{
-                            req.session.error = "ERROR: UNABLE TO CREATE NEW USER";
-                            return res.redirect("/");
-                        })
-                }else{
-                    req.session.merchantId = data.merchant_id;
-                    req.session.accessToken = data.access_token;
-
-                    return res.redirect("/merchant/create/square");
-                }
-            })
-            .catch((err)=>{
-                req.session.error = "ERROR: UNABLE TO RETRIEVE DATA FROM SQUARE";
-                return res.redirect("/");
-            });
-    },
-
-    logo: function(req, res){
-        return res.sendFile(path.resolve("./views/shared/images/logo.png"));
     }
 }

+ 3 - 3
controllers/passwordReset.js

@@ -22,7 +22,7 @@ module.exports = {
                 merchant.verifyId = helper.generateId(15);
                 
                 const mailgunData = {
-                    from: "The Subline <clientsupport@thesusbline.net>",
+                    from: "The Subline <clientsupport@thesubline.net>",
                     to: merchant.email,
                     subject: "Password Reset",
                     html: passwordReset({
@@ -45,7 +45,7 @@ module.exports = {
     },
 
     enterPassword: function(req, res){
-        return res.render("passwordResetPages/password", {id: req.params.id, code: req.params.code});
+        return res.render("passwordResetPages/password", {id: req.params.id, code: req.params.code, banner: res.locals.banner});
     },
 
     resetPassword: function(req, res){
@@ -77,7 +77,7 @@ module.exports = {
             .then((merchant)=>{
                 if(merchant !== undefined){
                     req.session.success = "PASSWORD SUCCESSFULLY UPDATED.  PLEASE LOG IN";
-                    return res.redirect("/");
+                    return res.redirect("/login");
                 }
             })
             .catch((err)=>{

+ 20 - 227
controllers/recipeData.js

@@ -1,13 +1,10 @@
 const Recipe = require("../models/recipe.js");
-const Merchant = require("../models/merchant.js");
 const ArchivedRecipe = require("../models/archivedRecipe.js");
 
 const helper = require("./helper.js");
 
-const axios = require("axios");
 const xlsx = require("xlsx");
 const fs = require("fs");
-const { Console } = require("console");
 
 module.exports = {
     /*
@@ -23,13 +20,8 @@ module.exports = {
     Return = newly created recipe in same form as above, with _id
     */
     createRecipe: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         let recipe = new Recipe({
-            merchant: req.session.user,
+            merchant: res.locals.merchant._id,
             name: req.body.name,
             price: Math.round(req.body.price * 100),
             ingredients: req.body.ingredients
@@ -37,6 +29,9 @@ module.exports = {
 
         recipe.save()
             .then((newRecipe)=>{
+                res.locals.merchant.recipes.push(recipe);
+                res.locals.merchant.save().catch((err)=>{throw err});
+
                 return res.json(newRecipe);
             })
             .catch((err)=>{
@@ -48,13 +43,6 @@ module.exports = {
                 }
                 return res.json("ERROR: UNABLE TO SAVE INGREDIENT");
             });
-
-        Merchant.findOne({_id: req.session.user})
-            .then((merchant)=>{
-                merchant.recipes.push(recipe);
-                return merchant.save();
-            })
-            .catch((err)=>{});
     },
 
     /*
@@ -70,15 +58,10 @@ module.exports = {
     }
     */
     updateRecipe: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         Recipe.findOne({_id: req.body.id})
             .then((recipe)=>{
                 new ArchivedRecipe({
-                    merchant: req.session.user,
+                    merchant: res.locals.merchant._id,
                     name: recipe.name,
                     price: recipe.price,
                     date: new Date(),
@@ -107,31 +90,18 @@ module.exports = {
 
     //DELETE - removes a single recipe from the merchant and the database
     removeRecipe: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
+        if(res.locals.merchant.pos === "clover"){
+            return res.json("YOU MUST EDIT YOUR RECIPES INSIDE CLOVER");
+        }
+        
+        for(let i = 0; i < res.locals.merchant.recipes.length; i++){
+            if(res.locals.merchant.recipes[i].toString() === req.params.id){
+                res.locals.merchant.recipes.splice(i, 1);
+                break;
+            }
         }
 
-        Merchant.findOne({_id: req.session.user})
-            .then((merchant)=>{
-                if(merchant.pos === "clover"){
-                    return res.json("YOU MUST EDIT YOUR RECIPES INSIDE CLOVER");
-                }
-                
-                for(let i = 0; i < merchant.recipes.length; i++){
-                    if(merchant.recipes[i].toString() === req.params.id){
-                        merchant.recipes.splice(i, 1);
-                        break;
-                    }
-                }
-
-                merchant.save()
-                    .catch((err)=>{
-                        return res.json("ERROR: UNABLE TO SAVE DATA");
-                    })
-
-                return Recipe.deleteOne({_id: req.params.id});
-            })
+        Promise.all([Recipe.deleteOne({_id: req.params.id}), res.locals.merchant.save()])
             .then((response)=>{
                 return res.json({});
             })
@@ -146,181 +116,7 @@ module.exports = {
             });
     },
 
-    //GET - Checks clover for new or deleted recipes
-    //Returns: 
-    //  merchant: Full merchant (recipe ingredients populated)
-    //  count: Number of new recipes
-    updateRecipesClover: function(req, res){
-        if(!req.session.user){
-            req.session.error = "Must be logged in to do that";
-            return res.redirect("/");
-        }
-
-        let merchant = {};
-        let newRecipes = [];
-        let deletedRecipes = []
-        Merchant.findOne({_id: req.session.user})
-            .populate("recipes")
-            .then((response)=>{
-                merchant = response;
-                return axios.get(`https://apisandbox.dev.clover.com/v3/merchants/${merchant.posId}/items?access_token=${merchant.posAccessToken}`);
-            })
-            .then((result)=>{
-                deletedRecipes = merchant.recipes.slice();
-                for(let i = 0; i < result.data.elements.length; i++){
-                    for(let j = 0; j < deletedRecipes.length; j++){
-                        if(result.data.elements[i].id === deletedRecipes[j].posId){
-                            result.data.elements.splice(i, 1);
-                            deletedRecipes.splice(j, 1);
-                            i--;
-                            break;
-                        }
-                    }
-                }
-
-                for(let i = 0; i < deletedRecipes.length; i++){
-                    for(let j = 0; j < merchant.recipes.length; j++){
-                        if(deletedRecipes[i]._id === merchant.recipes[j]._id){
-                            merchant.recipes.splice(j, 1);
-                            break;
-                        }
-                    }
-                }
-
-                for(let i = 0; i < result.data.elements.length; i++){
-                    let newRecipe = new Recipe({
-                        posId: result.data.elements[i].id,
-                        merchant: merchant._id,
-                        name: result.data.elements[i].name,
-                        ingredients: [],
-                        price: result.data.elements[i].price
-                    });
-
-                    merchant.recipes.push(newRecipe);
-                    newRecipes.push(newRecipe);
-                }
-
-                Recipe.create(newRecipes).catch((err)=>{});
-
-                return merchant.save();
-            })
-            .then((newMerchant)=>{
-                return res.json({new: newRecipes, removed: deletedRecipes});
-            })
-            .catch((err)=>{
-                if(typeof(err) === "string"){
-                    return res.json(err);
-                }
-                if(err.name === "ValidationError"){
-                    return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
-                }
-                return res.json("ERROR: UNABLE TO RETRIEVE MERCHANT DATA");
-            });
-    },
-
-    updateRecipesSquare: function(req, res){
-        if(!req.session.user){
-            req.session.error = "Must be logged in to do that";
-            return res.redirect("/");
-        }
-
-        let merchant = {};
-        let merchantRecipes = [];
-        let newRecipes = [];
-
-        Merchant.findOne({_id: req.session.user})
-            .populate("recipes")
-            .then((fetchedMerchant)=>{
-                merchant = fetchedMerchant;
-                return axios.post(`${process.env.SQUARE_ADDRESS}/v2/catalog/search`, {
-                    object_types: ["ITEM"]
-                }, {
-                    headers: {
-                        Authorization: `Bearer ${merchant.posAccessToken}`
-                    }
-                });
-            })
-            .then((response)=>{
-                merchantRecipes = merchant.recipes.slice();
-
-                
-                for(let i = 0; i < response.data.objects.length; i++){
-                    let itemData = response.data.objects[i].item_data;
-                    for(let j = 0; j < itemData.variations.length; j++){
-                        let isFound = false;
-
-                        for(let k = 0; k < merchantRecipes.length; k++){
-                            if(itemData.variations[j].id === merchantRecipes[k].posId){
-                                merchantRecipes.splice(k, 1);
-                                k--;
-                                isFound = true;
-                                break;
-                            }
-                        }
-
-                        if(!isFound){
-                            let newRecipe = new Recipe({
-                                posId: itemData.variations[j].id,
-                                merchant: merchant._id,
-                                name: "",
-                                price: itemData.variations[j].item_variation_data.price_money.amount,
-                                ingredients: []
-                            });
-
-                            if(itemData.variations.length > 1){
-                                newRecipe.name = `${itemData.name} '${itemData.variations[j].item_variation_data.name}'`;
-                            }else{
-                                newRecipe.name = itemData.name;
-                            }
-
-                            newRecipes.push(newRecipe);
-                            merchant.recipes.push(newRecipe);
-                        }
-                    }
-                }
-
-                let ids = [];
-                for(let i = 0; i < merchantRecipes.length; i++){
-                    ids.push(merchantRecipes[i]._id);
-                    for(let j = 0; j < merchant.recipes.length; j++){
-                        if(merchantRecipes[i]._id.toString() === merchant.recipes[j]._id.toString()){
-                            merchant.recipes.splice(j, 1);
-                            j--;
-                            break;
-                        }
-                    }
-                }
-
-                if(newRecipes.length > 0){
-                    Recipe.create(newRecipes);
-                }
-
-                if(merchantRecipes.length > 0){
-                    Recipe.deleteMany({_id: {$in: ids}});
-                }
-
-                return merchant.save();
-            })
-            .then((merchant)=>{
-                return res.json({new: newRecipes, removed: merchantRecipes});
-            })
-            .catch((err)=>{
-                if(typeof(err) === "string"){
-                    return res.json(err);
-                }
-                if(err.name === "ValidationError"){
-                    return res.json(err.errors[Object.keys(err.errors)[0]].properties.message);
-                }
-                return res.json("ERROR: UNABLE TO RETRIEVE RECIPE DATA FROM SQUARE");
-            });
-    },
-
     createFromSpreadsheet: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         //read file, get the correct sheet, create array from sheet
         let workbook = xlsx.readFile(req.file.path);
         fs.unlink(req.file.path, ()=>{});
@@ -351,8 +147,9 @@ module.exports = {
 
         let merchant = {};
         let ingredients = [];
-        Merchant.findOne({_id: req.session.user})
+        res.locals.merchant
             .populate("inventory.ingredient")
+            .execPopulate()
             .then((response)=>{
                 merchant = response;
 
@@ -375,7 +172,7 @@ module.exports = {
 
                     if(array[i][locations.name] !== undefined){
                         currentRecipe = {
-                            merchant: req.session.user,
+                            merchant: res.locals.merchant._id,
                             name: array[i][locations.name],
                             price: parseInt(array[i][locations.price] * 100),
                             ingredients: []
@@ -428,13 +225,9 @@ module.exports = {
     },
 
     spreadsheetTemplate: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-        
-        Merchant.findOne({_id: req.session.user})
+        res.locals.merchant
             .populate("inventory.ingredient")
+            .execPopulate()
             .then((merchant)=>{
                 let workbook = xlsx.utils.book_new();
                 workbook.SheetNames.push("Recipes");

+ 27 - 72
controllers/renderer.js

@@ -1,43 +1,26 @@
 const ObjectId = require("mongoose").Types.ObjectId;
 
-const Merchant = require("../models/merchant.js");
 const Transaction = require("../models/transaction.js");
-const Activity = require("../models/activity.js");
 
 const helper = require("./helper.js");
 
 module.exports = {
     /*
     GET - Shows the public landing page
-    Return = a single error message (only if there is an error)
-    Renders landingPage
     */
     landingPage: function(req, res){
-        new Activity({
-            ipAddr: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
-            merchant: req.session.user,
-            route: "landing",
-            date: new Date()
-        })
-            .save()
-            .catch(()=>{});
+        return res.render("otherPages/landing", {banner: res.locals.banner});
+    },
 
-        let error = {};
-        let isLoggedIn = req.session.isLoggedIn || false;
-        if(req.session.error){
-            error = req.session.error;
-            req.session.error = undefined;
-        }else{
-            error = null;
-        }
-        if(req.session.success){
-            success = req.session.success;
-            req.session.success = undefined;
-        }else{
-            success = null;
-        }
+    //GET: Renders the login page
+    loginPage: function(req, res){
+        if(req.session.user !== undefined) return res.redirect("/dashboard");
+        return res.render("otherPages/login", {banner: res.locals.banner});
+    },
 
-        return res.render("landingPage/landing", {error: error, success: success, isLoggedIn: isLoggedIn});
+    //GET: Renders the registration page
+    registerPage: function(req, res){
+        return res.render("otherPages/register", {banner: res.locals.banner});
     },
 
     /*
@@ -46,63 +29,33 @@ module.exports = {
     Renders inventoryPage
     */
     displayDashboard: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-        
-        new Activity({
-            ipAddr: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
-            merchant: req.session.user,
-            route: "dashboard",
-            date: new Date()
-        })
-            .save()
-            .catch(()=>{});
-
         let merchant2 = {};
-        Merchant.findOne(
-            {_id: req.session.user},
-            {
-                name: 1,
-                pos: 1,
-                posId: 1,
-                posAccessToken: 1,
-                lastUpdatedTime: 1,
-                inventory: 1,
-                recipes: 1,
-                squareLocation: 1,
-                status: 1
-            }
-        )
+        res.locals.merchant
             .populate("inventory.ingredient")
             .populate("recipes")
+            .execPopulate()
             .then(async (merchant)=>{
-                merchant2 = merchant;
-                if(merchant.status.includes("unverified")){
+                if(res.locals.merchant.status.includes("unverified")){
                     throw "unverified";
                 }
 
-                if(merchant.pos === "clover"){
-                    await helper.getCloverData(merchant);
-                }else if(merchant.pos === "square"){
-                    await helper.getSquareData(merchant);
+                if(res.locals.merchant.pos === "clover"){
+                    await helper.getCloverData(res.locals.merchant);
+                }else if(res.locals.merchant.pos === "square"){
+                    await helper.getSquareData(res.locals.merchant);
                 }else{
                     return;
                 }
 
-                return merchant.save();
+                return res.locals.merchant.save();
             })
             .then((merchant)=>{
-                if(merchant){
-                    merchant2 = merchant;
-                }
                 let date = new Date();
                 let firstDay = new Date(date.getFullYear(), date.getMonth() - 1, 1);
 
                 return Transaction.aggregate([
                     {$match: {
-                        merchant: new ObjectId(req.session.user),
+                        merchant: new ObjectId(res.locals.merchant._id),
                         date: {$gte: firstDay},
                     }},
                     {$sort: {date: -1}},
@@ -113,16 +66,18 @@ module.exports = {
                 ]);      
             })
             .then((transactions)=>{
-                merchant2._id = undefined;
-                merchant2.posAccessToken = undefined;
-                merchant2.lastUpdatedTime = undefined;
-                merchant2.accountStatus = undefined;
-                merchant2.status = undefined;
+                res.locals.merchant._id = undefined;
+                res.locals.merchant.posAccessToken = undefined;
+                res.locals.merchant.lastUpdatedTime = undefined;
+                res.locals.merchant.accountStatus = undefined;
+                res.locals.merchant.status = undefined;
 
-                return res.render("dashboardPage/dashboard", {merchant: merchant2, transactions: transactions});
+                return res.render("dashboardPage/dashboard", {merchant: res.locals.merchant, transactions: transactions});
             })
             .catch((err)=>{
+                //TODO: add banners to the necessary pages
                 if(err === "unverified"){
+                    req.session.error = "PLEASE VERIFY YOUR EMAIL ADDRESS";
                     return res.redirect(`/verify/email/${merchant2._id}`);
                 }
                 req.session.error = "ERROR: UNABLE TO RETRIEVE USER DATA";

+ 48 - 138
controllers/transactionData.js

@@ -1,7 +1,4 @@
 const Transaction = require("../models/transaction");
-const Merchant = require("../models/merchant");
-
-const helper = require("./helper.js");
 
 const ObjectId = require("mongoose").Types.ObjectId;
 const xlsx = require("xlsx");
@@ -17,11 +14,6 @@ module.exports = {
     }
     */
     getTransactions: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         let from = new Date(req.body.from);
         let to = new Date(req.body.to);
 
@@ -45,7 +37,7 @@ module.exports = {
 
         Transaction.aggregate([
             {$match: {
-                merchant: ObjectId(req.session.user),
+                merchant: ObjectId(res.locals.merchant._id),
                 date: {
                     $gte: from,
                     $lt: to
@@ -77,35 +69,26 @@ module.exports = {
     }
     */
     createTransaction: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-        
+        let keys = Object.keys(req.body.ingredientUpdates);
 
-        Merchant.findOne({_id: req.session.user})
-            .then((merchant)=>{
-                let keys = Object.keys(req.body.ingredientUpdates);
-
-                for(let i = 0; i < keys.length; i++){
-                    for(let j = 0; j < merchant.inventory.length; j++){
-                        if(merchant.inventory[j].ingredient._id.toString() === keys[i]){
-                            merchant.inventory[j].quantity -= req.body.ingredientUpdates[keys[i]];
+        for(let i = 0; i < keys.length; i++){
+            for(let j = 0; j < res.locals.merchant.inventory.length; j++){
+                if(res.locals.merchant.inventory[j].ingredient._id.toString() === keys[i]){
+                    res.locals.merchant.inventory[j].quantity -= req.body.ingredientUpdates[keys[i]];
 
-                            break;
-                        }
-                    }
+                    break;
                 }
+            }
+        }
 
-                return merchant.save();
-            })
+        res.locals.merchant.save()
             .then((merchant)=>{
                 if(req.body.date === null){
                     throw "NEW TRANSACTIONS MUST CONTAIN A DATE";
                 }
 
                 return new Transaction({
-                    merchant: req.session.user,
+                    merchant: res.locals.merchant._id,
                     date: new Date(req.body.date),
                     device: "none",
                     recipes: req.body.recipes
@@ -126,11 +109,6 @@ module.exports = {
     },
 
     createFromSpreadsheet: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
         //read file, get the correct sheet, create array from sheet
         let workbook = xlsx.readFile(req.file.path);
         fs.unlink(req.file.path, ()=>{});
@@ -178,12 +156,13 @@ module.exports = {
             }
         }
 
-        Merchant.findOne({_id: req.session.user})
+        res.locals.merchant
             .populate("recipes")
             .populate("inventory.ingredient")
+            .execPopulate()
             .then((merchant)=>{
                 let transaction = new Transaction({
-                    merchant: req.session.user,
+                    merchant: res.locals.merchant._id,
                     date: spreadsheetDate,
                     recipes: []
                 });
@@ -251,13 +230,9 @@ module.exports = {
     },
 
     spreadsheetTemplate: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
-        Merchant.findOne({_id: req.session.user})
+        res.locals.merchant
             .populate("recipes")
+            .execPopulate()
             .then((merchant)=>{
                 let workbook = xlsx.utils.book_new();
                 workbook.SheetNames.push("Transaction");
@@ -284,39 +259,26 @@ module.exports = {
     DELETE - Remove a transaction from the database
     */
     remove: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
-        let merchant = {};
-        let transaction = {};
-        Merchant.findOne({_id: req.session.user})
-            .then((response)=>{
-                merchant = response;
-                return Transaction.findOne({_id: req.params.id}).populate("recipes.recipe");
-            })
-            .then((response)=>{
-                transaction = response;
-                return Transaction.deleteOne({_id: req.params.id});
-            })
-            .then((response)=>{
-                res.json();
-
+        Transaction.findOne({_id: req.params.id})
+            .populate("recipes.recipe")
+            .then((transaction)=>{
                 for(let i = 0; i < transaction.recipes.length; i++){
                     const recipe = transaction.recipes[i].recipe;
                     for(let j = 0; j < recipe.ingredients.length; j++){
                         const ingredient = recipe.ingredients[j].ingredient;
-                        for(let k = 0; k < merchant.inventory.length; k++){
-                            if(ingredient.toString() === merchant.inventory[k].ingredient.toString()){
-                                merchant.inventory[k].quantity += recipe.ingredients[j].quantity * transaction.recipes[i].quantity;
+                        for(let k = 0; k < res.locals.merchant.inventory.length; k++){
+                            if(ingredient.toString() === res.locals.merchant.inventory[k].ingredient.toString()){
+                                res.locals.merchant.inventory[k].quantity += recipe.ingredients[j].quantity * transaction.recipes[i].quantity;
                                 break;
                             }
                         }
                     }
                 }
-
-                return merchant.save();
+                
+                return Promise.all([Transaction.deleteOne({_id: req.params.id}), res.locals.merchant.save()]);
+            })
+            .then((response)=>{
+                res.json({});
             })
             .catch((err)=>{
                 if(typeof(err) === "string"){
@@ -329,55 +291,10 @@ module.exports = {
             });
     },
 
-    /*
-    GET - get transactions between two dates, sorted and group by date
-    params:
-        from: Date string
-        to: Date string
-    return:
-        [{
-            date: Date
-            transactions:[[Recipe]]
-        }]
-    */
-    getTransactionsByDate: function(req, res){
-        if(!req.session.user){
-            req.session.error = "MUST BE LOGGED IN TO DO THAT";
-            return res.redirect("/");
-        }
-
-        const from = new Date(req.params.from);
-        const to = new Date(req.params.to);
-
-        Transaction.aggregate([
-            {$match: {
-                merchant: ObjectId(req.session.user),
-                date: {
-                    $gte: from,
-                    $lt: to
-                }
-            }},
-            {$sort: {
-                date: 1
-            }}
-        ])
-            .then((transactions)=>{
-                return res.json(transactions);
-            })
-            .catch((err)=>{
-                return res.json("ERROR: UNABLE TO RETRIEVE DATA");
-            });
-    },
-
     /*
     GET - Creates 5000 transactions for logged in merchant for testing
     */
     populate: function(req, res){
-        if(!req.session.user){
-            res.session.error = "Must be logged in to do that";
-            return res.redirect("/");
-        }
-
         function randomDate() {
             let now = new Date();
             let start = new Date();
@@ -385,39 +302,32 @@ module.exports = {
             return new Date(start.getTime() + Math.random() * (now.getTime() - start.getTime()));
         }
 
-        Merchant.findOne({_id: req.session.user})
-            .then((merchant)=>{
-                let newTransactions = [];
-
-                for(let i = 0; i < 5000; i++){
-                    let newTransaction = new Transaction({
-                        merchant: merchant._id,
-                        date: randomDate(),
-                        recipes: []
-                    });
+        let newTransactions = [];
+        for(let i = 0; i < 5000; i++){
+            let newTransaction = new Transaction({
+                merchant: res.locals.merchant._id,
+                date: randomDate(),
+                recipes: []
+            });
 
-                    let numberOfRecipes = Math.floor((Math.random() * 5) + 1);
+            let numberOfRecipes = Math.floor((Math.random() * 5) + 1);
 
-                    for(let j = 0; j < numberOfRecipes; j++){
-                        let recipeNumber = Math.floor(Math.random() * merchant.recipes.length);
-                        let randQuantity = Math.floor((Math.random() * 3) + 1);
+            for(let j = 0; j < numberOfRecipes; j++){
+                let recipeNumber = Math.floor(Math.random() * res.locals.merchant.recipes.length);
+                let randQuantity = Math.floor((Math.random() * 3) + 1);
 
-                        newTransaction.recipes.push({
-                            recipe: merchant.recipes[recipeNumber],
-                            quantity: randQuantity
-                        });
-                    }
+                newTransaction.recipes.push({
+                    recipe: res.locals.merchant.recipes[recipeNumber],
+                    quantity: randQuantity
+                });
+            }
 
-                    newTransactions.push(newTransaction);
-                }
+            newTransactions.push(newTransaction);
+        }
 
-                Transaction.create(newTransactions)
-                    .then((transactions)=>{
-                        return res.redirect("/dashboard");
-                    })
-                    .catch((err)=>{
-                        return;
-                    });
+        Transaction.create(newTransactions)
+            .then((transactions)=>{
+                return res.redirect("/dashboard");
             })
             .catch((err)=>{
                 return;

+ 60 - 0
middleware.js

@@ -0,0 +1,60 @@
+const Merchant = require("./models/merchant.js")
+
+module.exports = {
+    verifySession: function(req, res, next){
+        if(req.session.user === undefined) {
+            req.session.error = "PLEASE LOG IN";
+            return res.redirect("/login");
+        }
+    
+        Merchant.findOne({"session.sessionId": req.session.user})
+            .then((merchant)=>{
+                if(merchant === null){
+                    throw "no merchant";
+                }
+    
+                if(merchant.session.date < new Date()){
+                    let newExpiration = new Date();
+                    newExpiration.setDate(newExpiration.getDate() + 90);
+    
+                    merchant.session.sessionId = helper.generateId(25);
+                    merchant.session.date = newExpiration;
+                    merchant.save();
+                    req.session.error = "PLEASE LOG IN";
+                    return res.redirect("/login");
+                }
+    
+                res.locals.merchant = merchant;
+                return next();
+            })
+            .catch((err)=>{
+                if(err === "no merchant"){
+                    req.session.user = undefined;
+                    req.session.error = "PLEASE LOG IN";
+                    return res.redirect("/login");
+                }
+                return res.json("ERROR: UNABLE TO RETRIEVE DATA");
+            });
+    },
+
+    formatBanner: function(req, res, next){
+        if(req.session.error !== undefined){
+            res.locals.banner = {
+                type: "error",
+                message: req.session.error,
+                color: "red"
+            };
+            req.session.error = undefined;
+        }else if(req.session.success !== undefined){
+            res.locals.banner = {
+                type: "notification",
+                message: req.session.success,
+                color: "green"
+            };
+            req.session.success = undefined;
+        }
+
+        return next();
+    }
+}
+

+ 0 - 16
models/activity.js

@@ -1,16 +0,0 @@
-const mongoose = require("mongoose");
-
-const ActivitySchema = new mongoose.Schema({
-    ipAddr: String,
-    merchant: {
-        type: mongoose.Schema.Types.ObjectId,
-        ref: "Merchant"
-    },
-    route: String,
-    date: {
-        type: Date,
-        required: true
-    }
-});
-
-module.exports = mongoose.model("Activity", ActivitySchema);

+ 2 - 1
models/archivedRecipe.js

@@ -4,7 +4,8 @@ const ArchivedRecipeSchema = new mongoose.Schema({
     merchant: {
         type: mongoose.Schema.Types.ObjectId,
         ref: "Merchant",
-        required: true
+        required: true,
+        index: true
     },
     name: {
         type: String,

+ 2 - 1
models/inventoryAdjustment.js

@@ -13,7 +13,8 @@ let inventoryAdjustmentSchema = new mongoose.Schema({
     ingredient: {
         type: mongoose.Schema.Types.ObjectId,
         ref: "Ingredient",
-        required: [true, "Must provide the ingredient"]
+        required: [true, "Must provide the ingredient"],
+        index: true
     },
     quantity: {
         type: Number,

+ 9 - 1
models/merchant.js

@@ -21,7 +21,8 @@ const MerchantSchema = new mongoose.Schema({
         validate: {
             validator: emailValid,
             message: "INVALID EMAIL ADDRESS"
-        }
+        },
+        index: true
     },
     password: {
         type: String,
@@ -62,6 +63,13 @@ const MerchantSchema = new mongoose.Schema({
         type: mongoose.Schema.Types.ObjectId,
         ref: "Recipe"
     }],
+    session: {
+        sessionId: {
+            type: String,
+            index: true
+        },
+        expiration: Date
+    },
     verifyId: String
 });
 

+ 4 - 1
models/order.js

@@ -6,7 +6,8 @@ const OrderSchema = new mongoose.Schema({
     merchant: {
         type: mongoose.Schema.Types.ObjectId,
         ref: "Merchant",
-        required: [true, "MUST PROVIDE THE MERCHANT"]
+        required: [true, "MUST PROVIDE THE MERCHANT"],
+        index: true
     },
     name: {
         type: String,
@@ -44,4 +45,6 @@ const OrderSchema = new mongoose.Schema({
     }]
 });
 
+OrderSchema.index({merchant: 1, date: 1});
+
 module.exports = mongoose.model("Order", OrderSchema);

+ 4 - 1
models/transaction.js

@@ -5,7 +5,8 @@ const TransactionSchema = new mongoose.Schema({
     merchant: {
         type: mongoose.Schema.Types.ObjectId,
         ref: "Merchant",
-        required: [true, "MERCHANT IS REQUIRED FOR A TRANSACTION"]
+        required: [true, "MERCHANT IS REQUIRED FOR A TRANSACTION"],
+        index: true
     },
     date: {
         type: Date,
@@ -29,4 +30,6 @@ const TransactionSchema = new mongoose.Schema({
     posId: String
 });
 
+TransactionSchema.index({merchant: 1, date: 1});
+
 module.exports = mongoose.model("Transaction", TransactionSchema);

+ 7 - 20
package-lock.json

@@ -276,11 +276,11 @@
       "dev": true
     },
     "axios": {
-      "version": "0.19.2",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
-      "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+      "version": "0.21.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
+      "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
       "requires": {
-        "follow-redirects": "1.5.10"
+        "follow-redirects": "^1.10.0"
       }
     },
     "balanced-match": {
@@ -2041,22 +2041,9 @@
       }
     },
     "follow-redirects": {
-      "version": "1.5.10",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
-      "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
-      "requires": {
-        "debug": "=3.1.0"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        }
-      }
+      "version": "1.13.1",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
+      "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
     },
     "for-in": {
       "version": "1.0.2",

+ 1 - 1
package.json

@@ -21,7 +21,7 @@
   },
   "homepage": "https://github.com/The-Subline/Inventory-Management#readme",
   "dependencies": {
-    "axios": "^0.19.2",
+    "axios": "^0.21.1",
     "bcryptjs": "^2.4.3",
     "compression": "^1.7.4",
     "cookie-session": "^1.4.0",

+ 35 - 41
routes.js

@@ -9,76 +9,70 @@ const informationPages = require("./controllers/informationPages.js");
 const emailVerification = require("./controllers/emailVerification.js");
 const passwordReset = require("./controllers/passwordReset.js");
 
-const multer = require("multer");
-const upload = multer({dest: "uploads/"});
+const session = require("./middleware.js").verifySession;
+const banner = require("./middleware.js").formatBanner;
+
+const upload = require("multer")({dest: "uploads/"});
 
 module.exports = function(app){
     //Render page
-    app.get("/", renderer.landingPage);
-    app.get("/dashboard", renderer.displayDashboard);
+    app.get("/", banner, renderer.landingPage);
+    app.get("/login", banner, renderer.loginPage);
+    app.get("/register", banner, renderer.registerPage);
+    app.get("/dashboard", session, renderer.displayDashboard);
     app.get("/resetpassword/*", renderer.displayPassReset);
-
+    
     //Merchant
     app.post("/merchant/create/none", merchantData.createMerchantNone);
-    app.get("/merchant/create/clover", merchantData.createMerchantClover);
-    app.get("/merchant/create/square", merchantData.createMerchantSquare);
-    app.put("/merchant/ingredients/update", merchantData.updateMerchantIngredient); //also updates some data in ingredients
-    app.post("/merchant/password", merchantData.updatePassword);
+    app.put("/merchant/ingredients/update", session, merchantData.updateIngredientQuantities); //also updates some data in ingredients
+    app.post("/merchant/password", merchantData.updatePassword); //TODO: change to work with session
 
     //Ingredients
-    app.post("/ingredients/create", ingredientData.createIngredient);  //also adds to merchant
-    app.put("/ingredients/update", ingredientData.updateIngredient);
-    app.post("/ingredients/create/spreadsheet", upload.single("ingredients"), ingredientData.createFromSpreadsheet);
-    app.get("/ingredients/download/spreadsheet", ingredientData.spreadsheetTemplate);
-    app.delete("/ingredients/remove/:id", ingredientData.removeIngredient);
-    
+    app.post("/ingredients/create", session, ingredientData.createIngredient);  //also adds to merchant
+    app.put("/ingredients/update", session, ingredientData.updateIngredient);
+    app.post("/ingredients/create/spreadsheet", session, upload.single("ingredients"), ingredientData.createFromSpreadsheet);
+    app.get("/ingredients/download/spreadsheet", session, ingredientData.spreadsheetTemplate);
+    app.delete("/ingredients/remove/:id", session, ingredientData.removeIngredient);
 
     //Recipes
-    app.post("/recipe/create", recipeData.createRecipe);
-    app.put("/recipe/update", recipeData.updateRecipe);
-    app.delete("/recipe/remove/:id", recipeData.removeRecipe);
-    app.get("/recipe/update/clover", recipeData.updateRecipesClover);
-    app.get("/recipe/update/square", recipeData.updateRecipesSquare);
-    app.post("/recipes/create/spreadsheet", upload.single("recipes"), recipeData.createFromSpreadsheet);
-    app.get("/recipes/download/spreadsheet", recipeData.spreadsheetTemplate);
+    app.post("/recipe/create", session, recipeData.createRecipe);
+    app.put("/recipe/update", session, recipeData.updateRecipe);
+    app.delete("/recipe/remove/:id", session, recipeData.removeRecipe);
+    app.post("/recipes/create/spreadsheet", session, upload.single("recipes"), recipeData.createFromSpreadsheet);
+    app.get("/recipes/download/spreadsheet", session, recipeData.spreadsheetTemplate);
 
     //Orders
-    app.post("/orders/get", orderData.getOrders);
-    app.post("/order/create", orderData.createOrder);
-    app.post("/orders/create/spreadsheet", upload.single("orders"), orderData.createFromSpreadsheet);
-    app.get("/orders/download/spreadsheet", orderData.spreadsheetTemplate);
-    app.delete("/order/:id", orderData.removeOrder);
+    app.post("/orders/get", session, orderData.getOrders);
+    app.post("/order/create", session, orderData.createOrder);
+    app.post("/orders/create/spreadsheet", session, upload.single("orders"), orderData.createFromSpreadsheet);
+    app.get("/orders/download/spreadsheet", session, orderData.spreadsheetTemplate);
+    app.delete("/order/:id", session, orderData.removeOrder);
 
     //Transactions
-    app.post("/transaction", transactionData.getTransactions);
-    app.post("/transaction/create", transactionData.createTransaction);
-    app.post("/transactions/create/spreadsheet", upload.single("transactions"), transactionData.createFromSpreadsheet);
-    app.get("/transactions/download/spreadsheet", transactionData.spreadsheetTemplate);
-    app.delete("/transaction/:id", transactionData.remove);
-    app.get("/populatesometransactions", transactionData.populate);
+    app.post("/transaction", session, transactionData.getTransactions);
+    app.post("/transaction/create", session, transactionData.createTransaction);
+    app.post("/transactions/create/spreadsheet", session, upload.single("transactions"), transactionData.createFromSpreadsheet);
+    app.get("/transactions/download/spreadsheet", session, transactionData.spreadsheetTemplate);
+    app.delete("/transaction/:id", session, transactionData.remove);
+    app.get("/populatesometransactions", session, transactionData.populate);
 
     //Other
     app.post("/login", otherData.login);
     app.get("/logout", otherData.logout);
-    app.get("/cloverlogin", otherData.cloverRedirect);
-    app.get("/squarelogin", otherData.squareRedirect);
-    app.get("/cloverauth*", otherData.cloverAuth);
-    app.get("/squareauth", otherData.squareAuth);
-    app.get("/logo", otherData.logo);
-
+    
     //Information Pages
     app.get("/privacy", informationPages.privacy);
     app.get("/terms", informationPages.terms);
     app.get("/help", informationPages.help);
 
     //Email verification
-    app.get("/verify/email/:id", emailVerification.sendVerifyEmail);
+    app.get("/verify/email/:id", banner, emailVerification.sendVerifyEmail);
     app.post("/verify/resend", emailVerification.resendEmail);
     app.get("/verify/:id/:code", emailVerification.verify);
 
     //Password reset
     app.get("/reset/email", passwordReset.enterEmail);
     app.post("/reset/email", passwordReset.generateCode);
-    app.get("/reset/:id/:code", passwordReset.enterPassword);
+    app.get("/reset/:id/:code", banner, passwordReset.enterPassword);
     app.post("/reset", passwordReset.resetPassword);
 }

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
views/dashboardPage/bundle.js


+ 5 - 2
views/dashboardPage/dashboard.css

@@ -184,7 +184,7 @@ Multi-strand use classes
             background: rgb(201, 201, 201);
             color: black;
         }
-
+        
 /* 
 Home Strand 
 */
@@ -596,7 +596,10 @@ Modal
             align-items: center;
             justify-content: space-between;
             margin-top: 0;
-            padding: 25px;
+            padding: 25px 100px;
+            background: rgb(240, 252, 255);
+            border: 2px solid black;
+            border-radius: 10px;
         }
 
             .modalSpreadsheetUpload > *{

+ 1 - 0
views/dashboardPage/dashboard.ejs

@@ -53,6 +53,7 @@
         <% include ../shared/loader %>
         <% include ./modal %>
 
+        <script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
         <script>
             let data = {
                 merchant: <%- JSON.stringify(merchant) %>,

+ 13 - 15
views/dashboardPage/ejs/sidebars/orderCalculator.ejs

@@ -8,22 +8,20 @@
         </button>
     </div>
 
-    <h1>ORDER CALCULATOR</h1>
+    <h1>ORDER PREDICTION</h1>
 
-    <div class="scroller">
-        <table id="calculatorItems" class="calculatorItems" cellspacing="0" cellpadding="5px">
-            <thead>
-                <th>INGREDIENT</th>
-                <th>USAGE / DAY (PREDICTED)</th>
-            </thead>
-            <tbody id="calculatorItemsBody"></tbody>
-        </table>
+    <div class="dateRange">
+        <h3>Date Range:</h3>
+        <div class="dateRangeInput">
+            <input id="predictDateFrom" type="date">
+            <p> - </p>
+            <input id="predictDateTo" type="date">
+        </div>
     </div>
 
-    <template id="calculatorItem">
-        <tr class="calculatorItem">
-            <td></td>
-            <td class="calcUsage"></td>
-        </tr>
-    </template>
+    <select id="predictSelector"></select>
+
+    <button class="button" id="predictButton">PREDICT</button>
+
+    <h1 id="prediction"></h1>
 </div>

+ 1 - 1
views/dashboardPage/ejs/strands/orders.ejs

@@ -7,7 +7,7 @@
 
             <button id="newOrderBtn" class="button">NEW</button>
 
-            <button id="orderCalcBtn" class="button">CALCULATOR</button>
+            <button id="orderCalcBtn" class="button">PREDICTOR</button>
         </div>
     </div>
 

+ 12 - 63
views/dashboardPage/js/classes/Merchant.js

@@ -140,10 +140,7 @@ class Merchant{
     }
 
     set name(name){
-        if(this.isSanitaryString(name)){
-            this._name = name;
-        }
-        return false;
+        this._name = name;
     }
 
     get pos(){
@@ -268,6 +265,11 @@ class Merchant{
         return this._transactions;
     }
 
+    //TODO: remove this, just for testing purposes
+    clearTransactions(){
+        this._transactions = [];
+    }
+
     getTransactions(from = 0, to = new Date()){
         if(merchant._transactions.length <= 0){
             return [];
@@ -365,6 +367,10 @@ class Merchant{
         return this._orders;
     }
 
+    clearOrders(){
+        this._orders = [];
+    }
+
     addOrder(data, isNew = false){
         let order = new this._modules.Order(
             data._id,
@@ -393,10 +399,6 @@ class Merchant{
         this._modules.orders.isPopulated = false;
     }
 
-    setOrders(orders){
-        this._orders = orders;
-    }
-
     removeOrder(order){
         const index = this._orders.indexOf(order);
         if(index === undefined){
@@ -423,9 +425,6 @@ class Merchant{
     }
 
     getRevenue(from, to = new Date()){
-        if(from === 0){
-            from = this._transactions[0].date;
-        }
         const {start, end} = this.getTransactionIndices(from, to);
 
         let total = 0;
@@ -452,11 +451,7 @@ class Merchant{
             quantity: quantity of ingredient sold in default unit
         }]
     */
-    getIngredientsSold(from = 0, to = new Date()){
-        if(from === 0){
-            from = this._ingredients[0].date;
-        }
-        
+    getIngredientsSold(from, to = new Date()){
         let recipes = this.getRecipesSold(from, to);
         let ingredientList = [];
 
@@ -492,11 +487,7 @@ class Merchant{
         to = end Date
     return: quantity sold in default unit
     */
-    getSingleIngredientSold(ingredient, from = 0, to = new Date()){
-        if(from === 0){
-            from = this._transactions[0].date;
-        }
-
+    getSingleIngredientSold(ingredient, from, to = new Date()){
         const {start, end} = this.getTransactionIndices(from, to);
 
         let total = 0;
@@ -587,35 +578,6 @@ class Merchant{
         return ingredientsByCategory;
     }
 
-    unitizeIngredients(){
-        let ingredientsByUnit = [];
-
-        for(let i = 0; i < this.ingredients.length; i++){
-            let unitExists = false;
-            const innerIngredient = this.ingredients[i].ingredient;
-            for(let j = 0; j < ingredientsByUnit.length; j++){
-                if(innerIngredient.unit === ingredientsByUnit[j].name || innerIngredient.specialUnit === ingredientsByUnit[j].name){
-                    ingredientsByUnit[j].ingredients.push(this.ingredients[i]);
-
-                    unitExists = true;
-                    break;
-                }
-            }
-
-            if(!unitExists){
-                let unit = "";
-                unit = innerIngredient.unit;
-                
-                ingredientsByUnit.push({
-                    name: unit,
-                    ingredients: [this.ingredients[i]]
-                });
-            }
-        }
-
-        return ingredientsByUnit;
-    }
-
     getRecipesForIngredient(ingredient){
         let recipes = [];
 
@@ -654,19 +616,6 @@ class Merchant{
 
         return {start: start, end: end};
     }
-
-
-    isSanitaryString(str){
-        let disallowed = ["\\", "<", ">", "$", "{", "}", "(", ")"];
-
-        for(let i = 0; i < disallowed.length; i++){
-            if(str.includes(disallowed[i])){
-                return false;
-            }
-        }
-
-        return true;
-    }
 }
 
 module.exports = Merchant;

+ 0 - 11
views/dashboardPage/js/classes/Order.js

@@ -1,8 +1,5 @@
 class OrderIngredient{
     constructor(ingredient, quantity, pricePerUnit){
-        if(quantity < 0){
-            return false;
-        }
         this._ingredient = ingredient;
         this._quantity = quantity;
         this._pricePerUnit = pricePerUnit;
@@ -34,10 +31,6 @@ class OrderIngredient{
     }
 
     updateQuantity(quantity){
-        if(quantity < 0){
-            return false;
-        }
-
         this._quantity += this.convertToBase(quantity);
     }
 
@@ -119,10 +112,6 @@ class Order{
         this._ingredients = [];
         this._parent = parent;
 
-        if(date > new Date()){
-            return false;
-        }
-
         for(let i = 0; i < ingredients.length; i++){
             for(let j = 0; j < merchant.ingredients.length; j++){
                 if(merchant.ingredients[j].ingredient.id === ingredients[i].ingredient){

+ 0 - 42
views/dashboardPage/js/classes/Recipe.js

@@ -1,9 +1,5 @@
 class RecipeIngredient{
     constructor(ingredient, quantity){
-        if(quantity < 0){
-            controller.createBanner("QUANTITY CANNOT BE A NEGATIVE NUMBER", "error");
-            return false;
-        }
         this._ingredient = ingredient;
         this._quantity = quantity;
     }
@@ -37,11 +33,6 @@ class RecipeIngredient{
     }
 
     set quantity(quantity){
-        if(quantity < 0){
-            controller.createBanner("QUANTITY CANNOT BE A NEGATIVE NUMBER", "error");
-            return false;
-        }
-
         this_quantity = this.convertToBase(quantity);
     }
 
@@ -87,14 +78,6 @@ parent = merchant that it belongs to
 */
 class Recipe{
     constructor(id, name, price, ingredients, parent){
-        if(price < 0){
-            controller.createBanner("PRICE CANNOT BE A NEGATIVE NUMBER", "error");
-            return false;
-        }
-        if(!this.isSanitaryString(name)){
-            controller.createBanner("NAME CONTAINS ILLEGAL CHARACTERS", "error");
-            return false;
-        }
         this._id = id;
         this._name = name;
         this._price = price;
@@ -121,10 +104,6 @@ class Recipe{
     }
 
     set name(name){
-        if(!this.isSanitaryString(name)){
-            return false;
-        }
-
         this._name = name;
     }
 
@@ -133,10 +112,6 @@ class Recipe{
     }
 
     set price(price){
-        if(price < 0){
-            return false;
-        }
-
         this._price = price;
     }
 
@@ -149,11 +124,6 @@ class Recipe{
     }
 
     addIngredient(ingredient, quantity){
-        if(quantity < 0){
-            controller.createBanner("QUANTITY CANNOT BE A NEGATIVE NUMBER", "error");
-            return false;
-        }
-
         let recipeIngredient = new RecipeIngredient(ingredient, quantity);
         this._ingredients.push(recipeIngredient);
 
@@ -164,18 +134,6 @@ class Recipe{
     removeIngredients(){
         this._ingredients = [];
     }
-
-    isSanitaryString(str){
-        let disallowed = ["\\", "<", ">", "$", "{", "}", "(", ")"];
-
-        for(let i = 0; i < disallowed.length; i++){
-            if(str.includes(disallowed[i])){
-                return false;
-            }
-        }
-
-        return true;
-    }
 }
 
 module.exports = Recipe;

+ 0 - 12
views/dashboardPage/js/classes/Transaction.js

@@ -1,13 +1,5 @@
 class TransactionRecipe{
     constructor(recipe, quantity){
-        if(quantity < 0){
-            controller.createBanner("QUANTITY CANNOT BE A NEGATIVE NUMBER", "error");
-            return false;
-        }
-        if(quantity % 1 !== 0){
-            controller.createBanner("RECIPES WITHIN A TRANSACTION MUST BE WHOLE NUMBERS", "error");
-            return false;
-        }
         this._recipe = recipe;
         this._quantity = quantity;
     }
@@ -24,10 +16,6 @@ class TransactionRecipe{
 class Transaction{
     constructor(id, date, recipes, parent){
         date = new Date(date);
-        if(date > new Date()){
-            controller.createBanner("DATE CANNOT BE SET TO THE FUTURE", "error");
-            return false;
-        }
         this._id = id;
         this._parent = parent;
         this._date = date;

+ 2 - 8
views/dashboardPage/js/dashboard.js

@@ -78,8 +78,7 @@ controller = {
             case "orders":
                 activeButton = document.getElementById("ordersBtn");
                 document.getElementById("ordersStrand").style.display = "flex";
-                merchant.setOrders(data);
-                orders.display(Order);
+                orders.display();
                 break;
             case "transactions":
                 activeButton = document.getElementById("transactionsBtn");
@@ -324,12 +323,7 @@ document.getElementById("homeBtn").onclick = ()=>{controller.openStrand("home")}
 document.getElementById("ingredientsBtn").onclick = ()=>{controller.openStrand("ingredients")};
 document.getElementById("recipeBookBtn").onclick = ()=>{controller.openStrand("recipeBook")};
 document.getElementById("analyticsBtn").onclick = ()=>{controller.openStrand("analytics")};
-document.getElementById("ordersBtn").onclick = async ()=>{
-    if(merchant.orders.length === 0){
-        merchant.setOrders(await orders.getOrders(Order));
-    }
-    controller.openStrand("orders", merchant.orders);
-}
+document.getElementById("ordersBtn").onclick = async ()=>{controller.openStrand("orders")};
 document.getElementById("transactionsBtn").onclick = ()=>{controller.openStrand("transactions", merchant.getTransactions())};
 
 controller.openStrand("home");

+ 2 - 2
views/dashboardPage/js/sidebars/editIngredient.js

@@ -43,8 +43,8 @@ let editIngredient = {
             button.innerText = units[i].toUpperCase();
             button.onclick = ()=>{this.changeUnit(button)};
             buttonList.appendChild(button);
-
-            if(units[i] === ingredient.ingredient.unitType){
+            
+            if(units[i] === ingredient.ingredient.unit){
                 button.classList.add("unitActive");
             }
         }

+ 118 - 39
views/dashboardPage/js/sidebars/orderCalculator.js

@@ -1,56 +1,135 @@
 let orderCalculator = {
     display: function(){
-        let calculatorItems = document.getElementById("calculatorItemsBody");
-        let template = document.getElementById("calculatorItem").content.children[0];
-        let calculations = this.predict();
+        let to = new Date();
+        to.setDate(to.getDate() + 7);
+        to.setHours(0, 0, 0, 0);
+        from = new Date();
+        from.setDate(from.getDate() + 1);
+        from.setHours(0, 0, 0, 0);
 
-        while(calculatorItems.children.length > 0){
-            calculatorItems.removeChild(calculatorItems.firstChild);
+        document.getElementById("predictDateFrom").valueAsDate = from;
+        document.getElementById("predictDateTo").valueAsDate = to;
+        document.getElementById("predictButton").onclick = ()=>{this.predict()};
+
+        let selector = document.getElementById("predictSelector");
+        while(selector.children.length > 0){
+            selector.removeChild(selector.firstChild);
         }
 
-        for(let i = 0; i < calculations.length; i++){
-            let outputString = `${calculations[i].output.toFixed(2)} ${calculations[i].ingredient.unit.toUpperCase()}`;
-        
-            let item = template.cloneNode(true);
-            item.children[0].innerText = calculations[i].ingredient.name,
-            item.children[1].innerText = outputString;
-            calculatorItems.appendChild(item);
+        for(let i = 0; i < merchant.ingredients.length; i++){
+            let option = document.createElement("option");
+            option.innerText = merchant.ingredients[i].ingredient.name;
+            option.value = merchant.ingredients[i].ingredient.id;
+            selector.appendChild(option);
         }
     },
 
     predict: function(){
-        let now = new Date();
-        let yesterday = new Date();
-        yesterday.setHours(0, 0, 0, 0);
-        let monthAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 30);
-        let weekAgo = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
-    
-        let calculations = [];
-
-        let month = merchant.getIngredientsSold(monthAgo, yesterday);
-        let week = merchant.getIngredientsSold(weekAgo, yesterday);
-
-        let weights = {
-            month: 0.33,
-            week: 0.67
+        let from = document.getElementById("predictDateFrom").valueAsDate;
+        let to = document.getElementById("predictDateTo").valueAsDate;
+        let ingredient = merchant.getIngredient(document.getElementById("predictSelector").value);
+
+        let data = {
+            to: new Date(),
+            recipes: []
         }
 
-        for(let i = 0; i < month.length; i++){
-            for(let j = 0; j < week.length; j++){
-                if(month[i].ingredient.id === week[j].ingredient.id){
-                    let monthAverage = (month[i].quantity / 30) * weights.month;
-                    let weekAverage = (week[i].quantity / 7) * weights.week;
-
-                    let calc = {
-                        ingredient: month[i].ingredient,
-                        output: monthAverage + weekAverage
-                    };
-                    calculations.push(calc);
+        data.from = new Date(data.to.getFullYear() - 1, data.to.getMonth(), data.to.getDate());
+
+        fetch("/transaction", {
+            method: "post",
+            headers: {
+                "Content-Type": "application/json;charset=utf-8"
+            },
+            body: JSON.stringify(data)
+        })
+            .then(response => response.json())
+            .then((response)=>{
+                if(typeof(response) === "string"){
+                    controller.createBanner(response, "error");
+                }else{
+                    const options = {
+                        task: "regression",
+                        debug: false
+                    }
+
+                    const nn = ml5.neuralNetwork(options);
+
+                    this.createData(nn, response, ingredient.ingredient);
+                    nn.normalizeData();
+                    nn.train(()=>{
+                        let predictors = [];
+                        while(from <= to){
+                            predictors.push(nn.predict({
+                                month: from.getMonth(),
+                                day: from.getDay()
+                            }))
+
+                            from.setDate(from.getDate() + 1);
+                        }
+
+                        Promise.all(predictors)
+                            .then((predictions)=>{
+                                let total = 0
+                                for(let i = 0; i < predictions.length; i++){
+                                    total += predictions[i][0].value;
+                                }
+
+                                if(isNaN(total)) total = 0;
+                                document.getElementById("prediction").innerText = `${total.toFixed(2)} ${ingredient.ingredient.unit.toUpperCase()}`;
+                            })
+                            .catch((err)=>{
+                                controller.createBanner("ERROR: UNABLE TO MAKE PREDICTION", "error");
+                            });
+                    });
+                }
+            })
+            .catch((err)=>{
+                controller.createBanner("ERROR: UNABLE TO MAKE PREDICTION", "error");
+            });
+    },
+
+    createData: function(nn, transactions, ingredient){
+        let today = new Date(transactions[transactions.length-1].date);
+        today.setHours(0, 0, 0, 0);
+        let tomorrow = new Date(transactions[transactions.length-1].date);
+        tomorrow.setDate(tomorrow.getDate() + 1);
+        tomorrow.setHours(0, 0, 0, 0);
+        let dailySum = 0;
+
+        for(let i = transactions.length - 1; i >= 0; i--){
+            transactions[i].date = new Date(transactions[i].date);
+
+            if(transactions[i].date >= tomorrow){
+                let inputs = {
+                    month: today.getMonth(),
+                    day: today.getDay()
+                };
+
+                let output = {quantity: dailySum};
+
+                nn.addData(inputs, output);
+                dailySum = 0;
+                today.setDate(today.getDate() + 1);
+                tomorrow.setDate(tomorrow.getDate() + 1);
+            }
+
+            for(let j = 0; j < transactions[i].recipes.length; j++){
+                for(let k = 0; k < merchant.recipes.length; k++){
+                    if(merchant.recipes[k].id === transactions[i].recipes[j].recipe){
+                        for(let l = 0; l < merchant.recipes[k].ingredients.length; l++){
+                            if(merchant.recipes[k].ingredients[l].ingredient === ingredient){
+                                dailySum += merchant.recipes[k].ingredients[l].quantity * transactions[i].recipes[j].quantity;
+
+                                break;
+                            }
+                        }
+
+                        break;
+                    }
                 }
             }
         }
-
-        return calculations;
     }
 }
 

+ 4 - 2
views/dashboardPage/js/strands/analytics.js

@@ -175,7 +175,8 @@ let analytics = {
                 r: 10,
                 b: 20,
                 t: 30
-            }
+            },
+            paper_bgcolor: "rgba(0, 0, 0, 0)"
         }
 
         Plotly.newPlot("itemUseGraph", [trace], layout);
@@ -262,7 +263,8 @@ let analytics = {
                 r: 10,
                 b: 20,
                 t: 30
-            }
+            },
+            paper_bgcolor: "rgba(0, 0, 0, 0)"
         }
 
         Plotly.newPlot("recipeSalesGraph", [trace], layout);

+ 6 - 4
views/dashboardPage/js/strands/home.js

@@ -75,10 +75,11 @@ let home = {
             },
             yaxis: {
                 title: "$"
-            }
+            },
+            paper_bgcolor: "rgba(0, 0, 0, 0)"
         }
 
-        if(screen.width < 1400){
+        if(screen.width < 1200){
             layout.margin = {
                 l: 35,
                 r: 0
@@ -196,10 +197,11 @@ let home = {
                 },
                 yaxis: {
                     showticklabels: false
-                }
+                },
+                paper_bgcolor: "rgba(0, 0, 0, 0)"
             }
 
-            if(screen.width < 1400){
+            if(screen.width < 1200){
                 layout.margin = {
                     l: 10,
                     r: 10,

+ 43 - 35
views/dashboardPage/js/strands/orders.js

@@ -1,32 +1,31 @@
 let orders = {
+    isPopulated: false,
+
     display: function(){
         document.getElementById("orderFilterBtn").addEventListener("click", ()=>{controller.openSidebar("orderFilter")});
         document.getElementById("newOrderBtn").addEventListener("click", ()=>{controller.openSidebar("newOrder")});
         document.getElementById("orderCalcBtn").addEventListener("click", ()=>{controller.openSidebar("orderCalculator")});
 
-        let orderList = document.getElementById("orderList");
-        let template = document.getElementById("order").content.children[0];
-
-        while(orderList.children.length > 0){
-            orderList.removeChild(orderList.firstChild);
+        if(this.isPopulated === false){
+            this.getOrders()
+                .then((response)=>{
+                    if(typeof(response) === "string"){
+                        controller.createBanner(response, "error");
+                    }else{
+                        this.displayOrders();
+                    }
+                })
+                .catch((err)=>{
+                    controller.createBanner("UNABLE TO DISPLAY ORDERS", "error");
+                });
+        }else{
+            this.displayOrders();
         }
 
-        for(let i = 0; i < merchant.orders.length; i++){
-            let orderDiv = template.cloneNode(true);
-            orderDiv.order = merchant.orders[i];
-            orderDiv.children[0].innerText = merchant.orders[i].name;
-            orderDiv.children[1].innerText = `${merchant.orders[i].ingredients.length} ingredients`;
-            orderDiv.children[2].innerText = merchant.orders[i].date.toLocaleDateString("en-US");
-            orderDiv.children[3].innerText = `$${merchant.orders[i].getTotalCost().toFixed(2)}`;
-            orderDiv.onclick = ()=>{
-                controller.openSidebar("orderDetails", merchant.orders[i]);
-                orderDiv.classList.add("active");
-            }
-            orderList.appendChild(orderDiv);
-        }
+        this.isPopulated = true;
     },
 
-    getOrders: function(Order){
+    getOrders: function(){
         let loader = document.getElementById("loaderContainer");
         loader.style.display = "flex";
 
@@ -51,25 +50,11 @@ let orders = {
                 if(typeof(response) === "string"){
                     controller.createBanner(response, "error");
                 }else{
-                    let orders = [];
+                    merchant.clearOrders();
 
                     for(let i = 0; i < response.length; i++){
-                        orders.push(new Order(
-                            response[i]._id,
-                            response[i].name,
-                            response[i].date,
-                            response[i].taxes,
-                            response[i].fees,
-                            response[i].ingredients,
-                            merchant
-                        ));
+                        merchant.addOrder(response[i], true);
                     }
-
-                    if(merchant.orders.length === 0){
-                        merchant.setOrders(orders);
-                    }
-
-                    return orders;
                 }
             })
             .catch((err)=>{
@@ -78,6 +63,29 @@ let orders = {
             .finally(()=>{
                 loader.style.display = "none";
             });
+    },
+
+    displayOrders: function(){
+        let orderList = document.getElementById("orderList");
+        let template = document.getElementById("order").content.children[0];
+
+        while(orderList.children.length > 0){
+            orderList.removeChild(orderList.firstChild);
+        }
+
+        for(let i = 0; i < merchant.orders.length; i++){
+            let orderDiv = template.cloneNode(true);
+            orderDiv.order = merchant.orders[i];
+            orderDiv.children[0].innerText = merchant.orders[i].name;
+            orderDiv.children[1].innerText = `${merchant.orders[i].ingredients.length} ingredients`;
+            orderDiv.children[2].innerText = merchant.orders[i].date.toLocaleDateString("en-US");
+            orderDiv.children[3].innerText = `$${merchant.orders[i].getTotalCost().toFixed(2)}`;
+            orderDiv.onclick = ()=>{
+                controller.openSidebar("orderDetails", merchant.orders[i]);
+                orderDiv.classList.add("active");
+            }
+            orderList.appendChild(orderDiv);
+        }
     }
 }
 

+ 8 - 33
views/dashboardPage/sidebars.css

@@ -67,9 +67,9 @@
         background: none;
         border-style: none;
         color: white;
-        font-size: 20px;
+        font-size: 18px;
         margin: 15px 0;
-        padding-left: 50px;
+        padding-left: 15px;
         width: 100%;
         height: 75px;
         cursor: pointer;
@@ -581,35 +581,10 @@ ORDER CALCULATOR
     width: 100%;
 }
 
-    .scroller{
-        max-height: 90%;
-        overflow-y: auto;
-    }
-
-    .calculatorItems{
-        width: 100%;
+    #orderCalculator > *{
+        margin: 15px 0;
     }
 
-        .calculatorItems thead tr th:nth-child(1){
-            text-align: left;
-        }
-
-        .calculatorItems thead tr th:nth-child(2){
-            text-align: right;
-        }
-
-        .calculatorItem:nth-child(odd){
-            background: rgb(201, 201, 201);
-        }
-
-        .calcUsage{
-            display: table-cell;
-            vertical-align: middle;
-            text-align: right;
-            font-weight: bold;
-            padding: 5px;
-        }
-
 /*
 TRANSACTION DETAILS
 */
@@ -721,8 +696,8 @@ NEW TRANSACTION
             }
 
         /* ORDER CALCULATOR */
-        .calculatorItem:nth-child(2n+1){
-            color: black;
+        #orderCalcBtn{
+            display: none;
         }
     }
 
@@ -790,8 +765,8 @@ NEW TRANSACTION
                 }
             
             /* ORDER CALCULATOR */
-            .calculatorItem:nth-child(2n+1){
-                color: black;
+            #orderCalcBtn{
+                display: none;
             }
 
             /* INGREDIENT DETAILS */

+ 14 - 6
views/informationPages/help.ejs

@@ -10,7 +10,20 @@
         <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet">
     </head>
     <body>
-        <% include ../shared/header %>
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        
+            <div class="headerEnd">
+                <a id="helpLink" class="headerHide" href="/help">HELP</a>
+        
+                <a id="privacyPolicyLink" class="headerHide" href="/privacy">PRIVACY POLICY</a>
+        
+                <a id="termsLink" class="headerHide" href="/terms">TERMS AND CONDITIONS</a>        
+            </div>
+        </div>
 
         <div class="helpMenu">
             <ul>
@@ -209,10 +222,5 @@
 
             <a href="mailto:lee@thesubline.com">lee@thesubline.com</a>
         </div>
-
-        <script>
-            document.getElementById("privacyPolicyLink").classList.remove("headerHide");
-            document.getElementById("termsLink").classList.remove("headerHide");
-        </script>
     </body>
 </html>

+ 14 - 6
views/informationPages/privacyPolicy.ejs

@@ -10,7 +10,20 @@
         <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet">
     </head>
     <body>
-        <% include ../shared/header %>
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        
+            <div class="headerEnd">
+                <a id="helpLink" class="headerHide" href="/help">HELP</a>
+        
+                <a id="privacyPolicyLink" class="headerHide" href="/privacy">PRIVACY POLICY</a>
+        
+                <a id="termsLink" class="headerHide" href="/terms">TERMS AND CONDITIONS</a>        
+            </div>
+        </div>
 
         <div class="content">
             <h1>The Subline, LLC Privacy Policy</h1>
@@ -199,10 +212,5 @@
         
             <p class="contactInfo last">www.thesubline.com</p>
         </div>
-
-        <script>
-            document.getElementById("termsLink").classList.remove("headerHide");
-            document.getElementById("helpLink").classList.remove("headerHide");
-        </script>
     </body>
 </html>

+ 14 - 6
views/informationPages/terms.ejs

@@ -10,7 +10,20 @@
         <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet">
     </head>
     <body>
-        <% include ../shared/header %>
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        
+            <div class="headerEnd">
+                <a id="helpLink" class="headerHide" href="/help">HELP</a>
+        
+                <a id="privacyPolicyLink" class="headerHide" href="/privacy">PRIVACY POLICY</a>
+        
+                <a id="termsLink" class="headerHide" href="/terms">TERMS AND CONDITIONS</a>        
+            </div>
+        </div>
 
         <div class="content">
             <h1>The Subline, Terms and Conditions</h1>
@@ -293,10 +306,5 @@
 
             <p>16.3 All credit card transactions are processed using secure encryption are handled via Third Party Payment Providers.</p>
         </div>
-
-        <script>
-            document.getElementById("helpLink").classList.remove("headerHide");
-            document.getElementById("privacyPolicyLink").classList.remove("headerHide");
-        </script>
     </body>
 </html>

+ 0 - 53
views/landingPage/controller.js

@@ -1,53 +0,0 @@
-let controller = {
-    publicStrand: document.getElementById("publicStrand"),
-    loginStrand: document.getElementById("loginStrand"),
-    registerStrand: document.getElementById("registerStrand"),
-
-    onStart: function(){
-        if(error){
-            let bannerContainer = document.getElementById("bannerContainer");
-            let banner = document.getElementById("banner").content.children[0].cloneNode(true);
-            banner.children[0].style.backgroundColor = "rgb(200, 0, 0)";
-            banner.children[0].children[0].style.display = "block";
-            banner.children[1].innerText = error;
-            bannerContainer.appendChild(banner);
-
-            let timer = setTimeout(()=>{
-                bannerContainer.removeChild(banner);
-            }, 10000);
-
-            banner.children[2].addEventListener("click", ()=>{
-                bannerContainer.removeChild(banner);
-                clearTimeout(timer);
-            });
-        }
-
-        if(success){
-            let bannerContainer = document.getElementById("bannerContainer");
-            let banner = document.getElementById("banner").content.children[0].cloneNode(true);
-            banner.children[0].style.backgroundColor = "rgb(0, 145, 55)";
-            banner.children[0].children[2].style.display = "block";
-            banner.children[1].innerText = success;
-            bannerContainer.appendChild(banner);
-
-            let timer = setTimeout(()=>{
-                bannerContainer.removeChild(banner);
-            }, 10000);
-
-            banner.children[2].addEventListener("click", ()=>{
-                bannerContainer.removeChild(banner);
-                clearTimeout(timer);
-            });
-        }
-
-        publicObj.display();
-    },
-
-    clearScreen: function(){
-        this.publicStrand.style.display = "none";
-        this.loginStrand.style.display = "none";
-        this.registerStrand.style.display = "none";
-    }
-}
-
-controller.onStart();

+ 0 - 256
views/landingPage/landing.css

@@ -1,256 +0,0 @@
-/* Public Strand */
-#publicStrand{
-    display: flex;
-    flex-direction: column;
-}
-
-    .main-background{
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        background-image: url("/shared/images/backgroundImg01.jpg");
-        background-repeat: no-repeat;
-        background-size: cover;
-        height: 88vh;
-        width: 100%;
-        color: rgb(0, 27, 45);
-    }
-
-        .public-buttons{
-            position: absolute;
-            right: 50px;
-            top: 80px;
-        }
-            
-            .public-buttons *{
-                margin: 10px;
-            }
-
-        .logo-text{
-            display: flex;
-            flex-direction: column;
-            justify-content: center;
-            align-items: center;
-            max-width: 50vw;
-            text-align: center;
-        }
-
-            .logo-text img{
-                max-height: 25vh;
-                margin-bottom: 50px;
-            }
-
-            .logo-text p{
-                font-weight: bold;
-                font-size: 25px;
-                color: rgb(255, 99, 107);
-            }
-    
-    .more-info{
-        background: rgb(0, 27, 45);
-        color: rgb(201, 201, 201);
-        text-align: center;
-        padding: 75px;
-        border-top: 5px solid black;
-    }
-
-        .more-info h1{
-            color: rgb(255, 99, 107);
-            margin: 25px;
-            font-size: 45px;
-        }
-
-        .more-info p{
-            font-size: 25px;
-            color: rgb(201, 201, 201);
-            text-align: justify;
-            padding: 10px;
-        }
-
-        .more-info li{
-            font-size: 20px;
-            color: rgb(201, 201, 201);
-            text-align: justify;
-            padding: 10px;
-        }
-
-        .more-info span{
-            font-weight: bold;
-            color: rgb(255, 99, 107);
-        }
-
-/* Login Strand */
-
-#loginStrand, #registerStrand{
-    display: none;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    background-image: url("/shared/images/backgroundImg01.jpg");
-    background-repeat: no-repeat;
-    background-size: cover;
-    height: 91.5vh;
-}
-
-#registerStrand form{
-    margin: 0;
-}
-
-#registerStrand form > *{
-    margin: 2px;
-}
-
-/* ----The Checkbox and Inputs Start---- */
-
-/* The container */
-.container {
-    display: block;
-    position: relative;
-    padding-left: 35px;
-    margin-bottom: 12px;
-    cursor: pointer;
-    font-size: 16px;
-    -webkit-user-select: none;
-    -moz-user-select: none;
-    -ms-user-select: none;
-    user-select: none;
-  }
-  
-  /* Hide the browser's default checkbox */
-  .container input {
-    position: absolute;
-    opacity: 0;
-    cursor: pointer;
-    height: 0;
-    width: 0;
-  }
-  
-  /* Create a custom checkbox */
-  .checkmark {
-    position: absolute;
-    top: 0;
-    left: 0;
-    height: 24px;
-    width: 24px;
-    border: 1px solid#ccc;
-    border-radius: 4px;
-    background-color: rgb(255, 255, 255);
-  }
-  
-  /* On mouse-over, add a grey background color */
-  .checkmark:hover {
-    background-color: #ccc;
-  }
-  
-  /* When the checkbox is checked, add a blue background */
-  .container input:checked ~ .checkmark {
-    background-color: #5A6F7D;
-    border: 1px solid#5A6F7D;
-  }
-  
-  /* Create the checkmark/indicator (hidden when not checked) */
-  .checkmark:after {
-    content: "";
-    position: absolute;
-    display: none;
-  }
-  
-  /* Show the checkmark when checked */
-  .container input:checked ~ .checkmark:after {
-    display: block;
-  }
-  
-  /* Style the checkmark/indicator */
-  .container .checkmark:after {
-    left: 9px;
-    top: 5px;
-    width: 5px;
-    height: 10px;
-    border: solid white;
-    border-width: 0 3px 3px 0;
-    -webkit-transform: rotate(45deg);
-    -ms-transform: rotate(45deg);
-    transform: rotate(45deg);
-  }
-/* ----The Checkbox and Inputs End---- */
-
-/* Inputs Start */
-
-input[type=text], select {
-    width: 100%;
-    padding: 12px 20px;
-    margin: 8px 0;
-    display: inline-block;
-    border: 1px solid #ccc;
-    border-radius: 4px;
-    box-sizing: border-box;
-    padding-bottom: 16px;
-    font-size: 16px;
-
-  }
-
-  input[type=text], input[type=password], select {
-    width: 100%;
-    padding: 12px 20px;
-    margin: 8px 0;
-    display: inline-block;
-    border: 1px solid #ccc;
-    border-radius: 4px;
-    box-sizing: border-box;
-    font-size: 16px;
-  }
-  
-  input[type=submit] {
-    width: 100%;
-    background-color:rgb(255, 99, 107);
-    color: white;
-    padding: 14px 20px;
-    margin: 8px 0;
-    border: none;
-    border-radius: 4px;
-    cursor: pointer;
-    font-size: 22px;
-    margin-top: 30px;
-  }
-
-  input[type=submit]:hover {
-    background: rgb(243, 77, 86);
-    color: white;
-  }
-
-  input[type=text]:focus, input[type=password]:focus {
-    border: 2px solid #555;
-    outline: none;
-  }
-
-  input[type=button] {
-    background-color: rgb(255, 99, 107);
-  }
-
-  input[type=button]:hover {
-    background-color:rgb(0, 27, 45);
-  }
-
-  .input-error{
-    border-color: red;
-}
-
-/* Inputs End */
-
-@media screen and (max-width: 600px){
-    .logo-text img{
-        max-height: 20vh;
-    }
-
-    .logo-text p{
-        font-size: 20px;
-    }
-
-    .more-info{
-        padding: 25px;
-    }
-
-    .public-buttons{
-        right: 25vw;
-    }
-}

+ 0 - 170
views/landingPage/landing.ejs

@@ -1,170 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-    <head>
-        <meta charset="UTF-8">
-        <title>The Subline</title>
-        <link rel="icon" type="img/png" href="/shared/images/logo.png">
-        <link rel="stylesheet" href="/landingPage/landing.css">
-        <link rel="stylesheet" href="/shared/shared.css">
-        <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet">
-    </head>
-    <body>
-        <% include ../shared/header %>
-
-        <div id="bannerContainer">
-            <template id="banner">
-                <div class="banner">
-                    <div>
-                        <svg style="display:none;" width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
-                            <circle cx="12" cy="12" r="10"></circle>
-                            <line x1="15" y1="9" x2="9" y2="15"></line>
-                            <line x1="9" y1="9" x2="15" y2="15"></line>
-                        </svg>
-
-                        <svg style="display:none;" width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
-                            <circle cx="12" cy="12" r="10"></circle>
-                            <line x1="12" y1="8" x2="12" y2="12"></line>
-                            <line x1="12" y1="16" x2="12.01" y2="16"></line>
-                        </svg>
-
-                        <svg style="display:none;" width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
-                            <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
-                            <polyline points="22 4 12 14.01 9 11.01"></polyline>
-                        </svg>
-                    </div>
-                    <p></p>
-                    <button>
-                        <svg width="18" height="18" viewBox="0 0 24 24" fill="rgb(200, 0, 0)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
-                            <circle cx="12" cy="12" r="10"></circle>
-                            <line x1="15" y1="9" x2="9" y2="15"></line>
-                            <line x1="9" y1="9" x2="15" y2="15"></line>
-                        </svg>
-                    </button>
-                </div>
-            </template>
-        </div>
-
-        <div id="publicStrand">
-            <div class="main-background">
-                <div class="logo-text">
-                    <img src="/shared/images/logoWithText.png" alt="Subline">
-
-                    <p>The only inventory-POS integration that reduces food, time, and waste costs through automation, machine learning, and data-driven analysis.</p>
-                </div>
-            </div>
-
-            <div class="more-info">
-                <h1>TIME IS MONEY</h1>
-
-                <p>For any restaurant owner familiar with this age-old adage (ie: everyone), the Subline integration creates an immediate impact by freeing up time to focus on their heighest priorities.  Yes, all good managers understand why it is important to control food and waste costs but staying on top of this task becomes burdensome and pushed aside with the volume of operational and customer issues faced every day.  It's the unfortunate side-effect of restaurant owners who lack ideal support and continually compete against razor thin margins.</p>
-
-                <div class="line-break"></div>
-
-                <h1>HOW IT WORKS</h1>
-
-                <p>LET THE SUBLINE HELP SAVE YOU TIME WITH:</p>
-
-                <ul>
-                    <li><span>Real Time Tracking</span> - Inventory is updated in real-time with resupply and customer purchases.  This eliminates the guess work for owners who want to know what is on their shelves.</li>
-                    <li><span>True Cost of Inventory</span> - The true cost of each item on owner's shelves are tracked against waste, returns, samples, etc.  This allows owners to determine the opportunity cost of inventory, and how it affects the bottom line.</li>
-                    <li><span>Recipe Integration</span> - The Subline integrates recipes into inventory management.  This gives owners the ability to conduct cost modeling and profit projections based on ingredient type, quantity, and source.</li>
-                    <li><span>Report Generation</span> - The Subline generates weekly, monthly, and annual reports that includes KPIs and trend analysis to help owners make decisions.  The Subline also generates weekly spot-check reports to ensure you never run out of a customer favorite during peak demand.</li>
-                    <li><span>Waste Audit Reporting</span> - The Subline generates daily audits for the owner.  Integrating daily auditing into routine kitchen operations means that responsible waste prevention will become part of the restaurant's culture and will have implications both socially and on the bottom line.</li>
-                    <li><span>Trend Identification</span> - Trends happen in cycles.  The Subline helps identify these trends, such as seasonal changes in menu demand, supply pricing, and local supply shortages, to give owners the information they need for inventory management.</li>
-                </ul>
-            </div>
-        </div>
-
-        <div id="registerStrand">
-            <form onsubmit="registerObj.submit()">
-                <h1> Join Subline </h1>
-
-                <label id="nameLabel">Restaurant Name
-                    <input class="input" id="regName" name="name" type="text" required>
-                </label>
-        
-                <label>Email
-                    <input type="text" id="regEmail" name="email" type="email" required>
-                </label>
-        
-                <label>Password
-                    <input type="password" id="regPass" name="password" type="password" required>
-                </label>
-        
-                <label>Confirm Password
-                    <input type="password" id="regConfirmPass" name="confirmPassword" type="password" required>
-                </label>
-        
-                <div id="agreement">
-                    <label class="container">
-                        <input id="checkAgree" type="checkbox">
-
-                        <span class="checkmark"></span>
-        
-                        <p style="padding-top: 5px; padding-left: 7px;">I agree to the 
-                            <a class="link" href="/privacy">Privacy Policy</a> and the 
-                            <a class="link" href="/terms">Terms and Conditions</a>
-                        </p>
-                    </label>   
-                </div>
-
-                <input id="regButton" class="buttonDisabled" type="submit" value="Sign Up with Email">
-
-                <!-- <h1>OR</h1>
-
-                <a class="button buttonWithBorder" href="/cloverlogin">Sign Up with Clover</a>
-
-                <a class="button buttonWithBorder" href="/squarelogin">Sign Up with Square</a> -->
-
-                <h3 class="link" style="margin-top: 30px;" onclick="loginObj.display()"> Already have an account? Please Sign In </h3>
-            </form>
-        </div>
-
-        <div id="loginStrand">
-            <form action="/login" method="post">
-                <h1> Welcome Back </h1>
-        
-                <label>Email
-                    <input type="text" name="email" type="email" required>
-                </label>
-        
-                <label>Password
-                    <input type="password" name="password" required>
-                </label>
-        
-                <input id="signIn" type="submit" value="Sign In with Email">
-
-                <!-- <h1>OR</h1>
-
-                <a class="button buttonWithBorder" href="/cloverlogin">Sign In with Clover</a>
-
-                <a class="button buttonWithBorder" href="/squarelogin">Sign In with Square</a> -->
-                
-                <a href="/reset/email" class="link">Forgot your password?</a>
-
-                <h3 id="goToRegister" class="link">Need to register?</h3>
-            </form>
-        </div>
-
-        <% include ../shared/footer %>
-
-        <% include ../shared/loader %>
-
-        <script>
-            let error = <%- JSON.stringify(error) %>;
-            let success = <%- JSON.stringify(success) %>;
-            let isLoggedIn = <%- JSON.stringify(isLoggedIn) %>;
-        </script>
-        <script src="/landingPage/public.js"></script>
-        <script src="/landingPage/login.js"></script>
-        <script src="/landingPage/register.js"></script>
-        <script src="/landingPage/controller.js"></script>
-        <script>
-            let links = document.querySelector(".headerEnd").children;
-
-            for(let i = 0; i < links.length; i++){
-                links[i].classList.remove("headerHide");
-            }
-        </script>
-    </body>
-</html>

+ 0 - 14
views/landingPage/login.js

@@ -1,14 +0,0 @@
-let loginObj = {
-    display: function(username){
-        controller.clearScreen();
-        controller.loginStrand.style.display = "flex";
-
-        document.getElementById("goToRegister").addEventListener("click", ()=>{registerObj.display()});
-    },
-
-    cancel: function(){
-        event.preventDefault();
-        
-        publicObj.display();
-    },
-}

+ 0 - 6
views/landingPage/public.js

@@ -1,6 +0,0 @@
-let publicObj = {
-    display: function(){
-        controller.clearScreen();
-        controller.publicStrand.style.display = "flex";
-    }
-}

+ 0 - 48
views/landingPage/register.js

@@ -1,48 +0,0 @@
-let registerObj = {
-    display: function(){
-        controller.clearScreen();
-        controller.registerStrand.style.display = "flex";
-
-        document.getElementById("checkAgree").checked = false;
-        document.getElementById("regButton").classList = "buttonDisabled";
-    },
-
-    agreement: function(){
-        let checkbox = document.getElementById("checkAgree");
-        let button = document.getElementById("regButton");
-
-        if(checkbox.checked){
-            button.classList = "button";
-        }else{
-            button.classList = "buttonDisabled";
-        }
-    },
-
-    submit: function(){
-        event.preventDefault();
-
-        let form = document.querySelector("#registerStrand form");
-        let checkbox = document.getElementById("checkAgree");
-
-        if(!checkbox.checked){
-            banner.createError("Please agree to the Privacy Policy and Terms and Conditions to continue");
-            return;
-        }
-
-        let pass = document.getElementById("regPass").value;
-        let confirmPass = document.getElementById("regConfirmPass").value;
-        if(pass !== confirmPass){
-            banner.createError("Your passwords do not match");
-            return;
-        }
-
-        if(checkbox.checked){
-            document.getElementById("loaderContainer").style.display = "flex";
-            form.action = "merchant/create/none";
-            form.method = "post";
-            form.submit();
-        }else{
-            banner.createError("Please agree to the Privacy Policy and Terms and Conditions to continue");
-        }
-    }
-}

+ 79 - 0
views/otherPages/landing.ejs

@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>The Subline</title>
+        <link rel="icon" type="img/png" href="/shared/images/logo.png">
+        <link rel="stylesheet" href="/otherPages/style.css">
+        <link rel="stylesheet" href="/shared/shared.css">
+        <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet">
+    </head>
+    <body>
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        
+            <div class="headerEnd">
+                <a id="helpLink" href="/help">HELP</a>
+        
+                <a id="privacyPolicyLink" href="/privacy">PRIVACY POLICY</a>
+        
+                <a id="termsLink" href="/terms">TERMS AND CONDITIONS</a>
+        
+                <a id="logInButton" class="button" href="/login">LOG IN</a>
+        
+                <a id="joinButton" class="button" href="/register">JOIN</a>
+            </div>
+        </div>
+
+        <% if(banner !== undefined){ %>
+            <% include ../shared/banner.ejs %>
+        <% } %>
+
+        <div class="main-background">
+            <div class="logo-text">
+                <img src="/shared/images/logoWithText.png" alt="Subline">
+
+                <p>The only inventory-POS integration that reduces food, time, and waste costs through automation, machine learning, and data-driven analysis.</p>
+            </div>
+        </div>
+
+        <div class="more-info">
+            <h1>TIME IS MONEY</h1>
+
+            <p>For any restaurant owner familiar with this age-old adage (ie: everyone), the Subline integration creates an immediate impact by freeing up time to focus on their heighest priorities.  Yes, all good managers understand why it is important to control food and waste costs but staying on top of this task becomes burdensome and pushed aside with the volume of operational and customer issues faced every day.  It's the unfortunate side-effect of restaurant owners who lack ideal support and continually compete against razor thin margins.</p>
+
+            <div class="line-break"></div>
+
+            <h1>HOW IT WORKS</h1>
+
+            <p>LET THE SUBLINE HELP SAVE YOU TIME WITH:</p>
+
+            <ul>
+                <li><span>Real Time Tracking</span> - Inventory is updated in real-time with resupply and customer purchases.  This eliminates the guess work for owners who want to know what is on their shelves.</li>
+                <li><span>True Cost of Inventory</span> - The true cost of each item on owner's shelves are tracked against waste, returns, samples, etc.  This allows owners to determine the opportunity cost of inventory, and how it affects the bottom line.</li>
+                <li><span>Recipe Integration</span> - The Subline integrates recipes into inventory management.  This gives owners the ability to conduct cost modeling and profit projections based on ingredient type, quantity, and source.</li>
+                <li><span>Report Generation</span> - The Subline generates weekly, monthly, and annual reports that includes KPIs and trend analysis to help owners make decisions.  The Subline also generates weekly spot-check reports to ensure you never run out of a customer favorite during peak demand.</li>
+                <li><span>Waste Audit Reporting</span> - The Subline generates daily audits for the owner.  Integrating daily auditing into routine kitchen operations means that responsible waste prevention will become part of the restaurant's culture and will have implications both socially and on the bottom line.</li>
+                <li><span>Trend Identification</span> - Trends happen in cycles.  The Subline helps identify these trends, such as seasonal changes in menu demand, supply pricing, and local supply shortages, to give owners the information they need for inventory management.</li>
+            </ul>
+        </div>
+
+        <% include ../shared/loader %>
+
+        <script>
+            let banner = document.getElementById("banner");
+            
+            let timer = setTimeout(()=>{
+                if(banner !== null) banner.style.display = "none";
+            }, 10000);
+
+            document.getElementById("bannerButton").onclick = ()=>{
+                banner.style.display = "none";
+                clearTimeout(timer);
+            };
+        </script>
+    </body>
+</html>

+ 62 - 0
views/otherPages/login.ejs

@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8">
+        <title>The Subline</title>
+        <link rel="icon" type="img/png" href="/shared/images/logo.png">
+        <link rel="stylesheet" href="/otherPages/style.css">
+        <link rel="stylesheet" href="/shared/shared.css">
+        <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet">
+    </head>
+    <body id="login">
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        </div>
+
+        <% if(banner !== undefined){ %>
+            <% include ../shared/banner.ejs %>
+        <% } %>
+
+        <form action="/login" method="post">
+            <h1> Welcome Back </h1>
+    
+            <label>Email
+                <input type="text" name="email" type="email" required>
+            </label>
+    
+            <label>Password
+                <input type="password" name="password" required>
+            </label>
+    
+            <input id="signIn" type="submit" value="Sign In with Email">
+    
+            <!-- <h1>OR</h1>
+    
+            <a class="button buttonWithBorder" href="/cloverlogin">Sign In with Clover</a>
+    
+            <a class="button buttonWithBorder" href="/squarelogin">Sign In with Square</a> -->
+            
+            <a href="/reset/email" class="link">Forgot your password?</a>
+    
+            <a class="link" href="/register">Need to register?</a>
+        </form>
+
+        <% include ../shared/footer %>
+
+        <script>
+            let banner = document.getElementById("banner");
+            
+            let timer = setTimeout(()=>{
+                if(banner !== null) banner.style.display = "none";
+            }, 10000);
+
+            document.getElementById("bannerButton").onclick = ()=>{
+                banner.style.display = "none";
+                clearTimeout(timer);
+            };
+        </script>
+    </body>
+</html>

+ 77 - 0
views/otherPages/register.ejs

@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <title>The Subline</title>
+        <link rel="icon" type="img/png" href="/shared/images/logo.png">
+        <link rel="stylesheet" href="/otherPages/style.css">
+        <link rel="stylesheet" href="/shared/shared.css">
+        <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet">
+    </head>
+    <body id="register">
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        </div>
+
+        <% if(banner !== undefined){ %>
+            <% include ../shared/banner.ejs %>
+        <% } %>
+
+        <form action="/merchant/create/none" method="post">
+            <h1> Join The Subline </h1>
+
+            <label id="nameLabel">Restaurant Name
+                <input type="text" name="name" required>
+            </label>
+    
+            <label>Email
+                <input type="text" name="email" required>
+            </label>
+    
+            <label>Password
+                <input type="password" name="password" required>
+            </label>
+    
+            <label>Confirm Password
+                <input type="password" name="confirmPassword" required>
+            </label>
+    
+            <label id="agreement">
+                <input type="checkbox" name="agree">
+
+                <p>I agree to the 
+                    <a class="link" href="/privacy">Privacy Policy</a> and the 
+                    <a class="link" href="/terms">Terms and Conditions</a>
+                </p>
+            </label>
+
+            <input class="buttonDisabled" type="submit" value="Sign Up with Email">
+
+            <!-- <h1>OR</h1>
+
+            <a class="button buttonWithBorder" href="/cloverlogin">Sign Up with Clover</a>
+
+            <a class="button buttonWithBorder" href="/squarelogin">Sign Up with Square</a> -->
+
+            <a class="link" href="/login">Already have an account? Please Sign In</a>
+        </form>
+
+        <% include ../shared/footer %>
+
+        <script>
+            let banner = document.getElementById("banner");
+            
+            let timer = setTimeout(()=>{
+                if(banner !== null) banner.style.display = "none";
+            }, 10000);
+
+            document.getElementById("bannerButton").onclick = ()=>{
+                banner.style.display = "none";
+                clearTimeout(timer);
+            };
+        </script>
+    </body>
+</html>

+ 229 - 0
views/otherPages/style.css

@@ -0,0 +1,229 @@
+form{
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    border: 2px solid rgb(0, 27, 45);
+    padding: 30px;
+    border-radius: 10px;
+    background: rgb(240, 252, 255);
+    font-size: 16px;
+    margin-top: 40px;
+    width: 550px;
+    justify-content: space-evenly;
+    height: auto;
+}
+
+    input[type=text], select {
+        width: 100%;
+        padding: 12px 20px;
+        margin: 8px 0;
+        display: inline-block;
+        border: 1px solid #ccc;
+        border-radius: 4px;
+        box-sizing: border-box;
+        padding-bottom: 16px;
+        font-size: 16px;
+    }
+
+    input[type=text], input[type=password], select {
+        width: 100%;
+        padding: 12px 20px;
+        margin: 8px 0;
+        display: inline-block;
+        border: 1px solid #ccc;
+        border-radius: 4px;
+        box-sizing: border-box;
+        font-size: 16px;
+    }
+    
+    input[type=submit] {
+        width: 100%;
+        background-color:rgb(255, 99, 107);
+        color: white;
+        padding: 14px 20px;
+        margin: 8px 0;
+        border: none;
+        border-radius: 4px;
+        cursor: pointer;
+        font-size: 22px;
+    }
+
+    input[type=submit]:hover {
+        background: rgb(243, 77, 86);
+        color: white;
+    }
+
+    input[type=text]:focus, input[type=password]:focus {
+        border: 2px solid #555;
+        outline: none;
+    }
+
+    input[type=button] {
+        background-color: rgb(255, 99, 107);
+    }
+
+    input[type=button]:hover {
+        background-color:rgb(0, 27, 45);
+    }
+
+#logInButton, #joinButton{
+    color: white;
+}
+
+/* Public Strand */
+#publicStrand{
+    display: flex;
+    flex-direction: column;
+}
+
+    .main-background{
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        background-image: url("/shared/images/backgroundImg01.jpg");
+        background-repeat: no-repeat;
+        background-size: cover;
+        height: 88vh;
+        width: 100%;
+        color: rgb(0, 27, 45);
+    }
+
+        .public-buttons{
+            position: absolute;
+            right: 50px;
+            top: 80px;
+        }
+            
+            .public-buttons *{
+                margin: 10px;
+            }
+
+        .logo-text{
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            align-items: center;
+            max-width: 50vw;
+            text-align: center;
+        }
+
+            .logo-text img{
+                max-height: 25vh;
+                margin-bottom: 50px;
+            }
+
+            .logo-text p{
+                font-weight: bold;
+                font-size: 25px;
+                color: rgb(255, 99, 107);
+            }
+    
+    .more-info{
+        background: rgb(0, 27, 45);
+        color: rgb(201, 201, 201);
+        text-align: center;
+        padding: 75px;
+        border-top: 5px solid black;
+    }
+
+        .more-info h1{
+            color: rgb(255, 99, 107);
+            margin: 25px;
+            font-size: 45px;
+        }
+
+        .more-info p{
+            font-size: 25px;
+            color: rgb(201, 201, 201);
+            text-align: justify;
+            padding: 10px;
+        }
+
+        .more-info li{
+            font-size: 20px;
+            color: rgb(201, 201, 201);
+            text-align: justify;
+            padding: 10px;
+        }
+
+        .more-info span{
+            font-weight: bold;
+            color: rgb(255, 99, 107);
+        }
+
+/*
+Login Page
+*/
+#login{
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    background-image: url("/shared/images/backgroundImg01.jpg");
+    background-repeat: no-repeat;
+    background-size: cover;
+    height: 91.5vh;
+}
+
+/*
+RegisterPage
+*/
+#register{
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    background-image: url("/shared/images/backgroundImg01.jpg");
+    background-repeat: no-repeat;
+    background-size: cover;
+    height: 91.5vh;
+}
+
+    #agreement{
+        display: flex;
+    }
+
+        #agreement input{
+            margin-right: 15px;
+        }
+
+/*
+Footer Partial
+*/
+.spacer{
+    flex: 1;
+}
+
+.footer{
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 10px;
+    background: rgb(240, 252, 255);
+    width: 100%;
+    box-sizing: border-box;
+}
+
+    .footer > *{
+        margin: 5px;
+    }
+
+    .footer div > *{
+        margin: 0 25px;
+    }
+
+@media screen and (max-width: 600px){
+    .logo-text img{
+        max-height: 20vh;
+    }
+
+    .logo-text p{
+        font-size: 20px;
+    }
+
+    .more-info{
+        padding: 25px;
+    }
+
+    .public-buttons{
+        right: 25vw;
+    }
+}

+ 14 - 7
views/passwordResetPages/email.ejs

@@ -10,7 +10,20 @@
         <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet"> 
     </head>
     <body>
-        <% include ../shared/header %>
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        
+            <div class="headerEnd">
+                <a id="helpLink" class="headerHide" href="/help">HELP</a>
+        
+                <a id="privacyPolicyLink" class="headerHide" href="/privacy">PRIVACY POLICY</a>
+        
+                <a id="termsLink" class="headerHide" href="/terms">TERMS AND CONDITIONS</a>        
+            </div>
+        </div>
 
         <h1 class="title">Enter your email to reset your password</h1>
 
@@ -21,11 +34,5 @@
         </form>
 
         <p class="text">An email will be sent to you shortly with instructions on reseting your password.  If you don't recieve an email within one minute, then try again.</p>
-
-        <script>
-            document.getElementById("helpLink").style.display = "block";
-            document.getElementById("privacyPolicyLink").style.display = "block";
-            document.getElementById("termsLink").style.display = "block";
-        </script>
     </body>
 </html>

+ 28 - 4
views/passwordResetPages/password.ejs

@@ -10,7 +10,24 @@
         <link href="https://fonts.googleapis.com/css?family=Saira&display=swap" rel="stylesheet"> 
     </head>
     <body>
-        <% include ../shared/header %>
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        
+            <div class="headerEnd">
+                <a id="helpLink" class="headerHide" href="/help">HELP</a>
+        
+                <a id="privacyPolicyLink" class="headerHide" href="/privacy">PRIVACY POLICY</a>
+        
+                <a id="termsLink" class="headerHide" href="/terms">TERMS AND CONDITIONS</a>        
+            </div>
+        </div>
+
+        <% if(banner !== undefined){ %>
+            <% include ../shared/banner.ejs %>
+        <% } %>
 
         <h1 class="title">Reset Password</h1>
 
@@ -27,9 +44,16 @@
         </form>
 
         <script>
-            document.getElementById("helpLink").style.display = "block";
-            document.getElementById("privacyPolicyLink").style.display = "block";
-            document.getElementById("termsLink").style.display = "block";
+            let banner = document.getElementById("banner");
+            
+            let timer = setTimeout(()=>{
+                if(banner !== null) banner.style.display = "none";
+            }, 10000);
+
+            document.getElementById("bannerButton").onclick = ()=>{
+                banner.style.display = "none";
+                clearTimeout(timer);
+            };
         </script>
     </body>
 </html>

+ 30 - 34
views/shared/banner.ejs

@@ -1,34 +1,30 @@
-<div id="banner" class="banner"></div>
-
-<script>
-    let banner = {
-        errorList: [],
-        notificationList: [],
-
-        createNotification: function(value){
-            let banner = document.getElementById("banner");
-            banner.style.display = "flex";
-            banner.innerText = value;
-            banner.classList.add("notification");
-
-
-            setTimeout(()=>{
-                banner.style.display = "none";
-                banner.classList.remove("notification");
-            }, 10000);
-        },
-
-        createError: function(value){
-            let banner = document.getElementById("banner");
-            banner.style.display = "flex";
-            banner.innerText = value;
-            banner.classList.add("error");
-
-
-            setTimeout(()=>{
-                banner.style.display = "none";
-                banner.classList.remove("notification");
-            }, 10000);
-        }
-    }
-</script>
+<div id="banner" class="banner">
+    <div style="background:<%=banner.color%>">
+        <% if(banner.type === "error"){ %>
+            <svg width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                <circle cx="12" cy="12" r="10"></circle>
+                <line x1="15" y1="9" x2="9" y2="15"></line>
+                <line x1="9" y1="9" x2="15" y2="15"></line>
+            </svg>
+        <%}else if(banner.type === "success"){ %>
+            <svg width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                <circle cx="12" cy="12" r="10"></circle>
+                <line x1="12" y1="8" x2="12" y2="12"></line>
+                <line x1="12" y1="16" x2="12.01" y2="16"></line>
+            </svg>
+        <%}else { %>
+            <svg width="50" height="50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
+                <polyline points="22 4 12 14.01 9 11.01"></polyline>
+            </svg>
+        <% } %>
+    </div>
+    <p><%=banner.message%></p>
+    <button id="bannerButton">
+        <svg width="18" height="18" viewBox="0 0 24 24" fill="rgb(200, 0, 0)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+            <circle cx="12" cy="12" r="10"></circle>
+            <line x1="15" y1="9" x2="9" y2="15"></line>
+            <line x1="9" y1="9" x2="15" y2="15"></line>
+        </svg>
+    </button>
+</div>

+ 0 - 18
views/shared/header.ejs

@@ -1,18 +0,0 @@
-<div class="header">
-    <a class="headerStart" href="/" >
-        <img src="/shared/images/logo.png" alt="Subline">
-        <div class='headerLogo'>THE SUBLINE</div>
-    </a>
-
-    <div class="headerEnd">
-        <a id="helpLink" class="headerHide" href="/help">HELP</a>
-
-        <a id="privacyPolicyLink" class="headerHide" href="/privacy">PRIVACY POLICY</a>
-
-        <a id="termsLink" class="headerHide" href="/terms">TERMS AND CONDITIONS</a>
-
-        <button id="logInButton" class="button headerHide" onclick="loginObj.display()">LOG IN</button>
-
-        <button id="joinButton" class="button headerHide" onclick="registerObj.display()">JOIN</button>
-    </div>
-</div>

+ 56 - 98
views/shared/shared.css

@@ -8,6 +8,55 @@ body{
     min-height: 100vh;
 }
 
+/*
+Header
+*/
+.header{
+    display: flex;
+    justify-content: space-between;
+    background: #001b2d;
+    width: 100%;
+    height: 75px;
+}
+
+    .headerStart{
+        display: flex;
+        align-items:center;
+        text-decoration: none;
+        margin-left: 25px;
+        width: 275px;
+    }
+
+        .headerStart img{
+            max-height: 75%;
+        }
+
+        .headerLogo{
+            color: white;
+            font-size: 25px;
+            margin-left: 10px;
+        }
+
+        .mobileHomeButton{
+            display: none;
+        }
+
+    .headerEnd{
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        margin-right: 25px;
+        
+    }
+
+        .headerEnd > *{
+            margin-left: 20px;
+        }
+
+        .headerEnd a{
+            color: rgb(255, 99, 107);
+        }
+
 /* Loader */
 #loaderContainer{
     justify-content: center;
@@ -114,36 +163,21 @@ button::-moz-focus-inner{
     border: 0;
 }
 
-.link {
+.link{
     cursor: pointer;
     color: gray;
     text-decoration: underline;
-    margin-top: 20px;
     text-align: center;
     font-size: 20px;
 }
 
-.link:hover {
-    color: #ff636b;
-    text-decoration: underline;
-    cursor: pointer;
-}
+    .link:hover {
+        color: #ff636b;
+        text-decoration: underline;
+        cursor: pointer;
+    }
+
 
-form{
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
-    border: 2px solid rgb(0, 27, 45);
-    padding: 50px;
-    border-radius: 10px;
-    margin: 10px;
-    background: rgb(240, 252, 255);
-    font-size: 16px;
-    margin-top: 40px;
-    width: 550px;
-    justify-content: space-evenly;
-    height: auto;
-}
 
 .button{
     display: initial;
@@ -239,57 +273,6 @@ form{
     transition: width 1s linear;
 }
 
-/* Header partial */
-.header{
-    display: flex;
-    justify-content: space-between;
-    background: #001b2d;
-    width: 100%;
-    height: 75px;
-}
-
-    .headerHide{
-        display: none;
-    }
-
-    .headerStart{
-        display: flex;
-        align-items:center;
-        text-decoration: none;
-        margin-left: 25px;
-        width: 275px;
-    }
-
-        .headerStart img{
-            max-height: 75%;
-        }
-
-        .headerLogo{
-            color: white;
-            font-size: 25px;
-            margin-left: 10px;
-        }
-
-        .mobileHomeButton{
-            display: none;
-        }
-
-    .headerEnd{
-        display: flex;
-        align-items: center;
-        justify-content: flex-end;
-        margin-right: 25px;
-        
-    }
-
-        .headerEnd > *{
-            margin-left: 15px;
-        }
-
-        .headerEnd a{
-            color: rgb(255, 99, 107);
-        }
-
 /*
 Banner
 */
@@ -343,27 +326,6 @@ Banner
             pointer-events: all;
         }
 
-/* Footer Partial */
-.spacer{
-    flex: 1;
-}
-
-.footer{
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    padding: 10px;
-    background: rgb(240, 252, 255);
-}
-
-    .footer > *{
-        margin: 5px;
-    }
-
-    .footer div > *{
-        margin: 0 25px;
-    }
-
 @media screen and (max-width: 1400px){
     .button{
         font-size: 15px;
@@ -400,8 +362,4 @@ Banner
     .headerStart{
         width: 0;
     }
-}
-
-label {
-    margin-top:20px ;
 }

+ 23 - 5
views/verifyPage/verify.ejs

@@ -10,7 +10,24 @@
         <link rel="stylesheet" href="/verifyPage/verify.css">
     </head>
     <body>
-        <% include ../shared/header %>
+        <div class="header">
+            <a class="headerStart" href="/" >
+                <img src="/shared/images/logo.png" alt="Subline">
+                <div class='headerLogo'>THE SUBLINE</div>
+            </a>
+        
+            <div class="headerEnd">
+                <a id="helpLink" class="headerHide" href="/help">HELP</a>
+        
+                <a id="privacyPolicyLink" class="headerHide" href="/privacy">PRIVACY POLICY</a>
+        
+                <a id="termsLink" class="headerHide" href="/terms">TERMS AND CONDITIONS</a>        
+            </div>
+        </div>
+
+        <% if(banner !== undefined){ %>
+            <% include ../shared/banner.ejs %>
+        <% } %>
 
         <h1 class="title">An email has been sent to <%=email%>.  Use the link in the email to verify your account.</h1>
 
@@ -24,10 +41,11 @@
             <input class="button" type="submit" value="SUBMIT">
         </form>
     </body>
-
+    
     <script>
-        document.getElementById("helpLink").style.display = "block";
-        document.getElementById("privacyPolicyLink").style.display = "block";
-        document.getElementById("termsLink").style.display = "block";
+        setTimeout(()=>{
+            let banner = document.getElementById("banner");
+            if(banner !== null) banner.style.display = "none";
+        }, 10000);
     </script>
 </html>

+ 0 - 13
workspace.html

@@ -1,13 +0,0 @@
-<div id="passwordReset">
-    <header style="width:100%;height:75px;background:rgb(0,27,45);">
-        <img src="https://i.postimg.cc/dQky3vPX/logo.png" alt="Subline Logo" style="height:50px;padding:12px 12px;float:left;">
-
-        <h3 style="color:rgb(255,99,107);font-size:30px;margin:15px 0 0 0;float:left;">THE SUBLINE</h3>
-    </header>
-
-    <h1 style="text-align:center;">Password Resest for ${data.name}</h1>
-
-    <p>Follow the link below to go to reset your password.</p>
-
-    <p>${data.link}</p>
-</div>

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff