From b7c93f19cb151e58daddb39b176c46fa376685b9 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 25 Feb 2026 09:12:33 +0100 Subject: [PATCH] refactor: use core components --- lib/mv_web/components/core_components.ex | 115 +++- lib/mv_web/components/layouts.ex | 2 +- lib/mv_web/components/layouts/root.html.heex | 2 +- .../components/member_filter_component.ex | 24 +- .../live/components/sort_header_component.ex | 41 +- .../live/custom_field_live/form_component.ex | 6 +- .../live/custom_field_live/index_component.ex | 12 +- lib/mv_web/live/global_settings_live.ex | 4 +- lib/mv_web/live/group_live/form.ex | 45 +- lib/mv_web/live/group_live/index.ex | 120 ++-- lib/mv_web/live/group_live/show.ex | 571 +++++++++--------- .../live/member_field_live/form_component.ex | 8 +- .../live/member_field_live/index_component.ex | 7 +- lib/mv_web/live/member_live/form.ex | 421 +++++++------ lib/mv_web/live/member_live/index.html.heex | 16 +- lib/mv_web/live/member_live/show.ex | 418 ++++++------- .../show/membership_fees_component.ex | 62 +- .../live/membership_fee_settings_live.ex | 36 +- .../live/membership_fee_type_live/form.ex | 12 +- .../live/membership_fee_type_live/index.ex | 32 +- lib/mv_web/live/role_live/form.ex | 30 +- lib/mv_web/live/role_live/index.html.heex | 21 +- lib/mv_web/live/role_live/show.ex | 14 +- lib/mv_web/live/user_live/form.ex | 11 +- lib/mv_web/live/user_live/index.html.heex | 2 +- lib/mv_web/live/user_live/show.ex | 2 +- 26 files changed, 1080 insertions(+), 954 deletions(-) diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex index 21e3546..4f9d7af 100644 --- a/lib/mv_web/components/core_components.ex +++ b/lib/mv_web/components/core_components.ex @@ -60,15 +60,15 @@ defmodule MvWeb.CoreComponents do id={@id} phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")} role="alert" - class="z-50 toast toast-top toast-end" + class="z-50 toast toast-bottom toast-end" {@rest} >
<.icon :if={@kind == :info} name="hero-information-circle" class="size-5 shrink-0" /> <.icon :if={@kind == :error} name="hero-exclamation-circle" class="size-5 shrink-0" /> @@ -90,33 +90,71 @@ defmodule MvWeb.CoreComponents do @doc """ Renders a button with navigation support. + ## Variants (Design Guidelines §5.2) + - primary (main CTA) + - secondary (supporting) + - neutral (cancel/back) + - ghost (low emphasis; table/toolbars) + - outline (alternative CTA) + - danger (destructive) + - link (inline; rare) + - icon (icon-only) + + ## Sizes + - sm, md (default), lg + ## Examples <.button>Send! <.button phx-click="go" variant="primary">Send! - <.button navigate={~p"/"}>Home + <.button navigate={~p"/"} variant="secondary">Home + <.button variant="ghost" size="sm">Edit <.button disabled={true}>Disabled """ attr :rest, :global, include: ~w(href navigate patch method data-testid) - attr :variant, :string, values: ~w(primary) + + attr :variant, :string, + values: ~w(primary secondary neutral ghost outline danger link icon), + default: "primary" + + attr :size, :string, values: ~w(sm md lg), default: "md" attr :disabled, :boolean, default: false, doc: "Whether the button is disabled" slot :inner_block, required: true def button(assigns) do rest = assigns.rest - variants = %{"primary" => "btn-primary", nil => "btn-primary btn-soft"} - assigns = assign(assigns, :class, Map.fetch!(variants, assigns[:variant])) + variant = assigns[:variant] || "primary" + size = assigns[:size] || "md" + + variant_classes = %{ + "primary" => "btn-primary", + "secondary" => "btn-secondary", + "neutral" => "btn-neutral", + "ghost" => "btn-ghost", + "outline" => "btn-outline", + "danger" => "btn-error", + "link" => "btn-link", + "icon" => "btn-ghost btn-square" + } + + size_classes = %{ + "sm" => "btn-sm", + "md" => "", + "lg" => "btn-lg" + } + + base_class = Map.fetch!(variant_classes, variant) + size_class = size_classes[size] + btn_class = [base_class, size_class] |> Enum.reject(&(&1 == "")) |> Enum.join(" ") + + assigns = assign(assigns, :btn_class, btn_class) if rest[:href] || rest[:navigate] || rest[:patch] do - # For links, we can't use disabled attribute, so we use btn-disabled class - # DaisyUI's btn-disabled provides the same styling as :disabled on buttons link_class = if assigns[:disabled], - do: ["btn", assigns.class, "btn-disabled"], - else: ["btn", assigns.class] + do: ["btn", btn_class, "btn-disabled"], + else: ["btn", btn_class] - # Prevent interaction when disabled - # Remove navigation attributes to prevent "Open in new tab", "Copy link" etc. link_attrs = if assigns[:disabled] do rest @@ -138,13 +176,49 @@ defmodule MvWeb.CoreComponents do """ else ~H""" - """ end end + @doc """ + Wraps content with a DaisyUI tooltip. Use for icon-only actions, truncated content, + or status badges that need explanation (Design Guidelines §8.2). + + ## Examples + + <.tooltip content={gettext("Edit")}> + <.button variant="icon" size="sm"><.icon name="hero-pencil" /> + + + <.tooltip content={@full_name} position="top"> + {@full_name} + + """ + attr :content, :string, required: true, doc: "Tooltip text (data-tip)" + + attr :position, :string, + values: ~w(top bottom left right), + default: "bottom" + + attr :wrap_class, :string, default: nil, doc: "Additional classes for the wrapper" + slot :inner_block, required: true + + def tooltip(assigns) do + position_class = "tooltip tooltip-#{assigns.position}" + wrap_class = [position_class, assigns.wrap_class] |> Enum.reject(&is_nil/1) |> Enum.join(" ") + + assigns = assign(assigns, :wrap_class, wrap_class) + + ~H""" +
+ {render_slot(@inner_block)} +
+ """ + end + @doc """ Renders a dropdown menu. @@ -437,7 +511,7 @@ defmodule MvWeb.CoreComponents do {@rest} />{@label}* @@ -456,7 +530,7 @@ defmodule MvWeb.CoreComponents do {@label}* @@ -485,7 +559,7 @@ defmodule MvWeb.CoreComponents do {@label}* @@ -514,7 +588,7 @@ defmodule MvWeb.CoreComponents do {@label}* @@ -585,6 +659,11 @@ defmodule MvWeb.CoreComponents do @doc ~S""" Renders a table with generic styling. + When `row_click` is set, clicking a row (or a data cell) triggers the handler. + The action column has no phx-click on its ``, so action buttons do not trigger row navigation. + For interactive elements inside other columns (e.g. checkboxes, buttons), use + `Phoenix.LiveView.JS.stop_propagation()` in the element's phx-click so the row click is not fired. + ## Examples <.table id="users" rows={@users}> diff --git a/lib/mv_web/components/layouts.ex b/lib/mv_web/components/layouts.ex index 89e3549..765edec 100644 --- a/lib/mv_web/components/layouts.ex +++ b/lib/mv_web/components/layouts.ex @@ -115,7 +115,7 @@ defmodule MvWeb.Layouts do def flash_group(assigns) do ~H""" -
+
<.flash kind={:success} flash={@flash} /> <.flash kind={:warning} flash={@flash} /> <.flash kind={:info} flash={@flash} /> diff --git a/lib/mv_web/components/layouts/root.html.heex b/lib/mv_web/components/layouts/root.html.heex index a47fcc7..e107d5b 100644 --- a/lib/mv_web/components/layouts/root.html.heex +++ b/lib/mv_web/components/layouts/root.html.heex @@ -72,7 +72,7 @@
<.flash id="flash-success-root" kind={:success} flash={@flash} /> <.flash id="flash-warning-root" kind={:warning} flash={@flash} /> diff --git a/lib/mv_web/live/components/member_filter_component.ex b/lib/mv_web/live/components/member_filter_component.ex index ef6f32e..c020fc1 100644 --- a/lib/mv_web/live/components/member_filter_component.ex +++ b/lib/mv_web/live/components/member_filter_component.ex @@ -64,11 +64,11 @@ defmodule MvWeb.Components.MemberFilterComponent do phx-key="Escape" phx-target={@myself} > - +
- - +
diff --git a/lib/mv_web/live/components/sort_header_component.ex b/lib/mv_web/live/components/sort_header_component.ex index d548efa..c4850c4 100644 --- a/lib/mv_web/live/components/sort_header_component.ex +++ b/lib/mv_web/live/components/sort_header_component.ex @@ -19,25 +19,28 @@ defmodule MvWeb.Components.SortHeaderComponent do @impl true def render(assigns) do ~H""" -
- +
+ <.tooltip content={aria_sort(@field, @sort_field, @sort_order)} position="bottom"> + <.button + type="button" + variant="ghost" + aria-label={aria_sort(@field, @sort_field, @sort_order)} + class="select-none" + phx-click="sort" + phx-value-field={@field} + data-testid={@field} + > + {@label} + <%= if @sort_field == @field do %> + <.icon name={if @sort_order == :asc, do: "hero-chevron-up", else: "hero-chevron-down"} /> + <% else %> + <.icon + name="hero-chevron-up-down" + class="opacity-40" + /> + <% end %> + +
""" end 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 f89f767..8e59ac9 100644 --- a/lib/mv_web/live/custom_field_live/form_component.ex +++ b/lib/mv_web/live/custom_field_live/form_component.ex @@ -24,11 +24,13 @@ defmodule MvWeb.CustomFieldLive.FormComponent do
<.button type="button" + variant="neutral" phx-click="cancel" phx-target={@myself} aria-label={gettext("Back to settings")} > - <.icon name="hero-arrow-left" class="w-4 h-4" /> + <.icon name="hero-arrow-left" class="size-4" /> + {gettext("Back")}

