changes to accommodate a delete user modal
This commit is contained in:
parent
9651887f34
commit
0039146cd4
13 changed files with 302 additions and 209 deletions
|
@ -17,7 +17,7 @@ defmodule Bones73k.Accounts.User do
|
|||
|
||||
@derive {Inspect, except: [:password]}
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
# @foreign_key_type :binary_id
|
||||
schema "users" do
|
||||
field :email, :string
|
||||
field :password, :string, virtual: true
|
||||
|
|
|
@ -8,7 +8,7 @@ defmodule Bones73k.Properties.Property do
|
|||
field :description, :string
|
||||
field :name, :string
|
||||
field :price, :decimal
|
||||
belongs_to :user, Bones73k.Accounts.User
|
||||
field :user_id, :binary_id
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule Bones73kWeb.ModalComponent do
|
|||
phx-target="#<%= @id %>"
|
||||
phx-page-loading>
|
||||
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header">
|
||||
|
@ -19,9 +19,7 @@ defmodule Bones73kWeb.ModalComponent do
|
|||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<%= live_component @socket, @component, @opts %>
|
||||
</div>
|
||||
<%= live_component @socket, @component, @opts %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,10 +21,16 @@ defmodule Bones73kWeb.PropertyLive.FormComponent do
|
|||
{:noreply, assign(socket, :changeset, changeset)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("save", %{"property" => property_params}, socket) do
|
||||
save_property(socket, socket.assigns.action, property_params)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("cancel", _, socket) do
|
||||
{:noreply, push_event(socket, "modal-please-hide", %{})}
|
||||
end
|
||||
|
||||
defp save_property(socket, :edit, property_params) do
|
||||
case Properties.update_property(socket.assigns.property, property_params) do
|
||||
{:ok, _property} ->
|
||||
|
|
|
@ -5,41 +5,53 @@
|
|||
phx_submit: "save"
|
||||
], fn f -> %>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="mb-3" phx-feedback-for="<%= input_id(f, :name)%>">
|
||||
<%= label f, :name, class: "form-label" %>
|
||||
<div class="input-group has-validation">
|
||||
<%= text_input f, :name,
|
||||
class: input_class(f, :name, "form-control"),
|
||||
aria_describedby: error_id(f, :name)
|
||||
%>
|
||||
<%= error_tag f, :name %>
|
||||
<div class="mb-3" phx-feedback-for="<%= input_id(f, :name)%>">
|
||||
<%= label f, :name, class: "form-label" %>
|
||||
<div class="input-group has-validation">
|
||||
<%= text_input f, :name,
|
||||
class: input_class(f, :name, "form-control"),
|
||||
aria_describedby: error_id(f, :name)
|
||||
%>
|
||||
<%= error_tag f, :name %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" phx-feedback-for="<%= input_id(f, :price)%>">
|
||||
<%= label f, :price, class: "form-label" %>
|
||||
<div class="input-group has-validation">
|
||||
<%= number_input f, :price,
|
||||
class: input_class(f, :price, "form-control"),
|
||||
step: "any",
|
||||
aria_describedby: error_id(f, :price)
|
||||
%>
|
||||
<%= error_tag f, :price %>
|
||||
<div class="mb-3" phx-feedback-for="<%= input_id(f, :price)%>">
|
||||
<%= label f, :price, class: "form-label" %>
|
||||
<div class="input-group has-validation">
|
||||
<%= number_input f, :price,
|
||||
class: input_class(f, :price, "form-control"),
|
||||
step: "any",
|
||||
aria_describedby: error_id(f, :price)
|
||||
%>
|
||||
<%= error_tag f, :price %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" phx-feedback-for="<%= input_id(f, :description)%>">
|
||||
<%= label f, :description, class: "form-label" %>
|
||||
<div class="input-group has-validation">
|
||||
<%= textarea f, :description,
|
||||
class: input_class(f, :description, "form-control"),
|
||||
aria_describedby: error_id(f, :description)
|
||||
%>
|
||||
<%= error_tag f, :description %>
|
||||
<div class="mb-3" phx-feedback-for="<%= input_id(f, :description)%>">
|
||||
<%= label f, :description, class: "form-label" %>
|
||||
<div class="input-group has-validation">
|
||||
<%= textarea f, :description,
|
||||
class: input_class(f, :description, "form-control"),
|
||||
aria_describedby: error_id(f, :description)
|
||||
%>
|
||||
<%= error_tag f, :description %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= submit "Save", phx_disable_with: "Saving...", class: "btn btn-primary" %>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<%= link "Cancel", to: "#", class: "btn btn-secondary me-2", phx_click: "cancel", phx_target: @myself %>
|
||||
<%= submit "Save",
|
||||
class: "btn btn-primary ",
|
||||
disabled: !@changeset.valid?,
|
||||
aria_disabled: !@changeset.valid? && "true" || false,
|
||||
phx_disable_with: "Saving..."
|
||||
%>
|
||||
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
|
|
44
lib/bones73k_web/live/user_management/delete_component.ex
Normal file
44
lib/bones73k_web/live/user_management/delete_component.ex
Normal file
|
@ -0,0 +1,44 @@
|
|||
defmodule Bones73kWeb.UserManagement.DeleteComponent do
|
||||
use Bones73kWeb, :live_component
|
||||
|
||||
alias Bones73k.Accounts
|
||||
|
||||
@impl true
|
||||
def update(assigns, socket) do
|
||||
socket
|
||||
|> assign(assigns)
|
||||
|> live_okreply()
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("cancel", _, socket) do
|
||||
{:noreply, push_event(socket, "modal-please-hide", %{})}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("confirm", %{"id" => id, "email" => email}, socket) do
|
||||
id
|
||||
|> Accounts.get_user()
|
||||
|> Accounts.delete_user()
|
||||
|> case do
|
||||
{:ok, _} ->
|
||||
flash = {:info, "User deleted successfully: \"#{email}\""}
|
||||
send(self(), {:put_flash_message, flash})
|
||||
|
||||
socket
|
||||
|> push_event("modal-please-hide", %{})
|
||||
|> live_noreply()
|
||||
|
||||
{:error, _} ->
|
||||
flash =
|
||||
{:error,
|
||||
"Some error trying to delete user \"#{email}\". Possibly already deleted? Reloading list..."}
|
||||
|
||||
send(self(), {:put_flash_message, flash})
|
||||
|
||||
socket
|
||||
|> push_event("modal-please-hide", %{})
|
||||
|> live_noreply()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
<div class="modal-body">
|
||||
|
||||
Are you sure you want to delete "<%= @delete_user.email %>"?
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<%= link "Cancel", to: "#", class: "btn btn-secondary me-2", phx_click: "cancel", phx_target: @myself %>
|
||||
<%= link "Confirm Delete", to: "#",
|
||||
class: "btn btn-danger",
|
||||
phx_click: "confirm",
|
||||
phx_target: @myself,
|
||||
phx_value_id: @delete_user.id,
|
||||
phx_value_email: @delete_user.email %>
|
||||
|
||||
</div>
|
|
@ -10,7 +10,6 @@ defmodule Bones73kWeb.UserManagement.FormComponent do
|
|||
socket
|
||||
|> assign(assigns)
|
||||
|> init_changeset(assigns)
|
||||
|> assign(:role_id, 1)
|
||||
|> live_okreply()
|
||||
end
|
||||
|
||||
|
@ -40,7 +39,7 @@ defmodule Bones73kWeb.UserManagement.FormComponent do
|
|||
&Routes.user_confirmation_url(socket, :confirm, &1)
|
||||
)
|
||||
|
||||
flash = {:success, "User created successfully: #{user.email}"}
|
||||
flash = {:info, "User created successfully: #{user.email}"}
|
||||
send(self(), {:put_flash_message, flash})
|
||||
|
||||
socket
|
||||
|
@ -81,6 +80,11 @@ defmodule Bones73kWeb.UserManagement.FormComponent do
|
|||
save_user(socket, user_params)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("cancel", _, socket) do
|
||||
{:noreply, push_event(socket, "modal-please-hide", %{})}
|
||||
end
|
||||
|
||||
def role_description(role) when is_atom(role) do
|
||||
Keyword.get(User.roles(), role)
|
||||
end
|
||||
|
|
|
@ -4,64 +4,69 @@
|
|||
phx_submit: "save"
|
||||
], 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"] %>
|
||||
</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,
|
||||
autofocus: true,
|
||||
phx_debounce: "250",
|
||||
aria_describedby: error_id(f, :email)
|
||||
%>
|
||||
<%= error_tag f, :email %>
|
||||
<div class="modal-body">
|
||||
|
||||
<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"] %>
|
||||
</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,
|
||||
autofocus: true,
|
||||
phx_debounce: "250",
|
||||
aria_describedby: error_id(f, :email)
|
||||
%>
|
||||
<%= error_tag f, :email %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= if Roles.can?(@current_user, %User{}, :edit_role) do %>
|
||||
<%= label f, :role, class: "form-label" %>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">
|
||||
<%= icon_div @socket, "bi-shield-shaded", [class: "icon"] %>
|
||||
</span>
|
||||
<%= select f, :role, Enum.map(User.roles(), fn {k, _v} -> {String.capitalize(Atom.to_string(k)), k} end), class: "form-select" %>
|
||||
<span class="valid-feedback text-primary" style="display: block;">
|
||||
<%= role_description(input_value(f, :role)) %>
|
||||
</span>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= hidden_input f, :role, value: input_value(f, :role) %>
|
||||
<% end %>
|
||||
|
||||
<div phx-feedback-for="<%= input_id(f, :password) %>">
|
||||
<%= label f, :password, class: "form-label" %>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text">
|
||||
<%= icon_div @socket, "bi-key", [class: "icon"] %>
|
||||
</span>
|
||||
<%= password_input f, :password,
|
||||
value: input_value(f, :password),
|
||||
class: input_class(f, :password, "form-control"),
|
||||
maxlength: User.max_password,
|
||||
aria_describedby: error_id(f, :password)
|
||||
%>
|
||||
<%= error_tag f, :password %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
||||
<%= link "Cancel", to: "#", class: "btn btn-secondary", phx_click: "cancel", phx_target: @myself %>
|
||||
<%= submit "Save",
|
||||
class: "btn btn-primary ",
|
||||
disabled: !@changeset.valid?,
|
||||
aria_disabled: !@changeset.valid? && "true" || false,
|
||||
phx_disable_with: "Saving..."
|
||||
%>
|
||||
|
||||
<%= if Roles.can?(@current_user, %User{}, :edit_role) do %>
|
||||
<%= label f, :role, class: "form-label" %>
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text">
|
||||
<%= icon_div @socket, "bi-shield-shaded", [class: "icon"] %>
|
||||
</span>
|
||||
<%= select f, :role, Enum.map(User.roles(), fn {k, _v} -> {String.capitalize(Atom.to_string(k)), k} end), class: "form-select" %>
|
||||
<span class="valid-feedback text-primary" style="display: block;">
|
||||
<%= role_description(input_value(f, :role)) %>
|
||||
</span>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= hidden_input f, :role, value: input_value(f, :role) %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<div class="mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
|
||||
<%= label f, :password, class: "form-label" %>
|
||||
<div class="input-group has-validation">
|
||||
<span class="input-group-text">
|
||||
<%= icon_div @socket, "bi-key", [class: "icon"] %>
|
||||
</span>
|
||||
<%= password_input f, :password,
|
||||
value: input_value(f, :password),
|
||||
class: input_class(f, :password, "form-control"),
|
||||
maxlength: User.max_password,
|
||||
aria_describedby: error_id(f, :password)
|
||||
%>
|
||||
<%= error_tag f, :password %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<%= submit "Save",
|
||||
class: "btn btn-primary",
|
||||
disabled: !@changeset.valid?,
|
||||
aria_disabled: !@changeset.valid? && "true" || false,
|
||||
phx_disable_with: "Saving..."
|
||||
%>
|
||||
|
||||
<% end %>
|
||||
|
|
|
@ -14,6 +14,7 @@ defmodule Bones73kWeb.UserManagementLive.Index do
|
|||
def mount(_params, session, socket) do
|
||||
socket
|
||||
|> assign_defaults(session)
|
||||
|> assign(:page, nil)
|
||||
|> live_okreply()
|
||||
end
|
||||
|
||||
|
@ -27,7 +28,9 @@ defmodule Bones73kWeb.UserManagementLive.Index do
|
|||
socket
|
||||
|> assign(:query, query_map(params))
|
||||
|> assign_modal_return_to()
|
||||
|> page_query()
|
||||
|> assign(:delete_user, nil)
|
||||
|> assign(:page, nil)
|
||||
|> request_page_query()
|
||||
|> apply_action(socket.assigns.live_action, params)
|
||||
|> live_noreply()
|
||||
else
|
||||
|
@ -38,6 +41,11 @@ defmodule Bones73kWeb.UserManagementLive.Index do
|
|||
end
|
||||
end
|
||||
|
||||
defp request_page_query(%{assigns: %{query: query}} = socket) do
|
||||
send(self(), {:do_page_query, query})
|
||||
socket
|
||||
end
|
||||
|
||||
defp apply_action(socket, :index, _params) do
|
||||
socket
|
||||
|> assign(:page_title, "Listing Users")
|
||||
|
@ -78,56 +86,30 @@ defmodule Bones73kWeb.UserManagementLive.Index do
|
|||
}
|
||||
end
|
||||
|
||||
defp page_query(%{assigns: %{query: query}} = socket) do
|
||||
result_page =
|
||||
from(u in User)
|
||||
|> or_where([u], ilike(u.email, ^"%#{query.filter}%"))
|
||||
# |> or_where([u], ilike(u.singer_name, ^"%#{query.filter}%"))
|
||||
|> order_by([u], [
|
||||
{^String.to_existing_atom(query.sort_order), ^String.to_existing_atom(query.sort_by)}
|
||||
])
|
||||
|> Repo.paginate(page: query.page_number, page_size: query.page_size)
|
||||
|
||||
socket
|
||||
|> assign(:page, result_page)
|
||||
|> assign(:table_loading, false)
|
||||
defp page_query(query) do
|
||||
from(u in User)
|
||||
|> or_where([u], ilike(u.email, ^"%#{query.filter}%"))
|
||||
|> order_by([u], [
|
||||
{^String.to_existing_atom(query.sort_order), ^String.to_existing_atom(query.sort_by)}
|
||||
])
|
||||
|> Repo.paginate(page: query.page_number, page_size: query.page_size)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id, "email" => email}, socket) do
|
||||
id
|
||||
|> Accounts.get_user()
|
||||
|> Accounts.delete_user()
|
||||
|> case do
|
||||
{:ok, _} ->
|
||||
socket
|
||||
|> put_flash(:success, "User deleted successfully: \"#{email}\"")
|
||||
|> assign(:table_loading, true)
|
||||
|> page_query()
|
||||
|> live_noreply()
|
||||
|
||||
{:error, _} ->
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
"Something went wrong attempting to delete user \"#{email}\". Possibly already deleted? Reloading list..."
|
||||
)
|
||||
|> assign(:table_loading, true)
|
||||
|> page_query()
|
||||
|> live_noreply()
|
||||
end
|
||||
def handle_event("delete-modal", %{"id" => id}, socket) do
|
||||
{:noreply, assign(socket, :delete_user, Accounts.get_user(id))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("filter-change", params, socket) do
|
||||
send(self(), {:query_update, Map.put(params, "page_number", "1")})
|
||||
{:noreply, assign(socket, :table_loading, true)}
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("filter-clear", _params, socket) do
|
||||
send(self(), {:query_update, %{"filter" => "", "page_number" => "1"}})
|
||||
{:noreply, assign(socket, :table_loading, true)}
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
@ -143,32 +125,32 @@ defmodule Bones73kWeb.UserManagementLive.Index do
|
|||
)) ||
|
||||
send(self(), {:query_update, Map.put(params, "sort_order", "asc")})
|
||||
|
||||
{:noreply, assign(socket, :table_loading, true)}
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("sort-by-change", %{"sort" => params}, socket) do
|
||||
send(self(), {:query_update, params})
|
||||
{:noreply, assign(socket, :table_loading, true)}
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("sort-order-change", _params, socket) do
|
||||
new_sort_order = (socket.assigns.query.sort_order == "asc" && "desc") || "asc"
|
||||
send(self(), {:query_update, %{"sort_order" => new_sort_order}})
|
||||
{:noreply, assign(socket, :table_loading, true)}
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("page-change", params, socket) do
|
||||
send(self(), {:query_update, params})
|
||||
{:noreply, assign(socket, :table_loading, true)}
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("page-size-change", params, socket) do
|
||||
send(self(), {:query_update, Map.put(params, "page_number", "1")})
|
||||
{:noreply, assign(socket, :table_loading, true)}
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
@ -180,6 +162,11 @@ defmodule Bones73kWeb.UserManagementLive.Index do
|
|||
)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:do_page_query, query}, socket) do
|
||||
{:noreply, assign(socket, :page, page_query(query))}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:close_modal, _}, %{assigns: %{modal_return_to: to}} = socket) do
|
||||
socket |> copy_flash() |> push_patch(to: to) |> live_noreply()
|
||||
|
|
|
@ -7,6 +7,14 @@
|
|||
current_user: @current_user %>
|
||||
<% end %>
|
||||
|
||||
<%= if @delete_user do %>
|
||||
<%= live_modal @socket, Bones73kWeb.UserManagement.DeleteComponent,
|
||||
id: @delete_user.id,
|
||||
title: "Delete User",
|
||||
delete_user: @delete_user,
|
||||
current_user: @current_user %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<h2 class="mb-3">
|
||||
<%= icon_div @socket, "bi-people", [class: "icon baseline"] %>
|
||||
|
@ -14,16 +22,16 @@
|
|||
</h2>
|
||||
|
||||
<%# filtering and new item creation %>
|
||||
<div class="d-flex flex-column flex-sm-row justify-content-between d-flex align-items-start">
|
||||
<div class="d-flex flex-column flex-sm-row justify-content-between d-flex align-items-start mb-3">
|
||||
|
||||
<%= live_patch to: Routes.user_management_index_path(@socket, :new, Enum.into(@query, [])),
|
||||
class: "btn btn-primary mb-2" do %>
|
||||
class: "btn btn-primary mb-3 mb-sm-0" do %>
|
||||
<%= icon_div @socket, "bi-person-plus", [class: "icon baseline me-1"] %>
|
||||
New User
|
||||
<% end %>
|
||||
|
||||
<%= form_for :filter, "#", [phx_change: "filter-change"], fn flt -> %>
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group">
|
||||
|
||||
<span class="input-group-text">
|
||||
<%= icon_div @socket, "bi-filter", [class: "icon"] %>
|
||||
|
@ -47,89 +55,101 @@
|
|||
</div>
|
||||
|
||||
<%# main data table %>
|
||||
<table class="table">
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
|
||||
<thead>
|
||||
<tr class="<%= @table_loading && "loading" || "" %>">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
<th scope="col" phx-click="sort-change" phx-value-sort_by="email" class="cursor-pointer">
|
||||
Email
|
||||
<%= if @query.sort_by == "email", do: icon_div @socket,
|
||||
(@query.sort_order == "desc" && "bi-sort-up-alt" || "bi-sort-down-alt"),
|
||||
[class: "icon baseline ms-1"]
|
||||
%>
|
||||
</th>
|
||||
<th scope="col" phx-click="sort-change" phx-value-sort_by="email" class="cursor-pointer">
|
||||
Email
|
||||
<%= if @query.sort_by == "email", do: icon_div @socket,
|
||||
(@query.sort_order == "desc" && "bi-sort-up-alt" || "bi-sort-down-alt"),
|
||||
[class: "icon baseline ms-1"]
|
||||
%>
|
||||
</th>
|
||||
|
||||
<th scope="col" phx-click="sort-change" phx-value-sort_by="role" class="cursor-pointer">
|
||||
Role
|
||||
<%= if @query.sort_by == "role", do: icon_div @socket,
|
||||
(@query.sort_order == "desc" && "bi-sort-up-alt" || "bi-sort-down-alt"),
|
||||
[class: "icon baseline ms-1"]
|
||||
%>
|
||||
</th>
|
||||
<th scope="col" phx-click="sort-change" phx-value-sort_by="role" class="cursor-pointer">
|
||||
Role
|
||||
<%= if @query.sort_by == "role", do: icon_div @socket,
|
||||
(@query.sort_order == "desc" && "bi-sort-up-alt" || "bi-sort-down-alt"),
|
||||
[class: "icon baseline ms-1"]
|
||||
%>
|
||||
</th>
|
||||
|
||||
<th scope="col" phx-click="sort-change" phx-value-sort_by="inserted_at" class="cursor-pointer">
|
||||
Created at
|
||||
<%= if @query.sort_by == "inserted_at", do: icon_div @socket,
|
||||
(@query.sort_order == "desc" && "bi-sort-up-alt" || "bi-sort-down-alt"),
|
||||
[class: "icon baseline ms-1"]
|
||||
%>
|
||||
</th>
|
||||
<th scope="col" phx-click="sort-change" phx-value-sort_by="inserted_at" class="cursor-pointer">
|
||||
Created at
|
||||
<%= if @query.sort_by == "inserted_at", do: icon_div @socket,
|
||||
(@query.sort_order == "desc" && "bi-sort-up-alt" || "bi-sort-down-alt"),
|
||||
[class: "icon baseline ms-1"]
|
||||
%>
|
||||
</th>
|
||||
|
||||
<th scope="col">Confirmed?</th>
|
||||
<th scope="col">Confirmed?</th>
|
||||
|
||||
<th></th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody id="users">
|
||||
<%= for user <- @page.entries do %>
|
||||
<tr id="user-<%= user.id %>">
|
||||
<td class="align-middle"><%= user.email %></td>
|
||||
<td class="align-middle"><%= user.role |> Atom.to_string() |> String.capitalize() %></td>
|
||||
<td class="align-middle" style="white-space: nowrap;"><%= dt_out(user.inserted_at) %></td>
|
||||
<td class="align-middle">
|
||||
<%= if user.confirmed_at do %>
|
||||
<span class="visually-hidden">Yes</span>
|
||||
<%= icon_div @socket, "bi-check", [class: "icon baseline fs-4 text-success"], [role: "img", aria_hidden: false] %>
|
||||
<% else %>
|
||||
<span class="visually-hidden">No</span>
|
||||
<%= icon_div @socket, "bi-x", [class: "icon baseline fs-4 text-warning"], [role: "img", aria_hidden: false] %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="align-middle text-end">
|
||||
|
||||
<%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-outline-primary btn-sm" do %>
|
||||
<%= icon_div @socket, "bi-pencil", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
|
||||
Edit
|
||||
<% end %>
|
||||
|
||||
<%= if Roles.can?(@current_user, user, :delete) do %>
|
||||
<%= link to: "#",
|
||||
phx_click: "delete",
|
||||
phx_value_id: user.id,
|
||||
phx_value_email: user.email,
|
||||
data: [confirm: "Are you sure you want to delete this user? \"#{user.email}\""],
|
||||
class: "btn btn-outline-danger btn-sm" do %>
|
||||
<%= icon_div @socket, "bi-trash", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
|
||||
Delete
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
</td>
|
||||
<th></th>
|
||||
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</thead>
|
||||
|
||||
</table>
|
||||
<tbody id="users">
|
||||
<%= if !@page do %>
|
||||
<tr>
|
||||
<td class="text-center" colspan="5">
|
||||
<div class="spinner-border text-primary my-5" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% else %>
|
||||
<%= for user <- @page.entries do %>
|
||||
<tr id="user-<%= user.id %>">
|
||||
<td class="align-middle"><%= user.email %></td>
|
||||
<td class="align-middle"><%= user.role |> Atom.to_string() |> String.capitalize() %></td>
|
||||
<td class="align-middle" style="white-space: nowrap;"><%= dt_out(user.inserted_at) %></td>
|
||||
<td class="align-middle">
|
||||
<%= if user.confirmed_at do %>
|
||||
<span class="visually-hidden">Yes</span>
|
||||
<%= icon_div @socket, "bi-check", [class: "icon baseline fs-4 text-success"], [role: "img", aria_hidden: false] %>
|
||||
<% else %>
|
||||
<span class="visually-hidden">No</span>
|
||||
<%= icon_div @socket, "bi-x", [class: "icon baseline fs-4 text-warning"], [role: "img", aria_hidden: false] %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="align-middle text-end text-nowrap">
|
||||
|
||||
<%= live_patch to: Routes.user_management_index_path(@socket, :edit, user.id, Enum.into(@query, [])), class: "btn btn-outline-primary btn-sm text-nowrap" do %>
|
||||
<%= icon_div @socket, "bi-pencil", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
|
||||
Edit
|
||||
<% end %>
|
||||
|
||||
<%= if Roles.can?(@current_user, user, :delete) do %>
|
||||
|
||||
<button class="btn btn-outline-danger btn-sm text-nowrap" phx-click="delete-modal" phx-value-id="<%= user.id %>">
|
||||
<%= icon_div @socket, "bi-trash", [class: "icon baseline", style: "margin-right:0.125rem;"] %>
|
||||
Delete
|
||||
</button>
|
||||
|
||||
<% end %>
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<%# pagination interface %>
|
||||
<div class="d-flex justify-content-between d-flex align-items-start">
|
||||
<%= if @page do %>
|
||||
<div class="d-flex flex-column flex-sm-row justify-content-between d-flex align-items-start my-3">
|
||||
|
||||
<%# <div class="d-flex justify-content-between d-flex align-items-start"> %>
|
||||
|
||||
<%# items per page selector %>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="d-flex align-items-center mb-3 mb-sm-0">
|
||||
<%= form_for :page_size, "#", [phx_change: "page-size-change"], fn pgsz -> %>
|
||||
<%= select pgsz, :page_size,
|
||||
[10, 15, 20, 30, 50, 100] |> Enum.map(fn n -> {"#{n} per page", n} end),
|
||||
|
@ -193,3 +213,4 @@
|
|||
</nav>
|
||||
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Bones73k.Repo.Migrations.CreateProperties do
|
|||
add(:name, :string)
|
||||
add(:price, :decimal)
|
||||
add(:description, :text)
|
||||
add(:user_id, references(:users, type: :binary_id, on_delete: :nothing))
|
||||
add(:user_id, references(:users, type: :binary_id, on_delete: :delete_all), null: false)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ defmodule Bones73k.AccountsTest do
|
|||
describe "get_user!/1" do
|
||||
test "raises if id is invalid" do
|
||||
assert_raise Ecto.NoResultsError, fn ->
|
||||
Accounts.get_user!(-1)
|
||||
Ecto.UUID.generate() |> Accounts.get_user!()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue