feat: conistent danger zone delete flow
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
carla 2026-02-25 15:09:37 +01:00
parent e5a6003ace
commit 91cf7cca6a
19 changed files with 499 additions and 287 deletions

View file

@ -101,6 +101,31 @@ defmodule MvWeb.GroupLive.Form do
rows="4"
/>
</div>
<%!-- Danger zone: canonical pattern (same as member form) --%>
<%= if @group && can?(@current_user, :destroy, @group) do %>
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
<h2 id="danger-zone-heading" class="text-lg font-semibold mb-3 text-error">
{gettext("Danger zone")}
</h2>
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
<p class="text-base-content/70 mb-4">
{gettext(
"Deleting this group cannot be undone. All member-group associations will be permanently removed."
)}
</p>
<.button
variant="danger"
navigate={~p"/groups/#{@group.slug}?confirm_delete=1"}
data-testid="group-form-delete-btn"
aria-label={gettext("Delete group %{name}", name: @group.name)}
>
<.icon name="hero-trash" class="size-4" />
{gettext("Delete group")}
</.button>
</div>
</section>
<% end %>
</div>
</.form>
</Layouts.app>

View file

@ -77,24 +77,6 @@ defmodule MvWeb.GroupLive.Index do
<:col :let={group} label={gettext("Members")} class="text-right">
{group.member_count || 0}
</:col>
<:action :let={group}>
<.button
variant="ghost"
size="sm"
navigate={~p"/groups/#{group.slug}"}
>
{gettext("View")}
</.button>
<%= if can?(@current_user, :update, Mv.Membership.Group) do %>
<.button
variant="ghost"
size="sm"
navigate={~p"/groups/#{group.slug}/edit"}
>
{gettext("Edit group")}
</.button>
<% end %>
</:action>
</.table>
<% end %>
</div>

View file

@ -39,18 +39,18 @@ defmodule MvWeb.GroupLive.Show do
end
@impl true
def handle_params(%{"slug" => slug}, _url, socket) do
def handle_params(%{"slug" => slug} = params, _url, socket) do
actor = current_actor(socket)
# Check if user can read groups
if can?(actor, :read, Mv.Membership.Group) do
load_group_by_slug(socket, slug, actor)
load_group_by_slug(socket, slug, actor, params)
else
{:noreply, redirect(socket, to: ~p"/members")}
end
end
defp load_group_by_slug(socket, slug, actor) do
defp load_group_by_slug(socket, slug, actor, params \\ %{}) do
# Load group with members and member_count
# Using explicit load ensures efficient preloading of members relationship
require Ash.Query
@ -68,10 +68,16 @@ defmodule MvWeb.GroupLive.Show do
|> redirect(to: ~p"/groups")}
{:ok, group} ->
{:noreply,
socket
|> assign(:page_title, group.name)
|> assign(:group, group)}
open_delete = params["confirm_delete"] == "1" && can?(actor, :destroy, group)
socket =
socket
|> assign(:page_title, group.name)
|> assign(:group, group)
|> assign(:show_delete_modal, open_delete)
|> assign(:name_confirmation, "")
{:noreply, socket}
{:error, _error} ->
{:noreply,
@ -105,15 +111,6 @@ defmodule MvWeb.GroupLive.Show do
{gettext("Edit group")}
</.button>
<% end %>
<%= if can?(@current_user, :destroy, @group) do %>
<.button
variant="danger"
phx-click="open_delete_modal"
data-testid="group-show-delete-btn"
>
{gettext("Delete")}
</.button>
<% end %>
</:actions>
</.header>
@ -339,6 +336,32 @@ defmodule MvWeb.GroupLive.Show do
</div>
</div>
<%!-- Danger zone: canonical pattern (same as member show) --%>
<%= if can?(@current_user, :destroy, @group) do %>
<section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
<h2 id="danger-zone-heading" class="text-lg font-semibold mb-3 text-error">
{gettext("Danger zone")}
</h2>
<div class="border border-base-300 rounded-lg p-4 bg-base-100">
<p class="text-base-content/70 mb-4">
{gettext(
"Deleting this group cannot be undone. All member-group associations will be permanently removed."
)}
</p>
<.button
variant="danger"
type="button"
phx-click="open_delete_modal"
data-testid="group-show-delete-btn"
aria-label={gettext("Delete group %{name}", name: @group.name)}
>
<.icon name="hero-trash" class="size-4" />
{gettext("Delete group")}
</.button>
</div>
</section>
<% end %>
<%!-- Delete Confirmation Modal --%>
<%= if assigns[:show_delete_modal] do %>
<dialog id="delete-group-modal" class="modal modal-open" role="dialog">