Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sign up page #447

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions lib/safira/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -201,16 +201,25 @@ defmodule Safira.Accounts do
## Examples

iex> register_attendee_user(%{field: value})
{:ok, %User{}}
{:ok, %{user: %User{}, attendee: %Attendee{}}}

iex> register_attendee_user(%{field: bad_value})
{:error, %Ecto.Changeset{}}
{:error, :struct, %Ecto.Changeset{}, %{}}

"""
def register_attendee_user(attrs) do
%User{}
|> User.registration_changeset(attrs |> Map.put(:type, :attendee))
|> Repo.insert()
# Convert map to atom keys
attrs = for {key, val} <- attrs, into: %{}, do: {String.to_atom("#{key}"), val}

Ecto.Multi.new()
|> Ecto.Multi.insert(
:user,
User.registration_changeset(%User{}, Map.delete(attrs, :attendee))
)
|> Ecto.Multi.insert(:attendee, fn %{user: user} ->
Attendee.changeset(%Attendee{}, Map.put(Map.get(attrs, :attendee), "user_id", user.id))
end)
|> Repo.transaction()
end

@doc """
Expand Down
1 change: 1 addition & 0 deletions lib/safira/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ defmodule Safira.Accounts.User do
|> validate_email(opts)
|> validate_handle()
|> validate_password(opts)
|> cast_assoc(:attendee, with: &Attendee.changeset/2)
end

defp validate_email(changeset, opts) do
Expand Down
2 changes: 1 addition & 1 deletion lib/safira_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule SafiraWeb do
those modules here.
"""

def static_paths, do: ~w(assets fonts images docs favicon.ico robots.txt)
def static_paths, do: ~w(assets docs fonts images favicon.ico robots.txt)

def router do
quote do
Expand Down
2 changes: 1 addition & 1 deletion lib/safira_web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
data-website-id={Application.fetch_env!(:safira, :umami_website_id)}
/>
</head>
<body>
<body class="bg-primary">
<%= @inner_content %>
</body>
</html>
20 changes: 18 additions & 2 deletions lib/safira_web/controllers/user_session_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,24 @@ defmodule SafiraWeb.UserSessionController do
alias Safira.Accounts
alias SafiraWeb.UserAuth

def create(conn, %{"_action" => "registered"} = params) do
create(conn, params, "Account created successfully!")
def new(conn, %{"user" => user_params}) do
case Accounts.register_attendee_user(user_params) do
{:ok, %{user: user, attendee: _}} ->
{:ok, _} =
Accounts.deliver_user_confirmation_instructions(
user,
&url(~p"/users/confirm/#{&1}")
)

conn
|> UserAuth.log_in_user(user, user_params)
|> put_flash(:success, "Account created successfully")
|> redirect(to: ~p"/app")

{:error, _, %Ecto.Changeset{} = _changeset, _} ->
conn
|> put_flash(:error, "Unable to register. Check the errors below")
end
end

def create(conn, %{"_action" => "password_updated"} = params) do
Expand Down
74 changes: 74 additions & 0 deletions lib/safira_web/live/auth/confirmation_pending_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
defmodule SafiraWeb.ConfirmationPendingLive do
use SafiraWeb, :live_view

alias Safira.Accounts

import SafiraWeb.Components.Button

@delay_between_emails 30

@impl true
def render(assigns) do
~H"""
<div>
<img class="w-52 h-52 m-auto block" src={~p"/images/sei.svg"} />
<h1 class="font-terminal uppercase text-4xl sm:text-6xl text-center mt-24">
<%= gettext("Just one more step remaining!") %>
</h1>
<p class="font-terminal text-xl sm:text-2xl text-center mt-4">
<%= gettext(
"We have sent you an email containing instructions on how to verify your account. Click the link in it to conclude your registration for SEI'25!"
) %>
</p>
<p class="font-terminal text-md sm:text-xl text-center mt-2">
<%= gettext(
"If you haven't received an email, check your Spam and Junk folders. If it is not there, click the button below to receive a new link."
) %>
</p>
<div
id="seconds-remaining"
class="font-terminal text-center text-2xl sm:text-4xl mt-12"
phx-hook="Countdown"
>
<.action_button title={gettext("Re-send Verification Email")} phx-click="resend" />
</div>
<.link class="text-center block mt-8 underline" href="/users/log_out" method="delete">
Sign out
</.link>
</div>
"""
end

@impl true
def mount(_params, _session, socket) do
if is_nil(socket.assigns.current_user.confirmed_at) do
{:ok, socket |> assign(:last_sent, nil)}
else
{:ok, socket |> push_navigate(to: ~p"/app")}
end
end

@impl true
def handle_event("resend", _params, socket) do
# To avoid sending many emails if a user decides to smash the resend button,
# we only allow emails to be sent once every 30 seconds
if throttle_socket?(socket) do
{:noreply, socket}
else
Accounts.deliver_user_confirmation_instructions(
socket.assigns.current_user,
&url(~p"/users/confirm/#{&1}")
)

{:noreply,
socket
|> put_flash(:info, "The email has been sent to your inbox")
|> assign(:last_sent, DateTime.utc_now())}
end
end

defp throttle_socket?(socket) do
not is_nil(socket.assigns.last_sent) and
DateTime.diff(DateTime.utc_now(), socket.assigns.last_sent) < @delay_between_emails
end
end
23 changes: 15 additions & 8 deletions lib/safira_web/live/auth/user_confirmation_live.ex
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
defmodule SafiraWeb.UserConfirmationLive do
use SafiraWeb, :live_view

import SafiraWeb.Components.Button

alias Safira.Accounts

def render(%{live_action: :edit} = assigns) do
~H"""
<div class="mx-auto max-w-sm">
<.header class="text-center">Confirm Account</.header>
<img class="w-52 h-52 m-auto block" src={~p"/images/sei.svg"} />
<.header class="text-center">
<%= gettext("Verify your Account") %>
<:subtitle>
<%= "Click the button below to verify your account and complete registration for SEI'25" %>
</:subtitle>
</.header>

