├── github └── routing.gif ├── web ├── src │ ├── index.css │ ├── Pages │ │ ├── Home │ │ │ └── index.js │ │ ├── Dashboard │ │ │ └── index.js │ │ ├── User │ │ │ └── index.js │ │ └── SignIn │ │ │ └── index.js │ ├── components │ │ └── SomeSpinner │ │ │ └── index.js │ ├── hooks │ │ ├── useAuth.js │ │ └── useLocalStorage.js │ ├── setupTests.js │ ├── index.js │ ├── services │ │ └── auth.js │ ├── App.js │ ├── Routes │ │ ├── index.js │ │ └── PrivateRoute.js │ ├── Layout │ │ ├── styles.css │ │ └── index.js │ └── contexts │ │ └── auth.js ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── .gitignore └── package.json ├── README.md └── LICENSE /github/routing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cassiopieroni/reactjs-auth/HEAD/github/routing.gif -------------------------------------------------------------------------------- /web/src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cassiopieroni/reactjs-auth/HEAD/web/public/favicon.ico -------------------------------------------------------------------------------- /web/src/Pages/Home/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Home = () =>

Home Page

4 | 5 | export default Home; -------------------------------------------------------------------------------- /web/src/components/SomeSpinner/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SomeSpinner = () => ( 4 | 5 |
6 |

Loading...

7 |
8 | ); 9 | 10 | export default SomeSpinner; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reactjs-auth 2 | Autenticação de Rotas no React.JS com Context API - sem conexão com um servidor ( apenas simulando uma autenticação). 3 | 4 | ![usabilidade](https://github.com/cassiopieroni/reactjs-auth/blob/master/github/routing.gif) 5 | -------------------------------------------------------------------------------- /web/src/hooks/useAuth.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import AuthContext from '../contexts/auth'; 4 | 5 | const useAuth = () => { 6 | 7 | const auth = useContext(AuthContext); 8 | return auth; 9 | } 10 | 11 | export default useAuth; -------------------------------------------------------------------------------- /web/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/extend-expect'; 6 | -------------------------------------------------------------------------------- /web/src/Pages/Dashboard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | const Dashboard = () => { 5 | 6 | return ( 7 |
8 |

Dashboard Page

9 |

Private route

10 |
11 | ); 12 | } 13 | 14 | export default Dashboard; -------------------------------------------------------------------------------- /web/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | import './index.css'; 7 | 8 | 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | -------------------------------------------------------------------------------- /web/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 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /web/.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 | -------------------------------------------------------------------------------- /web/src/services/auth.js: -------------------------------------------------------------------------------- 1 | export const signInService = () => { 2 | 3 | return new Promise( resolve => { 4 | 5 | setTimeout( () => { 6 | resolve({ 7 | token: 'jk12h3j21h3jk212h3jk12h3jkh12j3kh12k123hh21g3f12f3', 8 | user: { 9 | name: 'admin', 10 | email: 'admin@authApp.com', 11 | } 12 | }); 13 | }, 2000); 14 | }); 15 | } -------------------------------------------------------------------------------- /web/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter } from "react-router-dom"; 3 | 4 | import { AuthProvider } from './contexts/auth'; 5 | 6 | import Layout from './Layout'; 7 | import Routes from './Routes/'; 8 | 9 | 10 | function App() { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /web/src/Routes/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route } from "react-router-dom"; 3 | 4 | import PrivateRoute from '../Routes/PrivateRoute'; 5 | 6 | import Home from '../Pages/Home'; 7 | import SignIn from '../Pages/SignIn'; 8 | import Dashboard from '../Pages/Dashboard'; 9 | import User from '../Pages/User'; 10 | 11 | 12 | const Routes = () => ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | export default Routes; -------------------------------------------------------------------------------- /web/src/Routes/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Redirect, Route } from 'react-router-dom'; 3 | import useAuth from '../hooks/useAuth'; 4 | 5 | import SomeSpinner from '../components/SomeSpinner'; 6 | 7 | 8 | const PrivateRoute = ({ component: Component, ...props }) => { 9 | 10 | const { signed, loading } = useAuth(); 11 | 12 | if (loading) { 13 | return 14 | } 15 | 16 | return ( 17 | 18 | signed 21 | ? 22 | : 23 | } 24 | /> 25 | ) 26 | } 27 | 28 | export default PrivateRoute; -------------------------------------------------------------------------------- /web/src/Pages/User/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | 3 | import useAuth from '../../hooks/useAuth'; 4 | 5 | import SomeSpinner from '../../components/SomeSpinner'; 6 | 7 | 8 | const User = () => { 9 | 10 | const { user, signOut, loading } = useAuth(); 11 | 12 | const handleSignOut = useCallback( () => { 13 | signOut(); 14 | }, [signOut]); 15 | 16 | 17 | if (loading) { 18 | return ( 19 | <> 20 |

User page

21 |

{ user.name }

22 | 23 | 24 | ) 25 | } 26 | 27 | return ( 28 |
29 |

User

30 |

{ user.name }

31 | 32 |
33 | ); 34 | } 35 | 36 | export default User; -------------------------------------------------------------------------------- /web/src/Layout/styles.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | min-height: 100vh; 3 | min-width: 100%; 4 | } 5 | 6 | .layout header { 7 | 8 | padding: 20px 30px; 9 | display: flex; 10 | justify-content: space-between; 11 | border-bottom: 1px solid black; 12 | } 13 | 14 | .layout header div a { 15 | 16 | margin: 8px; 17 | } 18 | 19 | .layout main { 20 | 21 | height: 100%; 22 | padding: 100px 30px; 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | } 27 | 28 | .layout main h1, .layout main h2, .layout main p { 29 | 30 | text-align: center; 31 | margin: 8px; 32 | } 33 | 34 | .layout main button { 35 | 36 | padding: 8px 16px; 37 | text-align: center; 38 | margin: 8px auto; 39 | cursor: pointer; 40 | margin-left: 12px; 41 | } 42 | 43 | .layout main .loading { 44 | display: flex; 45 | flex-direction: column; 46 | } -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teste", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "react": "^16.13.1", 10 | "react-dom": "^16.13.1", 11 | "react-router-dom": "^5.2.0", 12 | "react-scripts": "3.4.1" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /web/src/Layout/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | import useAuth from '../hooks/useAuth'; 5 | 6 | import './styles.css'; 7 | 8 | 9 | const Layout = ({ children }) => { 10 | 11 | const { signed, user } = useAuth(); 12 | 13 | return ( 14 | 15 |
16 |
17 | 18 |
19 | Home 20 | Dashboard 21 |
22 | 23 | { signed 24 | ? { user.name } 25 | : sign in 26 | } 27 | 28 |
29 | 30 |
31 | 32 | { children } 33 | 34 |
35 |
36 | ) 37 | } 38 | 39 | export default Layout; -------------------------------------------------------------------------------- /web/src/Pages/SignIn/index.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | 3 | import { Redirect } from 'react-router-dom'; 4 | 5 | import useAuth from '../../hooks/useAuth'; 6 | 7 | import SomeSpinner from '../../components/SomeSpinner'; 8 | 9 | 10 | const SignIn = () => { 11 | 12 | const { signed, signIn, loading } = useAuth(); 13 | 14 | const handleSignIn = useCallback( () => { 15 | signIn(); 16 | }, [signIn]); 17 | 18 | 19 | if (loading) { 20 | return ( 21 |
22 |

SignIn Page

23 | 24 |
25 | ) 26 | } 27 | 28 | 29 | return ( 30 | 31 |
32 | { signed ? ( 33 | 34 | ) : ( 35 | <> 36 |

SignIn Page

37 | 38 | 39 | )} 40 |
41 | ); 42 | } 43 | export default SignIn; -------------------------------------------------------------------------------- /web/src/hooks/useLocalStorage.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback } from 'react'; 2 | 3 | 4 | const useLocalStorage = (key, defaultValue = null) => { 5 | 6 | const storageValue = localStorage.getItem(key); 7 | const initValue = storageValue ? JSON.parse(storageValue) : null; 8 | 9 | const [value, setValue] = useState(initValue); 10 | useEffect( () => { 11 | 12 | if (defaultValue) { 13 | localStorage.setItem(key, JSON.stringify(defaultValue)) 14 | setValue(defaultValue) 15 | } 16 | }, []); 17 | 18 | 19 | const updatingValue = useCallback( newValue => { 20 | 21 | localStorage.setItem(key, JSON.stringify(newValue)); 22 | return setValue(newValue); 23 | }, [key]); 24 | 25 | 26 | const removingValue = useCallback( () => { 27 | 28 | localStorage.removeItem(key); 29 | return setValue(null); 30 | }, [key]); 31 | 32 | 33 | return [ 34 | value, 35 | valueToUp => updatingValue(valueToUp), 36 | () => removingValue() 37 | ] 38 | } 39 | 40 | export default useLocalStorage; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cássio Pieroni 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 26 | AuthApp 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /web/src/contexts/auth.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useEffect, useCallback } from 'react'; 2 | import { signInService } from '../services/auth'; 3 | import useLocalStorage from '../hooks/useLocalStorage'; 4 | 5 | 6 | const AuthContext = createContext({}); 7 | 8 | 9 | export const AuthProvider = ({ children }) => { 10 | 11 | const [storageUser, setStorageUser, removeStorageUser] = useLocalStorage('@authApp: user'); 12 | const [storageToken, setStorageToken, removeStorageToken] = useLocalStorage('@authApp: token'); 13 | 14 | const [user, setUser] = useState({}); 15 | const [loading, setLoading] = useState(true); 16 | 17 | 18 | useEffect(() => { 19 | 20 | if (storageUser && storageToken) { 21 | setUser(storageUser); 22 | // api.defaults.headers.Authorization = `Baerer ${storagedToken}`; 23 | } 24 | setLoading(false); 25 | }, []); 26 | 27 | 28 | const signIn = useCallback( async () => { 29 | 30 | setLoading(true); 31 | const response = await signInService(); 32 | setUser(response.user); 33 | // api.defaults.headers.Authorization = `Baerer ${response.token}`; 34 | setStorageUser(response.user); 35 | setStorageToken(response.token); 36 | setLoading(false); 37 | }, []); 38 | 39 | 40 | const signOut = useCallback( () => { 41 | 42 | setLoading(true); 43 | removeStorageUser(); 44 | removeStorageToken() 45 | setUser({}); 46 | setLoading(false); 47 | }, []); 48 | 49 | 50 | return ( 51 | 52 | 59 | { children } 60 | 61 | ) 62 | } 63 | 64 | 65 | export default AuthContext; --------------------------------------------------------------------------------