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 action = if params["slug"], do: :update, else: :create resource = Mv.Membership.Group if can?(actor, action, resource) do socket = load_group_for_form(socket, params, actor) {:ok, assign_form(socket)} else {:ok, redirect(socket, to: ~p"/groups")} end end defp load_group_for_form(socket, params, actor) do case params["slug"] do nil -> # New group socket |> assign(:group, nil) |> assign(:page_title, gettext("Create Group")) |> assign(:return_to, "index") slug -> # Edit existing group load_existing_group(socket, slug, actor) end end defp load_existing_group(socket, slug, actor) do case Membership.get_group_by_slug(slug, actor: actor) do {:ok, nil} -> socket |> put_flash(:error, gettext("Group not found.")) |> redirect(to: ~p"/groups") {:ok, group} -> socket |> assign(:group, group) |> assign(:page_title, gettext("Edit Group")) |> assign(:return_to, "show") {:error, _error} -> socket |> put_flash(:error, gettext("Failed to load group.")) |> redirect(to: ~p"/groups") end end @impl true def handle_params(params, _url, socket) do # Handle slug-based routing for edit case params do %{"slug" => slug} when is_binary(slug) -> actor = current_actor(socket) case Membership.get_group_by_slug(slug, actor: actor) 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()} {:error, _error} -> {:noreply, socket |> put_flash(:error, gettext("Failed to load group.")) |> redirect(to: ~p"/groups")} end _ -> {:noreply, socket} 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 = current_actor(socket) case submit_form(socket.assigns.form, group_params, actor) do {:ok, group} -> notify_parent({:saved, group}) redirect_path = if socket.assigns.return_to == "show" do ~p"/groups/#{group.slug}" else ~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) do group = assigns.group actor = assigns.current_user 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 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