defmodule PhoenixKitWeb.Components.UserDashboardNav do
@moduledoc """
User dashboard navigation components for the PhoenixKit user dashboard.
Provides navigation elements specifically for user dashboard pages.
"""
use PhoenixKitWeb, :html
alias PhoenixKit.Modules.Languages
alias PhoenixKit.Modules.Languages.DialectMapper
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth.Scope
alias PhoenixKit.Utils.Routes
alias PhoenixKitWeb.Components.Core.LanguageSwitcher
# Guest dropdown link catalog. Each entry is
# `{key, path, icon, label_fn, setting_gate_fn}`; `label_fn`/`gate_fn`
# are zero-arity so gettext + settings are evaluated at render time
# (per-request locale / live setting), not at compile time.
@guest_link_catalog [
{:login, "/users/log-in", "hero-arrow-right-on-rectangle"},
{:register, "/users/register", "hero-user-plus"},
{:reset, "/users/reset-password", "hero-key"},
{:magic_link, "/users/magic-link", "hero-sparkles"}
]
@doc """
Renders the user widget for dashboard navigation.
For authenticated visitors this is the avatar dropdown (email,
Admin/Dashboard/Settings, language switcher, log out). For anonymous
visitors the same dropdown *shape* is rendered with a generic
"not signed in" icon and guest-relevant links (log in, sign up, forgot
password, magic link) plus the same language switcher — so a single
widget covers both states and always offers a language switcher.
## Attributes
* `:scope` — current scope; `nil`/unauthenticated renders the guest dropdown.
* `:current_path` — used for active-link highlighting and locale-switch URLs.
* `:current_locale` — base code of the active locale (highlighted in the list).
* `:show_language_switcher` — include the in-menu language list (default `true`).
Set `false` when the host renders a standalone switcher elsewhere to avoid
a duplicate. Applies to both the signed-in and guest states.
* `:guest_links` — which guest links may appear, e.g.
`[:login, :register, :reset, :magic_link]` (default: all). Links are also
gated by the `allow_registration` / `magic_link_login_enabled` settings, so
this list can only narrow, never force-enable a disabled feature.
"""
attr(:scope, :any, default: nil)
attr(:current_path, :string, default: "")
attr(:current_locale, :string, default: "en")
attr(:admin_edit_url, :string, default: nil)
attr(:admin_edit_label, :string, default: nil)
attr(:show_language_switcher, :boolean, default: true)
attr(:guest_links, :list, default: [:login, :register, :reset, :magic_link])
def user_dropdown(assigns) do
user = Scope.user(assigns.scope)
assigns =
assigns
|> assign(:user, user)
~H"""
<%= if @scope && PhoenixKit.Users.Auth.Scope.authenticated?(@scope) do %>
<%= if PhoenixKit.Users.Auth.Scope.admin?(@scope) do %>
-
<.link
navigate={PhoenixKit.Utils.Routes.path("/admin")}
class={"flex items-center gap-3" <> if(active_path?(assigns[:current_path], "/admin"), do: " bg-primary text-primary-content", else: "")}
>
<.icon name="hero-shield-check" class="w-4 h-4" />
{gettext("Admin Panel")}
<%= if @admin_edit_url do %>
-
<.icon name="hero-pencil-square" class="w-4 h-4" />
{@admin_edit_label || "Edit"}
<% end %>
<% end %>
-
<.link
navigate={PhoenixKit.Utils.Routes.path("/dashboard", locale: @current_locale)}
class={"flex items-center gap-3" <> if(active_path?(assigns[:current_path], "/dashboard"), do: " bg-primary text-primary-content", else: "")}
>
<.icon name="hero-home" class="w-4 h-4" />
Dashboard
-
<.link
navigate={PhoenixKit.Utils.Routes.path("/dashboard/settings", locale: @current_locale)}
class={"flex items-center gap-3" <> if(active_path?(assigns[:current_path], "/dashboard/settings"), do: " bg-primary text-primary-content", else: "")}
>
<.icon name="hero-cog-6-tooth" class="w-4 h-4" />
Settings
<.language_menu_section
:if={@show_language_switcher}
current_path={@current_path}
current_locale={@current_locale}
/>
-
<.link
navigate={Routes.path("/users/log-out")}
method="delete"
class="flex items-center gap-3 text-error hover:bg-error hover:text-error-content"
>
<.icon name="hero-arrow-right-on-rectangle" class="w-4 h-4" />
Log Out
<% else %>
<.guest_dropdown
current_path={@current_path}
current_locale={@current_locale}
guest_links={@guest_links}
show_language_switcher={@show_language_switcher}
/>
<% end %>
"""
end
# Guest counterpart to the authenticated avatar dropdown: same shape and
# styling, a generic "not signed in" trigger icon, guest-relevant links,
# and the shared language switcher.
attr(:current_path, :string, required: true)
attr(:current_locale, :string, required: true)
attr(:guest_links, :list, required: true)
attr(:show_language_switcher, :boolean, required: true)
defp guest_dropdown(assigns) do
assigns = assign(assigns, :links, visible_guest_links(assigns.guest_links))
~H"""
<%!--
Rounded-rectangle placeholder matching the authenticated avatar shape
(md = w-10 h-10, rounded-lg via its `!rounded-lg`), so the guest and
signed-in triggers look consistent — a generic person silhouette
signals "not signed in".
--%>
<.icon name="hero-user" class="w-6 h-6" />
-
<.link navigate={Routes.path(link.path)} class="flex items-center gap-3">
<.icon name={link.icon} class="w-4 h-4" />
{link.label}
<.language_menu_section
:if={@show_language_switcher}
current_path={@current_path}
current_locale={@current_locale}
/>
"""
end
# Shared, independently scrollable language list used by both the
# authenticated and guest dropdowns. Renders nothing when fewer than two
# languages are enabled. Anchors are styled directly (no nested daisyUI
# `