From d0fa3991f724463d9377a563fd2fbfbfa97461aa Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 18 Feb 2026 22:28:55 +0100 Subject: [PATCH] feat(vereinfacht): member form flash and show page - Form: show Vereinfacht sync warning after save via SyncFlash - Show: load API debug response; MembershipFees: contact ID, link, no-contact warning --- lib/mv_web/live/member_live/form.ex | 29 ++++++++ lib/mv_web/live/member_live/show.ex | 16 +++- .../show/membership_fees_component.ex | 74 ++++++++++++++++++- 3 files changed, 117 insertions(+), 2 deletions(-) diff --git a/lib/mv_web/live/member_live/form.ex b/lib/mv_web/live/member_live/form.ex index f9588c0..7c138c4 100644 --- a/lib/mv_web/live/member_live/form.ex +++ b/lib/mv_web/live/member_live/form.ex @@ -319,11 +319,40 @@ defmodule MvWeb.MemberLive.Form do socket = socket |> put_flash(:info, flash_message) + |> maybe_put_vereinfacht_sync_flash(member.id) |> push_navigate(to: return_path(socket.assigns.return_to, member)) {:noreply, socket} end + defp maybe_put_vereinfacht_sync_flash(socket, member_id) do + case Mv.Vereinfacht.SyncFlash.take(to_string(member_id)) do + {:warning, message} -> + put_flash(socket, :warning, translate_vereinfacht_flash(message)) + + {:ok, _message} -> + # Optionally show sync success; for now we keep only the main success message + socket + + nil -> + socket + end + end + + defp translate_vereinfacht_flash(message) when is_binary(message) do + prefix = "Vereinfacht: " + + if String.starts_with?(message, prefix) do + detail = message |> String.trim_leading(prefix) |> String.trim() + + Gettext.dgettext(MvWeb.Gettext, "default", "Vereinfacht: %{detail}", + detail: Gettext.dgettext(MvWeb.Gettext, "default", detail) + ) + else + Gettext.dgettext(MvWeb.Gettext, "default", message) + end + end + defp handle_save_error(socket, form) do # Always show a flash message when save fails # Field-level validation errors are displayed in form fields, but flash provides additional feedback diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex index 47e8878..93e18b4 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -256,6 +256,7 @@ defmodule MvWeb.MemberLive.Show do id={"membership-fees-#{@member.id}"} member={@member} current_user={@current_user} + vereinfacht_debug_response={@vereinfacht_debug_response} /> <% end %> @@ -264,7 +265,10 @@ defmodule MvWeb.MemberLive.Show do @impl true def mount(_params, _session, socket) do - {:ok, assign(socket, :active_tab, :contact)} + {:ok, + socket + |> assign(:active_tab, :contact) + |> assign(:vereinfacht_debug_response, nil)} end @impl true @@ -316,6 +320,16 @@ defmodule MvWeb.MemberLive.Show do {:noreply, assign(socket, :active_tab, :membership_fees)} end + def handle_event("load_vereinfacht_debug", %{"contact_id" => contact_id}, socket) do + response = + case Mv.Vereinfacht.Client.get_contact(contact_id) do + {:ok, body} -> {:ok, body} + {:error, reason} -> {:error, reason} + end + + {:noreply, assign(socket, :vereinfacht_debug_response, response)} + end + # Flash set in LiveComponent is not shown in parent layout; child sends this to display flash @impl true def handle_info({:put_flash, type, message}, socket) do diff --git a/lib/mv_web/live/member_live/show/membership_fees_component.ex b/lib/mv_web/live/member_live/show/membership_fees_component.ex index 0739b5e..ce14317 100644 --- a/lib/mv_web/live/member_live/show/membership_fees_component.ex +++ b/lib/mv_web/live/member_live/show/membership_fees_component.ex @@ -50,6 +50,60 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do <% end %> + <%!-- Vereinfacht: contact info when synced, or warning when API is configured but no contact --%> + <%= if Mv.Config.vereinfacht_configured?() do %> + <%= if @member.vereinfacht_contact_id do %> +
+ +
+ + {gettext("Contact ID: %{id}", id: @member.vereinfacht_contact_id)} + + <.link + :if={Mv.Config.vereinfacht_contact_view_url(@member.vereinfacht_contact_id)} + href={Mv.Config.vereinfacht_contact_view_url(@member.vereinfacht_contact_id)} + target="_blank" + rel="noopener noreferrer" + class="link link-primary inline-flex items-center gap-1" + > + {gettext("View contact in Vereinfacht")} + <.icon name="hero-arrow-top-right-on-square" class="inline-block size-4" /> + +
+ {gettext("Debug:")} + +
+ <%= if @vereinfacht_debug_response do %> +
+
<%= format_vereinfacht_debug_response(@vereinfacht_debug_response) %>
+
+ <% end %> +
+
+ <% else %> +
+

+ <.icon name="hero-exclamation-triangle" class="size-5 shrink-0" /> + {gettext("No Vereinfacht contact exists for this member.")} +

+

+ {gettext( + "Sync this member from Settings (Vereinfacht section) or save the member again to create the contact." + )} +

+
+ <% end %> + <% end %> + <%!-- Action Buttons (only when user has permission) --%>
<.button @@ -439,7 +493,8 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do |> assign_new(:creating_cycle, fn -> false end) |> assign_new(:create_cycle_date, fn -> nil end) |> assign_new(:create_cycle_error, fn -> nil end) - |> assign_new(:regenerating, fn -> false end)} + |> assign_new(:regenerating, fn -> false end) + |> assign_new(:vereinfacht_debug_response, fn -> nil end)} end @impl true @@ -997,6 +1052,23 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do defp format_create_cycle_period(_date, _interval), do: "" + defp format_vereinfacht_debug_response({:ok, body}) when is_map(body) do + Jason.encode!(body, pretty: true) + end + + defp format_vereinfacht_debug_response({:error, {:http, status, detail}}) + when is_binary(detail) do + "Error: HTTP #{status} – #{detail}" + end + + defp format_vereinfacht_debug_response({:error, {:http, status, _}}) do + "Error: HTTP #{status}" + end + + defp format_vereinfacht_debug_response({:error, reason}) do + "Error: " <> inspect(reason) + end + # Helper component for section box attr :title, :string, required: true slot :inner_block, required: true