Packages

phoenix_kit

2.23.2
2.60.3 2.60.2 2.60.1 2.60.0 2.59.0 2.58.0 2.57.1 2.57.0 2.56.1 2.56.0 2.55.1 2.55.0 2.54.2 2.54.1 2.54.0 2.53.0 2.52.2 2.52.1 2.52.0 2.51.0 2.50.0 2.49.1 2.49.0 2.48.0 2.47.0 2.46.0 2.45.0 2.44.0 2.43.1 2.43.0 2.42.1 2.42.0 2.41.6 2.41.4 2.41.3 2.41.2 2.41.1 2.41.0 2.40.1 2.40.0 2.39.0 2.38.1 2.38.0 2.37.5 2.37.4 2.37.3 2.37.2 2.37.1 2.37.0 2.36.1 2.36.0 2.35.0 2.34.0 2.33.0 2.32.1 2.32.0 2.31.1 2.31.0 2.30.0 2.29.1 2.29.0 2.28.2 2.28.1 2.28.0 2.27.2 2.27.1 2.27.0 2.26.1 2.26.0 2.25.0 2.24.0 2.23.3 2.23.2 2.23.1 2.23.0 2.22.24 2.22.23 2.22.22 2.22.21 2.22.20 2.22.19 2.22.18 2.22.17 2.22.16 2.22.15 2.22.14 2.22.13 2.22.12 2.22.11 2.22.10 2.22.9 2.22.8 2.22.7 2.22.6 2.22.5 2.22.4 2.22.3 2.22.2 2.22.1 2.22.0 2.21.5 2.21.4 2.21.3 2.21.2 2.21.1 2.21.0 2.20.0 2.19.0 2.18.1 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.2 2.14.1 2.14.0 2.13.19 2.13.18 2.13.17 2.13.16 2.13.15 2.13.13 2.13.12 2.13.11 2.13.10 2.13.9 2.13.8 2.13.7 2.13.6 2.13.5 2.13.4 2.13.3 2.13.2 2.13.1 2.13.0 2.12.1 2.12.0 2.11.0 2.10.0 2.9.0 2.8.1 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.0 2.0.1 2.0.0 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 components auth_page_wrapper.ex
Raw

lib/phoenix_kit_web/components/auth_page_wrapper.ex

