From dd824d599e01f648d515c4d11d5f82f1d6cab5b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Tue, 2 Dec 2025 16:59:46 +0100 Subject: [PATCH 1/4] Hide OIDC ID and ID columns for users --- lib/mv_web/live/user_live/form.ex | 34 ++++++++++++----------- lib/mv_web/live/user_live/index.html.heex | 1 - lib/mv_web/live/user_live/show.ex | 8 ++---- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex index 9619a15..0639e75 100644 --- a/lib/mv_web/live/user_live/form.ex +++ b/lib/mv_web/live/user_live/form.ex @@ -42,7 +42,7 @@ defmodule MvWeb.UserLive.Form do <:subtitle>{gettext("Use this form to manage user records in your database.")} - <.form for={@form} id="user-form" phx-change="validate" phx-submit="save"> + <.form class="max-w-xl" for={@form} id="user-form" phx-change="validate" phx-submit="save"> <.input field={@form[:email]} label={gettext("Email")} required type="email" /> @@ -61,7 +61,7 @@ defmodule MvWeb.UserLive.Form do <%= if @show_password_fields do %> -
+
<.input field={@form[:password]} label={gettext("Password")} @@ -83,7 +83,7 @@ defmodule MvWeb.UserLive.Form do

{gettext("Password requirements")}:

-
    +
    • {gettext("At least 8 characters")}
    • {gettext("Include both letters and numbers")}
    • {gettext("Consider using special characters")}
    • @@ -91,7 +91,7 @@ defmodule MvWeb.UserLive.Form do
<%= if @user do %> -
+

