mitgliederverwaltung/lib/mv_web/live/group_live/form.ex

201 lines
5.6 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
action = if params["slug"], do: :update, else: :create
resource = Mv.Membership.Group
unless can?(actor, action, resource) do
{:ok, redirect(socket, to: ~p"/groups")}
else
socket =
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
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
{:ok, assign_form(socket)}
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"""
<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 = 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