├── .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 |
10 |

Avian
Admin Panel

11 | 12 |

all rights reserved © Avian 2021 | privacy policy | Terms & Conditions

13 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 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 | -------------------------------------------------------------------------------- /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 |
49 | 50 | 55 | 56 | {errors.verifyCode && "Please input valid verify code"} 57 | {props.messages.verifyEmail!=null&&props.messages.verifyEmail} 58 | 59 |
60 |
61 | 64 | 65 | 66 | 67 |

Didn't recieve email? {!isRunningCount&&Resend verification email} {isRunningCount&&Resend verification email}   
{isRunningCount&&format(count)}

68 |
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 |
41 |
42 |
43 | 44 | 48 |
49 |
50 |
51 |
55 |
56 | 57 |
58 | 59 |
60 |
61 |
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 |
41 |
42 |
43 | 44 | 48 |
49 |
50 |
51 |
55 |
56 |
57 |
58 | 62 |
63 |
64 |
65 | 74 |
75 | 76 |
77 | 78 |
79 |
80 |
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 |
17 | 18 |
24 | 25 | {errors.email?.type === 'required' && "* Email is required"} 26 | {errors.email?.type === 'pattern' && "* Please enter valid email"} 27 | {(props.messages.status=='un-verified'||props.messages.status=='wrong')&&props.messages.login} 28 |

29 | 30 |
33 | 34 | {errors.password?.type === 'required' && "* Password is required"} 35 |

36 | 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
RegisterForgot Password
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 |
22 | 23 |
26 | 27 | {errors.firstName?.type === 'required' && "First Name is required"} 28 |
29 | 30 |
33 | 34 | {errors.lastName?.type === 'required' && "Last Name is required"} 35 |
36 |
37 |
43 | 44 | {errors.gender?.type === 'required' && "Gender is required"} 45 |
46 | 47 |
53 | 54 | {errors.email?.type === 'required' && "Email is required"} 55 | {errors.email?.type === 'pattern' && "Please enter valid email"} 56 | {props.messages.registerStatus=="already" && props.messages.registration} 57 |
58 | 59 | 60 |
63 | 64 | {errors.password?.type === 'required' && "Password is required"} 65 |
66 | 67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 |
Login
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 |
16 | 19 |
24 | 25 | {errors.resetCode?.type === 'required' && "Please enter reset code"} 26 | {errors.resetCode?.type === 'minlength' || errors.resetCode?.type === 'maxlength' && "Please enter valid reset code"} 27 |

28 |
31 | 32 | {errors.password?.type === 'required' && "Password is required"} 33 | 34 |

35 | 36 |
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 |
23 | 24 |
30 | 31 | {errors.email?.type === 'required' && "Email is required"} 32 | {errors.email?.type === 'pattern' && "Please enter valid email"} 33 | 34 |

35 | 36 |
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); --------------------------------------------------------------------------------