feat: consistent and accessible modal on delete
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
2922a4d1ee
commit
e422e5f4ef
10 changed files with 424 additions and 102 deletions
|
|
@ -277,14 +277,7 @@ defmodule MvWeb.MemberLive.Form do
|
|||
<.button
|
||||
variant="danger"
|
||||
type="button"
|
||||
phx-click="delete"
|
||||
phx-value-id={@member.id}
|
||||
data-confirm={
|
||||
gettext(
|
||||
"Are you sure you want to delete %{name}? This action cannot be undone.",
|
||||
name: MvWeb.Helpers.MemberHelpers.display_name(@member)
|
||||
)
|
||||
}
|
||||
phx-click="open_delete_modal"
|
||||
data-testid="member-delete"
|
||||
aria-label={
|
||||
gettext("Delete member %{name}",
|
||||
|
|
@ -298,6 +291,40 @@ defmodule MvWeb.MemberLive.Form do
|
|||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<%!-- Delete Member Confirmation Modal (WCAG: focus in modal, keyboard confirm/cancel) --%>
|
||||
<%= if @member && assigns[:show_delete_modal] do %>
|
||||
<dialog id="delete-member-form-modal" class="modal modal-open" role="dialog" aria-labelledby="delete-member-form-modal-title">
|
||||
<div class="modal-box">
|
||||
<h3 id="delete-member-form-modal-title" class="text-lg font-bold">{gettext("Delete Member")}</h3>
|
||||
<p class="py-4">
|
||||
{gettext("Are you sure you want to delete %{name}? This action cannot be undone.",
|
||||
name: MvWeb.Helpers.MemberHelpers.display_name(@member)
|
||||
)}
|
||||
</p>
|
||||
<div class="modal-action">
|
||||
<.button
|
||||
type="button"
|
||||
variant="neutral"
|
||||
phx-click="cancel_delete_modal"
|
||||
phx-mounted={JS.focus()}
|
||||
id="delete-member-form-modal-cancel"
|
||||
aria-label={gettext("Cancel")}
|
||||
>
|
||||
{gettext("Cancel")}
|
||||
</.button>
|
||||
<.button
|
||||
type="button"
|
||||
variant="danger"
|
||||
phx-click={JS.push("delete", value: %{id: @member.id})}
|
||||
aria-label={gettext("Delete member")}
|
||||
>
|
||||
{gettext("Delete")}
|
||||
</.button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</.form>
|
||||
|
|
@ -348,6 +375,7 @@ defmodule MvWeb.MemberLive.Form do
|
|||
|> assign(:available_fee_types, available_fee_types)
|
||||
|> assign(:interval_warning, nil)
|
||||
|> assign(:member_field_required_map, member_field_required_map)
|
||||
|> assign_new(:show_delete_modal, fn -> false end)
|
||||
|> assign_form()}
|
||||
end
|
||||
|
||||
|
|
@ -419,6 +447,16 @@ defmodule MvWeb.MemberLive.Form do
|
|||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("open_delete_modal", _params, socket) do
|
||||
{:noreply, assign(socket, :show_delete_modal, true)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("cancel_delete_modal", _params, socket) do
|
||||
{:noreply, assign(socket, :show_delete_modal, false)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
member = socket.assigns.member
|
||||
|
|
@ -426,10 +464,16 @@ defmodule MvWeb.MemberLive.Form do
|
|||
|
||||
cond do
|
||||
is_nil(member) ->
|
||||
{:noreply, put_flash(socket, :error, gettext("Member not found"))}
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, gettext("Member not found"))
|
||||
|> assign(:show_delete_modal, false)}
|
||||
|
||||
to_string(id) != to_string(member.id) ->
|
||||
{:noreply, put_flash(socket, :error, gettext("Member not found"))}
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, gettext("Member not found"))
|
||||
|> assign(:show_delete_modal, false)}
|
||||
|
||||
true ->
|
||||
handle_member_delete_destroy(socket, member, actor)
|
||||
|
|
@ -446,11 +490,16 @@ defmodule MvWeb.MemberLive.Form do
|
|||
|
||||
{:error, %Ash.Error.Forbidden{}} ->
|
||||
{:noreply,
|
||||
put_flash(socket, :error, gettext("You do not have permission to delete this member"))}
|
||||
socket
|
||||
|> put_flash(:error, gettext("You do not have permission to delete this member"))
|
||||
|> assign(:show_delete_modal, false)}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.warning("Member delete failed: member_id=#{member.id} error=#{inspect(error)}")
|
||||
{:noreply, put_flash(socket, :error, format_destroy_error(error))}
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, format_destroy_error(error))
|
||||
|> assign(:show_delete_modal, false)}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -316,13 +316,7 @@ defmodule MvWeb.MemberLive.Show do
|
|||
</p>
|
||||
<.button
|
||||
variant="danger"
|
||||
phx-click="delete"
|
||||
phx-value-id={@member.id}
|
||||
data-confirm={
|
||||
gettext("Are you sure you want to delete %{name}? This action cannot be undone.",
|
||||
name: MvWeb.Helpers.MemberHelpers.display_name(@member)
|
||||
)
|
||||
}
|
||||
phx-click="open_delete_modal"
|
||||
data-testid="member-delete"
|
||||
aria-label={
|
||||
gettext("Delete member %{name}",
|
||||
|
|
@ -336,6 +330,40 @@ defmodule MvWeb.MemberLive.Show do
|
|||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<%!-- Delete Member Confirmation Modal (WCAG: focus in modal, keyboard confirm/cancel) --%>
|
||||
<%= if assigns[:show_delete_modal] do %>
|
||||
<dialog id="delete-member-modal" class="modal modal-open" role="dialog" aria-labelledby="delete-member-modal-title">
|
||||
<div class="modal-box">
|
||||
<h3 id="delete-member-modal-title" class="text-lg font-bold">{gettext("Delete Member")}</h3>
|
||||
<p class="py-4">
|
||||
{gettext("Are you sure you want to delete %{name}? This action cannot be undone.",
|
||||
name: MvWeb.Helpers.MemberHelpers.display_name(@member)
|
||||
)}
|
||||
</p>
|
||||
<div class="modal-action">
|
||||
<.button
|
||||
type="button"
|
||||
variant="neutral"
|
||||
phx-click="cancel_delete_modal"
|
||||
phx-mounted={JS.focus()}
|
||||
id="delete-member-modal-cancel"
|
||||
aria-label={gettext("Cancel")}
|
||||
>
|
||||
{gettext("Cancel")}
|
||||
</.button>
|
||||
<.button
|
||||
type="button"
|
||||
variant="danger"
|
||||
phx-click={JS.push("delete", value: %{id: @member.id})}
|
||||
aria-label={gettext("Delete member")}
|
||||
>
|
||||
{gettext("Delete")}
|
||||
</.button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
<% end %>
|
||||
</div>
|
||||
</Layouts.app>
|
||||
"""
|
||||
|
|
@ -346,7 +374,8 @@ defmodule MvWeb.MemberLive.Show do
|
|||
{:ok,
|
||||
socket
|
||||
|> assign(:active_tab, :contact)
|
||||
|> assign(:vereinfacht_receipts, nil)}
|
||||
|> assign(:vereinfacht_receipts, nil)
|
||||
|> assign_new(:show_delete_modal, fn -> false end)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
@ -398,13 +427,26 @@ defmodule MvWeb.MemberLive.Show do
|
|||
{:noreply, assign(socket, :active_tab, :membership_fees)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("open_delete_modal", _params, socket) do
|
||||
{:noreply, assign(socket, :show_delete_modal, true)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("cancel_delete_modal", _params, socket) do
|
||||
{:noreply, assign(socket, :show_delete_modal, false)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
member = socket.assigns.member
|
||||
actor = current_actor(socket)
|
||||
|
||||
if to_string(id) != to_string(member.id) do
|
||||
{:noreply, put_flash(socket, :error, gettext("Member not found"))}
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, gettext("Member not found"))
|
||||
|> assign(:show_delete_modal, false)}
|
||||
else
|
||||
case Ash.destroy(member, actor: actor) do
|
||||
:ok ->
|
||||
|
|
@ -415,16 +457,20 @@ defmodule MvWeb.MemberLive.Show do
|
|||
|
||||
{:error, %Ash.Error.Forbidden{}} ->
|
||||
{:noreply,
|
||||
put_flash(
|
||||
socket,
|
||||
socket
|
||||
|> put_flash(
|
||||
:error,
|
||||
gettext("You do not have permission to delete this member")
|
||||
)}
|
||||
)
|
||||
|> assign(:show_delete_modal, false)}
|
||||
|
||||
{:error, error} ->
|
||||
require Logger
|
||||
Logger.warning("Member delete failed: member_id=#{member.id} error=#{inspect(error)}")
|
||||
{:noreply, put_flash(socket, :error, format_error(error))}
|
||||
{:noreply,
|
||||
socket
|
||||
|> put_flash(:error, format_error(error))
|
||||
|> assign(:show_delete_modal, false)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -288,11 +288,18 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
<% end %>
|
||||
</.section_box>
|
||||
|
||||
<%!-- Edit Cycle Amount Modal --%>
|
||||
<%!-- Edit Cycle Amount Modal (WCAG: focus in modal, aria-labelledby) --%>
|
||||
<%= if @editing_cycle do %>
|
||||
<dialog id="edit-cycle-amount-modal" class="modal modal-open">
|
||||
<dialog
|
||||
id="edit-cycle-amount-modal"
|
||||
class="modal modal-open"
|
||||
role="dialog"
|
||||
aria-labelledby="edit-cycle-amount-modal-title"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">{gettext("Edit Cycle Amount")}</h3>
|
||||
<h3 id="edit-cycle-amount-modal-title" class="text-lg font-bold">
|
||||
{gettext("Edit Cycle Amount")}
|
||||
</h3>
|
||||
<form phx-submit="save_cycle_amount" phx-target={@myself}>
|
||||
<input type="hidden" name="cycle_id" value={@editing_cycle.id} />
|
||||
<div class="form-control w-full mt-4">
|
||||
|
|
@ -308,6 +315,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
value={Decimal.to_string(@editing_cycle.amount) |> String.replace(".", ",")}
|
||||
class="input input-bordered w-full"
|
||||
required
|
||||
phx-mounted={JS.focus()}
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
|
|
@ -326,11 +334,16 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
</dialog>
|
||||
<% end %>
|
||||
|
||||
<%!-- Delete Cycle Confirmation Modal --%>
|
||||
<%!-- Delete Cycle Confirmation Modal (WCAG: focus in modal, aria-labelledby) --%>
|
||||
<%= if @deleting_cycle do %>
|
||||
<dialog id="delete-cycle-modal" class="modal modal-open">
|
||||
<dialog
|
||||
id="delete-cycle-modal"
|
||||
class="modal modal-open"
|
||||
role="dialog"
|
||||
aria-labelledby="delete-cycle-modal-title"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">{gettext("Delete Cycle")}</h3>
|
||||
<h3 id="delete-cycle-modal-title" class="text-lg font-bold">{gettext("Delete Cycle")}</h3>
|
||||
<p class="py-4">
|
||||
{gettext("Are you sure you want to delete this cycle?")}
|
||||
</p>
|
||||
|
|
@ -341,7 +354,12 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
)} - {MembershipFeeHelpers.format_currency(@deleting_cycle.amount)}
|
||||
</p>
|
||||
<div class="modal-action">
|
||||
<.button variant="neutral" phx-click="cancel_delete_cycle" phx-target={@myself}>
|
||||
<.button
|
||||
variant="neutral"
|
||||
phx-click="cancel_delete_cycle"
|
||||
phx-target={@myself}
|
||||
phx-mounted={JS.focus()}
|
||||
>
|
||||
{gettext("Cancel")}
|
||||
</.button>
|
||||
<.button
|
||||
|
|
@ -357,11 +375,18 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
</dialog>
|
||||
<% end %>
|
||||
|
||||
<%!-- Delete All Cycles Confirmation Modal --%>
|
||||
<%!-- Delete All Cycles Confirmation Modal (WCAG: focus in modal, aria-labelledby) --%>
|
||||
<%= if @deleting_all_cycles do %>
|
||||
<dialog id="delete-all-cycles-modal" class="modal modal-open">
|
||||
<dialog
|
||||
id="delete-all-cycles-modal"
|
||||
class="modal modal-open"
|
||||
role="dialog"
|
||||
aria-labelledby="delete-all-cycles-modal-title"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold text-error">{gettext("Delete All Cycles")}</h3>
|
||||
<h3 id="delete-all-cycles-modal-title" class="text-lg font-bold text-error">
|
||||
{gettext("Delete All Cycles")}
|
||||
</h3>
|
||||
<div class="alert alert-warning mt-4">
|
||||
<.icon name="hero-exclamation-triangle" class="size-5" />
|
||||
<div>
|
||||
|
|
@ -389,6 +414,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
value={@delete_all_confirmation || ""}
|
||||
class="input input-bordered w-full"
|
||||
placeholder={gettext("Yes")}
|
||||
phx-mounted={JS.focus()}
|
||||
/>
|
||||
</div>
|
||||
<div class="modal-action">
|
||||
|
|
@ -411,11 +437,16 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
</dialog>
|
||||
<% end %>
|
||||
|
||||
<%!-- Create Cycle Modal --%>
|
||||
<%!-- Create Cycle Modal (WCAG: focus in modal, aria-labelledby) --%>
|
||||
<%= if @creating_cycle do %>
|
||||
<dialog id="create-cycle-modal" class="modal modal-open">
|
||||
<dialog
|
||||
id="create-cycle-modal"
|
||||
class="modal modal-open"
|
||||
role="dialog"
|
||||
aria-labelledby="create-cycle-modal-title"
|
||||
>
|
||||
<div class="modal-box">
|
||||
<h3 class="text-lg font-bold">{gettext("Create Cycle")}</h3>
|
||||
<h3 id="create-cycle-modal-title" class="text-lg font-bold">{gettext("Create Cycle")}</h3>
|
||||
<form phx-submit="create_cycle" phx-target={@myself}>
|
||||
<div class="form-control w-full mt-4">
|
||||
<label class="label" for="create-cycle-date">
|
||||
|
|
@ -431,6 +462,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
class="input input-bordered w-full"
|
||||
required
|
||||
aria-label={gettext("Date")}
|
||||
phx-mounted={JS.focus()}
|
||||
/>
|
||||
<label class="label">
|
||||
<span class="label-text-alt">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue