diff --git a/lib/mv_web/live/join_live.ex b/lib/mv_web/live/join_live.ex
index b679127..2c37f3a 100644
--- a/lib/mv_web/live/join_live.ex
+++ b/lib/mv_web/live/join_live.ex
@@ -8,6 +8,7 @@ defmodule MvWeb.JoinLive do
alias Ash.Resource.Info
alias Mv.Membership
alias Mv.Membership.CustomFieldLookup
+ alias MvWeb.Helpers.JoinDescriptionRenderer
alias MvWeb.JoinRateLimit
alias MvWeb.Translations.MemberFields
@@ -96,14 +97,20 @@ defmodule MvWeb.JoinLive do
class="checkbox checkbox-sm"
/>
- {field.label} *
+ {render_field_label(field)} *
<% else %>
assign(:form, to_form(params, as: "join"))}
end
+ # Renders a join field's label. When a custom field has a join_description it is
+ # rendered with auto-linked URLs/Markdown; otherwise the plain field label is used.
+ # Safe: join_description is admin-set settings content, never end-user input, and
+ # JoinDescriptionRenderer escapes all non-link text (only emits
tags).
+ defp render_field_label(%{join_description: join_description})
+ when is_binary(join_description) do
+ JoinDescriptionRenderer.render(join_description)
+ end
+
+ defp render_field_label(%{label: label}), do: label
+
defp build_join_fields_with_labels(allowlist) do
member_field_strings = Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)
custom_field_by_id = custom_field_map(allowlist, member_field_strings)
@@ -249,20 +267,36 @@ defmodule MvWeb.JoinLive 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)}
+
+ %{
+ id: id,
+ label: label,
+ required: required,
+ input_type: member_field_input_type(id),
+ join_description: nil
+ }
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}
+ %{
+ id: id,
+ label: label,
+ required: required,
+ input_type: input_type,
+ join_description: custom_field && custom_field.join_description
+ }
end
end
defp custom_field_map(allowlist, _member_field_strings) do
allowlist
|> Enum.map(& &1.id)
- |> CustomFieldLookup.fetch_map_by_ids(authorize?: false, select: [:id, :name, :value_type])
+ |> CustomFieldLookup.fetch_map_by_ids(
+ authorize?: false,
+ select: [:id, :name, :value_type, :join_description]
+ )
end
defp initial_form_params(join_fields) do
diff --git a/test/mv_web/live/join_live_test.exs b/test/mv_web/live/join_live_test.exs
index d0efee9..efe03bb 100644
--- a/test/mv_web/live/join_live_test.exs
+++ b/test/mv_web/live/join_live_test.exs
@@ -165,6 +165,34 @@ defmodule MvWeb.JoinLiveTest do
custom_field.name
)
end
+
+ @tag role: :unauthenticated
+ test "renders join_description with rendered link as label when set", %{conn: conn} do
+ {:ok, settings} = Membership.get_settings()
+ system_actor = Mv.Helpers.SystemActor.get_system_actor()
+
+ {:ok, custom_field} =
+ Membership.create_custom_field(
+ %{
+ name: "DSGVO",
+ value_type: :boolean,
+ join_description: "Akzeptiere die [Datenschutzerklärung](https://example.com/dsgvo)"
+ },
+ actor: system_actor
+ )
+
+ {:ok, _} =
+ Membership.update_settings(settings, %{
+ join_form_enabled: true,
+ join_form_field_ids: ["email", custom_field.id],
+ join_form_field_required: %{"email" => true, custom_field.id => false}
+ })
+
+ {:ok, _view, html} = live(conn, "/join")
+
+ assert html =~ ~s(Datenschutzerklärung)
+ assert html =~ "Akzeptiere die"
+ end
end
describe "join field input types" do