Create a React app with hat-js
Build a secure note app with React and @dataswift/hat-js
Using our Javascript SDK hat-js you can create data rich applications without writing a single line of backend server code. In this tutorial, you'll learn the fundamentals of using a Dataswift PDA, build on the HAT Microserver project, by making a fully functional app to store your private thoughts as Notes. When new notes get added to the app, the data will be stored in a HAT, in the cloud.
By the end of this tutorial you'll learn about:
  • Authenticating users
  • Register new users
  • Saving data to the HAT

Just the code (for the impatient)

Jump ahead by grabbing the code from our Github repo: git clone, yarn install and yarn start if you want to get hacking right away.

Set up your environment

Make sure your development environment includes Node.js and an npm package manager.
To check if you have Node.js installed, run this command in your terminal:
1
node -v
Copied!
To confirm that you have npm installed you can run this command in your terminal:
1
npm -v
Copied!

Install Create React App

You'll need to have Node v8.16.0 or Node v10.16.0 or later version on your machine.
1
npx create-react-app my-first-hat-app
Copied!
If you use npm 5.1 or earlier you can't use npx. Instead, install create-react-app globally:
1
npm install -g create-react-app
2
create-react-app my-first-hat-app
Copied!
And you can start by typing:
1
cd my-first-hat-app
2
npm start
Copied!
Then open http://localhost:3000/ to see your app.
Testing HATs are configured to support requests from https://localhost:3000 by default. If you run the project locally on a different port the requests will not come through by the CORS policy.

Install HAT-JS

Now you can install the HAT-JS:
1
npm install @dataswift/hat-js
Copied!
You'll need to restart the server every time you install a package using npm. You can use Ctrl + C to stop the server and npm start to start the server.

Create the main structure

Let's structure the project as following:
  • assets/: to keep all the styles and images.
  • app/: for the App component and Routing.
  • components/: for reusable React components.
  • features/: to group React components by feature.
  • utils/: to keep the helper functions.
Let's create the assets/, app/, components/, utils/ and features/ folders in the src/ folder. And move the App.js, App.css and App.test.js in the src/app/ folder.
The src/ folder structure should now look like this:
1
├───src/
2
│ ├───app/
3
│ │ ├───App.css
4
│ │ ├───App.js
5
│ │ └───App.test.js
6
│ ├───assets/
7
│ ├───components/
8
│ ├───features/
9
│ ├───utils/
10
│ ├───index.css
11
│ ├───index.js
12
│ ├───logo.svg
13
│ ├───serviceWorker.js
14
│ └───setupTests.js
Copied!

Assets

You can download the compressed folder with basic assets from here. This includes the background image and the basic CSS styles to use across the project.
Unzip folder and paste the files into the assets/ folder.
The images/ and the styles/ folder should look like this:
1
│ ├───assets/
2
│ │ ├───images/
3
│ │ │ └───background-image.jpg
4
│ │ └───styles/
5
│ │ ├───main.scss
6
│ │ ├───_base.scss
7
│ │ ├───_branding.scss
8
│ │ ├───_buttons.scss
9
│ │ ├───_flex.scss
10
│ │ ├───_inputs.scss
11
│ │ └───_texts.scss
Copied!
Install the node-sass to use Sass and SCSS:
1
npm install node-sass
Copied!
And add this line in the index.js:
1
// /index.js
2
import './assets/styles/main.scss';
Copied!
Also, include the material icons in the of the index.html file which is located in the public/ folder:
1
// public/index.html
2
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
Copied!

Main pages and Routing

For this application we'll need 4 routes:
  • "/" for the Home page.
  • "/login" for the Login page.
  • "/signup" for the Signup page.
  • "/authentication" to use it for the authentication callback URL.
First, we'll need to install the react-router and react-router-dom dependencies:
1
npm install react-router react-router-dom
Copied!
Create a new file Routing.js in the app/ folder and we can include the first Route to the Home page:
1
// src/app/Routing.js
2
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
3
import HomePage from "../features/Home/HomePage";
4
5
function Routing() {
6
return (
7
<Router>
8
<Switch>
9
<Route exact path="/" component={HomePage} />
10
</Switch>
11
</Router>
12
);
13
}
14
15
export default Routing;
Copied!

