Compare commits

..

1 commit

Author SHA1 Message Date
Renovate Bot
3dbd9d2164 chore(deps): update mix dependencies to v1
All checks were successful
continuous-integration/drone/push Build is passing
2026-05-06 00:10:19 +00:00
16 changed files with 139 additions and 785 deletions

View file

@ -284,7 +284,7 @@ environment:
steps: steps:
- name: renovate - name: renovate
image: renovate/renovate:43.165 image: renovate/renovate:43.163
environment: environment:
RENOVATE_CONFIG_FILE: "renovate_backend_config.js" RENOVATE_CONFIG_FILE: "renovate_backend_config.js"
RENOVATE_TOKEN: RENOVATE_TOKEN:

View file

@ -7,22 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Changed
- **Join request display and settings workflow** Improved join request rendering and related settings behavior in one cohesive update:
- Join request fields now respect their configured field types in the details view.
- Custom field labels in join request views were standardized.
- Join request field formatting was corrected for more consistent output.
- Join link settings now include a direct "Open" action in addition to copy/share workflows.
### Fixed ### Fixed
- **Runtime ENV handling** Empty or invalid environment variables (e.g. `SMTP_PORT=`, `PORT=`, `POOL_SIZE=`, `DATABASE_PORT=`) no longer cause `ArgumentError` at boot. Instead raises clear errors for required vars set but empty (e.g. DATABASE_HOST, PHX_HOST/DOMAIN, SECRET_KEY_BASE). - **Runtime ENV handling** Empty or invalid environment variables (e.g. `SMTP_PORT=`, `PORT=`, `POOL_SIZE=`, `DATABASE_PORT=`) no longer cause `ArgumentError` at boot. Instead raises clear errors for required vars set but empty (e.g. DATABASE_HOST, PHX_HOST/DOMAIN, SECRET_KEY_BASE).
- **PostgreSQL 18 Docker volume path** Corrected the database volume path to match PostgreSQL 18 expectations.
### Dependency updates
- Mix dependencies were updated.
- Renovate Docker image was updated to `v43.165`.
- Rauthy Docker image was updated to `v0.35.1`.
- `just` was updated to `v1.50.0`.
## [1.1.1] - 2026-03-16 ## [1.1.1] - 2026-03-16

View file

@ -1,56 +0,0 @@
defmodule Mv.Membership.CustomFieldLookup do
@moduledoc """
Shared helper for loading custom fields by ID.
"""
alias Mv.Constants
alias Mv.Membership
@spec fetch_map_by_ids([String.t()], keyword()) :: map()
def fetch_map_by_ids(field_ids, opts \\ []) when is_list(field_ids) do
member_field_strings = Constants.member_fields() |> Enum.map(&Atom.to_string/1)
custom_field_ids =
field_ids
|> Enum.uniq()
|> Enum.reject(&(&1 in member_field_strings))
if custom_field_ids == [] do
%{}
else
select = Keyword.get(opts, :select, [:id, :name, :value_type])
query =
Membership.CustomField
|> Ash.Query.select(select)
read_opts =
[domain: Membership]
|> maybe_put_actor(opts)
|> maybe_put_authorize(opts)
case Ash.read(query, read_opts) do
{:ok, fields} ->
allowed_ids = MapSet.new(custom_field_ids)
fields |> Enum.filter(&MapSet.member?(allowed_ids, &1.id)) |> Map.new(&{&1.id, &1})
{:error, _} ->
%{}
end
end
end
defp maybe_put_actor(opts, read_opts) do
case Keyword.fetch(read_opts, :actor) do
{:ok, actor} -> Keyword.put(opts, :actor, actor)
:error -> opts
end
end
defp maybe_put_authorize(opts, read_opts) do
case Keyword.fetch(read_opts, :authorize?) do
{:ok, authorize?} -> Keyword.put(opts, :authorize?, authorize?)
:error -> opts
end
end
end

View file

@ -138,7 +138,7 @@ defmodule MvWeb.Layouts do
# Single get_settings() for layout; derive club_name and join_form_enabled to avoid duplicate query. # Single get_settings() for layout; derive club_name and join_form_enabled to avoid duplicate query.
%{club_name: club_name, join_form_enabled: join_form_enabled} = get_layout_settings() %{club_name: club_name, join_form_enabled: join_form_enabled} = get_layout_settings()
# NOTE: Unprocessed count runs on every page load when join form is enabled; consider # TODO: unprocessed count runs on every page load when join form enabled; consider
# loading only on navigation or caching briefly if performance becomes an issue. # loading only on navigation or caching briefly if performance becomes an issue.
unprocessed_join_requests_count = unprocessed_join_requests_count =
get_unprocessed_join_requests_count(assigns.current_user, join_form_enabled) get_unprocessed_join_requests_count(assigns.current_user, join_form_enabled)

View file

@ -186,16 +186,6 @@ defmodule MvWeb.GlobalSettingsLive do
<.icon name="hero-clipboard-document" class="size-4" /> <.icon name="hero-clipboard-document" class="size-4" />
{gettext("Copy")} {gettext("Copy")}
</.button> </.button>
<.link
href={@join_url}
target="_blank"
rel="noopener noreferrer"
class="btn btn-secondary btn-sm"
>
<.icon name="hero-arrow-top-right-on-square" class="size-4" aria-hidden="true" />
{pgettext("action", "Open")}
<span class="sr-only">{gettext("join page URL in a new tab")}</span>
</.link>
</div> </div>
</div> </div>

View file

