Packages
phoenix_kit
1.2.2
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
Current section
Files
lib/phoenix_kit_web/live/users_live.ex
defmodule PhoenixKitWeb.Live.UsersLive do
use PhoenixKitWeb, :live_view
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
# Subscribe to user events for live updates
if connected?(socket) do
Events.subscribe_to_users()
Events.subscribe_to_stats()
end
# Get current path for navigation
current_path = get_current_path(socket, session)
# Get project title from settings
project_title = Settings.get_setting("project_title", "PhoenixKit")
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(:current_path, current_path)
|> assign(:page_title, "Users")
|> assign(:project_title, project_title)
|> load_users()
|> load_stats()
{:ok, socket}
end
def handle_params(_params, _url, socket) do
{: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
def handle_event("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 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 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 role_badge_class_for_custom(role_name) do
case role_name do
"Owner" -> "badge-error"
"Admin" -> "badge-warning"
"User" -> "badge-info"
_ -> "badge-primary"
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
defp get_current_path(_socket, _session) do
# For UsersLive, always return users path
"/phoenix_kit/admin/users"
end
# Optimized function using preloaded roles
defp user_primary_role(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({: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