- <.section_box title={gettext("Personal Data")}>
-
- <%!-- Name Row --%>
-
- <.data_field
- label={gettext("First Name")}
- value={@member.first_name}
- class="w-48"
- />
- <.data_field label={gettext("Last Name")} value={@member.last_name} class="w-48" />
-
+
@@ -328,6 +394,35 @@ defmodule MvWeb.MemberLive.Show do
{:noreply, assign(socket, :active_tab, :membership_fees)}
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"))}
+ else
+ case Ash.destroy(member, actor: actor) do
+ :ok ->
+ {:noreply,
+ socket
+ |> put_flash(:success, gettext("Member deleted successfully"))
+ |> push_navigate(to: ~p"/members")}
+
+ {:error, %Ash.Error.Forbidden{}} ->
+ {:noreply,
+ put_flash(
+ socket,
+ :error,
+ gettext("You do not have permission to delete this member")
+ )}
+
+ {:error, error} ->
+ {:noreply, put_flash(socket, :error, format_error(error))}
+ end
+ end
+ end
+
def handle_event("load_vereinfacht_receipts", %{"contact_id" => contact_id}, socket) do
response =
case Mv.Vereinfacht.Client.get_contact_with_receipts(contact_id) do
@@ -358,6 +453,19 @@ defmodule MvWeb.MemberLive.Show do
defp page_title(:show), do: gettext("Show Member")
defp page_title(:edit), do: gettext("Edit Member")
+ defp format_error(%Ash.Error.Invalid{errors: errors}) do
+ error_messages =
+ Enum.map(errors, fn
+ %{field: field, message: message} -> "#{field}: #{message}"
+ %{message: message} -> message
+ _ -> inspect(errors)
+ end)
+
+ Enum.join(error_messages, ", ")
+ end
+
+ defp format_error(error), do: inspect(error)
+
# -----------------------------------------------------------------
# Helper Components
# -----------------------------------------------------------------
diff --git a/test/mv_web/member_live/form_error_handling_test.exs b/test/mv_web/member_live/form_error_handling_test.exs
index d61d3fd..fec7df4 100644
--- a/test/mv_web/member_live/form_error_handling_test.exs
+++ b/test/mv_web/member_live/form_error_handling_test.exs
@@ -3,11 +3,38 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
Tests for error handling in the member form, specifically flash message display.
"""
use MvWeb.ConnCase, async: false
+ use Gettext, backend: MvWeb.Gettext
import Phoenix.LiveViewTest
require Ash.Query
+ describe "tab visibility" do
+ @tag :ui
+ test "Payments tab is not visible on new member form", %{conn: conn} do
+ conn = conn_with_oidc_user(conn)
+ {:ok, _view, html} = live(conn, "/members/new")
+
+ refute html =~ gettext("Payments")
+ end
+
+ @tag :ui
+ test "Payments tab is not visible on edit member form", %{conn: conn} do
+ system_actor = Mv.Helpers.SystemActor.get_system_actor()
+
+ {:ok, member} =
+ Mv.Membership.create_member(
+ %{first_name: "Edit", last_name: "Member", email: "edit@example.com"},
+ actor: system_actor
+ )
+
+ conn = conn_with_oidc_user(conn)
+ {:ok, _view, html} = live(conn, ~p"/members/#{member}/edit")
+
+ refute html =~ gettext("Payments")
+ end
+ end
+
describe "error handling - flash messages" do
setup do
{:ok, settings} = Mv.Membership.get_settings()
diff --git a/test/mv_web/member_live/index_test.exs b/test/mv_web/member_live/index_test.exs
index 53a2815..d8846ea 100644
--- a/test/mv_web/member_live/index_test.exs
+++ b/test/mv_web/member_live/index_test.exs
@@ -266,36 +266,42 @@ defmodule MvWeb.MemberLive.IndexTest do
assert is_list(state.socket.assigns.members)
end
- test "can delete a member without error", %{conn: conn} do
+ @tag :ui
+ test "member index does not render Edit or Delete actions", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
- # Create a test member first
- {:ok, member} =
+ {:ok, _member} =
Mv.Membership.create_member(
- %{
- first_name: "Test",
- last_name: "User",
- email: "test@example.com"
- },
+ %{first_name: "Test", last_name: "User", email: "test@example.com"},
actor: system_actor
)
conn = conn_with_oidc_user(conn)
- {:ok, index_view, _html} = live(conn, "/members")
+ {:ok, view, html} = live(conn, "/members")
- # Verify the member is displayed
- assert has_element?(index_view, "#members", "Test User")
+ refute has_element?(view, "[data-testid='member-edit']")
+ refute html =~ ~s(data-testid="member-delete")
+ end
- # Click the delete link for this member
- index_view
- |> element("a", "Delete")
+ @tag :ui
+ test "row click navigates to member show", %{conn: conn} do
+ system_actor = Mv.Helpers.SystemActor.get_system_actor()
+
+ {:ok, member} =
+ Mv.Membership.create_member(
+ %{first_name: "Row", last_name: "Click", email: "rowclick@example.com"},
+ actor: system_actor
+ )
+
+ conn = conn_with_oidc_user(conn)
+ {:ok, view, _html} = live(conn, "/members")
+
+ # Click a data cell (e.g. second column = first name) to trigger row navigation
+ view
+ |> element("#row-#{member.id} td:nth-child(2)")
|> render_click()
- # Verify the member is no longer displayed
- refute has_element?(index_view, "#members", "Test User")
-
- # Verify the member was actually deleted from the database
- assert not (Mv.Membership.Member |> Ash.Query.filter(id == ^member.id) |> Ash.exists?())
+ assert_redirect(view, ~p"/members/#{member}")
end
describe "copy_emails feature" do
diff --git a/test/mv_web/member_live/show_test.exs b/test/mv_web/member_live/show_test.exs
index 26c3f00..8c7a23a 100644
--- a/test/mv_web/member_live/show_test.exs
+++ b/test/mv_web/member_live/show_test.exs
@@ -134,6 +134,35 @@ defmodule MvWeb.MemberLive.ShowTest do
end
end
+ describe "delete action" do
+ test "renders Delete button when user can destroy member", %{
+ conn: conn,
+ member: member
+ } do
+ conn = conn_with_oidc_user(conn)
+ {:ok, view, _html} = live(conn, ~p"/members/#{member}")
+
+ assert has_element?(view, "[data-testid='member-delete']")
+ end
+
+ test "delete event removes member and redirects to index", %{
+ conn: conn,
+ member: member
+ } do
+ conn = conn_with_oidc_user(conn)
+ {:ok, view, _html} = live(conn, ~p"/members/#{member}")
+
+ view
+ |> render_click("delete", %{"id" => member.id})
+
+ assert_redirect(view, ~p"/members")
+
+ refute Mv.Membership.Member
+ |> Ash.Query.filter(id == ^member.id)
+ |> Ash.exists?()
+ end
+ end
+
describe "custom field value formatting" do
test "formats string custom field values", %{conn: conn, member: member, actor: actor} do
{:ok, custom_field} =