user settings updated to lv and lv tests created. all tests working

This commit is contained in:
Adam Piontek 2021-03-02 16:48:00 -05:00
parent 24502e2667
commit ef7b8e0bb8
26 changed files with 389 additions and 288 deletions

View file

@ -172,10 +172,10 @@ defmodule Bones73k.Accounts do
{:error, %Ecto.Changeset{}}
"""
def apply_user_email(user, password, attrs) do
def apply_user_email(user, %{"current_password" => curr_pw} = attrs) do
user
|> User.email_changeset(attrs)
|> User.validate_current_password(password)
|> User.validate_current_password(curr_pw)
|> Ecto.Changeset.apply_action(:update)
end
@ -247,11 +247,11 @@ defmodule Bones73k.Accounts do
{:error, %Ecto.Changeset{}}
"""
def update_user_password(user, password, attrs) do
def update_user_password(user, %{"current_password" => curr_pw} = attrs) do
changeset =
user
|> User.password_changeset(attrs)
|> User.validate_current_password(password)
|> User.validate_current_password(curr_pw)
Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset)

View file

@ -35,10 +35,6 @@ defmodule Bones73kWeb.UserAuth do
|> put_session(:user_token, token)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|> maybe_write_remember_me_cookie(token, params)
|> put_flash(
:info,
raw("Welcome back, #{user.email} — you were logged in successfuly.")
)
|> redirect(to: get_session(conn, :user_return_to) || signed_in_path(conn))
end

View file

@ -1,6 +1,7 @@
defmodule Bones73kWeb.UserSessionController do
use Bones73kWeb, :controller
alias Phoenix.HTML
alias Bones73k.Accounts
alias Bones73k.Accounts.User
alias Bones73kWeb.UserAuth
@ -11,7 +12,12 @@ defmodule Bones73kWeb.UserSessionController do
def create(conn, %{"user" => %{"email" => email, "password" => password} = user_params}) do
if user = Accounts.get_user_by_email_and_password(email, password) do
UserAuth.log_in_user(conn, user, user_params)
conn
|> put_flash(
:info,
HTML.raw("Welcome back, #{user.email} — you were logged in successfuly.")
)
|> UserAuth.log_in_user(user, user_params)
else
render(conn, "new.html", error_message: "Invalid email or password")
end

View file

@ -2,36 +2,6 @@ defmodule Bones73kWeb.UserSettingsController do
use Bones73kWeb, :controller
alias Bones73k.Accounts
alias Bones73kWeb.UserAuth
plug(:assign_email_and_password_changesets)
def edit(conn, _params) do
render(conn, "edit.html")
end
def update_email(conn, %{"current_password" => password, "user" => user_params}) do
user = conn.assigns.current_user
case Accounts.apply_user_email(user, password, user_params) do
{:ok, applied_user} ->
Accounts.deliver_update_email_instructions(
applied_user,
user.email,
&Routes.user_settings_url(conn, :confirm_email, &1)
)
conn
|> put_flash(
:info,
"A link to confirm your email change has been sent to the new address."
)
|> redirect(to: Routes.user_settings_path(conn, :edit))
{:error, changeset} ->
render(conn, "edit.html", email_changeset: changeset)
end
end
def confirm_email(conn, %{"token" => token}) do
case Accounts.update_user_email(conn.assigns.current_user, token) do
@ -46,27 +16,4 @@ defmodule Bones73kWeb.UserSettingsController do
|> redirect(to: Routes.user_settings_path(conn, :edit))
end
end
def update_password(conn, %{"current_password" => password, "user" => user_params}) do
user = conn.assigns.current_user
case Accounts.update_user_password(user, password, user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "Password updated successfully.")
|> put_session(:user_return_to, Routes.user_settings_path(conn, :edit))
|> UserAuth.log_in_user(user)
{:error, changeset} ->
render(conn, "edit.html", password_changeset: changeset)
end
end
defp assign_email_and_password_changesets(conn, _opts) do
user = conn.assigns.current_user
conn
|> assign(:email_changeset, Accounts.change_user_email(user))
|> assign(:password_changeset, Accounts.change_user_password(user))
end
end

View file

@ -4,12 +4,6 @@ defmodule Bones73kWeb.UserLive.Registration do
alias Bones73k.Accounts
alias Bones73k.Accounts.User
@messages [
success: "Welcome! New accout created.",
info:
"Some features may be unavailable until you confirm your email. Check your inbox for instructions."
]
@impl true
def mount(_params, session, socket) do
socket
@ -25,7 +19,11 @@ defmodule Bones73kWeb.UserLive.Registration do
%{
user_id: nil,
user_return_to: Map.get(session, "user_return_to", "/"),
messages: @messages
messages: [
success: "Welcome! Your new account has been created, and you've been logged in.",
info:
"Some features may be unavailable until you confirm your email address. Check your inbox for instructions."
]
}
end

View file

@ -27,7 +27,7 @@ defmodule Bones73kWeb.UserLive.ResetPassword do
{:ok, _} ->
{:noreply,
socket
|> put_flash(:success, "Password reset successfully.")
|> put_flash(:info, "Password reset successfully.")
|> redirect(to: Routes.user_session_path(socket, :new))}
{:error, changeset} ->

View file

@ -0,0 +1,39 @@
defmodule Bones73kWeb.UserLive.Settings do
use Bones73kWeb, :live_view
alias Bones73k.Accounts.User
@impl true
def mount(_params, session, socket) do
socket
|> assign_defaults(session)
|> alert_email_verified?()
|> live_okreply()
end
defp alert_email_verified?(socket) do
case socket.assigns.current_user do
%{confirmed_at: nil} ->
put_flash(socket, :warning, [
"Your email hasn't been confirmed, some areas may be restricted. Shall we ",
link("resend the verification email?",
to: Routes.user_confirmation_path(socket, :new),
class: "alert-link"
)
])
_ ->
socket
end
end
@impl true
def handle_info({:put_flash_message, {flash_type, msg}}, socket) do
socket |> put_flash(flash_type, msg) |> live_noreply()
end
@impl true
def handle_info({:clear_flash_message, flash_type}, socket) do
socket |> clear_flash(flash_type) |> live_noreply()
end
end

View file

@ -0,0 +1,9 @@
<h3 class="mb-3">
<%= icon_div @socket, "bi-sliders", [class: "icon baseline"] %>
User Settings
</h3>
<div class="row">
<%= live_component @socket, Bones73kWeb.UserLive.Settings.Email, id: "email-#{@current_user.id}", current_user: @current_user %>
<%= live_component @socket, Bones73kWeb.UserLive.Settings.Password, id: "password-#{@current_user.id}", current_user: @current_user %>
</div>

View file

@ -0,0 +1,62 @@
defmodule Bones73kWeb.UserLive.Settings.Email do
use Bones73kWeb, :live_component
alias Bones73k.Accounts
alias Bones73k.Accounts.User
@impl true
def update(%{current_user: user} = assigns, socket) do
socket
|> assign(id: assigns.id)
|> assign(current_user: user)
|> assign(changeset: get_changeset(user))
|> live_okreply()
end
defp get_changeset(user, user_params \\ %{}) do
Accounts.change_user_email(user, user_params)
end
@impl true
def handle_event("validate", %{"user" => user_params}, socket) do
cs = get_changeset(socket.assigns.current_user, user_params)
{:noreply, assign(socket, changeset: %{cs | action: :update})}
end
# user_settings_path GET /users/settings/confirm_email/:token Bones73kWeb.UserSettingsController :confirm_email
@impl true
def handle_event("save", %{"user" => user_params}, socket) do
case Accounts.apply_user_email(socket.assigns.current_user, user_params) do
{:ok, applied_user} ->
Accounts.deliver_update_email_instructions(
applied_user,
socket.assigns.current_user.email,
&Routes.user_settings_url(socket, :confirm_email, &1)
)
send(self(), {:clear_flash_message, :error})
send(
self(),
{:put_flash_message,
{:info, "A link to confirm your e-mail change has been sent to the new address."}}
)
socket
|> assign(changeset: get_changeset(socket.assigns.current_user))
|> live_noreply()
{:error, cs} ->
cu = socket.assigns.current_user
cpw = user_params["current_password"]
valid_password? = User.valid_password?(cu, cpw)
msg = (valid_password? && "Could not reset email.") || "Invalid current password."
send(self(), {:put_flash_message, {:error, msg}})
socket
|> assign(changeset: cs)
|> live_noreply()
end
end
end

View file

