this is a really good first commit
This commit is contained in:
@ -1,31 +0,0 @@
|
||||
defmodule SomethingErlangWeb.BookmarksLive.Show do
|
||||
use SomethingErlangWeb, :live_view
|
||||
on_mount SomethingErlangWeb.UserLiveAuth
|
||||
|
||||
alias SomethingErlang.Grover
|
||||
|
||||
require Logger
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
Grover.mount(socket.assigns.current_user)
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"page" => page}, _, socket) do
|
||||
bookmarks = Grover.get_bookmarks!(page |> String.to_integer())
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:page_title, "bookmarks")
|
||||
|> assign(:bookmarks, bookmarks)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(_, _, socket) do
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to: Routes.bookmarks_show_path(socket, :show, page: 1)
|
||||
)}
|
||||
end
|
||||
end
|
@ -1,16 +0,0 @@
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for thread <- @bookmarks do %>
|
||||
<tr>
|
||||
<th><%= raw thread.icon %></th>
|
||||
<td><%= raw thread.title %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
@ -1,60 +0,0 @@
|
||||
defmodule SomethingErlangWeb.LiveHelpers do
|
||||
import Phoenix.LiveView
|
||||
import Phoenix.LiveView.Helpers
|
||||
|
||||
alias Phoenix.LiveView.JS
|
||||
|
||||
@doc """
|
||||
Renders a live component inside a modal.
|
||||
|
||||
The rendered modal receives a `:return_to` option to properly update
|
||||
the URL when the modal is closed.
|
||||
|
||||
## Examples
|
||||
|
||||
<.modal return_to={Routes.thread_index_path(@socket, :index)}>
|
||||
<.live_component
|
||||
module={SomethingErlangWeb.ThreadLive.FormComponent}
|
||||
id={@thread.id || :new}
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
return_to={Routes.thread_index_path(@socket, :index)}
|
||||
thread: @thread
|
||||
/>
|
||||
</.modal>
|
||||
"""
|
||||
def modal(assigns) do
|
||||
assigns = assign_new(assigns, :return_to, fn -> nil end)
|
||||
|
||||
~H"""
|
||||
<div id="modal" class="phx-modal fade-in" phx-remove={hide_modal()}>
|
||||
<div
|
||||
id="modal-content"
|
||||
class="phx-modal-content fade-in-scale"
|
||||
phx-click-away={JS.dispatch("click", to: "#close")}
|
||||
phx-window-keydown={JS.dispatch("click", to: "#close")}
|
||||
phx-key="escape"
|
||||
>
|
||||
<%= if @return_to do %>
|
||||
<%= live_patch "✖",
|
||||
to: @return_to,
|
||||
id: "close",
|
||||
class: "phx-modal-close",
|
||||
phx_click: hide_modal()
|
||||
%>
|
||||
<% else %>
|
||||
<a id="close" href="#" class="phx-modal-close" phx-click={hide_modal()}>✖</a>
|
||||
<% end %>
|
||||
|
||||
<%= render_slot(@inner_block) %>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
defp hide_modal(js \\ %JS{}) do
|
||||
js
|
||||
|> JS.hide(to: "#modal", transition: "fade-out")
|
||||
|> JS.hide(to: "#modal-content", transition: "fade-out-scale")
|
||||
end
|
||||
end
|
@ -1,43 +1,25 @@
|
||||
defmodule SomethingErlangWeb.ThreadLive.Show do
|
||||
defmodule SomethingErlangWeb.ThreadLive do
|
||||
use SomethingErlangWeb, :live_view
|
||||
on_mount SomethingErlangWeb.UserLiveAuth
|
||||
|
||||
alias SomethingErlang.Grover
|
||||
|
||||
require Logger
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
Grover.mount(socket.assigns.current_user)
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"id" => id, "page" => page}, _, socket) do
|
||||
thread = Grover.get_thread!(id, page |> String.to_integer())
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:page_title, thread.title)
|
||||
|> assign(:thread, thread)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(%{"id" => id}, _, socket) do
|
||||
{:noreply,
|
||||
push_redirect(socket,
|
||||
to: Routes.thread_show_path(socket, :show, id, page: 1)
|
||||
)}
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<h1>Threads!</h1>
|
||||
<pre class="whitespace-pre-wrap break-all">
|
||||
<%= inspect(@current_user) %>
|
||||
</pre>
|
||||
"""
|
||||
end
|
||||
|
||||
def post(assigns) do
|
||||
~H"""
|
||||
<div class="post">
|
||||
<.user info={@author} />
|
||||
<article class="postbody">
|
||||
<%= raw @article %>
|
||||
</article>
|
||||
<.toolbar date={@date} />
|
||||
<.user info={@author} />
|
||||
<article class="postbody">
|
||||
<%= raw(@article) %>
|
||||
</article>
|
||||
<.toolbar date={@date} />
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
@ -45,10 +27,10 @@ defmodule SomethingErlangWeb.ThreadLive.Show do
|
||||
def user(assigns) do
|
||||
~H"""
|
||||
<aside class="userinfo bg-base-100">
|
||||
<h3 class="mb-4"><%= @info.name %></h3>
|
||||
<div class="title hidden sm:flex flex-col text-sm pr-4">
|
||||
<%= raw @info.title %>
|
||||
</div>
|
||||
<h3 class="mb-4"><%= @info.name %></h3>
|
||||
<div class="title hidden sm:flex flex-col text-sm pr-4">
|
||||
<%= raw(@info.title) %>
|
||||
</div>
|
||||
</aside>
|
||||
"""
|
||||
end
|
||||
@ -56,7 +38,8 @@ defmodule SomethingErlangWeb.ThreadLive.Show do
|
||||
def toolbar(assigns) do
|
||||
~H"""
|
||||
<div class="sm:col-span-2 text-sm p-2 px-4">
|
||||
<%= @date |> Calendar.strftime("%A, %b %d %Y @ %H:%M") %></div>
|
||||
<%= @date |> Calendar.strftime("%A, %b %d %Y @ %H:%M") %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
@ -80,22 +63,49 @@ defmodule SomethingErlangWeb.ThreadLive.Show do
|
||||
|
||||
~H"""
|
||||
<div class="navbar my-4 bg-base-200">
|
||||
<div class="flex-1"></div>
|
||||
<div class="pagination flex-none btn-group grid grid-cols-5">
|
||||
<%= for btn <- buttons do %>
|
||||
<%= live_redirect class: "btn btn-sm btn-ghost" <> btn.special,
|
||||
to: Routes.thread_show_path(@socket, :show, @thread.id, page: btn.page) do %>
|
||||
<%= case btn.label do %>
|
||||
<% "«" -> %><Icons.chevron_left_double /><%= btn.page %>
|
||||
<% "‹" -> %><Icons.chevron_left /><%= btn.page %>
|
||||
<% "›" -> %><%= btn.page %><Icons.chevron_right />
|
||||
<% "»" -> %><%= btn.page %><Icons.chevron_right_double />
|
||||
<% _ -> %><%= btn.page %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="flex-1"></div>
|
||||
<div class="pagination flex-none btn-group grid grid-cols-5">
|
||||
<%= for btn <- buttons do %>
|
||||
<%= live_redirect class: "btn btn-sm btn-ghost" <> btn.special,
|
||||
to: ~p"/thread/#{@thread.id}?page=#{btn.page}" do %>
|
||||
<%= case btn.label do %>
|
||||
<% "«" -> %>
|
||||
<Icons.chevron_left_double /><%= btn.page %>
|
||||
<% "‹" -> %>
|
||||
<Icons.chevron_left /><%= btn.page %>
|
||||
<% "›" -> %>
|
||||
<%= btn.page %><Icons.chevron_right />
|
||||
<% "»" -> %>
|
||||
<%= btn.page %><Icons.chevron_right_double />
|
||||
<% _ -> %>
|
||||
<%= btn.page %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def handle_params(%{"id" => id, "page" => page}, _, socket) do
|
||||
thread = Grover.get_thread!(id, page |> String.to_integer())
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:page_title, thread.title)
|
||||
|> assign(:thread, thread)}
|
||||
end
|
||||
|
||||
def handle_params(%{"id" => id}, _, socket) do
|
||||
params = %{page: 1}
|
||||
{:noreply, push_redirect(socket, to: ~p"/thread/#{id}?#{params}")}
|
||||
end
|
||||
|
||||
def handle_params(%{}, _, socket) do
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
@ -1,55 +0,0 @@
|
||||
defmodule SomethingErlangWeb.ThreadLive.FormComponent do
|
||||
use SomethingErlangWeb, :live_component
|
||||
|
||||
alias SomethingErlang.Forums
|
||||
|
||||
@impl true
|
||||
def update(%{thread: thread} = assigns, socket) do
|
||||
changeset = Forums.change_thread(thread)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> assign(:changeset, changeset)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("validate", %{"thread" => thread_params}, socket) do
|
||||
changeset =
|
||||
socket.assigns.thread
|
||||
|> Forums.change_thread(thread_params)
|
||||
|> Map.put(:action, :validate)
|
||||
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
end
|
||||
|
||||
def handle_event("save", %{"thread" => thread_params}, socket) do
|
||||
save_thread(socket, socket.assigns.action, thread_params)
|
||||
end
|
||||
|
||||
defp save_thread(socket, :edit, thread_params) do
|
||||
case Forums.update_thread(socket.assigns.thread, thread_params) do
|
||||
{:ok, _thread} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Thread updated successfully")
|
||||
|> push_redirect(to: socket.assigns.return_to)}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
end
|
||||
end
|
||||
|
||||
defp save_thread(socket, :new, thread_params) do
|
||||
case Forums.create_thread(thread_params) do
|
||||
{:ok, _thread} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Thread created successfully")
|
||||
|> push_redirect(to: socket.assigns.return_to)}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, changeset: changeset)}
|
||||
end
|
||||
end
|
||||
end
|
@ -1,24 +0,0 @@
|
||||
<div>
|
||||
<h2><%= @title %></h2>
|
||||
|
||||
<.form
|
||||
let={f}
|
||||
for={@changeset}
|
||||
id="thread-form"
|
||||
phx-target={@myself}
|
||||
phx-change="validate"
|
||||
phx-submit="save">
|
||||
|
||||
<%= label f, :title %>
|
||||
<%= text_input f, :title %>
|
||||
<%= error_tag f, :title %>
|
||||
|
||||
<%= label f, :thread_id %>
|
||||
<%= number_input f, :thread_id %>
|
||||
<%= error_tag f, :thread_id %>
|
||||
|
||||
<div>
|
||||
<%= submit "Save", phx_disable_with: "Saving..." %>
|
||||
</div>
|
||||
</.form>
|
||||
</div>
|
@ -1,46 +0,0 @@
|
||||
defmodule SomethingErlangWeb.ThreadLive.Index do
|
||||
use SomethingErlangWeb, :live_view
|
||||
|
||||
alias SomethingErlang.Forums
|
||||
alias SomethingErlang.Forums.Thread
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, assign(socket, :threads, list_threads())}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, _url, socket) do
|
||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
||||
end
|
||||
|
||||
defp apply_action(socket, :edit, %{"id" => id}) do
|
||||
socket
|
||||
|> assign(:page_title, "Edit Thread")
|
||||
|> assign(:thread, Forums.get_thread!(id))
|
||||
end
|
||||
|
||||
defp apply_action(socket, :new, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "New Thread")
|
||||
|> assign(:thread, %Thread{})
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "Listing Threads")
|
||||
|> assign(:thread, nil)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
thread = Forums.get_thread!(id)
|
||||
{:ok, _} = Forums.delete_thread(thread)
|
||||
|
||||
{:noreply, assign(socket, :threads, list_threads())}
|
||||
end
|
||||
|
||||
defp list_threads do
|
||||
Forums.list_threads()
|
||||
end
|
||||
end
|
@ -1,41 +0,0 @@
|
||||
<h1>Listing Threads</h1>
|
||||
|
||||
<%= if @live_action in [:new, :edit] do %>
|
||||
<.modal return_to={Routes.thread_index_path(@socket, :index)}>
|
||||
<.live_component
|
||||
module={SomethingErlangWeb.ThreadLive.FormComponent}
|
||||
id={@thread.id || :new}
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
thread={@thread}
|
||||
return_to={Routes.thread_index_path(@socket, :index)}
|
||||
/>
|
||||
</.modal>
|
||||
<% end %>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Thread</th>
|
||||
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="threads">
|
||||
<%= for thread <- @threads do %>
|
||||
<tr id={"thread-#{thread.id}"}>
|
||||
<td><%= thread.title %></td>
|
||||
<td><%= thread.thread_id %></td>
|
||||
|
||||
<td>
|
||||
<span><%= live_redirect "Show", to: Routes.thread_show_path(@socket, :show, thread) %></span>
|
||||
<span><%= live_patch "Edit", to: Routes.thread_index_path(@socket, :edit, thread) %></span>
|
||||
<span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: thread.id, data: [confirm: "Are you sure?"] %></span>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<span><%= live_patch "New Thread", to: Routes.thread_index_path(@socket, :new) %></span>
|
@ -1,26 +0,0 @@
|
||||
<%= if @live_action in [:edit] do %>
|
||||
<.modal return_to={Routes.thread_show_path(@socket, :show, @thread)}>
|
||||
<.live_component
|
||||
module={SomethingErlangWeb.ThreadLive.FormComponent}
|
||||
id={@thread.id}
|
||||
title={@page_title}
|
||||
action={@live_action}
|
||||
thread={@thread}
|
||||
return_to={Routes.thread_show_path(@socket, :show, @thread)}
|
||||
/>
|
||||
</.modal>
|
||||
<% end %>
|
||||
|
||||
<h2>
|
||||
<%= raw @thread.title %>
|
||||
</h2>
|
||||
|
||||
<div class="thread my-8">
|
||||
<.pagination socket={@socket} thread={@thread} />
|
||||
|
||||
<%= for post <- @thread.posts do %>
|
||||
<.post author={post.userinfo} article={post.postbody} date={post.postdate} />
|
||||
<% end %>
|
||||
|
||||
<.pagination socket={@socket} thread={@thread} />
|
||||
</div>
|
@ -0,0 +1,45 @@
|
||||
defmodule SomethingErlangWeb.UserConfirmationInstructionsLive do
|
||||
use SomethingErlangWeb, :live_view
|
||||
|
||||
alias SomethingErlang.Accounts
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.header>Resend confirmation instructions</.header>
|
||||
|
||||
<.simple_form :let={f} for={:user} id="resend_confirmation_form" phx-submit="send_instructions">
|
||||
<.input field={{f, :email}} type="email" label="Email" required />
|
||||
<:actions>
|
||||
<.button phx-disable-with="Sending...">Resend confirmation instructions</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
|
||||
<p>
|
||||
<.link href={~p"/users/register"}>Register</.link>
|
||||
|
|
||||
<.link href={~p"/users/log_in"}>Log in</.link>
|
||||
</p>
|
||||
"""
|
||||
end
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def handle_event("send_instructions", %{"user" => %{"email" => email}}, socket) do
|
||||
if user = Accounts.get_user_by_email(email) do
|
||||
Accounts.deliver_user_confirmation_instructions(
|
||||
user,
|
||||
&url(~p"/users/confirm/#{&1}")
|
||||
)
|
||||
end
|
||||
|
||||
info =
|
||||
"If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly."
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, info)
|
||||
|> redirect(to: ~p"/")}
|
||||
end
|
||||
end
|
58
lib/something_erlang_web/live/user_confirmation_live.ex
Normal file
58
lib/something_erlang_web/live/user_confirmation_live.ex
Normal file
@ -0,0 +1,58 @@
|
||||
defmodule SomethingErlangWeb.UserConfirmationLive do
|
||||
use SomethingErlangWeb, :live_view
|
||||
|
||||
alias SomethingErlang.Accounts
|
||||
|
||||
def render(%{live_action: :edit} = assigns) do
|
||||
~H"""
|
||||
<div class="mx-auto max-w-sm">
|
||||
<.header class="text-center">Confirm Account</.header>
|
||||
|
||||
<.simple_form :let={f} for={:user} id="confirmation_form" phx-submit="confirm_account">
|
||||
<.input field={{f, :token}} type="hidden" value={@token} />
|
||||
<:actions>
|
||||
<.button phx-disable-with="Confirming..." class="w-full">Confirm my account</.button>
|
||||
</: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
|
||||
|
||||
def mount(params, _session, socket) do
|
||||
{:ok, assign(socket, token: params["token"]), temporary_assigns: [token: nil]}
|
||||
end
|
||||
|
||||
# Do not log in the user after confirmation to avoid a
|
||||
# leaked token giving the user access to the account.
|
||||
def handle_event("confirm_account", %{"user" => %{"token" => token}}, socket) do
|
||||
case Accounts.confirm_user(token) do
|
||||
{:ok, _} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "User confirmed successfully.")
|
||||
|> redirect(to: ~p"/")}
|
||||
|
||||
:error ->
|
||||
# If there is a current user and the account was already confirmed,
|
||||
# then odds are that the confirmation link was already visited, either
|
||||
# by some automation or by the user themselves, so we redirect without
|
||||
# a warning message.
|
||||
case socket.assigns do
|
||||
%{current_user: %{confirmed_at: confirmed_at}} when not is_nil(confirmed_at) ->
|
||||
{:noreply, redirect(socket, to: ~p"/")}
|
||||
|
||||
%{} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, "User confirmation link is invalid or it has expired.")
|
||||
|> redirect(to: ~p"/")}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
51
lib/something_erlang_web/live/user_forgot_password_live.ex
Normal file
51
lib/something_erlang_web/live/user_forgot_password_live.ex
Normal file
@ -0,0 +1,51 @@
|
||||
defmodule SomethingErlangWeb.UserForgotPasswordLive do
|
||||
use SomethingErlangWeb, :live_view
|
||||
|
||||
alias SomethingErlang.Accounts
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="mx-auto max-w-sm">
|
||||
<.header class="text-center">
|
||||
Forgot your password?
|
||||
<:subtitle>We'll send a password reset link to your inbox</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.simple_form :let={f} id="reset_password_form" for={:user} phx-submit="send_email">
|
||||
<.input field={{f, :email}} type="email" placeholder="Email" required />
|
||||
<:actions>
|
||||
<.button phx-disable-with="Sending..." class="w-full">
|
||||
Send password reset instructions
|
||||
</.button>
|
||||
</: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
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def handle_event("send_email", %{"user" => %{"email" => email}}, socket) do
|
||||
if user = Accounts.get_user_by_email(email) do
|
||||
Accounts.deliver_user_reset_password_instructions(
|
||||
user,
|
||||
&url(~p"/users/reset_password/#{&1}")
|
||||
)
|
||||
end
|
||||
|
||||
info =
|
||||
"If your email is in our system, you will receive instructions to reset your password shortly."
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, info)
|
||||
|> redirect(to: ~p"/")}
|
||||
end
|
||||
end
|
@ -1,16 +0,0 @@
|
||||
defmodule SomethingErlangWeb.UserLiveAuth do
|
||||
import Phoenix.LiveView
|
||||
|
||||
alias SomethingErlang.Accounts
|
||||
|
||||
def on_mount(:default, _params, %{"user_token" => user_token} = _session, socket) do
|
||||
user = Accounts.get_user_by_session_token(user_token)
|
||||
socket = assign_new(socket, :current_user, fn -> user end)
|
||||
|
||||
if socket.assigns.current_user.confirmed_at do
|
||||
{:cont, socket}
|
||||
else
|
||||
{:halt, redirect(socket, to: "/login")}
|
||||
end
|
||||
end
|
||||
end
|
49
lib/something_erlang_web/live/user_login_live.ex
Normal file
49
lib/something_erlang_web/live/user_login_live.ex
Normal file
@ -0,0 +1,49 @@
|
||||
defmodule SomethingErlangWeb.UserLoginLive do
|
||||
use SomethingErlangWeb, :live_view
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="mx-auto max-w-sm">
|
||||
<.header class="text-center">
|
||||
Sign in to account
|
||||
<:subtitle>
|
||||
Don't have an account?
|
||||
<.link navigate={~p"/users/register"} class="font-semibold text-brand hover:underline">
|
||||
Sign up
|
||||
</.link>
|
||||
for an account now.
|
||||
</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.simple_form
|
||||
:let={f}
|
||||
id="login_form"
|
||||
for={:user}
|
||||
action={~p"/users/log_in"}
|
||||
as={:user}
|
||||
phx-update="ignore"
|
||||
>
|
||||
<.input field={{f, :email}} type="email" label="Email" required />
|
||||
<.input field={{f, :password}} type="password" label="Password" required />
|
||||
|
||||
<:actions :let={f}>
|
||||
<.input field={{f, :remember_me}} type="checkbox" label="Keep me logged in" />
|
||||
<.link href={~p"/users/reset_password"} class="text-sm font-semibold">
|
||||
Forgot your password?
|
||||
</.link>
|
||||
</:actions>
|
||||
<:actions>
|
||||
<.button phx-disable-with="Signing in..." class="w-full">
|
||||
Sign in <span aria-hidden="true">→</span>
|
||||
</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
email = live_flash(socket.assigns.flash, :email)
|
||||
{:ok, assign(socket, email: email), temporary_assigns: [email: nil]}
|
||||
end
|
||||
end
|
74
lib/something_erlang_web/live/user_registration_live.ex
Normal file
74
lib/something_erlang_web/live/user_registration_live.ex
Normal file
@ -0,0 +1,74 @@
|
||||
defmodule SomethingErlangWeb.UserRegistrationLive do
|
||||
use SomethingErlangWeb, :live_view
|
||||
|
||||
alias SomethingErlang.Accounts
|
||||
alias SomethingErlang.Accounts.User
|
||||
|
||||
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-brand hover:underline">
|
||||
Sign in
|
||||
</.link>
|
||||
to your account now.
|
||||
</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.simple_form
|
||||
:let={f}
|
||||
id="registration_form"
|
||||
for={@changeset}
|
||||
phx-submit="save"
|
||||
phx-change="validate"
|
||||
phx-trigger-action={@trigger_submit}
|
||||
action={~p"/users/log_in?_action=registered"}
|
||||
method="post"
|
||||
as={:user}
|
||||
>
|
||||
<.error :if={@changeset.action == :insert}>
|
||||
Oops, something went wrong! Please check the errors below.
|
||||
</.error>
|
||||
|
||||
<.input field={{f, :email}} type="email" label="Email" required />
|
||||
<.input field={{f, :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
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
changeset = Accounts.change_user_registration(%User{})
|
||||
socket = assign(socket, changeset: changeset, trigger_submit: false)
|
||||
{:ok, socket, temporary_assigns: [changeset: nil]}
|
||||
end
|
||||
|
||||
def handle_event("save", %{"user" => user_params}, socket) do
|
||||
case Accounts.register_user(user_params) do
|
||||
{:ok, user} ->
|
||||
{:ok, _} =
|
||||
Accounts.deliver_user_confirmation_instructions(
|
||||
user,
|
||||
&url(~p"/users/confirm/#{&1}")
|
||||
)
|
||||
|
||||
changeset = Accounts.change_user_registration(user)
|
||||
{:noreply, assign(socket, trigger_submit: true, changeset: changeset)}
|
||||
|
||||
{:error, %Ecto.Changeset{} = changeset} ->
|
||||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("validate", %{"user" => user_params}, socket) do
|
||||
changeset = Accounts.change_user_registration(%User{}, user_params)
|
||||
{:noreply, assign(socket, changeset: Map.put(changeset, :action, :validate))}
|
||||
end
|
||||
end
|
87
lib/something_erlang_web/live/user_reset_password_live.ex
Normal file
87
lib/something_erlang_web/live/user_reset_password_live.ex
Normal file
@ -0,0 +1,87 @@
|
||||
defmodule SomethingErlangWeb.UserResetPasswordLive do
|
||||
use SomethingErlangWeb, :live_view
|
||||
|
||||
alias SomethingErlang.Accounts
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="mx-auto max-w-sm">
|
||||
<.header class="text-center">Reset Password</.header>
|
||||
|
||||
<.simple_form
|
||||
:let={f}
|
||||
for={@changeset}
|
||||
id="reset_password_form"
|
||||
phx-submit="reset_password"
|
||||
phx-change="validate"
|
||||
>
|
||||
<.error :if={@changeset.action == :insert}>
|
||||
Oops, something went wrong! Please check the errors below.
|
||||
</.error>
|
||||
|
||||
<.input field={{f, :password}} type="password" label="New password" required />
|
||||
<.input
|
||||
field={{f, :password_confirmation}}
|
||||
type="password"
|
||||
label="Confirm new password"
|
||||
required
|
||||
/>
|
||||
<:actions>
|
||||
<.button phx-disable-with="Resetting..." class="w-full">Reset Password</.button>
|
||||
</: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
|
||||
|
||||
def mount(params, _session, socket) do
|
||||
socket = assign_user_and_token(socket, params)
|
||||
|
||||
socket =
|
||||
case socket.assigns do
|
||||
%{user: user} ->
|
||||
assign(socket, :changeset, Accounts.change_user_password(user))
|
||||
|
||||
_ ->
|
||||
socket
|
||||
end
|
||||
|
||||
{:ok, socket, temporary_assigns: [changeset: nil]}
|
||||
end
|
||||
|
||||
# Do not log in the user after reset password to avoid a
|
||||
# leaked token giving the user access to the account.
|
||||
def handle_event("reset_password", %{"user" => user_params}, socket) do
|
||||
case Accounts.reset_user_password(socket.assigns.user, user_params) do
|
||||
{:ok, _} ->
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:info, "Password reset successfully.")
|
||||
|> redirect(to: ~p"/users/log_in")}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, :changeset, Map.put(changeset, :action, :insert))}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("validate", %{"user" => user_params}, socket) do
|
||||
changeset = Accounts.change_user_password(socket.assigns.user, user_params)
|
||||
{:noreply, assign(socket, changeset: Map.put(changeset, :action, :validate))}
|
||||
end
|
||||
|
||||
defp assign_user_and_token(socket, %{"token" => token}) do
|
||||
if user = Accounts.get_user_by_reset_password_token(token) do
|
||||
assign(socket, user: user, token: token)
|
||||
else
|
||||
socket
|
||||
|> put_flash(:error, "Reset password link is invalid or it has expired.")
|
||||
|> redirect(to: ~p"/")
|
||||
end
|
||||
end
|
||||
end
|
161
lib/something_erlang_web/live/user_settings_live.ex
Normal file
161
lib/something_erlang_web/live/user_settings_live.ex
Normal file
@ -0,0 +1,161 @@
|
||||
defmodule SomethingErlangWeb.UserSettingsLive do
|
||||
use SomethingErlangWeb, :live_view
|
||||
|
||||
alias SomethingErlang.Accounts
|
||||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.header>Change Email</.header>
|
||||
|
||||
<.simple_form
|
||||
:let={f}
|
||||
id="email_form"
|
||||
for={@email_changeset}
|
||||
phx-submit="update_email"
|
||||
phx-change="validate_email"
|
||||
>
|
||||
<.error :if={@email_changeset.action == :insert}>
|
||||
Oops, something went wrong! Please check the errors below.
|
||||
</.error>
|
||||
|
||||
<.input field={{f, :email}} type="email" label="Email" required />
|
||||
|
||||
<.input
|
||||
field={{f, :current_password}}
|
||||
name="current_password"
|
||||
id="current_password_for_email"
|
||||
type="password"
|
||||
label="Current password"
|
||||
value={@email_form_current_password}
|
||||
required
|
||||
/>
|
||||
<:actions>
|
||||
<.button phx-disable-with="Changing...">Change Email</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
|
||||
<.header>Change Password</.header>
|
||||
|
||||
<.simple_form
|
||||
:let={f}
|
||||
id="password_form"
|
||||
for={@password_changeset}
|
||||
action={~p"/users/log_in?_action=password_updated"}
|
||||
method="post"
|
||||
phx-change="validate_password"
|
||||
phx-submit="update_password"
|
||||
phx-trigger-action={@trigger_submit}
|
||||
>
|
||||
<.error :if={@password_changeset.action == :insert}>
|
||||
Oops, something went wrong! Please check the errors below.
|
||||
</.error>
|
||||
|
||||
<.input field={{f, :email}} type="hidden" value={@current_email} />
|
||||
|
||||
<.input field={{f, :password}} type="password" label="New password" required />
|
||||
<.input field={{f, :password_confirmation}} type="password" label="Confirm new password" />
|
||||
<.input
|
||||
field={{f, :current_password}}
|
||||
name="current_password"
|
||||
type="password"
|
||||
label="Current password"
|
||||
id="current_password_for_password"
|
||||
value={@current_password}
|
||||
required
|
||||
/>
|
||||
<:actions>
|
||||
<.button phx-disable-with="Changing...">Change Password</.button>
|
||||
</:actions>
|
||||
</.simple_form>
|
||||
"""
|
||||
end
|
||||
|
||||
def mount(%{"token" => token}, _session, socket) do
|
||||
socket =
|
||||
case Accounts.update_user_email(socket.assigns.current_user, token) do
|
||||
:ok ->
|
||||
put_flash(socket, :info, "Email changed successfully.")
|
||||
|
||||
:error ->
|
||||
put_flash(socket, :error, "Email change link is invalid or it has expired.")
|
||||
end
|
||||
|
||||
{:ok, push_navigate(socket, to: ~p"/users/settings")}
|
||||
end
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
user = socket.assigns.current_user
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:current_password, nil)
|
||||
|> assign(:email_form_current_password, nil)
|
||||
|> assign(:current_email, user.email)
|
||||
|> assign(:email_changeset, Accounts.change_user_email(user))
|
||||
|> assign(:password_changeset, Accounts.change_user_password(user))
|
||||
|> assign(:trigger_submit, false)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def handle_event("validate_email", params, socket) do
|
||||
%{"current_password" => password, "user" => user_params} = params
|
||||
email_changeset = Accounts.change_user_email(socket.assigns.current_user, user_params)
|
||||
|
||||
socket =
|
||||
assign(socket,
|
||||
email_changeset: Map.put(email_changeset, :action, :validate),
|
||||
email_form_current_password: password
|
||||
)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("update_email", params, socket) do
|
||||
%{"current_password" => password, "user" => user_params} = params
|
||||
user = socket.assigns.current_user
|
||||
|
||||
case Accounts.apply_user_email(user, password, user_params) do
|
||||
{:ok, applied_user} ->
|
||||
Accounts.deliver_user_update_email_instructions(
|
||||
applied_user,
|
||||
user.email,
|
||||
&url(~p"/users/settings/confirm_email/#{&1}")
|
||||
)
|
||||
|
||||
info = "A link to confirm your email change has been sent to the new address."
|
||||
{:noreply, put_flash(socket, :info, info)}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, :email_changeset, Map.put(changeset, :action, :insert))}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("validate_password", params, socket) do
|
||||
%{"current_password" => password, "user" => user_params} = params
|
||||
password_changeset = Accounts.change_user_password(socket.assigns.current_user, user_params)
|
||||
|
||||
{:noreply,
|
||||
socket
|
||||
|> assign(:password_changeset, Map.put(password_changeset, :action, :validate))
|
||||
|> assign(:current_password, password)}
|
||||
end
|
||||
|
||||
def handle_event("update_password", params, socket) do
|
||||
%{"current_password" => password, "user" => user_params} = params
|
||||
user = socket.assigns.current_user
|
||||
|
||||
case Accounts.update_user_password(user, password, user_params) do
|
||||
{:ok, user} ->
|
||||
socket =
|
||||
socket
|
||||
|> assign(:trigger_submit, true)
|
||||
|> assign(:password_changeset, Accounts.change_user_password(user, user_params))
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, :password_changeset, changeset)}
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user