Skip to content

Commit

Permalink
Merge pull request #52 from 1710-most-graceful-shoppers/checkout-#17
Browse files Browse the repository at this point in the history
Checkout #17. Added checkout form, and checkout persistence (both session and user)
  • Loading branch information
BFriedman117 authored Jan 16, 2018
2 parents cbac7c1 + 12d8eac commit c845f14
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 37 deletions.
77 changes: 48 additions & 29 deletions client/components/Cart.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,62 @@
import React from 'react';
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Link} from 'react-router-dom';
import {updateCart, updateSessionCart, deleteFromCart, deleteFromSessionCart} from '../store';
import CheckoutForm from './CheckoutForm';

const Cart = (props) => {
const {userId, sessionCart, userCart, addMe, subtractMe, deleteMe} = props;
const cart = userId ? userCart : sessionCart;
let totalCost = 0;
return cart.products ? (
<div className="cart">
{cart.products.map(product => {
totalCost = totalCost + product.product_order.quantity * product.price;
return (
<div key={product.id} className="cart-product">
<Link to={`/products/${product.id}`}><h4>Name: {product.title}</h4></Link>
<h4>Quantity: {product.product_order.quantity}
<button onClick={() => addMe(userId, product.id)}>+</button>
<button
onClick={() => subtractMe(userId, product.id)}
disabled={(product.product_order.quantity === 1)}>-</button>
<button onClick={() => deleteMe(userId, product.id)}>Remove Product</button>
</h4>
<h4>Current Price: {product.price} coins ; Subtotal: {product.price * product.product_order.quantity} coins</h4>
</div>
)
})}
{/* USE STRIPE for payment processing */}
<h3>Total Cost: {totalCost} coins</h3><button>Checkout!</button>
</div>
) : null;
};
class Cart extends Component {
constructor(props) {
super(props);
this.state = {
coreyPromo: false
}
this.handleChange = this.handleChange.bind(this);
}

handleChange (e) {
(e.target.value === 'CoreyRulezOmriDroolz') ? this.setState({coreyPromo: true}) : this.setState({coreyPromo: false});
}

render() {
const {userId, sessionCart, userCart, addMe, subtractMe, deleteMe} = this.props;
const cart = userId ? userCart : sessionCart;
let totalCost = 0;
return cart.products ? (
<div className="cart">
{cart.products.map(product => {
totalCost = totalCost + product.product_order.quantity * product.price;
return (
<div key={product.id} className="cart-product">
<Link to={`/products/${product.id}`}><h4>Name: {product.title}</h4></Link>
<h4>Quantity: {product.product_order.quantity}
<button onClick={() => addMe(userId, product.id)}>+</button>
<button
onClick={() => subtractMe(userId, product.id)}
disabled={(product.product_order.quantity === 1)}>
-
</button>
<button onClick={() => deleteMe(userId, product.id)}>Remove Product</button>
</h4>
<h4>Current Price: {product.price} coins ; Subtotal: {product.price * product.product_order.quantity} coins</h4>
</div>
)
})}
{/* USE STRIPE for payment processing */}
<h3>Total Cost: {this.state.coreyPromo ? totalCost / 2 : totalCost} coins {this.state.coreyPromo ? 'Heck yes, you said it, not me! You save 50%!' : 'You can do better.'}</h3>
<button>Checkout!</button>
<CheckoutForm promoChange={this.handleChange} />
</div>
) : null;
}
}

const mapState = (state) => ({
userId: state.user.id,
sessionCart: state.sessionCart,
userCart: state.userCart
});

//Utility function to handle this
//Utility function to handle this

const mapDispatch = (dispatch) => ({
addMe: (userId, productId) => {userId ? dispatch(updateCart(userId, productId, 1)) : dispatch(updateSessionCart(productId, 1))},
Expand Down
103 changes: 103 additions & 0 deletions client/components/CheckoutForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {placeUserOrder, placeSessionOrder} from '../store';

class CheckoutForm extends Component {
constructor(props) {
super(props);
this.state = {

};
}

render() {
const {user, sessionCart, submitUserOrder, submitCartOrder, promoChange} = this.props;
const onSubmit = user.id ? (e) => submitUserOrder(e, user.id) : (e) => submitCartOrder(e, sessionCart);

return (
<div>
<form className="form" id="checkout-form" onSubmit={onSubmit}>
<fieldset>
<legend>Checkout information here!</legend>
<div className="form-group">
<label className="form-group-label">Shipping Information
<textarea
className="form-group-label-input"
placeholder="Where you want dis?"
name="address"
rows="3"
cols="50"
/>
</label>
<label className="form-group-label">Email Address
<input
className="form-group-label-input"
placeholder={user.email || 'New Email Here'}
name="email"
/>
</label>
<label className="form-group-label">Payment Method
<input
className="form-group-label-input"
placeholder="Gold? Galleons? Barter?"
name="payment"
/>
</label>
<label className="form-group-label">Promo Code?
<input
onChange={promoChange}
className="form-group-label-input"
placeholder="Rhymes with schoolz"
name="coreyPromo"
/>
</label>
</div>
<div className="form-group">
<button
type="submit"
className="form-group-submit"
>Place your ORDER!
</button>
</div>
</fieldset>
</form>
</div>
)
}
}

const mapStateToProps = (state, ownProps) => {
return {
user: state.user,
sessionCart: state.sessionCart,
userCart: state.userCart,
promoChange: ownProps.promoChange
}
};

const mapDispatchToProps = (dispatch) => {
return {
submitUserOrder: (e, userId) => {
e.preventDefault();
dispatch(placeUserOrder({
address: e.target.address.value,
email: e.target.email.value,
payment: e.target.payment.value,
coreyPromo: e.target.coreyPromo.value === 'CoreyRulezOmriDroolz',
isSold: true
}, userId));
},
submitCartOrder: (e, sessionCart) => {
e.preventDefault();
dispatch(placeSessionOrder({
address: e.target.address.value,
email: e.target.email.value,
payment: e.target.payment.value,
coreyPromo: e.target.coreyPromo.value === 'CoreyRulezOmriDroolz',
isSold: true
}, sessionCart));
}
}
}

export default connect(mapStateToProps, mapDispatchToProps)(CheckoutForm)
8 changes: 8 additions & 0 deletions client/store/sessionCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ export function deleteFromSessionCart(productId) {
}
}

export function placeSessionOrder(checkoutInfo, sessionCart) {
return (dispatch) => {
axios.post('/api/sessions/order', {checkoutInfo, sessionCart})
.then(cart => dispatch(gotSessionCart(cart)))
.catch(console.error);
}
}

export function fetchSessionCart() {
return (dispatch) => {
axios.get('/api/sessions/cart')
Expand Down
1 change: 1 addition & 0 deletions client/store/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const auth = (email, password, method) =>
.then(res => {
dispatch(getUser(res.data));
dispatch(fetchCart(res.data.id));
dispatch(deleteFromSessionCart());
history.push('/home')
}, authError => { // rare example: a good use case for parallel (non-catch) error handler
dispatch(getUser({error: authError}))
Expand Down
18 changes: 16 additions & 2 deletions client/store/userCart.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export function updateCart(userId, productId, quantity) {
productId,
quantity
})
.then(() => dispatch(fetchCart(userId)))
.then(() => {
dispatch(fetchCart(userId));
})
.catch(console.error);
}
}
Expand All @@ -30,7 +32,19 @@ export function deleteFromCart(userId, productId) {
productId
}
})
.then(() => dispatch(fetchCart(userId)))
.then(() => {
dispatch(fetchCart(userId));
})
.catch(console.error);
}
}