@ -0,0 +1,51 @@
<div id="<%= @id %>" class="col-sm-9 col-md-7 col-lg-5 col-xl-4 mt-1">
<h4>Change email</h4>
<%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, phx_target: @myself], fn f -> %>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :email) %>">
<%= label f, :email, class: "form-label" %>
<div class="input-group has-validation">
<span class="input-group-text">
<%= icon_div @socket, "bi-at", [class: "icon fs-5"] %>
</span>
<%= email_input f, :email,
value: input_value(f, :email),
class: input_class(f, :email, "form-control"),
placeholder: "e.g., babka@73k.us",
maxlength: User.max_email,
phx_debounce: "600",
aria_describedby: error_id(f, :email)
%>
<%= error_tag f, :email %>
</div>
</div>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>">
<%= label f, :current_password, class: "form-label" %>
<div class="input-group">
<span class="input-group-text">
<%= icon_div @socket, "bi-lock", [class: "icon fs-5"] %>
</span>
<%= password_input f, :current_password,
class: "form-control",
required: true,
aria_describedby: error_id(f, :current_password)
%>
<%= error_tag f, :current_password %>
</div>
</div>
<div class="mb-3">
<%= submit "Change email",
disabled: !@changeset.valid? || input_value(f, :current_password) == "",
class: "btn btn-primary",
phx_disable_with: "Saving..."
%>
</div>
<% end %>
</div>

View file

@ -0,0 +1,57 @@
defmodule Bones73kWeb.UserLive.Settings.Password do
use Bones73kWeb, :live_component
alias Bones73k.Accounts
alias Bones73k.Accounts.User
@impl true
def update(%{current_user: user} = assigns, socket) do
socket
|> assign(id: assigns.id)
|> assign(current_user: user)
|> assign(changeset: get_changeset(user))
|> assign(login_params: init_login_params(socket))
|> assign(trigger_submit: false)
|> live_okreply()
end
defp get_changeset(user, user_params \\ %{}) do
Accounts.change_user_password(user, user_params)
end
defp init_login_params(socket) do
%{
user_id: nil,
user_return_to: Routes.user_settings_path(socket, :edit),
messages: [info: "Password updated successfully."]
}
end
@impl true
def handle_event("validate", %{"user" => user_params}, socket) do
cs = get_changeset(socket.assigns.current_user, user_params)
{:noreply, assign(socket, changeset: %{cs | action: :update})}
end
@impl true
def handle_event("save", %{"user" => user_params}, socket) do
case Accounts.update_user_password(socket.assigns.current_user, user_params) do
{:ok, user} ->
socket
|> assign(login_params: %{socket.assigns.login_params | user_id: user.id})
|> assign(trigger_submit: true)
|> live_noreply()
{:error, cs} ->
cu = socket.assigns.current_user
cpw = user_params["current_password"]
valid_password? = User.valid_password?(cu, cpw)
msg = (valid_password? && "Could not change password.") || "Invalid current password."
send(self(), {:put_flash_message, {:error, msg}})
socket
|> assign(changeset: cs)
|> live_noreply()
end
end
end

View file

@ -0,0 +1,76 @@
<div id="<%= @id %>" class="col-sm-9 col-md-7 col-lg-5 col-xl-4 mt-1">
<h4>Change password</h4>
<%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, phx_target: @myself], fn f -> %>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
<%= label f, :password, "New password", class: "form-label" %>
<div class="input-group has-validation">
<span class="input-group-text">
<%= icon_div @socket, "bi-key", [class: "icon fs-5"] %>
</span>
<%= password_input f, :password,
value: input_value(f, :password),
class: input_class(f, :password, "form-control"),
maxlength: User.max_password,
phx_debounce: "600",
aria_describedby: error_id(f, :password)
%>
<%= error_tag f, :password %>
</div>
</div>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :password_confirmation) %>">
<%= label f, :password_confirmation, "Confirm new password", class: "form-label" %>
<div class="input-group has-validation">
<span class="input-group-text">
<%= icon_div @socket, "bi-key-fill", [class: "icon fs-5"] %>
</span>
<%= password_input f, :password_confirmation,
value: input_value(f, :password_confirmation),
class: input_class(f, :password_confirmation, "form-control"),
maxlength: User.max_password,
aria_describedby: error_id(f, :password_confirmation)
%>
<%= error_tag f, :password_confirmation %>
</div>
</div>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>">
<%= label f, :current_password, class: "form-label" %>
<div class="input-group">
<span class="input-group-text">
<%= icon_div @socket, "bi-lock", [class: "icon fs-5"] %>
</span>
<%= password_input f, :current_password,
class: "form-control",
required: true,
aria_describedby: error_id(f, :current_password)
%>
<%= error_tag f, :current_password %>
</div>
</div>
<div class="mb-3">
<%= submit "Change password",
disabled: !@changeset.valid? || input_value(f, :current_password) == "",
class: "btn btn-primary",
phx_disable_with: "Saving..."
%>
</div>
<% end %>
<%# hidden form for initial login after registration %>
<%= form_for :user, Routes.user_session_path(@socket, :create), [phx_trigger_action: @trigger_submit, id: "settings_pw_change_trigger"], fn f -> %>
<%= hidden_input f, :params_token, value: Phoenix.Token.encrypt(Bones73kWeb.Endpoint, "login_params", @login_params) %>
<% end %>
<%# hidden form to submit user for relogin after password change %>
<%#= form_for :user_login, Routes.user_session_path(@socket, :create), [phx_trigger_action: @trigger_submit], fn f -> %>
<%#= hidden_input f, :login_params_token, value: Phoenix.Token.encrypt(Bones73kWeb.Endpoint, "login_params", @login_params) %>
<%#= hidden_input f, :remember_me, value: false %>
<%# end %>
</div>

