- Replace Ash.get!/Ash.destroy! with Ash.get/Ash.destroy - Add case statements for Forbidden, NotFound, and generic errors - Display user-friendly flash messages for all error cases - Use Enum.map_join/3 for efficient error formatting
149 lines
4.2 KiB
Elixir
149 lines
4.2 KiB
Elixir
defmodule MvWeb.UserLive.Index do
|
|
@moduledoc """
|
|
LiveView for displaying and managing the user list.
|
|
|
|
## Features
|
|
- List all users with email and linked member
|
|
- Sort users by email (default)
|
|
- Delete users
|
|
- Navigate to user details and edit forms
|
|
- Bulk selection for future batch operations
|
|
|
|
## Relationships
|
|
Displays linked member information when a user is connected to a member account.
|
|
|
|
## Events
|
|
- `delete` - Remove a user from the database
|
|
- `select_user` - Toggle individual user selection
|
|
- `select_all` - Toggle selection of all visible users
|
|
|
|
## Security
|
|
User deletion requires admin permissions (enforced by Ash policies).
|
|
"""
|
|
use MvWeb, :live_view
|
|
import MvWeb.TableComponents
|
|
|
|
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
|
import MvWeb.LiveHelpers, only: [current_actor: 1]
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
actor = current_actor(socket)
|
|
users = Ash.read!(Mv.Accounts.User, domain: Mv.Accounts, load: [:member], actor: actor)
|
|
sorted = Enum.sort_by(users, & &1.email)
|
|
|
|
{:ok,
|
|
socket
|
|
|> assign(:page_title, gettext("Listing Users"))
|
|
|> assign(:sort_field, :email)
|
|
|> assign(:sort_order, :asc)
|
|
|> assign(:users, sorted)
|
|
|> assign(:selected_users, [])}
|
|
end
|
|
|
|
@impl true
|
|
def handle_event("delete", %{"id" => id}, socket) do
|
|
actor = current_actor(socket)
|
|
|
|
case Ash.get(Mv.Accounts.User, id, domain: Mv.Accounts, actor: actor) do
|
|
{:ok, user} ->
|
|
case Ash.destroy(user, domain: Mv.Accounts, actor: actor) do
|
|
:ok ->
|
|
updated_users = Enum.reject(socket.assigns.users, &(&1.id == id))
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:users, updated_users)
|
|
|> put_flash(:info, gettext("User deleted successfully"))}
|
|
|
|
{:error, %Ash.Error.Forbidden{}} ->
|
|
{:noreply,
|
|
put_flash(
|
|
socket,
|
|
:error,
|
|
gettext("You do not have permission to delete this user")
|
|
)}
|
|
|
|
{:error, error} ->
|
|
{:noreply, put_flash(socket, :error, format_error(error))}
|
|
end
|
|
|
|
{:error, %Ash.Error.Query.NotFound{}} ->
|
|
{:noreply, put_flash(socket, :error, gettext("User not found"))}
|
|
|
|
{:error, %Ash.Error.Forbidden{} = _error} ->
|
|
{:noreply,
|
|
put_flash(socket, :error, gettext("You do not have permission to access this user"))}
|
|
|
|
{:error, error} ->
|
|
{:noreply, put_flash(socket, :error, format_error(error))}
|
|
end
|
|
end
|
|
|
|
# Selects one user in the list of users
|
|
@impl true
|
|
def handle_event("select_user", %{"id" => id}, socket) do
|
|
selected =
|
|
if id in socket.assigns.selected_users do
|
|
List.delete(socket.assigns.selected_users, id)
|
|
else
|
|
[id | socket.assigns.selected_users]
|
|
end
|
|
|
|
{:noreply, assign(socket, :selected_users, selected)}
|
|
end
|
|
|
|
# Sorts the list of users according to a field, when you click on the column header
|
|
@impl true
|
|
def handle_event("sort", %{"field" => field_str}, socket) do
|
|
users = socket.assigns.users
|
|
field = String.to_existing_atom(field_str)
|
|
|
|
new_order =
|
|
if socket.assigns.sort_field == field do
|
|
toggle_order(socket.assigns.sort_order)
|
|
else
|
|
:asc
|
|
end
|
|
|
|
sorted_users =
|
|
users
|
|
|> Enum.sort_by(&Map.get(&1, field), sort_fun(new_order))
|
|
|
|
{:noreply,
|
|
socket
|
|
|> assign(:sort_field, field)
|
|
|> assign(:sort_order, new_order)
|
|
|> assign(:users, sorted_users)}
|
|
end
|
|
|
|
# Selects all users in the list of users
|
|
@impl true
|
|
def handle_event("select_all", _params, socket) do
|
|
users = socket.assigns.users
|
|
|
|
all_ids = Enum.map(users, & &1.id)
|
|
|
|
selected =
|
|
if Enum.sort(socket.assigns.selected_users) == Enum.sort(all_ids) do
|
|
[]
|
|
else
|
|
all_ids
|
|
end
|
|
|
|
{:noreply, assign(socket, :selected_users, selected)}
|
|
end
|
|
|
|
defp toggle_order(:asc), do: :desc
|
|
defp toggle_order(:desc), do: :asc
|
|
defp sort_fun(:asc), do: &<=/2
|
|
defp sort_fun(:desc), do: &>=/2
|
|
|
|
defp format_error(%Ash.Error.Invalid{errors: errors}) do
|
|
Enum.map_join(errors, ", ", fn %{message: message} -> message end)
|
|
end
|
|
|
|
defp format_error(error) do
|
|
inspect(error)
|
|
end
|
|
end
|