mitgliederverwaltung/lib/mv_web/live/group_live/form.ex
Simon ddc8335cc0
All checks were successful
continuous-integration/drone/push Build is passing
refactor: improve groups LiveView based on code review feedback
2026-01-28 10:33:27 +01:00

184 lines
5.1 KiB
Elixir

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"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.form for={@form} id="group-form" phx-change="validate" phx-submit="save">
<%!-- Header with Back button, Title, and Save button --%>
<div class="flex items-center justify-between gap-4 pb-4">
<.button navigate={return_path(@return_to, @group)} type="button">
<.icon name="hero-arrow-left" class="size-4" />
{gettext("Back")}
</.button>
<h1 class="text-2xl font-bold text-center flex-1">
{@page_title}
</h1>
<.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit">
{gettext("Save")}
</.button>
</div>
<div class="max-w-2xl space-y-4">
<.input field={@form[:name]} label={gettext("Name")} required />
<.input
field={@form[:description]}
type="textarea"
label={gettext("Description")}
rows="4"
/>
</div>
</.form>
</Layouts.app>
"""
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