defmodule PhoenixKitWeb.AdminNav do
@moduledoc """
Admin navigation components for the PhoenixKit admin panel.
Provides consistent navigation elements for both desktop sidebar and mobile drawer.
"""
use Phoenix.Component
alias Phoenix.LiveView.JS
alias PhoenixKit.Utils.Routes
@doc """
Renders an admin navigation item with proper active state styling.
## Examples
<.admin_nav_item
href={Routes.path("/admin/dashboard")}
icon="dashboard"
label="Dashboard"
current_path={Routes.path("/admin/dashboard")}
/>
<.admin_nav_item
href={Routes.path("/admin/users")}
icon="users"
label="Users"
current_path={Routes.path("/admin/dashboard")}
mobile={true}
/>
"""
attr :href, :string, required: true
attr :icon, :string, required: true
attr :label, :string, required: true
attr :description, :string, default: nil
attr :current_path, :string, required: true
attr :mobile, :boolean, default: false
attr :nested, :boolean, default: false
attr :disable_active, :boolean, default: false
def admin_nav_item(assigns) do
active =
if assigns.disable_active,
do: false,
else: nav_item_active?(assigns.current_path, assigns.href)
assigns = assign(assigns, :active, active)
~H"""
<.link
href={@href}
class={[
"flex items-center py-2 rounded-lg text-sm font-medium transition-colors",
"hover:bg-base-200 group",
if(@active,
do: "bg-primary text-primary-content",
else: "text-base-content hover:text-primary"
),
if(@mobile, do: "w-full", else: ""),
if(@nested, do: "pl-8 pr-3", else: "px-3")
]}
>
<%= if @nested do %>
{@label}
<% else %>
<.admin_nav_icon icon={@icon} active={@active} />
{@label}
<% end %>
"""
end
@doc """
Renders an icon for admin navigation items.
"""
attr :icon, :string, required: true
attr :active, :boolean, default: false
def admin_nav_icon(assigns) do
~H"""
<%= case @icon do %>
<% "dashboard" -> %>
<% "users" -> %>
<% "roles" -> %>
<% "modules" -> %>
<% "settings" -> %>
<% "sessions" -> %>
<% "live_sessions" -> %>
<% "referral_codes" -> %>
<% "email" -> %>
<% _ -> %>
<% end %>
"""
end
@doc """
Renders theme controller for admin panel.
Based on EZNews theme system with DaisyUI integration.
"""
attr :mobile, :boolean, default: false
def admin_theme_controller(assigns) do
~H"""
"""
end
@doc """
Renders user information section for admin panel sidebar.
Shows current user email and role information.
"""
attr :scope, :any, default: nil
def admin_user_info(assigns) do
~H"""
<%= if @scope && PhoenixKit.Users.Auth.Scope.authenticated?(@scope) do %>
{String.first(PhoenixKit.Users.Auth.Scope.user_email(@scope) || "?") |> String.upcase()}
{PhoenixKit.Users.Auth.Scope.user_email(@scope)}
<%= if PhoenixKit.Users.Auth.Scope.owner?(@scope) do %>
Owner
<% else %>
<%= if PhoenixKit.Users.Auth.Scope.admin?(@scope) do %>
Admin
<% else %>
User
<% end %>
<% end %>
<.link
href={Routes.path("/users/settings")}
class="btn btn-ghost btn-xs flex-1"
>
<.link
href={Routes.path("/users/log-out")}
method="delete"
class="btn btn-ghost btn-xs flex-1 text-error hover:bg-error hover:text-error-content"
>
<% else %>
Not authenticated
<.link href={Routes.path("/users/log-in")} class="btn btn-primary btn-sm w-full">
Login
<% end %>
"""
end
# Helper function to determine if navigation item is active
defp nav_item_active?(current_path, href) do
current_parts = parse_admin_path(current_path)
href_parts = parse_admin_path(href)
exact_match?(current_parts, href_parts) or
tab_match?(current_parts, href_parts) or
parent_match?(current_parts, href_parts)
end
# Check if paths match exactly
defp exact_match?(current_parts, href_parts) do
href_parts.base_path == current_parts.base_path &&
is_nil(href_parts.tab) &&
is_nil(current_parts.tab)
end
# Check if tab-specific paths match
defp tab_match?(current_parts, href_parts) do
href_parts.base_path == current_parts.base_path &&
href_parts.tab == current_parts.tab
end
# Check if parent page matches when on a tab
defp parent_match?(current_parts, href_parts) do
href_parts.base_path == current_parts.base_path &&
is_nil(href_parts.tab) &&
not is_nil(current_parts.tab)
end
# Helper function to parse admin path into components
defp parse_admin_path(path) when is_binary(path) do
# Remove query parameters and split path
[path_part | _] = String.split(path, "?")
# Get dynamic prefix and normalize paths
prefix = PhoenixKit.Config.get_url_prefix()
admin_prefix = if prefix == "/", do: "/admin", else: "#{prefix}/admin"
base_path =
path_part
|> String.replace_prefix(admin_prefix, "")
|> String.replace_prefix(prefix, "")
|> case do
# Default to dashboard for root
"" -> "dashboard"
"/" -> "dashboard"
path -> String.trim_leading(path, "/")
end
# Extract tab parameter if present
tab =
if String.contains?(path, "?tab=") do
path
|> String.split("?tab=")
|> List.last()
|> String.split("&")
|> List.first()
else
nil
end
%{base_path: base_path, tab: tab}
end
defp parse_admin_path(_), do: %{base_path: "dashboard", tab: nil}
end