Basic components

Home Page

In the feature folder, create a Home/ subfolder in the src/features/ folder with the HomePage.js and HomePage.scss files:
1
// src/features/Home/HomePage.js
2
import React from 'react';
3
import './HomePage.scss';
4
5
function HomePage() {
6
7
return (
8
<div className="home-wrapper"></div>
9
);
10
}
11
12
export default HomePage;
Copied!
1
// src/features/Home/HomePage.scss
2
.home-wrapper {
3
min-height: 100%;
4
background-size: cover;
5
background-image: linear-gradient(to bottom, rgba(74, 85, 107, 0.2), rgba(0, 0, 0, 0.66)), url('../../assets/images/background-image.jpg');
6
-webkit-background-size: cover;
7
}
Copied!

Header

To add a basic navigation for the app, you'll need to create the Header component in the src/components/ folder. Let's create a subfolder header to include the JavaScript and CSS file.
1
// src/components/header/Header.js
2
import React from 'react';
3
import { NavLink } from 'react-router-dom';
4
import './Header.scss';
5
6
function Header() {
7
return (
8
<>
9
<header className="header">
10
<NavLink exact activeClassName="active" to="/">
11
Home
12
</NavLink>
13
14
<ul>
15
<li>
16
<NavLink activeClassName="active" to="/login">
17
Log in
18
</NavLink>
19
</li>
20
<li>
21
<NavLink activeClassName="active" to="/signup">
22
Create account
23
</NavLink>
24
</li>
25
</ul>
26
</header>
27
</>
28
);
29
}
30
31
export default Header;
Copied!
1
// src/components/header/Header.scss
2
header {
3
display: flex;
4
flex-direction: row;
5
justify-content: space-between;
6
align-items: center;
7
height: 6.4rem;
8
border-bottom: 1px solid #657690;
9
padding-left: 10%;
10
padding-right: 10%;
11
margin: 0 auto;
12
background: #efefef;
13
14
a {
15
font-size: 1.6rem;
16
text-decoration: none;
17
color: #4a556b;
18
outline: 0;
19
20
&:hover {
21
color: #252736;
22
}
23
}
24
25
ul {
26
list-style: none;
27
align-self: end;
28
height: 100%;
29
padding: 2rem;
30
31
li {
32
display: inline-block;
33
margin-left: 2rem;
34
}
35
}
36
}
Copied!
Now, we can import the Header component in the already created Routing.js:
1
// src/app/Routing.js
2
...
3
// Use the following syntax to import the Header
4
import Header from "../components/header/Header";
5
6
function Routing() {
7
return (
8
<Router>
9
{/* Add the Header component as following */}
10
<Header />
11
<Switch>
12
<Route exact path="/" component={HomePage} />
13
</Switch>
14
</Router>
15
);
16
}
Copied!
The home page should look like this now:

Login Page