@ -5,9 +5,7 @@ defmodule MvWeb.JoinLive do
""" """
use MvWeb, :live_view use MvWeb, :live_view
alias Ash.Resource.Info
alias Mv.Membership alias Mv.Membership
alias Mv.Membership.CustomFieldLookup
alias MvWeb.JoinRateLimit alias MvWeb.JoinRateLimit
alias MvWeb.Translations.MemberFields alias MvWeb.Translations.MemberFields
@ -56,6 +54,10 @@ defmodule MvWeb.JoinLive do
{gettext("Become a member")} {gettext("Become a member")}
</.header> </.header>
<p class="text-base-content/80">
{gettext("Please enter your details for the membership application here.")}
</p>
<%= if @submitted do %> <%= if @submitted do %>
<div data-testid="join-success-message" class="alert alert-success"> <div data-testid="join-success-message" class="alert alert-success">
<p class="font-medium"> <p class="font-medium">
@ -65,9 +67,6 @@ defmodule MvWeb.JoinLive do
</p> </p>
</div> </div>
<% else %> <% else %>
<p class="text-base-content/80">
{gettext("Please enter your details for the membership application here.")}
</p>
<.form <.form
for={@form} for={@form}
id="join-form" id="join-form"
@ -81,41 +80,19 @@ defmodule MvWeb.JoinLive do
<% end %> <% end %>
<%= for field <- @join_fields do %> <%= for field <- @join_fields do %>
<%= if field.input_type == "checkbox" do %> <div>
<input type="hidden" name={field.id} value="off" /> <label for={"join-field-#{field.id}"} class="label">
<label <span class="label-text">{field.label}{if field.required, do: " *"}</span>
for={"join-field-#{field.id}"}
class="label cursor-pointer justify-start gap-3"
>
<input
type="checkbox"
name={field.id}
id={"join-field-#{field.id}"}
checked={checkbox_checked?(@form.params[field.id])}
required={field.required}
class="checkbox checkbox-sm"
/>
<span class="label-text">
{field.label}<span :if={field.required} aria-hidden="true"> *</span>
</span>
</label> </label>
<% else %> <input
<div> type={input_type(field.id)}
<label for={"join-field-#{field.id}"} class="label"> name={field.id}
<span class="label-text"> id={"join-field-#{field.id}"}
{field.label}<span :if={field.required} aria-hidden="true"> *</span> value={@form.params[field.id]}
</span> required={field.required}
</label> class="input input-bordered w-full"
<input />
type={field.input_type} </div>
name={field.id}
id={"join-field-#{field.id}"}
value={@form.params[field.id]}
required={field.required}
class="input input-bordered w-full"
/>
</div>
<% end %>
<% end %> <% end %>
<%!-- <%!--
@ -239,32 +216,19 @@ defmodule MvWeb.JoinLive do
defp build_join_fields_with_labels(allowlist) do defp build_join_fields_with_labels(allowlist) do
member_field_strings = Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1) member_field_strings = Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)
custom_field_by_id = custom_field_map(allowlist, member_field_strings)
Enum.map(allowlist, fn %{id: id, required: required} -> Enum.map(allowlist, fn %{id: id, required: required} ->
build_join_field(id, required, member_field_strings, custom_field_by_id) label =
if id in member_field_strings do
MemberFields.label(String.to_existing_atom(id))
else
gettext("Field")
end
%{id: id, label: label, required: required}
end) end)
end end
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
allowlist
|> Enum.map(& &1.id)
|> CustomFieldLookup.fetch_map_by_ids(authorize?: false, select: [:id, :name, :value_type])
end
defp initial_form_params(join_fields) do defp initial_form_params(join_fields) do
join_fields join_fields
|> Enum.map(fn f -> {f.id, ""} end) |> Enum.map(fn f -> {f.id, ""} end)
@ -272,42 +236,8 @@ defmodule MvWeb.JoinLive do
|> Map.put(@honeypot_field, "") |> Map.put(@honeypot_field, "")
end end
defp member_field_input_type("email"), do: "email" defp input_type("email"), do: "email"
defp input_type(_), do: "text"
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)
|> Map.get(:type)
|> input_type_for()
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: input_type_for(type)
defp input_type_for(:date), do: "date"
defp input_type_for(Ash.Type.Date), do: "date"
defp input_type_for(:integer), do: "number"
defp input_type_for(Ash.Type.Integer), do: "number"
defp input_type_for(:boolean), do: "checkbox"
defp input_type_for(Ash.Type.Boolean), do: "checkbox"
defp input_type_for(:email), do: "email"
defp input_type_for(Mv.Membership.Email), do: "email"
defp input_type_for(_), 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 defp build_submit_attrs(params, join_fields) do
allowlist_ids = MapSet.new(Enum.map(join_fields, & &1.id)) allowlist_ids = MapSet.new(Enum.map(join_fields, & &1.id))
@ -327,12 +257,9 @@ defmodule MvWeb.JoinLive do
} }
form_data = form_data =
join_fields params
|> Enum.filter(&(&1.id not in typed)) |> Enum.filter(fn {key, _} -> key in allowlist_ids and key not in typed end)
|> Map.new(fn field -> |> Map.new(fn {k, v} -> {k, String.trim(to_string(v))} end)
{field.id, normalize_join_field_value(params[field.id], field.input_type)}
end)
|> Map.take(MapSet.to_list(allowlist_ids))
attrs = %{attrs | form_data: form_data} attrs = %{attrs | form_data: form_data}
{:ok, attrs} {:ok, attrs}
@ -344,10 +271,6 @@ defmodule MvWeb.JoinLive do
if is_binary(v), do: String.trim(v), else: nil if is_binary(v), do: String.trim(v), else: nil
end end
defp normalize_join_field_value(raw, _input_type) when is_binary(raw), do: String.trim(raw)
defp normalize_join_field_value(_raw, "checkbox"), do: "off"
defp normalize_join_field_value(_raw, _input_type), do: ""
# Prefer X-Forwarded-For / X-Real-IP when behind a reverse proxy; fall back to peer_data. # Prefer X-Forwarded-For / X-Real-IP when behind a reverse proxy; fall back to peer_data.
# Uses :inet.ntoa/1 for correct IPv4 and IPv6 string representation. # Uses :inet.ntoa/1 for correct IPv4 and IPv6 string representation.
defp client_ip_from_socket(socket) do defp client_ip_from_socket(socket) do

View file

