Packages

phoenix_kit

1.3.0
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 users.ex
Raw

lib/phoenix_kit_web/live/users/users.ex

defmodule PhoenixKitWeb.Live.Users.Users do
@moduledoc """
User management LiveView for PhoenixKit admin panel.
Provides comprehensive user management including listing, search, role assignment, and status updates.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Admin.Events
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth
alias PhoenixKit.Users.Roles
alias PhoenixKit.Utils.Date, as: UtilsDate
@per_page 10
def mount(params, _session, socket) do
# Set locale for LiveView process
locale = params["locale"] || socket.assigns[:current_locale] || "en"
Gettext.put_locale(PhoenixKitWeb.Gettext, locale)
Process.put(:phoenix_kit_current_locale, locale)
# Subscribe to user events for live updates
if connected?(socket) do
Events.subscribe_to_users()
Events.subscribe_to_stats()
end
# Get project title from settings
project_title = Settings.get_setting("project_title", "PhoenixKit")
# Load date/time format settings once for performance optimization
# Use batch cached call for maximum efficiency
date_time_settings =
Settings.get_settings_cached(
["date_format", "time_format", "time_zone"],
%{
"date_format" => "Y-m-d",
"time_format" => "H:i",
"time_zone" => "0"
}
)
socket =
socket
|> assign(:page, 1)
|> assign(:per_page, @per_page)
|> assign(:search_query, "")
|> assign(:filter_role, "all")
|> assign(:show_role_modal, false)
|> assign(:managing_user, nil)
|> assign(:user_roles, [])
|> assign(:all_roles, [])
|> assign(:confirmation_modal, %{show: false})
|> assign(:page_title, "Users")
|> assign(:project_title, project_title)
|> assign(:date_time_settings, date_time_settings)
|> assign(:current_locale, locale)
|> load_users()
|> load_stats()
{:ok, socket}
end
def handle_params(%{"action" => "add"} = _params, _url, socket) do
# Open user registration form for adding new user
socket = assign(socket, :show_add_user_modal, true)
{:noreply, socket}
end
def handle_params(_params, _url, socket) do
# Default case - no action specified
socket = assign(socket, :show_add_user_modal, false)
{:noreply, socket}
end
def handle_event("search", %{"search" => search_query}, socket) do
socket =
socket
|> assign(:search_query, search_query)
|> assign(:page, 1)
|> load_users()
{:noreply, socket}
end
def handle_event("filter_by_role", %{"role" => role}, socket) do
socket =
socket
|> assign(:filter_role, role)
|> assign(:page, 1)
|> load_users()
{:noreply, socket}
end
def handle_event("change_page", %{"page" => page}, socket) do
page = String.to_integer(page)
socket =
socket
|> assign(:page, page)
|> load_users()
{:noreply, socket}
end
def handle_event("show_role_management", %{"user_id" => user_id}, socket) do
user_id_int = String.to_integer(user_id)
user = Auth.get_user!(user_id_int)
current_user = socket.assigns.phoenix_kit_current_user
# Prevent self-modification for critical operations
if current_user.id == user.id do
socket = put_flash(socket, :error, "Cannot modify your own roles")
{:noreply, socket}
else
# Get fresh user with preloaded roles to ensure accurate state
user_with_roles = Auth.get_user_with_roles(user_id_int)
user_roles = Roles.get_user_roles(user_with_roles)
all_roles = Roles.list_roles()
socket =
socket
|> assign(:managing_user, user_with_roles)
|> assign(:user_roles, user_roles)
|> assign(:all_roles, all_roles)
|> assign(:show_role_modal, true)
{:noreply, socket}
end
end
def handle_event("hide_role_management", _params, socket) do
socket =
socket
|> assign(:managing_user, nil)
|> assign(:user_roles, [])
|> assign(:all_roles, [])
|> assign(:show_role_modal, false)
{:noreply, socket}
end
def handle_event("sync_user_roles", %{"roles" => selected_roles}, socket) do
user = socket.assigns.managing_user
role_names = Map.values(selected_roles)
case Roles.sync_user_roles(user, role_names) do
{:ok, _assignments} ->
socket =
socket
|> put_flash(:info, "User roles updated successfully")
|> assign(:show_role_modal, false)
|> assign(:managing_user, nil)
|> assign(:user_roles, [])
|> assign(:all_roles, [])
|> load_users()
|> load_stats()
{:noreply, socket}
{:error, reason} ->
error_msg =
case reason do
:cannot_remove_last_owner -> "Cannot remove the last system owner"
:owner_role_protected -> "Owner role cannot be assigned manually"
_ -> "Failed to update user roles"
end
# Refresh the modal data on error to show current state
user_with_roles = Auth.get_user_with_roles(user.id)
updated_user_roles = Roles.get_user_roles(user_with_roles)
socket =
socket
|> put_flash(:error, error_msg)
|> assign(:managing_user, user_with_roles)
|> assign(:user_roles, updated_user_roles)
{:noreply, socket}
end
end
def handle_event("quick_toggle_role", %{"user_id" => user_id, "role_name" => role_name}, socket) do
user = Auth.get_user!(user_id)
current_user = socket.assigns.phoenix_kit_current_user
# Prevent self-modification
if current_user.id == user.id do
socket = put_flash(socket, :error, "Cannot modify your own roles")
{:noreply, socket}
else
handle_role_toggle_result(toggle_user_role(user, role_name), role_name, socket)
end
end
# New events for confirmation modal
def handle_event(
"request_status_toggle",
%{"user_id" => user_id, "is_active" => is_active},
socket
) do
is_active_bool = is_active == "true"
confirmation_modal = %{
show: true,
title: "Confirm Status Change",
message:
"Are you sure you want to #{if is_active_bool, do: "deactivate", else: "activate"} this user?",
button_text: if(is_active_bool, do: "Deactivate", else: "Activate"),
action: "toggle_user_status",
user_id: user_id
}
{:noreply, assign(socket, :confirmation_modal, confirmation_modal)}
end
def handle_event(
"request_confirmation_toggle",
%{"user_id" => user_id, "is_confirmed" => is_confirmed},
socket
) do
is_confirmed_bool = is_confirmed == "true"
confirmation_modal = %{
show: true,
title: "Confirm Email Status Change",
message:
"Are you sure you want to #{if is_confirmed_bool, do: "unconfirm", else: "confirm"} this user's email?",
button_text: if(is_confirmed_bool, do: "Unconfirm", else: "Confirm"),
action: "toggle_user_confirmation",
user_id: user_id
}
{:noreply, assign(socket, :confirmation_modal, confirmation_modal)}
end
def handle_event("cancel_confirmation", _params, socket) do
{:noreply, assign(socket, :confirmation_modal, %{show: false})}
end
def handle_event("confirm_action", %{"action" => action, "user_id" => user_id}, socket) do
# Close modal first
socket = assign(socket, :confirmation_modal, %{show: false})
# Execute the confirmed action
case action do
"toggle_user_status" ->
handle_toggle_user_status(%{"user_id" => user_id}, socket)
"toggle_user_confirmation" ->
handle_toggle_user_confirmation(%{"user_id" => user_id}, socket)
_ ->
{:noreply, socket}
end
end
# Keep old handlers for backward compatibility, but make them delegate to private handlers
def handle_event("toggle_user_status", %{"user_id" => user_id}, socket) do
handle_toggle_user_status(%{"user_id" => user_id}, socket)
end
def handle_event("toggle_user_confirmation", %{"user_id" => user_id}, socket) do
handle_toggle_user_confirmation(%{"user_id" => user_id}, socket)
end
# Keep the original handlers private for internal use
defp handle_toggle_user_status(%{"user_id" => user_id}, socket) do
current_user = socket.assigns.phoenix_kit_current_user
user = Auth.get_user!(user_id)
if current_user.id == user.id do
socket = put_flash(socket, :error, "Cannot modify your own status")
{:noreply, socket}
else
toggle_user_status_safely(socket, user)
end
end
defp handle_toggle_user_confirmation(%{"user_id" => user_id}, socket) do
current_user = socket.assigns.phoenix_kit_current_user
user = Auth.get_user!(user_id)
if current_user.id == user.id do
socket = put_flash(socket, :error, "Cannot modify your own confirmation status")
{:noreply, socket}
else
toggle_user_confirmation_safely(socket, user)
end
end
defp toggle_user_status_safely(socket, user) do
new_status = !user.is_active
case Auth.update_user_status(user, %{"is_active" => new_status}) do
{:ok, _user} ->
status_text = if new_status, do: "activated", else: "deactivated"
socket =
socket
|> put_flash(:info, "User #{status_text} successfully")
|> load_users()
|> load_stats()
{:noreply, socket}
{:error, :cannot_deactivate_last_owner} ->
socket = put_flash(socket, :error, "Cannot deactivate the last system owner")
{:noreply, socket}
{:error, _changeset} ->
socket = put_flash(socket, :error, "Failed to update user status")
{:noreply, socket}
end
end
defp toggle_user_confirmation_safely(socket, user) do
case Auth.toggle_user_confirmation(user) do
{:ok, updated_user} ->
status_text = if updated_user.confirmed_at, do: "confirmed", else: "unconfirmed"
socket =
socket
|> put_flash(:info, "User email #{status_text} successfully")
|> load_users()
|> load_stats()
{:noreply, socket}
{:error, _changeset} ->
socket = put_flash(socket, :error, "Failed to update user confirmation status")
{:noreply, socket}
end
end
defp load_users(socket) do
params = [
page: socket.assigns.page,
page_size: socket.assigns.per_page,
search: socket.assigns.search_query,
role: socket.assigns.filter_role
]
%{users: users, total_count: total_count, total_pages: total_pages} =
Auth.list_users_paginated(params)
# Force refresh by clearing users first, then setting new ones
# This ensures LiveView doesn't reuse stale user objects with cached roles
socket
|> assign(:users, [])
|> assign(:users, users)
|> assign(:total_count, total_count)
|> assign(:total_pages, total_pages)
end
defp load_stats(socket) do
stats = Roles.get_extended_stats()
socket
|> assign(:total_users, stats.total_users)
|> assign(:total_owners, stats.owner_count)
|> assign(:total_admins, stats.admin_count)
|> assign(:total_regular_users, stats.user_count)
|> assign(:active_users, stats.active_users)
|> assign(:inactive_users, stats.inactive_users)
|> assign(:confirmed_users, stats.confirmed_users)
|> assign(:pending_users, stats.pending_users)
end
defp get_user_roles(user) do
# Use preloaded roles if available
case Ecto.assoc_loaded?(user.roles) do
true ->
# Use preloaded roles directly since inactive assignments are deleted from DB
user.roles
|> Enum.map(& &1.name)
|> Enum.sort()
false ->
# Fallback to DB query if roles not preloaded
Roles.get_user_roles(user)
end
end
defp user_has_role?(user, role_name) do
role_name in get_user_roles(user)
end
defp toggle_user_role(user, role_name) do
if user_has_role?(user, role_name) do
case Roles.remove_role(user, role_name) do
{:ok, _} -> {:ok, :removed}
{:error, reason} -> {:error, reason}
end
else
case Roles.assign_role(user, role_name) do
{:ok, _} -> {:ok, :added}
{:error, reason} -> {:error, reason}
end
end
end
defp handle_role_toggle_result(result, role_name, socket) do
case result do
{:ok, action} ->
action_text = if action == :added, do: "added to", else: "removed from"
socket =
socket
|> put_flash(:info, "Role #{role_name} #{action_text} user successfully")
|> load_users()
|> load_stats()
{:noreply, socket}
{:error, reason} ->
error_msg = format_role_error_message(reason)
socket = put_flash(socket, :error, error_msg)
{:noreply, socket}
end
end
defp format_role_error_message(reason) do
case reason do
:cannot_remove_last_owner -> "Cannot remove the last system owner"
:owner_role_protected -> "Owner role cannot be assigned manually"
:role_not_found -> "Role not found"
_ -> "Failed to update user role"
end
end
# Optimized function using preloaded roles - used in template conditionals
def get_primary_role_name_unsafe(user) do
roles = get_user_roles(user)
cond do
"Owner" in roles -> "Owner"
"Admin" in roles -> "Admin"
true -> "User"
end
end
## Live Event Handlers
def handle_info({:user_created, _user}, socket) do
socket =
socket
|> load_users()
|> load_stats()
{:noreply, socket}
end
def handle_info({:user_updated, _user}, socket) do
socket =
socket
|> load_users()
|> load_stats()
{:noreply, socket}
end
def handle_info({:user_role_assigned, _user, _role_name}, socket) do
socket =
socket
|> load_users()
|> load_stats()
{:noreply, socket}
end
def handle_info({:user_role_removed, _user, _role_name}, socket) do
socket =
socket
|> load_users()
|> load_stats()
{:noreply, socket}
end
def handle_info({:user_roles_synced, _user, _new_roles}, socket) do
socket =
socket
|> load_users()
|> load_stats()
{:noreply, socket}
end
def handle_info({:user_confirmed, _user}, socket) do
socket =
socket
|> load_users()
|> load_stats()
{:noreply, socket}
end
def handle_info({:user_unconfirmed, _user}, socket) do
socket =
socket
|> load_users()
|> load_stats()
{:noreply, socket}
end
def handle_info({:stats_updated, stats}, socket) do
socket =
socket
|> assign(:total_users, stats.total_users)
|> assign(:total_owners, stats.owner_count)
|> assign(:total_admins, stats.admin_count)
|> assign(:total_regular_users, stats.user_count)
|> assign(:active_users, stats.active_users)
|> assign(:inactive_users, stats.inactive_users)
|> assign(:confirmed_users, stats.confirmed_users)
|> assign(:pending_users, stats.pending_users)
{:noreply, socket}
end
end