defmodule MvWeb.RoleLive.Form do @moduledoc """ LiveView form for creating and editing roles. ## Features - Create new roles - Edit existing roles (name, description, permission_set_name) - Custom dropdown for permission_set_name with badges - Form validation ## Security Only admins can access this page (enforced by authorization). """ use MvWeb, :live_view alias Mv.Authorization.PermissionSets @impl true def render(assigns) do ~H""" <.header> {@page_title} <:subtitle>{gettext("Use this form to manage roles in your database.")} <.form class="max-w-xl" for={@form} id="role-form" phx-change="validate" phx-submit="save"> <.input field={@form[:name]} type="text" label={gettext("Name")} required /> <.input field={@form[:description]} type="textarea" label={gettext("Description")} rows="3" />
<%= if @form.errors[:permission_set_name] do %> <%= for error <- List.wrap(@form.errors[:permission_set_name]) do %> <% {msg, _opts} = if is_tuple(error), do: error, else: {error, []} %>

<.icon name="hero-exclamation-circle" class="size-5" /> {msg}

<% end %> <% end %>
<.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> {gettext("Save Role")} <.button navigate={return_path(@return_to, @role)} type="button"> {gettext("Cancel")}
""" end @impl true def mount(params, _session, socket) do # Ensure current_user has role loaded for authorization checks socket = if socket.assigns[:current_user] do user = socket.assigns.current_user user_with_role = case Map.get(user, :role) do %Ash.NotLoaded{} -> Ash.load!(user, :role, domain: Mv.Accounts) nil -> Ash.load!(user, :role, domain: Mv.Accounts) role when not is_nil(role) -> user end assign(socket, :current_user, user_with_role) else socket end role = case params["id"] do nil -> nil id -> Ash.get!(Mv.Authorization.Role, id, domain: Mv.Authorization) end action = if is_nil(role), do: gettext("New"), else: gettext("Edit") page_title = action <> " " <> gettext("Role") {:ok, socket |> assign(:return_to, return_to(params["return_to"])) |> assign(:role, role) |> assign(:page_title, page_title) |> assign_form()} end @spec return_to(String.t() | nil) :: String.t() defp return_to("show"), do: "show" defp return_to(_), do: "index" @impl true def handle_event("validate", %{"role" => role_params}, socket) do validated_form = AshPhoenix.Form.validate(socket.assigns.form, role_params) {:noreply, assign(socket, form: validated_form)} end def handle_event("save", %{"role" => role_params}, socket) do case AshPhoenix.Form.submit(socket.assigns.form, params: role_params) do {:ok, role} -> notify_parent({:saved, role}) redirect_path = if socket.assigns.return_to == "show" do ~p"/admin/roles/#{role.id}" else ~p"/admin/roles" end socket = socket |> put_flash(:info, gettext("Role saved successfully")) |> push_navigate(to: redirect_path) {:noreply, socket} {:error, form} -> {:noreply, assign(socket, form: form)} end end @spec notify_parent(any()) :: any() defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) @spec assign_form(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t() defp assign_form(%{assigns: %{role: role}} = socket) do form = if role do AshPhoenix.Form.for_update(role, :update_role, domain: Mv.Authorization, as: "role") else AshPhoenix.Form.for_create( Mv.Authorization.Role, :create_role, domain: Mv.Authorization, as: "role" ) end assign(socket, form: to_form(form)) end defp all_permission_sets do PermissionSets.all_permission_sets() |> Enum.map(&Atom.to_string/1) end defp format_permission_set_option("own_data"), do: gettext("own_data - Access only to own data") defp format_permission_set_option("read_only"), do: gettext("read_only - Read access to all data") defp format_permission_set_option("normal_user"), do: gettext("normal_user - Create/Read/Update access") defp format_permission_set_option("admin"), do: gettext("admin - Unrestricted access") defp format_permission_set_option(set), do: set @spec return_path(String.t(), Mv.Authorization.Role.t() | nil) :: String.t() defp return_path("index", _role), do: ~p"/admin/roles" defp return_path("show", role) when not is_nil(role), do: ~p"/admin/roles/#{role.id}" defp return_path("show", _role), do: ~p"/admin/roles" defp return_path(_, role) when not is_nil(role), do: ~p"/admin/roles/#{role.id}" defp return_path(_, _role), do: ~p"/admin/roles" end