defmodule DexiWeb.Admin.UsersLive do use DexiWeb, :live_view alias Dexi.Accounts alias DexiWeb.Admin.UserListParams @impl true def mount(_params, _session, socket) do {:ok, socket |> assign(:page_title, "Users") |> assign(:search_term, "") |> assign(:role_filter, :all) |> assign(:current_page, 1) |> assign(:total_pages, 1) |> assign(:total_entries, 0) |> assign(:visible_pages, [1]) |> assign(:editing_user, nil) |> assign(:edit_form, nil) |> assign_search_form() |> stream_configure(:users, dom_id: &"user-#{&1.pubkey}") |> stream(:users, [])} end @impl true def handle_params(params, _uri, socket) do filter = UserListParams.from_query(params) page = Accounts.search_page(filter.search, filter.role, page: filter.page) socket = socket |> assign(:search_term, filter.search) |> assign(:role_filter, filter.role) |> assign_search_form() |> assign_page(page) socket = if filter.page == page.page do socket else push_patch(socket, to: users_path(filter.search, filter.role, page.page), replace: true ) end {:noreply, socket} end @impl true def handle_event("search", %{"search" => params}, socket) do filter = UserListParams.from_form(params) {:noreply, push_patch(socket, to: users_path(filter.search, filter.role, filter.page), replace: true )} end def handle_event("edit-user", %{"pubkey" => pubkey}, socket) do case Accounts.get_user_by_pubkey(pubkey) do nil -> {:noreply, put_flash(socket, :error, "User no longer exists.")} user -> {:noreply, open_editor(socket, user)} end end def handle_event("cancel-edit", _params, socket) do {:noreply, close_editor(socket)} end def handle_event("save-user", %{"user" => params}, socket) do actor = socket.assigns.current_scope.user case Accounts.update_user_details(actor, socket.assigns.editing_user, params) do {:ok, _user} -> {:noreply, socket |> close_editor() |> reload_users() |> put_flash(:info, "User updated.")} {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, :edit_form, to_form(changeset))} {:error, :unauthorized} -> {:noreply, socket |> close_editor() |> put_flash(:error, "Your admin access has changed. Reload and sign in again.")} {:error, _reason} -> {:noreply, put_flash(socket, :error, "The user could not be updated.")} end end def handle_event("set-role", %{"pubkey" => pubkey, "role" => role}, socket) do actor = socket.assigns.current_scope.user with target when not is_nil(target) <- Accounts.get_user_by_pubkey(pubkey), {:ok, _user} <- Accounts.set_role(actor, target, parse_role(role)) do {:noreply, reload_users(socket)} else {:error, :self_demotion} -> {:noreply, put_flash(socket, :error, "You cannot remove your own admin access here.")} {:error, :unauthorized} -> {:noreply, put_flash(socket, :error, "Your admin access has changed.")} _error -> {:noreply, put_flash(socket, :error, "The user role could not be changed.")} end end defp parse_role("admin"), do: :admin defp parse_role(_role), do: :user defp open_editor(socket, user) do socket |> assign(:editing_user, user) |> assign(:edit_form, to_form(Accounts.change_profile(user))) end defp close_editor(socket) do socket |> assign(:editing_user, nil) |> assign(:edit_form, nil) end defp reload_users(socket) do page = Accounts.search_page(socket.assigns.search_term, socket.assigns.role_filter, page: socket.assigns.current_page ) assign_page(socket, page) end defp assign_page(socket, page) do socket |> assign(:current_page, page.page) |> assign(:total_pages, page.total_pages) |> assign(:total_entries, page.total_entries) |> assign(:visible_pages, visible_pages(page.page, page.total_pages)) |> stream(:users, page.entries, reset: true) end defp visible_pages(current_page, total_pages) do first = max(current_page - 2, 1) last = min(current_page + 2, total_pages) Enum.to_list(first..last) end defp assign_search_form(socket) do params = %{ "query" => socket.assigns.search_term, "role" => Atom.to_string(socket.assigns.role_filter) } assign(socket, :search_form, to_form(params, as: :search)) end defp users_path(term, role, page) do filter = %UserListParams{search: term, role: role, page: page} ~p"/admin/users?#{UserListParams.query(filter)}" end @impl true def render(assigns) do ~H"""

Administration

Users

Search users, update profile details, and manage administrative access.

<.form for={@search_form} id="user-search-form" phx-change="search" class="mb-6 flex max-w-3xl items-end gap-3" >
<.input field={@search_form[:query]} type="search" label="Search users" placeholder="Public key, name, or email" phx-debounce="250" />
<.input field={@search_form[:role]} type="select" label="Role" options={[{"All", "all"}, {"Users", "user"}, {"Admins", "admin"}]} />
{@total_entries} {if(@total_entries == 1, do: "user", else: "users")}
""" end end