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