Skip to content

Commit

Permalink
Merge c1b072e into 3e4a61c
Browse files Browse the repository at this point in the history
  • Loading branch information
akhilome committed Jan 27, 2019
2 parents 3e4a61c + c1b072e commit dd7c1f0
Show file tree
Hide file tree
Showing 43 changed files with 1,590 additions and 603 deletions.
39 changes: 39 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -30,12 +30,14 @@
"axios": "^0.18.0",
"express": "^4.16.4",
"jwt-decode": "^2.2.0",
"lodash.omit": "^4.5.0",
"prop-types": "^15.6.2",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-hot-loader": "^4.6.3",
"react-redux": "^6.0.0",
"react-router-dom": "^4.3.1",
"react-toastify": "^4.5.2",
"redux": "^4.0.1",
"redux-thunk": "^2.3.0"
},
Expand Down
62 changes: 54 additions & 8 deletions src/actions/index.js
@@ -1,11 +1,14 @@
import omit from 'lodash.omit';
import { toast } from 'react-toastify';

import axios from '../services/axios';
import actionTypes from './types';
import types from './types';
import { saveToken, getToken } from '../utils/localStorage';
import jwt from '../utils/jwt';

export const startFetching = () => ({ type: actionTypes.START_FETCHING });
export const startFetching = () => ({ type: types.START_FETCHING });
export const stopFetching = (fetchSuccess = true, message = '') => ({
type: actionTypes.STOP_FETCHING,
type: types.STOP_FETCHING,
payload: {
error: !fetchSuccess,
message,
Expand All @@ -19,14 +22,15 @@ export const signUpUser = userData => async (dispatch) => {
if (status === 'success') saveToken(user.auth_token);
const { userName: name, userStatus: role } = jwt.decode(user.auth_token);
dispatch({
type: actionTypes.SIGN_UP,
type: types.SIGN_UP,
payload: {
name,
role,
},
});
return dispatch(stopFetching());
} catch (error) {
toast.error(error.response ? error.response.data.message : 'something went wrong');
return dispatch(
stopFetching(false, error.response ? error.response.data.message : 'something went wrong'),
);
Expand All @@ -40,14 +44,15 @@ export const logInUser = userData => async (dispatch) => {
if (status === 'success') saveToken(user.auth_token);
const { userName: name, userStatus: role } = jwt.decode(user.auth_token);
dispatch({
type: actionTypes.LOG_IN,
type: types.LOG_IN,
payload: {
name,
role,
},
});
return dispatch(stopFetching());
} catch (error) {
toast.error(error.response ? error.response.data.message : 'something went wrong');
return dispatch(
stopFetching(false, error.response ? error.response.data.message : 'something went wrong'),
);
Expand All @@ -58,11 +63,11 @@ export const checkAuthStatus = () => {
try {
const { userName: name, userStatus: role } = jwt.decode(getToken());
return {
type: actionTypes.CHECK_AUTH_STATUS,
type: types.CHECK_AUTH_STATUS,
payload: { name, role },
};
} catch (error) {
return { type: actionTypes.CHECK_AUTH_STATUS_FAIL };
return { type: types.CHECK_AUTH_STATUS_FAIL };
}
};

Expand All @@ -71,7 +76,7 @@ export const getMenu = () => async (dispatch) => {
try {
const { menu } = (await axios.get('/menu')).data;
dispatch({
type: actionTypes.GET_MENU,
type: types.GET_MENU,
payload: { menu },
});
return dispatch(stopFetching());
Expand All @@ -81,3 +86,44 @@ export const getMenu = () => async (dispatch) => {
);
}
};

export const addToCart = ({ foodId, foodName, foodPrice }) => {
const foodDetails = { [foodId]: { foodName, foodPrice } };
const cart = JSON.parse(localStorage.getItem('cart')) || {};
if (!Object.keys(cart).includes(`${foodId}`)) {
const updatedCart = { ...cart, ...foodDetails };
localStorage.setItem('cart', JSON.stringify(updatedCart));
toast.success(`${foodName} added to cart 🚀`);
return { type: types.ADD_TO_CART, payload: foodDetails };
}
toast.error(`${foodName} already in cart 🙅🏾‍♂️`);
return { type: types.ADD_TO_CART_FAIL };
};

export const getCart = () => {
const cart = JSON.parse(localStorage.getItem('cart')) || {};
return { type: types.GET_CART, payload: cart };
};

export const removeFromCart = (foodId) => {
const cart = JSON.parse(localStorage.getItem('cart')) || {};
const updatedCart = omit(cart, foodId);
localStorage.setItem('cart', JSON.stringify(updatedCart));
return { type: types.REMOVE_FROM_CART, payload: { foodId } };
};

export const checkout = foodIds => async (dispatch) => {
dispatch(startFetching());
try {
await axios.post('/orders', { foodIds });
localStorage.removeItem('cart');
dispatch({ type: types.CHECKOUT });
toast.success('Order placed! 🎉');
return dispatch(stopFetching());
} catch (error) {
toast.error('Error placing your order 😢 Please try again');
return dispatch(
stopFetching(false, error.response ? error.response.data.message : 'something went wrong'),
);
}
};
6 changes: 6 additions & 0 deletions src/actions/types/index.js
Expand Up @@ -6,4 +6,10 @@ export default {
CHECK_AUTH_STATUS: 'CHECK_AUTH_STATUS',
CHECK_AUTH_STATUS_FAIL: 'CHECK_AUTH_STATUS_FAIL',
GET_MENU: 'GET_MENU',
GET_CART: 'GET_CART',
ADD_TO_CART: 'ADD_TO_CART',
ADD_TO_CART_FAIL: 'ADD_TO_CART_FAIL',
REMOVE_FROM_CART: 'REMOVE_FROM_CART',
CHECKOUT: 'CHECKOUT',
CHECKOUT_FAIL: 'CHECKOUT_FAIL',
};
56 changes: 40 additions & 16 deletions src/components/App.jsx
@@ -1,41 +1,65 @@
import '../index.css';
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import propTypes from 'prop-types';
import { checkAuthStatus } from '../actions';
import PropTypes from 'prop-types';
import { checkAuthStatus, getCart } from '../actions';
import HomePage from './HomePage';
import LoginPage from './LoginPage';
import Signup from './SignupPage';
import Menu from './MenuPage';
import CartPage from './Cart';
import NotFoundPage from './NotFoundPage';
import Loader from './Loader';
import Nav from './Nav';

export class App extends Component {
componentDidMount() {
const { checkAuthStatus: checkUserAuthStatus } = this.props;
checkUserAuthStatus();
async componentDidMount() {
const { checkAuthStatus: checkUserAuthStatus, getCart: getCartItems } = this.props;
await checkUserAuthStatus();
const { isLoggedIn } = this.props;
if (isLoggedIn) getCartItems();
}

render() {
const { loading } = this.props;
return (
<Router>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/login" component={LoginPage} />
<Route path="/signup" component={Signup} />
<Route path="/menu" component={Menu} />
<Route component={NotFoundPage} />
</Switch>
<Fragment>
{loading && <Loader />}
<Nav />
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/login" component={LoginPage} />
<Route path="/signup" component={Signup} />
<Route path="/menu" component={Menu} />
<Route path="/cart" component={CartPage} />
<Route component={NotFoundPage} />
</Switch>
</Fragment>
</Router>
);
}
}

App.propTypes = {
checkAuthStatus: propTypes.func.isRequired,
checkAuthStatus: PropTypes.func.isRequired,
getCart: PropTypes.func.isRequired,
isLoggedIn: PropTypes.bool,
loading: PropTypes.bool,
};

App.defaultProps = {
isLoggedIn: false,
loading: false,
};

const mapStateToProps = state => ({
isLoggedIn: state.user.isLoggedIn,
loading: state.fetching.fetching,
});

export default connect(
null,
{ checkAuthStatus },
mapStateToProps,
{ checkAuthStatus, getCart },
)(App);
99 changes: 99 additions & 0 deletions src/components/Cart.jsx
@@ -0,0 +1,99 @@
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import CheckOutCard from './CheckOutCard';
import { removeFromCart, checkout } from '../actions';

export class Cart extends Component {
onCheckoutClick = async () => {
const { cart, checkout: placeOrder, history } = this.props;
const foodItems = Object.keys(cart).map(Number);
await placeOrder(foodItems);
const {
fetching: { fetching, errorMessage },
} = this.props;
if (!fetching && !errorMessage) return history.push('/');
return undefined;
};

onRemoveClick = (foodId) => {
const { removeFromCart: removeItemFromCart } = this.props;
removeItemFromCart(foodId);
};

render() {
const { cart } = this.props;

const cartItems = Object.keys(cart)
.map(Number)
.map(item => ({
id: item,
foodName: cart[item].foodName,
foodPrice: cart[item].foodPrice,
}));

return (
<div className="wrapper">
<h2>Items To Be Purchased</h2>
{cartItems.length ? (
<Fragment>
<section className="container checkout">
{cartItems.map(item => (
<CheckOutCard
key={item.id}
id={item.id}
foodName={item.foodName}
foodPrice={item.foodPrice}
buttonCallback={() => this.onRemoveClick(item.id)}
/>
))}
</section>
<div className="order-price">
<p>
<strong>total price</strong>
: ₦
<span id="order-price">
{cartItems
.map(item => item.foodPrice)
.reduce((a, b) => a + b)
.toLocaleString()}
</span>
</p>
</div>
<button
type="button"
className="btn-primary"
id="checkout"
onClick={this.onCheckoutClick}
>
Checkout
</button>
</Fragment>
) : (
<section className="container checkout">
<div>No Items in cart</div>
</section>
)}
</div>
);
}
}

Cart.propTypes = {
cart: PropTypes.instanceOf(Object).isRequired,
removeFromCart: PropTypes.func.isRequired,
checkout: PropTypes.func.isRequired,
history: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
fetching: PropTypes.instanceOf(Object).isRequired,
};

const mapStateToProps = state => ({
cart: state.cart,
fetching: state.fetching,
});

export default connect(
mapStateToProps,
{ removeFromCart, checkout },
)(Cart);

0 comments on commit dd7c1f0

Please sign in to comment.