This is part 2 in a series. In part 1 we created the users model using a generator. In this section, we'll be building the login flow by hand.
Let's start by creating the following files
live/session_live/new.ex
live/session_live/new.html.eex
Let's start with our template, new.html.eex
# lib/feenix_web/live/session_live/new.html.eex
<h1>Log in</h1>
<%= f = form_for :login, "#",
id: "login-form", phx_submit: "login" %>
<div>
<%= label f, :email %>
<%= email_input f, :email %>
<%= error_tag f, :email %>
</div>
<div>
<%= submit "Save", phx_disable_with: "Saving..." %>
</div>
</form>
This is a form very similar to our New User form, but we only need the email.
Next, let's do the LiveView
# lib/feenix_web/live/session_live/new.ex
defmodule PokerWithFriendsWeb.SessionLive.New do
use PokerWithFriendsWeb, :live_view
alias PokerWithFriends.Accounts
alias PokerWithFriends.Accounts.User
@impl true
def mount(_params, _session, socket) do
{:ok, socket}
end
def handle_event("login", %{"login" => %{"email" => email}}, socket) do
socket =
case Accounts.get_user_by(email: email) do
nil ->
socket
|> put_flash(:error, "You need to create an account first")
|> push_redirect(to: Routes.user_new_path(socket, :new))
user ->
# TODO: error handling for email
Accounts.deliver_login_email(user)
socket
|> put_flash(:info, "Login email sent")
|> push_redirect(to: socket.assigns.return_to)
end
end
{:noreply, socket}
end
end
This isn't as bad as it first looks
mount
is doing nothing special
handle_event/3
is receiving the login event with the email. It then checks if a user with that email exists. If not, it forwards to the User/new page, if so, we call Accounts.deliver_login_email(user)
, leaving a note for ourselves to handle errors. In both cases, we add a helpful flash message
Next, let's create those functions in our Account module.
# lib/feenix/accounts.ex
def get_user_by(params) do
Repo.get_by(User, params)
end
def deliver_login_email(%User{} = _user) do
require Logger
Logger.warn "deliver_login_email not implemented"
:ok
end
Ok, we need to implement our login email. For that, I am going to be using swoosh
Add swoosh and phoenix_swoosh to your mix deps
# mix.exs
defp deps do
...
{:swoosh, "~> 0.25"},
{:phoenix_swoosh, "~> 0.2"},
...
end
In the our config, let's set swoosh to use the local adapter
config :feenix, Feenix.Mailer, adapter: Swoosh.Adapters.Local
Then, we need to define a mailer. I'll put it in the top level of feenix_web
# lib/feenix_web/mailer.ex
defmodule Feenix.Mailer do
@moduledoc false
use Swoosh.Mailer, otp_app: :feenix
end
In our router, there is a line if Mix.env() in [:dev, :test] do
which has the live_dashboard route. Inside that if
, let's add a new dev scope for our mailbox
scope "/dev" do
pipe_through [:browser]
forward "/mailbox", Plug.Swoosh.MailboxPreview, base_path: "/dev/mailbox"
end
Next, we need to create our emails. I'll do this in a new feenix_web/emails folder
# lib/feenix_web/emails/login_email.ex
defmodule FeenixWeb.LoginEmail do
@moduledoc false
use Phoenix.Swoosh,
view: FeenixWeb.EmailView,
layout: {FeenixWeb.LayoutView, :email}
def login_email(user) do
new()
|> to(user.email)
|> from({"Feenix", "login@localhost"})
|> subject("Your login link")
|> render_body("login_email.html", %{})
end
end
And as you can probably guess from looking at the code, let's add a login_email.html.eex
template.
Here is your login link:
<div>
<a href="localhost:4000" />
<div>
We haven't created our token yet, but this is a great start.
Now, let's head back to our accounts context, and finish the deliver_login_email
function.
# lib/feenix/accounts.ex
alias FeenixWeb.{LoginEmail, Mailer}
def deliver_login_email(%User{} = user) do
user
|> LoginEmail()
|> Mailer.deliver()
end
Now, let's head over to the /dev/emails
endpoint we set up and verify that we got the email!
In part 3, we will create the tokens that will actually allow us to login. Until then, happy coding!
Top comments (0)