folder structure

aws_deployment
Marvin Ramos 3 months ago
parent f882f2cf56
commit 7cd1e49b57

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

File diff suppressed because it is too large Load Diff

@ -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,68 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `npm run build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

File diff suppressed because it is too large Load Diff

@ -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"
]
}
}

Binary file not shown.

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">&#8369; { 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>&#8369; { (item.unitPrice).toFixed(2) }</td>
<td>
<UpdateItemInput getItems={ props.getItems } _id={ item._id } getTotalCartQuantity={ props.getTotalCartQuantity } quantity={ item.quantity }/>
</td>
<td className="text-right">&#8369; { (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 &copy; 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">&#8369;{ 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'))
Loading…
Cancel
Save