towerops/lib/towerops_web/live/admin/user_live/index.ex
Graham McIntire 1b4b7f1773
fix user deletion bug and implement timezone support
User deletion bug:
- Store client IP in socket assigns during mount
- Access stored IP during delete event instead of connect_info
- Fixes RuntimeError when attempting to delete users

Timezone support:
- Add timezone offset maps for common timezones (no external deps)
- Implement shift_timezone/2 helper using DateTime.add/3
- Add timezone-aware format_datetime/2, format_date/2, format_iso8601/2
- Keep backwards-compatible 1-arity versions defaulting to UTC
- Add comprehensive test coverage for timezone conversions
- All tests passing (23 tests, 0 failures)

Note: Timezone offsets are standard time only (no DST handling)
Common timezones supported: America/New_York, America/Los_Angeles,
Europe/London, Europe/Paris, Asia/Tokyo, Asia/Shanghai, Australia/Sydney
2026-01-18 10:51:11 -06:00

49 lines
1.3 KiB
Elixir

defmodule ToweropsWeb.Admin.UserLive.Index do
@moduledoc """
Admin interface for viewing and managing users.
"""
use ToweropsWeb, :live_view
alias Towerops.Admin
@impl true
def mount(_params, _session, socket) do
users = Admin.list_all_users()
ip = get_connect_info_ip(socket)
{:ok,
socket
|> assign(:page_title, "All Users")
|> assign(:users, users)
|> assign(:client_ip, ip)}
end
@impl true
def handle_event("impersonate", %{"id" => user_id}, socket) do
{:noreply, redirect(socket, to: ~p"/admin/impersonate/#{user_id}")}
end
@impl true
def handle_event("delete_user", %{"id" => user_id}, socket) do
superuser = socket.assigns.current_scope.superuser || socket.assigns.current_scope.user
ip = socket.assigns.client_ip
case Admin.delete_user(user_id, superuser.id, ip) do
{:ok, _} ->
{:noreply,
socket
|> put_flash(:info, "User deleted successfully")
|> assign(:users, Admin.list_all_users())}
{:error, _} ->
{:noreply, put_flash(socket, :error, "Failed to delete user")}
end
end
defp get_connect_info_ip(socket) do
case get_connect_info(socket, :peer_data) do
%{address: address} -> to_string(:inet_parse.ntoa(address))
_ -> "unknown"
end
end
end