<.simple_form for={@form} id="confirmation_form" phx-submit="confirm_account">
<input type="hidden" name={@form[:token].name} value={@form[:token].value} />
<:actions>
<.button phx-disable-with="Confirming..." class="w-full">Confirm my account</.button>
<.action_button
title={gettext("Verify my Account")}
phx-disable-with="Confirming..."
class="w-full"
/>
</:actions>
</.simple_form>

<p class="text-center mt-4">
<.link href={~p"/users/register"}>Register</.link>
| <.link href={~p"/users/log_in"}>Log in</.link>
</p>
</div>
"""
end
Expand All @@ -36,7 +43,7 @@ defmodule SafiraWeb.UserConfirmationLive do
{:noreply,
socket
|> put_flash(:info, "User confirmed successfully.")
|> redirect(to: ~p"/")}
|> redirect(to: ~p"/app")}

:error ->
# If there is a current user and the account was already confirmed,
Expand Down
6 changes: 3 additions & 3 deletions lib/safira_web/live/auth/user_login_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule SafiraWeb.UserLoginLive do

alias Safira.Event

import SafiraWeb.Components.Button

def render(assigns) do
~H"""
<div class="mx-auto max-w-sm">
Expand Down Expand Up @@ -34,9 +36,7 @@ defmodule SafiraWeb.UserLoginLive do
</.link>
</:actions>
<:actions>
<.button phx-disable-with="Logging in..." class="w-full">
Log in <span aria-hidden="true">→</span>
</.button>
<.action_button title="Log in" class="w-full" />
</:actions>
</.simple_form>
</div>
Expand Down
65 changes: 23 additions & 42 deletions lib/safira_web/live/auth/user_registration_live.ex
Original file line number Diff line number Diff line change
@@ -1,72 +1,53 @@
defmodule SafiraWeb.UserRegistrationLive do
use SafiraWeb, :live_view
use SafiraWeb, :landing_view

alias Safira.Accounts
alias Safira.Accounts.User
alias SafiraWeb.UserAuth

