Packages
phoenix_kit
1.7.13
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
Current section
Files
lib/phoenix_kit_web/live/users/user_details.html.heex
<PhoenixKitWeb.Components.LayoutWrapper.app_layout
flash={@flash}
phoenix_kit_current_scope={assigns[:phoenix_kit_current_scope]}
page_title={@page_title}
current_path={@url_path}
project_title={@project_title}
current_locale={@current_locale}
>
<div class="container flex-col mx-auto px-4 py-6">
<%!-- Header Section --%>
<header class="w-full relative mb-6">
<%!-- Back Button (Left aligned) --%>
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/users")}
class="btn btn-outline btn-primary btn-sm absolute left-0 top-0"
>
<PhoenixKitWeb.Components.Core.Icons.icon_arrow_left /> {gettext("Back to Users")}
</.link>
<%!-- Title Section --%>
<div class="text-center pt-10 sm:pt-0">
<h1 class="text-3xl font-bold text-base-content mb-2">{@page_title}</h1>
<p class="text-base-content/70">{@user.email}</p>
</div>
<%!-- Action Buttons (Right aligned) --%>
<div class="absolute right-0 top-0 flex gap-2">
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin/users/edit/#{@user.id}")}
class="btn btn-primary btn-sm"
>
<PhoenixKitWeb.Components.Core.Icons.icon_edit /> {gettext("Edit")}
</.link>
<button
type="button"
class="btn btn-error btn-sm btn-outline"
phx-click="show_delete_modal"
>
<PhoenixKitWeb.Components.Core.Icons.icon_trash /> {gettext("Delete")}
</button>
</div>
</header>
<%!-- Tabs Navigation --%>
<div class="flex justify-center mb-6">
<div role="tablist" class="tabs tabs-boxed bg-base-200 p-1">
<button
type="button"
role="tab"
class={"tab gap-2 #{if @active_tab == "profile", do: "tab-active"}"}
phx-click="change_tab"
phx-value-tab="profile"
>
<.icon name="hero-user" class="w-4 h-4" />
{gettext("Profile")}
</button>
<%= if @connections_enabled do %>
<button
type="button"
role="tab"
class={"tab gap-2 #{if @active_tab == "connections", do: "tab-active"}"}
phx-click="change_tab"
phx-value-tab="connections"
>
<.icon name="hero-user-group" class="w-4 h-4" />
{gettext("Connections")}
</button>
<% end %>
<button
type="button"
role="tab"
class={"tab gap-2 #{if @active_tab == "notes", do: "tab-active"}"}
phx-click="change_tab"
phx-value-tab="notes"
>
<.icon name="hero-document-text" class="w-4 h-4" />
{gettext("Notes")}
<%= if length(@admin_notes) > 0 do %>
<span class="badge badge-sm">{length(@admin_notes)}</span>
<% end %>
</button>
</div>
</div>
<%!-- Tab Content --%>
<div class="max-w-4xl mx-auto">
<%= if @active_tab == "profile" do %>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<%!-- Left Column: Avatar and Quick Stats --%>
<div class="lg:col-span-1">
<div class="card bg-base-200 shadow-sm">
<div class="card-body items-center text-center">
<%!-- Avatar --%>
<div class="mb-4">
<.user_avatar user={@user} size="lg" class="!w-24 !h-24 !text-3xl" />
</div>
<%!-- Name --%>
<h2 class="card-title">{user_display_name(@user)}</h2>
<%!-- Username --%>
<%= if @user.username do %>
<p class="text-base-content/70">@{@user.username}</p>
<% end %>
<%!-- Status Badges --%>
<div class="flex flex-wrap gap-2 mt-4 justify-center">
<%= if @user.is_active do %>
<span class="badge badge-success">{gettext("Active")}</span>
<% else %>
<span class="badge badge-error">{gettext("Inactive")}</span>
<% end %>
<%= if @user.confirmed_at do %>
<span class="badge badge-info">{gettext("Confirmed")}</span>
<% else %>
<span class="badge badge-warning">{gettext("Unconfirmed")}</span>
<% end %>
</div>
<%!-- Roles --%>
<%= if @user.roles && length(@user.roles) > 0 do %>
<div class="mt-4">
<h3 class="text-sm font-semibold text-base-content/70 mb-2">
{gettext("Roles")}
</h3>
<div class="flex flex-wrap gap-1 justify-center">
<%= for role <- @user.roles do %>
<.role_badge role={role} size={:sm} />
<% end %>
</div>
</div>
<% end %>
</div>
</div>
</div>
<%!-- Right Column: Details --%>
<div class="lg:col-span-2 space-y-6">
<%!-- Basic Information --%>
<div class="card bg-base-200 shadow-sm">
<div class="card-body">
<h3 class="card-title text-lg">{gettext("Basic Information")}</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Email")}
</label>
<p class="text-base-content">{@user.email}</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Username")}
</label>
<p class="text-base-content">{@user.username || "-"}</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("First Name")}
</label>
<p class="text-base-content">{@user.first_name || "-"}</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Last Name")}
</label>
<p class="text-base-content">{@user.last_name || "-"}</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Timezone")}
</label>
<p class="text-base-content">{format_timezone(@user.user_timezone)}</p>
</div>
</div>
</div>
</div>
<%!-- Account Information --%>
<div class="card bg-base-200 shadow-sm">
<div class="card-body">
<h3 class="card-title text-lg">{gettext("Account Information")}</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("User ID")}
</label>
<p class="text-base-content font-mono text-sm">{@user.id}</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Status")}
</label>
<p class="text-base-content">
{if @user.is_active, do: gettext("Active"), else: gettext("Inactive")}
</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Email Confirmed")}
</label>
<p class="text-base-content">
<%= if @user.confirmed_at do %>
{UtilsDate.format_datetime_with_user_format(@user.confirmed_at)}
<% else %>
{gettext("Not confirmed")}
<% end %>
</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Registered")}
</label>
<p class="text-base-content">
{UtilsDate.format_datetime_with_user_format(@user.inserted_at)}
</p>
</div>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Last Updated")}
</label>
<p class="text-base-content">
{UtilsDate.format_datetime_with_user_format(@user.updated_at)}
</p>
</div>
</div>
</div>
</div>
<%!-- Registration Details --%>
<%= if @user.registration_ip || format_location(@user) do %>
<div class="card bg-base-200 shadow-sm">
<div class="card-body">
<h3 class="card-title text-lg">{gettext("Registration Details")}</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
<%= if @user.registration_ip do %>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Registration IP")}
</label>
<p class="text-base-content font-mono text-sm">{@user.registration_ip}</p>
</div>
<% end %>
<%= if format_location(@user) do %>
<div>
<label class="text-sm font-medium text-base-content/70">
{gettext("Registration Location")}
</label>
<p class="text-base-content">{format_location(@user)}</p>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
<%!-- Custom Fields --%>
<%= if @custom_field_definitions && length(@custom_field_definitions) > 0 do %>
<div class="card bg-base-200 shadow-sm">
<div class="card-body">
<h3 class="card-title text-lg">{gettext("Custom Fields")}</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
<%= for field <- @custom_field_definitions do %>
<div>
<label class="text-sm font-medium text-base-content/70">
{field["label"]}
</label>
<p class="text-base-content">
{format_custom_field_value(
get_custom_field_value(@user, field["key"]),
field["type"]
)}
</p>
</div>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
<%!-- Connections Tab Content --%>
<%= if @active_tab == "connections" and @connections_enabled do %>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
<%!-- Followers --%>
<div class="card bg-base-200 shadow-sm">
<div class="card-body items-center text-center p-4">
<.icon name="hero-users" class="w-8 h-8 text-primary" />
<div class="text-2xl font-bold">{@connections_stats.followers}</div>
<div class="text-sm text-base-content/70">{gettext("Followers")}</div>
</div>
</div>
<%!-- Following --%>
<div class="card bg-base-200 shadow-sm">
<div class="card-body items-center text-center p-4">
<.icon name="hero-user-plus" class="w-8 h-8 text-secondary" />
<div class="text-2xl font-bold">{@connections_stats.following}</div>
<div class="text-sm text-base-content/70">{gettext("Following")}</div>
</div>
</div>
<%!-- Connections --%>
<div class="card bg-base-200 shadow-sm">
<div class="card-body items-center text-center p-4">
<.icon name="hero-link" class="w-8 h-8 text-accent" />
<div class="text-2xl font-bold">{@connections_stats.connections}</div>
<div class="text-sm text-base-content/70">{gettext("Connections")}</div>
</div>
</div>
<%!-- Pending --%>
<div class="card bg-base-200 shadow-sm">
<div class="card-body items-center text-center p-4">
<.icon name="hero-clock" class="w-8 h-8 text-warning" />
<div class="text-2xl font-bold">{@connections_stats.pending}</div>
<div class="text-sm text-base-content/70">{gettext("Pending")}</div>
</div>
</div>
<%!-- Blocked --%>
<div class="card bg-base-200 shadow-sm">
<div class="card-body items-center text-center p-4">
<.icon name="hero-no-symbol" class="w-8 h-8 text-error" />
<div class="text-2xl font-bold">{@connections_stats.blocked}</div>
<div class="text-sm text-base-content/70">{gettext("Blocked")}</div>
</div>
</div>
</div>
<% end %>
<%!-- Notes Tab Content --%>
<%= if @active_tab == "notes" do %>
<div class="space-y-6">
<%!-- Add Note Form --%>
<div class="card bg-base-200 shadow-sm">
<div class="card-body">
<h3 class="card-title text-lg">
<%= if @editing_note_id do %>
{gettext("Edit Note")}
<% else %>
{gettext("Add Note")}
<% end %>
</h3>
<.form
for={@note_form}
phx-submit={if @editing_note_id, do: "update_note", else: "add_note"}
class="space-y-4"
>
<div class="form-control">
<textarea
name="admin_note[content]"
class="textarea textarea-bordered w-full h-24"
placeholder={gettext("Write a note about this user...")}
required
>{Phoenix.HTML.Form.input_value(@note_form, :content)}</textarea>
</div>
<div class="flex justify-end gap-2">
<%= if @editing_note_id do %>
<button type="button" class="btn btn-ghost" phx-click="cancel_edit">
{gettext("Cancel")}
</button>
<button type="submit" class="btn btn-primary">
<.icon name="hero-check" class="w-4 h-4" />
{gettext("Update Note")}
</button>
<% else %>
<button type="submit" class="btn btn-primary">
<.icon name="hero-plus" class="w-4 h-4" />
{gettext("Add Note")}
</button>
<% end %>
</div>
</.form>
</div>
</div>
<%!-- Notes List --%>
<%= if length(@admin_notes) > 0 do %>
<div class="card bg-base-200 shadow-sm">
<div class="card-body">
<h3 class="card-title text-lg">
{gettext("Notes")}
<span class="badge badge-neutral">{length(@admin_notes)}</span>
</h3>
<div class="divide-y divide-base-300">
<%= for note <- @admin_notes do %>
<div class="py-4 first:pt-0 last:pb-0">
<div class="flex items-start justify-between gap-4">
<div class="flex items-start gap-3 flex-1">
<.user_avatar user={note.author} size="sm" class="!w-8 !h-8 !text-sm" />
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 flex-wrap">
<span class="font-medium text-base-content">
<%= if note.author.first_name || note.author.last_name do %>
{note.author.first_name} {note.author.last_name}
<% else %>
{note.author.email}
<% end %>
</span>
<span class="text-sm text-base-content/50">
<.time_ago datetime={note.inserted_at} />
</span>
<%= if note.updated_at != note.inserted_at do %>
<span class="text-xs text-base-content/40">
({gettext("edited")})
</span>
<% end %>
</div>
<p class="mt-1 text-base-content whitespace-pre-wrap">
{note.content}
</p>
</div>
</div>
<div class="flex gap-1 shrink-0">
<button
type="button"
class="btn btn-ghost btn-xs"
phx-click="edit_note"
phx-value-id={note.id}
title={gettext("Edit")}
>
<.icon name="hero-pencil" class="w-4 h-4" />
</button>
<button
type="button"
class="btn btn-ghost btn-xs text-error"
phx-click="delete_note"
phx-value-id={note.id}
data-confirm={gettext("Are you sure you want to delete this note?")}
title={gettext("Delete")}
>
<.icon name="hero-trash" class="w-4 h-4" />
</button>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
<% else %>
<div class="card bg-base-200 shadow-sm">
<div class="card-body items-center text-center py-12">
<.icon name="hero-document-text" class="w-12 h-12 text-base-content/30" />
<p class="text-base-content/50 mt-2">{gettext("No notes yet")}</p>
<p class="text-sm text-base-content/40">
{gettext("Add a note above to start documenting information about this user.")}
</p>
</div>
</div>
<% end %>
</div>
<% end %>
</div>
</div>
<%!-- Delete Confirmation Modal --%>
<%= if @show_delete_modal do %>
<div class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg text-error">{gettext("Delete User")}</h3>
<p class="py-4">
{gettext("Are you sure you want to delete")} <strong>{@user.email}</strong>? {gettext(
"This action cannot be undone."
)}
</p>
<div class="modal-action">
<button type="button" class="btn btn-error" phx-click="delete_user">
{gettext("Delete")}
</button>
<button type="button" class="btn btn-outline" phx-click="hide_delete_modal">
{gettext("Cancel")}
</button>
</div>
</div>
<div class="modal-backdrop" phx-click="hide_delete_modal"></div>
</div>
<% end %>
</PhoenixKitWeb.Components.LayoutWrapper.app_layout>