├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
├── robots.txt
└── undraw_page_not_found_su7k.svg
└── src
├── App.css
├── App.js
├── App.test.js
├── components
├── Blockui.js
├── GlobalStyles.js
├── MockApi.js
├── MockApi.test.js
├── Page.js
└── Page.test.js
├── contexts
└── AuthContext.js
├── index.css
├── index.js
├── layouts
└── DashboardLayout
│ ├── NavBar
│ ├── NavItem.js
│ └── index.js
│ ├── TopBar.js
│ └── index.js
├── logo.svg
├── serviceWorker.js
├── setupTest.js
├── theme
├── index.js
├── shadows.js
└── typography.js
└── views
├── Dashboard.js
├── Homepage.js
├── Login.js
├── NotFoundPage.js
└── SignIn.js
/.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 | ###### Türkçe bilgilendirmeyi aşağıda bulabilirsiniz.
2 | # React Authentication Using Context Api
3 |
4 | Context was came after React version 16.3. In react, data is passed top-down (Parent to child) via props. When you want to pass data from Layout to one component where inside a few layer. You have to need pass props to every layer componentes. Context is primarily used when some data needs to be accessible by many components at different nesting levels. In this expamle, Demonstrates how to use context in authenticate.
5 |
6 | You can learn more information about context in [here](https://reactjs.org/docs/context.html)
7 |
8 | ## Installation
9 |
10 | If you want to clone your local environment. You can use below command
11 |
12 | ```bash
13 | git clone https://github.com/mbozkaya/react-authentication-with-context-api.git
14 | ```
15 |
16 | ## Live Preview
17 |
18 | You can click [here](https://sad-kalam-2fe6f8.netlify.app/) to look at the live preview.
19 |
20 | ## Usage
21 |
22 | In this example, You able to use these features which login, signup and logout. Also I want to say, I've done login and signup requests use client side. If you want to look at this you can clik [here](https://github.com/mbozkaya/react-authentication-with-context-api/blob/main/src/components/MockApi.js)
23 |
24 | ## Contributing
25 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
26 |
27 | Please make sure to update tests as appropriate.
28 |
29 | ## License
30 | [MIT](https://choosealicense.com/licenses/mit/)
31 |
32 | #
33 | #
34 |
35 | # Context Kullanarak React Authentication Yapılandırması
36 |
37 | Context özelliği React'ın 16.3 versiyonundan sonra gelmiştir. Bu özellik sayesinde artık componentler arası state paylaşımı basit hale gelmiştir. React'ta bilindiği üzere componentler arası veri paylaşımı yukarıdan aşağıya yani parenttan child'a doğrudur ve aksi yönde veri paylaşmak için farklı yöntemler kullanılmaktadır. Ayrıca veriyi paylaşmak için en üst componentten hedef component'e kadar her component veriyi içeri aktarmak zorundadır. Context özelliğiyle birlikte bu zorunluluk ortadan kalkmış ve Oluşturulan bir context, useContext metoduyla her yerden erişilebilir hale gelmiştir. Bu sayede veri paylaşımı basit hale gelmiştir. Bu örnekte Context yaklaşımıyla authentication yapılandırmasını göreceksiniz.
38 |
39 | Context hakkında daha fazla bilgi almak için [burayı](https://reactjs.org/docs/context.html) ziyaret edebilirsiniz.
40 |
41 | ## Canlı Örnek
42 |
43 | [Buraya](https://sad-kalam-2fe6f8.netlify.app/) tıklayarak canlı örneğe göz atabilirsiniz.
44 |
45 | ## Kullanım
46 |
47 | Basit bir şekilde web sitesine giriş yapma, çıkış yapma ve kaydolma özelliklerini kullanabilirsiniz. Tamamıyla client tarafında çalışan bir örnek olduğu için ön tarafta sahte bir api requesti kullanmaktadır. Sahte apiyi incelemek için [buraya](https://github.com/mbozkaya/react-authentication-with-context-api/blob/main/src/components/MockApi.js) tıklayabilirsiniz.
48 |
49 |
50 | ## Katılımcı Olmak
51 |
52 | Bir düzeltme yada geliştirme yapmak için rahatlıkla çekme isteği(PR yada MR) oluşturabilirsiniz.
53 |
54 | ## Lisans
55 | [MIT](https://choosealicense.com/licenses/mit/)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.11.0",
7 | "@material-ui/icons": "^4.9.1",
8 | "@material-ui/lab": "^4.0.0-alpha.56",
9 | "@material-ui/styles": "^4.10.0",
10 | "@testing-library/jest-dom": "^4.2.4",
11 | "@testing-library/react": "^9.5.0",
12 | "@testing-library/user-event": "^7.2.1",
13 | "clsx": "^1.1.1",
14 | "formik": "^2.1.5",
15 | "history": "^5.0.0",
16 | "prop-types": "^15.7.2",
17 | "react": "^16.14.0",
18 | "react-dom": "^16.14.0",
19 | "react-feather": "^2.0.8",
20 | "react-helmet": "^6.1.0",
21 | "react-router": "^6.0.0-beta.0",
22 | "react-router-dom": "^6.0.0-beta.0",
23 | "react-scripts": "3.4.3",
24 | "yup": "^0.29.3"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": "react-app"
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | },
47 | "devDependencies": {
48 | "enzyme": "^3.11.0",
49 | "enzyme-adapter-react-16": "^1.15.5",
50 | "enzyme-to-json": "^3.6.1",
51 | "react-test-renderer": "^17.0.1"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbozkaya/react-authentication-with-context-api/905d20bcea70eef72c6fa16e9d83c2e8466540bf/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbozkaya/react-authentication-with-context-api/905d20bcea70eef72c6fa16e9d83c2e8466540bf/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbozkaya/react-authentication-with-context-api/905d20bcea70eef72c6fa16e9d83c2e8466540bf/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 |
--------------------------------------------------------------------------------
/public/undraw_page_not_found_su7k.svg:
--------------------------------------------------------------------------------
1 | page not found
--------------------------------------------------------------------------------
/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/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ThemeProvider } from '@material-ui/core';
3 | import './App.css';
4 | import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
5 | import { AuthProvider, AuthRoute } from './contexts/AuthContext';
6 | import HomePage from './views/Homepage';
7 | import Dashboard from './views/Dashboard';
8 | import Login from './views/Login';
9 | import theme from './theme';
10 | import GlobalStyles from './components/GlobalStyles';
11 | import Blockui from './components/Blockui';
12 | import SignIn from './views/SignIn';
13 | import NotFoundView from './views/NotFoundPage';
14 |
15 |
16 | function App() {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 | } />
25 | } />
26 | } />
27 | } />
28 | } />
29 | } />
30 |
31 |
32 |
33 |
34 | );
35 | }
36 |
37 | export default App;
38 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme';
2 | import React from 'react';
3 | import App from './App';
4 | import Adapter from 'enzyme-adapter-react-16';
5 | import { configure } from 'enzyme';
6 | configure({ adapter: new Adapter() });
7 | it("renders without crashing", () => {
8 | shallow( );
9 | });
10 |
11 | it("renders Account header", () => {
12 | const wrapper = shallow( );
13 | const welcome =
;
14 | expect(wrapper.contains(welcome)).toEqual(false);
15 | });
--------------------------------------------------------------------------------
/src/components/Blockui.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from 'react';
2 | import Backdrop from '@material-ui/core/Backdrop';
3 | import CircularProgress from '@material-ui/core/CircularProgress';
4 | import { makeStyles } from '@material-ui/core/styles';
5 | import { AuthContext } from '../contexts/AuthContext';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | backdrop: {
9 | zIndex: theme.zIndex.drawer + 1,
10 | color: '#fff',
11 | },
12 | }));
13 |
14 | const Blockui = () => {
15 | const authProvider = useContext(AuthContext);
16 | const classes = useStyles();
17 | const [open, setOpen] = useState(false);
18 | useEffect(() => {
19 | setOpen(authProvider.backdrop);
20 | }, [authProvider.backdrop])
21 | return (
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | export default Blockui;
31 |
32 |
--------------------------------------------------------------------------------
/src/components/GlobalStyles.js:
--------------------------------------------------------------------------------
1 | import { createStyles, makeStyles } from '@material-ui/core';
2 |
3 | const useStyles = makeStyles(() => createStyles({
4 | '@global': {
5 | '*': {
6 | boxSizing: 'border-box',
7 | margin: 0,
8 | padding: 0,
9 | },
10 | html: {
11 | '-webkit-font-smoothing': 'antialiased',
12 | '-moz-osx-font-smoothing': 'grayscale',
13 | height: '100%',
14 | width: '100%'
15 | },
16 | body: {
17 | backgroundColor: '#f4f6f8',
18 | height: '100%',
19 | width: '100%'
20 | },
21 | a: {
22 | textDecoration: 'none'
23 | },
24 | '#root': {
25 | height: '100%',
26 | width: '100%'
27 | }
28 | }
29 | }));
30 |
31 | const GlobalStyles = () => {
32 | useStyles();
33 |
34 | return null;
35 | };
36 |
37 | export default GlobalStyles;
38 |
--------------------------------------------------------------------------------
/src/components/MockApi.js:
--------------------------------------------------------------------------------
1 | export default class MockApi {
2 | constructor(defaultUsers = [], latency = 500) {
3 | this.defaultUsers = defaultUsers;
4 | this.latency = latency;
5 | this.setUsers(defaultUsers);
6 | }
7 |
8 | getAllUsers() {
9 | let users = JSON.parse(localStorage.getItem('mockApiUsers'));
10 | if (!Array.isArray(users)) {
11 | users = Array(users);
12 | }
13 | return users;
14 | }
15 |
16 | setUsers(users) {
17 | if (!Array.isArray(users)) {
18 | users = Array(users);
19 | }
20 | return localStorage.setItem('mockApiUsers', JSON.stringify(users));
21 | }
22 |
23 | signInUser(username, password) {
24 | return new Promise(resolve => {
25 | setTimeout(() => {
26 | let apiUsers = [];
27 | apiUsers = this.getAllUsers();
28 | let user = apiUsers.find((user) => user.username === username);
29 | if (user) {
30 | resolve({ error: true, message: 'There is a user who have same credentials' });
31 | } else {
32 | apiUsers.push({ username, password });
33 | this.setUsers(apiUsers);
34 | resolve({ error: false });
35 | }
36 | }, this.latency);
37 | });
38 | }
39 |
40 | authenticate(username, password) {
41 | let users = this.getAllUsers();
42 | if (users.length === 0) {
43 | throw new Error('Empty User Store');
44 | }
45 | return new Promise((resolve, reject) => {
46 | setTimeout(() => {
47 | users = this.getAllUsers();
48 | let user = users.find((user) => user.username === username && user.password === password);
49 | if (user) {
50 | resolve({ error: false, data: users });
51 | } else {
52 | resolve({ error: true, data: 'Invalid User Credentials' });
53 | }
54 | }, this.latency);
55 | });
56 | }
57 |
58 | checkAuthenticate() {
59 | const email = localStorage.getItem('email');
60 | const password = localStorage.getItem('password');
61 | const users = this.getAllUsers();
62 | let user = users.find((user) => user.username === email && user.password === password);
63 |
64 | if (user) {
65 | return true;
66 | }
67 |
68 | return false;
69 | }
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/src/components/MockApi.test.js:
--------------------------------------------------------------------------------
1 | import Adapter from 'enzyme-adapter-react-16';
2 | import { configure, shallow } from 'enzyme';
3 | import { MockApi } from './MockApi';
4 | configure({ adapter: new Adapter() });
5 |
6 | jest.mock('./MockApi', () => {
7 | // Works and lets you check for constructor calls:
8 | return {
9 | MockApi: jest.fn().mockImplementation(() => {
10 | return {
11 | getAllUSers: () => [],
12 | setUsers: () => undefined,
13 | };
14 | }),
15 | };
16 | });
17 |
18 | beforeEach(() => {
19 | MockApi.mockClear();
20 | });
21 |
22 |
23 |
24 | it('requires be an array to get all users method result', () => {
25 | const mockApi = new MockApi();
26 | expect(mockApi.getAllUSers()).toEqual([]);
27 | });
28 |
29 | it('requires set users as an array', () => {
30 | const mockApi = new MockApi();
31 | expect(mockApi.setUsers()).toEqual(undefined);
32 | });
33 |
34 |
--------------------------------------------------------------------------------
/src/components/Page.js:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 | import { Helmet } from 'react-helmet';
3 | import PropTypes from 'prop-types';
4 |
5 | const Page = forwardRef(({
6 | children,
7 | title = '',
8 | ...rest
9 | }, ref) => {
10 | return (
11 |
15 |
16 | {title}
17 |
18 | {children}
19 |
20 | );
21 | });
22 |
23 | Page.propTypes = {
24 | children: PropTypes.node.isRequired,
25 | title: PropTypes.string
26 | };
27 |
28 | export default Page;
--------------------------------------------------------------------------------
/src/components/Page.test.js:
--------------------------------------------------------------------------------
1 | import { mount, shallow } from 'enzyme';
2 | import React from 'react';
3 | import Adapter from 'enzyme-adapter-react-16';
4 | import { configure } from 'enzyme';
5 | import Page from './Page';
6 | configure({ adapter: new Adapter() });
7 |
8 | it('renders without crashing', () => {
9 | shallow(
)
10 | });
11 |
12 | it('requires render with children', () => {
13 | const children =
;
14 | const wrapper = shallow({children} );
15 | expect(wrapper.contains(children)).toEqual(true);
16 | });
17 |
18 | it('requires contain children props', () => {
19 | const children = this is children
;
20 | const wrapper = mount({children} );
21 | expect(wrapper.props().children).toEqual(children);
22 | });
--------------------------------------------------------------------------------
/src/contexts/AuthContext.js:
--------------------------------------------------------------------------------
1 | import React, { useLayoutEffect, useState } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Navigate, Route } from 'react-router';
4 | import MockApi from '../components/MockApi';
5 |
6 |
7 | const AuthContext = React.createContext();
8 |
9 | const AuthProvider = props => {
10 | const [contextState, setContextState] = useState({
11 | authorize: false,
12 | checkAuth: false,
13 | backdrop: false,
14 | error: false,
15 | success: false,
16 | });
17 |
18 | const { children } = props;
19 | const mockApi = new MockApi(JSON.parse(localStorage.getItem('mockApiUsers')) || { username: 'test@test.com', password: 'test123' }, 1000);
20 |
21 | const onLogin = model => {
22 | setContextState({
23 | ...contextState,
24 | backdrop: true,
25 | });
26 | mockApi.authenticate(model.email, model.password)
27 | .then(response => {
28 | if (response.error === false) {
29 | localStorage.setItem('email', model.email);
30 | localStorage.setItem('password', model.password);
31 | setContextState({
32 | authorize: true,
33 | checkAuth: true,
34 | backdrop: false,
35 | error: false,
36 | });
37 | console.clear();
38 | } else {
39 | setContextState({
40 | authorize: false,
41 | checkAuth: true,
42 | backdrop: false,
43 | error: true,
44 | });
45 | }
46 | });
47 | };
48 |
49 | const onLogout = () => {
50 | localStorage.removeItem('email');
51 | localStorage.removeItem('password');
52 | setContextState({
53 | ...contextState,
54 | authorize: false,
55 | checkAuth: true,
56 | });
57 | };
58 |
59 | const signIn = model => {
60 | setContextState({
61 | ...contextState,
62 | backdrop: true,
63 | });
64 | mockApi.signInUser(model.email, model.password).then(response => {
65 | if (response && response.error === false) {
66 | setContextState({
67 | ...contextState,
68 | backdrop: false,
69 | success: true,
70 | error:false,
71 | });
72 | } else {
73 | setContextState({
74 | ...contextState,
75 | backdrop: false,
76 | error: true,
77 | success: false,
78 | });
79 | }
80 | });
81 | }
82 |
83 | useLayoutEffect(() => {
84 | const auth = mockApi.checkAuthenticate();
85 | if (auth !== contextState.authorize) {
86 | setContextState({
87 | ...contextState,
88 | authorize: auth,
89 | checkAuth: true,
90 | });
91 | }
92 | // eslint-disable-next-line react-hooks/exhaustive-deps
93 | }, []);
94 |
95 | return (
96 |
108 | {children}
109 |
110 | );
111 | }
112 |
113 | const AuthRoute = ({ component: Component, ...rest }) => (
114 |
115 | {({ authorize, checkAuth }) => {
116 | let content = '';
117 |
118 | if (authorize) {
119 | content = (
120 | (
122 |
123 | )}
124 | {...rest}
125 | />
126 | );
127 | } else if (checkAuth && !authorize) {
128 | console.log('You must be login')
129 | content = ;
130 | }
131 | return content;
132 | }}
133 |
134 | );
135 |
136 | AuthProvider.propTypes = {
137 | children: PropTypes.node.isRequired,
138 | };
139 |
140 | export { AuthContext, AuthProvider, AuthRoute };
141 |
142 |
--------------------------------------------------------------------------------
/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 * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render(
8 | ,
9 | document.getElementById('root')
10 | );
11 |
12 | // If you want your app to work offline and load faster, you can change
13 | // unregister() to register() below. Note this comes with some pitfalls.
14 | // Learn more about service workers: https://bit.ly/CRA-PWA
15 | serviceWorker.unregister();
16 |
--------------------------------------------------------------------------------
/src/layouts/DashboardLayout/NavBar/NavItem.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink as RouterLink } from 'react-router-dom';
3 | import clsx from 'clsx';
4 | import PropTypes from 'prop-types';
5 | import {
6 | Button,
7 | ListItem,
8 | makeStyles
9 | } from '@material-ui/core';
10 |
11 | const useStyles = makeStyles((theme) => ({
12 | item: {
13 | display: 'flex',
14 | paddingTop: 0,
15 | paddingBottom: 0
16 | },
17 | button: {
18 | color: theme.palette.text.secondary,
19 | fontWeight: theme.typography.fontWeightMedium,
20 | justifyContent: 'flex-start',
21 | letterSpacing: 0,
22 | padding: '10px 8px',
23 | textTransform: 'none',
24 | width: '100%'
25 | },
26 | icon: {
27 | marginRight: theme.spacing(1)
28 | },
29 | title: {
30 | marginRight: 'auto'
31 | },
32 | active: {
33 | color: theme.palette.primary.main,
34 | '& $title': {
35 | fontWeight: theme.typography.fontWeightMedium
36 | },
37 | '& $icon': {
38 | color: theme.palette.primary.main
39 | }
40 | }
41 | }));
42 |
43 | const NavItem = ({
44 | className,
45 | href,
46 | icon: Icon,
47 | title,
48 | ...rest
49 | }) => {
50 | const classes = useStyles();
51 |
52 | return (
53 |
58 |
64 | {Icon && (
65 |
69 | )}
70 |
71 | {title}
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | NavItem.propTypes = {
79 | className: PropTypes.string,
80 | href: PropTypes.string,
81 | icon: PropTypes.elementType,
82 | title: PropTypes.string
83 | };
84 |
85 | export default NavItem;
86 |
--------------------------------------------------------------------------------
/src/layouts/DashboardLayout/NavBar/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { Link as RouterLink, useLocation } from 'react-router-dom';
3 | import PropTypes from 'prop-types';
4 | import {
5 | Avatar,
6 | Box,
7 | Divider,
8 | Drawer,
9 | Hidden,
10 | Typography,
11 | makeStyles
12 | } from '@material-ui/core';
13 |
14 | const user = {
15 | avatar: '/static/images/avatars/avatar_6.png',
16 | jobTitle: 'Developer',
17 | name: localStorage.getItem('email'),
18 | };
19 |
20 |
21 | const useStyles = makeStyles(() => ({
22 | mobileDrawer: {
23 | width: 256
24 | },
25 | desktopDrawer: {
26 | width: 256,
27 | top: 64,
28 | height: 'calc(100% - 64px)'
29 | },
30 | avatar: {
31 | cursor: 'pointer',
32 | width: 64,
33 | height: 64
34 | }
35 | }));
36 |
37 | const NavBar = ({ onMobileClose, openMobile }) => {
38 | const classes = useStyles();
39 | const location = useLocation();
40 |
41 | useEffect(() => {
42 | if (openMobile && onMobileClose) {
43 | onMobileClose();
44 | }
45 | // eslint-disable-next-line react-hooks/exhaustive-deps
46 | }, [location.pathname]);
47 |
48 | const content = (
49 |
54 |
60 |
66 |
71 | {user.name}
72 |
73 |
77 | {user.jobTitle}
78 |
79 |
80 |
81 |
82 | );
83 |
84 | return (
85 | <>
86 |
87 |
94 | {content}
95 |
96 |
97 |
98 |
104 | {content}
105 |
106 |
107 | >
108 | );
109 | };
110 |
111 | NavBar.propTypes = {
112 | onMobileClose: PropTypes.func,
113 | openMobile: PropTypes.bool
114 | };
115 |
116 | NavBar.defaultProps = {
117 | onMobileClose: () => {},
118 | openMobile: false
119 | };
120 |
121 | export default NavBar;
122 |
--------------------------------------------------------------------------------
/src/layouts/DashboardLayout/TopBar.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link as RouterLink } from 'react-router-dom';
3 | import clsx from 'clsx';
4 | import PropTypes from 'prop-types';
5 | import {
6 | AppBar,
7 | Badge,
8 | Box,
9 | Hidden,
10 | IconButton,
11 | Toolbar,
12 | makeStyles,
13 | Dialog,
14 | DialogActions,
15 | DialogContent,
16 | DialogContentText,
17 | DialogTitle,
18 | Button,
19 | } from '@material-ui/core';
20 | import MenuIcon from '@material-ui/icons/Menu';
21 | import NotificationsIcon from '@material-ui/icons/NotificationsOutlined';
22 | import InputIcon from '@material-ui/icons/Input';
23 | import logo from '../../logo.svg';
24 | import { AuthContext } from '../../contexts/AuthContext';
25 |
26 | const useStyles = makeStyles(() => ({
27 | root: {},
28 | avatar: {
29 | width: 60,
30 | height: 60
31 | }
32 | }));
33 |
34 | const TopBar = ({
35 | className,
36 | onMobileNavOpen,
37 | ...rest
38 | }) => {
39 | const classes = useStyles();
40 | const [notifications] = useState([]);
41 | const [dialogOpen, setDialogOpen] = useState(false);
42 |
43 | return (
44 |
45 | {
46 | ({ authorize, onLogin, onLogout }) => (
47 |
52 |
53 |
54 |
55 |
56 |
57 |
62 |
63 |
64 |
65 | setDialogOpen(true)}>
66 |
67 |
68 |
69 |
70 |
74 |
75 |
76 |
77 |
78 | setDialogOpen(false)}>
79 | Are you sure?
80 |
81 | Are you want to quit?
82 |
83 |
84 | setDialogOpen(false)}>
85 | Cancel
86 |
87 | {
88 | setDialogOpen(false);
89 | onLogout();
90 | }}>
91 | Yes
92 |
93 |
94 |
95 |
96 | )
97 | }
98 |
99 | );
100 | };
101 |
102 | TopBar.propTypes = {
103 | className: PropTypes.string,
104 | onMobileNavOpen: PropTypes.func
105 | };
106 |
107 | export default TopBar;
108 |
--------------------------------------------------------------------------------
/src/layouts/DashboardLayout/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { makeStyles } from '@material-ui/core';
3 | import NavBar from './NavBar';
4 | import TopBar from './TopBar';
5 | import propsTypes from 'prop-types';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | root: {
9 | backgroundColor: theme.palette.background.dark,
10 | display: 'flex',
11 | height: '100%',
12 | overflow: 'hidden',
13 | width: '100%'
14 | },
15 | wrapper: {
16 | display: 'flex',
17 | flex: '1 1 auto',
18 | overflow: 'hidden',
19 | paddingTop: 64,
20 | [theme.breakpoints.up('lg')]: {
21 | paddingLeft: 256
22 | }
23 | },
24 | contentContainer: {
25 | display: 'flex',
26 | flex: '1 1 auto',
27 | overflow: 'hidden'
28 | },
29 | content: {
30 | flex: '1 1 auto',
31 | height: '100%',
32 | overflow: 'auto'
33 | }
34 | }));
35 |
36 | const DashboardLayout = props => {
37 | const classes = useStyles();
38 | const [isMobileNavOpen, setMobileNavOpen] = useState(false);
39 |
40 | return (
41 |
42 |
setMobileNavOpen(true)} />
43 | setMobileNavOpen(false)}
45 | openMobile={isMobileNavOpen}
46 | />
47 |
48 |
49 |
50 | {props.children}
51 |
52 |
53 |
54 |
55 | );
56 | };
57 |
58 | export default DashboardLayout;
59 |
60 | DashboardLayout.propsType = {
61 | children: propsTypes.node.isRequired,
62 | };
63 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl, {
104 | headers: { 'Service-Worker': 'script' },
105 | })
106 | .then(response => {
107 | // Ensure service worker exists, and that we really are getting a JS file.
108 | const contentType = response.headers.get('content-type');
109 | if (
110 | response.status === 404 ||
111 | (contentType != null && contentType.indexOf('javascript') === -1)
112 | ) {
113 | // No service worker found. Probably a different app. Reload the page.
114 | navigator.serviceWorker.ready.then(registration => {
115 | registration.unregister().then(() => {
116 | window.location.reload();
117 | });
118 | });
119 | } else {
120 | // Service worker found. Proceed as normal.
121 | registerValidSW(swUrl, config);
122 | }
123 | })
124 | .catch(() => {
125 | console.log(
126 | 'No internet connection found. App is running in offline mode.'
127 | );
128 | });
129 | }
130 |
131 | export function unregister() {
132 | if ('serviceWorker' in navigator) {
133 | navigator.serviceWorker.ready
134 | .then(registration => {
135 | registration.unregister();
136 | })
137 | .catch(error => {
138 | console.error(error.message);
139 | });
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/setupTest.js:
--------------------------------------------------------------------------------
1 | import { configure } from "enzyme";
2 | import Adapter from "enzyme-adapter-react-16";
3 | configure({ adapter: new Adapter() });
4 |
5 |
--------------------------------------------------------------------------------
/src/theme/index.js:
--------------------------------------------------------------------------------
1 | import { createMuiTheme, colors } from '@material-ui/core';
2 | import shadows from './shadows';
3 | import typography from './typography';
4 |
5 | const theme = createMuiTheme({
6 | palette: {
7 | background: {
8 | dark: '#F4F6F8',
9 | default: colors.common.white,
10 | paper: colors.common.white
11 | },
12 | primary: {
13 | main: colors.indigo[500]
14 | },
15 | secondary: {
16 | main: colors.indigo[500]
17 | },
18 | text: {
19 | primary: colors.blueGrey[900],
20 | secondary: colors.blueGrey[600]
21 | }
22 | },
23 | shadows,
24 | typography
25 | });
26 |
27 | export default theme;
28 |
--------------------------------------------------------------------------------
/src/theme/shadows.js:
--------------------------------------------------------------------------------
1 | export default [
2 | 'none',
3 | '0 0 0 1px rgba(63,63,68,0.05), 0 1px 2px 0 rgba(63,63,68,0.15)',
4 | '0 0 1px 0 rgba(0,0,0,0.31), 0 2px 2px -2px rgba(0,0,0,0.25)',
5 | '0 0 1px 0 rgba(0,0,0,0.31), 0 3px 4px -2px rgba(0,0,0,0.25)',
6 | '0 0 1px 0 rgba(0,0,0,0.31), 0 3px 4px -2px rgba(0,0,0,0.25)',
7 | '0 0 1px 0 rgba(0,0,0,0.31), 0 4px 6px -2px rgba(0,0,0,0.25)',
8 | '0 0 1px 0 rgba(0,0,0,0.31), 0 4px 6px -2px rgba(0,0,0,0.25)',
9 | '0 0 1px 0 rgba(0,0,0,0.31), 0 4px 8px -2px rgba(0,0,0,0.25)',
10 | '0 0 1px 0 rgba(0,0,0,0.31), 0 5px 8px -2px rgba(0,0,0,0.25)',
11 | '0 0 1px 0 rgba(0,0,0,0.31), 0 6px 12px -4px rgba(0,0,0,0.25)',
12 | '0 0 1px 0 rgba(0,0,0,0.31), 0 7px 12px -4px rgba(0,0,0,0.25)',
13 | '0 0 1px 0 rgba(0,0,0,0.31), 0 6px 16px -4px rgba(0,0,0,0.25)',
14 | '0 0 1px 0 rgba(0,0,0,0.31), 0 7px 16px -4px rgba(0,0,0,0.25)',
15 | '0 0 1px 0 rgba(0,0,0,0.31), 0 8px 18px -8px rgba(0,0,0,0.25)',
16 | '0 0 1px 0 rgba(0,0,0,0.31), 0 9px 18px -8px rgba(0,0,0,0.25)',
17 | '0 0 1px 0 rgba(0,0,0,0.31), 0 10px 20px -8px rgba(0,0,0,0.25)',
18 | '0 0 1px 0 rgba(0,0,0,0.31), 0 11px 20px -8px rgba(0,0,0,0.25)',
19 | '0 0 1px 0 rgba(0,0,0,0.31), 0 12px 22px -8px rgba(0,0,0,0.25)',
20 | '0 0 1px 0 rgba(0,0,0,0.31), 0 13px 22px -8px rgba(0,0,0,0.25)',
21 | '0 0 1px 0 rgba(0,0,0,0.31), 0 14px 24px -8px rgba(0,0,0,0.25)',
22 | '0 0 1px 0 rgba(0,0,0,0.31), 0 16px 28px -8px rgba(0,0,0,0.25)',
23 | '0 0 1px 0 rgba(0,0,0,0.31), 0 18px 30px -8px rgba(0,0,0,0.25)',
24 | '0 0 1px 0 rgba(0,0,0,0.31), 0 20px 32px -8px rgba(0,0,0,0.25)',
25 | '0 0 1px 0 rgba(0,0,0,0.31), 0 22px 34px -8px rgba(0,0,0,0.25)',
26 | '0 0 1px 0 rgba(0,0,0,0.31), 0 24px 36px -8px rgba(0,0,0,0.25)'
27 | ];
28 |
--------------------------------------------------------------------------------
/src/theme/typography.js:
--------------------------------------------------------------------------------
1 | export default {
2 | h1: {
3 | fontWeight: 500,
4 | fontSize: 35,
5 | letterSpacing: '-0.24px'
6 | },
7 | h2: {
8 | fontWeight: 500,
9 | fontSize: 29,
10 | letterSpacing: '-0.24px'
11 | },
12 | h3: {
13 | fontWeight: 500,
14 | fontSize: 24,
15 | letterSpacing: '-0.06px'
16 | },
17 | h4: {
18 | fontWeight: 500,
19 | fontSize: 20,
20 | letterSpacing: '-0.06px'
21 | },
22 | h5: {
23 | fontWeight: 500,
24 | fontSize: 16,
25 | letterSpacing: '-0.05px'
26 | },
27 | h6: {
28 | fontWeight: 500,
29 | fontSize: 14,
30 | letterSpacing: '-0.05px'
31 | },
32 | overline: {
33 | fontWeight: 500
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/src/views/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Container,
4 | Grid,
5 | makeStyles
6 | } from '@material-ui/core';
7 | import Page from '../components/Page';
8 | import DashboardLayout from '../layouts/DashboardLayout/index';
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | root: {
12 | backgroundColor: theme.palette.background.dark,
13 | minHeight: '100%',
14 | paddingBottom: theme.spacing(3),
15 | paddingTop: theme.spacing(3)
16 | }
17 | }));
18 |
19 | const Dashboard = () => {
20 |
21 | const classes = useStyles();
22 |
23 | return (
24 |
25 |
29 |
30 |
34 |
41 | Welcome to Dashboard
42 |
43 |
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | export default Dashboard;
--------------------------------------------------------------------------------
/src/views/Homepage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from '../logo.svg';
3 | import '../App.css';
4 | import { Link } from 'react-router-dom';
5 | import { AuthContext } from '../contexts/AuthContext';
6 | import { makeStyles } from '@material-ui/core';
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | wrapper: {
10 | paddingLeft:theme.spacing(3),
11 | }
12 | }));
13 |
14 | function HomePage() {
15 | const styles = useStyles();
16 |
17 | return (
18 |
19 | {
20 | ({ authorize }) => (
21 |
33 | )
34 | }
35 |
36 |
37 | );
38 | }
39 |
40 | export default HomePage;
--------------------------------------------------------------------------------
/src/views/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Navigate } from 'react-router';
3 | import { AuthContext } from '../contexts/AuthContext';
4 | import * as Yup from 'yup';
5 | import { Formik } from 'formik';
6 | import {
7 | Box,
8 | Button,
9 | Container,
10 | TextField,
11 | makeStyles
12 | } from '@material-ui/core';
13 | import { Alert, AlertTitle } from '@material-ui/lab';
14 | import Page from '../components/Page';
15 |
16 | const useStyles = makeStyles((theme) => ({
17 | root: {
18 | backgroundColor: theme.palette.background.dark,
19 | height: '100%',
20 | paddingBottom: theme.spacing(3),
21 | paddingTop: theme.spacing(3),
22 | },
23 | alert: {
24 | width: '100%',
25 | '& > * + *': {
26 | marginTop: theme.spacing(2),
27 | },
28 | }
29 | }));
30 |
31 |
32 |
33 | const Login = () => {
34 | const classes = useStyles();
35 | return (
36 |
37 | {({ authorize, onLogin, error }) => (
38 | (authorize ? :
39 |
43 |
49 |
50 | {
60 | onLogin(model);
61 | }}
62 | >
63 | {({
64 | errors,
65 | handleBlur,
66 | handleChange,
67 | handleSubmit,
68 | touched,
69 | values
70 | }) => (
71 |
115 | )}
116 |
117 |
118 |
119 |
120 | )
121 | )}
122 |
123 | );
124 | }
125 |
126 | export default Login;
--------------------------------------------------------------------------------
/src/views/NotFoundPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Box,
4 | Container,
5 | Typography,
6 | makeStyles
7 | } from '@material-ui/core';
8 | import Page from '../components/Page';
9 |
10 | const useStyles = makeStyles((theme) => ({
11 | root: {
12 | backgroundColor: theme.palette.background.dark,
13 | height: '100%',
14 | paddingBottom: theme.spacing(3),
15 | paddingTop: theme.spacing(3)
16 | },
17 | image: {
18 | marginTop: 50,
19 | display: 'inline-block',
20 | maxWidth: '100%',
21 | width: 560
22 | }
23 | }));
24 |
25 | const NotFoundView = () => {
26 | const classes = useStyles();
27 |
28 | return (
29 |
33 |
39 |
40 |
45 | 404: The page you are looking for isn’t here
46 |
47 |
52 | You either tried some shady route or you came here by mistake.
53 | Whichever it is, try using the navigation
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 | );
66 | };
67 |
68 | export default NotFoundView;
69 |
--------------------------------------------------------------------------------
/src/views/SignIn.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link as RouterLink, Navigate, useNavigate } from 'react-router-dom';
3 | import { AuthContext } from '../contexts/AuthContext';
4 | import * as Yup from 'yup';
5 | import { Formik } from 'formik';
6 | import {
7 | Box,
8 | Button,
9 | Container,
10 | TextField,
11 | makeStyles,
12 | Typography,
13 | Checkbox,
14 | FormHelperText,
15 | Link
16 | } from '@material-ui/core';
17 | import { Alert, AlertTitle } from '@material-ui/lab';
18 | import Snackbar from '@material-ui/core/Snackbar';
19 | import Page from '../components/Page';
20 |
21 | const useStyles = makeStyles((theme) => ({
22 | root: {
23 | backgroundColor: theme.palette.background.dark,
24 | height: '100%',
25 | paddingBottom: theme.spacing(3),
26 | paddingTop: theme.spacing(3)
27 | }
28 | }));
29 |
30 | const SignIn = () => {
31 | const classes = useStyles();
32 | const navigate = useNavigate();
33 | const [snackbar, setSnackbar] = useState(false);
34 |
35 | const handleNavigate = () => {
36 | setSnackbar(true);
37 | setTimeout(() => {
38 | navigate('/login', { replace: true });
39 | }, 2000);
40 | }
41 |
42 | return (
43 |
44 | {
45 | ({ authorize, signIn, error, success }) =>
46 | (authorize ? ( ) : (
47 |
51 | {
52 | success && handleNavigate()
53 | }
54 |
60 |
61 | {
79 | const { email, password } = model;
80 | signIn({ email, password });
81 | }}
82 | >
83 | {({
84 | errors,
85 | handleBlur,
86 | handleChange,
87 | handleSubmit,
88 | isSubmitting,
89 | touched,
90 | values
91 | }) => (
92 |
208 | )}
209 |
210 |
211 |
212 |
213 |
setSnackbar(false)}>
214 | setSnackbar(false)} severity="success">
215 | You will redirect for 2 seconds
216 |
217 |
218 |
219 |
220 | ))
221 | }
222 |
223 | );
224 | };
225 |
226 | export default SignIn;
--------------------------------------------------------------------------------