├── .gitignore
├── README.md
├── config
└── ApiConfig.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── App.css
├── App.js
├── App.test.js
├── components
│ ├── AuthLayout.css
│ ├── AuthLayout.js
│ └── DashboardLayout.js
├── config
│ └── ApiConfig.js
├── images
│ ├── auth-svg.svg
│ ├── default_pro_pic.png
│ ├── down.png
│ ├── image-1.png
│ ├── left.png
│ ├── loader.svg
│ └── logo.png
├── index.css
├── index.js
├── logo.svg
├── pages
│ ├── Auth.css
│ ├── EmailVerify.js
│ ├── Home.js
│ ├── ItemTypes.js
│ ├── Items.js
│ ├── Landing.js
│ ├── Landing.module.css
│ ├── Login.js
│ ├── NotFound.js
│ ├── Register.js
│ ├── ResetPassword.js
│ └── forgot-password.js
├── redux
│ ├── AppSettings.js
│ ├── Store.js
│ └── User.js
├── reportWebVitals.js
├── setupTests.js
└── util
│ ├── AnonymousAuth.js
│ └── PrivateRoute.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React+Redux+JWT Authentication Boilerplate
2 |
3 | This boilerplate work with springboot-jwt-boilerplate backend.
4 |
5 | https://github.com/tharukaCodeWorks/springboot-jwt-boilerplate
6 |
7 |
8 |
--------------------------------------------------------------------------------
/config/ApiConfig.js:
--------------------------------------------------------------------------------
1 | const HOST = "https://avian-spring.herokuapp.com"
2 | // const HOST = "http://localhost:8080"
3 | export default HOST;
4 | export const LOGIN = `${HOST}/token/generate-token`;
5 | export const REGISTER = `${HOST}/signup`;
6 | export const VERIFY_EMAIL = `${HOST}/guest/email/verify`;
7 | export const FORGOT_PASSWORD = `${HOST}/guest/password/forgot`;
8 | export const PASSWORD_RESET_CODE_VERIFY = `${HOST}/guest/password/verify-code`;
9 | export const RESET_PASSWORD = `${HOST}/guest/password/reset`;
10 | export const RESEND_VERIFY_EMAIL = `${HOST}/user/resend-verification-email?email=`;
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-auth-boilerplate",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.4.1",
7 | "@emotion/styled": "^11.3.0",
8 | "@material-ui/core": "^4.12.3",
9 | "@material-ui/icons": "^4.11.2",
10 | "@mui/material": "^5.0.0",
11 | "@reduxjs/toolkit": "^1.6.1",
12 | "@testing-library/jest-dom": "^5.11.4",
13 | "@testing-library/react": "^11.1.0",
14 | "@testing-library/user-event": "^12.1.10",
15 | "axios": "^0.21.1",
16 | "react": "^17.0.2",
17 | "react-dom": "^17.0.2",
18 | "react-hook-form": "^7.11.1",
19 | "react-redux": "^7.2.4",
20 | "react-router-dom": "^5.2.0",
21 | "react-scripts": "4.0.3",
22 | "web-vitals": "^1.0.1"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tharukaCodeWorks/react-redux-auth-boilerplate/4b7b45f0662cbff0690fcf4fb6caa204e65d4dc9/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tharukaCodeWorks/react-redux-auth-boilerplate/4b7b45f0662cbff0690fcf4fb6caa204e65d4dc9/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tharukaCodeWorks/react-redux-auth-boilerplate/4b7b45f0662cbff0690fcf4fb6caa204e65d4dc9/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | html, body{
2 | height:100%
3 | }
4 |
5 | #root{
6 | height:100%
7 | }
8 |
9 | a{
10 | color: inherit !important;
11 | }
12 |
13 | a:hover{
14 | color: #2B6CB8;
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from './logo.svg';
2 | import './App.css';
3 | import {
4 | BrowserRouter as Router,
5 | Switch,
6 | Route
7 | } from "react-router-dom";
8 | import Login from './pages/Login';
9 | import Register from './pages/Register';
10 | import ForgotPassword from './pages/forgot-password';
11 | import PrivateRoute from './util/PrivateRoute';
12 | import AnonymousAuth from './util/AnonymousAuth';
13 | import EmailVerify from './pages/EmailVerify';
14 | import NotFound from './pages/NotFound';
15 | import ResetPassword from './pages/ResetPassword';
16 | import Landing from './pages/Landing';
17 | import Home from './pages/Home';
18 | import Item from './pages/Items';
19 | import ItemTypes from './pages/ItemTypes';
20 |
21 | function App() {
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 |
60 | export default App;
61 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/AuthLayout.css:
--------------------------------------------------------------------------------
1 | .auth {
2 | display: flex;
3 | height: 100%;
4 | /* flex-wrap: wrap; */
5 | }
6 |
7 | .auth-left {
8 | flex: 70%;
9 | display: flex;
10 | flex-flow: column;
11 | justify-content: center;
12 | align-items: center;
13 | padding: 10px;
14 | }
15 |
16 | .auth-right {
17 | flex: 30%;
18 | background-color: #242125;
19 | height: 100%;
20 | padding: 0 40px;
21 | }
22 |
23 | .auth-left>img{
24 | flex: 1;
25 | width: 600px;
26 | }
27 |
28 | .credit{
29 | align-self: flex-end;
30 | width: 100%;
31 | text-align: center;
32 | color: rgb(119, 119, 119);
33 | font-size: small;
34 | }
35 |
36 | .slug{
37 | /* margin-top: 40px; */
38 | align-self: flex-start;
39 | color: #2B6CB8;
40 | font-size: 36px;
41 | width: 100%;
42 | text-align: center;
43 | }
44 |
45 | .teachmeit-link:hover{
46 | color: #21ba45;
47 | }
48 |
49 | .teachmeit-link{
50 | color: inherit;
51 | }
52 |
53 | @media screen and (max-width: 992px) {
54 | .auth {
55 | flex: 50%;
56 | }
57 | }
58 |
59 | /* On screens that are 600px wide or less, make the columns stack on top of each other instead of next to each other */
60 | @media screen and (max-width: 600px) {
61 | .auth {
62 | flex-direction: column;
63 | }
64 |
65 | .auth-left>img{
66 | width: 200px;
67 | }
68 |
69 | .slug{
70 | /* margin-top: 40px; */
71 | align-self: flex-start;
72 | color: #2B6CB8;
73 | font-size: 24px;
74 | width: 100%;
75 | text-align: center;
76 | }
77 |
78 | .credit{
79 | display: none;
80 | }
81 |
82 | .auth-right {
83 | flex: 30%;
84 | background-color: #2B6CB8;
85 | height: 100%;
86 | padding: 20px 40px;
87 | }
88 | }
--------------------------------------------------------------------------------
/src/components/AuthLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './AuthLayout.css';
3 | import authSvg from '../images/auth-svg.svg';
4 |
5 | const AuthLayout = (props) =>{
6 | return(
7 |
8 |
9 |
14 |
15 |
16 | {props.children}
17 |
18 |
19 | );
20 | }
21 |
22 | export default AuthLayout;
--------------------------------------------------------------------------------
/src/components/DashboardLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import { makeStyles, useTheme } from '@material-ui/core/styles';
4 | import Drawer from '@material-ui/core/Drawer';
5 | import CssBaseline from '@material-ui/core/CssBaseline';
6 | import AppBar from '@material-ui/core/AppBar';
7 | import Toolbar from '@material-ui/core/Toolbar';
8 | import List from '@material-ui/core/List';
9 | import Typography from '@material-ui/core/Typography';
10 | import Divider from '@material-ui/core/Divider';
11 | import IconButton from '@material-ui/core/IconButton';
12 | import MenuIcon from '@material-ui/icons/Menu';
13 | import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
14 | import ChevronRightIcon from '@material-ui/icons/ChevronRight';
15 | import ListItem from '@material-ui/core/ListItem';
16 | import ListItemIcon from '@material-ui/core/ListItemIcon';
17 | import ListItemText from '@material-ui/core/ListItemText';
18 | import ListIcon from '@material-ui/icons/List';
19 | import DashboardIcon from '@material-ui/icons/Dashboard';
20 | import ExitToApp from '@material-ui/icons/ExitToApp';
21 | import { logout } from '../redux/User';
22 | import { connect } from 'react-redux';
23 | // import createHistory from 'history/createBrowserHistory';
24 | import { useHistory } from "react-router-dom";
25 |
26 | const drawerWidth = 240;
27 |
28 | const useStyles = makeStyles((theme) => ({
29 | root: {
30 | display: 'flex',
31 | },
32 | appBar: {
33 | transition: theme.transitions.create(['margin', 'width'], {
34 | easing: theme.transitions.easing.sharp,
35 | duration: theme.transitions.duration.leavingScreen,
36 | }),
37 | },
38 | appBarShift: {
39 | width: `calc(100% - ${drawerWidth}px)`,
40 | marginLeft: drawerWidth,
41 | transition: theme.transitions.create(['margin', 'width'], {
42 | easing: theme.transitions.easing.easeOut,
43 | duration: theme.transitions.duration.enteringScreen,
44 | }),
45 | },
46 | menuButton: {
47 | marginRight: theme.spacing(2),
48 | },
49 | hide: {
50 | display: 'none',
51 | },
52 | drawer: {
53 | width: drawerWidth,
54 | flexShrink: 0,
55 | },
56 | drawerPaper: {
57 | width: drawerWidth,
58 | },
59 | drawerHeader: {
60 | display: 'flex',
61 | alignItems: 'center',
62 | padding: theme.spacing(0, 1),
63 | // necessary for content to be below app bar
64 | ...theme.mixins.toolbar,
65 | justifyContent: 'flex-end',
66 | },
67 | content: {
68 | flexGrow: 1,
69 | padding: theme.spacing(3),
70 | transition: theme.transitions.create('margin', {
71 | easing: theme.transitions.easing.sharp,
72 | duration: theme.transitions.duration.leavingScreen,
73 | }),
74 | marginLeft: -drawerWidth,
75 | },
76 | contentShift: {
77 | transition: theme.transitions.create('margin', {
78 | easing: theme.transitions.easing.easeOut,
79 | duration: theme.transitions.duration.enteringScreen,
80 | }),
81 | marginLeft: 0,
82 | },
83 | }));
84 |
85 | const DashboardLayout=(props)=>{
86 | const history = useHistory();
87 | const classes = useStyles();
88 | const theme = useTheme();
89 | const [open, setOpen] = React.useState(false);
90 |
91 | const handleDrawerOpen = () => {
92 | setOpen(true);
93 | };
94 |
95 | const handleDrawerClose = () => {
96 | setOpen(false);
97 | };
98 |
99 | const handleOnNavClick=(path)=>{
100 | // createHistory.push(path);
101 | history.push(path);
102 | }
103 |
104 | return (
105 |
106 |
107 |
113 |
114 |
121 |
122 |
123 |
124 | Avian Admin Panel
125 |
126 |
127 |
128 |
137 |
138 |
139 | {theme.direction === 'ltr' ? : }
140 |
141 |
142 |
143 |
144 | handleOnNavClick('/home')}>
145 |
146 |
147 |
148 |
149 | handleOnNavClick('/dashboard/item-types')}>
150 |
151 |
152 |
153 |
154 | handleOnNavClick('/dashboard/items')}>
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
177 |
178 | {props.children}
179 |
180 |
181 | );
182 | }
183 |
184 | export default connect(null, {logout})(DashboardLayout);
185 |
--------------------------------------------------------------------------------
/src/config/ApiConfig.js:
--------------------------------------------------------------------------------
1 | // const HOST = "http://localhost:8080";
2 | const HOST = "https://avian-spring.herokuapp.com"
3 | export default HOST;
4 | export const LOGIN = `${HOST}/auth/sign-in`;
5 | export const REGISTER = `${HOST}/auth/signup`;
6 | export const VERIFY_EMAIL = `${HOST}/auth/email/verify`;
7 | export const FORGOT_PASSWORD = `${HOST}/auth/password/forgot`;
8 | export const PASSWORD_RESET_CODE_VERIFY = `${HOST}/auth/password/verify-code`;
9 | export const RESET_PASSWORD = `${HOST}/auth/password/reset`;
10 | export const RESEND_VERIFY_EMAIL = `${HOST}/user/resend-verification-email?email=`;
11 |
--------------------------------------------------------------------------------
/src/images/auth-svg.svg:
--------------------------------------------------------------------------------
1 |
31 |
--------------------------------------------------------------------------------
/src/images/default_pro_pic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tharukaCodeWorks/react-redux-auth-boilerplate/4b7b45f0662cbff0690fcf4fb6caa204e65d4dc9/src/images/default_pro_pic.png
--------------------------------------------------------------------------------
/src/images/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tharukaCodeWorks/react-redux-auth-boilerplate/4b7b45f0662cbff0690fcf4fb6caa204e65d4dc9/src/images/down.png
--------------------------------------------------------------------------------
/src/images/image-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tharukaCodeWorks/react-redux-auth-boilerplate/4b7b45f0662cbff0690fcf4fb6caa204e65d4dc9/src/images/image-1.png
--------------------------------------------------------------------------------
/src/images/left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tharukaCodeWorks/react-redux-auth-boilerplate/4b7b45f0662cbff0690fcf4fb6caa204e65d4dc9/src/images/left.png
--------------------------------------------------------------------------------
/src/images/loader.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tharukaCodeWorks/react-redux-auth-boilerplate/4b7b45f0662cbff0690fcf4fb6caa204e65d4dc9/src/images/logo.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 | import { Provider } from 'react-redux'
7 | import store from './redux/Store';
8 |
9 | ReactDOM.render(
10 |
11 |
12 |
13 |
14 | ,
15 | document.getElementById('root')
16 | );
17 |
18 | // If you want to start measuring performance in your app, pass a function
19 | // to log results (for example: reportWebVitals(console.log))
20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
21 | // reportWebVitals();
22 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/Auth.css:
--------------------------------------------------------------------------------
1 | /* @import url('https://fonts.googleapis.com/css2?family=Raleway:wght@100&display=swap'); */
2 |
3 | input[type=text], input[type=email], input[type=password], input[type=number], input[type=date], select, textarea {
4 | border: 1px solid #fff;
5 | background-color: transparent;
6 | color: rgb(255, 255, 255);
7 | padding: 8px;
8 | border-radius: 4px;
9 | flex-grow: inherit;
10 | width: 100%;
11 | box-sizing: border-box;
12 |
13 | }
14 |
15 | input:disabled {
16 | background: #ccc;
17 | }
18 |
19 | input[type=submit] {
20 | background-color: #2BB86C;
21 | padding: 8px;
22 | border-radius: 4px;
23 | border: none;
24 | outline:none;
25 | color: #fff;
26 | width: 100%;
27 | }
28 |
29 | input[type=submit]:hover {
30 | background-color: #229759;
31 | cursor: pointer;
32 | }
33 |
34 | input[type=submit]:disabled {
35 | background-color: #5f9c7b;
36 | cursor:default;
37 | }
38 |
39 | .input-label{
40 | font-size: small;
41 | color: #fff;
42 | margin-bottom: 4px;
43 | }
44 |
45 | .link-style{
46 | text-decoration: none;
47 | font-size: medium;
48 | color: #fff !important;
49 | }
50 |
51 | .input-error {
52 | border: 1px solid #fd8369 !important;
53 | color: #000000 !important;
54 | }
55 |
56 | ::placeholder{
57 | color: #ccc;
58 | opacity: 0.5;
59 | }
60 |
61 | .error-message{
62 | color: #fd8369;
63 | font-size: small;
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/src/pages/EmailVerify.js:
--------------------------------------------------------------------------------
1 | import React, {useEffect, useState} from 'react';
2 | import { Redirect, withRouter } from 'react-router';
3 | import queryString from 'query-string';
4 | import { useForm } from "react-hook-form";
5 | import { connect } from 'react-redux';
6 | import { verifyEmail } from '../redux/User';
7 | import AuthLayout from '../components/AuthLayout';
8 | import {RESEND_VERIFY_EMAIL} from '../config/ApiConfig';
9 | import axios from 'axios';
10 |
11 | const EmailVerify = (props) => {
12 | const { register, handleSubmit, formState: { errors } } = useForm();
13 | const [count, setCount] = useState(180);
14 | const [isRunningCount, setIsRunningCount] = useState(false);
15 | const onSubmit = data => props.verifyEmail(data);
16 |
17 | useEffect(()=>{
18 | if (isRunningCount){
19 | if(count==0){
20 | setIsRunningCount(false);
21 | setCount(180)
22 | } else {
23 | setTimeout(() => setCount(count - 1), 1000);
24 | }
25 | }
26 |
27 | },[count, isRunningCount])
28 |
29 | const resendVerificationEmail=()=>{
30 | axios.get(`${RESEND_VERIFY_EMAIL}${queryString.parse(props.location.search).email}`).then(res=>{
31 | setIsRunningCount(true);
32 | })
33 | }
34 |
35 | const format=(time)=>{
36 | let seconds = time % 60;
37 | let minutes = Math.floor(time / 60);
38 | minutes = minutes.toString().length === 1 ? "0" + minutes : minutes;
39 | seconds = seconds.toString().length === 1 ? "0" + seconds : seconds;
40 | return minutes + ':' + seconds;
41 | }
42 |
43 | return(
44 |
45 | {
46 | props.messages.emailVerifyStatus=="success"?:<>
47 | Verify Email
48 |
69 | >
70 | }
71 |
72 |
73 | );
74 | }
75 |
76 | const mapStateToProps =state=> state.user ;
77 | export default connect(mapStateToProps, {verifyEmail})(withRouter(EmailVerify));
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { logout } from '../redux/User';
3 | import { connect } from 'react-redux';
4 | import DashboardLayout from '../components/DashboardLayout';
5 |
6 | const Home = (props) =>{
7 | return(
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default connect(null, {logout})(Home);
--------------------------------------------------------------------------------
/src/pages/ItemTypes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DashboardLayout from '../components/DashboardLayout';
3 | import Table from '@mui/material/Table';
4 | import TableBody from '@mui/material/TableBody';
5 | import TableCell from '@mui/material/TableCell';
6 | import TableContainer from '@mui/material/TableContainer';
7 | import TableHead from '@mui/material/TableHead';
8 | import TableRow from '@mui/material/TableRow';
9 | import Paper from '@mui/material/Paper';
10 | import Box from '@mui/material/Box';
11 | import { Breadcrumbs, Link, Typography } from '@material-ui/core';
12 | import TextField from '@mui/material/TextField';
13 |
14 | function createData(name, calories, fat, carbs, protein) {
15 | return { name, calories };
16 | }
17 |
18 | const rows = [
19 | createData('1', 'Sample 1'),
20 | createData('2', 'Sample 2'),
21 | createData('3', 'Sample 3'),
22 | createData('4', 'Sample 4'),
23 | createData('5', 'Sample 5'),
24 | ];
25 |
26 | const ItemTypes=(props)=>{
27 | return (
28 |
29 |
30 |
31 |
35 | Dashboard
36 |
37 | Items
38 |
39 |
40 |
62 |
63 |
64 |
65 |
66 |
67 | Id
68 | Item Type Name
69 | Item Type Image
70 | Actions
71 |
72 |
73 |
74 | {rows.map((row) => (
75 |
79 |
80 | {row.name}
81 |
82 | {row.calories}
83 | N/A
84 |
85 |
86 |
87 |
88 |
89 | ))}
90 |
91 |
92 |
93 |
94 |
95 | );
96 | }
97 |
98 | export default ItemTypes;
--------------------------------------------------------------------------------
/src/pages/Items.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DashboardLayout from '../components/DashboardLayout';
3 | import Table from '@mui/material/Table';
4 | import TableBody from '@mui/material/TableBody';
5 | import TableCell from '@mui/material/TableCell';
6 | import TableContainer from '@mui/material/TableContainer';
7 | import TableHead from '@mui/material/TableHead';
8 | import TableRow from '@mui/material/TableRow';
9 | import Paper from '@mui/material/Paper';
10 | import Box from '@mui/material/Box';
11 | import { Breadcrumbs, Link, Typography } from '@material-ui/core';
12 | import TextField from '@mui/material/TextField';
13 |
14 | function createData(name, calories, fat, carbs, protein) {
15 | return { name, calories, fat, carbs, protein };
16 | }
17 |
18 | const rows = [
19 | createData('1', 'Sample 1', 'N/A', 24, 4.0),
20 | createData('2', 'Sample 2', 'N/A', 37, 4.3),
21 | createData('3', 'Sample 3', 'N/A', 24, 6.0),
22 | createData('4', 'Sample 4', 'N/A', 67, 4.3),
23 | createData('5', 'Sample 5', 'N/A', 49, 3.9),
24 | ];
25 |
26 | const Item=(props)=>{
27 | return (
28 |
29 |
30 |
31 |
35 | Dashboard
36 |
37 | Items
38 |
39 |
40 |
81 |
82 |
83 |
84 |
85 |
86 | Id
87 | Item Name
88 | Item Image
89 | Selling price
90 | Actions
91 |
92 |
93 |
94 | {rows.map((row) => (
95 |
99 |
100 | {row.name}
101 |
102 | {row.calories}
103 | {row.fat}
104 | {row.carbs}
105 |
106 |
107 |
108 |
109 |
110 | ))}
111 |
112 |
113 |
114 |
115 |
116 | );
117 | }
118 |
119 | export default Item;
--------------------------------------------------------------------------------
/src/pages/Landing.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Styles from './Landing.module.css';
3 |
4 | const Landing = () =>{
5 | return(
6 |
7 |
React Redux Authentication Boilerplate
8 |
9 | );
10 | }
11 |
12 | export default Landing;
--------------------------------------------------------------------------------
/src/pages/Landing.module.css:
--------------------------------------------------------------------------------
1 | .landingContainer {
2 | box-sizing: border-box;
3 | width: 100%;
4 | padding: 0;
5 | margin: 0;
6 | display: flex;
7 | flex-direction: column;
8 | }
9 |
10 |
11 | @media (max-width: 800px) {
12 | .navigationBar {
13 | padding: 0.2rem 1rem !important;
14 | justify-content: space-between;
15 | }
16 |
17 | .footerTop>ul>li{
18 | font-size: 12px !important;
19 | }
20 |
21 | .footerTop{
22 | height: 100px !important;
23 | }
24 |
25 | .countings {
26 | flex-direction: row;
27 | }
28 |
29 | .describeSection {
30 | flex-direction: column !important;
31 | }
32 | }
33 |
34 | .navigationBar {
35 | background-color: #fff;
36 | border: 1px solid rgb(243, 243, 243);
37 | padding: 5px 6rem;
38 | display: flex;
39 | justify-content: space-between;
40 | align-items: center;
41 | }
42 |
43 | .signup {
44 | background-color: #3587E8;
45 | color: #fff;
46 | font-size: 18px;
47 | padding: 12px 20px;
48 | border: none;
49 | border-radius: 50px;
50 | }
51 |
52 | .signup:hover {
53 | background-color: #2c72c7;
54 | cursor: pointer;
55 | }
56 |
57 | .login {
58 | border: none;
59 | color: #3587E8;
60 | background-color: transparent;
61 | font-size: 18px;
62 | margin-right: 10px;
63 | }
64 |
65 | .login:hover{
66 | color: #2c72c7;
67 | cursor: pointer;
68 | }
69 |
70 | .menu>li{
71 | display: inline;
72 | margin-right: 20px;
73 | color: #616161;
74 | }
75 |
76 | .menu>li:hover{
77 | color: #000000;
78 | cursor: pointer;
79 | }
80 |
81 | .headerSection{
82 | height: 500px;
83 | background-color: #f8f8f8;
84 | display: flex;
85 | align-items: center;
86 | justify-content: center;
87 | flex-direction: column;
88 | }
89 |
90 | .headerTopic {
91 | text-align: center;
92 | font-size: 50px;
93 | font-weight: bold;
94 | }
95 |
96 | .getStarted {
97 | padding: 20px 50px;
98 | border: none;
99 | border-radius: 8px;
100 | font-size: larger;
101 | font-weight: bold;
102 | background-color: #3587E8;
103 | color: #fff;
104 | box-shadow: 0px 20px 30px #3586e893;
105 | }
106 |
107 | .getStarted:hover {
108 | background-color: #2c72c7;
109 | cursor: pointer;
110 | box-shadow: 0px 20px 30px #3586e8d0;
111 | }
112 |
113 | .footer {
114 | display: flex;
115 | flex-direction: column;
116 | }
117 |
118 | .footerBottom {
119 | background-color: #000000;
120 | color: #fff;
121 | text-align: center;
122 | font-size: x-small;
123 | padding: 20px 0px;
124 | }
125 |
126 | .footerTop{
127 | background-color: #202020;
128 | height: 200px;
129 | display: flex;
130 | justify-content: center;
131 | align-items: center;
132 | }
133 |
134 | .footerTop>ul>li{
135 | display: inline;
136 | margin: 10px;
137 | color: #fff;
138 | font-size:medium;
139 | font-weight: bold;
140 | }
141 |
142 | .footerTop>ul{
143 | padding: 0;
144 | }
145 |
146 | .footerTop>ul>li:hover{
147 | color: #3587E8;
148 | cursor: pointer;
149 | }
150 |
151 | .describeSection {
152 | display: flex;
153 | flex-direction: row;
154 | padding: 10px 96px;
155 | }
156 |
157 | .subHeader {
158 | font-size: 40px;
159 | margin-top: 0;
160 | color: #2b2b2b;
161 | }
162 |
163 | .sectionTextContainer {
164 | padding: 0 15px;
165 | }
166 |
167 | .countings {
168 | justify-content: space-evenly;
169 | font-size: 20px;
170 | padding: 10px 0;
171 | background-color: #3587E8;
172 | color: white;
173 | align-items: center;
174 | }
175 |
176 | .countings>h1{
177 | width: 100%;
178 | text-align: center;
179 | }
--------------------------------------------------------------------------------
/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useForm } from "react-hook-form";
3 | import { connect } from 'react-redux';
4 | import { Link } from 'react-router-dom';
5 | import {login} from '../redux/User';
6 | import './Auth.css';
7 | import AuthLayout from '../components/AuthLayout';
8 |
9 | const Login = (props) =>{
10 | const { register, handleSubmit, formState: { errors } } = useForm();
11 | const onSubmit = data => props.login(data);
12 |
13 | return(
14 |
15 | Login
16 |
38 |
39 |
40 |
41 | Register |
42 | Forgot Password |
43 |
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | const mapStateToProps =state=> state.user ;
51 | export default connect(mapStateToProps, {login})(Login);
--------------------------------------------------------------------------------
/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFound = () =>{
4 | return(
5 |
6 | 404 Not Found
7 |
8 | );
9 | }
10 |
11 | export default NotFound;
--------------------------------------------------------------------------------
/src/pages/Register.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import { useForm } from "react-hook-form";
3 | import { connect } from 'react-redux';
4 | import { Link, Redirect } from 'react-router-dom';
5 | import AuthLayout from '../components/AuthLayout';
6 | import {registerAction} from '../redux/User';
7 |
8 | const Register = (props) =>{
9 | const { register, handleSubmit, formState: { errors } } = useForm();
10 | const [formData, setFormData] = useState({email:""});
11 | const onSubmit = data => {
12 | setFormData(data);
13 | props.registerAction(data);
14 | };
15 |
16 | return(
17 |
18 | Register
19 | {
20 | props.messages.registerStatus=="success"?:
21 |
68 |
69 |
70 |
71 | Login |
72 |
73 |
74 |
75 |
76 | }
77 |
78 | );
79 | }
80 |
81 | const mapStateToProps =state=> state.user ;
82 | export default connect(mapStateToProps, {registerAction})(Register);
--------------------------------------------------------------------------------
/src/pages/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import queryString from 'query-string';
4 | import { useForm } from "react-hook-form";
5 | import { withRouter } from 'react-router';
6 | import {resetPassword} from '../redux/User';
7 | import AuthLayout from '../components/AuthLayout';
8 |
9 | const ResetPassword = (props) =>{
10 | const { register, handleSubmit, formState: { errors } } = useForm();
11 | const onSubmit = data => props.resetPassword(data);
12 | return(
13 |
14 | Reset Password
15 |
37 |
38 | );
39 | }
40 |
41 | const mapStateToProps =state=> state.user ;
42 | export default connect(mapStateToProps, {resetPassword})(withRouter(ResetPassword));
--------------------------------------------------------------------------------
/src/pages/forgot-password.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useForm } from "react-hook-form";
3 | import { connect } from 'react-redux';
4 | import { Redirect } from 'react-router';
5 | import AuthLayout from '../components/AuthLayout';
6 | import {forgotPassword} from '../redux/User';
7 |
8 | const ForgotPassword = (props) =>{
9 | const { register, handleSubmit, formState: { errors } } = useForm();
10 | const [formData, setFormData] = useState("");
11 | const onSubmit = data => {
12 | setFormData(data);
13 | props.forgotPassword(data)
14 | };
15 |
16 | return(
17 |
18 | Forgot Password
19 | {
20 | props.messages.status=="success"?:
21 | {props.messages.forgotPassword!=null?
{props.messages.forgotPassword}
:null}
22 |
37 |
38 | }
39 |
40 | );
41 | }
42 |
43 | const mapStateToProps =state=> state.user ;
44 | export default connect(mapStateToProps, {forgotPassword})(ForgotPassword);
--------------------------------------------------------------------------------
/src/redux/AppSettings.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 |
3 | // Slice
4 | const slice = createSlice({
5 | name: 'appSettings',
6 | initialState: {
7 | alertBoxShow: false
8 | },
9 | reducers: {
10 | showAlertBox: (state, action) => {
11 | state.alertBoxShow = true;
12 | },
13 | hideAlertBox: (state, action) => {
14 | state.alertBoxShow = false;
15 | }
16 | },
17 | });
18 |
19 | export default slice.reducer;
20 |
21 | // Actions
22 | const { showAlertBox, hideAlertBox } = slice.actions;
23 | export const showAlertAction = () => dispatch => {
24 | dispatch(showAlertBox());
25 | }
26 |
27 | export const hideAlertAction = () => dispatch => {
28 | dispatch(hideAlertBox());
29 | }
30 |
--------------------------------------------------------------------------------
/src/redux/Store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import { combineReducers } from 'redux';
3 | import user from './User';
4 | import appSettings from './AppSettings';
5 | const reducer = combineReducers({
6 | user,
7 | appSettings
8 | })
9 | /* eslint-disable no-underscore-dangle */
10 | const store = configureStore({
11 | reducer,
12 | devTools: window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
13 | })
14 | /* eslint-enable */
15 | export default store;
--------------------------------------------------------------------------------
/src/redux/User.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from '@reduxjs/toolkit';
2 | import axios from 'axios';
3 | import { FORGOT_PASSWORD, LOGIN, REGISTER, RESET_PASSWORD, VERIFY_EMAIL } from '../config/ApiConfig';
4 |
5 | const initialUser = localStorage.getItem('user')? JSON.parse(localStorage.getItem('user')): null;
6 | const authToken = localStorage.getItem('authToken')? JSON.parse(localStorage.getItem('authToken')): null;
7 | // const wait=ms=>new Promise(resolve => setTimeout(resolve, ms));
8 |
9 | if(authToken != null){
10 | axios.defaults.headers.common['Authorization'] = `Bearer ${authToken}`;
11 | }
12 | // Slice
13 | const slice = createSlice({
14 | name: 'user',
15 | initialState: {
16 | user: initialUser,
17 | authToken: authToken,
18 | messages: {
19 | login: null,
20 | isProcessing: false,
21 | registration: null,
22 | verifyEmail: null,
23 | forgotPassword: null,
24 | resetPassword: null,
25 | status: null,
26 | registerStatus: null,
27 | emailVerifyStatus: null
28 | }
29 | },
30 | reducers: {
31 | loginSuccess: (state, action) => {
32 | state.messages.login = action.payload.message;
33 | state.messages.status = action.payload.status;
34 | if(action.payload.status == "success"){
35 | axios.defaults.headers.common['Authorization'] = `Bearer ${action.payload.body[0].token}`;
36 | localStorage.setItem('user', JSON.stringify(action.payload.body[1]));
37 | localStorage.setItem('authToken', JSON.stringify(action.payload.body[0].token));
38 | state.user = action.payload.body[1];
39 | state.authToken = action.payload.body[0].token;
40 | }
41 | state.messages.isProcessing = false;
42 | },
43 | logoutSuccess: (state, action) => {
44 | state.user = null;
45 | state.authToken = null;
46 | localStorage.removeItem('user');
47 | localStorage.removeItem('authToken');
48 | state.messages.isProcessing = false;
49 | },
50 | registerSuccess: (state, action) =>{
51 | state.messages.registration = action.payload.message;
52 | state.messages.registerStatus = action.payload.status;
53 | state.messages.isProcessing = false;
54 | },
55 | verifySuccess: (state, action) => {
56 | state.messages.verifyEmail = action.payload.message;
57 | state.messages.emailVerifyStatus = action.payload.status;
58 | state.messages.isProcessing = false;
59 | },
60 | verifyForgotPassword: (state, action) => {
61 | state.messages.forgotPassword = action.payload.message;
62 | state.messages.status = action.payload.status;
63 | state.messages.isProcessing = false;
64 | },
65 | resetPasswordSuccess: (state, action) => {
66 | state.messages.resetPassword = action.payload.message;
67 | state.messages.status = action.payload.status;
68 | state.messages.isProcessing = false;
69 | },
70 | updateUserData: (state, action) => {
71 | state.user = action.payload;
72 | localStorage.setItem('user', JSON.stringify(action.payload))
73 | state.messages.isProcessing = false;
74 | },
75 | setIsprocessing:(state, action)=>{
76 | state.messages.isProcessing = true;
77 | }
78 | },
79 | });
80 |
81 | export default slice.reducer;
82 |
83 | // Actions
84 | const { loginSuccess, logoutSuccess, registerSuccess, verifySuccess, verifyForgotPassword, resetPasswordSuccess, updateUserData, setIsprocessing } = slice.actions;
85 | export const login = ({ email, password }) => async dispatch => {
86 | try {
87 | dispatch(setIsprocessing())
88 | const res = await axios.post(LOGIN, { email: email, password: password });
89 | dispatch(loginSuccess(res.data));
90 | } catch (e) {
91 | return console.error(e.message);
92 | }
93 | }
94 |
95 | export const logout = () => async dispatch => {
96 | try {
97 | // const res = await api.post('/api/auth/logout/')
98 | dispatch(setIsprocessing())
99 | return dispatch(logoutSuccess())
100 | } catch (e) {
101 | return console.error(e.message);
102 | }
103 | }
104 |
105 | export const updateUserDataAction=(data)=> async dispatch =>{
106 | dispatch(setIsprocessing())
107 | return dispatch(updateUserData(data))
108 | }
109 |
110 | export const registerAction = ({firstName, lastName, email, password, gender}) => async dispatch =>{
111 | try{
112 | dispatch(setIsprocessing())
113 | const res = await axios.post(REGISTER, { firstName: email, passlastName: password, email: email, password: password, gender: gender });
114 | return dispatch(registerSuccess(res.data));
115 | }catch(e){
116 | return console.error(e.message);
117 | }
118 | }
119 |
120 | export const verifyEmail = ({email, verifyCode}) => async dispatch =>{
121 | try{
122 | dispatch(setIsprocessing())
123 | const res = await axios.post(VERIFY_EMAIL, { email: email, verifyCode: verifyCode });
124 | return dispatch(verifySuccess(res.data));
125 | }catch(e){
126 | return console.error(e.message);
127 | }
128 | }
129 |
130 | export const forgotPassword = ({email}) => async dispatch =>{
131 | try{
132 | dispatch(setIsprocessing())
133 | let formData = new FormData();
134 | formData.append("email", email);
135 | const res = await axios.post(FORGOT_PASSWORD, formData);
136 | return dispatch(verifyForgotPassword(res.data));
137 | }catch(e){
138 | return console.error(e.message);
139 | }
140 | }
141 |
142 | export const resetPassword = ({email, resetCode, password}) => async dispatch =>{
143 | try{
144 | dispatch(setIsprocessing())
145 | let formData = new FormData();
146 | formData.append("email", email);
147 | formData.append("resetCode", resetCode);
148 | formData.append("newPassword", password);
149 | const res = await axios.post(RESET_PASSWORD, formData);
150 | return dispatch(resetPasswordSuccess(res.data));
151 | }catch(e){
152 | return console.error(e.message);
153 | }
154 | }
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/util/AnonymousAuth.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Redirect, Route } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 |
5 | const AnonymousAuth = (props) => {
6 |
7 | // Add your own authentication on the below line.
8 |
9 | return (
10 |
12 | props.authToken==null ? (
13 | {...props.children}
14 | ) : (
15 |
16 | )
17 | }
18 | />
19 |
20 | )
21 | }
22 |
23 | const mapStateToProps = (state) => state.user;
24 | export default connect(mapStateToProps, null)(AnonymousAuth);
--------------------------------------------------------------------------------
/src/util/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Redirect, Route } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 |
5 | const PrivateRoute = (props) => {
6 |
7 | // Add your own authentication on the below line.
8 |
9 | return (
10 |
12 | props.authToken!=null ? (
13 | {...props.children}
14 | ) : (
15 |
16 | )
17 | }
18 | />
19 |
20 | )
21 | }
22 |
23 | const mapStateToProps = (state) => state.user;
24 | export default connect(mapStateToProps, null)(PrivateRoute);
--------------------------------------------------------------------------------