├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── _redirects
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.js
├── assets
├── logo.svg
└── main.svg
├── components
├── FormRow.js
└── Navbar.js
├── context.js
├── index.css
├── index.js
├── pages
├── Dashboard.js
├── Error.js
├── ForgotPassword.js
├── Home.js
├── Login.js
├── ProtectedRoute.js
├── Register.js
├── ResetPassword.js
├── Verify.js
└── index.js
└── utils
├── localState.js
└── url.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 | #### Proxy
2 |
3 | "proxy": "https://user-workflow-11.herokuapp.com"
4 | "proxy": "http://localhost:5000"
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "email-workflow-front-end",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.14.1",
7 | "@testing-library/react": "^11.2.7",
8 | "@testing-library/user-event": "^12.8.3",
9 | "axios": "^0.21.1",
10 | "normalize.css": "^8.0.1",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "react-icons": "^4.2.0",
14 | "react-router-dom": "^5.2.1",
15 | "react-scripts": "4.0.3",
16 | "styled-components": "^5.3.1",
17 | "web-vitals": "^1.1.2"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "CI= react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | },
43 | "proxy": "http://localhost:5000"
44 | }
45 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /api/* https://user-workflow-11.herokuapp.com/api/:splat 200
2 |
3 | /* /index.html 200
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-smilga/react-node-user-workflow-front-end/6052147b23c0d98401b89cc77c080f8741353791/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | Auth Workflow
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-smilga/react-node-user-workflow-front-end/6052147b23c0d98401b89cc77c080f8741353791/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/john-smilga/react-node-user-workflow-front-end/6052147b23c0d98401b89cc77c080f8741353791/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.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
2 | import {
3 | Home,
4 | Error,
5 | Register,
6 | Login,
7 | Verify,
8 | Dashboard,
9 | ProtectedRoute,
10 | ForgotPassword,
11 | ResetPassword,
12 | } from './pages';
13 | import Navbar from './components/Navbar';
14 | import { useGlobalContext } from './context';
15 | function App() {
16 | const { isLoading } = useGlobalContext();
17 | if (isLoading) {
18 | return (
19 |
22 | );
23 | }
24 | return (
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 | export default App;
58 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/assets/main.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/FormRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const FormRow = ({ type, name, value, handleChange }) => {
4 | return (
5 |
6 |
7 | {name}
8 |
9 |
16 |
17 | );
18 | };
19 |
20 | export default FormRow;
21 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import logo from '../assets/logo.svg';
4 | import { Link } from 'react-router-dom';
5 | import { useGlobalContext } from '../context';
6 |
7 | const Navbar = () => {
8 | const { user, logoutUser } = useGlobalContext();
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | {user && (
16 |
17 |
hello, {user.name}
18 |
{
21 | logoutUser();
22 | }}
23 | >
24 | logout
25 |
26 |
27 | )}
28 |
29 |
30 | );
31 | };
32 |
33 | const Wrapper = styled.nav`
34 | background: var(--white);
35 | height: 6rem;
36 | display: flex;
37 | align-items: center;
38 | justify-content: center;
39 | .nav-center {
40 | width: var(--fluid-width);
41 | max-width: var(--max-width);
42 | display: flex;
43 | justify-content: space-between;
44 | align-items: center;
45 | flex-wrap: wrap;
46 | }
47 | .nav-links {
48 | display: flex;
49 | flex-direction: column;
50 | }
51 | .nav-links p {
52 | margin: 0;
53 | text-transform: capitalize;
54 | margin-bottom: 0.25rem;
55 | }
56 | .home-link {
57 | display: flex;
58 | align-items: flex-end;
59 | }
60 | @media (min-width: 776px) {
61 | .nav-links {
62 | flex-direction: row;
63 | align-items: center;
64 | }
65 | .nav-links p {
66 | margin: 0;
67 | margin-right: 1.5rem;
68 | }
69 | } ;
70 | `;
71 |
72 | export default Navbar;
73 |
--------------------------------------------------------------------------------
/src/context.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import React, { useContext, useState, useEffect } from 'react';
3 | import url from './utils/url';
4 | const AppContext = React.createContext();
5 |
6 | const AppProvider = ({ children }) => {
7 | const [isLoading, setIsLoading] = useState(true);
8 | const [user, setUser] = useState(null);
9 | const saveUser = (user) => {
10 | setUser(user);
11 | };
12 |
13 | const removeUser = () => {
14 | setUser(null);
15 | };
16 |
17 | const fetchUser = async () => {
18 | try {
19 | const { data } = await axios.get(`/api/v1/users/showMe`);
20 | saveUser(data.user);
21 | } catch (error) {
22 | removeUser();
23 | }
24 | setIsLoading(false);
25 | };
26 |
27 | const logoutUser = async () => {
28 | try {
29 | await axios.delete('/api/v1/auth/logout');
30 | removeUser();
31 | } catch (error) {
32 | console.log(error);
33 | }
34 | };
35 |
36 | useEffect(() => {
37 | fetchUser();
38 | }, []);
39 |
40 | return (
41 |
49 | {children}
50 |
51 | );
52 | };
53 | // make sure use
54 | export const useGlobalContext = () => {
55 | return useContext(AppContext);
56 | };
57 |
58 | export { AppProvider };
59 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | *,
2 | ::after,
3 | ::before {
4 | box-sizing: border-box;
5 | }
6 | /* fonts */
7 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600&family=Montserrat&display=swap');
8 |
9 | html {
10 | font-size: 100%;
11 | } /*16px*/
12 |
13 | :root {
14 | /* colors */
15 | --primary-100: #e2e0ff;
16 | --primary-200: #c1beff;
17 | --primary-300: #a29dff;
18 | --primary-400: #837dff;
19 | --primary-500: #645cff;
20 | --primary-600: #504acc;
21 | --primary-700: #3c3799;
22 | --primary-800: #282566;
23 | --primary-900: #141233;
24 |
25 | /* grey */
26 | --grey-50: #f8fafc;
27 | --grey-100: #f1f5f9;
28 | --grey-200: #e2e8f0;
29 | --grey-300: #cbd5e1;
30 | --grey-400: #94a3b8;
31 | --grey-500: #64748b;
32 | --grey-600: #475569;
33 | --grey-700: #334155;
34 | --grey-800: #1e293b;
35 | --grey-900: #0f172a;
36 | /* rest of the colors */
37 | --black: #222;
38 | --white: #fff;
39 | --red-light: #f8d7da;
40 | --red-dark: #842029;
41 | --green-light: #d1e7dd;
42 | --green-dark: #0f5132;
43 |
44 | /* fonts */
45 | --headingFont: 'Roboto', sans-serif;
46 | --bodyFont: 'Nunito', sans-serif;
47 | --smallText: 0.7em;
48 | /* rest of the vars */
49 | --backgroundColor: var(--grey-50);
50 | --textColor: var(--grey-900);
51 | --borderRadius: 0.25rem;
52 | --letterSpacing: 1px;
53 | --transition: 0.3s ease-in-out all;
54 | --max-width: 1120px;
55 | --fixed-width: 500px;
56 | --fluid-width: 90vw;
57 |
58 | /* box shadow*/
59 | --shadow-1: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
60 | --shadow-2: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
61 | 0 2px 4px -1px rgba(0, 0, 0, 0.06);
62 | --shadow-3: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
63 | 0 4px 6px -2px rgba(0, 0, 0, 0.05);
64 | --shadow-4: 0 20px 25px -5px rgba(0, 0, 0, 0.1),
65 | 0 10px 10px -5px rgba(0, 0, 0, 0.04);
66 | }
67 |
68 | body {
69 | background: var(--backgroundColor);
70 | font-family: var(--bodyFont);
71 | font-weight: 400;
72 | line-height: 1.75;
73 | color: var(--textColor);
74 | }
75 |
76 | p {
77 | margin-bottom: 1.5rem;
78 | max-width: 40em;
79 | }
80 |
81 | h1,
82 | h2,
83 | h3,
84 | h4,
85 | h5 {
86 | margin: 0;
87 | margin-bottom: 1.38rem;
88 | font-family: var(--headingFont);
89 | font-weight: 400;
90 | line-height: 1.3;
91 | text-transform: capitalize;
92 | letter-spacing: var(--letterSpacing);
93 | }
94 |
95 | h1 {
96 | margin-top: 0;
97 | font-size: 3.052rem;
98 | }
99 |
100 | h2 {
101 | font-size: 2.441rem;
102 | }
103 |
104 | h3 {
105 | font-size: 1.953rem;
106 | }
107 |
108 | h4 {
109 | font-size: 1.563rem;
110 | }
111 |
112 | h5 {
113 | font-size: 1.25rem;
114 | }
115 |
116 | small,
117 | .text-small {
118 | font-size: var(--smallText);
119 | }
120 |
121 | a {
122 | text-decoration: none;
123 | }
124 | ul {
125 | list-style-type: none;
126 | padding: 0;
127 | }
128 |
129 | .img {
130 | width: 100%;
131 | display: block;
132 | object-fit: cover;
133 | }
134 | /* buttons */
135 |
136 | .btn {
137 | cursor: pointer;
138 | color: var(--white);
139 | background: var(--primary-500);
140 | border: transparent;
141 | border-radius: var(--borderRadius);
142 | letter-spacing: var(--letterSpacing);
143 | padding: 0.375rem 0.75rem;
144 | box-shadow: var(--shadow-1);
145 | transition: var(--transition);
146 | text-transform: capitalize;
147 | display: inline-block;
148 | }
149 | .btn:hover {
150 | background: var(--primary-700);
151 | box-shadow: var(--shadow-3);
152 | }
153 | .btn-hipster {
154 | color: var(--primary-500);
155 | background: var(--primary-200);
156 | }
157 | .btn-hipster:hover {
158 | color: var(--primary-200);
159 | background: var(--primary-700);
160 | }
161 | .btn-block {
162 | width: 100%;
163 | }
164 | .btn-small {
165 | padding: 0.25rem 0.5rem;
166 | font-size: 0.75rem;
167 | }
168 | .btn:disabled {
169 | cursor: not-allowed;
170 | }
171 | /* alerts */
172 | .alert {
173 | padding: 0.375rem 0.75rem;
174 | margin: 0 auto;
175 | border-color: transparent;
176 | border-radius: var(--borderRadius);
177 | width: var(--fluid-width);
178 | max-width: var(--fixed-width);
179 | text-align: center;
180 | text-transform: capitalize;
181 | }
182 |
183 | .alert-danger {
184 | color: var(--red-dark);
185 | background: var(--red-light);
186 | }
187 | .alert-success {
188 | color: var(--green-dark);
189 | background: var(--green-light);
190 | }
191 | /* form */
192 |
193 | .form {
194 | width: var(--fluid-width);
195 | max-width: var(--fixed-width);
196 | background: var(--white);
197 | border-radius: var(--borderRadius);
198 | box-shadow: var(--shadow-2);
199 | padding: 2rem 2.5rem;
200 | margin: 3rem auto;
201 | position: relative;
202 | }
203 | .form-label {
204 | display: block;
205 | font-size: var(--smallText);
206 | margin-bottom: 0.5rem;
207 | text-transform: capitalize;
208 | letter-spacing: var(--letterSpacing);
209 | }
210 | .form-input,
211 | .form-textarea {
212 | width: 100%;
213 | padding: 0.375rem 0.75rem;
214 | border-radius: var(--borderRadius);
215 | background: var(--backgroundColor);
216 | border: 1px solid var(--grey-200);
217 | }
218 |
219 | .form-row {
220 | margin-bottom: 1rem;
221 | }
222 |
223 | .form-textarea {
224 | height: 7rem;
225 | }
226 | ::placeholder {
227 | font-family: inherit;
228 | color: var(--grey-400);
229 | }
230 | .form-alert {
231 | color: var(--red-dark);
232 | letter-spacing: var(--letterSpacing);
233 | text-transform: capitalize;
234 | }
235 | /* alert */
236 |
237 | @keyframes spinner {
238 | to {
239 | transform: rotate(360deg);
240 | }
241 | }
242 |
243 | .loading {
244 | width: 6rem;
245 | height: 6rem;
246 | border: 5px solid var(--grey-400);
247 | border-radius: 50%;
248 | border-top-color: var(--primary-500);
249 | animation: spinner 0.6s linear infinite;
250 | margin: 0 auto;
251 | }
252 | .loading {
253 | margin: 0 auto;
254 | }
255 | /* title */
256 |
257 | .title {
258 | text-align: center;
259 | }
260 |
261 | .title-underline {
262 | background: var(--primary-500);
263 | width: 7rem;
264 | height: 0.25rem;
265 | margin: 0 auto;
266 | margin-top: -1rem;
267 | }
268 | .page {
269 | min-height: calc(100vh - 6rem);
270 | width: var(--fluid-width);
271 | max-width: var(--max-width);
272 | margin: 0 auto;
273 | padding-top: 3rem;
274 | }
275 | .page-center {
276 | min-height: 100vh;
277 | display: grid;
278 | place-items: center;
279 | }
280 | .form-loading::after {
281 | content: '';
282 | position: absolute;
283 | top: 0;
284 | left: 0;
285 | border-radius: var(--borderRadius);
286 | width: 100%;
287 | height: 100%;
288 | background: rgba(255, 255, 255, 0.6);
289 | cursor: not-allowed;
290 | }
291 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import 'normalize.css';
5 | import App from './App';
6 | import { AppProvider } from './context';
7 | ReactDOM.render(
8 |
9 |
10 |
11 |
12 | ,
13 | document.getElementById('root')
14 | );
15 |
--------------------------------------------------------------------------------
/src/pages/Dashboard.js:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import styled from 'styled-components';
3 | import main from '../assets/main.svg';
4 | import { Redirect } from 'react-router-dom';
5 | import { useGlobalContext } from '../context';
6 | function Dashboard() {
7 | const { user } = useGlobalContext();
8 | const { name, userId, role } = user;
9 | return (
10 | <>
11 |
12 | Hello there, {user.name}
13 |
14 | Your ID : {userId}
15 |
16 |
17 | Your Role : {role}
18 |
19 |
20 | >
21 | );
22 | }
23 |
24 | const Wrapper = styled.div`
25 | p span {
26 | background: var(--primary-500);
27 | padding: 0.15rem 0.25rem;
28 | color: var(--white);
29 | border-radius: var(--borderRadius);
30 | letter-spacing: var(--letterSpacing);
31 | }
32 | `;
33 |
34 | export default Dashboard;
35 |
--------------------------------------------------------------------------------
/src/pages/Error.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Link } from 'react-router-dom';
4 | const Error = () => {
5 | return (
6 |
7 |
8 |
404
9 | page not found
10 |
11 | Back Home
12 |
13 |
14 |
15 | );
16 | };
17 |
18 | const Wrapper = styled.main`
19 | text-align: center;
20 | padding-top: 5rem;
21 | h1 {
22 | font-size: 9rem;
23 | }
24 | `;
25 |
26 | export default Error;
27 |
--------------------------------------------------------------------------------
/src/pages/ForgotPassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import styled from 'styled-components';
3 | import { Link } from 'react-router-dom';
4 | import FormRow from '../components/FormRow';
5 | import axios from 'axios';
6 | import useLocalState from '../utils/localState';
7 |
8 | const ForgotPassword = () => {
9 | const [email, setEmail] = useState('');
10 | const {
11 | alert,
12 | showAlert,
13 | loading,
14 | setLoading,
15 | success,
16 | setSuccess,
17 | hideAlert,
18 | } = useLocalState();
19 |
20 | const handleChange = (e) => {
21 | setEmail(e.target.value);
22 | };
23 |
24 | const handleSubmit = async (e) => {
25 | e.preventDefault();
26 | setLoading(true);
27 | hideAlert();
28 | if (!email) {
29 | showAlert({
30 | text: 'Please provide email',
31 | });
32 | setLoading(false);
33 | return;
34 | }
35 | try {
36 | const { data } = await axios.post('/api/v1/auth/forgot-password', {
37 | email,
38 | });
39 | showAlert({ text: data.msg, type: 'success' });
40 | setSuccess(true);
41 | } catch (error) {
42 | showAlert({
43 | text: 'Something went wrong, please try again',
44 | });
45 | setSuccess(true);
46 | }
47 | setLoading(false);
48 | };
49 | return (
50 |
51 | {alert.show && (
52 | {alert.text}
53 | )}
54 | {!success && (
55 |
78 | )}
79 |
80 | );
81 | };
82 |
83 | const Wrapper = styled.main`
84 | h4,
85 | p {
86 | text-align: center;
87 | }
88 | p {
89 | margin: 0;
90 | margin-top: 1rem;
91 | }
92 | .login-link {
93 | display: inline-block;
94 | margin-left: 0.25rem;
95 | text-transform: capitalize;
96 | color: var(--primary-500);
97 | cursor: pointer;
98 | }
99 | `;
100 |
101 | export default ForgotPassword;
102 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import { Link } from 'react-router-dom';
2 | import styled from 'styled-components';
3 | import main from '../assets/main.svg';
4 | import { Redirect } from 'react-router-dom';
5 | import { useGlobalContext } from '../context';
6 | function Home() {
7 | const { user } = useGlobalContext();
8 | return (
9 | <>
10 | {user && }
11 |
12 |
13 |
14 | Auth
15 | Workflow
16 |
17 |
18 | I'm baby viral enamel pin chartreuse cliche retro af selfies kinfolk
19 | photo booth plaid jianbing actually squid 3 wolf moon lumbersexual.
20 | Hell of humblebrag gluten-free lo-fi man braid leggings.
21 |
22 |
23 | Cloud bread kale chips wayfarers deep v chicharrones leggings
24 | fingerstache actually blog cliche four dollar toast. Sriracha ugh
25 | kickstarter, next level la croix butcher lomo.
26 |
27 |
28 |
29 | Login
30 |
31 |
32 | Register
33 |
34 |
35 |
36 |
37 | >
38 | );
39 | }
40 |
41 | const Wrapper = styled.div`
42 | display: grid;
43 | align-items: center;
44 | h2 {
45 | font-weight: 700;
46 | }
47 | h2 span {
48 | color: var(--primary-500);
49 | }
50 | .main-img {
51 | display: none;
52 | }
53 | @media (min-width: 992px) {
54 | grid-template-columns: 1fr 1fr;
55 | column-gap: 6rem;
56 | .main-img {
57 | display: block;
58 | }
59 | }
60 | .btn {
61 | margin-left: 0.25rem;
62 | margin-right: 0.25rem;
63 | }
64 | `;
65 |
66 | export default Home;
67 |
--------------------------------------------------------------------------------
/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import styled from 'styled-components';
3 | import { Link, useHistory, Redirect } from 'react-router-dom';
4 | import FormRow from '../components/FormRow';
5 | import { useGlobalContext } from '../context';
6 | import useLocalState from '../utils/localState';
7 |
8 | import axios from 'axios';
9 |
10 | function Login() {
11 | const { saveUser } = useGlobalContext();
12 | const history = useHistory();
13 | const [values, setValues] = useState({
14 | email: '',
15 | password: '',
16 | });
17 | const { alert, showAlert, loading, setLoading, hideAlert } = useLocalState();
18 |
19 | const handleChange = (e) => {
20 | setValues({ ...values, [e.target.name]: e.target.value });
21 | };
22 | const onSubmit = async (e) => {
23 | e.preventDefault();
24 | hideAlert();
25 | setLoading(true);
26 | const { email, password } = values;
27 | const loginUser = { email, password };
28 | try {
29 | const { data } = await axios.post(`/api/v1/auth/login`, loginUser);
30 | setValues({ name: '', email: '', password: '' });
31 | showAlert({
32 | text: `Welcome, ${data.user.name}. Redirecting to dashboard...`,
33 | type: 'success',
34 | });
35 | setLoading(false);
36 | saveUser(data.user);
37 | history.push('/dashboard');
38 | } catch (error) {
39 | showAlert({ text: error.response.data.msg });
40 | setLoading(false);
41 | }
42 | };
43 |
44 | return (
45 | <>
46 |
47 | {alert.show && (
48 | {alert.text}
49 | )}
50 |
86 |
87 | >
88 | );
89 | }
90 |
91 | const Wrapper = styled.section`
92 | .alert {
93 | margin-top: 3rem;
94 | }
95 | h4 {
96 | text-align: center;
97 | }
98 | p {
99 | margin: 0;
100 | text-align: center;
101 | }
102 | .btn {
103 | margin-bottom: 1.5rem;
104 | }
105 | .register-link,
106 | .reset-link {
107 | display: inline-block;
108 | margin-left: 0.25rem;
109 | text-transform: capitalize;
110 | color: var(--primary-500);
111 | cursor: pointer;
112 | }
113 | .reset-link {
114 | margin-top: 0.25rem;
115 | }
116 | .btn:disabled {
117 | cursor: not-allowed;
118 | }
119 | `;
120 |
121 | export default Login;
122 |
--------------------------------------------------------------------------------
/src/pages/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Redirect } from 'react-router-dom';
3 | // import { useUserContext } from '../context/user_context'
4 | import { useGlobalContext } from '../context';
5 |
6 | const PrivateRoute = ({ children, ...rest }) => {
7 | const { user } = useGlobalContext();
8 | return (
9 | {
12 | return user ? children : ;
13 | }}
14 | >
15 | );
16 | };
17 | export default PrivateRoute;
18 |
--------------------------------------------------------------------------------
/src/pages/Register.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import styled from 'styled-components';
3 | import { Link } from 'react-router-dom';
4 | import FormRow from '../components/FormRow';
5 | import axios from 'axios';
6 | import useLocalState from '../utils/localState';
7 |
8 | function Register() {
9 | const [values, setValues] = useState({
10 | name: '',
11 | email: '',
12 | password: '',
13 | });
14 |
15 | const {
16 | alert,
17 | showAlert,
18 | loading,
19 | setLoading,
20 | success,
21 | setSuccess,
22 | hideAlert,
23 | } = useLocalState();
24 |
25 | const handleChange = (e) => {
26 | setValues({ ...values, [e.target.name]: e.target.value });
27 | };
28 | const onSubmit = async (e) => {
29 | e.preventDefault();
30 | hideAlert();
31 | setLoading(true);
32 | const { name, email, password } = values;
33 | const registerNewUser = { name, email, password };
34 |
35 | try {
36 | const { data } = await axios.post(
37 | `/api/v1/auth/register`,
38 | registerNewUser
39 | );
40 |
41 | setSuccess(true);
42 | setValues({ name: '', email: '', password: '' });
43 | showAlert({ text: data.msg, type: 'success' });
44 | } catch (error) {
45 | const { msg } = error.response.data;
46 | showAlert({ text: msg });
47 | }
48 | setLoading(false);
49 | };
50 |
51 | return (
52 | <>
53 |
54 | {alert.show && (
55 | {alert.text}
56 | )}
57 | {!success && (
58 |
97 | )}
98 |
99 | >
100 | );
101 | }
102 |
103 | const Wrapper = styled.section`
104 | .alert {
105 | margin-top: 3rem;
106 | margin-bottom: -1.5rem;
107 | }
108 | h4 {
109 | text-align: center;
110 | }
111 | p {
112 | margin: 0;
113 | margin-top: 1rem;
114 | text-align: center;
115 | }
116 | .login-link {
117 | display: inline-block;
118 | margin-left: 0.25rem;
119 | text-transform: capitalize;
120 | color: var(--primary-500);
121 | cursor: pointer;
122 | }
123 | .btn:disabled {
124 | cursor: not-allowed;
125 | }
126 | `;
127 |
128 | export default Register;
129 |
--------------------------------------------------------------------------------
/src/pages/ResetPassword.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useLocation, useHistory, Link } from 'react-router-dom';
3 | import styled from 'styled-components';
4 | import axios from 'axios';
5 | import FormRow from '../components/FormRow';
6 | import useLocalState from '../utils/localState';
7 |
8 | function useQuery() {
9 | return new URLSearchParams(useLocation().search);
10 | }
11 |
12 | const ResetPasswordForm = () => {
13 | const history = useHistory();
14 | const [password, setPassword] = useState('');
15 | const { alert, showAlert, loading, setLoading, success, setSuccess } =
16 | useLocalState();
17 |
18 | const query = useQuery();
19 |
20 | const handleChange = async (e) => {
21 | setPassword(e.target.value);
22 | };
23 | const handleSubmit = async (e) => {
24 | e.preventDefault();
25 | setLoading(true);
26 | if (!password) {
27 | showAlert({ text: 'please enter password' });
28 | setLoading(false);
29 | return;
30 | }
31 | try {
32 | const { data } = await axios.post('/api/v1/auth/reset-password', {
33 | password,
34 | token: query.get('token'),
35 | email: query.get('email'),
36 | });
37 | setLoading(false);
38 | setSuccess(true);
39 | showAlert({
40 | text: `Success, redirecting to login page shortly`,
41 | type: 'success',
42 | });
43 | setTimeout(() => {
44 | history.push('/login');
45 | }, 3000);
46 | } catch (error) {
47 | showAlert({ text: error.response.data.msg });
48 | setLoading(false);
49 | }
50 | };
51 | return (
52 |
53 | {alert.show && (
54 | {alert.text}
55 | )}
56 | {!success && (
57 |
74 | )}
75 |
76 | );
77 | };
78 |
79 | const Wrapper = styled.section`
80 | h4,
81 | p {
82 | text-align: center;
83 | }
84 | p {
85 | margin: 0;
86 | margin-top: 1rem;
87 | }
88 | `;
89 |
90 | export default ResetPasswordForm;
91 |
--------------------------------------------------------------------------------
/src/pages/Verify.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useLocation, Link } from 'react-router-dom';
3 | import styled from 'styled-components';
4 | import { useGlobalContext } from '../context';
5 | import axios from 'axios';
6 | function useQuery() {
7 | return new URLSearchParams(useLocation().search);
8 | }
9 |
10 | const VerifyPage = () => {
11 | const [error, setError] = useState(false);
12 | const [loading, setLoading] = useState(false);
13 | const { isLoading } = useGlobalContext();
14 | const query = useQuery();
15 |
16 | const verifyToken = async () => {
17 | setLoading(true);
18 | try {
19 | const { data } = await axios.post('/api/v1/auth/verify-email', {
20 | verificationToken: query.get('token'),
21 | email: query.get('email'),
22 | });
23 | } catch (error) {
24 | // console.log(error.response);
25 | setError(true);
26 | }
27 | setLoading(false);
28 | };
29 |
30 | useEffect(() => {
31 | if (!isLoading) {
32 | verifyToken();
33 | }
34 | }, []);
35 |
36 | if (loading) {
37 | return (
38 |
39 | Loading...
40 |
41 | );
42 | }
43 |
44 | if (error) {
45 | return (
46 |
47 | There was an error, please double check your verification link
48 |
49 | );
50 | }
51 |
52 | return (
53 |
54 | Account Confirmed
55 |
56 | Please login
57 |
58 |
59 | );
60 | };
61 |
62 | const Wrapper = styled.section``;
63 |
64 | export default VerifyPage;
65 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import Home from './Home';
2 | import Error from './Error';
3 | import Register from './Register';
4 | import Login from './Login';
5 | import Verify from './Verify';
6 | import Dashboard from './Dashboard';
7 | import ProtectedRoute from './ProtectedRoute';
8 | import ForgotPassword from './ForgotPassword';
9 | import ResetPassword from './ResetPassword';
10 | export {
11 | Home,
12 | Error,
13 | Register,
14 | Login,
15 | Verify,
16 | Dashboard,
17 | ProtectedRoute,
18 | ForgotPassword,
19 | ResetPassword,
20 | };
21 |
--------------------------------------------------------------------------------
/src/utils/localState.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useLocalState = () => {
4 | const [alert, setAlert] = useState({
5 | show: false,
6 | text: '',
7 | type: 'danger',
8 | });
9 | const [loading, setLoading] = useState(false);
10 | const [success, setSuccess] = useState(false);
11 |
12 | const showAlert = ({ text, type = 'danger' }) => {
13 | setAlert({ show: true, text, type });
14 | };
15 | const hideAlert = () => {
16 | setAlert({ show: false, text: '', type: 'danger' });
17 | };
18 | return {
19 | alert,
20 | showAlert,
21 | loading,
22 | setLoading,
23 | success,
24 | setSuccess,
25 | hideAlert,
26 | };
27 | };
28 |
29 | export default useLocalState;
30 |
--------------------------------------------------------------------------------
/src/utils/url.js:
--------------------------------------------------------------------------------
1 | const url = 'http://localhost:5000';
2 |
3 | export default url;
4 |
--------------------------------------------------------------------------------