From ba08434604c12a81bc382ac336c29dc37bf9c679 Mon Sep 17 00:00:00 2001 From: carla Date: Thu, 12 Mar 2026 15:59:53 +0100 Subject: [PATCH 1/5] style: consistent save buttons and active tab --- DESIGN_GUIDELINES.md | 27 +- assets/css/app.css | 11 +- .../field_visibility_dropdown_component.ex | 1 + .../live/custom_field_live/form_component.ex | 25 +- lib/mv_web/live/group_live/form.ex | 21 +- lib/mv_web/live/group_live/show.ex | 484 +++++++++--------- .../live/member_field_live/form_component.ex | 11 +- lib/mv_web/live/member_live/form.ex | 17 +- lib/mv_web/live/member_live/index.html.heex | 14 +- .../live/membership_fee_settings_live.ex | 28 +- .../live/membership_fee_type_live/form.ex | 29 +- lib/mv_web/live/role_live/form.ex | 19 +- lib/mv_web/live/role_live/show.ex | 98 ++-- lib/mv_web/live/user_live/form.ex | 33 +- lib/mv_web/live/user_live/show.ex | 106 ++-- 15 files changed, 486 insertions(+), 438 deletions(-) diff --git a/DESIGN_GUIDELINES.md b/DESIGN_GUIDELINES.md index 92f7a90..0a10a70 100644 --- a/DESIGN_GUIDELINES.md +++ b/DESIGN_GUIDELINES.md @@ -46,14 +46,14 @@ Every authenticated page should follow the same structure: **MUST:** Use `<.header>` on every page (except login/public pages). **SHOULD:** Put short explanations into `<:subtitle>` rather than sprinkling random text blocks. -### 2.2 Edit/New form header: Back button left (mandatory) +### 2.2 Edit/New form header and footer buttons (mandatory) For LiveViews that render an edit or new form (e.g. member, group, role, user, custom field, membership fee type): -- **MUST:** Provide a **Back** button on the **left** side of the header using the `<:leading>` slot (same as data fields: Back left, title next, primary action on the right). +- **MUST:** Provide a **Back** button on the **left** side of the header using the `<:leading>` slot (same as data fields: Back left, title next). - **MUST:** Use the same pattern everywhere: Back button with `variant="neutral"`, arrow-left icon, and label “Back”. It navigates to the previous context (e.g. detail page or index) via a `return_path`-style helper. -- **SHOULD:** Place the primary action (e.g. “Save”) in `<:actions>` on the right. -- **Rationale:** Users expect a consistent way to leave the form without submitting; Back left matches the data fields edit view and keeps primary actions on the right. +- **MUST:** Place **exactly one** form button bar **below all form fields**, inside the `<.form>`, with: **Abbrechen** (Cancel) left, **Speichern** (Save) right. Use `gettext("Cancel")`, `gettext("Save ")`, `phx-disable-with={gettext("Saving...")}` on the submit button. No submit button in the header; no duplicate submit buttons. +- **Rationale:** Users expect a consistent way to leave the form without submitting; Back left. One primary action (Save) per form, in the footer, avoids double submits and matches the reference (member edit form). **Template for form pages:** ```heex @@ -66,15 +66,20 @@ For LiveViews that render an edit or new form (e.g. member, group, role, user, c Page title (e.g. “Edit Member” or “New User”) <:subtitle>Short explanation. - <:actions> - <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save")} - - -``` -If the `<.header>` is outside the `<.form>`, the submit button must reference the form via the `form` attribute (e.g. `form="user-form"`). +<.form for={@form} id="..." phx-change="validate" phx-submit="save"> + <%!-- form sections and fields --%> +
+ <.button navigate={return_path(@return_to, @resource)} variant="neutral" type="button"> + {gettext("Abbrechen")} + + <.button type="submit" phx-disable-with={gettext("Speichern...")} variant="primary"> + {gettext("Speichern")} + +
+ +``` ## 3) Typography (system) diff --git a/assets/css/app.css b/assets/css/app.css index e3c6e83..4118f09 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -577,7 +577,9 @@ } /* ============================================ - WCAG 2.2 AA: Tab list inactive tab text contrast (4.5:1) + Member detail tabs (show + edit): inactive vs active contrast + WCAG 2.2 AA: inactive tab text contrast (4.5:1) + Active tab: visible border (DaisyUI tabs-bordered) and weight so which tab is selected is clear. ============================================ */ #member-tablist .tab:not(.tab-active) { color: oklch(0.35 0.02 285); @@ -586,6 +588,13 @@ color: oklch(0.72 0.02 257); } +/* Active tab: stronger underline (DaisyUI --tab-border-color) and font weight */ +#member-tablist .tab.tab-active, +#member-tablist .tab[aria-selected="true"] { + --tab-border-color: var(--color-base-content); + font-weight: 600; +} + /* ============================================ WCAG 2.2 AA: Link contrast - primary and accent ============================================ */ diff --git a/lib/mv_web/live/components/field_visibility_dropdown_component.ex b/lib/mv_web/live/components/field_visibility_dropdown_component.ex index 58777da..f31d914 100644 --- a/lib/mv_web/live/components/field_visibility_dropdown_component.ex +++ b/lib/mv_web/live/components/field_visibility_dropdown_component.ex @@ -70,6 +70,7 @@ defmodule MvWeb.Components.FieldVisibilityDropdownComponent do id="field-visibility-menu" icon="hero-adjustments-horizontal" button_label={gettext("Show/Hide Columns")} + button_class="btn-secondary" items={@all_items} checkboxes={true} selected={@selected_fields} diff --git a/lib/mv_web/live/custom_field_live/form_component.ex b/lib/mv_web/live/custom_field_live/form_component.ex index 2e98aeb..67e407b 100644 --- a/lib/mv_web/live/custom_field_live/form_component.ex +++ b/lib/mv_web/live/custom_field_live/form_component.ex @@ -19,7 +19,7 @@ defmodule MvWeb.CustomFieldLive.FormComponent do @impl true def render(assigns) do ~H""" -
+
<.button @@ -98,6 +98,20 @@ defmodule MvWeb.CustomFieldLive.FormComponent do label={gettext("Show in overview")} /> + <%!-- Buttons: below all form fields, above Danger zone --%> +
+ <.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}> + {gettext("Cancel")} + + <.button + type="submit" + phx-disable-with={gettext("Saving...")} + variant="primary" + > + {gettext("Save Data Field")} + +
+ <%= if @custom_field do %> <%!-- Danger zone: canonical pattern (same as member form) --%>
@@ -125,15 +139,6 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
<% end %> - -
- <.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}> - {gettext("Cancel")} - - <.button phx-disable-with={gettext("Saving...")} variant="primary"> - {gettext("Save Data Field")} - -
diff --git a/lib/mv_web/live/group_live/form.ex b/lib/mv_web/live/group_live/form.ex index 2e79a7f..d47ebff 100644 --- a/lib/mv_web/live/group_live/form.ex +++ b/lib/mv_web/live/group_live/form.ex @@ -77,7 +77,7 @@ defmodule MvWeb.GroupLive.Form do def render(assigns) do ~H""" - <.form for={@form} id="group-form" phx-change="validate" phx-submit="save"> + <.form class="max-w-2xl" for={@form} id="group-form" phx-change="validate" phx-submit="save"> <.header> <:leading> <.button navigate={return_path(@return_to, @group)} variant="neutral"> @@ -86,11 +86,6 @@ defmodule MvWeb.GroupLive.Form do {@page_title} - <:actions> - <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save")} - -
@@ -104,6 +99,20 @@ defmodule MvWeb.GroupLive.Form do />
+ <%!-- Buttons: below all form fields, Abbrechen left (secondary), Speichern right (primary) --%> +
+ <.button navigate={return_path(@return_to, @group)} variant="neutral" type="button"> + {gettext("Cancel")} + + <.button + type="submit" + phx-disable-with={gettext("Saving...")} + variant="primary" + > + {gettext("Save group")} + +
+ <%!-- Danger zone: canonical pattern (same as member form) --%> <%= if @group && can?(@current_user, :destroy, @group) do %>
diff --git a/lib/mv_web/live/group_live/show.ex b/lib/mv_web/live/group_live/show.ex index 80c0df5..d65392d 100644 --- a/lib/mv_web/live/group_live/show.ex +++ b/lib/mv_web/live/group_live/show.ex @@ -118,259 +118,261 @@ defmodule MvWeb.GroupLive.Show do
- <%!-- Group Information --%> -
-
-

{gettext("Description")}

-
- <%= if @group.description && String.trim(@group.description) != "" do %> -

{@group.description}

- <% else %> -

{gettext("No description")}

- <% end %> +
+ <%!-- Group Information --%> +
+
+

{gettext("Description")}

+
+ <%= if @group.description && String.trim(@group.description) != "" do %> +

{@group.description}

+ <% else %> +

{gettext("No description")}

+ <% end %> +
-
-
-

{gettext("Members")}

-
-

- {ngettext( - "Total: %{count} member", - "Total: %{count} members", - @group.member_count || 0, - count: @group.member_count || 0 - )} -

- - <%= if can?(@current_user, :update, @group) do %> -
- <%= if assigns[:show_add_member_input] do %> -
-
-
-
- <%= for member <- @selected_members do %> - <.badge - variant="primary" - style="outline" - class="flex items-center gap-1" - > - {MvWeb.Helpers.MemberHelpers.display_name(member)} - <.tooltip content={gettext("Remove")} position="top"> - <.button - type="button" - variant="icon" - size="sm" - phx-click="remove_selected_member" - phx-value-member_id={member.id} - aria-label={ - gettext("Remove %{name}", - name: MvWeb.Helpers.MemberHelpers.display_name(member) - ) - } - class="p-0 h-4 w-4 min-h-0" - > - <.icon name="hero-x-mark" class="size-3" /> - - - - <% end %> - -
- - <%= if length(@available_members) > 0 do %> -
- <%= for {member, index} <- Enum.with_index(@available_members) do %> -
-

- {MvWeb.Helpers.MemberHelpers.display_name(member)} -

-

- {member.email || gettext("No email")} -

-
- <% end %> -
- <% end %> -
-
- <.button - type="button" - variant="primary" - phx-click="add_selected_members" - data-testid="group-show-add-selected-members-btn" - disabled={Enum.empty?(@selected_member_ids)} - aria-label={gettext("Add members")} - class="join-item" - > - <.icon name="hero-plus" class="size-5" /> - - <.button - type="button" - variant="neutral" - phx-click="hide_add_member_input" - aria-label={gettext("Cancel")} - class="join-item" - > - {gettext("Cancel")} - -
- <% else %> - <.button - variant="primary" - phx-click="show_add_member_input" - aria-label={gettext("Add Member")} - > - {gettext("Add Member")} - - <% end %> -
- <% end %> - - <%= if Enum.empty?(@group.members || []) do %> -

- {gettext("No members in this group")} +

+

{gettext("Members")}

+
+

+ {ngettext( + "Total: %{count} member", + "Total: %{count} members", + @group.member_count || 0, + count: @group.member_count || 0 + )}

- <% else %> -
- - - - - - <%= if can?(@current_user, :update, @group) do %> - - <% end %> - - - - <%= for member <- @group.members do %> - - - - <%= if can?(@current_user, :update, @group) do %> - + {MvWeb.Helpers.MemberHelpers.display_name(member)} + <.tooltip content={gettext("Remove")} position="top"> + <.button + type="button" + variant="icon" + size="sm" + phx-click="remove_selected_member" + phx-value-member_id={member.id} + aria-label={ + gettext("Remove %{name}", + name: MvWeb.Helpers.MemberHelpers.display_name(member) + ) + } + class="p-0 h-4 w-4 min-h-0" + > + <.icon name="hero-x-mark" class="size-3" /> + + + + <% end %> + + + + <%= if length(@available_members) > 0 do %> +
+ <%= for {member, index} <- Enum.with_index(@available_members) do %> +
+

+ {MvWeb.Helpers.MemberHelpers.display_name(member)} +

+

+ {member.email || gettext("No email")} +

+
+ <% end %> +
+ <% end %> + + + <.button + type="button" + variant="primary" + phx-click="add_selected_members" + data-testid="group-show-add-selected-members-btn" + disabled={Enum.empty?(@selected_member_ids)} + aria-label={gettext("Add members")} + class="join-item" + > + <.icon name="hero-plus" class="size-5" /> + + <.button + type="button" + variant="neutral" + phx-click="hide_add_member_input" + aria-label={gettext("Cancel")} + class="join-item" + > + {gettext("Cancel")} + + + <% else %> + <.button + variant="primary" + phx-click="show_add_member_input" + aria-label={gettext("Add Member")} + > + {gettext("Add Member")} + + <% end %> + + <% end %> + + <%= if Enum.empty?(@group.members || []) do %> +

+ {gettext("No members in this group")} +

+ <% else %> +
+
{gettext("Name")}{gettext("Email")}{gettext("Actions")}
- <.link - navigate={~p"/members/#{member.id}"} - class="link link-primary" - > - {MvWeb.Helpers.MemberHelpers.display_name(member)} - - - <.maybe_value value={member.email} empty_sr_text={gettext("No email")}> - - {member.email} - - - - <.tooltip content={gettext("Remove")} position="left"> - <.button - type="button" - variant="danger" - size="sm" - phx-click="remove_member" - phx-value-member_id={member.id} - data-testid="group-show-remove-member" - aria-label={gettext("Remove member from group")} + + <%= if can?(@current_user, :update, @group) do %> +
+ <%= if assigns[:show_add_member_input] do %> +
+
+
+
+ <%= for member <- @selected_members do %> + <.badge + variant="primary" + style="outline" + class="flex items-center gap-1" > - <.icon name="hero-trash" class="size-4" /> - - -
+ + + + + <%= if can?(@current_user, :update, @group) do %> + <% end %> - <% end %> - -
{gettext("Name")}{gettext("Email")}{gettext("Actions")}
-
- <% end %> + + + <%= for member <- @group.members do %> + + + <.link + navigate={~p"/members/#{member.id}"} + class="link link-primary" + > + {MvWeb.Helpers.MemberHelpers.display_name(member)} + + + + <.maybe_value value={member.email} empty_sr_text={gettext("No email")}> + + {member.email} + + + + <%= if can?(@current_user, :update, @group) do %> + + <.tooltip content={gettext("Remove")} position="left"> + <.button + type="button" + variant="danger" + size="sm" + phx-click="remove_member" + phx-value-member_id={member.id} + data-testid="group-show-remove-member" + aria-label={gettext("Remove member from group")} + > + <.icon name="hero-trash" class="size-4" /> + + + + <% end %> + + <% end %> + + +
+ <% end %> +
-
- <%!-- Danger zone: canonical pattern (same as member show) --%> - <%= if can?(@current_user, :destroy, @group) do %> -
-

- {gettext("Danger zone")} -

-
-

- {gettext( - "Deleting this group cannot be undone. All member-group associations will be permanently removed." - )} -

- <.button - id="delete-group-trigger" - 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")} - -
-
- <% end %> + <%!-- Danger zone: same width as form (max-w-2xl) --%> + <%= if can?(@current_user, :destroy, @group) do %> +
+

+ {gettext("Danger zone")} +

+
+

+ {gettext( + "Deleting this group cannot be undone. All member-group associations will be permanently removed." + )} +

+ <.button + id="delete-group-trigger" + 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")} + +
+
+ <% end %> +
<%!-- Delete Confirmation Modal (WCAG: focus in modal, aria-labelledby) --%> <%= if assigns[:show_delete_modal] do %> diff --git a/lib/mv_web/live/member_field_live/form_component.ex b/lib/mv_web/live/member_field_live/form_component.ex index 84889e5..400c777 100644 --- a/lib/mv_web/live/member_field_live/form_component.ex +++ b/lib/mv_web/live/member_field_live/form_component.ex @@ -166,12 +166,17 @@ defmodule MvWeb.MemberFieldLive.FormComponent do />
-
+ <%!-- Buttons: below all form fields, Cancel left (secondary), Speichern right (primary) --%> +
<.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}> {gettext("Cancel")} - <.button phx-disable-with={gettext("Saving...")} variant="primary"> - {gettext("Save Field")} + <.button + type="submit" + phx-disable-with={gettext("Saving...")} + variant="primary" + > + {gettext("Save datafield")}
diff --git a/lib/mv_web/live/member_live/form.ex b/lib/mv_web/live/member_live/form.ex index 6d187fa..bc7e7d1 100644 --- a/lib/mv_web/live/member_live/form.ex +++ b/lib/mv_web/live/member_live/form.ex @@ -63,6 +63,7 @@ defmodule MvWeb.MemberLive.Form do
<%!-- Tab navigation: same styling as member show; only Contact Data tab (no Membership Fees on edit) --%>
@@ -259,13 +260,17 @@ defmodule MvWeb.MemberLive.Form do
- <%!-- Bottom Action Buttons --%> -
- <.button navigate={return_path(@return_to, @member)} variant="neutral" type="button"> + <%!-- Buttons: below all form fields, Cancel left (secondary), Speichern right (primary) --%> +
+ <.link navigate={return_path(@return_to, @member)} class="btn btn-secondary"> {gettext("Cancel")} - - <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save Member")} + + <.button + type="submit" + phx-disable-with={gettext("Saving...")} + class="btn btn-primary" + > + {gettext("Save member")}
diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index b35d426..c9e8960 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -44,6 +44,13 @@ query={@query} placeholder={gettext("Search...")} /> + <.live_component + module={MvWeb.Components.FieldVisibilityDropdownComponent} + id="field-visibility-dropdown" + all_fields={@all_available_fields} + custom_fields={@all_custom_fields} + selected_fields={@user_field_selection} + /> <.live_component module={MvWeb.Components.MemberFilterComponent} id="member-filter" @@ -86,13 +93,6 @@ - <.live_component - module={MvWeb.Components.FieldVisibilityDropdownComponent} - id="field-visibility-dropdown" - all_fields={@all_available_fields} - custom_fields={@all_custom_fields} - selected_fields={@user_field_selection} - />
<%!-- On desktop (lg:), only the table area scrolls; header and filters stay visible. On mobile, normal flow. --%> diff --git a/lib/mv_web/live/membership_fee_settings_live.ex b/lib/mv_web/live/membership_fee_settings_live.ex index f95fa8a..694aad4 100644 --- a/lib/mv_web/live/membership_fee_settings_live.ex +++ b/lib/mv_web/live/membership_fee_settings_live.ex @@ -144,11 +144,6 @@ defmodule MvWeb.MembershipFeeSettingsLive do <:subtitle> {gettext("Configure fee types for membership fees.")} - <:actions> - <.button variant="primary" navigate={~p"/membership_fee_settings/new_fee_type"}> - <.icon name="hero-plus" /> {gettext("New Membership Fee Type")} - - <%!-- One card: default setting + fee types table --%> @@ -220,13 +215,6 @@ defmodule MvWeb.MembershipFeeSettingsLive do <% end %> <% end %> - -
- <.button type="submit" variant="primary"> - <.icon name="hero-check" class="size-5" /> - {gettext("Save Settings")} - -
    @@ -237,12 +225,24 @@ defmodule MvWeb.MembershipFeeSettingsLive do )}
+ + <%!-- Save button: below default settings form, no icon (consistent with other Save buttons) --%> +
+ <.button type="submit" phx-disable-with={gettext("Saving...")} variant="primary"> + {gettext("Save default settings")} + +
- <%!-- Fee types table: row click opens edit --%> -

{gettext("Membership Fee Types")}

+ <%!-- Fee types section: heading and "New" button on same line --%> +
+

{gettext("Membership Fee Types")}

+ <.button variant="primary" navigate={~p"/membership_fee_settings/new_fee_type"}> + <.icon name="hero-plus" /> {gettext("New Membership Fee Type")} + +
<.table id="membership_fee_types" rows={@membership_fee_types} diff --git a/lib/mv_web/live/membership_fee_type_live/form.ex b/lib/mv_web/live/membership_fee_type_live/form.ex index 215bc2f..60ff9aa 100644 --- a/lib/mv_web/live/membership_fee_type_live/form.ex +++ b/lib/mv_web/live/membership_fee_type_live/form.ex @@ -34,16 +34,6 @@ defmodule MvWeb.MembershipFeeTypeLive.Form do {@page_title} - <:actions> - <.button - form="membership-fee-type-form" - phx-disable-with={gettext("Saving...")} - variant="primary" - type="submit" - > - {gettext("Save")} - - <.form @@ -139,13 +129,22 @@ defmodule MvWeb.MembershipFeeTypeLive.Form do rows="3" /> -
- <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save Membership Fee Type")} - - <.button navigate={return_path(@return_to, @membership_fee_type)} type="button"> + <%!-- Buttons: below all form fields, Cancel left (secondary), Speichern right (primary) --%> +
+ <.button + navigate={return_path(@return_to, @membership_fee_type)} + variant="neutral" + type="button" + > {gettext("Cancel")} + <.button + type="submit" + phx-disable-with={gettext("Saving...")} + variant="primary" + > + {gettext("Save membership fee type")} +
diff --git a/lib/mv_web/live/role_live/form.ex b/lib/mv_web/live/role_live/form.ex index f3b1663..fd783cc 100644 --- a/lib/mv_web/live/role_live/form.ex +++ b/lib/mv_web/live/role_live/form.ex @@ -30,11 +30,6 @@ defmodule MvWeb.RoleLive.Form do {@page_title} - <:actions> - <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save")} - -
@@ -85,6 +80,20 @@ defmodule MvWeb.RoleLive.Form do <% end %>
+ + <%!-- Buttons: below all form fields, Cancel left (secondary), Speichern right (primary) --%> +
+ <.button navigate={return_path(@return_to, @role)} variant="neutral" type="button"> + {gettext("Cancel")} + + <.button + type="submit" + phx-disable-with={gettext("Saving...")} + variant="primary" + > + {gettext("Save role")} + +
""" diff --git a/lib/mv_web/live/role_live/show.ex b/lib/mv_web/live/role_live/show.ex index a5402c4..5553d93 100644 --- a/lib/mv_web/live/role_live/show.ex +++ b/lib/mv_web/live/role_live/show.ex @@ -223,55 +223,57 @@ defmodule MvWeb.RoleLive.Show do phx-hook="FocusRestore" phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil} > - <.list> - <:item title={gettext("Name")}>{@role.name} - <:item title={gettext("Description")}> - <%= if @role.description do %> - {@role.description} - <% else %> - {gettext("No description")} - <% end %> - - <:item title={gettext("Permission Set")}> - <.badge variant={permission_set_badge_variant(@role.permission_set_name)}> - {@role.permission_set_name} - - - <:item title={gettext("System Role")}> - <.badge :if={@role.is_system_role} variant="warning"> - {gettext("Yes")} - - <.badge :if={!@role.is_system_role} variant="neutral"> - {gettext("No")} - - - +
+ <.list> + <:item title={gettext("Name")}>{@role.name} + <:item title={gettext("Description")}> + <%= if @role.description do %> + {@role.description} + <% else %> + {gettext("No description")} + <% end %> + + <:item title={gettext("Permission Set")}> + <.badge variant={permission_set_badge_variant(@role.permission_set_name)}> + {@role.permission_set_name} + + + <:item title={gettext("System Role")}> + <.badge :if={@role.is_system_role} variant="warning"> + {gettext("Yes")} + + <.badge :if={!@role.is_system_role} variant="neutral"> + {gettext("No")} + + + - <%!-- Danger zone: canonical pattern (same as member show) --%> - <%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not @role.is_system_role do %> -
-

- {gettext("Danger zone")} -

-
-

- {gettext( - "Deleting this role cannot be undone. Users assigned to this role must be reassigned first." - )} -

- <.button - id="delete-role-trigger" - variant="danger" - phx-click="open_delete_modal" - data-testid="role-delete" - aria-label={gettext("Delete role %{name}", name: @role.name)} - > - <.icon name="hero-trash" class="size-4" /> - {gettext("Delete role")} - -
-
- <% end %> + <%!-- Danger zone: canonical pattern (same as member show) --%> + <%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not @role.is_system_role do %> +
+

+ {gettext("Danger zone")} +

+
+

+ {gettext( + "Deleting this role cannot be undone. Users assigned to this role must be reassigned first." + )} +

+ <.button + id="delete-role-trigger" + variant="danger" + phx-click="open_delete_modal" + data-testid="role-delete" + aria-label={gettext("Delete role %{name}", name: @role.name)} + > + <.icon name="hero-trash" class="size-4" /> + {gettext("Delete role")} + +
+
+ <% end %> +
<%!-- Delete Role Confirmation Modal (WCAG: focus moves into modal, keyboard confirm/cancel) --%> <%= if assigns[:show_delete_modal] do %> diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex index 5232ec7..b910353 100644 --- a/lib/mv_web/live/user_live/form.ex +++ b/lib/mv_web/live/user_live/form.ex @@ -60,16 +60,6 @@ defmodule MvWeb.UserLive.Form do {@page_title} - <:actions> - <.button - form="user-form" - phx-disable-with={gettext("Saving...")} - variant="primary" - type="submit" - > - {gettext("Save User")} - -
<% end %> + <%!-- Buttons: below all form fields, above Danger zone --%> +
+ <.button navigate={return_path(@return_to, @user)} variant="neutral" type="button"> + {gettext("Cancel")} + + <.button + type="submit" + phx-disable-with={gettext("Saving...")} + variant="primary" + > + {gettext("Save user")} + +
+ <%!-- Danger zone: canonical pattern (same as member form) --%> <%= if @user && can?(@current_user, :destroy, @user) && !SystemActor.system_user?(@user) do %>
@@ -378,15 +382,6 @@ defmodule MvWeb.UserLive.Form do
<% end %> - -
- <.button navigate={return_path(@return_to, @user)} variant="neutral"> - {gettext("Cancel")} - - <.button phx-disable-with={gettext("Saving...")} variant="primary"> - {gettext("Save User")} - -
diff --git a/lib/mv_web/live/user_live/show.ex b/lib/mv_web/live/user_live/show.ex index 72070d8..56c0549 100644 --- a/lib/mv_web/live/user_live/show.ex +++ b/lib/mv_web/live/user_live/show.ex @@ -67,59 +67,61 @@ defmodule MvWeb.UserLive.Show do phx-hook="FocusRestore" phx-window-keydown={if @show_delete_modal, do: "window_keydown", else: nil} > - <.list> - <:item title={gettext("Email")}>{@user.email} - <:item title={gettext("Role")}>{@user.role.name} - <:item title={gettext("Password Authentication")}> - {if MvWeb.Helpers.UserHelpers.has_password?(@user), - do: gettext("Enabled"), - else: gettext("Not enabled")} - - <:item title={gettext("OIDC")}> - {if MvWeb.Helpers.UserHelpers.has_oidc?(@user), - do: gettext("Linked"), - else: gettext("Not linked")} - - <:item title={gettext("Linked Member")}> - <%= if @user.member do %> - <.link - navigate={~p"/members/#{@user.member}"} - class="text-blue-600 underline hover:text-blue-800" - > - <.icon name="hero-users" class="inline w-4 h-4 mr-1" /> - {MvWeb.Helpers.MemberHelpers.display_name(@user.member)} - - <% else %> - {gettext("No member linked")} - <% end %> - - +
+ <.list> + <:item title={gettext("Email")}>{@user.email} + <:item title={gettext("Role")}>{@user.role.name} + <:item title={gettext("Password Authentication")}> + {if MvWeb.Helpers.UserHelpers.has_password?(@user), + do: gettext("Enabled"), + else: gettext("Not enabled")} + + <:item title={gettext("OIDC")}> + {if MvWeb.Helpers.UserHelpers.has_oidc?(@user), + do: gettext("Linked"), + else: gettext("Not linked")} + + <:item title={gettext("Linked Member")}> + <%= if @user.member do %> + <.link + navigate={~p"/members/#{@user.member}"} + class="text-blue-600 underline hover:text-blue-800" + > + <.icon name="hero-users" class="inline w-4 h-4 mr-1" /> + {MvWeb.Helpers.MemberHelpers.display_name(@user.member)} + + <% else %> + {gettext("No member linked")} + <% end %> + + - <%!-- Danger zone: canonical pattern (same as member show) --%> - <%= if can?(@current_user, :destroy, @user) and not Mv.Helpers.SystemActor.system_user?(@user) do %> -
-

- {gettext("Danger zone")} -

-
-

- {gettext( - "Deleting this user cannot be undone. The user account and any linked member association will be affected." - )} -

- <.button - id="delete-user-trigger" - variant="danger" - phx-click="open_delete_modal" - data-testid="user-delete" - aria-label={gettext("Delete user %{email}", email: @user.email)} - > - <.icon name="hero-trash" class="size-4" /> - {gettext("Delete user")} - -
-
- <% end %> + <%!-- Danger zone: canonical pattern (same as member show) --%> + <%= if can?(@current_user, :destroy, @user) and not Mv.Helpers.SystemActor.system_user?(@user) do %> +
+

+ {gettext("Danger zone")} +

+
+

+ {gettext( + "Deleting this user cannot be undone. The user account and any linked member association will be affected." + )} +

+ <.button + id="delete-user-trigger" + variant="danger" + phx-click="open_delete_modal" + data-testid="user-delete" + aria-label={gettext("Delete user %{email}", email: @user.email)} + > + <.icon name="hero-trash" class="size-4" /> + {gettext("Delete user")} + +
+
+ <% end %> +
<%!-- Delete User Confirmation Modal (WCAG: focus in modal, keyboard confirm/cancel) --%> <%= if assigns[:show_delete_modal] do %> -- 2.47.2 From 5aa0e4679afb3250cd7cdde1bec2588553e10b2c Mon Sep 17 00:00:00 2001 From: carla Date: Thu, 12 Mar 2026 16:03:39 +0100 Subject: [PATCH 2/5] i18n: update translations --- priv/gettext/de/LC_MESSAGES/default.po | 76 ++++++++++++++++---------- priv/gettext/default.pot | 66 ++++++++++++---------- priv/gettext/en/LC_MESSAGES/default.po | 76 ++++++++++++++++---------- 3 files changed, 134 insertions(+), 84 deletions(-) diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 419fbd3..05d737b 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -161,16 +161,12 @@ msgstr "Bezahlt" msgid "Postal Code" msgstr "Postleitzahl" -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Member" -msgstr "Mitglied speichern" - #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/global_settings_live.ex #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex +#: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/form.ex #: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex @@ -261,12 +257,14 @@ msgstr "Dein Passwort wurde erfolgreich zurückgesetzt" #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex +#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex +#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/user_live/form.ex #: lib/mv_web/live/user_live/show.ex @@ -381,11 +379,6 @@ msgstr "Mitglied auswählen" msgid "Settings" msgstr "Einstellungen" -#: lib/mv_web/live/user_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save User" -msgstr "Benutzer*in speichern" - #: lib/mv_web/live/user_live/show.ex #, elixir-autogen, elixir-format msgid "Show User" @@ -629,11 +622,6 @@ msgstr "Vereinsdaten" msgid "Manage global settings for the association." msgstr "Verwalte die globalen Einstellungen des Vereins." -#: lib/mv_web/live/membership_fee_settings_live.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Settings" -msgstr "Einstellungen speichern" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format msgid "Settings updated successfully" @@ -789,10 +777,7 @@ msgstr "Zahlungen" msgid "Personal Data" msgstr "Persönliche Daten" -#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex -#: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Save" msgstr "Speichern" @@ -1393,16 +1378,6 @@ msgstr "Zyklen regenerieren" msgid "Regenerating..." msgstr "Regeneriere..." -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Field" -msgstr "Benutzerdefiniertes Feld speichern" - -#: lib/mv_web/live/membership_fee_type_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Membership Fee Type" -msgstr "Mitgliedsbeitragsart speichern" - #: lib/mv_web/live/member_live/form.ex #, elixir-autogen, elixir-format msgid "Select a membership fee type for this member. Members can only switch between types with the same interval." @@ -3623,11 +3598,56 @@ msgstr "Diese*r Benutzer*in ist über SSO (Single Sign-On) verbunden. Ein hier f msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." msgstr "Wenn aktiviert und OIDC konfiguriert ist, zeigt die Anmeldeseite nur den Single Sign-On-Button." +#: lib/mv_web/live/member_field_live/form_component.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save datafield" +msgstr "Datenfeld speichern" + +#: lib/mv_web/live/membership_fee_settings_live.ex +#, elixir-autogen, elixir-format +msgid "Save default settings" +msgstr "Standardeinstellungen speichern" + +#: lib/mv_web/live/group_live/form.ex +#, elixir-autogen, elixir-format +msgid "Save group" +msgstr "Gruppe speichern" + +#: lib/mv_web/live/member_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save member" +msgstr "Mitglied speichern" + +#: lib/mv_web/live/membership_fee_type_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save membership fee type" +msgstr "Mitgliedsbeitragsart speichern" + +#: lib/mv_web/live/role_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save role" +msgstr "Rolle speichern" + +#: lib/mv_web/live/user_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save user" +msgstr "Benutzer*in speichern" + #~ #: lib/mv_web/live/import_live/components.ex #~ #, elixir-autogen, elixir-format, fuzzy #~ msgid "Manage Member Data" #~ msgstr "Mitgliederdaten verwalten" +#~ #: lib/mv_web/live/member_field_live/form_component.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Field" +#~ msgstr "Datenfeld speichern" + +#~ #: lib/mv_web/live/membership_fee_settings_live.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Settings" +#~ msgstr "Einstellungen speichern" + #~ #: lib/mv_web/live/global_settings_live.ex #~ #, elixir-autogen, elixir-format #~ msgid "Vereinfacht Integration" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index c9c9e54..667e6d0 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -162,16 +162,12 @@ msgstr "" msgid "Postal Code" msgstr "" -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Member" -msgstr "" - #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/global_settings_live.ex #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex +#: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/form.ex #: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex @@ -262,12 +258,14 @@ msgstr "" #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex +#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex +#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/user_live/form.ex #: lib/mv_web/live/user_live/show.ex @@ -382,11 +380,6 @@ msgstr "" msgid "Settings" msgstr "" -#: lib/mv_web/live/user_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save User" -msgstr "" - #: lib/mv_web/live/user_live/show.ex #, elixir-autogen, elixir-format msgid "Show User" @@ -630,11 +623,6 @@ msgstr "" msgid "Manage global settings for the association." msgstr "" -#: lib/mv_web/live/membership_fee_settings_live.ex -#, elixir-autogen, elixir-format -msgid "Save Settings" -msgstr "" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format msgid "Settings updated successfully" @@ -790,10 +778,7 @@ msgstr "" msgid "Personal Data" msgstr "" -#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex -#: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Save" msgstr "" @@ -1394,16 +1379,6 @@ msgstr "" msgid "Regenerating..." msgstr "" -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format -msgid "Save Field" -msgstr "" - -#: lib/mv_web/live/membership_fee_type_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Membership Fee Type" -msgstr "" - #: lib/mv_web/live/member_live/form.ex #, elixir-autogen, elixir-format msgid "Select a membership fee type for this member. Members can only switch between types with the same interval." @@ -3622,3 +3597,38 @@ msgstr "" #, elixir-autogen, elixir-format msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." msgstr "" + +#: lib/mv_web/live/member_field_live/form_component.ex +#, elixir-autogen, elixir-format +msgid "Save datafield" +msgstr "" + +#: lib/mv_web/live/membership_fee_settings_live.ex +#, elixir-autogen, elixir-format +msgid "Save default settings" +msgstr "" + +#: lib/mv_web/live/group_live/form.ex +#, elixir-autogen, elixir-format +msgid "Save group" +msgstr "" + +#: lib/mv_web/live/member_live/form.ex +#, elixir-autogen, elixir-format +msgid "Save member" +msgstr "" + +#: lib/mv_web/live/membership_fee_type_live/form.ex +#, elixir-autogen, elixir-format +msgid "Save membership fee type" +msgstr "" + +#: lib/mv_web/live/role_live/form.ex +#, elixir-autogen, elixir-format +msgid "Save role" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex +#, elixir-autogen, elixir-format +msgid "Save user" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index e6f6e9e..a93a1ad 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -162,16 +162,12 @@ msgstr "" msgid "Postal Code" msgstr "" -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Member" -msgstr "" - #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/global_settings_live.ex #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex +#: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/form.ex #: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex @@ -262,12 +258,14 @@ msgstr "" #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex +#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex +#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/user_live/form.ex #: lib/mv_web/live/user_live/show.ex @@ -382,11 +380,6 @@ msgstr "" msgid "Settings" msgstr "" -#: lib/mv_web/live/user_live/form.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save User" -msgstr "" - #: lib/mv_web/live/user_live/show.ex #, elixir-autogen, elixir-format, fuzzy msgid "Show User" @@ -630,11 +623,6 @@ msgstr "" msgid "Manage global settings for the association." msgstr "" -#: lib/mv_web/live/membership_fee_settings_live.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Settings" -msgstr "" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format msgid "Settings updated successfully" @@ -790,10 +778,7 @@ msgstr "" msgid "Personal Data" msgstr "" -#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex -#: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format, fuzzy msgid "Save" msgstr "" @@ -1394,16 +1379,6 @@ msgstr "" msgid "Regenerating..." msgstr "" -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Field" -msgstr "" - -#: lib/mv_web/live/membership_fee_type_live/form.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Membership Fee Type" -msgstr "" - #: lib/mv_web/live/member_live/form.ex #, elixir-autogen, elixir-format msgid "Select a membership fee type for this member. Members can only switch between types with the same interval." @@ -3623,11 +3598,56 @@ msgstr "" msgid "When enabled and OIDC is configured, the sign-in page shows only the Single Sign-On button." msgstr "" +#: lib/mv_web/live/member_field_live/form_component.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save datafield" +msgstr "" + +#: lib/mv_web/live/membership_fee_settings_live.ex +#, elixir-autogen, elixir-format +msgid "Save default settings" +msgstr "" + +#: lib/mv_web/live/group_live/form.ex +#, elixir-autogen, elixir-format +msgid "Save group" +msgstr "" + +#: lib/mv_web/live/member_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save member" +msgstr "" + +#: lib/mv_web/live/membership_fee_type_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save membership fee type" +msgstr "" + +#: lib/mv_web/live/role_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save role" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save user" +msgstr "" + #~ #: lib/mv_web/live/import_live/components.ex #~ #, elixir-autogen, elixir-format, fuzzy #~ msgid "Manage Member Data" #~ msgstr "" +#~ #: lib/mv_web/live/member_field_live/form_component.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Field" +#~ msgstr "" + +#~ #: lib/mv_web/live/membership_fee_settings_live.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Settings" +#~ msgstr "" + #~ #: lib/mv_web/live/global_settings_live.ex #~ #, elixir-autogen, elixir-format #~ msgid "Vereinfacht Integration" -- 2.47.2 From a0a76b6ffcd29b2884bd06f71d68e7f74c3fb40c Mon Sep 17 00:00:00 2001 From: carla Date: Thu, 12 Mar 2026 16:14:57 +0100 Subject: [PATCH 3/5] i18n: fix translations after merge --- priv/gettext/de/LC_MESSAGES/default.po | 84 +++++++++++++++----------- priv/gettext/default.pot | 32 +--------- priv/gettext/en/LC_MESSAGES/default.po | 82 ++++++++++--------------- 3 files changed, 84 insertions(+), 114 deletions(-) diff --git a/priv/gettext/de/LC_MESSAGES/default.po b/priv/gettext/de/LC_MESSAGES/default.po index 40340b9..d8d8443 100644 --- a/priv/gettext/de/LC_MESSAGES/default.po +++ b/priv/gettext/de/LC_MESSAGES/default.po @@ -126,13 +126,6 @@ msgid "Admin Note" msgstr "Administrator*innen-Hinweis" #: lib/mv_web/live/global_settings_live.ex -#: lib/mv_web/live/group_live/form.ex -#: lib/mv_web/live/member_field_live/form_component.ex -#: lib/mv_web/live/member_live/form.ex -#: lib/mv_web/live/membership_fee_settings_live.ex -#: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex -#: lib/mv_web/live/user_live/form.ex #, elixir-autogen, elixir-format msgid "Admin group name" msgstr "Admin-Gruppenname" @@ -231,7 +224,6 @@ msgstr "Apr." msgid "Are you sure you want to delete %{name}? This action cannot be undone." msgstr "Möchtest du %{name} wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden." -#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/role_live/show.ex #, elixir-autogen, elixir-format, fuzzy msgid "Are you sure you want to delete the role %{name}? This action cannot be undone." @@ -401,12 +393,14 @@ msgstr "Kann jederzeit geändert werden. Änderungen des Betrags betreffen nur z #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex +#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex +#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/user_live/form.ex #: lib/mv_web/live/user_live/show.ex @@ -2794,10 +2788,7 @@ msgstr "SSL (Port 465)" msgid "SSO / OIDC user" msgstr "SSO / OIDC Benutzer*in" -#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex -#: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Save" msgstr "Speichern" @@ -2807,21 +2798,6 @@ msgstr "Speichern" msgid "Save Data Field" msgstr "Datenfeld speichern" -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Field" -msgstr "Benutzerdefiniertes Feld speichern" - -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Member" -msgstr "Mitglied speichern" - -#: lib/mv_web/live/membership_fee_type_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Membership Fee Type" -msgstr "Mitgliedsbeitragsart speichern" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format, fuzzy msgid "Save Name" @@ -2837,16 +2813,6 @@ msgstr "OIDC-Einstellungen speichern" msgid "Save SMTP Settings" msgstr "SMTP-Einstellungen speichern" -#: lib/mv_web/live/membership_fee_settings_live.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Settings" -msgstr "Einstellungen speichern" - -#: lib/mv_web/live/user_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save User" -msgstr "Benutzer*in speichern" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format msgid "Save Vereinfacht Settings" @@ -2862,6 +2828,7 @@ msgstr "Speichern, um die Verknüpfung zu bestätigen." #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex +#: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/form.ex #: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex @@ -3784,3 +3751,48 @@ msgstr "aktualisiert" #, elixir-autogen, elixir-format msgid "without %{name}" msgstr "ohne %{name}" + +#: lib/mv_web/live/member_field_live/form_component.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save datafield" +msgstr "Datenfeld speichern" + +#: lib/mv_web/live/membership_fee_settings_live.ex +#, elixir-autogen, elixir-format +msgid "Save default settings" +msgstr "Standardeinstellungen speichern" + +#: lib/mv_web/live/group_live/form.ex +#, elixir-autogen, elixir-format +msgid "Save group" +msgstr "Gruppe speichern" + +#: lib/mv_web/live/member_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save member" +msgstr "Mitglied speichern" + +#: lib/mv_web/live/membership_fee_type_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save membership fee type" +msgstr "Mitgliedsbeitragsart speichern" + +#: lib/mv_web/live/role_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save role" +msgstr "Rolle speichern" + +#: lib/mv_web/live/user_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "Save user" +msgstr "Benutzer*in speichern" + +#~ #: lib/mv_web/live/member_field_live/form_component.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Field" +#~ msgstr "Benutzerdefiniertes Feld speichern" + +#~ #: lib/mv_web/live/membership_fee_settings_live.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Settings" +#~ msgstr "Einstellungen speichern" diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index 51bd86d..152f905 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -225,7 +225,6 @@ msgstr "" msgid "Are you sure you want to delete %{name}? This action cannot be undone." msgstr "" -#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/role_live/show.ex #, elixir-autogen, elixir-format msgid "Are you sure you want to delete the role %{name}? This action cannot be undone." @@ -395,12 +394,14 @@ msgstr "" #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex +#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex +#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/user_live/form.ex #: lib/mv_web/live/user_live/show.ex @@ -2788,10 +2789,7 @@ msgstr "" msgid "SSO / OIDC user" msgstr "" -#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex -#: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format msgid "Save" msgstr "" @@ -2801,21 +2799,6 @@ msgstr "" msgid "Save Data Field" msgstr "" -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format -msgid "Save Field" -msgstr "" - -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Member" -msgstr "" - -#: lib/mv_web/live/membership_fee_type_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save Membership Fee Type" -msgstr "" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format msgid "Save Name" @@ -2831,16 +2814,6 @@ msgstr "" msgid "Save SMTP Settings" msgstr "" -#: lib/mv_web/live/membership_fee_settings_live.ex -#, elixir-autogen, elixir-format -msgid "Save Settings" -msgstr "" - -#: lib/mv_web/live/user_live/form.ex -#, elixir-autogen, elixir-format -msgid "Save User" -msgstr "" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format msgid "Save Vereinfacht Settings" @@ -2856,6 +2829,7 @@ msgstr "" #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex +#: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/form.ex #: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 7857e3b..06676b8 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -273,19 +273,7 @@ msgstr "" msgid "Attempting to reconnect" msgstr "" -#: lib/mv_web/live/custom_field_live/form_component.ex -#: lib/mv_web/live/custom_field_live/index_component.ex -#: lib/mv_web/live/group_live/form.ex -#: lib/mv_web/live/group_live/show.ex -#: lib/mv_web/live/member_field_live/form_component.ex -#: lib/mv_web/live/member_live/form.ex -#: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex -#: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex -#: lib/mv_web/live/role_live/show.ex -#: lib/mv_web/live/user_live/form.ex -#: lib/mv_web/live/user_live/show.ex #, elixir-autogen, elixir-format msgid "Aug." msgstr "" @@ -406,12 +394,14 @@ msgstr "" #: lib/mv_web/live/custom_field_live/form_component.ex #: lib/mv_web/live/custom_field_live/index_component.ex +#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/group_live/show.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex #: lib/mv_web/live/member_live/show.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex #: lib/mv_web/live/membership_fee_type_live/form.ex +#: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/role_live/show.ex #: lib/mv_web/live/user_live/form.ex #: lib/mv_web/live/user_live/show.ex @@ -2799,10 +2789,7 @@ msgstr "" msgid "SSO / OIDC user" msgstr "" -#: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_live/show/membership_fees_component.ex -#: lib/mv_web/live/membership_fee_type_live/form.ex -#: lib/mv_web/live/role_live/form.ex #, elixir-autogen, elixir-format, fuzzy msgid "Save" msgstr "" @@ -2812,21 +2799,6 @@ msgstr "" msgid "Save Data Field" msgstr "" -#: lib/mv_web/live/member_field_live/form_component.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Field" -msgstr "" - -#: lib/mv_web/live/member_live/form.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Member" -msgstr "" - -#: lib/mv_web/live/membership_fee_type_live/form.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Membership Fee Type" -msgstr "" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format, fuzzy msgid "Save Name" @@ -2842,16 +2814,6 @@ msgstr "" msgid "Save SMTP Settings" msgstr "" -#: lib/mv_web/live/membership_fee_settings_live.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save Settings" -msgstr "" - -#: lib/mv_web/live/user_live/form.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "Save User" -msgstr "" - #: lib/mv_web/live/global_settings_live.ex #, elixir-autogen, elixir-format msgid "Save Vereinfacht Settings" @@ -2867,6 +2829,7 @@ msgstr "" #: lib/mv_web/live/group_live/form.ex #: lib/mv_web/live/member_field_live/form_component.ex #: lib/mv_web/live/member_live/form.ex +#: lib/mv_web/live/membership_fee_settings_live.ex #: lib/mv_web/live/membership_fee_type_live/form.ex #: lib/mv_web/live/role_live/form.ex #: lib/mv_web/live/user_live/form.ex @@ -3773,6 +3736,22 @@ msgstr "" msgid "unpaid" msgstr "" +#: lib/mv_web/live/custom_field_live/form_component.ex +#: lib/mv_web/live/member_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "update" +msgstr "" + +#: lib/mv_web/live/user_live/form.ex +#, elixir-autogen, elixir-format, fuzzy +msgid "updated" +msgstr "" + +#: lib/mv_web/live/components/member_filter_component.ex +#, elixir-autogen, elixir-format +msgid "without %{name}" +msgstr "without %{name}" + #: lib/mv_web/live/member_field_live/form_component.ex #, elixir-autogen, elixir-format, fuzzy msgid "Save datafield" @@ -3818,17 +3797,22 @@ msgstr "" #~ msgid "Save Field" #~ msgstr "" +#~ #: lib/mv_web/live/member_live/form.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Member" +#~ msgstr "" + +#~ #: lib/mv_web/live/membership_fee_type_live/form.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save Membership Fee Type" +#~ msgstr "" + #~ #: lib/mv_web/live/membership_fee_settings_live.ex #~ #, elixir-autogen, elixir-format, fuzzy #~ msgid "Save Settings" #~ msgstr "" -#: lib/mv_web/live/user_live/form.ex -#, elixir-autogen, elixir-format, fuzzy -msgid "updated" -msgstr "" - -#: lib/mv_web/live/components/member_filter_component.ex -#, elixir-autogen, elixir-format -msgid "without %{name}" -msgstr "without %{name}" +#~ #: lib/mv_web/live/user_live/form.ex +#~ #, elixir-autogen, elixir-format, fuzzy +#~ msgid "Save User" +#~ msgstr "" -- 2.47.2 From 08c32dce7b4694a384d9232043f533ba34a2f2ce Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 13 Mar 2026 19:20:06 +0100 Subject: [PATCH 4/5] fix: failing tests from full suite --- config/test.exs | 4 ++++ test/membership/setting_join_form_test.exs | 2 +- test/mv_web/live/group_live/index_test.exs | 8 ++++---- test/mv_web/live/group_live/show_test.exs | 4 ++-- test/mv_web/user_live/index_test.exs | 7 +++---- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/config/test.exs b/config/test.exs index 84ccd70..ef54982 100644 --- a/config/test.exs +++ b/config/test.exs @@ -58,3 +58,7 @@ config :mv, :sql_sandbox, true # Join form rate limit: low limit so tests can trigger rate limiting (e.g. 2 per minute) config :mv, :join_rate_limit, scale_ms: 60_000, limit: 2 + +# Ash: silence "after_transaction hooks in surrounding transaction" warning when using +# Ecto sandbox (tests run in a transaction; create_member after_transaction is expected). +config :ash, warn_on_transaction_hooks?: false diff --git a/test/membership/setting_join_form_test.exs b/test/membership/setting_join_form_test.exs index a9cf599..f9cb2f4 100644 --- a/test/membership/setting_join_form_test.exs +++ b/test/membership/setting_join_form_test.exs @@ -56,7 +56,7 @@ defmodule Mv.Membership.SettingJoinFormTest do Membership.update_settings(settings, attrs) end - defp error_message(errors, field) when is_atom(field) do + defp _error_message(errors, field) when is_atom(field) do errors |> Enum.filter(fn err -> Map.get(err, :field) == field end) |> Enum.map(&Map.get(&1, :message, "")) diff --git a/test/mv_web/live/group_live/index_test.exs b/test/mv_web/live/group_live/index_test.exs index 59e32a4..5adfe56 100644 --- a/test/mv_web/live/group_live/index_test.exs +++ b/test/mv_web/live/group_live/index_test.exs @@ -144,8 +144,8 @@ defmodule MvWeb.GroupLive.IndexTest do # Verify query count is reasonable (should avoid N+1 queries) # Expected: 1 query for groups list + 1 batch query for member counts + LiveView setup queries # Allow overhead for authorization, LiveView setup, and other initialization queries - assert final_count <= 12, - "Expected max 12 queries (groups list + batch member counts + LiveView setup + auth), got #{final_count}. This suggests N+1 query problem." + assert final_count <= 13, + "Expected max 13 queries (groups list + batch member counts + LiveView setup + auth), got #{final_count}. This suggests N+1 query problem." end test "member count is loaded efficiently via calculation", %{conn: conn} do @@ -185,8 +185,8 @@ defmodule MvWeb.GroupLive.IndexTest do # Verify query count is reasonable (member count should be calculated efficiently) # Expected: 1 query for groups + 1 batch query for member counts + LiveView setup queries # Allow overhead for authorization, LiveView setup, and other initialization queries - assert final_count <= 12, - "Expected max 12 queries (groups + batch member counts + LiveView setup + auth), got #{final_count}. This suggests inefficient member count calculation." + assert final_count <= 13, + "Expected max 13 queries (groups + batch member counts + LiveView setup + auth), got #{final_count}. This suggests inefficient member count calculation." end end end diff --git a/test/mv_web/live/group_live/show_test.exs b/test/mv_web/live/group_live/show_test.exs index 4d64739..ee30991 100644 --- a/test/mv_web/live/group_live/show_test.exs +++ b/test/mv_web/live/group_live/show_test.exs @@ -253,8 +253,8 @@ defmodule MvWeb.GroupLive.ShowTest do # Verify query count is reasonable (should avoid N+1 queries). # Baseline: group + members preload + member_count aggregate + 1 layout get_settings + auth/role/join-count. - assert final_count <= 22, - "Expected max 22 queries (group + members preload + member_count + layout + auth), got #{final_count}. This suggests N+1 query problem." + assert final_count <= 23, + "Expected max 23 queries (group + members preload + member_count + layout + auth), got #{final_count}. This suggests N+1 query problem." end test "slug lookup is efficient (uses unique_slug index)", %{conn: conn} do diff --git a/test/mv_web/user_live/index_test.exs b/test/mv_web/user_live/index_test.exs index c75196e..ab1d174 100644 --- a/test/mv_web/user_live/index_test.exs +++ b/test/mv_web/user_live/index_test.exs @@ -24,16 +24,15 @@ defmodule MvWeb.UserLive.IndexTest do @tag :ui test "shows translated titles in different locales", %{conn: conn} do - # Test German translation + # Page title/heading uses sidebar label (Users / Benutzer*innen), not "Listing Users" conn = conn_with_oidc_user(conn) conn = Plug.Test.init_test_session(conn, locale: "de") {:ok, _view, html_de} = live(conn, "/users") - assert html_de =~ "Benutzer*innen auflisten" + assert html_de =~ "Benutzer*innen" - # Test English translation conn = Plug.Test.init_test_session(conn, locale: "en") {:ok, _view, html_en} = live(conn, "/users") - assert html_en =~ "Listing Users" + assert html_en =~ "Users" end end -- 2.47.2 From 81fb794c7651e36b65099507c9d30658e66b367b Mon Sep 17 00:00:00 2001 From: simon Date: Fri, 13 Mar 2026 19:23:59 +0100 Subject: [PATCH 5/5] revert 08c32dce7b4694a384d9232043f533ba34a2f2ce revert fix: failing tests from full suite --- config/test.exs | 4 ---- test/membership/setting_join_form_test.exs | 2 +- test/mv_web/live/group_live/index_test.exs | 8 ++++---- test/mv_web/live/group_live/show_test.exs | 4 ++-- test/mv_web/user_live/index_test.exs | 7 ++++--- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/config/test.exs b/config/test.exs index ef54982..84ccd70 100644 --- a/config/test.exs +++ b/config/test.exs @@ -58,7 +58,3 @@ config :mv, :sql_sandbox, true # Join form rate limit: low limit so tests can trigger rate limiting (e.g. 2 per minute) config :mv, :join_rate_limit, scale_ms: 60_000, limit: 2 - -# Ash: silence "after_transaction hooks in surrounding transaction" warning when using -# Ecto sandbox (tests run in a transaction; create_member after_transaction is expected). -config :ash, warn_on_transaction_hooks?: false diff --git a/test/membership/setting_join_form_test.exs b/test/membership/setting_join_form_test.exs index f9cb2f4..a9cf599 100644 --- a/test/membership/setting_join_form_test.exs +++ b/test/membership/setting_join_form_test.exs @@ -56,7 +56,7 @@ defmodule Mv.Membership.SettingJoinFormTest do Membership.update_settings(settings, attrs) end - defp _error_message(errors, field) when is_atom(field) do + defp error_message(errors, field) when is_atom(field) do errors |> Enum.filter(fn err -> Map.get(err, :field) == field end) |> Enum.map(&Map.get(&1, :message, "")) diff --git a/test/mv_web/live/group_live/index_test.exs b/test/mv_web/live/group_live/index_test.exs index 5adfe56..59e32a4 100644 --- a/test/mv_web/live/group_live/index_test.exs +++ b/test/mv_web/live/group_live/index_test.exs @@ -144,8 +144,8 @@ defmodule MvWeb.GroupLive.IndexTest do # Verify query count is reasonable (should avoid N+1 queries) # Expected: 1 query for groups list + 1 batch query for member counts + LiveView setup queries # Allow overhead for authorization, LiveView setup, and other initialization queries - assert final_count <= 13, - "Expected max 13 queries (groups list + batch member counts + LiveView setup + auth), got #{final_count}. This suggests N+1 query problem." + assert final_count <= 12, + "Expected max 12 queries (groups list + batch member counts + LiveView setup + auth), got #{final_count}. This suggests N+1 query problem." end test "member count is loaded efficiently via calculation", %{conn: conn} do @@ -185,8 +185,8 @@ defmodule MvWeb.GroupLive.IndexTest do # Verify query count is reasonable (member count should be calculated efficiently) # Expected: 1 query for groups + 1 batch query for member counts + LiveView setup queries # Allow overhead for authorization, LiveView setup, and other initialization queries - assert final_count <= 13, - "Expected max 13 queries (groups + batch member counts + LiveView setup + auth), got #{final_count}. This suggests inefficient member count calculation." + assert final_count <= 12, + "Expected max 12 queries (groups + batch member counts + LiveView setup + auth), got #{final_count}. This suggests inefficient member count calculation." end end end diff --git a/test/mv_web/live/group_live/show_test.exs b/test/mv_web/live/group_live/show_test.exs index ee30991..4d64739 100644 --- a/test/mv_web/live/group_live/show_test.exs +++ b/test/mv_web/live/group_live/show_test.exs @@ -253,8 +253,8 @@ defmodule MvWeb.GroupLive.ShowTest do # Verify query count is reasonable (should avoid N+1 queries). # Baseline: group + members preload + member_count aggregate + 1 layout get_settings + auth/role/join-count. - assert final_count <= 23, - "Expected max 23 queries (group + members preload + member_count + layout + auth), got #{final_count}. This suggests N+1 query problem." + assert final_count <= 22, + "Expected max 22 queries (group + members preload + member_count + layout + auth), got #{final_count}. This suggests N+1 query problem." end test "slug lookup is efficient (uses unique_slug index)", %{conn: conn} do diff --git a/test/mv_web/user_live/index_test.exs b/test/mv_web/user_live/index_test.exs index ab1d174..c75196e 100644 --- a/test/mv_web/user_live/index_test.exs +++ b/test/mv_web/user_live/index_test.exs @@ -24,15 +24,16 @@ defmodule MvWeb.UserLive.IndexTest do @tag :ui test "shows translated titles in different locales", %{conn: conn} do - # Page title/heading uses sidebar label (Users / Benutzer*innen), not "Listing Users" + # Test German translation conn = conn_with_oidc_user(conn) conn = Plug.Test.init_test_session(conn, locale: "de") {:ok, _view, html_de} = live(conn, "/users") - assert html_de =~ "Benutzer*innen" + assert html_de =~ "Benutzer*innen auflisten" + # Test English translation conn = Plug.Test.init_test_session(conn, locale: "en") {:ok, _view, html_en} = live(conn, "/users") - assert html_en =~ "Users" + assert html_en =~ "Listing Users" end end -- 2.47.2