def render(assigns) do
~H"""
<div class="mx-auto max-w-sm">
<.header class="text-center">
Register for an account
<:subtitle>
Already registered?
<.link navigate={~p"/users/log_in"} class="font-semibold text-primary hover:underline">
Log in
</.link>
to your account now.
</:subtitle>
</.header>

<.simple_form
for={@form}
id="registration_form"
phx-submit="save"
phx-change="validate"
phx-trigger-action={@trigger_submit}
action={~p"/users/log_in?_action=registered"}
method="post"
>
<.error :if={@check_errors}>
Oops, something went wrong! Please check the errors below.
</.error>

<.input field={@form[:email]} type="email" label="Email" required />
<.input field={@form[:password]} type="password" label="Password" required />

<:actions>
<.button phx-disable-with="Creating account..." class="w-full">Create an account</.button>
</:actions>
</.simple_form>
</div>
"""
end
import SafiraWeb.Components.Button

def mount(_params, _session, socket) do
changeset = Accounts.change_user_registration(%User{})

courses =
Accounts.list_courses()
|> Enum.map(fn c -> {c.name, c.id} end)

socket =
socket
|> assign(trigger_submit: false, check_errors: false)
|> assign(:courses, courses)
|> assign_form(changeset)

{:ok, socket, temporary_assigns: [form: nil]}
end

def handle_event("save", %{"user" => user_params}, socket) do
case Accounts.register_attendee_user(user_params) do
{:ok, user} ->
{:ok, %{user: user, attendee: _}} ->
{:ok, _} =
Accounts.deliver_user_confirmation_instructions(
user,
&url(~p"/users/confirm/#{&1}")
)

changeset = Accounts.change_user_registration(user)
{:noreply, socket |> assign(trigger_submit: true) |> assign_form(changeset)}

{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, socket |> assign(check_errors: true) |> assign_form(changeset)}
{:noreply,
socket
|> UserAuth.log_in_user(user, user_params)
|> assign(trigger_submit: true)
|> assign(check_errors: false)
|> assign_form(changeset)
|> push_navigate(to: ~p"/app")}

{:error, _, %Ecto.Changeset{} = changeset, _} ->
{:noreply,
socket
|> assign(check_errors: true)
|> assign_form(changeset)
|> put_flash(:error, "Unable to register. Check the errors below")}
end
end

Expand Down
67 changes: 67 additions & 0 deletions lib/safira_web/live/auth/user_registration_live.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<div class="mx-auto max-w-sm py-2">
<div class="px-32 py-12">
<img class="w-full h-full block" src={~p"/images/sei-logo.svg"} />
</div>
<.header class="text-center">
<h1 class="text-3xl text-center font-terminal uppercase">Register for an account</h1>
<:subtitle>
<%= gettext("Already registered?") %>
<.link navigate={~p"/users/log_in"} class="font-semibold text-primary hover:underline">
Log in
</.link>
<%= gettext("to your account now") %>
</:subtitle>
</.header>

<.simple_form
for={@form}
id="registration_form"
phx-change="validate"
phx-trigger-action={@trigger_submit}
action={~p"/users/register"}
>
<.input field={@form[:email]} type="email" label="Email" required />
<.input field={@form[:password]} type="password" label="Password" required />
<.input
field={@form[:password_confirmation]}
type="password"
label="Confirm Password"
required
/>
<.input field={@form[:name]} type="text" label="Name" required />
<.input field={@form[:handle]} type="text" label="Nickname" required />

<.inputs_for :let={att} field={@form[:attendee]}>
<.input
class="text-black"
field={att[:course_id]}
type="select"
label="Course"
prompt="Choose a course"
options={@courses}
/>
</.inputs_for>

<.label>
<div class="flex">
<div class="mr-1 mt-1">
<.input name="consent" label="" type="checkbox" value={false} class="w-fit" required />
</div>

<div class="inline">
<%= gettext("I have read and agree to the ") %>
<.link href={~p"/docs/regulation.pdf"} target="_blank">
<%= gettext("General Regulation") %>
</.link>
<%= gettext(" and ") %>
<.link href={~p"/docs/privacy_policy.pdf"} target="_blank">
<%= gettext("Privacy Policy") %>
</.link>
</div>
</div>
</.label>
<:actions>
<.action_button title="Create an account" subtitle="" class="w-full" disabled={false} />
</:actions>
</.simple_form>
</div>
Loading
Loading