201 lines
5.6 KiB
Elixir
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
|