{if @custom_field, do: gettext("Edit Data Field"), else: gettext("New Data Field")} @@ -97,7 +99,7 @@ defmodule MvWeb.CustomFieldLive.FormComponent do />
- <.button type="button" phx-click="cancel" phx-target={@myself}> + <.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}> {gettext("Cancel")} <.button phx-disable-with={gettext("Saving...")} variant="primary"> diff --git a/lib/mv_web/live/custom_field_live/index_component.ex b/lib/mv_web/live/custom_field_live/index_component.ex index a670a3e..a944c85 100644 --- a/lib/mv_web/live/custom_field_live/index_component.ex +++ b/lib/mv_web/live/custom_field_live/index_component.ex @@ -100,7 +100,7 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do <.link phx-click={ JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself) }> - {gettext("Edit")} + {gettext("Edit datafield")} @@ -164,17 +164,17 @@ defmodule MvWeb.CustomFieldLive.IndexComponent do

diff --git a/lib/mv_web/live/global_settings_live.ex b/lib/mv_web/live/global_settings_live.ex index 752c8d6..f3a61bc 100644 --- a/lib/mv_web/live/global_settings_live.ex +++ b/lib/mv_web/live/global_settings_live.ex @@ -181,18 +181,18 @@ defmodule MvWeb.GlobalSettingsLive do <.button :if={Mv.Config.vereinfacht_configured?()} type="button" + variant="outline" phx-click="test_vereinfacht_connection" phx-disable-with={gettext("Testing...")} - class="btn-outline" > {gettext("Test Integration")} <.button :if={Mv.Config.vereinfacht_configured?()} type="button" + variant="outline" phx-click="sync_vereinfacht_contacts" phx-disable-with={gettext("Syncing...")} - class="btn-outline" > {gettext("Sync all members without Vereinfacht contact")} diff --git a/lib/mv_web/live/group_live/form.ex b/lib/mv_web/live/group_live/form.ex index 0ffba09..5f781a7 100644 --- a/lib/mv_web/live/group_live/form.ex +++ b/lib/mv_web/live/group_live/form.ex @@ -78,30 +78,29 @@ defmodule MvWeb.GroupLive.Form do ~H""" <.form for={@form} id="group-form" phx-change="validate" phx-submit="save"> - <%!-- Header with Back button, Title, and Save button --%> -
- <.button navigate={return_path(@return_to, @group)} type="button"> - <.icon name="hero-arrow-left" class="size-4" /> - {gettext("Back")} - + <.header> + {@page_title} + <:actions> + <.button navigate={return_path(@return_to, @group)} variant="neutral"> + <.icon name="hero-arrow-left" class="size-4" /> + {gettext("Back")} + + <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> + {gettext("Save")} + + + -

- {@page_title} -

- - <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save")} - -
- -
- <.input field={@form[:name]} label={gettext("Name")} required /> - <.input - field={@form[:description]} - type="textarea" - label={gettext("Description")} - rows="4" - /> +
+
+ <.input field={@form[:name]} label={gettext("Name")} required /> + <.input + field={@form[:description]} + type="textarea" + label={gettext("Description")} + rows="4" + /> +
diff --git a/lib/mv_web/live/group_live/index.ex b/lib/mv_web/live/group_live/index.ex index deab7e1..b6c8277 100644 --- a/lib/mv_web/live/group_live/index.ex +++ b/lib/mv_web/live/group_live/index.ex @@ -39,72 +39,64 @@ defmodule MvWeb.GroupLive.Index do def render(assigns) do ~H""" -
-