With the same way let's create the Login page in the features/login/ folder:
1
// src/features/Login/LoginPage.js
2
import React from 'react';
3
4
function LoginPage() {
5
6
return (
7
<form className={'flex-column-wrapper flex-content-center flex-align-items-center'}>
8
<div className={'flex-spacer-small'} />
9
10
<h3>Log in</h3>
11
12
<div className={'flex-spacer-small'} />
13
14
<input
15
name={'username'}
16
type={'text'}
17
placeholder="Username"
18
autoComplete={'username'}
19
/>
20
21
<div className={'flex-spacer-large'} />
22
23
<button className={'btn btn-accent'} type={'submit'}>
24
Next
25
</button>
26
27
<div className={'flex-spacer-small'} />
28
</form>
29
);
30
}
31
32
export default LoginPage;
Copied!
And include the "/login" Route into the Routing.js:
1
// src/app/Routing.js
2
import LoginPage from "../features/Login/LoginPage";
3
4
function Routing() {
5
return (
6
<Router>
7
<Header />
8
<Switch>
9
<Route exact path="/" component={HomePage} />
10
<Route path="/login" component={LoginPage} />
11
</Switch>
12
</Router>
13
);
14
}
Copied!
We can access the Login page on "/login" route or by clicking on the "Login" button.
The HTML form elements work a bit differently from other DOM elements in React.As in HTML the form element keep its own internally state, in React we have the option to keep the state in the Component level. For convenience we can have JavaScript function to handle the input changes and the submission of the form.
Since React 16.8, we can easily set the state in function components as following:
1
// Declare a new state variable, which we'll call "username"
2
const [username, setUsername] = useState('');
Copied!
You can learn more about forms and hooks
1
// src/features/Login/LoginPage.js
2
import React, { useState } from 'react';
3
4
function LoginPage() {
5
const [username, setUsername] = useState('');
6
7
const handleChange = event => {
8
setUsername(event.target.value);
9
};
10
11
const handleSubmit = event => {
12
event.preventDefault();
13
};
14
15
return (
16
<form className={'flex-column-wrapper flex-content-center flex-align-items-center'}
17
onSubmit={e => handleSubmit(e)}
18
>
19
.
20
.
21
<input
22
.
23
.
24
value={username}
25
onChange={e => handleChange(e)}
26
/
27
.
28
.
29
</form>
30
);
31
}
Copied!
Next step is to add the validation for the username. Let's create a validation.js file in the utils/ folder.
The username must follow these rules:
  • start with a letter
  • contain alphanumeric characters only
  • contain lowercase letters only
  • be between 4 to 21 characters
1
// src/utils/validations.js
2
const HAT_NAME_REGEX = /^[a-z][a-z0-9]{2,19}[a-z0-9]$/;
3
4
export const isHatName = hatName => {
5
return HAT_NAME_REGEX.test(hatName);
6
};
Copied!
And you'll use this function to validate the user's input and display an error message when the username is not valid to submit. In the LoginPage.js we can validate the user's input and display an error message when the username is not valid to submit. In the onChange method we can reset the error message.
1
// src/features/Login/LoginPage.js
2
import React, { useState } from 'react';
3
import { isHatName } from "../../utils/validations";
4
5
function LoginPage() {
6
const [username, setUsername] = useState('');
7
const [errorMsg, setErrorMsg] = useState('');
8
9
const errorMessages = {
10
usernameNotValid: 'Sorry, this username is not valid'
11
};
12
13
const handleChange = event => {
14
setUsername(event.target.value);
15
// Reset the error message when the user is start typing.
16
setErrorMsg('');
17
};
18
19
const handleSubmit = event => {
20
event.preventDefault();
21
validateLoginDetails();
22
};
23
24
const validateLoginDetails = () => {
25
// Validate the username and display an error message if it's not valid.
26
if (isHatName(username)) {
27
// TODO redirect the user to their PDA.
28
} else {
29
setErrorMsg(errorMessages.usernameNotValid);
30
}
31
};
32
33
34
return (
35
<form
36
className={'flex-column-wrapper flex-content-center flex-align-items-center'}
37
onSubmit={e => handleSubmit(e)}
38
>
39
.
40
.
41
<input
42
className={` ${errorMsg ? 'input-error-field' : null}`}
43
.
44
.
45
/>
46
{errorMsg && <div className={'input-error-label'}>{errorMsg}</div>}
47
.
48
.
49
</form>
50
);
51
}
Copied!

Redirect the user to the Login page to get the Access token

First, we need to redirect the user to their PDA where they can enter their password to complete the authorization.
We will need to build the URL using our app ID, our callback URI and a URI in case of failure.
The URL will look similar to this:
1
https://<hat-url>/#/hatlogin?
2
name=<application-id>
3
&redirect=<redirect>
4
&fallback=<fallback>
Copied!
  • hat-url — The PDA’s unique URL. eg. testing.hubat.net
  • application-id — Our application ID. eg. testhatapp
  • redirect — The callback URI for our application. eg. http://localhost:3000/authentication
  • fallback — The URI will be called on authorization failure.
