In the previous lesson, we made it possible for the user to sign up and log in to our website. In this chapter, we want to take advantage of this new functionality to make the UI of our app more dynamic!
This is sometimes referred to as the difference between a website (which has the same static content for everyone), and a web app.
Extracting the header into a template
The first thing we want to do is to change the header depending on if the user is logged-in our logged-out. We can refer to our Sketch file to see how we intend to make it look:
Alright, let's implement that! Since we're going to add quite a bit of logic to our header now, it makes sense to extract it into its own template. We'll put that template in the
layout-folder (that makes sense, because a header is part of the layout, right?):
Now we can remove the header-tag from our
app.html.eex template, and replace it with a simple
Nothing fancy here! We just specify the view (
LayoutView), the template (
"header.html"), and then we use
assigns in order to be able to use
@conn in our template (which we will need in order to retrieve the logged-in user, as you saw in the previous lesson).
Now comes the fun part! Remember how we learned that views "transform our data to make it easy to use when rendering a template"? Well, that's exactly what we're going to do now. We want to make the logic in our template as simple as possible, by defining good functions in our Layout View.
The first thing we want to know in our template is whether the user is logged-in or not. For that, we'll use an if-statement in our header template to render different things depending on the result of the
We obviously have to define this
logged_in?/1-function now, or else we get a compilation error.
A tip when using libraries like Guardian: if you're too lazy to check the documentation (like I am sometimes), you can always use the special
__info__ function to get information about Elixir modules. To get a list of all the functions that the
Guardian.Plug module has for example, you simply use
IO.inspect Guardian.Plug.__info__(:functions). You should then see
authenticated? among the list of functions in your console, along with the number of parameters it takes. That's the function that we're going to use:
Showing a username and an avatar
Just showing "Logged in!" in the header is kind of lame, so the next step is to show the user's username and the avatar. First, we make some changes to the template and call the
username/1 function inside the if-statement:
Now we need to define this
username/1 function in our view. Here we can just use Guardian's
current_resource function that we saw earlier, and then extract the username from that using pattern matching:
Next, we want to also add an avatar. For this, we don't want to make the user upload a profile picture or anything – that's beyond the scope of this project. Instead we'll use Gravatar to fetch whatever profile picture (if they have one) that is linked to the user's email address.
If we check Gravatar's documentation for image requests, we learn that we need to hash the email address in the image URL. Let's do that in a new function!
Now we can add our image tag to the template, and call the
Adding a "log out"-button
We're almost done with the header. The last thing we want to add is a "log out"-button, next to the username. The button will just be a link to the URL
/logout which will be connected to a function in our Page Controller.
You know the drill for new routes by now! We start by adding a line to our router file:
Next we add the
logout/2-function to our PageController. In it, we'll just use Guardian's
sign_out function, set a flash message, and redirect to the landing page:
And finally we add the link to /logout in our header template, before the avatar and username:
Once that's done, you should be able to log in and log out as much as you want!
Fixing the landing page forms
There's a little flaw in our landing page that we've overlooked until now – the login and signup boxes don't work! Right now, we can only log in by going to the
/login route, and we can only sign up by going to
Thankfully, we already have all the logic ready, so we just need to connect them to the relevant actions in our Page controller.
First of all, we need a User changeset on the landing page for the signup form to work, so we need to set it in our existing
index/2-function in the controller:
Next, we simply replace the form in the login box with the dynamic form from
login.html.eex, and the form in the signup box with the one from
signup.html.eex. The final
index.html.eex template should look like this:
That's it! Now you can log in and sign up straight from the landing page!
Our final task in this lesson is to create a new route (
/messages), that's only available for logged-in users. First we create the route:
Since this route is for logged-in users only, it's going to work in a lightly different way than our previous routes. Therefore, we use a new controller for it – the ChatController. We need to create a file for it, and add its
As you know, we also need a view for this controller if we want the template to render, so let's create ChatView:
Finally we need a template. By following Phoenix's conventions, we know that this needs to be created at
messengyr_web/templates/chat/index.html.eex. The file can be completely empty for now.
Now we want this route to only be available to users who are logged-in. For this, we can use Guardian's
EnsureAuthenticated plug in the ChatController:
handler should point to a module that contains an
auth_error/3 function. In that function, we specify what happens if the user is not logged-in, yet tries to hit a route connected to this controller.
Since we currently only have a single controller that requires authentication (
ChatController), we can simply set this handler to
__MODULE__ to tell Guardian to look for the
auth_error/3-function in the current module.
Let's create this
auth_error/3-function now to handle unauthenticated users:
That's all! Now try to go to
/messages without being logged in and you should be redirected an see a flash message. If you are logged in however, you should see the empty
chat/index-template, like before.
A final note:
Guardian also has an
EnsureNotAuthenticated plug that we could use to make sure that certain routes can only be accessed when the user is not logged in. This could be used for the login page for example (because why should you be able to access the login page when you're already logged in?). For now though, we'll keep things simple and let these pages be available even for authenticated users.
Congratulations! We've now gone from having a mostly static website to something that resembles a web app! In the next couple of lessons, we're going stay on the
/messages route and build the interactive messaging interface!