If you are follower of this blog, you already know how this works. That router is the one showed [here][7]. Just download it from [@nicolasparada/router][8] and save it to `static/router.js`.
We registered four routes. At the root `/` we show the home or access page whether the user is authenticated. At `/callback` we show the callback page. On `/conversations/{conversationID}` we show the conversation or access page whether the user is authenticated and for every other URL, we show a not found page.
We tell the router to render the result to the document body and dispatch a `disconnect` event to each page before leaving.
We have each page in a different file and we import them with the new dynamic `import()`.
### Auth
`guard()` is a function that given two functions, executes the first one if the user is authenticated, or the sencond one if not. It comes from `auth.js` so lets create a `static/auth.js` file with the following content:
`isAuthenticated()` checks for `token` and `expires_at` from localStorage to tell if the user is authenticated. `getAuthUser()` gets the authenticated user from localStorage.
When we login, we’ll save all the data to localStorage so it will make sense.
### Access Page
![access page screenshot][9]
Lets start with the access page. Create a file `static/pages/access-page.js` with the following content:
The callback page doesn’t render anything. It’s an async function that does a GET request to `/api/auth_user` using the token from the URL query string and saves all the data to localStorage. Then it redirects to `/`.
### HTTP
There is an HTTP module. Create a `static/http.js` file with the following content:
```
import { isAuthenticated } from './auth.js'
async function handleResponse(res) {
const body = await res.clone().json().catch(() => res.text())
if (res.status === 401) {
localStorage.removeItem('auth_user')
localStorage.removeItem('token')
localStorage.removeItem('expires_at')
}
if (!res.ok) {
const message = typeof body === 'object' && body !== null && 'message' in body
const eventSource = new EventSource(urlWithToken.toString())
eventSource.onmessage = ev => {
let data
try {
data = JSON.parse(ev.data)
} catch (err) {
console.error('could not parse message data as JSON:', err)
return
}
callback(data)
}
const unsubscribe = () => {
eventSource.close()
}
return unsubscribe
},
}
```
This module is a wrapper around the [fetch][10] and [EventSource][11] APIs. The most important part is that it adds the JSON web token to the requests.
### Home Page
![home page screenshot][12]
So, when the user login, the home page will be shown. Create a `static/pages/home-page.js` file with the following content:
For this post, this is the only content we render on the home page. We show the current authenticated user and a logout button.
When the user clicks to logout, we clear all inside localStorage and do a reload of the page.
### Avatar
That `avatar()` function is to show the user’s avatar. Because it’s used in more than one place, I moved it to a `shared.js` file. Create the file `static/shared.js` with the following content:
We use a small figure with the user’s initial in case the avatar URL is null.
You can show the initial with a little of CSS using the `attr()` function.
```
.avatar[data-initial]::after {
content: attr(data-initial);
}
```
### Development Login
![access page with login form screenshot][13]
In the previous post we coded a login for development. Lets add a form for that in the access page. Go to `static/pages/access-page.js` and modify it a little.
I added a login form. When the user submits the form. It does a POST requets to `/api/login` with the username. Saves all the data to localStorage and reloads the page.
Remember to remove this form once you are done with the frontend.
* * *
That’s all for this post. In the next one, we’ll continue with the home page to add a form to start conversations and display a list with the latest ones.