Lets create a configuration file app.config.js in the src/ folder:
1
// src/app.config.js
2
export const appConfig = {
3
applicationId: 'testhatapp',
4
namespace: 'testhatapp',
5
hatCluster: '.hubat.net',
6
hatApiVersion: 'v2.6',
7
secure: true,
8
};
Copied!
  • applicationId — The application ID.
  • namespace — The application's namespace.
  • hatCluster — The HAT's cluster for the login. In this case we are using '.hubat.net' for testing purposes only. For production you'd use a real non-dev-sandbox PDA.
  • hatApiVersion — The API version of the HAT.
  • secure — If you want to run the HAT locally, you have to modify this field to 'false'.
For better user experience, it's good practice to validate the user's input and navigate them to their PDA to Login only if their username is valid and exists. Otherwise we'll display an error message to inform the user about the issue. You can use the HAT-JS to check if a PDA with this username exists.
Let's create the redirectValidUser method to redirect an existing user. We can use the hat-js to check if the user exists:
1
// src/features/Login/LoginPage.js
2
import React, { useState } from 'react';
3
import { HatClient } from '@dataswift/hat-js';
4
import { isHatName } from "../../utils/validations";
5
import { appConfig } from "../../app.config";
6
7
function LoginPage() {
8
const [username, setUsername] = useState('');
9
const [errorMsg, setErrorMsg] = useState('');
10
11
const errorMessages = {
12
usernameNotValid: 'Sorry, this username is not valid',
13
usernameNotRecognised: 'Sorry, this username is not recognised'
14
};
15
16
const redirectValidUser = async (username) => {
17
try {
18
const hat = new HatClient({});
19
const hatDomain = username + appConfig.hatCluster;
20
// Use the hat-js's helper method to check if the user exists
21
const res = await hat.auth().isDomainRegistered(hatDomain);
22
// if the res is true, navigate the user to their PDA
23
if (res) {
24
const hatUrl = `https://${hatDomain}`;
25
const redirectUrl = `http://${window.location.host}/authentication`;
26
const fallback = `http://${window.location.host}/authentication`;
27
const applicationId = appConfig.applicationId;
28
29
window.location.href = hat.auth().generateHatLoginUrl(hatUrl, applicationId, redirectUrl, fallback);
30
} else {
31
// Display an error message when the username is not recognised
32
setErrorMsg(errorMessages.usernameNotRecognised);
33
}
34
} catch (e) {
35
console.log(e);
36
}
37
};
38
39
const validateLoginDetails = () => {
40
if (isHatName(username)) {
41
// Use the new method when the username is valid
42
redirectValidUser(username);
43
} else {
44
setErrorMsg(errorMessages.usernameNotValid);
45
}
46
};
47
48
49
return (
50
<form>
51
.
52
.
53
</form>
54
);
55
}
Copied!
Upon successful login, the HAT will verify if the application has been granted all the permissions it needs. If not, the HAT user will need to do so for the app to access their HAT.
Lastly, the user will be redirected back to our application with the authentication token attached. This should be stored for subsequent API calls:
1
http://localhost:3000/authentication?token=eyJ0eXAiOiJKV1Qi….
Copied!

Authenticate user

To collect the token parameter, we will need to create a new component in the Login feature

Create the authentication handler

In this example we will store the token value in the Session Storage. Upon successful login we will navigate the user back to the HomePage where we will need to add a different screen to display to the user when they are authenticated. In case of error, we will redirect the user back to the Login page.
1
// src/features/Login/AuthenticationHandler.js
2
import React, { useEffect } from 'react';
3
import { useHistory } from 'react-router';
4
import { getParameterByName } from "../../utils/windowHelper";
5
6
function AuthenticationHandler() {
7
const history = useHistory();
8
9
useEffect(() => {
10
const token = getParameterByName('token');
11
const error = getParameterByName('error');
12
13
if (error) {
14
history.push('/login');
15
}
16
17
if (token) {
18
sessionStorage.setItem('token', token);
19
history.push('/');
20
}
21
}, [history]);
22
23
return <div>Loading...</div>;
24
}
25
26
export default AuthenticationHandler;
Copied!

Get parameter by name