{gettext("Groups")}

- <%= if can?(@current_user, :create, Mv.Membership.Group) do %> - <.button navigate={~p"/groups/new"} variant="primary"> - <.icon name="hero-plus" class="size-4 mr-2" /> - {gettext("Create Group")} - + <.header> + {gettext("Groups")} + <:actions> + <%= if can?(@current_user, :create, Mv.Membership.Group) do %> + <.button navigate={~p"/groups/new"} variant="primary"> + <.icon name="hero-plus" class="size-4 mr-2" /> + {gettext("Create Group")} + + <% end %> + + + +
+ <%= if Enum.empty?(@groups) do %> +
+

{gettext("No groups")}

+
+ <% else %> + <.table + id="groups-table" + rows={@groups} + row_id={fn group -> "group-#{group.id}" end} + row_click={fn group -> JS.navigate(~p"/groups/#{group.slug}") end} + > + <:col :let={group} label={gettext("Name")}> + {group.name} + + <:col :let={group} label={gettext("Description")}> + <%= if group.description do %> + {group.description} + <% else %> + + <% end %> + + <:col :let={group} label={gettext("Members")} class="text-right"> + {group.member_count || 0} + + <:action :let={group}> + <.button + variant="ghost" + size="sm" + navigate={~p"/groups/#{group.slug}"} + > + {gettext("View")} + + <%= if can?(@current_user, :update, Mv.Membership.Group) do %> + <.button + variant="ghost" + size="sm" + navigate={~p"/groups/#{group.slug}/edit"} + > + {gettext("Edit group")} + + <% end %> + + <% end %>
- - <%= if Enum.empty?(@groups) do %> -
-

{gettext("No groups")}

-
- <% else %> -
- - - - - - - - - - - <%= for group <- @groups do %> - - - - - - - <% end %> - -
{gettext("Name")}{gettext("Description")}{gettext("Members")}{gettext("Actions")}
- {group.name} - - <%= if group.description do %> - {group.description} - <% else %> - - <% end %> - - <%= if group.member_count do %> - {group.member_count} - <% else %> - 0 - <% end %> - -
- <.link navigate={~p"/groups/#{group.slug}"} class="btn btn-sm btn-ghost"> - {gettext("View")} - - <%= if can?(@current_user, :update, Mv.Membership.Group) do %> - <.link - navigate={~p"/groups/#{group.slug}/edit"} - class="btn btn-sm btn-ghost" - > - {gettext("Edit")} - - <% end %> -
-
-
- <% end %> """ end diff --git a/lib/mv_web/live/group_live/show.ex b/lib/mv_web/live/group_live/show.ex index 0c7e93e..46766ef 100644 --- a/lib/mv_web/live/group_live/show.ex +++ b/lib/mv_web/live/group_live/show.ex @@ -85,318 +85,327 @@ defmodule MvWeb.GroupLive.Show do def render(assigns) do ~H""" - <%!-- Header with Back button, Name, and Edit/Delete buttons --%> -
- <.button navigate={~p"/groups"} aria-label={gettext("Back to groups list")}> - <.icon name="hero-arrow-left" class="size-4" /> - {gettext("Back")} - - -

- {@group.name} -

