defmodule MvWeb.GroupLive.Form do @moduledoc """ LiveView form for creating and editing groups. ## Features - Create new groups with name and description - Edit existing group details (name and description) - Slug is automatically generated and immutable - Form validation with visual feedback ## Security - Only admin users can create/edit groups - Non-admin users are redirected """ use MvWeb, :live_view import MvWeb.LiveHelpers, only: [current_actor: 1, submit_form: 3] import MvWeb.Authorization alias Mv.Membership @impl true def mount(params, _session, socket) do actor = current_actor(socket) # Check authorization based on whether we are creating or updating action = if params["slug"], do: :update, else: :create resource = Mv.Membership.Group if can?(actor, action, resource) do {:ok, socket |> assign(:actor, actor) |> assign(:group, nil) |> assign(:page_title, page_title_for_params(params)) |> assign(:return_to, return_to_for_params(params))} else {:ok, redirect(socket, to: ~p"/groups")} end end @impl true def handle_params(params, _url, socket) do actor = socket.assigns.actor case params do %{"slug" => slug} when is_binary(slug) -> case Membership.get_group_by_slug(slug, actor: actor, load: []) do {:ok, nil} -> {:noreply, socket |> put_flash(:error, gettext("Group not found.")) |> redirect(to: ~p"/groups")} {:ok, group} -> {:noreply, socket |> assign(:group, group) |> assign(:page_title, gettext("Edit Group")) |> assign(:return_to, :show) |> assign_form(actor)} {:error, _error} -> {:noreply, socket |> put_flash(:error, gettext("Failed to load group.")) |> redirect(to: ~p"/groups")} end _ -> # New group - ensure form is initialized for create {:noreply, assign_form(socket, actor)} end end @impl true def render(assigns) do ~H""" <.form for={@form} id="group-form" phx-change="validate" phx-submit="save"> <%!-- Header with Back button, Title, and Save button --%>
<.button navigate={return_path(@return_to, @group)} type="button"> <.icon name="hero-arrow-left" class="size-4" /> {gettext("Back")}

{@page_title}

<.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> {gettext("Save")}
<.input field={@form[:name]} label={gettext("Name")} required /> <.input field={@form[:description]} type="textarea" label={gettext("Description")} rows="4" />
""" end @impl true def handle_event("validate", %{"group" => group_params}, socket) do validated_form = AshPhoenix.Form.validate(socket.assigns.form, group_params) {:noreply, assign(socket, form: validated_form)} end def handle_event("save", %{"group" => group_params}, socket) do actor = socket.assigns.actor case submit_form(socket.assigns.form, group_params, actor) do {:ok, group} -> notify_parent({:saved, group}) redirect_path = case socket.assigns.return_to do :show -> ~p"/groups/#{group.slug}" _ -> ~p"/groups" end socket = socket |> put_flash(:info, gettext("Group saved successfully.")) |> push_navigate(to: redirect_path) {:noreply, socket} {:error, form} -> {:noreply, assign(socket, form: form)} end end defp notify_parent(msg), do: send(self(), {__MODULE__, msg}) defp assign_form(%{assigns: assigns} = socket, actor) do group = Map.get(assigns, :group) form = if group do AshPhoenix.Form.for_update( group, :update, api: Membership, as: "group", actor: actor ) else AshPhoenix.Form.for_create( Mv.Membership.Group, :create, api: Membership, as: "group", actor: actor ) end assign(socket, form: to_form(form)) end defp page_title_for_params(%{"slug" => _slug}), do: gettext("Edit Group") defp page_title_for_params(_params), do: gettext("Create Group") defp return_to_for_params(%{"slug" => _slug}), do: :show defp return_to_for_params(_params), do: :index defp return_path(:index, _group), do: ~p"/groups" defp return_path(:show, group) when not is_nil(group), do: ~p"/groups/#{group.slug}" defp return_path(:show, _group), do: ~p"/groups" defp return_path(_, group) when not is_nil(group), do: ~p"/groups/#{group.slug}" defp return_path(_, _group), do: ~p"/groups" end