diff --git a/lib/web/components/core_components.ex b/lib/web/components/core_components.ex index 235e9ee..92d5c24 100644 --- a/lib/web/components/core_components.ex +++ b/lib/web/components/core_components.ex @@ -3,4 +3,99 @@ defmodule Web.CoreComponents do Provides core UI components. """ use Phoenix.Component + + alias Phoenix.HTML.FormField + + attr :id, :any, default: nil + attr :name, :any + attr :label, :string, default: nil + attr :value, :any + attr :type, :string, default: "text", values: ~w[text password] + attr :field, FormField + attr :errors, :list, default: [] + attr :rest, :global, include: ~w[disabled form pattern placeholder readonly required] + + def input(%{field: %FormField{} = field} = assigns) do + errors = + if Phoenix.Component.used_input?(field) do + field.errors + else + [] + end + + assigns + |> assign(field: nil, id: assigns.id || field.id, errors: Enum.map(errors, &translate_error/1)) + |> assign_new(:name, fn -> field.name end) + |> assign_new(:value, fn -> field.value end) + |> input() + end + + def input(assigns) do + ~H""" + <div> + <.label for={@id}>{@label}</.label> + <input + id={@id} + type={@type} + name={@name} + value={Phoenix.HTML.Form.normalize_value(@type, @value)} + {@rest} + /> + <.error :for={error <- @errors}>{error}</.error> + </div> + """ + end + + attr :for, :string, default: nil + slot :inner_block, required: true + + def label(assigns) do + ~H""" + <label for={@for}> + {render_slot(@inner_block)} + </label> + """ + end + + slot :inner_block, required: true + + def error(assigns) do + ~H""" + <p> + <.icon name="hero-exclamation-circle-mini" class="h-5 w-5 flex-none" /> + {render_slot(@inner_block)} + </p> + """ + end + + attr :name, :string, required: true + attr :class, :string, default: nil + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + <span class={[@name, @class]} /> + """ + end + + def translate_error({msg, opts}) do + Enum.reduce(opts, msg, fn {key, value}, acc -> + String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end) + end) + end + + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end + + attr :type, :string, default: "button", values: ~w[button submit] + attr :rest, :global + slot :inner_block, required: true + + def button(assigns) do + ~H""" + <button type={@type} {@rest}> + {render_slot(@inner_block)} + </button> + """ + end end diff --git a/lib/web/live/user_login_live.ex b/lib/web/live/user_login_live.ex index 3dcf3ac..3381c3f 100644 --- a/lib/web/live/user_login_live.ex +++ b/lib/web/live/user_login_live.ex @@ -2,46 +2,24 @@ defmodule Web.UserLoginLive do @moduledoc false use Web, :live_view + def mount(_params, _session, socket) do + form = to_form(%{}, as: "user") + {:ok, assign(socket, form: form), temporary_assigns: [form: form], layout: false} + end + def render(assigns) do - # ~H""" - # <div class="mx-auto max-w-sm"> - # <.header class="text-center"> - # Log in to account - # <:subtitle> - # Don't have an account? - # <.link navigate={~p/admin/users/register"} class="font-semibold text-brand hover:underline"> - # Sign up - # </.link> - # for an account now. - # </:subtitle> - # </.header> - - # <.simple_form for={@form} id="login_form" action={~p/admin/users/log_in"} phx-update="ignore"> - # <.input field={@form[:email]} type="email" label="Email" required /> - # <.input field={@form[:password]} type="password" label="Password" required /> - - # <:actions> - # <.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" /> - # <.link href={~p/admin/users/reset_password"} class="text-sm font-semibold"> - # Forgot your password? - # </.link> - # </:actions> - # <:actions> - # <.button phx-disable-with="Logging in..." class="w-full"> - # Log in <span aria-hidden="true">→</span> - # </.button> - # </:actions> - # </.simple_form> - # </div> - # """ ~H""" - <pre>UserLoginLive</pre> + <div class="mx-auto max-w-sm"> + <header>sign in</header> + + <.form for={@form} id="login_form" action={~p"/admin/users/log_in"} phx-update="ignore"> + <.input field={@form[:username]} type="text" label="username" required /> + <.input field={@form[:password]} type="password" label="password" required /> + <.button phx-disable-with="signing in..." class="w-full" type="submit"> + sign in + </.button> + </.form> + </div> """ end - - def mount(_params, _session, socket) do - email = Phoenix.Flash.get(socket.assigns.flash, :email) - form = to_form(%{"email" => email}, as: "user") - {:ok, assign(socket, form: form), temporary_assigns: [form: form]} - end end diff --git a/lib/web/live/user_registration_live.ex b/lib/web/live/user_registration_live.ex index 880aaa1..41e6e83 100644 --- a/lib/web/live/user_registration_live.ex +++ b/lib/web/live/user_registration_live.ex @@ -4,67 +4,29 @@ defmodule Web.UserRegistrationLive do alias Core.Accounts - def render(assigns) do - # ~H""" - # <div class="mx-auto max-w-sm"> - # <.header class="text-center"> - # Register for an account - # <:subtitle> - # Already registered? - # <.link navigate={~p/admin/users/log_in"} class="font-semibold text-brand hover:underline"> - # Log in - # </.link> - # to your account now. - # </:subtitle> - # </.header> - - # <.simple_form - # for={@form} - # id="registration_form" - # phx-submit="save" - # phx-change="validate" - # phx-trigger-action={@trigger_submit} - # action={~p/admin/users/log_in?_action=registered"} - # method="post" - # > - # <.error :if={@check_errors}> - # Oops, something went wrong! Please check the errors below. - # </.error> - - # <.input field={@form[:email]} type="email" label="Email" required /> - # <.input field={@form[:password]} type="password" label="Password" required /> - - # <:actions> - # <.button phx-disable-with="Creating account..." class="w-full">Create an account</.button> - # </:actions> - # </.simple_form> - # </div> - # """ - ~H""" - <pre>UserRegistrationLive</pre> - """ - end - def mount(_params, _session, socket) do changeset = Accounts.change_user_registration(%Schema.User{}) socket = socket - |> assign(trigger_submit: false, check_errors: false) + |> assign(trigger_submit: false) |> assign_form(changeset) - {:ok, socket, temporary_assigns: [form: nil]} + {:ok, socket, temporary_assigns: [form: nil], layout: false} end def handle_event("save", %{"user" => user_params}, socket) do - case Accounts.register_user(user_params) do - {:ok, user} -> - changeset = Accounts.change_user_registration(user) - {:noreply, socket |> assign(trigger_submit: true) |> assign_form(changeset)} + socket = + case Accounts.register_user(user_params) do + {:ok, user} -> + changeset = Accounts.change_user_registration(user) + socket |> assign(trigger_submit: true) |> assign_form(changeset) - {:error, %Ecto.Changeset{} = changeset} -> - {:noreply, socket |> assign(check_errors: true) |> assign_form(changeset)} - end + {:error, %Ecto.Changeset{} = changeset} -> + assign_form(socket, changeset) + end + + {:noreply, socket} end def handle_event("validate", %{"user" => user_params}, socket) do @@ -72,13 +34,31 @@ defmodule Web.UserRegistrationLive do {:noreply, assign_form(socket, Map.put(changeset, :action, :validate))} end + def render(assigns) do + ~H""" + <div class="mx-auto max-w-sm"> + <header class="text-center">finish installation</header> + + <.form + for={@form} + id="registration_form" + phx-submit="save" + phx-change="validate" + phx-trigger-action={@trigger_submit} + action={~p"/admin/users/log_in?_action=registered"} + method="post" + > + <.input field={@form[:username]} type="text" label="username" /> + <.input field={@form[:password]} type="password" label="password" /> + <.button type="submit">create administrator</.button> + </.form> + </div> + """ + end + defp assign_form(socket, %Ecto.Changeset{} = changeset) do form = to_form(changeset, as: "user") - if changeset.valid? do - assign(socket, form: form, check_errors: false) - else - assign(socket, form: form) - end + assign(socket, form: form) end end