- -
+ <.header> + {@group.name} + <:actions> + <.button + navigate={~p"/groups"} + variant="neutral" + aria-label={gettext("Back to groups list")} + > + <.icon name="hero-arrow-left" class="size-4" /> + {gettext("Back")} + <%= if can?(@current_user, :update, @group) do %> <.button variant="primary" navigate={~p"/groups/#{@group.slug}/edit"} data-testid="group-show-edit-btn" > - {gettext("Edit")} + {gettext("Edit group")} <% end %> <%= if can?(@current_user, :destroy, @group) do %> <.button - class="btn-error" + variant="danger" phx-click="open_delete_modal" data-testid="group-show-delete-btn" > {gettext("Delete")} <% end %> -
-
+ + - <%!-- 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 %> - - {MvWeb.Helpers.MemberHelpers.display_name(member)} - - - <% 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 %> -
-
- - -
- <% 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 %> - - - + + + <.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)} - - - <%= if member.email do %> - +
+ + + + <%= 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)} + + + + <%= if member.email do %> + + {member.email} + + <% else %> + + <% end %> + + <%= 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 %> +
-
- <%!-- Delete Confirmation Modal --%> - <%= if assigns[:show_delete_modal] do %> - - + <% end %> +
""" end 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 ae9e239..17266a8 100644 --- a/lib/mv_web/live/member_field_live/form_component.ex +++ b/lib/mv_web/live/member_field_live/form_component.ex @@ -42,11 +42,13 @@ defmodule MvWeb.MemberFieldLive.FormComponent do
<.button type="button" + variant="neutral" phx-click="cancel" phx-target={@myself} - aria-label={gettext("Back to Settings")} + aria-label={gettext("Back to settings")} > - <.icon name="hero-arrow-left" class="w-4 h-4" /> + <.icon name="hero-arrow-left" class="size-4" /> + {gettext("Back")}

{gettext("Edit Field: %{field}", field: @field_label)} @@ -176,7 +178,7 @@ defmodule MvWeb.MemberFieldLive.FormComponent do

- <.button type="button" phx-click="cancel" phx-target={@myself}> + <.button type="button" variant="neutral" phx-click="cancel" phx-target={@myself}> {gettext("Cancel")} <.button phx-disable-with={gettext("Saving...")} variant="primary"> diff --git a/lib/mv_web/live/member_field_live/index_component.ex b/lib/mv_web/live/member_field_live/index_component.ex index db62778..1e8cf05 100644 --- a/lib/mv_web/live/member_field_live/index_component.ex +++ b/lib/mv_web/live/member_field_live/index_component.ex @@ -52,6 +52,11 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do :if={!@show_form} id="member_fields" rows={@member_fields} + row_click={ + fn {field_name, _field_data} -> + JS.push("edit_member_field", value: %{"field" => field_name}, target: @myself) + end + } > <:col :let={{_field_name, field_data}} label={gettext("Name")}> {MemberFields.label(field_data.field)} @@ -93,7 +98,7 @@ defmodule MvWeb.MemberFieldLive.IndexComponent do phx-value-field={Atom.to_string(field_data.field)} phx-target={@myself} > - {gettext("Edit")} + {gettext("Edit datafield")} diff --git a/lib/mv_web/live/member_live/form.ex b/lib/mv_web/live/member_live/form.ex index 6b3ce67..625ab2a 100644 --- a/lib/mv_web/live/member_live/form.ex +++ b/lib/mv_web/live/member_live/form.ex @@ -38,227 +38,226 @@ defmodule MvWeb.MemberLive.Form do ~H""" <.form for={@form} id="member-form" phx-change="validate" phx-submit="save"> - <%!-- Header with Back button, Name display, and Save button --%> -
- <.button navigate={return_path(@return_to, @member)} type="button"> - <.icon name="hero-arrow-left" class="size-4" /> - {gettext("Back")} - + <.header> + <%= if @member do %> + {MvWeb.Helpers.MemberHelpers.display_name(@member)} + <% else %> + {gettext("New Member")} + <% end %> + <:actions> + <.button navigate={return_path(@return_to, @member)} variant="neutral"> + <.icon name="hero-arrow-left" class="size-4" /> + {gettext("Back")} + + <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> + {gettext("Save")} + + + -

- <%= if @member do %> - {MvWeb.Helpers.MemberHelpers.display_name(@member)} - <% else %> - {gettext("New Member")} +
+ <%!-- Tab Navigation --%> +
+ + +
+ + <%!-- Personal Data and Custom Fields Row --%> +
+ <%!-- Personal Data Section --%> +
+ <.form_section title={gettext("Personal Data")}> +
+ <%!-- Name Row --%> +
+
+ <.input + field={@form[:first_name]} + label={gettext("First Name")} + required={@member_field_required_map[:first_name]} + /> +
+
+ <.input + field={@form[:last_name]} + label={gettext("Last Name")} + required={@member_field_required_map[:last_name]} + /> +
+
+ + <%!-- Address Row --%> +
+
+ <.input + field={@form[:street]} + label={gettext("Street")} + required={@member_field_required_map[:street]} + /> +
+
+ <.input + field={@form[:house_number]} + label={gettext("Nr.")} + required={@member_field_required_map[:house_number]} + /> +
+
+ <.input + field={@form[:postal_code]} + label={gettext("Postal Code")} + required={@member_field_required_map[:postal_code]} + /> +
+
+ <.input + field={@form[:city]} + label={gettext("City")} + required={@member_field_required_map[:city]} + /> +
+
+ + <%!-- Email (always required) --%> +
+ <.input field={@form[:email]} label={gettext("Email")} required type="email" /> +
+ + <%!-- Membership Dates Row --%> +
+
+ <.input + field={@form[:join_date]} + label={gettext("Join Date")} + type="date" + required={@member_field_required_map[:join_date]} + /> +
+
+ <.input + field={@form[:exit_date]} + label={gettext("Exit Date")} + type="date" + required={@member_field_required_map[:exit_date]} + /> +
+
+ + <%!-- Notes --%> +
+ <.input + field={@form[:notes]} + label={gettext("Notes")} + type="textarea" + required={@member_field_required_map[:notes]} + /> +
+
+ +
+ + <%!-- Custom Fields Section --%> + <%= if Enum.any?(@custom_fields) do %> +
+ <.form_section title={gettext("Custom Fields")}> +
+ <%!-- Render in sorted order by finding the form for each sorted custom field --%> + <%= for cf <- @sorted_custom_fields do %> + <.inputs_for :let={f_cfv} field={@form[:custom_field_values]}> + <%= if f_cfv[:custom_field_id].value == cf.id do %> +
+ <.inputs_for :let={value_form} field={f_cfv[:value]}> + <.input + field={value_form[:value]} + label={cf.name} + type={custom_field_input_type(cf.value_type)} + required={cf.required} + /> + + +
+ <% end %> + + <% end %> +
+ +
<% end %> -