View file

@ -72,13 +72,9 @@ defmodule Bones73kWeb.Router do
pipe_through([:browser, :require_authenticated_user])
# # liveview user settings
# live "/users/settings", UserLive.Settings, :edit
live "/users/settings", UserLive.Settings, :edit
# original user routes from phx.gen.auth
# TODO:
get("/users/settings", UserSettingsController, :edit)
put("/users/settings/update_password", UserSettingsController, :update_password)
put("/users/settings/update_email", UserSettingsController, :update_email)
get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email)
end
@ -86,7 +82,6 @@ defmodule Bones73kWeb.Router do
pipe_through([:browser])
delete("/users/log_out", UserSessionController, :delete)
# TODO: understanding/testing force_logout?
get("/users/force_logout", UserSessionController, :force_logout)
get("/users/confirm", UserConfirmationController, :new)
post("/users/confirm", UserConfirmationController, :create)

View file

@ -2,7 +2,7 @@
<%# phoenix flash alerts: %>
<div class="row justify-content-center">
<div class="col-md-11 col-lg-9 col-xl-8 ">
<div class="col-md-12 col-lg-10 col-xl-9 ">
<%= for {kind, color} <- alert_kinds() do %>
<%= if flash_content = get_flash(@conn, kind) do %>
<div class="alert alert-<%= color %> alert-dismissible fade show" role="alert">

View file

@ -2,7 +2,7 @@
<%# liveview flash alerts: %>
<div class="row justify-content-center">
<div class="col-md-11 col-lg-9 col-xl-8 ">
<div class="col-md-12 col-lg-10 col-xl-9 ">
<%= for {kind, color} <- alert_kinds() do %>
<%= if flash_content = live_flash(@flash, kind) do %>
<div class="alert alert-<%= color %> alert-dismissible fade show" role="alert" id="lv-alert-<%= kind %>" phx-hook="AlertRemover" data-key="<%= kind %>">

View file

@ -1,49 +0,0 @@
<h1>Settings</h1>
<h3>Change email</h3>
<%= form_for @email_changeset, Routes.user_settings_path(@conn, :update_email), fn f -> %>
<%= if @email_changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= label f, :email %>
<%= email_input f, :email, required: true %>
<%= error_tag f, :email %>
<%= label f, :current_password, for: "current_password_for_email" %>
<%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_email" %>
<%= error_tag f, :current_password %>
<div>
<%= submit "Change email" %>
</div>
<% end %>
<h3>Change password</h3>
<%= form_for @password_changeset, Routes.user_settings_path(@conn, :update_password), fn f -> %>
<%= if @password_changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= label f, :password, "New password" %>
<%= password_input f, :password, required: true %>
<%= error_tag f, :password %>
<%= label f, :password_confirmation, "Confirm new password" %>
<%= password_input f, :password_confirmation, required: true %>
<%= error_tag f, :password_confirmation %>
<%= label f, :current_password, for: "current_password_for_password" %>
<%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %>
<%= error_tag f, :current_password %>
<div>
<%= submit "Change password" %>
</div>
<% end %>

View file

@ -1,3 +0,0 @@
defmodule Bones73kWeb.UserSettingsView do
use Bones73kWeb, :view
end