Hello friends! So, this is the final part of the MERN Stack series. In the first four parts, we discussed the backend part of our application in complete detail — from setting up the routes to accepting payments via stripe, we did all the backend work in those four parts. Then in the fifth and sixth parts, we dealt with Redux actions, reducers and store and also built out the authentication components.
Notice: I will publish the complete detailed version of all the articles on the Medium website. Here I will give an overview and give the codes for the various pages part by part.
So, please click here to go to Medium and read it in completion. (These are friend links so do not worry about paywall)
So, in the final part, we will be completing the project by building out the React components we would need for this project.
So, we will build out all the components one by one. We will build out all these components inside the components folder we created in the previous article.
AppNavbar
import { Component, Fragment } from 'react';
import { Collapse, Navbar, NavbarToggler, NavbarBrand, Nav, NavItem, Container, NavLink } from 'reactstrap';
import RegisterModal from './auth/registerModal';
import Logout from './auth/Logout';
import LoginModal from './auth/loginModal';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
class AppNavbar extends Component {
state = {
isOpen: false
}
static propTypes = {
auth: PropTypes.object.isRequired
}
toggle = () => {
this.setState({
isOpen: !this.state.isOpen
});
}
render() {
const { isAuthenticated, user } = this.props.auth;
const authLinks = (
<Fragment>
<NavItem>
<span className="navbar-text mr-3">
<strong>{ user ? `Welcome ${user.name}` : ''}</strong>
</span>
</NavItem>
<NavItem>
<NavLink href="/">Home</NavLink>
</NavItem>
<NavItem>
<NavLink href="/cart">Cart</NavLink>
</NavItem>
<NavItem className="mr-2">
<NavLink href="/orders">Orders</NavLink>
</NavItem>
<NavItem>
<Logout/>
</NavItem>
</Fragment>
);
const guestLinks = (
<Fragment>
<NavItem>
<RegisterModal/>
</NavItem>
<NavItem>
<LoginModal/>
</NavItem>
</Fragment>
);
return(
<div>
<Navbar color="dark" dark expand="sm" className="mb-5">
<Container>
<NavbarBrand href="/">E Commerce Store</NavbarBrand>
<NavbarToggler onClick={this.toggle}/>
<Collapse isOpen={this.state.isOpen} navbar>
<Nav className="ml-auto" navbar>
{ isAuthenticated ? authLinks: guestLinks}
</Nav>
</Collapse>
</Container>
</Navbar>
</div>
);
}
}
const mapStateToProps = state => ({
auth: state.auth
})
export default connect(mapStateToProps, null)(AppNavbar);
Home
import { Component } from 'react';
import AppNavbar from './AppNavbar';
import {Card, CardText, CardBody, CardTitle, CardSubtitle, Button, Container} from 'reactstrap';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getItems } from '../actions/itemActions';
import { addToCart } from '../actions/cartActions';
class Home extends Component {
componentDidMount(){
this.props.getItems();
}
static propTypes = {
getItems: PropTypes.func.isRequired,
item: PropTypes.object.isRequired,
isAuthenticated: PropTypes.bool,
addToCart: PropTypes.func.isRequired,
user: PropTypes.object.isRequired
}
onAddToCart = async (id, productId) => {
await this.props.addToCart(id, productId, 1);
alert ('Item added to Cart');
}
render(){
const { items } = this.props.item;
const user = this.props.user;
return (
<div>
<AppNavbar/>
<Container>
<div className="row">
{items.map((item)=>(
<div className="col-md-4">
<Card className="mb-4">
<CardBody>
<CardTitle tag="h5">{item.title}</CardTitle>
<CardSubtitle tag="h6">Rs. {item.price}</CardSubtitle>
<CardText>{item.category}</CardText>
{this.props.isAuthenticated ?
<Button
color="success"
size="sm"
onClick={this.onAddToCart.bind(this, user._id, item._id)}
>Add To Cart</Button> :
null}
</CardBody>
</Card>
</div>
))}
</div>
</Container>
</div>
)
}
}
const mapStateToProps = (state) => ({
item: state.item,
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user
})
export default connect(mapStateToProps, {getItems, addToCart})(Home);
AddItem
import { Component } from 'react';
import { Button, Form, FormGroup, Label, Input, Container, Alert } from 'reactstrap';
import { connect } from 'react-redux';
import { addItem } from '../actions/itemActions';
import PropTypes from 'prop-types';
import AppNavbar from './AppNavbar';
class AddItem extends Component {
state = {
title: '',
description: '',
category: '',
price: '',
}
static propTypes = {
isAuthenticated: PropTypes.bool
}
onChange = (e) => {
this.setState({[e.target.name]:e.target.value});
}
onSubmit = async (e) => {
e.preventDefault();
const newItem = {
title: this.state.title,
description: this.state.description,
category: this.state.category,
price: this.state.price
}
await this.props.addItem(newItem);
alert('Item added successfully');
}
render(){
return(
<div>
<AppNavbar/>
<Container>
<h2 className="text-center mb-3">Add a new Item</h2>
{ this.props.isAuthenticated ?
<Form onSubmit={this.onSubmit}>
<FormGroup>
<Label for="title">Title</Label>
<Input
type="text"
name="title"
id="title"
placeholder="Title of the item"
onChange={this.onChange}
/>
<br/>
<Label for="description">Description</Label>
<Input
type="text"
name="description"
id="description"
placeholder="Description of the item"
onChange={this.onChange}
/>
<br/>
<Label for="category">Category</Label>
<Input
type="text"
name="category"
id="category"
placeholder="Category of the item"
onChange={this.onChange}
>
</Input>
<br/>
<Label for="price">Price</Label>
<Input
type="number"
name="price"
id="price"
placeholder="Price of the item"
onChange={this.onChange}
/>
<Button
color="dark"
style={{marginTop: '2rem'}}
block
>Add Item</Button>
</FormGroup>
</Form> :
<Alert className="text-center" color="danger">Login to add items!</Alert>
}
</Container>
</div>
)
}
}
const mapStateToProps = (state) => ({
item: state.item,
isAuthenticated: state.auth.isAuthenticated,
});
export default connect(mapStateToProps,{addItem})(AddItem);
Checkout
import StripeCheckout from 'react-stripe-checkout';
const STRIPE_PUBLISHABLE = 'pk_test_********************';
const onToken = (user,checkout) => token =>
checkout(user, token.id);
const Checkout = ({ amount, user, checkout }) =>
<StripeCheckout
amount={amount*100}
token={onToken(user,checkout)}
currency='INR'
stripeKey={STRIPE_PUBLISHABLE}
/>
export default Checkout;
Cart
import { Component, Fragment } from 'react';
import AppNavbar from './AppNavbar';
import {Card, CardText, CardBody, CardTitle, CardSubtitle, Button, Alert, Container} from 'reactstrap';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getCart, deleteFromCart } from '../actions/cartActions';
import Checkout from './Checkout';
import { checkout } from '../actions/orderActions';
class Cart extends Component {
state = {
loaded: false,
}
static propTypes = {
getCart: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool,
addToCart: PropTypes.func.isRequired,
deleteFromCart: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
cart: PropTypes.object.isRequired,
checkout: PropTypes.func.isRequired
}
getCartItems = async (id) => {
await this.props.getCart(id);
this.state.loaded = true;
}
onDeleteFromCart = (id, itemId) => {
this.props.deleteFromCart(id, itemId);
}
render(){
const user = this.props.user;
if(this.props.isAuthenticated && !this.props.cart.loading && !this.state.loaded){
this.getCartItems(user._id);
}
return(
<div>
<AppNavbar/>
{this.props.isAuthenticated ?
<Fragment>
{this.props.cart.cart ? null :
<Alert color="info" className="text-center">Your cart is empty!</Alert>
}
</Fragment>
: <Alert color="danger" className="text-center">Login to View!</Alert>
}
{this.props.isAuthenticated && !this.props.cart.loading && this.state.loaded && this.props.cart.cart?
<Container>
<div className="row">
{this.props.cart.cart.items.map((item)=>(
<div className="col-md-4">
<Card>
<CardBody>
<CardTitle tag="h5">{item.name}</CardTitle>
<CardSubtitle tag="h6">Rs. {item.price}</CardSubtitle>
<CardText>Quantity - {item.quantity}</CardText>
<Button color="danger" onClick={this.onDeleteFromCart.bind(this, user._id, item.productId)}>Delete</Button>
</CardBody>
</Card>
<br/>
</div>
))}
<div class="col-md-12">
<Card>
<CardBody>
<CardTitle tag="h5">Total Cost = Rs. {this.props.cart.cart.bill}</CardTitle>
<Checkout
user={user._id}
amount={this.props.cart.cart.bill}
checkout={this.props.checkout}
/>
</CardBody>
</Card>
</div>
</div>
</Container>
:null}
</div>
)
}
}
const mapStateToProps = (state) => ({
cart: state.cart,
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user,
})
export default connect(mapStateToProps, {getCart, deleteFromCart, checkout})(Cart);
Order
import { Component, Fragment } from 'react';
import AppNavbar from './AppNavbar';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getOrders } from '../actions/orderActions';
import {Card, CardText, CardBody, CardTitle, CardSubtitle, Button, Alert, Container} from 'reactstrap';
class Orders extends Component {
state = {
loaded: false,
}
static propTypes = {
isAuthenticated: PropTypes.bool,
user: PropTypes.object.isRequired,
order: PropTypes.object.isRequired,
getOrders: PropTypes.func.isRequired
}
ongetOrders = async (id) => {
await this.props.getOrders(id);
this.state.loaded = true;
}
render(){
const user = this.props.user;
if(this.props.isAuthenticated && !this.props.order.loading && !this.state.loaded){
this.ongetOrders(user._id);
}
return(
<div>
<AppNavbar/>
{this.props.isAuthenticated ?
<Fragment>
{this.props.order.orders!==[] ? null :
<Alert color="info" className="text-center">You have no orders!</Alert>
}
</Fragment>
: <Alert color="danger" className="text-center">Login to View!</Alert>
}
{this.props.isAuthenticated && !this.props.order.loading && this.state.loaded && this.props.order.orders.length?
<Container>
<div className="row">
{this.props.order.orders.map((order)=>(
<div className="col-md-12">
<Card>
<CardBody>
<CardTitle tag="h4">{order.items.length} items - Total cost: Rs. {order.bill}</CardTitle>
<div className="row">
{order.items.map((item)=>(
<div className="col-md-4">
<Card className="mb-2">
<CardBody>
<CardTitle tag="h5">{item.name} ({item.quantity} pieces)</CardTitle>
<CardSubtitle tag="h6">Rs. {item.price}/piece</CardSubtitle>
</CardBody>
</Card>
</div>
))}
</div>
</CardBody>
</Card>
<br/>
</div>
))}
</div>
</Container>
:null}
</div>
)
}
}
const mapStateToProps = (state) => ({
order: state.order,
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user,
})
export default connect(mapStateToProps, {getOrders})(Orders);
Main
import { Component } from 'react';
import AddItem from './AddItem';
import Home from './Home';
import { Switch, Route, Redirect, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import Cart from './Cart';
import Orders from './Oder';
class Main extends Component {
render(){
return (
<div>
<Switch>
<Route path='/home'>
<Home/>
</Route>
<Route path='/addItem'>
<AddItem/>
</Route>
<Route path='/cart'>
<Cart/>
</Route>
<Route path='/orders'>
<Orders/>
</Route>
<Redirect to='/home'/>
</Switch>
</div>
)
}
}
export default withRouter(connect()(Main));
App
import { Component } from 'react';
import { Provider } from 'react-redux';
import 'bootstrap/dist/css/bootstrap.min.css';
import Main from './components/Main';
import store from './store';
import {loadUser} from './actions/authActions';
import { BrowserRouter } from 'react-router-dom';
class App extends Component {
componentDidMount(){
store.dispatch(loadUser());
}
render(){
return (
<Provider store={store}>
<BrowserRouter>
<div className="App">
<Main/>
</div>
</BrowserRouter>
</Provider>
);
}
}
export default App;
To read the complete tutorial, please move to Medium and read the complete article.
Top comments (4)
Hi,is there any resource to learn mern stack you advice?
Yes, you can follow The Net Ninja and Traversy Media Youtube channels. These are the best resources according to me. I learnt everything about MERN stack from there.
very helpful
Great.Wonderful!!!!!!!!