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')) | ||||