In order to get the query parameter value, let's create a new function getParameterByName in the utils/ folder:
1
// src/utils/windowHelper.js
2
export const getParameterByName = variable => {
3
const query = window.location.search.substring(1);
4
const vars = query.split('&');
5
6
for (let i = 0; i < vars.length; i++) {
7
const pair = vars[i].split('=');
8
if (pair[0] === variable) {
9
return decodeURIComponent(pair[1]);
10
}
11
}
12
return null;
13
};
Copied!
Import the function in the AuthenticationHandler.js:
1
// src/features/Login/AuthenticationHandler.js
2
import { getParameterByName } from "../../utils/windowHelper";
Copied!
Include the AuthenticationHandler in the Routing:
1
// src/app/Routing.js
2
import AuthenticationHandler from "../features/Login/AuthenticationHandler";
3
4
function Routing() {
5
return (
6
<Router>
7
<Header />
8
<Switch>
9
.
10
.
11
<Route path="/authentication" component={AuthenticationHandler} />
12
</Switch>
13
</Router>
14
);
15
}
Copied!

Create Signup Page

In the same way with the Login page, we can create a page for the registration.
For the registration, we need to collect the username and the email, and redirect to the Hatters to complete the registration.
Lets create the SignupPage.js in the Signup feature folder:
1
// src/features/Signup/SignupPage.js
2
import React, { useState } from 'react';
3
import { appConfig } from '../../app.config';
4
import { isEmail, isHatName } from '../../utils/validations';
5
6
function SignupPage() {
7
const initUser = {
8
username: '',
9
email: '',
10
};
11
12
const [user, setUser] = useState(initUser);
13
const [errorMsg, setErrorMsg] = useState('');
14
15
const errorMessages = {
16
usernameNotValid: 'Sorry, this username is not valid',
17
emailNotRecognised: 'Sorry, email is not valid',
18
};
19
20
const navigateToHatters = () => {
21
const redirectUrl = `http://${window.location.host}/authentication`;
22
const applicationId = appConfig.applicationId;
23
24
window.location.href = `https://hatters.dataswift.io/services/baas/signup?email=${user.email}&hat_name=${user.username}&application_id=${applicationId}&redirect_uri=${redirectUrl}&lang=en`;
25
};
26
27
const handleChange = event => {
28
const name = event.target.name;
29
const value = event.target.value;
30
31
setUser({ ...user, [name]: value });
32
setErrorMsg('');
33
};
34
35
const handleSubmit = event => {
36
event.preventDefault();
37
validateSignupDetails();
38
};
39
40
const validateSignupDetails = () => {
41
let errorMsg = '';
42
43
if (!isHatName(user.username)) {
44
errorMsg = errorMessages.usernameNotValid;
45
} else if (!isEmail(user.email)) {
46
errorMsg = errorMessages.emailNotRecognised;
47
} else {
48
navigateToHatters();
49
}
50
51
if (errorMsg) {
52
setErrorMsg(errorMsg);
53
}
54
};
55
56
return (
57
<form onSubmit={e => handleSubmit(e)} className={'flex-column-wrapper flex-content-center flex-align-items-center'}>
58
<div className={'flex-spacer-small'} />
59
60
<h3>Create Account</h3>
61
<div className={'flex-spacer-small'} />
62
63
<input
64
className={` ${errorMsg ? 'input-error-field' : null}`}
65
name={'username'}
66
type={'text'}
67
placeholder="Username"
68
autoComplete={'username'}
69
value={user.username}
70
onChange={e => handleChange(e)}
71
/>
72
<input
73
className={` ${errorMsg ? 'input-error-field' : null}`}
74
name={'email'}
75
type={'text'}
76
placeholder="Email"
77
autoComplete={'email'}
78
value={user.email}
79
onChange={e => handleChange(e)}
80
/>
81
{errorMsg && <div className={'input-error-label'}>{errorMsg}</div>}
82
83
<div className={'flex-spacer-large'} />
84
85
<button className={'btn btn-accent'} type={'submit'}>
86
Next
87
</button>
88
89
<div className={'flex-spacer-small'} />
90
</form>
91
);
92
}
93
94
export default SignupPage;
Copied!
In the validations.js file include a function to check if the Email is valid:
1
// src/utils/validations.js
2
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\[email protected]"]+(\.[^<>()[\]\\.,;:\[email protected]"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
3
4
export const isEmail = email => {
5
return EMAIL_REGEX.test(email);
6
};
Copied!
Include the "/signup" route in the Routing.js:
1
// src/app/Routing.js
2
function Routing() {
3
return (
4
<Router>
5
<Header />
6
<Switch>
7
<Route exact path="/" component={HomePage} />
8
<Route path="/login" component={LoginPage} />
9
<Route path="/signup" component={SignupPage} />
10
<Route path="/authentication" component={AuthenticationHandler} />
11
</Switch>
12
</Router>
13
);
14
}
15
16
export default Routing;
Copied!