@ -21,7 +21,6 @@ defmodule MvWeb.JoinRequestLive.Show do
alias Mv.Constants alias Mv.Constants
alias Mv.Membership alias Mv.Membership
alias Mv.Membership.CustomFieldLookup
alias MvWeb.Helpers.DateFormatter alias MvWeb.Helpers.DateFormatter
alias MvWeb.JoinRequestLive.Helpers, as: JoinRequestHelpers alias MvWeb.JoinRequestLive.Helpers, as: JoinRequestHelpers
alias MvWeb.Translations.MemberFields, as: MemberFieldsTranslations alias MvWeb.Translations.MemberFields, as: MemberFieldsTranslations
@ -32,7 +31,6 @@ defmodule MvWeb.JoinRequestLive.Show do
{:ok, {:ok,
socket socket
|> assign(:join_request, nil) |> assign(:join_request, nil)
|> assign(:custom_field_by_id, %{})
|> assign(:join_form_field_ids, []) |> assign(:join_form_field_ids, [])
|> Layouts.assign_page_title(gettext("Join request"))} |> Layouts.assign_page_title(gettext("Join request"))}
else else
@ -55,16 +53,9 @@ defmodule MvWeb.JoinRequestLive.Show do
{:ok, request} -> {:ok, request} ->
field_ids = Membership.get_join_form_allowlist() |> Enum.map(& &1.id) field_ids = Membership.get_join_form_allowlist() |> Enum.map(& &1.id)
custom_field_by_id =
CustomFieldLookup.fetch_map_by_ids(field_ids ++ Map.keys(request.form_data || %{}),
actor: actor,
select: [:id, :name, :value_type]
)
{:noreply, {:noreply,
socket socket
|> assign(:join_request, request) |> assign(:join_request, request)
|> assign(:custom_field_by_id, custom_field_by_id)
|> assign(:join_form_field_ids, field_ids) |> assign(:join_form_field_ids, field_ids)
|> Layouts.assign_page_title(gettext("Join request %{email}", email: request.email))} |> Layouts.assign_page_title(gettext("Join request %{email}", email: request.email))}
@ -140,58 +131,52 @@ defmodule MvWeb.JoinRequestLive.Show do
<%!-- Single block: all applicant-provided data in join form order --%> <%!-- Single block: all applicant-provided data in join form order --%>
<div> <div>
<h2 class="text-lg font-semibold mb-2">{gettext("Applicant data")}</h2> <h2 class="text-lg font-semibold mb-2">{gettext("Applicant data")}</h2>
<div class="border border-base-300 rounded-lg p-4 bg-base-100"> <div class="border border-base-300 rounded-lg p-4 bg-base-100 space-y-2">
<dl class="grid gap-1 md:grid-cols-[14rem_minmax(0,1fr)] md:gap-2"> <%= for {label, value} <- applicant_data_rows(@join_request, @join_form_field_ids || []) do %>
<%= for {label, value} <- <.field_row label={label} value={value} empty_text={gettext("Not specified")} />
applicant_data_rows( <% end %>
@join_request,
@join_form_field_ids || [],
@custom_field_by_id || %{}
) do %>
<.field_row label={label} value={value} empty_text={gettext("Not specified")} />
<% end %>
</dl>
</div> </div>
</div> </div>
<%!-- Status and review (submitted_at, status; if decided: approved/rejected at, reviewed by) --%> <%!-- Status and review (submitted_at, status; if decided: approved/rejected at, reviewed by) --%>
<div> <div>
<h2 class="text-lg font-semibold mb-2">{gettext("Status and review")}</h2> <h2 class="text-lg font-semibold mb-2">{gettext("Status and review")}</h2>
<div class="border border-base-300 rounded-lg p-4 bg-base-100"> <div class="border border-base-300 rounded-lg p-4 bg-base-100 space-y-2">
<dl class="grid gap-1 md:grid-cols-[14rem_minmax(0,1fr)] md:gap-2"> <.field_row
<.field_row label={gettext("Submitted at")}
label={gettext("Submitted at")} value={DateFormatter.format_datetime(@join_request.submitted_at, @browser_timezone)}
value={DateFormatter.format_datetime(@join_request.submitted_at, @browser_timezone)} />
/> <div class="flex gap-2">
<.field_row label={gettext("Status")}> <span class="text-base-content/60 min-w-32 shrink-0">{gettext("Status")}:</span>
<span>
<.badge variant={JoinRequestHelpers.status_badge_variant(@join_request.status)}> <.badge variant={JoinRequestHelpers.status_badge_variant(@join_request.status)}>
{JoinRequestHelpers.format_status(@join_request.status)} {JoinRequestHelpers.format_status(@join_request.status)}
</.badge> </.badge>
</.field_row> </span>
<%= if @join_request.status in [:approved, :rejected] do %> </div>
<%= if @join_request.approved_at do %> <%= if @join_request.status in [:approved, :rejected] do %>
<.field_row <%= if @join_request.approved_at do %>
label={gettext("Approved at")}
value={
DateFormatter.format_datetime(@join_request.approved_at, @browser_timezone)
}
/>
<% end %>
<%= if @join_request.rejected_at do %>
<.field_row
label={gettext("Rejected at")}
value={
DateFormatter.format_datetime(@join_request.rejected_at, @browser_timezone)
}
/>
<% end %>
<.field_row <.field_row
label={gettext("Review by")} label={gettext("Approved at")}
value={JoinRequestHelpers.reviewer_display(@join_request)} value={
empty_text="-" DateFormatter.format_datetime(@join_request.approved_at, @browser_timezone)
}
/> />
<% end %> <% end %>
</dl> <%= if @join_request.rejected_at do %>
<.field_row
label={gettext("Rejected at")}
value={
DateFormatter.format_datetime(@join_request.rejected_at, @browser_timezone)
}
/>
<% end %>
<.field_row
label={gettext("Review by")}
value={JoinRequestHelpers.reviewer_display(@join_request)}
empty_text="-"
/>
<% end %>
</div> </div>
</div> </div>
@ -224,30 +209,28 @@ defmodule MvWeb.JoinRequestLive.Show do
attr :label, :string, required: true attr :label, :string, required: true
attr :value, :any, default: nil attr :value, :any, default: nil
attr :empty_text, :string, default: nil attr :empty_text, :string, default: nil
slot :inner_block
defp field_row(assigns) do defp field_row(assigns) do
~H""" ~H"""
<dt class="m-0 text-base-content/60 whitespace-normal break-words">{@label}:</dt> <div class="flex gap-2">
<dd class="m-0 min-w-0"> <span class="text-base-content/60 min-w-32 shrink-0">{@label}:</span>
<%= cond do %> <span>
<% @inner_block != [] -> %> <%= if @value && @value != "" do %>
{render_slot(@inner_block)}
<% @value && @value != "" -> %>
{@value} {@value}
<% true -> %> <% else %>
<span class="text-base-content/40 italic"> <span class="text-base-content/40 italic">
{@empty_text || gettext("Not specified")} {@empty_text || gettext("Not specified")}
</span> </span>
<% end %> <% end %>
</dd> </span>
</div>
""" """
end end
# Builds a single list of {label, display_value} for all applicant-provided data in join form # Builds a single list of {label, display_value} for all applicant-provided data in join form
# order. Typed fields (email, first_name, last_name) and form_data are merged; legacy # order. Typed fields (email, first_name, last_name) and form_data are merged; legacy
# form_data keys (not in current join form config) are appended at the end. # form_data keys (not in current join form config) are appended at the end.
defp applicant_data_rows(join_request, ordered_field_ids, custom_field_by_id) do defp applicant_data_rows(join_request, ordered_field_ids) do
member_field_strings = Constants.member_fields() |> Enum.map(&Atom.to_string/1) member_field_strings = Constants.member_fields() |> Enum.map(&Atom.to_string/1)
form_data = join_request.form_data || %{} form_data = join_request.form_data || %{}
@ -261,9 +244,8 @@ defmodule MvWeb.JoinRequestLive.Show do
ordered_field_ids ordered_field_ids
|> Enum.map(fn key -> |> Enum.map(fn key ->
value = Map.get(typed, key) || Map.get(form_data, key) value = Map.get(typed, key) || Map.get(form_data, key)
label = field_key_to_label(key, member_field_strings, custom_field_by_id) label = field_key_to_label(key, member_field_strings)
value_type = field_key_to_value_type(key, member_field_strings, custom_field_by_id) {label, format_applicant_value(value)}
{label, format_applicant_value(value, value_type)}
end) end)
legacy_keys = legacy_keys =
@ -276,66 +258,34 @@ defmodule MvWeb.JoinRequestLive.Show do
legacy_entries = legacy_entries =
Enum.map(legacy_keys, fn key -> Enum.map(legacy_keys, fn key ->
label = field_key_to_label(key, member_field_strings, custom_field_by_id) label = field_key_to_label(key, member_field_strings)
value_type = field_key_to_value_type(key, member_field_strings, custom_field_by_id) {label, format_applicant_value(form_data[key])}
{label, format_applicant_value(form_data[key], value_type)}
end) end)
in_order ++ legacy_entries in_order ++ legacy_entries
end end
defp format_applicant_value(nil, _type), do: nil defp format_applicant_value(nil), do: nil
defp format_applicant_value("", _type), do: nil defp format_applicant_value(""), do: nil
defp format_applicant_value(%Date{} = date, _type), do: DateFormatter.format_date(date) defp format_applicant_value(%Date{} = date), do: DateFormatter.format_date(date)
defp format_applicant_value(value, type) when is_map(value), defp format_applicant_value(value) when is_map(value),
do: format_applicant_value_from_map(value, type) do: format_applicant_value_from_map(value)
defp format_applicant_value(value, _type) when is_boolean(value), defp format_applicant_value(value) when is_boolean(value),
do: if(value, do: gettext("Yes"), else: gettext("No")) do: if(value, do: gettext("Yes"), else: gettext("No"))
defp format_applicant_value(value, type) when is_binary(value), defp format_applicant_value(value) when is_binary(value) or is_number(value),
do: format_binary_applicant_value(value, type) do: to_string(value)
defp format_applicant_value(value, _type) when is_number(value), do: to_string(value) defp format_applicant_value(value), do: to_string(value)
defp format_applicant_value(value, _type), do: to_string(value) defp format_applicant_value_from_map(value) do
defp format_binary_applicant_value(value, type) do
trimmed_value = String.trim(value)
cond do
trimmed_value == "" ->
nil
String.downcase(trimmed_value) in ["on", "true", "1"] ->
gettext("Yes")
String.downcase(trimmed_value) in ["off", "false", "0"] ->
gettext("No")
type in [:date, Ash.Type.Date] ->
format_iso_date_string(trimmed_value)
true ->
trimmed_value
end
end
defp format_iso_date_string(value) do
case Date.from_iso8601(value) do
{:ok, date} -> DateFormatter.format_date(date)
_ -> value
end
end
defp format_applicant_value_from_map(value, fallback_type) do
raw = Map.get(value, "_union_value") || Map.get(value, "value") raw = Map.get(value, "_union_value") || Map.get(value, "value")
type = Map.get(value, "_union_type") || Map.get(value, "type") type = Map.get(value, "_union_type") || Map.get(value, "type")
effective_type = type || fallback_type
if raw && effective_type in ["date", :date, Ash.Type.Date] do if raw && type in ["date", :date] do
format_applicant_value(raw, :date) format_applicant_value(raw)
else else
format_applicant_value_simple(raw, value) format_applicant_value_simple(raw, value)
end end
@ -349,39 +299,11 @@ defmodule MvWeb.JoinRequestLive.Show do
defp format_applicant_value_simple(raw, _value) when is_integer(raw), do: to_string(raw) defp format_applicant_value_simple(raw, _value) when is_integer(raw), do: to_string(raw)
defp format_applicant_value_simple(_raw, value), do: to_string(value) defp format_applicant_value_simple(_raw, value), do: to_string(value)
defp field_key_to_label(key, member_field_strings, custom_field_by_id) defp field_key_to_label(key, member_field_strings) when is_binary(key) do
when is_binary(key) do if key in member_field_strings,
if key in member_field_strings do do: MemberFieldsTranslations.label(String.to_existing_atom(key)),
MemberFieldsTranslations.label(String.to_existing_atom(key)) else: key
else
case Map.get(custom_field_by_id, key) do
%{name: name} -> name
_ -> key
end
end
end end
defp field_key_to_label(key, _, _), do: to_string(key) defp field_key_to_label(key, _), do: to_string(key)
defp field_key_to_value_type("email", _member_field_strings, _custom_field_by_id), do: :string
defp field_key_to_value_type("first_name", _member_field_strings, _custom_field_by_id),
do: :string
defp field_key_to_value_type("last_name", _member_field_strings, _custom_field_by_id),
do: :string
defp field_key_to_value_type(key, member_field_strings, custom_field_by_id)
when is_binary(key) do
if key in member_field_strings do
:string
else
case Map.get(custom_field_by_id, key) do
%{value_type: value_type} -> value_type
_ -> nil
end
end
end
defp field_key_to_value_type(_key, _member_field_strings, _custom_field_by_id), do: nil
end end