export function placeUserOrder(checkoutInfo, userId) {
return (dispatch) => {
axios.post(`/api/users/${userId}/orders`, checkoutInfo)
.then(() => {
dispatch(clearCart());
})
.catch(console.error);
}
}
Expand Down
20 changes: 20 additions & 0 deletions server/api/sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,26 @@ router.delete('/cart', (req, res, next) => {
res.json(req.session.cart);
})

router.post('/order', (req, res, next) => {
const {checkoutInfo, sessionCart} = req.body;
Order.create(checkoutInfo)
.then(order => Promise.all(sessionCart.products.map(product => {
return Product.findById(product.id)
.then(foundProduct => order.addProduct(foundProduct, {
through: {
quantity: product.product_order.quantity,
price: foundProduct.price
}
}))
.catch(next);
})))
.then(() => {
req.session.cart = {products: []};
res.json(req.session.cart);
})
.catch(next);
})

module.exports = router;

// /api/users/:id/cart
Expand Down
8 changes: 7 additions & 1 deletion server/api/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ router.get('/:id/orders', (req, res, next) => {
.catch(next);
});

router.post('/:id/orders', cartHelper, (req, res, next) => {
req.user.cart.update(req.body)
.then(newOrder => res.json(newOrder))
.catch(next);
})

//getting a logged-in user cart.
router.get('/:id/cart', cartHelper, (req, res, next) => {
res.json(req.user.cart)
Expand Down Expand Up @@ -101,7 +107,7 @@ router.put('/:id/cart', cartHelper, (req, res, next) => {

Product.findById(productId)
.then(product => {
let productFinder = req.user.cart.products.findIndex(prod => Number(prod.id) === Number(product.id))
let productFinder = (req.user.cart.products) ? req.user.cart.products.findIndex(prod => Number(prod.id) === Number(product.id)) : -1;
if (productFinder !== -1) {
return req.user.cart.addProduct(product, {
through: {
Expand Down
9 changes: 5 additions & 4 deletions server/auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,15 @@ function addToCart(req, res, next) {
return Promise.all(productsArr.map((product, index) => {
let productFinder = (req.user.cart.products) ? req.user.cart.products.findIndex(prod => Number(prod.id) === Number(product.id)) : -1;
if (productFinder !== -1) {
//return
req.user.cart.addProduct(product, {
return req.user.cart.addProduct(product, {
through: {
quantity: req.session.cart.products[index].product_order.quantity + req.user.cart.products[productFinder].product_order.quantity,
price: product.price
}
})
}
else {
//return
req.user.cart.addProduct(product, {
return req.user.cart.addProduct(product, {
through: {
quantity: req.session.cart.products[index].product_order.quantity,
price: product.price
Expand All @@ -87,6 +85,9 @@ function addToCart(req, res, next) {
}
}))
})
})
.then(() => {
req.session.cart = {products: []};
res.json(req.user);
})
.catch(next)
Expand Down
15 changes: 14 additions & 1 deletion server/db/models/order.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,23 @@ const Order = db.define('order', {
status: {
type: Sequelize.ENUM('Created', 'Processing', 'Cancelled', 'Completed')
},
address: {
type: Sequelize.TEXT
},
email: {
type: Sequelize.STRING,
validate: {
isEmail: true
}
},
coreyPromo: {
type: Sequelize.BOOLEAN,
defaultValue: false
}
},
{
hooks: {
afterUpdate: (order) => {
beforeUpdate: (order) => {
if (order.getDataValue('isSold')) {
order.setDataValue('status', 'Created')
}
Expand Down
1 change: 1 addition & 0 deletions server/socket/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ module.exports = (io) => {
socket.on('disconnect', () => {
console.log(`Connection ${socket.id} has left the building`)
})

})
}

0 comments on commit c845f14

Please sign in to comment.