+
- <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save")} - -
- - <%!-- Tab Navigation --%> -
- - -
- - <%!-- Personal Data and Custom Fields Row --%> -
- <%!-- Personal Data Section --%> -
- <.form_section title={gettext("Personal Data")}> + <%!-- Membership Fee Section --%> +
+ <.form_section title={gettext("Membership Fee")}>
- <%!-- Name Row --%> -
-
- <.input - field={@form[:first_name]} - label={gettext("First Name")} - required={@member_field_required_map[:first_name]} - /> -
-
- <.input - field={@form[:last_name]} - label={gettext("Last Name")} - required={@member_field_required_map[:last_name]} - /> -
-
- - <%!-- Address Row --%> -
-
- <.input - field={@form[:street]} - label={gettext("Street")} - required={@member_field_required_map[:street]} - /> -
-
- <.input - field={@form[:house_number]} - label={gettext("Nr.")} - required={@member_field_required_map[:house_number]} - /> -
-
- <.input - field={@form[:postal_code]} - label={gettext("Postal Code")} - required={@member_field_required_map[:postal_code]} - /> -
-
- <.input - field={@form[:city]} - label={gettext("City")} - required={@member_field_required_map[:city]} - /> -
-
- - <%!-- Email (always required) --%>
- <.input field={@form[:email]} label={gettext("Email")} required type="email" /> -
- - <%!-- Membership Dates Row --%> -
-
- <.input - field={@form[:join_date]} - label={gettext("Join Date")} - type="date" - required={@member_field_required_map[:join_date]} - /> -
-
- <.input - field={@form[:exit_date]} - label={gettext("Exit Date")} - type="date" - required={@member_field_required_map[:exit_date]} - /> -
-
- - <%!-- Notes --%> -
- <.input - field={@form[:notes]} - label={gettext("Notes")} - type="textarea" - required={@member_field_required_map[:notes]} - /> + + + <%= for error <- List.wrap(@form.errors[:membership_fee_type_id] || []) do %> + <% {msg, _opts} = if is_tuple(error), do: error, else: {error, []} %> +

{msg}

+ <% end %> + <%= if @interval_warning do %> +
+ <.icon name="hero-exclamation-triangle" class="size-5" /> + {@interval_warning} +
+ <% end %> +

+ {gettext( + "Select a membership fee type for this member. Members can only switch between types with the same interval." + )} +

- <%!-- Custom Fields Section --%> - <%= if Enum.any?(@custom_fields) do %> -
- <.form_section title={gettext("Custom Fields")}> -
- <%!-- Render in sorted order by finding the form for each sorted custom field --%> - <%= for cf <- @sorted_custom_fields do %> - <.inputs_for :let={f_cfv} field={@form[:custom_field_values]}> - <%= if f_cfv[:custom_field_id].value == cf.id do %> -
- <.inputs_for :let={value_form} field={f_cfv[:value]}> - <.input - field={value_form[:value]} - label={cf.name} - type={custom_field_input_type(cf.value_type)} - required={cf.required} - /> - - -
- <% end %> - - <% end %> -
- -
- <% end %> -
- - <%!-- Membership Fee Section --%> -
- <.form_section title={gettext("Membership Fee")}> -
-
- - - <%= for error <- List.wrap(@form.errors[:membership_fee_type_id] || []) do %> - <% {msg, _opts} = if is_tuple(error), do: error, else: {error, []} %> -

{msg}

- <% end %> - <%= if @interval_warning do %> -
- <.icon name="hero-exclamation-triangle" class="size-5" /> - {@interval_warning} -
- <% end %> -

- {gettext( - "Select a membership fee type for this member. Members can only switch between types with the same interval." - )} -

