Packages

phoenix_kit

1.7.83
1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit_web live users user_details.ex
Raw

lib/phoenix_kit_web/live/users/user_details.ex

defmodule PhoenixKitWeb.Live.Users.UserDetails do
@moduledoc """
LiveView for displaying detailed user information.
Displays user profile information in a tabbed interface with:
- Profile tab: Basic info, status, roles, registration details
- Connections tab: Follower/following/connection stats (if enabled)
- Notes tab: Admin notes about the user
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Admin.Events
alias PhoenixKit.Modules.Connections
alias PhoenixKit.Settings
alias PhoenixKit.Users.AdminNote
alias PhoenixKit.Users.Auth
alias PhoenixKit.Users.CustomFields
alias PhoenixKit.Utils.Date, as: UtilsDate
alias PhoenixKit.Utils.Routes
@impl true
def mount(%{"id" => user_uuid}, _session, socket) do
project_title = Settings.get_project_title()
user = Auth.get_user_with_roles(user_uuid)
case user do
nil ->
{:ok,
socket
|> put_flash(:error, gettext("User not found"))
|> push_navigate(to: Routes.path("/admin/users"))}
user ->
if connected?(socket), do: Events.subscribe_to_users()
custom_field_definitions = CustomFields.list_field_definitions()
# Load connections stats if module is enabled
connections_enabled = Connections.enabled?()
connections_stats =
if connections_enabled do
%{
followers: Connections.followers_count(user),
following: Connections.following_count(user),
connections: Connections.connections_count(user),
pending: Connections.pending_requests_count(user),
blocked: length(Connections.list_blocked(user))
}
else
nil
end
# Load admin notes
admin_notes = Auth.list_admin_notes(user)
socket =
socket
|> assign(:user, user)
|> assign(:page_title, user_display_name(user))
|> assign(:project_title, project_title)
|> assign(:active_tab, "profile")
|> assign(:custom_field_definitions, custom_field_definitions)
|> assign(:show_delete_modal, false)
|> assign(:connections_enabled, connections_enabled)
|> assign(:connections_stats, connections_stats)
|> assign(:admin_notes, admin_notes)
|> assign(:note_form, to_form(Auth.change_admin_note(%AdminNote{})))
|> assign(:editing_note_uuid, nil)
{:ok, socket}
end
end
@impl true
def handle_event("change_tab", %{"tab" => tab}, socket) do
{:noreply, assign(socket, :active_tab, tab)}
end
@impl true
def handle_event("show_delete_modal", _params, socket) do
{:noreply, assign(socket, :show_delete_modal, true)}
end
@impl true
def handle_event("hide_delete_modal", _params, socket) do
{:noreply, assign(socket, :show_delete_modal, false)}
end
@impl true
def handle_event("delete_user", _params, socket) do
user = socket.assigns.user
current_user = socket.assigns.phoenix_kit_current_scope.user
opts = %{
current_user: current_user,
ip_address: socket.assigns[:ip_address],
user_agent: socket.assigns[:user_agent]
}
case Auth.delete_user(user, opts) do
{:ok, _result} ->
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> put_flash(:info, gettext("User deleted successfully"))
|> push_navigate(to: Routes.path("/admin/users"))}
{:error, :cannot_delete_self} ->
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> put_flash(:error, gettext("Cannot delete your own account"))}
{:error, :cannot_delete_last_owner} ->
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> put_flash(:error, gettext("Cannot delete the last system owner"))}
{:error, _reason} ->
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> put_flash(:error, gettext("Failed to delete user"))}
end
end
# Admin Notes Events
@impl true
def handle_event("add_note", %{"admin_note" => note_params}, socket) do
current_user = socket.assigns.phoenix_kit_current_scope.user
user = socket.assigns.user
case Auth.create_admin_note(user, current_user, note_params) do
{:ok, _note} ->
admin_notes = Auth.list_admin_notes(user)
{:noreply,
socket
|> assign(:admin_notes, admin_notes)
|> assign(:note_form, to_form(Auth.change_admin_note(%AdminNote{})))
|> put_flash(:info, gettext("Note added successfully"))}
{:error, changeset} ->
{:noreply, assign(socket, :note_form, to_form(changeset))}
end
end
@impl true
def handle_event("edit_note", %{"uuid" => note_uuid}, socket) do
note = Enum.find(socket.assigns.admin_notes, &(to_string(&1.uuid) == note_uuid))
if note do
changeset = Auth.change_admin_note(note)
{:noreply,
socket
|> assign(:editing_note_uuid, note_uuid)
|> assign(:note_form, to_form(changeset))}
else
{:noreply, socket}
end
end
@impl true
def handle_event("cancel_edit", _params, socket) do
{:noreply,
socket
|> assign(:editing_note_uuid, nil)
|> assign(:note_form, to_form(Auth.change_admin_note(%AdminNote{})))}
end
@impl true
def handle_event("update_note", %{"admin_note" => note_params}, socket) do
note_uuid = socket.assigns.editing_note_uuid
note = Enum.find(socket.assigns.admin_notes, &(to_string(&1.uuid) == note_uuid))
if note do
case Auth.update_admin_note(note, note_params) do
{:ok, _note} ->
admin_notes = Auth.list_admin_notes(socket.assigns.user)
{:noreply,
socket
|> assign(:admin_notes, admin_notes)
|> assign(:editing_note_uuid, nil)
|> assign(:note_form, to_form(Auth.change_admin_note(%AdminNote{})))
|> put_flash(:info, gettext("Note updated successfully"))}
{:error, changeset} ->
{:noreply, assign(socket, :note_form, to_form(changeset))}
end
else
{:noreply, socket}
end
end
@impl true
def handle_event("delete_note", %{"uuid" => note_uuid}, socket) do
note = Enum.find(socket.assigns.admin_notes, &(to_string(&1.uuid) == note_uuid))
if note do
case Auth.delete_admin_note(note) do
{:ok, _} ->
admin_notes = Auth.list_admin_notes(socket.assigns.user)
{:noreply,
socket
|> assign(:admin_notes, admin_notes)
|> put_flash(:info, gettext("Note deleted successfully"))}
{:error, _} ->
{:noreply, put_flash(socket, :error, gettext("Failed to delete note"))}
end
else
{:noreply, socket}
end
end
# --- PubSub Handlers ---
@impl true
def handle_info({:user_updated, user}, socket) do
{:noreply, maybe_refresh_user(socket, user)}
end
@impl true
def handle_info({:user_role_assigned, user, _role}, socket) do
{:noreply, maybe_refresh_user(socket, user)}
end
@impl true
def handle_info({:user_role_removed, user, _role}, socket) do
{:noreply, maybe_refresh_user(socket, user)}
end
@impl true
def handle_info({:user_roles_synced, user, _roles}, socket) do
{:noreply, maybe_refresh_user(socket, user)}
end
@impl true
def handle_info({:user_confirmed, user}, socket) do
{:noreply, maybe_refresh_user(socket, user)}
end
@impl true
def handle_info({:user_unconfirmed, user}, socket) do
{:noreply, maybe_refresh_user(socket, user)}
end
@impl true
def handle_info({:user_deleted, user}, socket) do
if user.uuid == socket.assigns.user.uuid do
{:noreply,
socket
|> put_flash(:info, gettext("This user has been deleted"))
|> push_navigate(to: Routes.path("/admin/users"))}
else
{:noreply, socket}
end
end
@impl true
def handle_info({:user_created, _user}, socket), do: {:noreply, socket}
@impl true
def handle_info(_msg, socket), do: {:noreply, socket}
defp maybe_refresh_user(socket, event_user) do
if event_user.uuid == socket.assigns.user.uuid do
case Auth.get_user_with_roles(event_user.uuid) do
nil ->
socket
|> put_flash(:info, gettext("This user has been deleted"))
|> push_navigate(to: Routes.path("/admin/users"))
user ->
assign(socket, :user, user)
end
else
socket
end
end
defp user_display_name(user) do
cond do
user.first_name && user.last_name ->
"#{user.first_name} #{user.last_name}"
user.first_name ->
user.first_name
user.username ->
user.username
true ->
user.email
end
end
defp format_location(user) do
[user.registration_city, user.registration_region, user.registration_country]
|> Enum.filter(&(&1 && &1 != ""))
|> Enum.join(", ")
|> case do
"" -> nil
location -> location
end
end
defp format_timezone(nil), do: "Not set"
defp format_timezone(offset) when is_binary(offset) do
case Integer.parse(offset) do
{num, _} -> format_timezone_offset(num)
:error -> offset
end
end
defp format_timezone(offset) when is_integer(offset), do: format_timezone_offset(offset)
defp format_timezone_offset(offset) do
sign = if offset >= 0, do: "+", else: ""
"UTC#{sign}#{offset}"
end
defp get_custom_field_value(user, field_key) do
case user.custom_fields do
nil -> nil
fields -> Map.get(fields, field_key)
end
end
defp format_custom_field_value(nil, _type, _field_key), do: "-"
defp format_custom_field_value("", _type, _field_key), do: "-"
defp format_custom_field_value(value, "boolean", _field_key) do
case value do
true -> gettext("Yes")
"true" -> gettext("Yes")
false -> gettext("No")
"false" -> gettext("No")
_ -> to_string(value)
end
end
defp format_custom_field_value(value, type, field_key)
when type in ["select", "radio", "checkbox"] do
CustomFields.get_option_text(field_key, value) || to_string(value)
end
defp format_custom_field_value(value, _type, _field_key), do: to_string(value)
end