defmodule PhoenixKitWeb.Components.AuthPageWrapper do
  @moduledoc """
  Wrapper component for all auth pages (login, registration, etc.).

  Reads branding settings (logo, background image/color) from Settings
  and renders a consistent layout with optional custom branding.
  Supports separate background images for desktop and mobile viewports.
  """
  use Phoenix.Component

  import Phoenix.HTML, only: [raw: 1]

  alias PhoenixKit.Modules.Languages
  alias PhoenixKit.Modules.Storage.URLSigner
  alias PhoenixKit.Settings
  alias PhoenixKit.Utils.CssValue
  alias PhoenixKitWeb.Components.Core.DevNotice
  alias PhoenixKitWeb.Components.Core.LanguageSwitcher
  alias PhoenixKitWeb.Components.LayoutWrapper

  attr :flash, :map, required: true
  attr :phoenix_kit_current_scope, :any, default: nil
  attr :page_title, :string, required: true
  attr :current_path, :string, default: nil

  attr :dev_mailbox_message, :string,
    default: nil,
    doc:
      "If set, renders <.dev_mailbox_notice message={...} /> below the card in local-mailer dev mode."

  slot :inner_block, required: true

  def auth_page_wrapper(assigns) do
    assigns =
      assigns
      |> assign_new(:auth_logo_url, fn ->
        case Settings.get_logo_uuid() do
          uuid when is_binary(uuid) and uuid != "" -> URLSigner.signed_url(uuid, "medium")
          _ -> ""
        end
      end)
      |> assign_new(:auth_bg_image, fn ->
        case Settings.get_setting("auth_background_image_file_uuid", "") do
          uuid when is_binary(uuid) and uuid != "" ->
            CssValue.url(URLSigner.signed_url(uuid, "original"))

          _ ->
            ""
        end
      end)
      |> assign_new(:auth_bg_image_mobile, fn ->
        case Settings.get_setting("auth_background_image_mobile_file_uuid", "") do
          uuid when is_binary(uuid) and uuid != "" ->
            CssValue.url(URLSigner.signed_url(uuid, "original"))

          _ ->
            ""
        end
      end)
      # Sanitised on READ, not only on write: these values reach a `<style>`
      # element, whose contents are raw character data — HTML escaping does not
      # apply there, so a value containing `</style>` closes the element and
      # opens whatever follows. Guarding here also neutralises anything a
      # previous release already stored. See `PhoenixKit.Utils.CssValue`.
      |> assign_new(:auth_bg_color, fn ->
        CssValue.color(Settings.get_setting("auth_background_color", ""))
      end)
      |> assign_new(:project_title, fn -> Settings.get_project_title() end)
      |> assign_new(:show_language_switcher, fn ->
        Languages.enabled?() and length(Languages.get_enabled_languages()) > 1
      end)

    assigns = assign(assigns, :bg_style_tag, bg_style_tag(assigns))

    ~H"""
    <LayoutWrapper.auth_layout
      flash={@flash}
      phoenix_kit_current_scope={@phoenix_kit_current_scope}
      page_title={@page_title}
      current_path={@current_path}
    >
      {raw(@bg_style_tag)}
      <%!-- Sizing here is deliberately free of viewport units.
            `w-[100vw]` overflowed by exactly the scrollbar-gutter width — the
            host root sets `scrollbar-gutter: stable`, so the gutter is always
            reserved and 100vw counts it, producing a horizontal scrollbar on a
            page that has nothing to scroll. `w-full` is a percentage of the
            containing block and excludes it.
            `min-h-full` for the same reason: a percentage can never exceed its
            parent, so it cannot manufacture a vertical scrollbar either. The
            parent's height comes from `auth_layout`'s `min-h-dvh`.
            The old `-my-8` / `-mx-[calc(50vw-50%)]` compensated for a chrome
            wrapper that auth no longer renders inside. --%>
      <div class="auth-bg min-h-full w-full flex items-center justify-center px-4 py-8">
        <div class="card bg-base-100 w-full max-w-sm shadow-2xl">
          <div class="card-body">
            <%= if @auth_logo_url != "" do %>
              <div class="flex justify-center mb-6">
                <img src={@auth_logo_url} alt={@project_title} class="h-20 object-contain" />
              </div>
            <% end %>
            {render_slot(@inner_block)}
            <%= if @show_language_switcher do %>
              <div class="flex justify-center mt-4">
                <LanguageSwitcher.language_switcher_dropdown
                  current_path={@current_path}
                  show_current={true}
                />
              </div>
            <% end %>
            <%= if @dev_mailbox_message do %>
              <DevNotice.dev_mailbox_notice message={@dev_mailbox_message} />
            <% end %>
          </div>
        </div>
      </div>
    </LayoutWrapper.auth_layout>
    """
  end

  # This function builds markup by string concatenation and its result is
  # emitted through `raw/1`, so EVERY value interpolated here must already be
  # known-safe. The assigns are sanitised on read above, and re-sanitised here
  # because `assign_new/3` lets a caller supply them directly — this is the one
  # place the stylesheet is assembled, so this is where the guarantee belongs.
  # `CssValue.color/1` and `CssValue.url/1` return `""` for anything they do not
  # recognise, and `""` degrades to "no background", never to broken markup.
  defp bg_style_tag(assigns) do
    desktop = bg_css(CssValue.url(assigns.auth_bg_image), CssValue.color(assigns.auth_bg_color))

    mobile =
      case CssValue.url(assigns.auth_bg_image_mobile) do
        "" ->
          ""

        url ->
          "@media (max-width: 768px) { .auth-bg { background-image: url('#{url}'); } }"
      end

    "<style>.auth-bg { #{desktop} background-size: cover; background-position: center; } #{mobile}</style>"
  end

  defp bg_css("", ""), do: ""

  defp bg_css(image_url, _color) when image_url != "",
    do: "background-image: url('#{image_url}');"

  defp bg_css("", color), do: "background: #{color};"
end