├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── server │ └── db.json ├── store │ ├── Reducers │ │ ├── index.js │ │ └── TestReducer.js │ ├── index.js │ └── Actions │ │ └── TestActions.js ├── setupTests.js ├── App.test.js ├── index.css ├── reportWebVitals.js ├── App.js ├── components │ ├── ProtectedRoute.js │ └── Users.js ├── index.js ├── App.css ├── View │ ├── Home │ │ └── index.js │ └── Authentication │ │ └── Signin.js └── logo.svg ├── .gitignore ├── README.md └── package.json /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuminousIT/auth-protected-route/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuminousIT/auth-protected-route/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuminousIT/auth-protected-route/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/server/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "username": "admin", 4 | "password": "12345678" 5 | } 6 | } -------------------------------------------------------------------------------- /src/store/Reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import TestReducer from "./TestReducer"; 3 | 4 | const reducers = combineReducers({ 5 | Test: TestReducer, 6 | }); 7 | 8 | export default reducers; 9 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, createStore } from "redux"; 2 | import thunk from "redux-thunk"; 3 | import reducers from "./Reducers"; 4 | 5 | const store = createStore(reducers, applyMiddleware(thunk)); 6 | 7 | export default store; 8 | -------------------------------------------------------------------------------- /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/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/store/Reducers/TestReducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | users: null, 3 | }; 4 | 5 | export default function TestReducer(state = initialState, action) { 6 | switch (action.type) { 7 | case "DO_THIS": 8 | return { 9 | ...state, 10 | users: action.payload, 11 | }; 12 | default: 13 | return state; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/store/Actions/TestActions.js: -------------------------------------------------------------------------------- 1 | export const getAllPosts = () => { 2 | return (dispatch) => { 3 | // fetch placeholder data from jsonplaceholder 4 | fetch("https://jsonplaceholder.typicode.com/users") 5 | .then((response) => response.json()) 6 | .then((result) => 7 | //dispatch response to the store 8 | dispatch({ type: "DO_THIS", payload: result }) 9 | ); 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /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/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/App.js: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import Home from "./View/Home"; 3 | import { BrowserRouter, Route } from "react-router-dom"; 4 | import Signin from "./View/Authentication/Signin"; 5 | import ProtectedRoute from "./components/ProtectedRoute"; 6 | 7 | function App() { 8 | return ( 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /src/components/ProtectedRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redirect, Route } from "react-router-dom"; 3 | 4 | function ProtectedRoute({ component: Component, ...restOfProps }) { 5 | const isAuthenticated = localStorage.getItem("isAuthenticated"); 6 | console.log("this", isAuthenticated); 7 | 8 | return ( 9 | 12 | isAuthenticated ? : 13 | } 14 | /> 15 | ); 16 | } 17 | 18 | export default ProtectedRoute; 19 | -------------------------------------------------------------------------------- /src/components/Users.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | 4 | function Users() { 5 | const users = useSelector((state) => state.Test.users); 6 | console.log("users", users); 7 | return ( 8 |
9 |

Users

10 | {users && 11 | users.map((item, index) => ( 12 |
13 |
14 |

{item.name}

15 |

Phone: {item.phone}

Website: {item.website}

16 |
17 | ))} 18 |
19 | ); 20 | } 21 | 22 | export default Users; 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 "./store"; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById("root") 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Feature 2 | 3 | This is the code for **Implementing Protected Route and Authentication in ReactJS** and **Structuring Redux in a React Web App**, an article series shared on dev.to 4 | 5 | # Getting Started with Create React App 6 | 7 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 8 | 9 | ## Available Scripts 10 | 11 | In the project directory, you can run: 12 | 13 | ### `npm start` 14 | 15 | Runs the app in the development mode.\ 16 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 17 | 18 | The page will reload if you make edits.\ 19 | You will also see any lint errors in the console. 20 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/View/Home/index.js: -------------------------------------------------------------------------------- 1 | import { useDispatch } from "react-redux"; 2 | import { useEffect } from "react"; 3 | import { getAllPosts } from "../../store/Actions/TestActions"; 4 | import Users from "../../components/Users"; 5 | import "../../App.css"; 6 | 7 | function Home() { 8 | const dispatch = useDispatch(); 9 | 10 | useEffect(() => { 11 | dispatch(getAllPosts()); 12 | }, []); 13 | 14 | const handleLogout = () => { 15 | localStorage.clear(); 16 | window.location.pathname = "/signin"; 17 | }; 18 | return ( 19 |
20 |

This is a React-Redux Structure Demo App

21 |

Welcome to the Homepage / Landing page

22 | 25 | 26 |
27 | ); 28 | } 29 | 30 | export default Home; 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-practice", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.9", 7 | "@testing-library/react": "^11.2.5", 8 | "@testing-library/user-event": "^12.8.1", 9 | "json-server": "^0.16.3", 10 | "react": "^17.0.1", 11 | "react-dom": "^17.0.1", 12 | "react-redux": "^7.2.2", 13 | "react-router": "^5.2.0", 14 | "react-router-dom": "^5.2.0", 15 | "react-scripts": "4.0.3", 16 | "redux": "^4.0.5", 17 | "redux-thunk": "^2.3.0", 18 | "thunk": "0.0.1", 19 | "web-vitals": "^1.1.0" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 33 | 34 | 35 | 41 | 42 | React App 43 | 44 | 45 | 46 |
47 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/View/Authentication/Signin.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { useHistory } from "react-router-dom"; 4 | 5 | function Signin() { 6 | const [userData, setUserData] = useState({ username: "", password: "" }); 7 | const [errorMessage, setErrorMessage] = useState({ value: "" }); 8 | const history = useHistory(); 9 | console.log("auth", localStorage.getItem("isAuthenticated")); 10 | 11 | const handleInputChange = (e) => { 12 | setUserData((prevState) => { 13 | return { 14 | ...prevState, 15 | [e.target.name]: e.target.value, 16 | }; 17 | }); 18 | }; 19 | 20 | const handleSubmit = (e) => { 21 | e.preventDefault(); 22 | //if username or password field is empty, return error message 23 | if (userData.username === "" || userData.password === "") { 24 | setErrorMessage((prevState) => ({ 25 | value: "Empty username/password field", 26 | })); 27 | } else if (userData.username == "admin" && userData.password == "123456") { 28 | //Signin Success 29 | localStorage.setItem("isAuthenticated", "true"); 30 | window.location.pathname = "/"; 31 | } else { 32 | //If credentials entered is invalid 33 | setErrorMessage((prevState) => ({ value: "Invalid username/password" })); 34 | } 35 | }; 36 | 37 | return ( 38 |
39 |

Signin User

40 |
48 |
49 | 50 | handleInputChange(e)} 55 | /> 56 |
57 | 58 |
59 | 60 | handleInputChange(e)} 65 | /> 66 |
67 | 74 | 75 | {errorMessage.value && ( 76 |

{errorMessage.value}

77 | )} 78 |
79 |
80 | ); 81 | } 82 | 83 | export default Signin; 84 | --------------------------------------------------------------------------------