Create a private Route for the authenticated username

In this case, we need to create a "global" state to keep information for the current authenticated user. With the Context we can create a simple state share across all the components.
We'll need to keep information such as a flag to check if the user is authenticated, the JWT token, and the username.
1
state = {
2
user: {
3
isAuthenticated: false,
4
token: '',
5
hatName: '',
6
},
7
}
Copied!
Firstly, we have to create the Context object. Create the AuthContext.js file in the context folder under the components/ folder:
1
// src/components/context/AuthContext.js
2
import React from 'react';
3
4
const AuthContext = React.createContext();
5
6
export default AuthContext;
Copied!
Secondly, create the Context Provider with state and helper function to log in/out. Create the AuthProvider.js:
1
// src/components/context/AuthProvider.js
2
import React, { Component } from 'react';
3
import AuthContext from './AuthContext';
4
import { HatTokenValidation } from '@dataswift/hat-js/lib/utils/HatTokenValidation';
5
6
export class AuthProvider extends Component {
7
state = {
8
user: {
9
isAuthenticated: false,
10
token: '',
11
hatName: '',
12
},
13
};
14
15
render() {
16
return (
17
<AuthContext.Provider
18
value={{
19
user: this.state.user,
20
logout: () => {
21
sessionStorage.removeItem('token');
22
23
this.setState({
24
user: {
25
isAuthenticated: false,
26
token: '',
27
hatName: '',
28
},
29
});
30
},
31
login: (token, hatName) => {
32
if (token && !HatTokenValidation.isEncodedTokenExpired(token)) {
33
this.setState({
34
user: {
35
isAuthenticated: true,
36
token: token,
37
hatName: hatName,
38
},
39
});
40
}
41
},
42
}}
43
>
44
{this.props.children}
45
</AuthContext.Provider>
46
);
47
}
48
}
Copied!

Include AuthProvider in the App component

1
// src/app/App.js
2
import { AuthProvider } from "../components/context/AuthProvider";
3
4
function App() {
5
return (
6
<AuthProvider>
7
<div className="app">
8
<Routing/>
9
</div>
10
</AuthProvider>
11
);
12
}
Copied!

Check if the user is authenticated

Now, in the Home page, we can check if the user is authenticated and display a protected page. In order to see this private page, they must login first.
HAT-JS offer the option to import the HatTokenValidation class to help decoding the token, accessing the JWT's properties and check the token expiration time:
1
// src/features/Home/HomePage.js
2
import React, { useEffect, useContext } from 'react';
3
import './HomePage.scss';
4
import { HatTokenValidation } from '@dataswift/hat-js/lib/utils/HatTokenValidation';
5
import AuthContext from "../../components/context/AuthContext";
6
function HomePage() {
7
const authContext = useContext(AuthContext);
8
9
useEffect(() => {
10
const token = sessionStorage.getItem('token');
11
12
if (token) {
13
const decodedToken = HatTokenValidation.decodeToken(token);
14
15
// If the token has not expired, log in the user.
16
if (!HatTokenValidation.isExpired(decodedToken)) {
17
authContext.login(token, decodedToken.iss);
18
}
19
}
20
}, []);
21
22
return (
23
<AuthContext.Consumer>
24
{context => (
25
<>
26
<div className="home-wrapper">{context.user.isAuthenticated && <div>Authenticated!</div>}</div>
27
</>
28
)}
29
</AuthContext.Consumer>
30
);
31
}
32
33
export default HomePage;
Copied!

Add a log out button