View file

@ -1291,7 +1291,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
defp translate_receipt_status("paid"), do: gettext("Paid") defp translate_receipt_status("paid"), do: gettext("Paid")
defp translate_receipt_status("unpaid"), do: gettext("Unpaid") defp translate_receipt_status("unpaid"), do: gettext("Unpaid")
defp translate_receipt_status("suspended"), do: gettext("Suspended") defp translate_receipt_status("suspended"), do: gettext("Suspended")
defp translate_receipt_status("open"), do: pgettext("status", "Open") defp translate_receipt_status("open"), do: gettext("Open")
defp translate_receipt_status("cancelled"), do: gettext("Cancelled") defp translate_receipt_status("cancelled"), do: gettext("Cancelled")
defp translate_receipt_status("draft"), do: gettext("Draft") defp translate_receipt_status("draft"), do: gettext("Draft")
defp translate_receipt_status("incompleted"), do: gettext("Incomplete") defp translate_receipt_status("incompleted"), do: gettext("Incomplete")

View file

@ -22,7 +22,7 @@
"credo": {:hex, :credo, "1.7.18", "5c5596bf7aedf9c8c227f13272ac499fe8eae6237bd326f2f07dfc173786f042", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a189d164685fd945809e862fe76a7420c4398fa288d76257662aecb909d6b3e5"}, "credo": {:hex, :credo, "1.7.18", "5c5596bf7aedf9c8c227f13272ac499fe8eae6237bd326f2f07dfc173786f042", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a189d164685fd945809e862fe76a7420c4398fa288d76257662aecb909d6b3e5"},
"crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"}, "crux": {:hex, :crux, "0.1.2", "4441c9e3a34f1e340954ce96b9ad5a2de13ceb4f97b3f910211227bb92e2ca90", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "563ea3748ebfba9cc078e6d198a1d6a06015a8fae503f0b721363139f0ddb350"},
"db_connection": {:hex, :db_connection, "2.10.0", "8ff756471e41765bd5563b633f73e9a94bbc138816e8644bb17d0d91bf260a95", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02cdd01b45efb1b550e68edbbea41be32de9b24bb07e1ea0e9cbc522ac377e54"}, "db_connection": {:hex, :db_connection, "2.10.0", "8ff756471e41765bd5563b633f73e9a94bbc138816e8644bb17d0d91bf260a95", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02cdd01b45efb1b550e68edbbea41be32de9b24bb07e1ea0e9cbc522ac377e54"},
"decimal": {:hex, :decimal, "3.0.0", "ce2befbd7218427e4a57d1c6efa6bf50cfc7d0c480c422e70f4fb533074a5f33", [], [], "hexpm", "7a6ab3f806f09738991fc951b2fd2390b3377113feec605a540121aaf772a87b"}, "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"}, "dns_cluster": {:hex, :dns_cluster, "0.2.0", "aa8eb46e3bd0326bd67b84790c561733b25c5ba2fe3c7e36f28e88f384ebcb33", [:mix], [], "hexpm", "ba6f1893411c69c01b9e8e8f772062535a4cf70f3f35bcc964a324078d8c8240"},
"ecto": {:hex, :ecto, "3.13.6", "352135b474f91d1ab99a1b502171d207e9db60421c9e3d0ecab4c7ab96b24d14", [:mix], [{:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8afa059bc16cd2c94739ec0a11e3e5df69d828125119109bef35f20a21a76af2"}, "ecto": {:hex, :ecto, "3.13.6", "352135b474f91d1ab99a1b502171d207e9db60421c9e3d0ecab4c7ab96b24d14", [:mix], [{:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8afa059bc16cd2c94739ec0a11e3e5df69d828125119109bef35f20a21a76af2"},
"ecto_commons": {:hex, :ecto_commons, "0.3.7", "f33c162a6f63695d5939af02c65a0e76aa6e7278b82c7bfc357ffbfea353bf0f", [:mix], [{:burnex, "~> 3.0", [hex: :burnex, repo: "hexpm", optional: true]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ex_phone_number, "~> 0.4", [hex: :ex_phone_number, repo: "hexpm", optional: false]}, {:luhn, "~> 0.3.0", [hex: :luhn, repo: "hexpm", optional: false]}], "hexpm", "9c33771ebd38cd83d3f90fab6069826ba9d4f7580f1481b3c0913f8b9795c5fd"}, "ecto_commons": {:hex, :ecto_commons, "0.3.7", "f33c162a6f63695d5939af02c65a0e76aa6e7278b82c7bfc357ffbfea353bf0f", [:mix], [{:burnex, "~> 3.0", [hex: :burnex, repo: "hexpm", optional: true]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ex_phone_number, "~> 0.4", [hex: :ex_phone_number, repo: "hexpm", optional: false]}, {:luhn, "~> 0.3.0", [hex: :luhn, repo: "hexpm", optional: false]}], "hexpm", "9c33771ebd38cd83d3f90fab6069826ba9d4f7580f1481b3c0913f8b9795c5fd"},
@ -59,7 +59,7 @@
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
"owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"},
"phoenix": {:hex, :phoenix, "1.8.7", "d8d755b4ff4b449f610223dd706b4ae64155cb720d3dc09c706c079ecea189e4", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "47352f72d6ab31009ef77516b1b3a14745be97b54061fd458031b9d8294869d5"}, "phoenix": {:hex, :phoenix, "1.8.6", "7106a0da114619c4b12b056bbaef39fdbc75d3d0cf9cf24af683364064c12dc3", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d0d0b7931916c8196b6903a1efa118b5da28487e7a75ad32a54dfd77de59d421"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"},
"phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
"phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"}, "phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"},
@ -74,7 +74,7 @@
"plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
"plug_cowboy": {:hex, :plug_cowboy, "2.8.1", "5aa391a5e8d1ac3192e36a3bcaff12b5fd6ef6c7e29b53a38e63a860783e77d0", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "4c200288673d5bc86a0ab7dc6a2a069176a74e5d573ef62740a1c517458a5f26"}, "plug_cowboy": {:hex, :plug_cowboy, "2.8.1", "5aa391a5e8d1ac3192e36a3bcaff12b5fd6ef6c7e29b53a38e63a860783e77d0", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "4c200288673d5bc86a0ab7dc6a2a069176a74e5d573ef62740a1c517458a5f26"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"postgrex": {:hex, :postgrex, "0.22.1", "b3665ad17e15441557da8f45eeebfcd56e4a2b0b98538b855679a13d05e5cc5d", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "df59f828b167b49a5853f645b65f57eb1bc5f3b230497ceaca7af5d8ac05afef"}, "postgrex": {:hex, :postgrex, "0.22.0", "fb027b58b6eab1f6de5396a2abcdaaeb168f9ed4eccbb594e6ac393b02078cbd", [:mix], [{:db_connection, "~> 2.9", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "a68c4261e299597909e03e6f8ff5a13876f5caadaddd0d23af0d0a61afcc5d84"},
"ranch": {:hex, :ranch, "1.8.1", "208169e65292ac5d333d6cdbad49388c1ae198136e4697ae2f474697140f201c", [:make, :rebar3], [], "hexpm", "aed58910f4e21deea992a67bf51632b6d60114895eb03bb392bb733064594dd0"}, "ranch": {:hex, :ranch, "1.8.1", "208169e65292ac5d333d6cdbad49388c1ae198136e4697ae2f474697140f201c", [:make, :rebar3], [], "hexpm", "aed58910f4e21deea992a67bf51632b6d60114895eb03bb392bb733064594dd0"},
"reactor": {:hex, :reactor, "1.0.1", "ca3b5cf3c04ec8441e67ea2625d0294939822060b1bfd00ffdaaf75b7682d991", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3497db2b204c9a3cabdaf1b26d2405df1dfbb138ce0ce50e616e9db19fec0043"}, "reactor": {:hex, :reactor, "1.0.1", "ca3b5cf3c04ec8441e67ea2625d0294939822060b1bfd00ffdaaf75b7682d991", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:libgraph, "~> 0.16", [hex: :libgraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "3497db2b204c9a3cabdaf1b26d2405df1dfbb138ce0ce50e616e9db19fec0043"},
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},

View file

@ -2396,6 +2396,11 @@ msgstr "Nur Administrator*innen oder die verknüpfte*n Benutzer*in(nen) können
msgid "Only possible if no members are assigned to this type." msgid "Only possible if no members are assigned to this type."
msgstr "Nur möglich, wenn diesem Typ keine Mitglieder zugewiesen sind." msgstr "Nur möglich, wenn diesem Typ keine Mitglieder zugewiesen sind."
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Open"
msgstr "Offen"
#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open email program with BCC recipients" msgid "Open email program with BCC recipients"
@ -3900,20 +3905,3 @@ msgstr "Nur OIDC-Anmeldung ist aktiv. Diese Option ist deaktiviert."
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Only sign-in via Single Sign-On (SSO) is allowed." msgid "Only sign-in via Single Sign-On (SSO) is allowed."
msgstr "Nur Anmeldung per Single Sign-On (SSO) ist erlaubt." msgstr "Nur Anmeldung per Single Sign-On (SSO) ist erlaubt."
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgctxt "action"
msgid "Open"
msgstr "Öffnen"
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgctxt "status"
msgid "Open"
msgstr "Offen"
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "join page URL in a new tab"
msgstr "Beitrittslink in einem neuen Tab"

View file

@ -2397,6 +2397,11 @@ msgstr ""
msgid "Only possible if no members are assigned to this type." msgid "Only possible if no members are assigned to this type."
msgstr "" msgstr ""
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Open"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open email program with BCC recipients" msgid "Open email program with BCC recipients"
@ -3900,20 +3905,3 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Only sign-in via Single Sign-On (SSO) is allowed." msgid "Only sign-in via Single Sign-On (SSO) is allowed."
msgstr "" msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgctxt "action"
msgid "Open"
msgstr ""
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgctxt "status"
msgid "Open"
msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "join page URL in a new tab"
msgstr ""

View file

@ -2397,6 +2397,11 @@ msgstr ""
msgid "Only possible if no members are assigned to this type." msgid "Only possible if no members are assigned to this type."
msgstr "" msgstr ""
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgid "Open"
msgstr ""
#: lib/mv_web/live/member_live/index.html.heex #: lib/mv_web/live/member_live/index.html.heex
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Open email program with BCC recipients" msgid "Open email program with BCC recipients"
@ -3900,20 +3905,3 @@ msgstr ""
#, elixir-autogen, elixir-format #, elixir-autogen, elixir-format
msgid "Only sign-in via Single Sign-On (SSO) is allowed." msgid "Only sign-in via Single Sign-On (SSO) is allowed."
msgstr "" msgstr ""
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgctxt "action"
msgid "Open"
msgstr "Open"
#: lib/mv_web/live/member_live/show/membership_fees_component.ex
#, elixir-autogen, elixir-format
msgctxt "status"
msgid "Open"
msgstr "Open"
#: lib/mv_web/live/global_settings_live.ex
#, elixir-autogen, elixir-format
msgid "join page URL in a new tab"
msgstr "join page URL in a new tab"

View file

@ -16,13 +16,9 @@ defmodule Mv.Membership.MemberGroupsRelationshipTest do
describe "Relationships" do describe "Relationships" do
test "member has many_to_many groups relationship (load with preloading)", %{actor: actor} do test "member has many_to_many groups relationship (load with preloading)", %{actor: actor} do
{:ok, member} = Membership.create_member(%{email: unique_email("member")}, actor: actor) {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
{:ok, group1} = Membership.create_group(%{name: "Group One"}, actor: actor)
{:ok, group1} = {:ok, group2} = Membership.create_group(%{name: "Group Two"}, actor: actor)
Membership.create_group(%{name: unique_group_name("Group One")}, actor: actor)
{:ok, group2} =
Membership.create_group(%{name: unique_group_name("Group Two")}, actor: actor)
{:ok, _mg1} = {:ok, _mg1} =
Membership.create_member_group(%{member_id: member.id, group_id: group1.id}, Membership.create_member_group(%{member_id: member.id, group_id: group1.id},
@ -44,11 +40,9 @@ defmodule Mv.Membership.MemberGroupsRelationshipTest do
end end
test "load multiple members with groups preloaded (N+1 prevention)", %{actor: actor} do test "load multiple members with groups preloaded (N+1 prevention)", %{actor: actor} do
{:ok, member1} = Membership.create_member(%{email: unique_email("member1")}, actor: actor) {:ok, member1} = Membership.create_member(%{email: "member1@test.com"}, actor: actor)
{:ok, member2} = Membership.create_member(%{email: unique_email("member2")}, actor: actor) {:ok, member2} = Membership.create_member(%{email: "member2@test.com"}, actor: actor)
{:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)
{:ok, group} =
Membership.create_group(%{name: unique_group_name("Test Group")}, actor: actor)
{:ok, _mg1} = {:ok, _mg1} =
Membership.create_member_group(%{member_id: member1.id, group_id: group.id}, Membership.create_member_group(%{member_id: member1.id, group_id: group.id},
@ -76,10 +70,8 @@ defmodule Mv.Membership.MemberGroupsRelationshipTest do
describe "Member-Group Association Operations" do describe "Member-Group Association Operations" do
test "add member to group via Ash API", %{actor: actor} do test "add member to group via Ash API", %{actor: actor} do
{:ok, member} = Membership.create_member(%{email: unique_email("member")}, actor: actor) {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
{:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)
{:ok, group} =
Membership.create_group(%{name: unique_group_name("Test Group")}, actor: actor)
assert {:ok, member_group} = assert {:ok, member_group} =
Membership.create_member_group(%{member_id: member.id, group_id: group.id}, Membership.create_member_group(%{member_id: member.id, group_id: group.id},
@ -91,10 +83,8 @@ defmodule Mv.Membership.MemberGroupsRelationshipTest do
end end
test "remove member from group via Ash API", %{actor: actor} do test "remove member from group via Ash API", %{actor: actor} do
{:ok, member} = Membership.create_member(%{email: unique_email("member")}, actor: actor) {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
{:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)
{:ok, group} =
Membership.create_group(%{name: unique_group_name("Test Group")}, actor: actor)
{:ok, member_group} = {:ok, member_group} =
Membership.create_member_group(%{member_id: member.id, group_id: group.id}, Membership.create_member_group(%{member_id: member.id, group_id: group.id},
@ -117,16 +107,10 @@ defmodule Mv.Membership.MemberGroupsRelationshipTest do
end end
test "add member to multiple groups in single operation", %{actor: actor} do test "add member to multiple groups in single operation", %{actor: actor} do
{:ok, member} = Membership.create_member(%{email: unique_email("member")}, actor: actor) {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
{:ok, group1} = Membership.create_group(%{name: "Group One"}, actor: actor)
{:ok, group1} = {:ok, group2} = Membership.create_group(%{name: "Group Two"}, actor: actor)
Membership.create_group(%{name: unique_group_name("Group One")}, actor: actor) {:ok, group3} = Membership.create_group(%{name: "Group Three"}, actor: actor)
{:ok, group2} =
Membership.create_group(%{name: unique_group_name("Group Two")}, actor: actor)
{:ok, group3} =
Membership.create_group(%{name: unique_group_name("Group Three")}, actor: actor)
# Add to all groups # Add to all groups
{:ok, _mg1} = {:ok, _mg1} =
@ -154,10 +138,8 @@ defmodule Mv.Membership.MemberGroupsRelationshipTest do
describe "Edge Cases" do describe "Edge Cases" do
test "adding member to same group twice fails (duplicate prevention)", %{actor: actor} do test "adding member to same group twice fails (duplicate prevention)", %{actor: actor} do
{:ok, member} = Membership.create_member(%{email: unique_email("member")}, actor: actor) {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
{:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)
{:ok, group} =
Membership.create_group(%{name: unique_group_name("Test Group")}, actor: actor)
{:ok, _mg1} = {:ok, _mg1} =
Membership.create_member_group(%{member_id: member.id, group_id: group.id}, Membership.create_member_group(%{member_id: member.id, group_id: group.id},
@ -172,10 +154,8 @@ defmodule Mv.Membership.MemberGroupsRelationshipTest do
end end
test "removing member from group they're not in (idempotent, no error)", %{actor: actor} do test "removing member from group they're not in (idempotent, no error)", %{actor: actor} do
{:ok, member} = Membership.create_member(%{email: unique_email("member")}, actor: actor) {:ok, member} = Membership.create_member(%{email: "test@test.com"}, actor: actor)
{:ok, group} = Membership.create_group(%{name: "Test Group"}, actor: actor)
{:ok, group} =
Membership.create_group(%{name: unique_group_name("Test Group")}, actor: actor)
# Verify no association exists # Verify no association exists
{:ok, nil} = {:ok, nil} =
@ -214,12 +194,4 @@ defmodule Mv.Membership.MemberGroupsRelationshipTest do
assert result == :ok || match?({:error, _}, result) assert result == :ok || match?({:error, _}, result)
end end
end end
defp unique_email(prefix) do
"#{prefix}-#{System.unique_integer([:positive])}@test.com"
end
defp unique_group_name(prefix) do
"#{prefix} #{System.unique_integer([:positive])}"
end
end end

View file

@ -64,21 +64,6 @@ defmodule MvWeb.GlobalSettingsLiveTest do
assert html =~ "must be present" assert html =~ "must be present"
end end
test "shows open button for join page URL in same row as copy", %{conn: conn} do
{:ok, settings} = Membership.get_settings()
{:ok, _} = Membership.update_settings(settings, %{join_form_enabled: true})
{:ok, view, _html} = live(conn, ~p"/settings")
assert has_element?(view, "#copy-join-url-btn")
assert has_element?(
view,
"a[href][target=\"_blank\"][rel=\"noopener noreferrer\"]",
"Open"
)
end
end end
describe "SMTP / E-Mail section" do describe "SMTP / E-Mail section" do

View file

@ -12,9 +12,10 @@ defmodule MvWeb.JoinLiveTest do
# async: false → shared sandbox; all processes (including LiveView) share the DB connection. # async: false → shared sandbox; all processes (including LiveView) share the DB connection.
use MvWeb.ConnCase, async: false use MvWeb.ConnCase, async: false
import Phoenix.LiveViewTest import Phoenix.LiveViewTest
import Ecto.Query
alias Mv.Membership alias Mv.Membership
alias Mv.Membership.JoinRequest alias Mv.Repo
describe "GET /join" do describe "GET /join" do
@tag role: :unauthenticated @tag role: :unauthenticated
@ -54,12 +55,11 @@ defmodule MvWeb.JoinLiveTest do
}) })
|> render_submit() |> render_submit()
assert_eventually(fn -> count_join_requests() == count_before + 1 end) # Anti-enumeration delay is applied in LiveView via send_after (100300 ms); wait for success UI.
Process.sleep(400)
assert_eventually(fn ->
view |> element("[data-testid='join-success-message']") |> has_element?()
end)
assert count_join_requests() == count_before + 1
assert view |> element("[data-testid='join-success-message']") |> has_element?()
assert render(view) =~ "saved your details" assert render(view) =~ "saved your details"
assert render(view) =~ "click the link" assert render(view) =~ "click the link"
end end
@ -135,181 +135,6 @@ defmodule MvWeb.JoinLiveTest do
end end
end end
describe "join field labels" do
@tag role: :unauthenticated
test "renders custom field name as label for custom field IDs", %{conn: conn} do
{:ok, settings} = Membership.get_settings()
system_actor = Mv.Helpers.SystemActor.get_system_actor()
{:ok, custom_field} =
Membership.create_custom_field(
%{
name: "Preferred Pronouns",
value_type: :string
},
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 has_element?(
view,
"label[for='join-field-#{custom_field.id}'] .label-text",
custom_field.name
)
end
end
describe "join field input types" do
@tag role: :unauthenticated
test "renders boolean custom field as checkbox input", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
{:ok, settings} = Membership.get_settings()
{:ok, boolean_field} =
Membership.create_custom_field(
%{
name: "Subscribe to newsletter",
value_type: :boolean
},
actor: system_actor
)
{:ok, _} =
Membership.update_settings(settings, %{
join_form_enabled: true,
join_form_field_ids: ["email", boolean_field.id],
join_form_field_required: %{"email" => true, boolean_field.id => false}
})
{: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}[type='checkbox']")
refute has_element?(view, "input#join-field-#{boolean_field.id}[type='text']")
end
@tag role: :unauthenticated
test "renders typed custom fields with matching HTML input types", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
{:ok, settings} = Membership.get_settings()
{:ok, integer_field} =
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
)
{:ok, email_field} =
Membership.create_custom_field(%{name: "Secondary email", value_type: :email},
actor: system_actor
)
{:ok, _} =
Membership.update_settings(settings, %{
join_form_enabled: true,
join_form_field_ids: ["email", integer_field.id, date_field.id, email_field.id],
join_form_field_required: %{
"email" => true,
integer_field.id => false,
date_field.id => false,
email_field.id => false
}
})
{:ok, view, _html} = live(conn, "/join")
assert has_element?(view, "input#join-field-#{integer_field.id}[type='number']")
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
setup do
reset_rate_limiter()
:ok
end
@tag role: :unauthenticated
test "persists checked boolean custom field and ignores non-allowlisted field", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
{:ok, settings} = Membership.get_settings()
{:ok, boolean_field} =
Membership.create_custom_field(
%{
name: "Receive announcements",
value_type: :boolean
},
actor: system_actor
)
{:ok, _} =
Membership.update_settings(settings, %{
join_form_enabled: true,
join_form_field_ids: ["email", boolean_field.id],
join_form_field_required: %{"email" => true, boolean_field.id => false}
})
count_before = count_join_requests()
{:ok, view, _html} = live(conn, "/join")
view
|> element("#join-form")
|> render_submit(%{
"email" => "typed#{System.unique_integer([:positive])}@example.com",
"website" => "",
boolean_field.id => "on",
"not_allowlisted" => "should-not-be-persisted"
})
assert_eventually(fn -> count_join_requests() == count_before + 1 end)
assert_eventually(fn ->
view |> element("[data-testid='join-success-message']") |> has_element?()
end)
form_data = latest_join_request_form_data()
assert Map.get(form_data, boolean_field.id) == "on"
refute Map.has_key?(form_data, "not_allowlisted")
end
end
defp enable_join_form(enabled) do defp enable_join_form(enabled) do
{:ok, settings} = Membership.get_settings() {:ok, settings} = Membership.get_settings()
{:ok, _} = Membership.update_settings(settings, %{join_form_enabled: enabled}) {:ok, _} = Membership.update_settings(settings, %{join_form_enabled: enabled})
@ -329,40 +154,7 @@ defmodule MvWeb.JoinLiveTest do
end end
defp count_join_requests do defp count_join_requests do
case Ash.count(JoinRequest, domain: Membership, authorize?: false) do Repo.one(from j in "join_requests", select: count(j.id)) || 0
{:ok, count} -> count
_ -> 0
end
end
defp latest_join_request_form_data do
query =
JoinRequest
|> Ash.Query.sort(inserted_at: :desc)
|> Ash.Query.limit(1)
case Ash.read(query, domain: Membership, authorize?: false) do
{:ok, [request]} -> request.form_data || %{}
_ -> %{}
end
end
defp assert_eventually(fun, timeout_ms \\ 1500) when is_function(fun, 0) do
deadline = System.monotonic_time(:millisecond) + timeout_ms
do_assert_eventually(fun, deadline)
end
defp do_assert_eventually(fun, deadline) do
if fun.() do
true
else
if System.monotonic_time(:millisecond) < deadline do
Process.sleep(25)
do_assert_eventually(fun, deadline)
else
assert fun.()
end
end
end end
defp reset_rate_limiter do defp reset_rate_limiter do

View file

@ -1,124 +0,0 @@
defmodule MvWeb.JoinRequestLive.ShowTest do
@moduledoc """
Tests for join request detail view label rendering.
Focus: applicant data labels for custom fields should use custom field names,
not raw UUIDs.
"""
use MvWeb.ConnCase, async: false
import Phoenix.LiveViewTest
alias Mv.Fixtures
alias Mv.Membership
alias MvWeb.Helpers.DateFormatter
setup do
{:ok, settings} = Membership.get_settings()
saved = %{
join_form_enabled: settings.join_form_enabled,
join_form_field_ids: settings.join_form_field_ids,
join_form_field_required: settings.join_form_field_required
}
on_exit(fn ->
{:ok, current_settings} = Membership.get_settings()
Membership.update_settings(current_settings, %{
join_form_enabled: saved.join_form_enabled,
join_form_field_ids: saved.join_form_field_ids || [],
join_form_field_required: saved.join_form_field_required || %{}
})
end)
:ok
end
describe "custom field labels in applicant data" do
@tag role: :normal_user
test "renders custom field name instead of custom field UUID", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
{:ok, settings} = Membership.get_settings()
{:ok, custom_field} =
Membership.create_custom_field(
%{
name: "Emergency contact",
value_type: :string
},
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}
})
join_request =
Fixtures.submitted_join_request_fixture(%{
form_data: %{custom_field.id => "Alice Example"}
})
{:ok, view, _html} = live(conn, "/join_requests/#{join_request.id}")
assert has_element?(view, "dt", "#{custom_field.name}:")
assert has_element?(view, "dd", "Alice Example")
refute has_element?(view, "dt", "#{custom_field.id}:")
end
@tag role: :normal_user
test "formats boolean/date values and renders status in aligned row", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
{:ok, settings} = Membership.get_settings()
{:ok, boolean_field} =
Membership.create_custom_field(
%{
name: "Privacy accepted",
value_type: :boolean
},
actor: system_actor
)
{:ok, date_field} =
Membership.create_custom_field(
%{
name: "Birth date",
value_type: :date
},
actor: system_actor
)
{:ok, _} =
Membership.update_settings(settings, %{
join_form_enabled: true,
join_form_field_ids: ["email", boolean_field.id, date_field.id],
join_form_field_required: %{
"email" => true,
boolean_field.id => false,
date_field.id => false
}
})
join_request =
Fixtures.submitted_join_request_fixture(%{
form_data: %{
boolean_field.id => "on",
date_field.id => "2000-01-12"
}
})
{:ok, view, _html} = live(conn, "/join_requests/#{join_request.id}")
assert has_element?(view, "dt", "Privacy accepted:")
assert has_element?(view, "dd", "Yes")
assert has_element?(view, "dt", "Birth date:")
assert has_element?(view, "dd", DateFormatter.format_date(~D[2000-01-12]))
assert has_element?(view, "dt", "Status:")
assert has_element?(view, "dd", "Submitted")
end
end
end