-
-
- -
- - <%!-- Bottom Action Buttons --%> -
- <.button navigate={return_path(@return_to, @member)} type="button"> - {gettext("Cancel")} - - <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save Member")} - + <%!-- Bottom Action Buttons --%> +
+ <.button navigate={return_path(@return_to, @member)} variant="neutral" type="button"> + {gettext("Cancel")} + + <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> + {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 fcf06c8..c54ec7c 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -9,7 +9,7 @@ selected_count={@selected_count} /> <.button - class="secondary" + variant="secondary" id="copy-emails-btn" phx-hook="CopyToClipboard" phx-click="copy_emails" @@ -20,7 +20,7 @@ {gettext("Copy email addresses")} ({@selected_count}) <.button - class="secondary" + variant="secondary" id="open-email-btn" href={"mailto:?bcc=" <> @mailto_bcc} disabled={not @any_selected?} @@ -54,13 +54,11 @@ boolean_filters={@boolean_custom_field_filters} member_count={length(@members)} /> - + <.live_component module={MvWeb.Components.FieldVisibilityDropdownComponent} id="field-visibility-dropdown" @@ -386,7 +384,7 @@ <%= if can?(@current_user, :update, member) do %> <.link navigate={~p"/members/#{member}/edit"} data-testid="member-edit"> - {gettext("Edit")} + {gettext("Edit member")} <% end %> diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex index a85bf69..3af0ed2 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -30,235 +30,243 @@ defmodule MvWeb.MemberLive.Show do def render(assigns) do ~H""" - <%!-- Header with Back button, Name, and Edit button --%> -
- <.button navigate={~p"/members"} aria-label={gettext("Back to members list")}> - <.icon name="hero-arrow-left" class="size-4" /> - {gettext("Back")} - - -

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

- - <%= if can?(@current_user, :update, @member) do %> + <.header> + {MvWeb.Helpers.MemberHelpers.display_name(@member)} + <:actions> <.button - variant="primary" - navigate={~p"/members/#{@member}/edit?return_to=show"} - data-testid="member-edit" + navigate={~p"/members"} + variant="neutral" + aria-label={gettext("Back to members list")} > - {gettext("Edit Member")} + <.icon name="hero-arrow-left" class="size-4" /> + {gettext("Back")} - <% end %> -
+ <%= if can?(@current_user, :update, @member) do %> + <.button + variant="primary" + navigate={~p"/members/#{@member}/edit?return_to=show"} + data-testid="member-edit" + > + {gettext("Edit member")} + + <% end %> + + - <%!-- Tab Navigation --%> -
- - -
+
+ <%!-- Tab Navigation --%> +
+ + +
- <%= if @active_tab == :contact do %> - <%!-- Contact Data Tab Content --%> - <%!-- Personal Data and Custom Fields Row --%> -
- <%!-- Personal Data Section --%> -
- <.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" /> -
+ <%= if @active_tab == :contact do %> + <%!-- Contact Data Tab Content --%> + <%!-- Personal Data and Custom Fields Row --%> +
+ <%!-- Personal Data Section --%> +
+ <.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" /> +
- <%!-- Address --%> -
- <.data_field label={gettext("Address")} value={format_address(@member)} /> -
+ <%!-- Address --%> +
+ <.data_field label={gettext("Address")} value={format_address(@member)} /> +
- <%!-- Email --%> -
- <.data_field label={gettext("Email")}> - - {@member.email} - - -
+ <%!-- Email --%> +
+ <.data_field label={gettext("Email")}> + + {@member.email} + + +
- <%!-- Membership Dates Row --%> -
- <.data_field - label={gettext("Join Date")} - value={format_date(@member.join_date)} - class="w-28" - /> - <.data_field - label={gettext("Exit Date")} - value={format_date(@member.exit_date)} - class="w-28" - /> -
+ <%!-- Membership Dates Row --%> +
+ <.data_field + label={gettext("Join Date")} + value={format_date(@member.join_date)} + class="w-28" + /> + <.data_field + label={gettext("Exit Date")} + value={format_date(@member.exit_date)} + class="w-28" + /> +
- <%!-- Linked User: only show when current user can see other users (e.g. admin). + <%!-- Linked User: only show when current user can see other users (e.g. admin). read_only cannot see linked user, so hide the section to avoid "No user linked" when a user is linked but not visible. --%> - <%= if can_access_page?(@current_user, "/users") do %> + <%= if can_access_page?(@current_user, "/users") do %> +
+ <.data_field label={gettext("Linked User")}> + <%= if @member.user do %> + <.link + navigate={~p"/users/#{@member.user}"} + class="text-blue-700 hover:text-blue-800 underline inline-flex items-center gap-1" + > + <.icon name="hero-user" class="size-4" /> + {@member.user.email} + + <% else %> + {gettext("No user linked")} + <% end %> + +
+ <% end %> + + <%!-- Groups (in Personal Data) --%> + <% groups = @member.groups || [] %>
- <.data_field label={gettext("Linked User")}> - <%= if @member.user do %> - <.link - navigate={~p"/users/#{@member.user}"} - class="text-blue-700 hover:text-blue-800 underline inline-flex items-center gap-1" - > - <.icon name="hero-user" class="size-4" /> - {@member.user.email} - + <.data_field label={gettext("Groups")}> + <%= if Enum.empty?(groups) do %> + {gettext("No groups")} <% else %> - {gettext("No user linked")} +
+ <%= for group <- groups do %> + <.button + variant="outline" + size="sm" + navigate={~p"/groups/#{group.slug}"} + aria-label={gettext("Member of group %{name}", name: group.name)} + > + {group.name} + + <% end %> +
<% end %>
- <% end %> - <%!-- Groups (in Personal Data) --%> - <% groups = @member.groups || [] %> -
- <.data_field label={gettext("Groups")}> - <%= if Enum.empty?(groups) do %> - {gettext("No groups")} - <% else %> -
- <%= for group <- groups do %> - <.link - navigate={~p"/groups/#{group.slug}"} - class="btn btn-xs btn-outline btn-primary" - aria-label={gettext("Member of group %{name}", name: group.name)} - > - {group.name} - - <% end %> -
- <% end %> - -
- - <%!-- Notes --%> - <%= if @member.notes && String.trim(@member.notes) != "" do %> -
- <.data_field label={gettext("Notes")}> -

{@member.notes}

- -
- <% end %> -
- -
- - <%!-- Custom Fields Section --%> - <%= if Enum.any?(@custom_fields) do %> -
- <.section_box title={gettext("Custom Fields")}> -
- <%= for custom_field <- @custom_fields do %> - <% cfv = find_custom_field_value(@member.custom_field_values, custom_field.id) %> - <.data_field label={custom_field.name}> - {format_custom_field_value(cfv, custom_field.value_type)} - + <%!-- Notes --%> + <%= if @member.notes && String.trim(@member.notes) != "" do %> +
+ <.data_field label={gettext("Notes")}> +

{@member.notes}

+ +
<% end %>
- <% end %> -
- <%!-- Payment Data Section --%> -
- <.section_box title={gettext("Payment Data")}> - <%= if @member.membership_fee_type do %> -
- <.data_field - label={gettext("Type")} - value={@member.membership_fee_type.name} - class="min-w-32" - /> - <.data_field - label={gettext("Membership Fee")} - value={MembershipFeeHelpers.format_currency(@member.membership_fee_type.amount)} - class="min-w-24" - /> - <.data_field - label={gettext("Payment Interval")} - value={MembershipFeeHelpers.format_interval(@member.membership_fee_type.interval)} - class="min-w-32" - /> - <.data_field label={gettext("Last Cycle")} class="min-w-32"> - <%= if @member.last_cycle_status do %> - <% status = @member.last_cycle_status %> - - {format_status_label(status)} - - <% else %> - {gettext("No cycles")} - <% end %> - - <.data_field label={gettext("Current Cycle")} class="min-w-36"> - <%= if @member.current_cycle_status do %> - <% status = @member.current_cycle_status %> - - {format_status_label(status)} - - <% else %> - {gettext("No cycles")} - <% end %> - -
- <% else %> -
- {gettext("No membership fee type assigned")} + <%!-- Custom Fields Section --%> + <%= if Enum.any?(@custom_fields) do %> +
+ <.section_box title={gettext("Custom Fields")}> +
+ <%= for custom_field <- @custom_fields do %> + <% cfv = find_custom_field_value(@member.custom_field_values, custom_field.id) %> + <.data_field label={custom_field.name}> + {format_custom_field_value(cfv, custom_field.value_type)} + + <% end %> +
+
<% end %> - -
- <% end %> +
- <%= if @active_tab == :membership_fees do %> - <%!-- Membership Fees Tab Content --%> - <.live_component - module={MvWeb.MemberLive.Show.MembershipFeesComponent} - id={"membership-fees-#{@member.id}"} - member={@member} - current_user={@current_user} - vereinfacht_receipts={@vereinfacht_receipts} - /> - <% end %> + <%!-- Payment Data Section --%> +
+ <.section_box title={gettext("Payment Data")}> + <%= if @member.membership_fee_type do %> +
+ <.data_field + label={gettext("Type")} + value={@member.membership_fee_type.name} + class="min-w-32" + /> + <.data_field + label={gettext("Membership Fee")} + value={MembershipFeeHelpers.format_currency(@member.membership_fee_type.amount)} + class="min-w-24" + /> + <.data_field + label={gettext("Payment Interval")} + value={MembershipFeeHelpers.format_interval(@member.membership_fee_type.interval)} + class="min-w-32" + /> + <.data_field label={gettext("Last Cycle")} class="min-w-32"> + <%= if @member.last_cycle_status do %> + <% status = @member.last_cycle_status %> + + {format_status_label(status)} + + <% else %> + {gettext("No cycles")} + <% end %> + + <.data_field label={gettext("Current Cycle")} class="min-w-36"> + <%= if @member.current_cycle_status do %> + <% status = @member.current_cycle_status %> + + {format_status_label(status)} + + <% else %> + {gettext("No cycles")} + <% end %> + +
+ <% else %> +
+ {gettext("No membership fee type assigned")} +
+ <% end %> + +
+ <% end %> + + <%= if @active_tab == :membership_fees do %> + <%!-- Membership Fees Tab Content --%> + <.live_component + module={MvWeb.MemberLive.Show.MembershipFeesComponent} + id={"membership-fees-#{@member.id}"} + member={@member} + current_user={@current_user} + vereinfacht_receipts={@vereinfacht_receipts} + /> + <% end %> +
""" end @@ -403,7 +411,7 @@ defmodule MvWeb.MemberLive.Show do ~H""" {@display} 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 b8757a0..2e75b57 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 @@ -66,14 +66,15 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do <.icon name="hero-arrow-top-right-on-square" class="inline-block size-4" />
- +
<%= if @vereinfacht_receipts do %>
<.button :if={Enum.any?(@cycles) and @can_destroy_cycle} + variant="outline" + size="sm" phx-click="delete_all_cycles" phx-target={@myself} - class="btn btn-sm btn-error btn-outline" title={gettext("Delete all cycles")} > <.icon name="hero-trash" class="size-4" /> @@ -158,9 +160,10 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do <.button :if={@member.membership_fee_type != nil and @can_create_cycle} + variant="primary" + size="sm" phx-click="open_create_cycle_modal" phx-target={@myself} - class="btn btn-sm btn-primary" title={gettext("Create a new cycle manually")} > <.icon name="hero-plus" class="size-4" /> @@ -259,17 +262,18 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
<% end %> <%= if @can_destroy_cycle do %> - + <% end %>
@@ -309,10 +313,15 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do />
@@ -334,17 +343,17 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do )} - {MembershipFeeHelpers.format_currency(@deleting_cycle.amount)}

@@ -385,20 +394,20 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do />
@@ -472,10 +481,15 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
<% end %>
diff --git a/lib/mv_web/live/membership_fee_settings_live.ex b/lib/mv_web/live/membership_fee_settings_live.ex index bfa20f8..aabb210 100644 --- a/lib/mv_web/live/membership_fee_settings_live.ex +++ b/lib/mv_web/live/membership_fee_settings_live.ex @@ -239,10 +239,10 @@ defmodule MvWeb.MembershipFeeSettingsLive do
- +
@@ -333,24 +333,27 @@ defmodule MvWeb.MembershipFeeSettingsLive do <:action :let={mft}> - <.link - navigate={~p"/membership_fee_settings/#{mft.id}/edit_fee_type"} - class="btn btn-ghost btn-xs" - aria-label={gettext("Edit membership fee type")} - > - <.icon name="hero-pencil" class="size-4" /> - + <.tooltip content={gettext("Edit membership fee type")} position="left"> + <.button + variant="ghost" + size="sm" + navigate={~p"/membership_fee_settings/#{mft.id}/edit_fee_type"} + aria-label={gettext("Edit membership fee type")} + > + <.icon name="hero-pencil" class="size-4" /> + + <:action :let={mft}> -
0} - class="tooltip tooltip-left" - data-tip={ + content={ gettext("Cannot delete - %{count} member(s) assigned", count: get_member_count(mft, @member_counts) ) } + position="left" > -
- + 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 d8569e2..72add11 100644 --- a/lib/mv_web/live/membership_fee_type_live/form.ex +++ b/lib/mv_web/live/membership_fee_type_live/form.ex @@ -176,20 +176,20 @@ defmodule MvWeb.MembershipFeeTypeLive.Form do
diff --git a/lib/mv_web/live/membership_fee_type_live/index.ex b/lib/mv_web/live/membership_fee_type_live/index.ex index f5f760f..0a17920 100644 --- a/lib/mv_web/live/membership_fee_type_live/index.ex +++ b/lib/mv_web/live/membership_fee_type_live/index.ex @@ -78,24 +78,27 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do <:action :let={mft}> - <.link - navigate={~p"/membership_fee_settings/#{mft.id}/edit_fee_type"} - class="btn btn-ghost btn-xs" - aria-label={gettext("Edit membership fee type")} - > - <.icon name="hero-pencil" class="size-4" /> - + <.tooltip content={gettext("Edit membership fee type")} position="left"> + <.button + variant="ghost" + size="sm" + navigate={~p"/membership_fee_settings/#{mft.id}/edit_fee_type"} + aria-label={gettext("Edit membership fee type")} + > + <.icon name="hero-pencil" class="size-4" /> + + <:action :let={mft}> -
0} - class="tooltip tooltip-left" - data-tip={ + content={ gettext("Cannot delete - %{count} member(s) assigned", count: get_member_count(mft, @member_counts) ) } + position="left" > -
- + diff --git a/lib/mv_web/live/role_live/form.ex b/lib/mv_web/live/role_live/form.ex index ea76fe8..ccd03cf 100644 --- a/lib/mv_web/live/role_live/form.ex +++ b/lib/mv_web/live/role_live/form.ex @@ -21,13 +21,23 @@ defmodule MvWeb.RoleLive.Form do def render(assigns) do ~H""" - <.header> - {@page_title} - <:subtitle>{gettext("Use this form to manage roles in your database.")} - - <.form class="max-w-xl" for={@form} id="role-form" phx-change="validate" phx-submit="save"> - <.input field={@form[:name]} type="text" label={gettext("Name")} required /> + <.header> + {@page_title} + <:subtitle>{gettext("Use this form to manage roles in your database.")} + <:actions> + <.button navigate={return_path(@return_to, @role)} variant="neutral"> + <.icon name="hero-arrow-left" class="size-4" /> + {gettext("Back")} + + <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> + {gettext("Save")} + + + + +
+ <.input field={@form[:name]} type="text" label={gettext("Name")} required /> <.input field={@form[:description]} @@ -73,14 +83,6 @@ defmodule MvWeb.RoleLive.Form do <% end %> <% end %>
- -
- <.button phx-disable-with={gettext("Saving...")} variant="primary" type="submit"> - {gettext("Save Role")} - - <.button navigate={return_path(@return_to, @role)} type="button"> - {gettext("Cancel")} -
diff --git a/lib/mv_web/live/role_live/index.html.heex b/lib/mv_web/live/role_live/index.html.heex index f409944..5829bca 100644 --- a/lib/mv_web/live/role_live/index.html.heex +++ b/lib/mv_web/live/role_live/index.html.heex @@ -59,28 +59,29 @@ <%= if can?(@current_user, :update, Mv.Authorization.Role) do %> - <.link navigate={~p"/admin/roles/#{role}/edit"} class="btn btn-ghost btn-sm"> + <.button variant="ghost" size="sm" navigate={~p"/admin/roles/#{role}/edit"}> <.icon name="hero-pencil" class="size-4" /> - {gettext("Edit")} - + {gettext("Edit role")} + <% end %> <:action :let={role}> <%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not role.is_system_role do %> - <.link + <.button + variant="danger" + size="sm" phx-click={JS.push("delete", value: %{id: role.id}) |> hide("#row-#{role.id}")} data-confirm={gettext("Are you sure?")} - class="btn btn-ghost btn-sm text-error" > <.icon name="hero-trash" class="size-4" /> {gettext("Delete")} - + <% else %> -
-
+ <% end %> diff --git a/lib/mv_web/live/role_live/show.ex b/lib/mv_web/live/role_live/show.ex index 0e1c7ca..4dbbb1f 100644 --- a/lib/mv_web/live/role_live/show.ex +++ b/lib/mv_web/live/role_live/show.ex @@ -165,23 +165,23 @@ defmodule MvWeb.RoleLive.Show do <:subtitle>{gettext("Role details and permissions.")} <:actions> - <.button navigate={~p"/admin/roles"} aria-label={gettext("Back to roles list")}> - <.icon name="hero-arrow-left" /> - {gettext("Back to roles list")} + <.button navigate={~p"/admin/roles"} variant="neutral" aria-label={gettext("Back to roles list")}> + <.icon name="hero-arrow-left" class="size-4" /> + {gettext("Back")} <%= if can?(@current_user, :update, Mv.Authorization.Role) do %> <.button variant="primary" navigate={~p"/admin/roles/#{@role}/edit"}> - <.icon name="hero-pencil-square" /> {gettext("Edit Role")} + <.icon name="hero-pencil-square" /> {gettext("Rolle bearbeiten")} <% end %> <%= if can?(@current_user, :destroy, Mv.Authorization.Role) and not @role.is_system_role do %> - <.link + <.button + variant="danger" phx-click={JS.push("delete", value: %{id: @role.id})} data-confirm={gettext("Are you sure?")} - class="btn btn-error" > <.icon name="hero-trash" /> {gettext("Delete Role")} - + <% end %> diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex index 46e23b3..f9f17bb 100644 --- a/lib/mv_web/live/user_live/form.ex +++ b/lib/mv_web/live/user_live/form.ex @@ -167,13 +167,14 @@ defmodule MvWeb.UserLive.Form do

{@user.member.email}

- + <% else %> @@ -281,10 +282,12 @@ 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")} - <.button navigate={return_path(@return_to, @user)}>{gettext("Cancel")}
diff --git a/lib/mv_web/live/user_live/index.html.heex b/lib/mv_web/live/user_live/index.html.heex index ab13f90..364e5a4 100644 --- a/lib/mv_web/live/user_live/index.html.heex +++ b/lib/mv_web/live/user_live/index.html.heex @@ -91,7 +91,7 @@ <%= if can?(@current_user, :update, user) do %> <.link navigate={~p"/users/#{user}/edit"} data-testid="user-edit"> - {gettext("Edit")} + {gettext("Edit user")} <% end %> diff --git a/lib/mv_web/live/user_live/show.ex b/lib/mv_web/live/user_live/show.ex index 4d803cd..3530b36 100644 --- a/lib/mv_web/live/user_live/show.ex +++ b/lib/mv_web/live/user_live/show.ex @@ -37,7 +37,7 @@ defmodule MvWeb.UserLive.Show do <:subtitle>{gettext("This is a user record from your database.")} <:actions> - <.button navigate={~p"/users"} aria-label={gettext("Back to users list")}> + <.button navigate={~p"/users"} variant="neutral" aria-label={gettext("Back to users list")}> <.icon name="hero-arrow-left" /> {gettext("Back to users list")}