Compare commits
	
		
			1 Commits 
		
	
	
		
			develop
			...
			aws_deploy
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 7cd1e49b57 | 1 year ago | 
| Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB | 
| Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB | 
| Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB | 
| @ -0,0 +1,2 @@ | |||||||
|  | /node_modules | ||||||
|  | .env | ||||||
| @ -0,0 +1,131 @@ | |||||||
|  | const Item = require('../models/item') | ||||||
|  | const User = require('../models/user') | ||||||
|  | const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY) | ||||||
|  | const auth = require('../jwt-auth') | ||||||
|  | 
 | ||||||
|  | module.exports.info = (req, res) => { | ||||||
|  | 	let cart = req.body | ||||||
|  | 	let _ids = [] | ||||||
|  | 
 | ||||||
|  | 	for (let i = 0; i < cart.length; i++) { | ||||||
|  | 		_ids.push(cart[i]._id) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	let condition = { '_id': { $in: _ids } } | ||||||
|  | 
 | ||||||
|  | 	Item.find(condition, (err, items) => { | ||||||
|  | 		let cartInfo = [] | ||||||
|  | 
 | ||||||
|  | 		for (let i = 0; i < items.length; i++) { | ||||||
|  | 			cartInfo.push({ | ||||||
|  | 				_id: items[i]._id, | ||||||
|  | 				name: items[i].name, | ||||||
|  | 				quantity: cart[i].quantity, | ||||||
|  | 				unitPrice: items[i].unitPrice | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		res.status(200).json(cartInfo) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.checkout = (req, res) => { | ||||||
|  | 	let userId = auth.getId(req.headers.authorization) | ||||||
|  | 	let cart = JSON.parse(req.body.cart) | ||||||
|  | 
 | ||||||
|  | 	let _ids = [] | ||||||
|  | 	let orderItems = [] | ||||||
|  | 	let totalPrice = 0 | ||||||
|  | 
 | ||||||
|  | 	for (let i = 0; i < cart.length; i++) { | ||||||
|  | 		_ids.push(cart[i]._id) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Item.find({ '_id': { $in: _ids } }, (err, items) => { | ||||||
|  | 		for (let i = 0; i < items.length; i++) { | ||||||
|  | 			for (let j = 0; j < cart.length; j++) { | ||||||
|  | 				if (items[i]._id == cart[j]._id) { | ||||||
|  | 					orderItems.push({ | ||||||
|  | 						_id: items[i]._id, | ||||||
|  | 						name: items[i].name, | ||||||
|  | 						category: items[i].categoryName, | ||||||
|  | 						quantity: cart[j].quantity, | ||||||
|  | 						unitPrice: items[i].unitPrice | ||||||
|  | 					}) | ||||||
|  | 					totalPrice += (cart[j].quantity * items[i].unitPrice) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		User.findOne({ _id: userId }).exec().then((user) => { | ||||||
|  | 			let newOrderDoc = user.orders.create({ | ||||||
|  | 				datetimeRecorded: new Date(), | ||||||
|  | 				paymentMode: 'Cash on Delivery', | ||||||
|  | 				totalPrice: totalPrice, | ||||||
|  | 				items: orderItems | ||||||
|  | 			}) | ||||||
|  | 			user.orders.push(newOrderDoc) | ||||||
|  | 			user.save() | ||||||
|  | 		}) | ||||||
|  | 	}).catch((err) => { | ||||||
|  | 		res.status(400).json({ error: err.message }) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	res.json({ result: 'success' }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.checkoutStripe = (req, res) => { | ||||||
|  | 	let userId = auth.getId(req.headers.authorization) | ||||||
|  | 	let cart = JSON.parse(req.body.cart) | ||||||
|  | 
 | ||||||
|  | 	let _ids = [] | ||||||
|  | 	let orderItems = [] | ||||||
|  | 	let totalPrice = 0 | ||||||
|  | 
 | ||||||
|  | 	for (let i = 0; i < cart.length; i++) { | ||||||
|  | 		console.log(cart[i]) | ||||||
|  | 		_ids.push(cart[i]._id) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	Item.find({ '_id': { $in: _ids } }, (err, items) => { | ||||||
|  | 		for (let i = 0; i < items.length; i++) { | ||||||
|  | 			for (let j = 0; j < cart.length; j++) { | ||||||
|  | 				if (items[i]._id == cart[j]._id) { | ||||||
|  | 					orderItems.push({ | ||||||
|  | 						_id: items[i]._id, | ||||||
|  | 						name: items[i].name, | ||||||
|  | 						category: items[i].categoryName, | ||||||
|  | 						quantity: cart[j].quantity, | ||||||
|  | 						unitPrice: items[i].unitPrice | ||||||
|  | 					}) | ||||||
|  | 					totalPrice += (cart[j].quantity * items[i].unitPrice) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		stripe.charges.create({ | ||||||
|  | 			amount: totalPrice * 100, | ||||||
|  | 			description: null, | ||||||
|  | 			currency: 'php', | ||||||
|  | 			customer: auth.getStripeCustomerId(req.headers.authorization) | ||||||
|  | 		}, (err, charge) => { | ||||||
|  | 			if (err) { return res.status(400).json({ error: err.message }) } | ||||||
|  | 
 | ||||||
|  | 			User.findOne({ _id: userId }).exec().then((user) => { | ||||||
|  | 				let newOrderDoc = user.orders.create({ | ||||||
|  | 					datetimeRecorded: new Date(), | ||||||
|  | 					paymentMode: 'Cash on Delivery', | ||||||
|  | 					stripeChargeId: charge.id, | ||||||
|  | 					totalPrice: totalPrice, | ||||||
|  | 					items: orderItems | ||||||
|  | 				}) | ||||||
|  | 				user.orders.push(newOrderDoc) | ||||||
|  | 				user.save() | ||||||
|  | 			}) | ||||||
|  | 
 | ||||||
|  | 			res.status(200).json({ result: 'success' }) | ||||||
|  | 		}) | ||||||
|  | 	}).catch((err) => { | ||||||
|  | 		res.status(400).json({ error: err.message }) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @ -0,0 +1,9 @@ | |||||||
|  | const Category = require('../models/category') | ||||||
|  | 
 | ||||||
|  | module.exports.all = (req, res) => { | ||||||
|  | 	Category.find().then((categories) => { | ||||||
|  | 		res.status(200).json(categories) | ||||||
|  | 	}).catch(err => { | ||||||
|  | 		res.status(400).json({ error: err.message }) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @ -0,0 +1,49 @@ | |||||||
|  | const Item = require('../models/item') | ||||||
|  | 
 | ||||||
|  | module.exports.all = (req, res) => { | ||||||
|  | 	Item.find({ isArchived: false }).then((items) => { | ||||||
|  | 		res.status(200).json(items) | ||||||
|  | 	}).catch(err => { | ||||||
|  | 		res.status(400).json({ error: err.message }) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.detail = (req, res) => { | ||||||
|  | 	let condition = { _id: req.params._id } | ||||||
|  | 
 | ||||||
|  | 	Item.findOne(condition).exec().then((item) => { | ||||||
|  | 		res.status(200).json(item) | ||||||
|  | 	}).catch(err => { | ||||||
|  |         res.status(400).json({ error: err.message }) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.new = (req, res) => { | ||||||
|  | 	Item.create(req.body).then((item) => { | ||||||
|  | 		res.status(200).json({ result: 'success' }) | ||||||
|  | 	}).catch((err) => { | ||||||
|  | 		res.status(400).json({ error: err.message }) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.update = (req, res) => { | ||||||
|  | 	let searchParam = { _id: req.body._id } | ||||||
|  | 	let updateParam = req.body | ||||||
|  | 
 | ||||||
|  | 	Item.findOneAndUpdate(searchParam, updateParam, (item) => { | ||||||
|  | 		res.status(200).json({ result: 'success' }) | ||||||
|  | 	}).catch((err) => {	 | ||||||
|  | 		res.status(400).json({ error: err.message }) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.delete = (req, res) => { | ||||||
|  | 	let searchParam = { _id: req.body._id } | ||||||
|  | 	let updateParam = { isArchived: true } | ||||||
|  | 
 | ||||||
|  | 	Item.findOneAndUpdate(searchParam, updateParam, (item) => { | ||||||
|  | 		res.status(200).json({ result: 'success' }) | ||||||
|  | 	}).catch((err) => {	 | ||||||
|  | 		res.status(400).json({ error: err.message }) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @ -0,0 +1,14 @@ | |||||||
|  | const User = require('../models/user') | ||||||
|  | const auth = require('../jwt-auth') | ||||||
|  | 
 | ||||||
|  | module.exports.user = (req, res) => { | ||||||
|  | 	let condition = { | ||||||
|  | 		_id: auth.getId(req.headers.authorization) | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	User.findOne(condition).exec().then((user) => { | ||||||
|  | 		res.status(200).json(user.orders) | ||||||
|  | 	}).catch((err) => { | ||||||
|  | 		res.status(400).json({ error: err.message }) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @ -0,0 +1,54 @@ | |||||||
|  | const User = require('../models/user') | ||||||
|  | const stripe = require('stripe')('sk_test_7UMpcYtFni9koYkpFIruYXmT00EzMN9ci9') | ||||||
|  | const bcrypt = require('bcrypt') | ||||||
|  | const auth = require('../jwt-auth'); | ||||||
|  | 
 | ||||||
|  | module.exports.login = (req, res) => { | ||||||
|  | 	let condition = { email: req.body.email } | ||||||
|  | 
 | ||||||
|  | 	User.findOne(condition).exec().then((user) => { | ||||||
|  |         bcrypt.compare(req.body.password, user.password, (err, result) => { | ||||||
|  |             if (err) { return res.status(400).json({ error: err.message }) } | ||||||
|  | 
 | ||||||
|  |             if (result) { | ||||||
|  |                 return res.status(200).json({ | ||||||
|  |                     result: 'authenticated', | ||||||
|  |                     role: user.role, | ||||||
|  |                     name: user.name, | ||||||
|  |                     token: auth.createToken(user.toObject()) | ||||||
|  |                 }) | ||||||
|  |             } else { | ||||||
|  |                 return res.status(400).json({ error: err.message }) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     }).catch(err => { | ||||||
|  |         return res.status(400).json({ error: err.message }) | ||||||
|  |     }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.register = (req, res) => { | ||||||
|  | 	bcrypt.hash(req.body.password, 10, (err, hash) => { | ||||||
|  | 		// In case encryption encountered an error.
 | ||||||
|  |         if (err) { return res.status(400).json({ error: err.message }) } | ||||||
|  | 
 | ||||||
|  |         stripe.customers.create({ | ||||||
|  |             email: req.body.emailAddress, | ||||||
|  |             source: 'tok_mastercard', | ||||||
|  |         }, (err, customer) => { | ||||||
|  |         	// In case Stripe customer creation failed.
 | ||||||
|  |             if (err) { return res.status(400).json({ error: err.message }) } | ||||||
|  |              | ||||||
|  |             // Provide additional request body information.
 | ||||||
|  |             req.body.password = hash | ||||||
|  |             req.body.stripeCustomerId = customer.id | ||||||
|  |             req.body.role = 'customer' | ||||||
|  | 
 | ||||||
|  |             // Create a new user.
 | ||||||
|  |             User.create(req.body).then((user) => { | ||||||
|  |                 res.status(200).json({ result: 'success' }) | ||||||
|  |             }).catch(err => { | ||||||
|  |                 res.status(400).json({ error: err.message }) | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |     }) | ||||||
|  | } | ||||||
| @ -0,0 +1,50 @@ | |||||||
|  | // Declare dependencies.
 | ||||||
|  | 
 | ||||||
|  | const express = require('express') | ||||||
|  | const bodyParser = require('body-parser') | ||||||
|  | const mongoose = require('mongoose') | ||||||
|  | 
 | ||||||
|  | require('dotenv').config() | ||||||
|  | 
 | ||||||
|  | // Declare constants.
 | ||||||
|  | 
 | ||||||
|  | const app = express() | ||||||
|  | const defaultPort = 4000 | ||||||
|  | const designatedPort = process.env.PORT || defaultPort | ||||||
|  | 
 | ||||||
|  | // Declare middlewares.
 | ||||||
|  | 
 | ||||||
|  | app.use(bodyParser.json()) | ||||||
|  | 
 | ||||||
|  | // Enable the Cross Origin Resource Sharing (CORS).
 | ||||||
|  | 
 | ||||||
|  | app.use(function(req, res, next) { | ||||||
|  | 	res.header('Access-Control-Allow-Origin', '*') | ||||||
|  | 	res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization') | ||||||
|  | 	res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS') | ||||||
|  | 	next() | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // Declare database connection.
 | ||||||
|  | 
 | ||||||
|  | mongoose.connect(process.env.MONGODB_SRV, {  | ||||||
|  | 	useNewUrlParser:true,  | ||||||
|  | 	useCreateIndex: true, | ||||||
|  | 	useUnifiedTopology: true  | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | mongoose.connection.once('open', () => { | ||||||
|  | 	console.log('Connection to MongoDB Atlas has been successfully tested.') | ||||||
|  | }).catch(function(err) { | ||||||
|  | 	console.log(err) | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | // Declare routes.
 | ||||||
|  | 
 | ||||||
|  | app.use('/api', require('./routes')) | ||||||
|  | 
 | ||||||
|  | // Listen to server requests.
 | ||||||
|  | 
 | ||||||
|  | app.listen(designatedPort, () => { | ||||||
|  | 	console.log('Node.js backend now online in port ' + designatedPort) | ||||||
|  | }) | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | const jwt = require('jsonwebtoken') | ||||||
|  | const secret = 'MERN-Ecommerce' | ||||||
|  | 
 | ||||||
|  | module.exports.verify = (req, res, next) => { | ||||||
|  |     let header = req.headers.authorization | ||||||
|  |     console.log(req.headers.authorization) | ||||||
|  |      | ||||||
|  |     if (typeof header !== 'undefined') { | ||||||
|  |         req.token = header.slice(7, header.length) | ||||||
|  |          | ||||||
|  |         jwt.verify(req.token, secret, (err, data) => { | ||||||
|  |             (err) ? res.json({ error: 'token-auth-failed' }) : next() | ||||||
|  |         }) | ||||||
|  |     } else { | ||||||
|  |         res.json({ error: 'undefined-auth-header' }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.createToken = (user) => { | ||||||
|  |     let data = { | ||||||
|  |         _id: user._id,  | ||||||
|  |         email: user.email,  | ||||||
|  |         role: user.role,  | ||||||
|  |         stripeCustomerId: user.stripeCustomerId | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return jwt.sign(data, secret, { expiresIn: '2h' }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.getId = (authToken) => { | ||||||
|  |     return getPayload(authToken)._id  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.getStripeCustomerId = (authToken) => { | ||||||
|  |     return getPayload(authToken).stripeCustomerId  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports.getRole = (authToken) => { | ||||||
|  |     return getPayload(authToken).role  | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | getPayload = (authToken) => { | ||||||
|  |     return jwt.decode(authToken.slice(7, authToken.length), {complete: true}).payload | ||||||
|  | } | ||||||
| @ -0,0 +1,12 @@ | |||||||
|  | const mongoose = require('mongoose') | ||||||
|  | const Schema = mongoose.Schema | ||||||
|  | 
 | ||||||
|  | const CategorySchema = new Schema({ | ||||||
|  | 	name: { | ||||||
|  | 		type: String | ||||||
|  | 	} | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const Category = mongoose.model('category', CategorySchema) | ||||||
|  | 
 | ||||||
|  | module.exports = Category | ||||||
| @ -0,0 +1,32 @@ | |||||||
|  | const mongoose = require('mongoose') | ||||||
|  | const Schema = mongoose.Schema | ||||||
|  | 
 | ||||||
|  | const ItemSchema = new Schema({ | ||||||
|  | 	name: { | ||||||
|  | 		type: String, | ||||||
|  | 		required: [true, 'Item name is required.'] | ||||||
|  | 	}, | ||||||
|  | 	description: { | ||||||
|  | 		type: String, | ||||||
|  | 		required: [true, 'Description is required.'] | ||||||
|  | 	}, | ||||||
|  | 	unitPrice: { | ||||||
|  | 		type: Number, | ||||||
|  | 		required: [true, 'Unit price is required.'] | ||||||
|  | 	}, | ||||||
|  | 	imageLocation: { | ||||||
|  | 		type: String | ||||||
|  | 	}, | ||||||
|  | 	categoryName: { | ||||||
|  | 		type: String, | ||||||
|  | 		required: [true, 'Category name is required.'] | ||||||
|  | 	}, | ||||||
|  | 	isArchived: { | ||||||
|  | 		type: Boolean, | ||||||
|  | 		required: [true, 'Archive status is required.'] | ||||||
|  | 	} | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const Item = mongoose.model('item', ItemSchema) | ||||||
|  | 
 | ||||||
|  | module.exports = Item | ||||||
| @ -0,0 +1,71 @@ | |||||||
|  | const mongoose = require('mongoose') | ||||||
|  | const Schema = mongoose.Schema | ||||||
|  | 
 | ||||||
|  | const UserSchema = new Schema({ | ||||||
|  |     name: { | ||||||
|  |         type: String, | ||||||
|  |         required: [true, 'Name is required.'] | ||||||
|  |     }, | ||||||
|  |     email: { | ||||||
|  |         type: String, | ||||||
|  |         required: [true, 'Email is required.'] | ||||||
|  |     }, | ||||||
|  |     password: { | ||||||
|  |         type: String, | ||||||
|  |         required: [true, 'Pasword is required.'] | ||||||
|  |     }, | ||||||
|  |     role: { | ||||||
|  |         type: String, | ||||||
|  |         default: 'customer' | ||||||
|  |     }, | ||||||
|  |     stripeCustomerId: { | ||||||
|  |         type: String, | ||||||
|  |         required: [true, 'Stripe customer ID is required.'] | ||||||
|  |     }, | ||||||
|  |     orders: [ | ||||||
|  |         { | ||||||
|  |             datetimeRecorded: { | ||||||
|  |                 type: Date, | ||||||
|  |                 default: new Date() | ||||||
|  |             }, | ||||||
|  |             paymentMode: { | ||||||
|  |                 type: String, | ||||||
|  |                 required: [true, 'Payment mode is required.'] | ||||||
|  |             }, | ||||||
|  |             totalPrice: { | ||||||
|  |                 type: Number, | ||||||
|  |                 required: [true, 'Total price is required.'] | ||||||
|  |             }, | ||||||
|  |             stripeChargeId: { | ||||||
|  |                 type: String, | ||||||
|  |                 default: null | ||||||
|  |             }, | ||||||
|  |             items: [ | ||||||
|  |                 { | ||||||
|  |                     name: { | ||||||
|  |                         type: String, | ||||||
|  |                         required: [true, 'Name is required.'] | ||||||
|  |                     }, | ||||||
|  |                     category: { | ||||||
|  |                         type: String, | ||||||
|  |                         required: [true, 'Category is required.'] | ||||||
|  |                     }, | ||||||
|  |                     quantity: { | ||||||
|  |                         type: Number, | ||||||
|  |                         require: [true, 'Quantity is required.'] | ||||||
|  |                     }, | ||||||
|  |                     unitPrice: { | ||||||
|  |                         type: Number, | ||||||
|  |                         require: [true, 'Unit price is required.'] | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             ] | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | }, { | ||||||
|  |     timestamps: true | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | const User = mongoose.model('user', UserSchema) | ||||||
|  | 
 | ||||||
|  | module.exports = User | ||||||
| @ -0,0 +1,26 @@ | |||||||
|  | { | ||||||
|  |   "name": "mern-ecommerce", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "description": "", | ||||||
|  |   "main": "index.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "test": "echo \"Error: no test specified\" && exit 1", | ||||||
|  |     "start": "nodemon index.js" | ||||||
|  |   }, | ||||||
|  |   "author": "Sylvan Q. Cahilog", | ||||||
|  |   "license": "ISC", | ||||||
|  |   "dependencies": { | ||||||
|  |     "bcrypt": "^5.0.1", | ||||||
|  |     "body-parser": "^1.19.0", | ||||||
|  |     "cors": "^2.8.5", | ||||||
|  |     "dotenv": "^10.0.0", | ||||||
|  |     "express": "^4.17.1", | ||||||
|  |     "express-session": "^1.16.2", | ||||||
|  |     "jsonwebtoken": "^8.5.1", | ||||||
|  |     "mongoose": "^5.6.1", | ||||||
|  |     "multer": "^1.4.1", | ||||||
|  |     "nodemon": "^1.19.1", | ||||||
|  |     "session-file-store": "^1.3.0", | ||||||
|  |     "stripe": "^7.4.0" | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -0,0 +1,32 @@ | |||||||
|  | // Declare constants.
 | ||||||
|  | 
 | ||||||
|  | const express = require('express') | ||||||
|  | const router = express.Router() | ||||||
|  | const auth = require('./jwt-auth') | ||||||
|  | 
 | ||||||
|  | const ItemController = require('./controllers/item') | ||||||
|  | const CategoryController = require('./controllers/category') | ||||||
|  | const UserController = require('./controllers/user') | ||||||
|  | const OrderController = require('./controllers/order') | ||||||
|  | const CartController = require('./controllers/cart') | ||||||
|  | 
 | ||||||
|  | // Route declarations.
 | ||||||
|  | 
 | ||||||
|  | router.get('/items', ItemController.all) | ||||||
|  | router.get('/item/:_id', ItemController.detail) | ||||||
|  | router.post('/item', ItemController.new) | ||||||
|  | router.put('/item', ItemController.update) | ||||||
|  | router.delete('/item', ItemController.delete) | ||||||
|  | 
 | ||||||
|  | router.get('/categories', CategoryController.all) | ||||||
|  | 
 | ||||||
|  | router.get('/orders/user', auth.verify, OrderController.user) | ||||||
|  | 
 | ||||||
|  | router.post('/user/login', UserController.login) | ||||||
|  | router.post('/user/register', UserController.register) | ||||||
|  | 
 | ||||||
|  | router.post('/cart/info', CartController.info) | ||||||
|  | router.post('/cart/checkout', CartController.checkout) | ||||||
|  | router.post('/cart/checkout-stripe', CartController.checkoutStripe) | ||||||
|  | 
 | ||||||
|  | module.exports = router | ||||||
| @ -0,0 +1,24 @@ | |||||||
|  | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||||||
|  | 
 | ||||||
|  | # dependencies | ||||||
|  | /node_modules | ||||||
|  | /.pnp | ||||||
|  | .pnp.js | ||||||
|  | 
 | ||||||
|  | # testing | ||||||
|  | /coverage | ||||||
|  | 
 | ||||||
|  | # production | ||||||
|  | /build | ||||||
|  | 
 | ||||||
|  | # misc | ||||||
|  | .DS_Store | ||||||
|  | .env | ||||||
|  | .env.local | ||||||
|  | .env.development.local | ||||||
|  | .env.test.local | ||||||
|  | .env.production.local | ||||||
|  | 
 | ||||||
|  | npm-debug.log* | ||||||
|  | yarn-debug.log* | ||||||
|  | yarn-error.log* | ||||||
| @ -0,0 +1,36 @@ | |||||||
|  | { | ||||||
|  |   "name": "frontend", | ||||||
|  |   "version": "0.1.0", | ||||||
|  |   "private": true, | ||||||
|  |   "dependencies": { | ||||||
|  |     "bootstrap": "^4.3.1", | ||||||
|  |     "jquery": "^3.4.1", | ||||||
|  |     "popper.js": "^1.15.0", | ||||||
|  |     "query-string": "^6.8.1", | ||||||
|  |     "react": "^16.8.6", | ||||||
|  |     "react-dom": "^16.8.6", | ||||||
|  |     "react-router-dom": "^5.0.1", | ||||||
|  |     "react-scripts": "3.0.1" | ||||||
|  |   }, | ||||||
|  |   "scripts": { | ||||||
|  |     "start": "react-scripts start", | ||||||
|  |     "build": "react-scripts build", | ||||||
|  |     "test": "react-scripts test", | ||||||
|  |     "eject": "react-scripts eject" | ||||||
|  |   }, | ||||||
|  |   "eslintConfig": { | ||||||
|  |     "extends": "react-app" | ||||||
|  |   }, | ||||||
|  |   "browserslist": { | ||||||
|  |     "production": [ | ||||||
|  |       ">0.2%", | ||||||
|  |       "not dead", | ||||||
|  |       "not op_mini all" | ||||||
|  |     ], | ||||||
|  |     "development": [ | ||||||
|  |       "last 1 chrome version", | ||||||
|  |       "last 1 firefox version", | ||||||
|  |       "last 1 safari version" | ||||||
|  |     ] | ||||||
|  |   } | ||||||
|  | } | ||||||
| After Width: | Height: | Size: 3.8 KiB | 
| @ -0,0 +1,24 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | 
 | ||||||
|  | <html lang="en"> | ||||||
|  | 
 | ||||||
|  | 	<head> | ||||||
|  | 
 | ||||||
|  | 		<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"/> | ||||||
|  | 
 | ||||||
|  | 		<meta charset="utf-8"/> | ||||||
|  | 		<meta name="viewport" content="width=device-width, initial-scale=1"/> | ||||||
|  | 		<meta name="theme-color" content="#000000"/> | ||||||
|  | 
 | ||||||
|  | 		<title>React App</title> | ||||||
|  | 
 | ||||||
|  | 	</head> | ||||||
|  | 
 | ||||||
|  | 	<body> | ||||||
|  | 
 | ||||||
|  | 		<noscript>You need to enable JavaScript to run this app.</noscript> | ||||||
|  | 		<div id="root" class="pt-5 mb-5 pb-3"></div> | ||||||
|  | 		 | ||||||
|  | 	</body> | ||||||
|  | 
 | ||||||
|  | </html> | ||||||
| @ -0,0 +1,15 @@ | |||||||
|  | { | ||||||
|  |   "short_name": "React App", | ||||||
|  |   "name": "Create React App Sample", | ||||||
|  |   "icons": [ | ||||||
|  |     { | ||||||
|  |       "src": "favicon.ico", | ||||||
|  |       "sizes": "64x64 32x32 24x24 16x16", | ||||||
|  |       "type": "image/x-icon" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "start_url": ".", | ||||||
|  |   "display": "standalone", | ||||||
|  |   "theme_color": "#000000", | ||||||
|  |   "background_color": "#ffffff" | ||||||
|  | } | ||||||
| @ -0,0 +1 @@ | |||||||
|  | module.exports.url = process.env.REACT_APP_API_URL | ||||||
| @ -0,0 +1,95 @@ | |||||||
|  | import React, { Component, Fragment } from 'react' | ||||||
|  | import { BrowserRouter, Route, Switch } from 'react-router-dom' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | import Footer from './Footer' | ||||||
|  | import Header from './Header' | ||||||
|  | 
 | ||||||
|  | import Menu from './Menu' | ||||||
|  | import Register from './Register' | ||||||
|  | import Login from './Login' | ||||||
|  | import Logout from './Logout' | ||||||
|  | import ItemCreate from './ItemCreate' | ||||||
|  | import ItemUpdate from './ItemUpdate' | ||||||
|  | import ItemDelete from './ItemDelete' | ||||||
|  | import Cart from './Cart' | ||||||
|  | import Transactions from './Transactions' | ||||||
|  | 
 | ||||||
|  | class App extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			name: localStorage.getItem('name'), | ||||||
|  | 			role: localStorage.getItem('role'), | ||||||
|  | 			token: localStorage.getItem('token'), | ||||||
|  | 			cartQuantity: 0 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	componentDidMount() { | ||||||
|  | 		this.getTotalCartQuantity() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	setUser() { | ||||||
|  | 		this.setState({ | ||||||
|  | 			name: localStorage.getItem('name'), | ||||||
|  | 			role: localStorage.getItem('role'), | ||||||
|  | 			token: localStorage.getItem('token') | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	unsetUser() { | ||||||
|  | 		localStorage.clear() | ||||||
|  | 
 | ||||||
|  | 		this.setState({ | ||||||
|  | 			name: localStorage.getItem('name'), | ||||||
|  | 			role: localStorage.getItem('role'), | ||||||
|  | 			token: localStorage.getItem('token') | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	getTotalCartQuantity() { | ||||||
|  | 		let cartQuantity = 0 | ||||||
|  |         let cart = JSON.parse(localStorage.getItem('cart')) | ||||||
|  | 
 | ||||||
|  | 		if (cart != null) { | ||||||
|  | 			for (let i = 0; i < cart.length; i++) { | ||||||
|  | 				cartQuantity += parseFloat(cart[i].quantity)  | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		this.setState({ cartQuantity: cartQuantity }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		let LoginComponent = (props) => (<Login {...props} setUser={ this.setUser.bind(this) }/>) | ||||||
|  | 		let LogoutComponent = (props) => (<Logout {...props} unsetUser={ this.unsetUser.bind(this) }/>) | ||||||
|  | 		let MenuComponent = (props) => (<Menu {...props} getTotalCartQuantity={ this.getTotalCartQuantity.bind(this) }/>) | ||||||
|  | 		let CartComponent = (props) => (<Cart {...props} getTotalCartQuantity={ this.getTotalCartQuantity.bind(this) }/>) | ||||||
|  | 
 | ||||||
|  | 		return ( | ||||||
|  | 			<Fragment> | ||||||
|  | 				<BrowserRouter> | ||||||
|  | 					<Header token={ this.state.token } name={ this.state.name } role={ this.state.role } cartQuantity={ this.state.cartQuantity }/> | ||||||
|  | 					<Switch> | ||||||
|  | 						<Route exact path='/' render={ MenuComponent }/> | ||||||
|  | 						<Route exact path='/register' component={ Register }/> | ||||||
|  | 						<Route exact path='/login' render={ LoginComponent }/> | ||||||
|  | 						<Route exact path='/logout' render={ LogoutComponent }/> | ||||||
|  | 						<Route exact path='/item-create' component={ ItemCreate }/> | ||||||
|  | 						<Route exact path='/item-update' component={ ItemUpdate }/> | ||||||
|  | 						<Route exact path='/item-delete' component={ ItemDelete }/> | ||||||
|  | 						<Route exact path='/cart' render={ CartComponent }/> | ||||||
|  | 						<Route exact path='/transactions' component={ Transactions }/> | ||||||
|  | 					</Switch> | ||||||
|  | 				</BrowserRouter> | ||||||
|  | 				<Footer/> | ||||||
|  | 			</Fragment> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default App | ||||||
| @ -0,0 +1,239 @@ | |||||||
|  | import React, { Component } from 'react' | ||||||
|  | import { Redirect, Link } from 'react-router-dom' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | document.title = 'Cart' | ||||||
|  | 
 | ||||||
|  | class Cart extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			items: [], | ||||||
|  | 			gotoTransactions: false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	componentWillMount() { | ||||||
|  | 		this.getItems() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	getItems() { | ||||||
|  | 		let payload = { | ||||||
|  | 			method: 'post', | ||||||
|  | 			headers: { | ||||||
|  | 				'Content-Type': 'application/json' | ||||||
|  | 			}, | ||||||
|  | 			body: localStorage.getItem('cart') | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/cart/info', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((cartItems) => { | ||||||
|  | 			console.log(cartItems) | ||||||
|  | 			this.setState({ items: cartItems }) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	emptyCart() { | ||||||
|  | 		localStorage.removeItem('cart') | ||||||
|  | 		this.props.getTotalCartQuantity() | ||||||
|  | 		this.setState({ items: [] }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	proceedToCheckout() { | ||||||
|  | 		let cart = localStorage.getItem('cart') | ||||||
|  | 
 | ||||||
|  | 		let payload = { | ||||||
|  | 			method: 'post', | ||||||
|  | 			headers: { | ||||||
|  | 				'Content-Type': 'application/json', | ||||||
|  | 				'Authorization': localStorage.getItem('token') | ||||||
|  | 			}, | ||||||
|  | 			body: JSON.stringify({ | ||||||
|  | 				cart: cart | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/cart/checkout', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((response) => { | ||||||
|  | 			if (response.result == 'success') { | ||||||
|  | 				this.setState({ gotoTransactions: true }) | ||||||
|  | 				this.emptyCart() | ||||||
|  | 			} else { | ||||||
|  | 				alert(response.error) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	proceedToStripeCheckout() { | ||||||
|  | 		let cart = localStorage.getItem('cart') | ||||||
|  | 
 | ||||||
|  | 		let payload = { | ||||||
|  | 			method: 'post', | ||||||
|  | 			headers: { | ||||||
|  | 				'Content-Type': 'application/json', | ||||||
|  | 				'Authorization': localStorage.getItem('token') | ||||||
|  | 			}, | ||||||
|  | 			body: JSON.stringify({ | ||||||
|  | 				cart: cart | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/cart/checkout-stripe', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((response) => { | ||||||
|  | 			if (response.result == 'success') { | ||||||
|  | 				this.setState({ gotoTransactions: true }) | ||||||
|  | 				this.emptyCart() | ||||||
|  | 			} else { | ||||||
|  | 				alert(response.error) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		if (this.state.gotoTransactions) { | ||||||
|  | 			return <Redirect to='/transactions'/> | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (this.state.items.length == 0) { | ||||||
|  | 			return ( | ||||||
|  | 				<div className="container-fluid mt-3"> | ||||||
|  | 					<h3> No items in cart. </h3> | ||||||
|  | 					<h4> Select a menu item to add to cart from <Link to="/">here</Link>.</h4> | ||||||
|  | 				</div> | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		let totalPrice = 0; | ||||||
|  | 
 | ||||||
|  | 		this.state.items.map((item) => { | ||||||
|  | 			totalPrice += (item.quantity * item.unitPrice) | ||||||
|  | 		}) | ||||||
|  | 
 | ||||||
|  | 		return ( | ||||||
|  | 			<div className="container-fluid mt-3"> | ||||||
|  | 
 | ||||||
|  | 				<h3>Cart</h3> | ||||||
|  | 
 | ||||||
|  | 				<table className="table table-bordered"> | ||||||
|  | 
 | ||||||
|  | 					<thead> | ||||||
|  | 
 | ||||||
|  | 						<tr>  | ||||||
|  | 							<th>Item</th> | ||||||
|  | 							<th>Unit Price</th> | ||||||
|  | 							<th>Quantity</th> | ||||||
|  | 							<th>Subtotal</th> | ||||||
|  | 							<th>Action</th> | ||||||
|  | 						</tr> | ||||||
|  | 
 | ||||||
|  | 					</thead> | ||||||
|  | 
 | ||||||
|  | 					<tbody> | ||||||
|  | 
 | ||||||
|  | 						<CartList getItems={ this.getItems.bind(this) } items={ this.state.items } getTotalCartQuantity={ this.props.getTotalCartQuantity }/> | ||||||
|  | 						 | ||||||
|  | 						<tr>  | ||||||
|  | 							<th colSpan="3" className="text-right">Total</th> | ||||||
|  | 							<th className="text-right">₱ { totalPrice.toFixed(2) }</th> | ||||||
|  | 							<th></th> | ||||||
|  | 						</tr> | ||||||
|  | 
 | ||||||
|  | 					</tbody> | ||||||
|  | 
 | ||||||
|  | 				</table> | ||||||
|  | 
 | ||||||
|  | 				<button className="btn btn-danger mr-3" onClick={ this.emptyCart.bind(this) }>Empty Cart</button> | ||||||
|  | 				<button className="btn btn-primary mr-3" onClick={ this.proceedToCheckout.bind(this) }>Proceed to Checkout</button> | ||||||
|  | 				<button className="btn btn-primary" onClick={ this.proceedToStripeCheckout.bind(this) }>Proceed to Checkout (Stripe)</button> | ||||||
|  | 
 | ||||||
|  | 			</div> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const CartList = (props) => { | ||||||
|  | 	return ( | ||||||
|  | 		props.items.map((item) => { | ||||||
|  | 			return ( | ||||||
|  | 				<tr key={ item._id }> | ||||||
|  | 					<td>{ item.name }</td> | ||||||
|  | 					<td>₱ { (item.unitPrice).toFixed(2) }</td> | ||||||
|  | 					<td> | ||||||
|  | 						<UpdateItemInput getItems={ props.getItems } _id={ item._id } getTotalCartQuantity={ props.getTotalCartQuantity } quantity={ item.quantity }/> | ||||||
|  | 					</td> | ||||||
|  | 					<td className="text-right">₱ { (item.unitPrice * item.quantity).toFixed(2) }</td> | ||||||
|  | 					<td> | ||||||
|  | 						<RemoveItemButton getItems={ props.getItems } _id={ item._id } getTotalCartQuantity={ props.getTotalCartQuantity } /> | ||||||
|  | 					</td> | ||||||
|  | 				</tr> | ||||||
|  | 			) | ||||||
|  | 		}) | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const RemoveItemButton = (props) => { | ||||||
|  | 	const removeItem = () => { | ||||||
|  | 		let cart = JSON.parse(localStorage.getItem('cart')) | ||||||
|  | 
 | ||||||
|  | 		for (let i = 0; i < cart.length; i++) { | ||||||
|  | 			if (cart[i]._id == props._id) { | ||||||
|  | 				cart.splice(i) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (cart.length == 0) { | ||||||
|  | 			localStorage.removeItem('cart') | ||||||
|  | 		} else { | ||||||
|  | 			localStorage.setItem('cart', JSON.stringify(cart)) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		props.getTotalCartQuantity() | ||||||
|  | 		props.getItems() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ( | ||||||
|  | 		<button onClick={ removeItem } className="btn btn-danger">Remove</button> | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class UpdateItemInput extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props)  | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			quantity: props.quantity | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	updateQuantity(e) { | ||||||
|  | 		let cart = JSON.parse(localStorage.getItem('cart')) | ||||||
|  | 
 | ||||||
|  | 		for (let i = 0; i < cart.length; i++) { | ||||||
|  | 			if (cart[i]._id == this.props._id) { | ||||||
|  | 				cart[i].quantity = parseFloat(e.target.value) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		localStorage.setItem('cart', JSON.stringify(cart)) | ||||||
|  | 		 | ||||||
|  | 		this.setState({ quantity: e.target.value }) | ||||||
|  | 		this.props.getTotalCartQuantity() | ||||||
|  |         this.props.getItems() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		return ( | ||||||
|  | 			<input value={ this.state.quantity } onChange={ this.updateQuantity.bind(this) } type="number" className="form-control" min="1"/> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Cart | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | import React from 'react' | ||||||
|  | 
 | ||||||
|  | const Footer = () => { | ||||||
|  | 	return ( | ||||||
|  | 		<footer className="bg-dark fixed-bottom"> | ||||||
|  | 			<p className="text-center py-2 text-white my-0"> | ||||||
|  | 				Copyright © 2019 Zuitt Bootcampers | ||||||
|  | 			</p> | ||||||
|  | 		</footer> | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Footer | ||||||
| @ -0,0 +1,68 @@ | |||||||
|  | import React from 'react' | ||||||
|  | import { Link } from 'react-router-dom' | ||||||
|  | 
 | ||||||
|  | const Header = (props) => { | ||||||
|  | 	let navRight = (props.token === null) ? | ||||||
|  | 		( | ||||||
|  | 			<React.Fragment> | ||||||
|  | 				<li className="nav-item"> | ||||||
|  | 					<Link className="nav-link" to="/login">Login</Link> | ||||||
|  | 				</li> | ||||||
|  | 
 | ||||||
|  | 				<li className="nav-item"> | ||||||
|  | 					<Link className="nav-link" to="/register">Register</Link> | ||||||
|  | 				</li> | ||||||
|  | 			</React.Fragment> | ||||||
|  | 		) : | ||||||
|  | 		( | ||||||
|  | 			<React.Fragment> | ||||||
|  | 				<li className="nav-item"> | ||||||
|  | 					<Link className="nav-link" to="/">{ props.name }</Link> | ||||||
|  | 				</li> | ||||||
|  | 
 | ||||||
|  | 				<li className="nav-item"> | ||||||
|  | 					<Link className="nav-link" to="/transactions">Transactions</Link> | ||||||
|  | 				</li> | ||||||
|  | 
 | ||||||
|  | 				<li className="nav-item"> | ||||||
|  | 					<Link className="nav-link" to="/logout">Logout</Link> | ||||||
|  | 				</li> | ||||||
|  | 			</React.Fragment> | ||||||
|  | 		) | ||||||
|  | 
 | ||||||
|  | 	return ( | ||||||
|  | 		<nav className="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> | ||||||
|  | 
 | ||||||
|  | 			<Link className="navbar-brand" to="/">MERN E-Commerce</Link> | ||||||
|  | 
 | ||||||
|  | 			<button className="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbar"> | ||||||
|  | 				<span className="navbar-toggler-icon"></span> | ||||||
|  | 			</button> | ||||||
|  | 
 | ||||||
|  | 			<div className="collapse navbar-collapse" id="navbar"> | ||||||
|  | 
 | ||||||
|  | 				<ul className="navbar-nav mr-auto"> | ||||||
|  | 
 | ||||||
|  | 					<li className="nav-item"> | ||||||
|  | 						<Link className="nav-link" to="/">Menu</Link> | ||||||
|  | 					</li> | ||||||
|  | 
 | ||||||
|  | 					<li className="nav-item"> | ||||||
|  | 						<Link className="nav-link" to="/cart">Cart <span className="badge badge-light">{ props.cartQuantity }</span></Link> | ||||||
|  | 					</li> | ||||||
|  | 
 | ||||||
|  | 				</ul> | ||||||
|  | 
 | ||||||
|  | 				<ul className="navbar-nav ml-auto"> | ||||||
|  | 
 | ||||||
|  | 					{ navRight } | ||||||
|  | 
 | ||||||
|  | 				</ul> | ||||||
|  | 
 | ||||||
|  | 			</div> | ||||||
|  | 
 | ||||||
|  | 		</nav> | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Header | ||||||
| @ -0,0 +1,152 @@ | |||||||
|  | import React, { Component, Fragment } from 'react' | ||||||
|  | import { Redirect } from 'react-router-dom' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | document.title = 'Create Item' | ||||||
|  | 
 | ||||||
|  | const ItemCreate = () => ( | ||||||
|  | 	<div className="container-fluid mt-3"> | ||||||
|  | 
 | ||||||
|  | 		<div className="row"> | ||||||
|  | 
 | ||||||
|  | 			<div className="col-6 mx-auto"> | ||||||
|  | 
 | ||||||
|  | 				<h3 className="text-center">Add Item</h3> | ||||||
|  | 
 | ||||||
|  | 				<div className="card"> | ||||||
|  | 
 | ||||||
|  | 					<div className="card-header">Item Information</div> | ||||||
|  | 
 | ||||||
|  | 					<div className="card-body"> | ||||||
|  | 
 | ||||||
|  | 						<ItemCreateForm/> | ||||||
|  | 
 | ||||||
|  | 					</div> | ||||||
|  | 
 | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 			</div> | ||||||
|  | 
 | ||||||
|  | 		</div> | ||||||
|  | 
 | ||||||
|  | 	</div> | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | class ItemCreateForm extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.fileInput = React.createRef() | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			itemName: '', | ||||||
|  | 			description: '', | ||||||
|  | 			unitPrice: '', | ||||||
|  | 			categoryName: undefined, | ||||||
|  | 			categories: [], | ||||||
|  | 			returnToMenu: false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	componentWillMount() { | ||||||
|  | 		fetch(api.url + '/categories') | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((categories) => { | ||||||
|  | 			this.setState({ categories: categories })		 | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	itemNameChangeHandler(e) { | ||||||
|  | 		this.setState({ itemName: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	descriptionChangeHandler(e) { | ||||||
|  | 		this.setState({ description: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	unitPriceChangeHandler(e) { | ||||||
|  | 		this.setState({ unitPrice: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	categoryNameChangeHandler(e) { | ||||||
|  | 		this.setState({ categoryName: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	formSubmitHandler(e) { | ||||||
|  | 		e.preventDefault() | ||||||
|  | 
 | ||||||
|  | 		let payload = { | ||||||
|  | 			method: 'post', | ||||||
|  | 			headers: { | ||||||
|  | 				'Content-Type': 'application/json' | ||||||
|  | 			}, | ||||||
|  | 			body: JSON.stringify({ | ||||||
|  | 				name: this.state.itemName, | ||||||
|  | 				description: this.state.description, | ||||||
|  | 				unitPrice: this.state.unitPrice, | ||||||
|  | 				categoryName: this.state.categoryName, | ||||||
|  | 				isArchived: 0 | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/item', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((response) => { | ||||||
|  | 			if (response.error != null) { | ||||||
|  | 				alert(response.error) | ||||||
|  | 			} else { | ||||||
|  | 				this.setState({ returnToMenu: true }) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		if (this.state.returnToMenu) { | ||||||
|  | 			return <Redirect to='/'/> | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return ( | ||||||
|  | 			<form onSubmit={ this.formSubmitHandler.bind(this) }> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Item Name</label> | ||||||
|  | 					<input value={ this.state.itemName } onChange={ this.itemNameChangeHandler.bind(this) } type="text" className="form-control" required/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Description</label> | ||||||
|  | 					<input value={ this.state.description } onChange={ this.descriptionChangeHandler.bind(this) } type="text" className="form-control"/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Unit Price</label> | ||||||
|  | 					<input value={ this.state.unitPrice } onChange={ this.unitPriceChangeHandler.bind(this) } type="number" className="form-control" required/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Category</label> | ||||||
|  | 					<select value={ this.state.categoryName } onChange={ this.categoryNameChangeHandler.bind(this) } className="form-control" > | ||||||
|  | 						<option value selected disabled>Select Category</option> | ||||||
|  | 						{ | ||||||
|  | 							this.state.categories.map((category) => { | ||||||
|  | 								return <option key={ category._id } value={ category.name }>{ category.name }</option> | ||||||
|  | 							}) | ||||||
|  | 						} | ||||||
|  | 					</select> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Image</label> | ||||||
|  | 					<input type="file" className="form-control" ref={ this.fileInput }/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<button type="submit" className="btn btn-success btn-block">Add</button> | ||||||
|  | 
 | ||||||
|  | 			</form> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default ItemCreate | ||||||
| @ -0,0 +1,140 @@ | |||||||
|  | import React, { Component } from 'react' | ||||||
|  | import queryString from 'query-string' | ||||||
|  | import { Redirect, Link } from 'react-router-dom' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | document.title = 'Delete Item' | ||||||
|  | 
 | ||||||
|  | const ItemDelete = (props) => ( | ||||||
|  | 	<div className="container-fluid mt-3"> | ||||||
|  | 
 | ||||||
|  |         <div className="row"> | ||||||
|  | 
 | ||||||
|  |             <div className="col-6 mx-auto"> | ||||||
|  | 
 | ||||||
|  |                 <h3 className="text-center">Delete Item</h3> | ||||||
|  | 
 | ||||||
|  |                 <div className="card"> | ||||||
|  | 
 | ||||||
|  |                     <div className="card-header">Item Information</div> | ||||||
|  | 
 | ||||||
|  |                     <div className="card-body"> | ||||||
|  | 
 | ||||||
|  |                     	<ItemDeleteForm urlParam={ props.location.search } /> | ||||||
|  | 
 | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |     </div> | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | class ItemDeleteForm extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		let params = queryString.parse(this.props.urlParam) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			_id: params._id, | ||||||
|  | 			itemName: '', | ||||||
|  | 			description: '', | ||||||
|  | 			unitPrice: '', | ||||||
|  | 			categoryName: undefined, | ||||||
|  | 			categories: [], | ||||||
|  | 			returnToMenu: false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	componentWillMount() { | ||||||
|  | 		fetch(api.url + '/categories') | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((categories) => { | ||||||
|  | 			this.setState({ categories: categories })	 | ||||||
|  | 
 | ||||||
|  | 			fetch(api.url + '/item/' + this.state._id) | ||||||
|  | 			.then((response) => response.json()) | ||||||
|  | 			.then((item) => { | ||||||
|  | 				this.setState({  | ||||||
|  | 					itemName: item.name, | ||||||
|  | 					description: item.description, | ||||||
|  | 					unitPrice: item.unitPrice, | ||||||
|  | 					categoryName: item.categoryName | ||||||
|  | 				}) | ||||||
|  | 			}) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	formSubmitHandler(e) { | ||||||
|  | 		e.preventDefault() | ||||||
|  | 
 | ||||||
|  | 		let payload = { | ||||||
|  | 			method: 'delete', | ||||||
|  | 			headers: { | ||||||
|  | 				'Content-Type': 'application/json' | ||||||
|  | 			}, | ||||||
|  | 			body: JSON.stringify({ | ||||||
|  | 				'_id': this.state._id | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/item', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((response) => { | ||||||
|  | 			if (response.error != null) { | ||||||
|  | 				alert(response.error) | ||||||
|  | 			} else { | ||||||
|  | 				this.setState({ returnToMenu: true }) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		if (this.state.returnToMenu) { | ||||||
|  | 			return <Redirect to='/'/> | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return ( | ||||||
|  | 			<form onSubmit={ this.formSubmitHandler.bind(this) }> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Item Name</label> | ||||||
|  | 					<input value={ this.state.itemName } type="text" className="form-control" readOnly/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Description</label> | ||||||
|  | 					<input value={ this.state.description } type="text" className="form-control" readOnly/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Unit Price</label> | ||||||
|  | 					<input value={ this.state.unitPrice } type="number" className="form-control"  readOnly/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Category</label> | ||||||
|  | 					<select value={ this.state.categoryName } className="form-control" readOnly > | ||||||
|  | 						<option value disabled>Select Category</option> | ||||||
|  | 						{ | ||||||
|  | 							this.state.categories.map((category) => { | ||||||
|  | 								return <option key={ category.id } value= { category.name }>{ category.name }</option> | ||||||
|  | 							}) | ||||||
|  | 						} | ||||||
|  | 					</select> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<button type="submit" className="btn btn-danger btn-block">Delete</button> | ||||||
|  | 				<Link className="btn btn-warning btn-block" to="/">Cancel</Link> | ||||||
|  | 
 | ||||||
|  | 			</form> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default ItemDelete | ||||||
| @ -0,0 +1,169 @@ | |||||||
|  | import React, { Component } from 'react' | ||||||
|  | import queryString from 'query-string' | ||||||
|  | import { Redirect, Link } from 'react-router-dom' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | document.title = 'Update Item' | ||||||
|  | 
 | ||||||
|  | const ItemUpdate = (props) => ( | ||||||
|  | 	<div className="container-fluid mt-3"> | ||||||
|  | 
 | ||||||
|  |         <div className="row"> | ||||||
|  | 
 | ||||||
|  |             <div className="col-6 mx-auto"> | ||||||
|  | 
 | ||||||
|  |                 <h3 className="text-center">Update Item</h3> | ||||||
|  | 
 | ||||||
|  |                 <div className="card"> | ||||||
|  | 
 | ||||||
|  |                     <div className="card-header">Item Information</div> | ||||||
|  | 
 | ||||||
|  |                     <div className="card-body"> | ||||||
|  | 
 | ||||||
|  | 						<ItemUpdateForm urlParam={ props.location.search }/> | ||||||
|  | 
 | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|  |                 </div> | ||||||
|  | 
 | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |     </div> | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | class ItemUpdateForm extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.fileInput = React.createRef() | ||||||
|  | 		let params = queryString.parse(this.props.urlParam) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			_id: params._id, | ||||||
|  | 			itemName: '', | ||||||
|  | 			description: '', | ||||||
|  | 			unitPrice: '', | ||||||
|  | 			categoryName: undefined, | ||||||
|  | 			categories: [], | ||||||
|  | 			returnToMenu: false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	componentWillMount() { | ||||||
|  | 		fetch(api.url + '/categories') | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((categories) => { | ||||||
|  | 			this.setState({ categories: categories }) | ||||||
|  | 
 | ||||||
|  | 			fetch(api.url + '/item/' + this.state._id) | ||||||
|  | 			.then((response) => response.json()) | ||||||
|  | 			.then((item) => { | ||||||
|  | 				this.setState({  | ||||||
|  | 					itemName: item.name, | ||||||
|  | 					description: item.description, | ||||||
|  | 					unitPrice: item.unitPrice, | ||||||
|  | 					categoryName: item.categoryName | ||||||
|  | 				}) | ||||||
|  | 			})		 | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	itemNameChangeHandler(e) { | ||||||
|  | 		this.setState({ itemName: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	descriptionChangeHandler(e) { | ||||||
|  | 		this.setState({ description: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	unitPriceChangeHandler(e) { | ||||||
|  | 		this.setState({ unitPrice: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	categoryNameChangeHandler(e) { | ||||||
|  | 		this.setState({ categoryName: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	formSubmitHandler(e) { | ||||||
|  | 		e.preventDefault() | ||||||
|  | 
 | ||||||
|  | 		let payload = { | ||||||
|  | 			method: 'put', | ||||||
|  | 			headers: { | ||||||
|  | 				'Content-Type': 'application/json' | ||||||
|  | 			}, | ||||||
|  | 			body: JSON.stringify({ | ||||||
|  | 				_id: this.state._id, | ||||||
|  | 				name: this.state.itemName, | ||||||
|  | 				description: this.state.description, | ||||||
|  | 				unitPrice: this.state.unitPrice, | ||||||
|  | 				categoryName: this.state.categoryName, | ||||||
|  | 				isArchived: 0 | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/item', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((response) => { | ||||||
|  | 			if (response.error != null) { | ||||||
|  | 				alert(response.error) | ||||||
|  | 			} else { | ||||||
|  | 				this.setState({ returnToMenu: true }) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		if (this.state.returnToMenu) { | ||||||
|  | 			return <Redirect to='/'/> | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return ( | ||||||
|  | 			<form onSubmit={ this.formSubmitHandler.bind(this) }> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Item Name</label> | ||||||
|  | 					<input value={ this.state.itemName } onChange={ this.itemNameChangeHandler.bind(this) } type="text" className="form-control" required/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Description</label> | ||||||
|  | 					<input value={ this.state.description } onChange={ this.descriptionChangeHandler.bind(this) } type="text" className="form-control"/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Unit Price</label> | ||||||
|  | 					<input value={ this.state.unitPrice } onChange={ this.unitPriceChangeHandler.bind(this) } type="number" className="form-control" required/> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Category</label> | ||||||
|  | 					<select value={ this.state.categoryName } onChange={ this.categoryNameChangeHandler.bind(this) } className="form-control" > | ||||||
|  | 						<option value disabled>Select Category</option> | ||||||
|  | 						{ | ||||||
|  | 							this.state.categories.map((category) => { | ||||||
|  | 								return <option key={ category._id } value= { category.name }>{ category.name }</option> | ||||||
|  | 							}) | ||||||
|  | 						} | ||||||
|  | 					</select> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group"> | ||||||
|  | 					<label>Image</label> | ||||||
|  | 					<input type="file" className="form-control" ref={ this.fileInput } /> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<button type="submit" className="btn btn-success btn-block">Update</button> | ||||||
|  | 				<Link className="btn btn-warning btn-block" to="/">Cancel</Link> | ||||||
|  | 
 | ||||||
|  | 			</form> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default ItemUpdate | ||||||
| @ -0,0 +1,134 @@ | |||||||
|  | import React, { Component } from 'react' | ||||||
|  | import queryString from 'query-string' | ||||||
|  | import { Redirect } from 'react-router-dom' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | document.title = 'Login' | ||||||
|  | 
 | ||||||
|  | const Login = (props) => { | ||||||
|  | 	if (localStorage.getItem('token') != null) { | ||||||
|  | 		return <Redirect to='/'/> | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ( | ||||||
|  | 		<div className="container-fluid mt-3"> | ||||||
|  | 
 | ||||||
|  | 			<div className="row"> | ||||||
|  | 
 | ||||||
|  | 				<div className="col-6 mx-auto"> | ||||||
|  | 
 | ||||||
|  | 					<h3 className="text-center">Login</h3> | ||||||
|  | 
 | ||||||
|  | 					<div className="card"> | ||||||
|  | 					 | ||||||
|  | 						<div className="card-header">Enter Login Information</div> | ||||||
|  | 
 | ||||||
|  | 						<div className="card-body"> | ||||||
|  | 
 | ||||||
|  | 							<LoginForm urlParam={ props.location.search } setUser={ props.setUser }/>	 | ||||||
|  | 							 | ||||||
|  | 						</div> | ||||||
|  | 
 | ||||||
|  | 					</div> | ||||||
|  | 
 | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 			</div> | ||||||
|  | 		 | ||||||
|  | 		</div> | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class LoginForm extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			email:'', | ||||||
|  | 			password:'', | ||||||
|  | 			errorMessage:'', | ||||||
|  | 			gotoMenu: false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	emailChangeHandler(e) { | ||||||
|  | 		this.setState({ email: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	passwordChangeHandler(e) { | ||||||
|  | 		this.setState({ password: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	formSubmitHandler(e) { | ||||||
|  | 		e.preventDefault() | ||||||
|  | 
 | ||||||
|  | 		let payload = {  | ||||||
|  | 			method: 'post',  | ||||||
|  | 			headers: { | ||||||
|  | 				'Content-Type': 'application/json' | ||||||
|  | 			}, | ||||||
|  | 			body: JSON.stringify({ | ||||||
|  | 				'email': this.state.email, | ||||||
|  | 				'password': this.state.password | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/user/login', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((response) => { | ||||||
|  | 			if (response.result == 'authenticated') { | ||||||
|  | 				localStorage.setItem('role', response.role) | ||||||
|  | 				localStorage.setItem('name', response.name) | ||||||
|  | 				localStorage.setItem('token', 'Bearer ' + response.token) | ||||||
|  | 				this.props.setUser() | ||||||
|  | 				this.setState({ | ||||||
|  | 					gotoMenu: true | ||||||
|  | 				}) | ||||||
|  | 			} else { | ||||||
|  | 				this.setState({ | ||||||
|  | 					errorMessage: <div className="alert alert-danger">{ response.error }</div> | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		if (this.state.gotoMenu) { | ||||||
|  | 			return <Redirect to='/'/> | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		let url = this.props.urlParam  | ||||||
|  | 		let params = queryString.parse(url) | ||||||
|  | 		let registerSuccessMessage = null | ||||||
|  | 		let message = null | ||||||
|  | 
 | ||||||
|  | 		if (params.register_success) { | ||||||
|  | 			registerSuccessMessage = <div className="alert alert-success">Registration successful, you may now login.</div> | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (this.state.errorMessage == '' && registerSuccessMessage != null) { | ||||||
|  | 			message = registerSuccessMessage | ||||||
|  | 		} else { | ||||||
|  | 			message = this.state.errorMessage | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return( | ||||||
|  | 			<form onSubmit={ this.formSubmitHandler.bind(this) }> | ||||||
|  | 
 | ||||||
|  | 				{ message }					 | ||||||
|  | 
 | ||||||
|  | 				<label>Email</label> | ||||||
|  | 				<input value={ this.state.email } onChange={ this.emailChangeHandler.bind(this) } type="email" className="form-control"></input> | ||||||
|  | 
 | ||||||
|  | 				<label className="mt-2">Password</label> | ||||||
|  | 				<input value={ this.state.password } onChange={ this.passwordChangeHandler.bind(this) } type="password" className="form-control"></input> | ||||||
|  | 
 | ||||||
|  | 				<button type="submit" className="btn btn-success btn-block mt-3">Login</button> | ||||||
|  | 
 | ||||||
|  | 			</form>  | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Login | ||||||
| @ -0,0 +1,10 @@ | |||||||
|  | import React from 'react' | ||||||
|  | import { Redirect } from 'react-router-dom' | ||||||
|  | 
 | ||||||
|  | const Logout = (props) => { | ||||||
|  | 	props.unsetUser() | ||||||
|  | 
 | ||||||
|  | 	return <Redirect to='/login'/> | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Logout | ||||||
| @ -0,0 +1,154 @@ | |||||||
|  | import React, { Component } from 'react' | ||||||
|  | import { Link } from 'react-router-dom' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | const Menu = (props) => { | ||||||
|  | 	document.title = 'Menu' | ||||||
|  | 	let btnAddItem = null | ||||||
|  | 
 | ||||||
|  | 	if (localStorage.getItem('role') == 'admin') { | ||||||
|  | 		btnAddItem = <Link className="btn btn-sm btn-primary" to="/item-create">Add Item</Link> | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	return ( | ||||||
|  | 		<div className="container-fluid mt-3"> | ||||||
|  | 			<h3>Items on the Menu</h3> | ||||||
|  | 			{ btnAddItem } | ||||||
|  | 			<MenuList getTotalCartQuantity={ props.getTotalCartQuantity }/> | ||||||
|  | 		</div> | ||||||
|  | 	)	 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class MenuList extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			items: [] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	componentWillMount() { | ||||||
|  | 		fetch(api.url + '/items') | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((items) => { | ||||||
|  | 			this.setState({ items: items }) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		return ( | ||||||
|  | 			<div className="row mt-3"> | ||||||
|  | 			{ | ||||||
|  | 				this.state.items.map((item) => { | ||||||
|  | 					return ( | ||||||
|  | 						<div key={ item._id } className="col-3 mb-3"> | ||||||
|  | 
 | ||||||
|  | 							<div className="card"> | ||||||
|  | 
 | ||||||
|  | 								<img className="card-img-top" width="100%" height="200px" style={ {objectFit: 'cover'} } src={ item.imageLocation }/> | ||||||
|  | 
 | ||||||
|  | 								<div className="card-body"> | ||||||
|  | 
 | ||||||
|  | 									<h4 className="card-title">{ item.name }</h4> | ||||||
|  | 
 | ||||||
|  | 									<p className="card-text">{ item.description }</p> | ||||||
|  | 
 | ||||||
|  | 									<p className="card-text">₱{ item.unitPrice }</p> | ||||||
|  | 
 | ||||||
|  | 									{ | ||||||
|  | 										(localStorage.getItem('role') == 'admin') ?  | ||||||
|  | 										( | ||||||
|  | 											<div className="btn-group btn-block"> | ||||||
|  | 												<Link className="btn btn-info" to={"/item-update?_id="+item._id }>Edit</Link> | ||||||
|  | 												<Link className="btn btn-danger" to={"/item-delete?_id="+item._id }>Delete</Link> | ||||||
|  | 											</div> | ||||||
|  | 										) :  | ||||||
|  | 										( | ||||||
|  | 											<AddToCartForm _id={ item._id } getTotalCartQuantity={ this.props.getTotalCartQuantity }/> | ||||||
|  | 										) | ||||||
|  | 									} | ||||||
|  | 
 | ||||||
|  | 								</div> | ||||||
|  | 
 | ||||||
|  | 							</div> | ||||||
|  | 
 | ||||||
|  | 						</div> | ||||||
|  | 					) | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 			</div> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class AddToCartForm extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			_id: props._id, | ||||||
|  | 			quantity: 0 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	quantityChangeHandler(e) { | ||||||
|  | 		this.setState({ quantity: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	formSubmitHandler(e) { | ||||||
|  | 		e.preventDefault() | ||||||
|  | 
 | ||||||
|  | 		let cart = JSON.parse(localStorage.getItem('cart')) | ||||||
|  | 
 | ||||||
|  | 		if (cart != null) { | ||||||
|  | 			for (let i = 0; i < cart.length; i++) { | ||||||
|  | 				if (cart[i]._id == this.state._id) { | ||||||
|  | 					cart[i].quantity = parseFloat(cart[i].quantity) + parseFloat(this.state.quantity) | ||||||
|  | 					 | ||||||
|  | 					localStorage.setItem('cart', JSON.stringify(cart)) | ||||||
|  | 					alert('Item has been added to cart') | ||||||
|  | 
 | ||||||
|  | 					this.setState({ quantity: 0 }) | ||||||
|  | 					this.props.getTotalCartQuantity() | ||||||
|  | 
 | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			cart.push({ | ||||||
|  | 				'_id': this.state._id, | ||||||
|  | 				'quantity': parseFloat(this.state.quantity) | ||||||
|  | 			}) | ||||||
|  | 		} else { | ||||||
|  | 			cart = [] | ||||||
|  | 			cart.push({ | ||||||
|  | 				'_id': this.state._id, | ||||||
|  | 				'quantity': parseFloat(this.state.quantity) | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		localStorage.setItem('cart', JSON.stringify(cart)) | ||||||
|  | 		alert('Item has been added to cart') | ||||||
|  | 
 | ||||||
|  | 		this.setState({ quantity: 0 }) | ||||||
|  | 		this.props.getTotalCartQuantity() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		return ( | ||||||
|  | 			<form onSubmit={ this.formSubmitHandler.bind(this) }> | ||||||
|  | 				<div className="input-group">				 | ||||||
|  | 					<div className="input-group-prepend"> | ||||||
|  | 						<input value={ this.state.quantity } onChange={ this.quantityChangeHandler.bind(this) } type="number" className="form-control" min="1"/> | ||||||
|  | 						<button type="submit" className="btn btn-success btn-add-to-cart">Add</button> | ||||||
|  | 					</div>	 | ||||||
|  | 				</div> | ||||||
|  | 			</form> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Menu | ||||||
| @ -0,0 +1,134 @@ | |||||||
|  | import React, { Component } from 'react' | ||||||
|  | import { Redirect } from 'react-router-dom' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | document.title = 'Register' | ||||||
|  | 
 | ||||||
|  | const Register = () => { | ||||||
|  | 	if (localStorage.getItem('token') != null) { | ||||||
|  | 		return <Redirect to='/'/> | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ( | ||||||
|  | 		<div className="container-fluid mt-3"> | ||||||
|  | 
 | ||||||
|  | 			<div className="row"> | ||||||
|  | 			 | ||||||
|  | 				<div className="col-6 mx-auto"> | ||||||
|  | 					 | ||||||
|  | 					<h3 className="text-center">Registration Page</h3> | ||||||
|  | 
 | ||||||
|  | 					<div className="card"> | ||||||
|  | 						 | ||||||
|  | 						<div className="card-header">Registration Information</div> | ||||||
|  | 
 | ||||||
|  | 							<div className="card-body"> | ||||||
|  | 								 | ||||||
|  | 								<RegisterForm/> | ||||||
|  | 
 | ||||||
|  | 							</div> | ||||||
|  | 
 | ||||||
|  | 					</div>			 | ||||||
|  | 
 | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 			</div> | ||||||
|  | 			 | ||||||
|  | 		</div> | ||||||
|  | 	) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class RegisterForm extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			name:'', | ||||||
|  | 			email:'', | ||||||
|  | 			password:'', | ||||||
|  | 			gotoLogin: false, | ||||||
|  | 			isSubmitDisabled: true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	nameChangeHandler(e) { | ||||||
|  | 		this.setState({ name: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	emailChangeHandler(e) { | ||||||
|  | 		this.setState({ email: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	passwordChangeHandler(e) { | ||||||
|  | 		if (e.target.value.length < 8) { | ||||||
|  | 			this.setState({ isSubmitDisabled: true }) | ||||||
|  | 		} else { | ||||||
|  | 			this.setState({ isSubmitDisabled: false }) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		this.setState({ password: e.target.value }) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	formSubmitHandler(e) { | ||||||
|  | 		e.preventDefault() | ||||||
|  | 
 | ||||||
|  | 		let payload = { | ||||||
|  | 			method: 'POST', | ||||||
|  | 			headers: { | ||||||
|  | 				'Content-Type': 'application/json' | ||||||
|  | 			}, | ||||||
|  | 			body: JSON.stringify({ | ||||||
|  | 				'name': this.state.name, | ||||||
|  | 				'email': this.state.email, | ||||||
|  | 				'password': this.state.password | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/user/register', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((response) => { | ||||||
|  | 			if (response.error != null) { | ||||||
|  | 				alert(response.error) | ||||||
|  | 			} else { | ||||||
|  | 				this.setState({ gotoLogin: true }) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		if (this.state.gotoLogin) { | ||||||
|  | 			return <Redirect to='/login?register_success=true'/> | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return ( | ||||||
|  | 			<form onSubmit= { this.formSubmitHandler.bind(this) }> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group">					 | ||||||
|  | 					<label>Name</label> | ||||||
|  | 					<input value={ this.state.name } onChange= { this.nameChangeHandler.bind(this) } type="text" className="form-control mb-1"/> | ||||||
|  | 					<span className="text-danger"></span> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group">					 | ||||||
|  | 					<label>Email</label> | ||||||
|  | 					<input value= { this.state.email } onChange={ this.emailChangeHandler.bind(this) } type="email" className="form-control mb-1"/> | ||||||
|  | 					<span className="text-danger"></span> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 				<div className="form-group">					 | ||||||
|  | 					<label>Password</label> | ||||||
|  | 					<input value= { this.state.password } onChange={ this.passwordChangeHandler.bind(this) } type="password" className="form-control mb-1"/> | ||||||
|  | 					<span className="text-danger"></span> | ||||||
|  | 				</div> | ||||||
|  | 
 | ||||||
|  | 				<button disabled={ this.state.isSubmitDisabled } type="submit" className="btn btn-success btn-block">Register</button> | ||||||
|  | 
 | ||||||
|  | 			</form> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Register | ||||||
| @ -0,0 +1,70 @@ | |||||||
|  | import React, { Component } from 'react' | ||||||
|  | import api from '../api-proxy' | ||||||
|  | 
 | ||||||
|  | document.title = 'Transactions' | ||||||
|  | 
 | ||||||
|  | class Transactions extends Component { | ||||||
|  | 
 | ||||||
|  | 	constructor(props) { | ||||||
|  | 		super(props) | ||||||
|  | 
 | ||||||
|  | 		this.state = { | ||||||
|  | 			orders: [] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	componentWillMount() { | ||||||
|  | 		let payload = { | ||||||
|  | 			method: 'get', | ||||||
|  | 			headers: { | ||||||
|  | 				authorization: localStorage.getItem('token') | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		fetch(api.url + '/orders/user', payload) | ||||||
|  | 		.then((response) => response.json()) | ||||||
|  | 		.then((response) => { | ||||||
|  | 			if (response.error == 'token-auth-failed' || response.error == 'undefined-auth-header') { | ||||||
|  | 				window.location.href = '/login' | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			this.setState({ orders: response }) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		return ( | ||||||
|  | 			<div className="container-fluid mt-3"> | ||||||
|  | 
 | ||||||
|  | 				<h3>Transactions</h3> | ||||||
|  | 
 | ||||||
|  | 				<table className="table table-bordered"> | ||||||
|  | 
 | ||||||
|  | 					<thead> | ||||||
|  | 						<tr> | ||||||
|  | 							<th>Order ID</th> | ||||||
|  | 							<th>Total Price</th> | ||||||
|  | 							<th>Order Datetime</th> | ||||||
|  | 						</tr> | ||||||
|  | 					</thead> | ||||||
|  | 
 | ||||||
|  | 					<tbody> | ||||||
|  | 						{ | ||||||
|  | 							this.state.orders.map((order)=> { | ||||||
|  | 								return <tr> | ||||||
|  | 									<td>{ order._id }</td> | ||||||
|  | 									<td className="text-right">{ order.totalPrice.toFixed(2) }</td> | ||||||
|  | 									<td>{ new Date(order.datetimeRecorded).toLocaleString() }</td> | ||||||
|  | 								</tr> | ||||||
|  | 							}) | ||||||
|  | 						} | ||||||
|  | 					</tbody> | ||||||
|  | 
 | ||||||
|  | 				</table> | ||||||
|  | 				 | ||||||
|  | 			</div> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default Transactions | ||||||
| @ -0,0 +1,8 @@ | |||||||
|  | import 'bootstrap/dist/css/bootstrap.min.css' | ||||||
|  | import 'bootstrap/dist/js/bootstrap.bundle.min' | ||||||
|  | 
 | ||||||
|  | import React from 'react' | ||||||
|  | import ReactDOM from 'react-dom' | ||||||
|  | import App from './components/App' | ||||||
|  | 
 | ||||||
|  | ReactDOM.render(<App/>, document.getElementById('root')) | ||||||