# SPDX-FileCopyrightText: 2022 Alembic Pty Ltd # # SPDX-License-Identifier: MIT defmodule AshAuthentication.Phoenix.Components.Password.Input do use AshAuthentication.Phoenix.Overrides.Overridable, field_class: "CSS class for `div` elements surrounding the fields.", label_class: "CSS class for `label` elements.", input_class: "CSS class for text/password `input` elements.", identity_input_label: "Label for identity field.", identity_input_placeholder: "Placeholder for identity field.", password_input_label: "Label for password field.", password_confirmation_input_label: "Label for password confirmation field.", input_class_with_error: "CSS class for text/password `input` elements when there is a validation error.", submit_class: "CSS class for the form submit `input` element.", error_ul: "CSS class for the `ul` element on error lists.", error_li: "CSS class for the `li` elements on error lists.", input_debounce: "Number of milliseconds to debounce input by (or `nil` to disable).", remember_me_class: "CSS class for the `div` element surrounding the remember me field.", remember_me_input_label: "Label for remember me field.", checkbox_class: "CSS class for the `input` element of the remember me field.", checkbox_label_class: "CSS class for the `label` element of the remember me field." @moduledoc """ Function components for dealing with form input during password authentication. ## Component hierarchy These function components are consumed by `AshAuthentication.Phoenix.Components.Password.SignInForm`, `AshAuthentication.Phoenix.Components.Password.RegisterForm` and `AshAuthentication.Phoenix.Components.ResetForm`. #{AshAuthentication.Phoenix.Overrides.Overridable.generate_docs()} """ use AshAuthentication.Phoenix.Web, :component alias AshAuthentication.Strategy alias AshPhoenix.Form alias Phoenix.LiveView.{Rendered, Socket} import Phoenix.HTML.Form import PhoenixHTMLHelpers.Form @doc """ Generate a form field for the configured identity field. ## Props * `socket` - Phoenix LiveView socket. This is needed to be able to retrieve the correct CSS configuration. Required. * `strategy` - The configuration map as per `AshAuthentication.authenticated_resources/1`. Required. * `form` - An `AshPhoenix.Form`. Required. * `input_type` - Either `:text` or `:email`. If not set it will try and guess based on the name of the identity field. * `overrides` - A list of override modules. * `gettext_fn` - Optional text translation function. """ @spec identity_field(%{ required(:socket) => Socket.t(), required(:strategy) => Strategy.t(), required(:form) => Form.t(), optional(:input_type) => :text | :email, optional(:overrides) => [module], optional(:gettext_fn) => {module, atom} }) :: Rendered.t() | no_return def identity_field(assigns) do identity_field = assigns.strategy.identity_field assigns = assigns |> assign(:identity_field, identity_field) |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:gettext_fn, fn -> nil end) |> assign_new(:input_type, fn -> identity_field |> to_string() |> String.contains?("email") |> then(fn true -> :email _ -> :text end) end) |> assign_new(:input_class, fn -> if has_error?(assigns.form, identity_field) do override_for(assigns.overrides, :input_class_with_error) else override_for(assigns.overrides, :input_class) end end) ~H"""
{label(@form, @identity_field, _gettext(override_for(@overrides, :identity_input_label)), class: override_for(@overrides, :label_class) )} {text_input(@form, @identity_field, type: to_string(@input_type), class: @input_class, phx_debounce: override_for(@overrides, :input_debounce), autofocus: "true", placeholder: override_for(@overrides, :identity_input_placeholder) )} <.error form={@form} field={@identity_field} overrides={@overrides} />
""" end @doc """ Generate a form field for the configured password entry field. ## Props * `socket` - Phoenix LiveView socket. This is needed to be able to retrieve the correct CSS configuration. Required. * `strategy` - The configuration map as per `AshAuthentication.authenticated_resources/1`. Required. * `form` - An `AshPhoenix.Form`. Required. * `overrides` - A list of override modules. * `gettext_fn` - Optional text translation function. """ @spec password_field(%{ required(:socket) => Socket.t(), required(:strategy) => Strategy.t(), required(:form) => Form.t(), optional(:overrides) => [module], optional(:gettext_fn) => {module, atom} }) :: Rendered.t() | no_return def password_field(assigns) do password_field = assigns.strategy.password_field assigns = assigns |> assign(:password_field, password_field) |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:gettext_fn, fn -> nil end) |> assign_new(:input_class, fn -> if has_error?(assigns.form, password_field) do override_for(assigns.overrides, :input_class_with_error) else override_for(assigns.overrides, :input_class) end end) ~H"""
{label(@form, @password_field, _gettext(override_for(@overrides, :password_input_label)), class: override_for(@overrides, :label_class) )} {password_input(@form, @password_field, class: @input_class, value: input_value(@form, @password_field), phx_debounce: override_for(@overrides, :input_debounce) )} <.error form={@form} field={@password_field} overrides={@overrides} />
""" end @doc """ Generate a form field for the configured password confirmation entry field. ## Props * `socket` - Phoenix LiveView socket. This is needed to be able to retrieve the correct CSS configuration. Required. * `strategy` - The configuration map as per `AshAuthentication.authenticated_resources/1`. Required. * `form` - An `AshPhoenix.Form`. Required. * `overrides` - A list of override modules. * `gettext_fn` - Optional text translation function. """ @spec password_confirmation_field(%{ required(:socket) => Socket.t(), required(:strategy) => Strategy.t(), required(:form) => Form.t(), optional(:overrides) => [module], optional(:gettext_fn) => {module, atom} }) :: Rendered.t() | no_return def password_confirmation_field(assigns) do password_confirmation_field = assigns.strategy.password_confirmation_field assigns = assigns |> assign(:password_confirmation_field, password_confirmation_field) |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:gettext_fn, fn -> nil end) |> assign_new(:input_class, fn -> if has_error?(assigns.form, password_confirmation_field) do override_for(assigns.overrides, :input_class_with_error) else override_for(assigns.overrides, :input_class) end end) ~H"""
{label( @form, @password_confirmation_field, _gettext(override_for(@overrides, :password_confirmation_input_label)), class: override_for(@overrides, :label_class) )} {password_input(@form, @password_confirmation_field, class: @input_class, value: input_value(@form, @password_confirmation_field), phx_debounce: override_for(@overrides, :input_debounce) )} <.error form={@form} field={@password_confirmation_field} overrides={@overrides} />
""" end @doc """ Generate a form field for the remember me field. ## Props * `socket` - Phoenix LiveView socket. This is needed to be able to retrieve the correct CSS configuration. Required. * `strategy` - The configuration map as per `AshAuthentication.authenticated_resources/1`. Required. * `form` - An `AshPhoenix.Form`. Required. * `name` - The name of the field. Defaults to `:remember_me`. * `overrides` - A list of override modules. * `gettext_fn` - Optional text translation function. """ @spec remember_me_field(%{ required(:socket) => Socket.t(), required(:strategy) => Strategy.t(), required(:form) => Form.t(), optional(:name) => atom, optional(:overrides) => [module], optional(:gettext_fn) => {module, atom} }) :: Rendered.t() | no_return def remember_me_field(assigns) do assigns = assigns |> assign_new(:name, fn -> :remember_me end) |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:gettext_fn, fn -> nil end) |> assign_new(:checkbox_class, fn -> override_for(assigns.overrides, :checkbox_class) end) |> assign_new(:label_class, fn -> override_for(assigns.overrides, :label_class) end) ~H"""
{checkbox(@form, @name, class: @checkbox_class, value: input_value(@form, @name), phx_debounce: override_for(@overrides, :input_debounce) )} {label( @form, @name, _gettext(override_for(@overrides, :remember_me_input_label)), class: override_for(@overrides, :checkbox_label_class) )}
""" end @doc """ Generate an form submit button. ## Props * `socket` - Phoenix LiveView socket. This is needed to be able to retrieve the correct CSS configuration. Required. * `strategy` - The configuration map as per `AshAuthentication.authenticated_resources/1`. Required. * `form` - An `AshPhoenix.Form`. Required. * `action` - Either `:sign_in`, `:register`, `:request_reset` or `:reset`. Required. * `label` - The text to show in the submit label. Generated from the configured action name (via `Phoenix.Naming.humanize/1`) if not supplied. * `overrides` - A list of override modules. * `gettext_fn` - Optional text translation function. """ @spec submit(%{ required(:socket) => Socket.t(), required(:strategy) => Strategy.t(), required(:form) => Form.t(), required(:action) => :sign_in | :register | :request_reset | :reset, optional(:label) => String.t(), optional(:overrides) => [module], optional(:gettext_fn) => {module, atom} }) :: Rendered.t() | no_return def submit(assigns) do assigns = assigns |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:gettext_fn, fn -> nil end) |> assign_new(:label, fn -> case assigns.action do :reset -> assigns.strategy.resettable |> Kernel.||(%{}) |> Map.get(:password_reset_action_name, :reset) |> to_string() |> String.trim_trailing("_with_password") :request_reset -> assigns.strategy.resettable |> Kernel.||(%{}) |> Map.get(:request_password_reset_action_name, :reset_request) |> to_string() |> String.trim_trailing("_with_password") :sign_in -> assigns.strategy.sign_in_action_name |> to_string() |> String.trim_trailing("_with_password") :register -> assigns.strategy.register_action_name |> to_string() |> String.trim_trailing("_with_password") other -> other end |> humanize() end) |> assign_new(:disable_text, fn -> nil end) ~H""" {submit(_gettext(@label), class: override_for(@overrides, :submit_class), phx_disable_with: _gettext(@disable_text) )} """ end @doc """ Generate a list of errors for a field (if there are any). ## Props * `socket` - Phoenix LiveView socket. This is needed to be able to retrieve the correct CSS configuration. Required. * `form` - An `AshPhoenix.Form`. Required. * `field` - The field for which to retrieve the errors. Required. * `overrides` - A list of override modules. * `gettext_fn` - Optional text translation function. """ @spec error(%{ required(:socket) => Socket.t(), required(:form) => Form.t(), required(:field) => atom, optional(:field_label) => String.Chars.t(), optional(:errors) => [{atom, String.t()}], optional(:gettext_fn) => {module, atom} }) :: Rendered.t() | no_return def error(assigns) do assigns = assigns |> assign_new(:overrides, fn -> [AshAuthentication.Phoenix.Overrides.Default] end) |> assign_new(:gettext_fn, fn -> nil end) |> assign_new(:errors, fn -> assigns.form |> Form.errors() |> Keyword.get_values(assigns.field) end) |> assign_new(:field_label, fn -> humanize(assigns.field) end) ~H""" <%= if Enum.any?(@errors) do %> <% end %> """ end defp has_error?(form, field) do form |> Form.errors() |> Keyword.has_key?(field) end end