{gettext("Admin Note")}: {gettext( "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system." @@ -102,7 +102,7 @@ defmodule MvWeb.UserLive.Form do

<% else %> <%= if @user do %> -
+

{gettext("Note")}: {gettext( "Check 'Change Password' above to set a new password for this user." @@ -110,7 +110,7 @@ defmodule MvWeb.UserLive.Form do

<% else %> -
+

{gettext("Note")}: {gettext( "User will be created without a password. Check 'Set Password' to add one." @@ -123,11 +123,11 @@ defmodule MvWeb.UserLive.Form do

-

{gettext("Linked Member")}

+

{gettext("Linked Member")}

<%= if @user && @user.member && !@unlink_member do %> -
+

@@ -147,7 +147,7 @@ defmodule MvWeb.UserLive.Form do <% else %> <%= if @unlink_member do %> -

+

{gettext("Unlinking scheduled")}: {gettext( "Member will be unlinked when you save. Cannot select new member until saved." @@ -219,7 +219,7 @@ defmodule MvWeb.UserLive.Form do

<%= if @user && @user.email && @available_members != [] && Enum.all?(@available_members, &(&1.email == to_string(@user.email))) do %> -
+

{gettext("Note")}: {gettext( "A member with this email already exists. To link with a different member, please change one of the email addresses first." @@ -231,12 +231,12 @@ defmodule MvWeb.UserLive.Form do <%= if @selected_member_id && @selected_member_name do %>

{gettext("Selected")}: {@selected_member_name}

-

+

{gettext("Save to confirm linking.")}

@@ -245,10 +245,12 @@ defmodule MvWeb.UserLive.Form do <% end %>
- <.button phx-disable-with={gettext("Saving...")} variant="primary"> - {gettext("Save User")} - - <.button navigate={return_path(@return_to, @user)}>{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 3582046..9a98159 100644 --- a/lib/mv_web/live/user_live/index.html.heex +++ b/lib/mv_web/live/user_live/index.html.heex @@ -49,7 +49,6 @@ > {user.email} - <:col :let={user} label={gettext("OIDC ID")}>{user.oidc_id} <:col :let={user} label={gettext("Linked Member")}> <%= if user.member do %> {user.member.first_name} {user.member.last_name} diff --git a/lib/mv_web/live/user_live/show.ex b/lib/mv_web/live/user_live/show.ex index 664f99f..777def1 100644 --- a/lib/mv_web/live/user_live/show.ex +++ b/lib/mv_web/live/user_live/show.ex @@ -46,9 +46,7 @@ defmodule MvWeb.UserLive.Show do <.list> - <:item title={gettext("ID")}>{@user.id} <:item title={gettext("Email")}>{@user.email} - <:item title={gettext("OIDC ID")}>{@user.oidc_id || gettext("Not set")} <:item title={gettext("Password Authentication")}> {if @user.hashed_password, do: gettext("Enabled"), else: gettext("Not enabled")} @@ -56,13 +54,13 @@ defmodule MvWeb.UserLive.Show do <%= if @user.member do %> <.link navigate={~p"/members/#{@user.member}"} - class="text-blue-600 hover:text-blue-800 underline" + class="text-blue-600 underline hover:text-blue-800" > - <.icon name="hero-users" class="h-4 w-4 inline mr-1" /> + <.icon name="hero-users" class="inline w-4 h-4 mr-1" /> {@user.member.first_name} {@user.member.last_name} <% else %> - {gettext("No member linked")} + {gettext("No member linked")} <% end %> From 05241ad292542541a37bb5bbf06748cda958efc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Tue, 2 Dec 2025 16:59:46 +0100 Subject: [PATCH 2/4] Show dates in european format --- lib/mv_web/helpers/date_formatter.ex | 27 +++++++++++++++++++ lib/mv_web/live/member_live/index.ex | 4 +++ lib/mv_web/live/member_live/index.html.heex | 2 +- .../live/member_live/index/formatter.ex | 5 ++-- lib/mv_web/live/member_live/show.ex | 23 +++++++++++----- .../index_custom_fields_display_test.exs | 4 +-- 6 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 lib/mv_web/helpers/date_formatter.ex diff --git a/lib/mv_web/helpers/date_formatter.ex b/lib/mv_web/helpers/date_formatter.ex new file mode 100644 index 0000000..eaa9271 --- /dev/null +++ b/lib/mv_web/helpers/date_formatter.ex @@ -0,0 +1,27 @@ +defmodule MvWeb.Helpers.DateFormatter do + @moduledoc """ + Centralized date formatting helper for the application. + Formats dates in European format (dd.mm.yyyy). + """ + + use Gettext, backend: MvWeb.Gettext + + @doc """ + Formats a Date struct to European format (dd.mm.yyyy). + + ## Examples + + iex> MvWeb.Helpers.DateFormatter.format_date(~D[2024-03-15]) + "15.03.2024" + + iex> MvWeb.Helpers.DateFormatter.format_date(nil) + "" + """ + def format_date(%Date{} = date) do + Calendar.strftime(date, "%d.%m.%Y") + end + + def format_date(nil), do: "" + + def format_date(_), do: "Invalid date" +end diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index 3d30d76..69a3dd6 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -32,6 +32,7 @@ defmodule MvWeb.MemberLive.Index do alias Mv.Membership alias MvWeb.MemberLive.Index.Formatter + alias MvWeb.Helpers.DateFormatter # Prefix used in sort field names for custom fields (e.g., "custom_field_") @custom_field_prefix "custom_field_" @@ -932,4 +933,7 @@ defmodule MvWeb.MemberLive.Index do Map.get(visibility_config, Atom.to_string(field), true) end) end + + # Public helper function to format dates for use in templates + def format_date(date), do: DateFormatter.format_date(date) end diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index 58e22b6..7554d6f 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -219,7 +219,7 @@ """ } > - {member.join_date} + {MvWeb.MemberLive.Index.format_date(member.join_date)} <:col :let={member} label={gettext("Paid")}> Date.to_string(date) + {:ok, date} -> DateFormatter.format_date(date) _ -> value end end diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex index de46a3a..7601f46 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -23,6 +23,7 @@ defmodule MvWeb.MemberLive.Show do """ use MvWeb, :live_view import Ash.Query + alias MvWeb.Helpers.DateFormatter @impl true def render(assigns) do @@ -52,8 +53,8 @@ defmodule MvWeb.MemberLive.Show do {if @member.paid, do: gettext("Yes"), else: gettext("No")} <:item title={gettext("Phone Number")}>{@member.phone_number} - <:item title={gettext("Join Date")}>{@member.join_date} - <:item title={gettext("Exit Date")}>{@member.exit_date} + <:item title={gettext("Join Date")}>{DateFormatter.format_date(@member.join_date)} + <:item title={gettext("Exit Date")}>{DateFormatter.format_date(@member.exit_date)} <:item title={gettext("Notes")}>{@member.notes} <:item title={gettext("City")}>{@member.city} <:item title={gettext("Street")}>{@member.street} @@ -81,10 +82,7 @@ defmodule MvWeb.MemberLive.Show do # name cfv.custom_field && cfv.custom_field.name, # value - case cfv.value do - %{value: v} -> v - v -> v - end + format_custom_field_value(cfv) } end) } /> @@ -114,4 +112,17 @@ defmodule MvWeb.MemberLive.Show do defp page_title(:show), do: gettext("Show Member") defp page_title(:edit), do: gettext("Edit Member") + + defp format_custom_field_value(cfv) do + value = + case cfv.value do + %{value: v} -> v + v -> v + end + + case value do + %Date{} = date -> DateFormatter.format_date(date) + other -> other + end + end end diff --git a/test/mv_web/member_live/index_custom_fields_display_test.exs b/test/mv_web/member_live/index_custom_fields_display_test.exs index 0485f5e..802cc8f 100644 --- a/test/mv_web/member_live/index_custom_fields_display_test.exs +++ b/test/mv_web/member_live/index_custom_fields_display_test.exs @@ -231,8 +231,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members") - # Date should be displayed in readable format - assert html =~ "1990" or html =~ "1990-05-15" or html =~ "15.05.1990" + # Date should be displayed in European format (dd.mm.yyyy) + assert html =~ "15.05.1990" end test "formats email custom field values correctly", %{conn: conn, member1: _member1} do From 70c6aff41360ee825dd1431499cc4394266875e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Tue, 2 Dec 2025 16:59:46 +0100 Subject: [PATCH 3/4] Hide OIDC ID and ID columns for users --- lib/mv_web/live/user_live/form.ex | 34 +++++++++++--------- lib/mv_web/live/user_live/index.html.heex | 1 - lib/mv_web/live/user_live/show.ex | 8 ++--- test/mv_web/live/profile_navigation_test.exs | 2 -- test/mv_web/user_live/index_test.exs | 6 ---- 5 files changed, 21 insertions(+), 30 deletions(-) diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex index 9619a15..0639e75 100644 --- a/lib/mv_web/live/user_live/form.ex +++ b/lib/mv_web/live/user_live/form.ex @@ -42,7 +42,7 @@ defmodule MvWeb.UserLive.Form do <:subtitle>{gettext("Use this form to manage user records in your database.")} - <.form for={@form} id="user-form" phx-change="validate" phx-submit="save"> + <.form class="max-w-xl" for={@form} id="user-form" phx-change="validate" phx-submit="save"> <.input field={@form[:email]} label={gettext("Email")} required type="email" /> @@ -61,7 +61,7 @@ defmodule MvWeb.UserLive.Form do <%= if @show_password_fields do %> -
+
<.input field={@form[:password]} label={gettext("Password")} @@ -83,7 +83,7 @@ defmodule MvWeb.UserLive.Form do

{gettext("Password requirements")}:

-
    +
    • {gettext("At least 8 characters")}
    • {gettext("Include both letters and numbers")}
    • {gettext("Consider using special characters")}
    • @@ -91,7 +91,7 @@ defmodule MvWeb.UserLive.Form do
<%= if @user do %> -
+

{gettext("Admin Note")}: {gettext( "As an administrator, you can directly set a new password for this user using the same secure Ash Authentication system." @@ -102,7 +102,7 @@ defmodule MvWeb.UserLive.Form do

<% else %> <%= if @user do %> -
+

{gettext("Note")}: {gettext( "Check 'Change Password' above to set a new password for this user." @@ -110,7 +110,7 @@ defmodule MvWeb.UserLive.Form do

<% else %> -
+

{gettext("Note")}: {gettext( "User will be created without a password. Check 'Set Password' to add one." @@ -123,11 +123,11 @@ defmodule MvWeb.UserLive.Form do

-

{gettext("Linked Member")}

+

{gettext("Linked Member")}

<%= if @user && @user.member && !@unlink_member do %> -
+

@@ -147,7 +147,7 @@ defmodule MvWeb.UserLive.Form do <% else %> <%= if @unlink_member do %> -

+

{gettext("Unlinking scheduled")}: {gettext( "Member will be unlinked when you save. Cannot select new member until saved." @@ -219,7 +219,7 @@ defmodule MvWeb.UserLive.Form do

<%= if @user && @user.email && @available_members != [] && Enum.all?(@available_members, &(&1.email == to_string(@user.email))) do %> -
+

{gettext("Note")}: {gettext( "A member with this email already exists. To link with a different member, please change one of the email addresses first." @@ -231,12 +231,12 @@ defmodule MvWeb.UserLive.Form do <%= if @selected_member_id && @selected_member_name do %>

{gettext("Selected")}: {@selected_member_name}

-

+

{gettext("Save to confirm linking.")}

@@ -245,10 +245,12 @@ defmodule MvWeb.UserLive.Form do <% end %>
- <.button phx-disable-with={gettext("Saving...")} variant="primary"> - {gettext("Save User")} - - <.button navigate={return_path(@return_to, @user)}>{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 3582046..9a98159 100644 --- a/lib/mv_web/live/user_live/index.html.heex +++ b/lib/mv_web/live/user_live/index.html.heex @@ -49,7 +49,6 @@ > {user.email} - <:col :let={user} label={gettext("OIDC ID")}>{user.oidc_id} <:col :let={user} label={gettext("Linked Member")}> <%= if user.member do %> {user.member.first_name} {user.member.last_name} diff --git a/lib/mv_web/live/user_live/show.ex b/lib/mv_web/live/user_live/show.ex index 664f99f..777def1 100644 --- a/lib/mv_web/live/user_live/show.ex +++ b/lib/mv_web/live/user_live/show.ex @@ -46,9 +46,7 @@ defmodule MvWeb.UserLive.Show do <.list> - <:item title={gettext("ID")}>{@user.id} <:item title={gettext("Email")}>{@user.email} - <:item title={gettext("OIDC ID")}>{@user.oidc_id || gettext("Not set")} <:item title={gettext("Password Authentication")}> {if @user.hashed_password, do: gettext("Enabled"), else: gettext("Not enabled")} @@ -56,13 +54,13 @@ defmodule MvWeb.UserLive.Show do <%= if @user.member do %> <.link navigate={~p"/members/#{@user.member}"} - class="text-blue-600 hover:text-blue-800 underline" + class="text-blue-600 underline hover:text-blue-800" > - <.icon name="hero-users" class="h-4 w-4 inline mr-1" /> + <.icon name="hero-users" class="inline w-4 h-4 mr-1" /> {@user.member.first_name} {@user.member.last_name} <% else %> - {gettext("No member linked")} + {gettext("No member linked")} <% end %> diff --git a/test/mv_web/live/profile_navigation_test.exs b/test/mv_web/live/profile_navigation_test.exs index 3222825..5ba5eb0 100644 --- a/test/mv_web/live/profile_navigation_test.exs +++ b/test/mv_web/live/profile_navigation_test.exs @@ -90,8 +90,6 @@ defmodule MvWeb.ProfileNavigationTest do # Verify we're on the correct profile page with OIDC specific information {:ok, _profile_view, html} = live(conn, "/users/#{user.id}") assert html =~ to_string(user.email) - # OIDC ID should be visible - assert html =~ "oidc_123" # Password auth should be disabled for OIDC users assert html =~ "Not enabled" end diff --git a/test/mv_web/user_live/index_test.exs b/test/mv_web/user_live/index_test.exs index c0b0275..360ef72 100644 --- a/test/mv_web/user_live/index_test.exs +++ b/test/mv_web/user_live/index_test.exs @@ -33,8 +33,6 @@ defmodule MvWeb.UserLive.IndexTest do assert html =~ "alice@example.com" assert html =~ "bob@example.com" - assert html =~ "alice123" - assert html =~ "bob456" end test "shows correct action links", %{conn: conn} do @@ -386,10 +384,6 @@ defmodule MvWeb.UserLive.IndexTest do # Should still show the table structure assert html =~ "Email" - assert html =~ "OIDC ID" - # Should show the authenticated user at minimum - # Matches the generated email pattern oidc.user{unique_id}@example.com - assert html =~ "oidc.user" end test "handles users with missing OIDC ID", %{conn: conn} do From 960e15db292fc46c7aadfe3779d37a7931b9a28d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Eppl=C3=A9e?= Date: Tue, 2 Dec 2025 16:59:46 +0100 Subject: [PATCH 4/4] Show dates in european format --- lib/mv_web/helpers/date_formatter.ex | 27 +++++++++++++++++++ lib/mv_web/live/member_live/index.ex | 4 +++ lib/mv_web/live/member_live/index.html.heex | 2 +- .../live/member_live/index/formatter.ex | 5 ++-- lib/mv_web/live/member_live/show.ex | 23 +++++++++++----- .../index_custom_fields_display_test.exs | 4 +-- 6 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 lib/mv_web/helpers/date_formatter.ex diff --git a/lib/mv_web/helpers/date_formatter.ex b/lib/mv_web/helpers/date_formatter.ex new file mode 100644 index 0000000..eaa9271 --- /dev/null +++ b/lib/mv_web/helpers/date_formatter.ex @@ -0,0 +1,27 @@ +defmodule MvWeb.Helpers.DateFormatter do + @moduledoc """ + Centralized date formatting helper for the application. + Formats dates in European format (dd.mm.yyyy). + """ + + use Gettext, backend: MvWeb.Gettext + + @doc """ + Formats a Date struct to European format (dd.mm.yyyy). + + ## Examples + + iex> MvWeb.Helpers.DateFormatter.format_date(~D[2024-03-15]) + "15.03.2024" + + iex> MvWeb.Helpers.DateFormatter.format_date(nil) + "" + """ + def format_date(%Date{} = date) do + Calendar.strftime(date, "%d.%m.%Y") + end + + def format_date(nil), do: "" + + def format_date(_), do: "Invalid date" +end diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index 3d30d76..69a3dd6 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -32,6 +32,7 @@ defmodule MvWeb.MemberLive.Index do alias Mv.Membership alias MvWeb.MemberLive.Index.Formatter + alias MvWeb.Helpers.DateFormatter # Prefix used in sort field names for custom fields (e.g., "custom_field_") @custom_field_prefix "custom_field_" @@ -932,4 +933,7 @@ defmodule MvWeb.MemberLive.Index do Map.get(visibility_config, Atom.to_string(field), true) end) end + + # Public helper function to format dates for use in templates + def format_date(date), do: DateFormatter.format_date(date) end diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index 58e22b6..7554d6f 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -219,7 +219,7 @@ """ } > - {member.join_date} + {MvWeb.MemberLive.Index.format_date(member.join_date)} <:col :let={member} label={gettext("Paid")}> Date.to_string(date) + {:ok, date} -> DateFormatter.format_date(date) _ -> value end end diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex index de46a3a..7601f46 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -23,6 +23,7 @@ defmodule MvWeb.MemberLive.Show do """ use MvWeb, :live_view import Ash.Query + alias MvWeb.Helpers.DateFormatter @impl true def render(assigns) do @@ -52,8 +53,8 @@ defmodule MvWeb.MemberLive.Show do {if @member.paid, do: gettext("Yes"), else: gettext("No")} <:item title={gettext("Phone Number")}>{@member.phone_number} - <:item title={gettext("Join Date")}>{@member.join_date} - <:item title={gettext("Exit Date")}>{@member.exit_date} + <:item title={gettext("Join Date")}>{DateFormatter.format_date(@member.join_date)} + <:item title={gettext("Exit Date")}>{DateFormatter.format_date(@member.exit_date)} <:item title={gettext("Notes")}>{@member.notes} <:item title={gettext("City")}>{@member.city} <:item title={gettext("Street")}>{@member.street} @@ -81,10 +82,7 @@ defmodule MvWeb.MemberLive.Show do # name cfv.custom_field && cfv.custom_field.name, # value - case cfv.value do - %{value: v} -> v - v -> v - end + format_custom_field_value(cfv) } end) } /> @@ -114,4 +112,17 @@ defmodule MvWeb.MemberLive.Show do defp page_title(:show), do: gettext("Show Member") defp page_title(:edit), do: gettext("Edit Member") + + defp format_custom_field_value(cfv) do + value = + case cfv.value do + %{value: v} -> v + v -> v + end + + case value do + %Date{} = date -> DateFormatter.format_date(date) + other -> other + end + end end diff --git a/test/mv_web/member_live/index_custom_fields_display_test.exs b/test/mv_web/member_live/index_custom_fields_display_test.exs index 0485f5e..802cc8f 100644 --- a/test/mv_web/member_live/index_custom_fields_display_test.exs +++ b/test/mv_web/member_live/index_custom_fields_display_test.exs @@ -231,8 +231,8 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do conn = conn_with_oidc_user(conn) {:ok, _view, html} = live(conn, "/members") - # Date should be displayed in readable format - assert html =~ "1990" or html =~ "1990-05-15" or html =~ "15.05.1990" + # Date should be displayed in European format (dd.mm.yyyy) + assert html =~ "15.05.1990" end test "formats email custom field values correctly", %{conn: conn, member1: _member1} do