defmodule PhoenixKitWeb.Components.Dashboard.AdminSidebar do @moduledoc """ Admin sidebar component for the PhoenixKit admin panel. Renders the admin navigation using registry-driven Tab structs instead of hardcoded HEEX. Supports: - Permission-gated tabs (filtered by Registry) - Module-enabled filtering (filtered by Registry) - Dynamic children for Entities and Publishing - Subtab expand/collapse - Full reuse of the TabItem component for consistent rendering ## An entry is rendered only if the visitor can open it The menu must never offer a page that bounces the visitor on arrival, so every entry is filtered against the SAME gates its destination enforces — see `reachable_tabs/2`. This matters because `/admin` is the guaranteed landing for every authenticated user: a visitor with no permissions at all now renders this shell, where before only a permission holder could. The registry already drops a tab whose `:permission` key the scope does not hold (`PhoenixKit.Dashboard.Registry.get_tabs/1` → `Tab.permission_granted?/2`) and a tab whose `:visible` function says no. Two gates it does NOT apply are added here: * `Scope.can_access_admin_area?/1`, the first thing `:phoenix_kit_ensure_admin` checks. Fail it and EVERY `/admin` page redirects you — including the personal ones — so the whole menu is empty. * `PhoenixKitWeb.Users.Auth.can_access_admin_view?/2` for an entry that names a `live_view:`. A tab may name a view and no permission key; the mount gate then treats that view as *unmapped* and admits only a scope holding every enabled permission, while the sidebar happily linked it for everyone. ## Usage <.admin_sidebar current_path={@current_path} scope={@phoenix_kit_current_scope} locale={@current_locale} /> """ use Phoenix.Component require Logger alias PhoenixKit.Dashboard.{Group, Registry, Tab} alias PhoenixKit.Users.Auth.Scope alias PhoenixKitWeb.Components.Dashboard.TabItem alias PhoenixKitWeb.Users.Auth import PhoenixKit.Dashboard.TabHelpers import PhoenixKitWeb.Components.Core.Icon, only: [icon: 1] @doc """ Renders the complete admin sidebar navigation. ## Attributes - `current_path` - The current URL path for active state detection - `scope` - The current authentication scope for permission filtering - `locale` - The current locale for path generation - `class` - Additional CSS classes """ attr :current_path, :string, default: "/admin" attr :scope, :any, default: nil attr :locale, :string, default: nil attr :class, :string, default: "" def admin_sidebar(assigns) do # Get admin tabs, already filtered by level, permission, and module-enabled # Expand dynamic children BEFORE active state so dynamic tabs get checked too tabs = :telemetry.span([:phoenix_kit, :admin_sidebar, :render], %{}, fn -> result = assigns.scope |> admin_tabs_for_scope(assigns[:locale]) |> add_active_state(assigns.current_path) {result, %{tab_count: length(result)}} end) # Group tabs grouped_tabs = group_tabs(tabs) groups = Registry.get_groups() assigns = assigns |> assign(:tabs, tabs) |> assign(:grouped_tabs, grouped_tabs) |> assign(:groups, groups) ~H""" <%!-- No `