Packages

phoenix_kit

1.7.234
1.7.236 1.7.235 1.7.234 1.7.233 1.7.232 1.7.231 1.7.230 1.7.229 1.7.228 1.7.227 1.7.226 1.7.225 1.7.224 1.7.223 1.7.222 1.7.221 1.7.220 1.7.219 1.7.218 1.7.217 1.7.216 1.7.215 1.7.214 1.7.213 1.7.212 1.7.211 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
phoenix_kit lib phoenix_kit_web live dashboard.ex
Raw

lib/phoenix_kit_web/live/dashboard.ex

defmodule PhoenixKitWeb.Live.Dashboard do
@moduledoc """
The `/admin` landing page.
It is built to be the page ANY authenticated visitor can safely be sent to,
whatever their permissions. Two halves:
* a **welcome block**, rendered for everyone, greeting the visitor by name;
* the **operator overview** (`PhoenixKitWeb.Components.Core.DashboardOverview`),
every block of which is permission-gated by
`PhoenixKitWeb.Live.Dashboard.Overview`.
A visitor holding no permissions sees the welcome block and nothing else —
no statistics, no System Information, no cards, and (because the overview
decides before it queries) not one operator query or PubSub subscription on
their behalf. That is what makes the page safe as a universal landing.
Reaching it is not the same as being allowed to see all of it. The route sits
in the ordinary admin `live_session`, alongside every other `/admin/*` page,
so admin navigation to and from it stays a live patch rather than a full page
reload. What admits a permission-less visitor is the GATE:
`:phoenix_kit_ensure_admin` recognises this view via
`PhoenixKitWeb.Users.Auth.landing_view?/1` and skips the admin-area and
per-view permission checks for it alone — authentication, the account gate
and the locale hook still run. The overview's own gates are what keep the
operator blocks away from whoever gets in.
Because nobody is ever evicted from this page, it has to survive a permission
change in place: `PhoenixKitWeb.Users.Auth`'s scope-refresh hook skips its
eviction for the landing view and instead calls
`phoenix_kit_scope_changed/1`, which re-runs
`PhoenixKitWeb.Live.Dashboard.Overview.assign_scope_gates/1`. A demoted
operator watches the cards and statistics disappear (and the subscriptions
behind them close) without leaving the page or reloading it.
`/dashboard` is a SEPARATE page (`PhoenixKitWeb.Live.Dashboard.Index`),
deprecated since 2026-07-27 and sharing nothing with this one.
"""
use PhoenixKitWeb, :live_view
use Gettext, backend: PhoenixKitWeb.Gettext
use PhoenixKitWeb.Live.Dashboard.Overview
alias PhoenixKit.Settings
alias PhoenixKit.Users.Auth.Scope
alias PhoenixKit.Utils.Routes
alias PhoenixKit.Utils.Values
alias PhoenixKitWeb.Live.Dashboard.Overview
@impl true
def mount(_params, session, socket) do
socket =
socket
|> assign(:project_title, Settings.get_project_title())
|> assign(:page_title, "Dashboard")
# Every scope-derived assign on this page — `:can_access_admin_area?`
# included — comes from `Overview.assign_scope_gates/1`, and from nowhere
# else. That is what lets a mid-session permission change recompute all of
# them at once: the scope-refresh hook calls the same function through the
# `phoenix_kit_scope_changed/1` callback `use Overview` injects.
|> Overview.assign_overview(session, Routes.path("/admin"))
{:ok, socket}
end
attr :scope, :any,
required: true,
doc: "the visitor's `PhoenixKit.Users.Auth.Scope`, or `nil`"
@doc """
The welcome half of `/admin` — the part with no permission gate at all, and
therefore the whole page for a visitor holding nothing.
Deliberately an `<h2>`, not an `<h1>`: the page's one header is the
`LayoutWrapper` breadcrumb, and this sits at the same level as the operator
overview's own section headings.
The greeting reuses the EXISTING `"Welcome back"` msgid (it is
`lib/phoenix_kit_web/users/login.html.heex`'s), so it arrives already
translated in every shipped locale. The name is appended in markup rather
than interpolated into a new `"Welcome back, %{name}"` msgid, which would
ship untranslated everywhere. It is a function component rather than mount
assigns so that `gettext` runs at RENDER time: a locale switch that only
patches params (`handle_params`, after mount) still reaches it.
"""
def welcome_block(assigns) do
name = welcome_name(assigns.scope)
assigns =
assigns
|> assign(:welcome_name, name)
|> assign(:welcome_email, welcome_email(assigns.scope, name))
~H"""
<div class="card bg-base-100 shadow-sm">
<div class="card-body">
<h2 class="card-title text-2xl">
{gettext("Welcome back")}<span :if={@welcome_name}>, {@welcome_name}</span>
</h2>
<p :if={@welcome_email} class="text-sm text-base-content/60">{@welcome_email}</p>
</div>
</div>
"""
end
# Who to greet. `Scope.user_full_name/1` is `nil` for anyone who never filled
# in a name — most plain users — and `User.full_name/1` can still hand back a
# blank string (an organization row whose `organization_name` was cleared, or
# a first name of `" "`), hence `presence/1` rather than a `nil` test.
#
# The fallback is the email: every account has one and it is the identifier a
# brand-new user recognises. A scope with no user yields `nil`, and the
# greeting then renders without a name rather than with a dangling comma.
defp welcome_name(scope) do
Values.presence(Scope.user_full_name(scope)) || Values.presence(Scope.user_email(scope))
end
# The email is the page's only statement of WHICH account the visitor is
# signed in as — core ships a multi-account switcher, so that is a real
# question. Suppressed when the greeting already fell back to the email, so
# it is never printed twice.
defp welcome_email(scope, welcome_name) do
case Values.presence(Scope.user_email(scope)) do
^welcome_name -> nil
email -> email
end
end
end