In the Header.js we can use the AuthContext and display the "Logout" button when the user is authenticated:
1
// src/components/header/Header.js
2
import React from 'react';
3
import { Link, NavLink } from 'react-router-dom';
4
import './Header.scss';
5
import AuthContext from '../context/AuthContext';
6
7
function Header() {
8
return (
9
<AuthContext.Consumer>
10
{context => (
11
<>
12
<header className="header">
13
<NavLink exact activeClassName="active" to="/">
14
Home
15
</NavLink>
16
17
{context.user.isAuthenticated && (
18
<Link to="/login" onClick={() => context.logout()}>
19
Log out
20
</Link>
21
)}
22
23
{!context.user.isAuthenticated && (
24
<ul>
25
<li>
26
<NavLink activeClassName="active" to="/login">
27
Log in
28
</NavLink>
29
</li>
30
<li>
31
<NavLink activeClassName="active" to="/signup">
32
Create account
33
</NavLink>
34
</li>
35
</ul>
36
)}
37
</header>
38
</>
39
)}
40
</AuthContext.Consumer>
41
);
42
}
43
export default Header;
Copied!

Create a protected Home component

The next step is to create the protected home component. Lets create the HomePrivate.js file into the Home feature:
1
// src/features/Home/HomePrivate.js
2
import React from 'react';
3
4
function HomePrivate() {
5
return (
6
<div className="home-private">
7
<div className="container">
8
<div className="content">
9
Private content
10
</div>
11
</div>
12
</div>
13
);
14
}
15
16
export default HomePrivate;
Copied!
And include this CSS in the HomePage.scss:
1
// src/features/Home/HomePage.scss
2
.
3
.
4
5
.home-private {
6
height: 100%;
7
width: 100%;
8
9
.container {
10
border-radius: 6px;
11
padding: 2.4rem;
12
}
13
14
.content {
15
background: #e9e9e9;
16
padding: 2.4rem;
17
border-radius: 6px;
18
}
19
}
Copied!
And then, replace the \Authenticated!\</div> with the component:
1
// src/features/Home/HomePage.js
2
.
3
.
4
import HomePrivate from "./HomePrivate";
5
6
function HomePage() {
7
.
8
.
9
10
return (
11
<AuthContext.Consumer>
12
{context => (
13
<>
14
<div className="home-wrapper">{context.user.isAuthenticated && <HomePrivate />}</div>
15
</>
16
)}
17
</AuthContext.Consumer>
18
);
19
}
20
21
export default HomePage;
Copied!

Create Notes component

Create a new folder Notes in the features/ with the Notes.js file:
1
// src/features/Notes/Notes.js
2
import React from 'react';
3
import './Notes.scss';
4
5
function Notes() {
6
7
return (
8
<form
9
className={'notes-wrapper flex-column-wrapper flex-content-center flex-align-items-center'}
10
>
11
<div className={'flex-spacer-small'} />
12
<h3>Save a note on your HAT</h3>
13
<input
14
name={'note'}
15
type={'text'}
16
placeholder="Remember to..."
17
autoComplete={'text'}
18
/>
19
<div className={'flex-spacer-small'} />
20
<button className={'btn btn-accent'} type={'submit'}>
21
Save
22
</button>
23
<div className={'flex-spacer-small'} />
24
</form>
25
);
26
}
27
28
export default Notes;
Copied!
And the Notes.scss:
1
// src/features/Notes/Notes.scss
2
.notes-wrapper {
3
input {
4
margin-top: 2.5rem;
5
margin-bottom: 2.5rem;
6
}
7
}
8
9
.note-row-wrapper {
10
display: flex;
11
flex-direction: row;
12
margin-top: 1rem;
13
14
.note-content {
15
text-align: left;
16
flex: 1 1;
17
color: #4a556b;
18
font-size: 1.6rem;
19
}
20
21
button {
22
margin-left: 1rem;
23
}
24
}
25
26
.notes-list {
27
margin-top: 2.5rem;
28
width: 100%;
29
max-width: 32rem;
30
list-style-type: none;
31
}
Copied!
Include the Notes component in the HomePrivate.js:
1
// src/features/Home/HomePrivate.js
2
import React from 'react';
3
import Notes from "../Notes/Notes";
4
5
function HomePrivate() {
6
return (
7