By Hemanta Sundaray on 2021-10-28
In the src folder, create a folder named components and inside the components folder, create a file named Signup.js.
import React from "react";
import { useHistory } from "react-router-dom";
import { useFormik } from "formik";
import * as Yup from "yup";
import { useDispatch, useSelector } from "react-redux";
import { signupUser } from "./stateSlices/signupSlice";
const Signup = () => {
const dispatch = useDispatch();
const history = useHistory();
const { status, userRegistered, error } = useSelector(
(state) => state.signup
);
const formik = useFormik({
initialValues: {
firstName: "",
email: "",
password: "",
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(20, "Must be 20 characters or less")
.required("Please enter your first name"),
email: Yup.string()
.email("Invalid email address")
.required("Please enter your email address"),
password: Yup.string()
.min(5, "Must be 5 characters or more")
.required("Please enter your password"),
}),
onSubmit: (values) => {
dispatch(signupUser(values));
},
});
if (userRegistered) {
history.push("/signin");
}
return (
<div className="register-form-container">
<div className="col-10 col-sm-8 col-md-4 mx-auto">
<h1 className="font-weight-bold">Register</h1>
</div>
<form onSubmit={formik.handleSubmit}>
<div className="form-group col-10 col-sm-8 col-md-4 mx-auto mt-5">
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
<label htmlFor="firstName">First Name</label>
<input
className="form-control form-control-lg"
id="firstName"
name="firstName"
type="text"
{...formik.getFieldProps("firstName")}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<small className="form-text text-danger">
{formik.errors.firstName}
</small>
) : null}
</div>
<div className="form-group col-10 col-sm-8 col-md-4 mx-auto mt-3">
<label htmlFor="email">Email</label>
<input
className="form-control form-control-lg"
id="email"
name="email"
type="email"
{...formik.getFieldProps("email")}
/>
{formik.touched.email && formik.errors.email ? (
<small className="form-text text-danger">
{formik.errors.email}
</small>
) : null}
</div>
<div className="form-group col-10 col-sm-8 col-md-4 mx-auto mt-3">
<label htmlFor="password">Password</label>
<input
className="form-control form-control-lg"
id="password"
name="password"
type="password"
{...formik.getFieldProps("password")}
/>
{formik.touched.password && formik.errors.password ? (
<small className="form-text text-danger">
{formik.errors.password}
</small>
) : null}
</div>
<div className="col-10 col-sm-8 col-md-4 mx-auto mt-3">
<button
type="submit"
className="btn btn-lg btn-primary btn-block register-button"
>
{status === "loading" ? (
<div className="spinner-border text-light" role="status">
<span className="sr-only"></span>
</div>
) : null}{" "}
Register
</button>
</div>
</form>
</div>
);
};
export default Signup;
In the components folder, create a file named Signin.js.
import React from "react";
import { useFormik } from "formik";
import * as Yup from "yup";
import { signinUser } from "./stateSlices/signinSlice";
import { useSelector, useDispatch } from "react-redux";
import { useHistory } from "react-router-dom";
const Signin = () => {
const { status, loggedInUser, error } = useSelector((state) => state.signin);
const dispatch = useDispatch();
const history = useHistory();
const formik = useFormik({
initialValues: {
email: "",
password: "",
},
validationSchema: Yup.object({
email: Yup.string()
.email("Invalid email address")
.required("Please enter your email address"),
password: Yup.string().required("Please enter your password"),
}),
onSubmit: async (values) => {
dispatch(signinUser(values));
},
});
if (loggedInUser) {
localStorage.setItem("loggedInUser", JSON.stringify(loggedInUser));
history.push("/");
}
return (
<div className="login-form-container">
<div className="col-10 col-sm-8 col-md-4 mx-auto">
<h1 className="font-weight-bold">Login</h1>
</div>
<form onSubmit={formik.handleSubmit}>
<div className="form-group col-10 col-sm-8 col-md-4 mx-auto mt-5">
{error && (
<div className="alert alert-danger" role="alert">
{error}
</div>
)}
<label htmlFor="email">Email</label>
<input
className="form-control form-control-lg"
id="email"
name="email"
type="email"
{...formik.getFieldProps("email")}
/>
{formik.touched.email && formik.errors.email ? (
<small className="form-text text-danger">
{formik.errors.email}
</small>
) : null}
</div>
<div className="form-group col-10 col-sm-8 col-md-4 mx-auto mt-3">
<label htmlFor="password">Password</label>
<input
className="form-control form-control-lg"
id="password"
name="password"
type="password"
{...formik.getFieldProps("password")}
/>
{formik.touched.password && formik.errors.password ? (
<small className="form-text text-danger">
{formik.errors.password}
</small>
) : null}
</div>
<div className="col-10 col-sm-8 col-md-4 mx-auto mt-3">
<button type="submit" className="btn btn-lg btn-primary btn-block">
{status === "loading" ? (
<div className="spinner-border text-light" role="status">
<span className="sr-only"></span>
</div>
) : null}{" "}
Login
</button>
</div>
</form>
</div>
);
};
export default Signin;
In the components folder, create a file named Users.js.
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchUsers } from "./stateSlices/usersSlice";
import { useHistory } from "react-router-dom";
const Users = () => {
const { loggedInUser } = useSelector((state) => state.signin);
const { status, users, error } = useSelector((state) => state.users);
const dispatch = useDispatch();
const history = useHistory();
useEffect(() => {
if (!loggedInUser || !loggedInUser.isAdmin) {
history.push("/signin");
}
if (loggedInUser && loggedInUser.isAdmin) {
dispatch(fetchUsers({ token: loggedInUser.token }));
}
}, [dispatch, loggedInUser]);
return (
<div className="col-10, col-sm-8, col-md-6 mx-auto">
<h1>Registered Email IDs</h1>
<table className="table table-striped table-bordered table-hover mt-3">
<thead>
<tr>
<th scope="col">First Name</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
{users
? users.map((user) => (
<tr>
<td>{user.firstName}</td>
<td>{user.email}</td>
</tr>
))
: null}
</tbody>
</table>
</div>
);
};
export default Users;
In the components folder, create a file named Home.js.
import React from "react";
const Home = () => {
return <h1>Home</h1>;
};
export default Home;
In the components folder, create a file named Header.js.
import React from "react";
import { NavLink } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { logout } from "./stateSlices/signinSlice";
import { useHistory } from "react-router-dom";
const Header = () => {
const { loggedInUser } = useSelector((state) => state.signin);
const dispatch = useDispatch();
const history = useHistory();
const logoutSubmitHandler = () => {
dispatch(logout());
localStorage.removeItem("loggedInUser");
history.push("/");
};
return (
<header>
<nav>
<ul>
<li>
<NavLink to="/" exact activeClassName="active">
Home
</NavLink>
</li>
<li>
<NavLink to="/books" activeClassName="active">
Books
</NavLink>
</li>
<li>
{loggedInUser ? (
<button
onClick={logoutSubmitHandler}
className="btn btn-lg btn-primary btn-block"
>
Sign out
</button>
) : (
<ul>
<li>
<NavLink to="/signup" activeClassName="active">
Sign up
</NavLink>
</li>
<li>
<NavLink to="/signin" activeClassName="active">
Sign in
</NavLink>
</li>
</ul>
)}
</li>
</ul>
</nav>
</header>
);
};
export default Header;
In the components folder, create a file named Books.js.
import React from "react";
import { downloadBook } from "./stateSlices/bookSlice";
import { useDispatch, useSelector } from "react-redux";
import { useHistory } from "react-router-dom";
const Books = () => {
const dispatch = useDispatch();
const history = useHistory();
const { data } = useSelector((state) => state.book);
const { loggedInUser } = useSelector((state) => state.signin);
const downloadBookHandler = () => {
if (!loggedInUser) {
history.push("/signin");
} else {
dispatch(
downloadBook({
token: loggedInUser.token,
})
);
}
};
return (
<>
<button
className="btn btn-lg btn-primary btn-block"
onClick={downloadBookHandler}
>
Download Book
</button>
<h4>{data ? data.status : null}</h4>
</>
);
};
export default Books;
In the src folder, create a file named store.js.
import { configureStore } from "@reduxjs/toolkit";
import signupReducer from "./components/stateSlices/signupSlice";
import signinReducer from "./components/stateSlices/signinSlice";
import downloadBookReducer from "./components/stateSlices/bookSlice";
import usersReducer from "./components/stateSlices/usersSlice";
const loggedInUserFromStorage = localStorage.getItem("loggedInUser")
? JSON.parse(localStorage.getItem("loggedInUser"))
: null;
const preloadedState = {
signin: {
loggedInUser: loggedInUserFromStorage,
},
};
export default configureStore({
reducer: {
signup: signupReducer,
signin: signinReducer,
book: downloadBookReducer,
users: usersReducer,
},
preloadedState,
});
In the components folder, create a folder named stateSlices and inside the stateSlices folder, create four files: signupSlice.js, signinSlice.js, bookSlice.js and usersSlice.js.
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
const initialState = {
status: "idle",
userRegistered: null,
error: null,
};
export const signupUser = createAsyncThunk(
"signup/signupUser",
async (signupFormData, { rejectWithValue }) => {
try {
const { data } = await axios.post("/api/signup", signupFormData);
return data;
} catch (err) {
return rejectWithValue(err.response.data);
}
}
);
export const signupSlice = createSlice({
name: "signup",
initialState,
reducers: {},
extraReducers: {
[signupUser.pending]: (state, action) => {
state.status = "loading";
},
[signupUser.fulfilled]: (state, action) => {
state.status = "succeeded";
state.userRegistered = true;
},
[signupUser.rejected]: (state, action) => {
state.status = "failed";
state.error = action.payload.message;
},
},
});
export default signupSlice.reducer;
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
const initialState = {
status: "idle",
loggedInUser: null,
error: null,
};
export const signinUser = createAsyncThunk(
"signin/signinUser",
async (signinFormData, { rejectWithValue }) => {
try {
const { data } = await axios.post("/api/signin", signinFormData);
return data;
} catch (err) {
return rejectWithValue(err.response.data);
}
}
);
export const signinSlice = createSlice({
name: "signin",
initialState,
reducers: {
logout(state, action) {
state.loggedInUser = null;
},
},
extraReducers: {
[signinUser.pending]: (state, action) => {
state.status = "loading";
},
[signinUser.fulfilled]: (state, action) => {
state.status = "succeeded";
state.loggedInUser = action.payload;
},
[signinUser.rejected]: (state, action) => {
state.status = "failed";
state.error = action.payload.message;
},
},
});
export const { logout } = signinSlice.actions;
export default signinSlice.reducer;
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
const initialState = {
status: "idle",
data: null,
error: null,
};
export const downloadBook = createAsyncThunk(
"book/downloadBook",
async ({ token }, { rejectWithValue }) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
try {
const { data } = await axios.get("/api", config);
return data;
} catch (err) {
return rejectWithValue(err.response.data);
}
}
);
export const bookSlice = createSlice({
name: "book",
initialState,
reducers: {},
extraReducers: {
[downloadBook.pending]: (state, action) => {
state.status = "loading";
},
[downloadBook.fulfilled]: (state, action) => {
state.status = "succeeded";
state.data = action.payload;
},
[downloadBook.rejected]: (state, action) => {
state.status = "failed";
state.error = action.payload.message;
},
},
});
export default bookSlice.reducer;
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
const initialState = {
status: "idle",
users: [],
error: null,
};
export const fetchUsers = createAsyncThunk(
"users/fetchUsers",
async ({ token }, { rejectWithValue }) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
try {
const { data } = await axios.get("/api/users", config);
return data;
} catch (err) {
return rejectWithValue(err.response.data);
}
}
);
export const usersSlice = createSlice({
name: "users",
initialState,
reducers: {},
extraReducers: {
[fetchUsers.pending]: (state, action) => {
state.status = "loading";
},
[fetchUsers.fulfilled]: (state, action) => {
state.status = "succeeded";
state.users = [...action.payload];
},
[fetchUsers.rejected]: (state, action) => {
state.status = "failed";
state.error = action.payload.message;
},
},
});
export default usersSlice.reducer;
In production, we run only the Express server. The create-react-app server doesn’t even exist.
Before deploying our app to production, we will build our React project using the command npm run build. The build process will result in an optimized production build of our React app in the build folder, which will contain all the JS files, CSS files and media assets.
In the event of a request coming from the client, we will serve the root index.html file, the main.js file, CSS files and the media assets created during the build process.
In the src folder, create a file named setupProxy.js.
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
["/api", "/api/signin", "/api/signup"],
createProxyMiddleware({
target: "http://localhost:5000",
})
);
};
Paste the following code snippets in the App.js file.
import React from "react";
import Home from "./components/Home";
import Signup from "./components/Signup";
import Signin from "./components/Signin";
import Books from "./components/Books";
import Header from "./components/Header";
import Users from "./components/Users";
import { Switch, Route } from "react-router-dom";
const App = () => {
return (
<>
<Header />
<main>
<Switch>
<Route path="/users">
<Users />
</Route>
<Route path="/signup">
<Signup />
</Route>
<Route path="/signin">
<Signin />
</Route>
<Route path="/books">
<Books />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</main>
</>
);
};
export default App;
Paste the following code snippet in the index.js file.
import React from "react";
import ReactDOM from "react-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "./index.css";
import App from "./App";
import store from "./store";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
</Router>
</Provider>,
document.getElementById("root")
);
Paste the following styles rules in the index.css file.
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
font-size: 62.5%;
font-family: sans-serif;
}
header {
width: 100%;
height: 6rem;
background-color: black;
}
nav {
width: 100%;
height: 100%;
}
nav ul {
width: 100%;
height: 100%;
list-style: none;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 1rem;
}
nav ul li:not(:last-child) {
margin-right: 1rem;
}
nav a {
font-size: 1.5rem;
text-decoration: none;
}
nav a:link,
a:visited {
color: gainsboro;
}
nav a:hover {
color: white;
}
nav a:active {
color: limegreen;
}
main {
margin: 2rem;
}
.active {
font-weight: 900;
}
/*****************************/
/******* REGISTER & LOGIN*****/
/*****************************/
.login-form-container {
margin-top: 5rem;
}
.register-form-container {
margin-top: 5rem;
}
label {
font-size: 1.5rem;
}
/***********************************/
/*******END OF REGISTER & LOGIN*****/
/***********************************/
Our application is ready. cd into the auth folder and start both the servers using the following command:
PS C:\Users\Delhivery\Desktop\auth> npm run dev