From 6327ea00ebe0cb1488f30fd3bfb37f55b14a7f69 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 6 May 2026 11:37:40 +0200 Subject: [PATCH] feat: respect field types in join requests --- lib/mv_web/live/join_live.ex | 110 +++++++++++++++++++++------- test/mv_web/live/join_live_test.exs | 36 ++++++++- 2 files changed, 115 insertions(+), 31 deletions(-) diff --git a/lib/mv_web/live/join_live.ex b/lib/mv_web/live/join_live.ex index d3d66f0..430a6fe 100644 --- a/lib/mv_web/live/join_live.ex +++ b/lib/mv_web/live/join_live.ex @@ -5,6 +5,7 @@ defmodule MvWeb.JoinLive do """ use MvWeb, :live_view + alias Ash.Resource.Info alias Mv.Membership alias MvWeb.JoinRateLimit alias MvWeb.Translations.MemberFields @@ -54,10 +55,6 @@ defmodule MvWeb.JoinLive do {gettext("Become a member")} -

- {gettext("Please enter your details for the membership application here.")} -

- <%= if @submitted do %>

@@ -67,6 +64,9 @@ defmodule MvWeb.JoinLive do

<% else %> +

+ {gettext("Please enter your details for the membership application here.")} +

<.form for={@form} id="join-form" @@ -80,18 +80,31 @@ defmodule MvWeb.JoinLive do <% end %> <%= for field <- @join_fields do %> -
+
- + <%= if field.input_type == "checkbox" do %> + + <% else %> + + <% end %>
<% end %> @@ -216,21 +229,27 @@ defmodule MvWeb.JoinLive do defp build_join_fields_with_labels(allowlist) do member_field_strings = Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1) - custom_field_name_by_id = custom_field_name_map(allowlist, member_field_strings) + custom_field_by_id = custom_field_map(allowlist, member_field_strings) Enum.map(allowlist, fn %{id: id, required: required} -> - label = - if id in member_field_strings do - MemberFields.label(String.to_existing_atom(id)) - else - Map.get(custom_field_name_by_id, id, gettext("Field")) - end - - %{id: id, label: label, required: required} + build_join_field(id, required, member_field_strings, custom_field_by_id) end) end - defp custom_field_name_map(allowlist, member_field_strings) do + defp build_join_field(id, required, member_field_strings, custom_field_by_id) do + if id in member_field_strings do + label = MemberFields.label(String.to_existing_atom(id)) + %{id: id, label: label, required: required, input_type: member_field_input_type(id)} + else + custom_field = Map.get(custom_field_by_id, id) + label = if custom_field, do: custom_field.name, else: gettext("Field") + input_type = custom_field_input_type(custom_field && custom_field.value_type) + + %{id: id, label: label, required: required, input_type: input_type} + end + end + + defp custom_field_map(allowlist, member_field_strings) do custom_field_ids = allowlist |> Enum.map(& &1.id) @@ -242,7 +261,7 @@ defmodule MvWeb.JoinLive do ids -> Mv.Membership.CustomField - |> Ash.Query.select([:id, :name]) + |> Ash.Query.select([:id, :name, :value_type]) |> Ash.read(domain: Mv.Membership, authorize?: false) |> case do {:ok, fields} -> @@ -250,7 +269,7 @@ defmodule MvWeb.JoinLive do fields |> Enum.filter(&MapSet.member?(allowed_ids, &1.id)) - |> Map.new(&{&1.id, &1.name}) + |> Map.new(&{&1.id, &1}) {:error, _} -> %{} @@ -265,8 +284,45 @@ defmodule MvWeb.JoinLive do |> Map.put(@honeypot_field, "") end - defp input_type("email"), do: "email" - defp input_type(_), do: "text" + defp member_field_input_type("email"), do: "email" + + defp member_field_input_type(field_id) when is_binary(field_id) do + case member_field_atom(field_id) do + nil -> + "text" + + field_atom -> + Mv.Membership.Member + |> Info.attribute(field_atom) + |> attribute_to_input_type() + end + end + + defp member_field_input_type(_), do: "text" + + defp member_field_atom(field_id) when is_binary(field_id) do + Mv.Constants.member_fields() + |> Enum.find(&(Atom.to_string(&1) == field_id)) + end + + defp custom_field_input_type(type), do: attribute_to_input_type(%{type: type}) + + defp attribute_to_input_type(%{type: type}) when type in [:date, Ash.Type.Date], do: "date" + + defp attribute_to_input_type(%{type: type}) when type in [:integer, Ash.Type.Integer], + do: "number" + + defp attribute_to_input_type(%{type: type}) when type in [:boolean, Ash.Type.Boolean], + do: "checkbox" + + defp attribute_to_input_type(%{type: type}) when type in [:email, Mv.Membership.Email], + do: "email" + + defp attribute_to_input_type(%{type: _}), do: "text" + defp attribute_to_input_type(nil), do: "text" + + defp checkbox_checked?(value) when value in [true, "true", "on", "1"], do: true + defp checkbox_checked?(_), do: false defp build_submit_attrs(params, join_fields) do allowlist_ids = MapSet.new(Enum.map(join_fields, & &1.id)) diff --git a/test/mv_web/live/join_live_test.exs b/test/mv_web/live/join_live_test.exs index 20cd5cf..7bac60f 100644 --- a/test/mv_web/live/join_live_test.exs +++ b/test/mv_web/live/join_live_test.exs @@ -192,7 +192,12 @@ defmodule MvWeb.JoinLiveTest do {:ok, view, _html} = live(conn, "/join") assert has_element?(view, "#join-form") - assert has_element?(view, "input#join-field-#{boolean_field.id}[name='#{boolean_field.id}']") + + assert has_element?( + view, + "input#join-field-#{boolean_field.id}[name='#{boolean_field.id}']" + ) + assert has_element?(view, "input#join-field-#{boolean_field.id}[type='checkbox']") refute has_element?(view, "input#join-field-#{boolean_field.id}[type='text']") end @@ -203,13 +208,19 @@ defmodule MvWeb.JoinLiveTest do {:ok, settings} = Membership.get_settings() {:ok, integer_field} = - Membership.create_custom_field(%{name: "Lucky number", value_type: :integer}, actor: system_actor) + Membership.create_custom_field(%{name: "Lucky number", value_type: :integer}, + actor: system_actor + ) {:ok, date_field} = - Membership.create_custom_field(%{name: "Birth date", value_type: :date}, actor: system_actor) + Membership.create_custom_field(%{name: "Birth date", value_type: :date}, + actor: system_actor + ) {:ok, email_field} = - Membership.create_custom_field(%{name: "Secondary email", value_type: :email}, actor: system_actor) + Membership.create_custom_field(%{name: "Secondary email", value_type: :email}, + actor: system_actor + ) {:ok, _} = Membership.update_settings(settings, %{ @@ -229,6 +240,23 @@ defmodule MvWeb.JoinLiveTest do assert has_element?(view, "input#join-field-#{date_field.id}[type='date']") assert has_element?(view, "input#join-field-#{email_field.id}[type='email']") end + + @tag role: :unauthenticated + test "renders standard date member fields with date input type", %{conn: conn} do + {:ok, settings} = Membership.get_settings() + + {:ok, _} = + Membership.update_settings(settings, %{ + join_form_enabled: true, + join_form_field_ids: ["email", "join_date"], + join_form_field_required: %{"email" => true, "join_date" => false} + }) + + {:ok, view, _html} = live(conn, "/join") + + assert has_element?(view, "input#join-field-join_date[type='date']") + refute has_element?(view, "input#join-field-join_date[type='text']") + end end describe "submit join form with typed custom fields" do