Use current_actor/1 helper in all LiveViews
Replace inconsistent actor access patterns with current_actor/1 helper and ensure actor is passed to all Ash operations for proper authorization.
This commit is contained in:
parent
74fe60f768
commit
cd7e6b0843
9 changed files with 268 additions and 57 deletions
|
|
@ -23,6 +23,8 @@ defmodule MvWeb.MemberLive.Form do
|
|||
|
||||
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
||||
|
||||
import MvWeb.LiveHelpers, only: [current_actor: 1]
|
||||
|
||||
alias Mv.MembershipFees
|
||||
alias Mv.MembershipFees.MembershipFeeType
|
||||
alias MvWeb.Helpers.MembershipFeeHelpers
|
||||
|
|
@ -174,7 +176,7 @@ defmodule MvWeb.MemberLive.Form do
|
|||
<select
|
||||
class="select select-bordered w-full"
|
||||
name={@form[:membership_fee_type_id].name}
|
||||
phx-change="validate_membership_fee_type"
|
||||
phx-change="validate"
|
||||
value={@form[:membership_fee_type_id].value || ""}
|
||||
>
|
||||
<option value="">{gettext("None")}</option>
|
||||
|
|
@ -225,7 +227,7 @@ defmodule MvWeb.MemberLive.Form do
|
|||
@impl true
|
||||
def mount(params, _session, socket) do
|
||||
# current_user should be set by on_mount hooks (LiveUserAuth + LiveHelpers)
|
||||
actor = socket.assigns[:current_user] || socket.assigns.current_user
|
||||
actor = current_actor(socket)
|
||||
{:ok, custom_fields} = Mv.Membership.list_custom_fields()
|
||||
|
||||
initial_custom_field_values =
|
||||
|
|
@ -269,28 +271,29 @@ defmodule MvWeb.MemberLive.Form do
|
|||
|
||||
@impl true
|
||||
def handle_event("validate", %{"member" => member_params}, socket) do
|
||||
validated_form = AshPhoenix.Form.validate(socket.assigns.form, member_params)
|
||||
# Merge with existing form values to preserve unchanged fields (especially custom_field_values)
|
||||
# Extract values directly from form fields to get current state
|
||||
existing_values = get_existing_form_values(socket.assigns.form)
|
||||
|
||||
# Merge existing values with new params (new params take precedence)
|
||||
merged_params = Map.merge(existing_values, member_params)
|
||||
|
||||
validated_form = AshPhoenix.Form.validate(socket.assigns.form, merged_params)
|
||||
|
||||
# Check for interval mismatch if membership_fee_type_id changed
|
||||
socket = check_interval_change(socket, member_params)
|
||||
socket = check_interval_change(socket, merged_params)
|
||||
|
||||
{:noreply, assign(socket, form: validated_form)}
|
||||
end
|
||||
|
||||
def handle_event(
|
||||
"validate_membership_fee_type",
|
||||
%{"member" => %{"membership_fee_type_id" => fee_type_id}},
|
||||
socket
|
||||
) do
|
||||
# Same validation as above, but triggered by select change
|
||||
handle_event("validate", %{"member" => %{"membership_fee_type_id" => fee_type_id}}, socket)
|
||||
end
|
||||
|
||||
def handle_event("save", %{"member" => member_params}, socket) do
|
||||
try do
|
||||
actor = socket.assigns[:current_user] || socket.assigns.current_user
|
||||
actor = current_actor(socket)
|
||||
|
||||
case AshPhoenix.Form.submit(socket.assigns.form, params: member_params, actor: actor) do
|
||||
case AshPhoenix.Form.submit(socket.assigns.form,
|
||||
params: member_params,
|
||||
action_opts: [actor: actor]
|
||||
) do
|
||||
{:ok, member} ->
|
||||
handle_save_success(socket, member)
|
||||
|
||||
|
|
@ -483,4 +486,167 @@ defmodule MvWeb.MemberLive.Form do
|
|||
defp custom_field_input_type(:date), do: "date"
|
||||
defp custom_field_input_type(:email), do: "email"
|
||||
defp custom_field_input_type(_), do: "text"
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Helper Functions for Form Value Preservation
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
# Helper to extract existing form values to preserve them when only one field changes
|
||||
# This ensures custom_field_values and other fields are preserved when only the dropdown changes
|
||||
defp get_existing_form_values(form) do
|
||||
%{}
|
||||
|> extract_form_value(form, :first_name, &to_string/1)
|
||||
|> extract_form_value(form, :last_name, &to_string/1)
|
||||
|> extract_form_value(form, :email, &to_string/1)
|
||||
|> extract_form_value(form, :street, &to_string/1)
|
||||
|> extract_form_value(form, :house_number, &to_string/1)
|
||||
|> extract_form_value(form, :postal_code, &to_string/1)
|
||||
|> extract_form_value(form, :city, &to_string/1)
|
||||
|> extract_form_value(form, :join_date, &format_date_value/1)
|
||||
|> extract_form_value(form, :exit_date, &format_date_value/1)
|
||||
|> extract_form_value(form, :notes, &to_string/1)
|
||||
|> extract_form_value(form, :membership_fee_type_id, &to_string/1)
|
||||
|> extract_form_value(form, :membership_fee_start_date, &format_date_value/1)
|
||||
|> extract_custom_field_values(form)
|
||||
end
|
||||
|
||||
# Helper to extract a single form field value
|
||||
defp extract_form_value(acc, form, field, formatter) do
|
||||
if form[field] && form[field].value do
|
||||
Map.put(acc, to_string(field), formatter.(form[field].value))
|
||||
else
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
# Extracts custom field values from the form structure
|
||||
# The form is a Phoenix.HTML.Form with source being AshPhoenix.Form
|
||||
# Custom field values are in form.source.params["custom_field_values"] as a map
|
||||
defp extract_custom_field_values(acc, form) do
|
||||
cfv_params = get_custom_field_values_params(form)
|
||||
|
||||
if map_size(cfv_params) > 0 do
|
||||
custom_field_values = convert_cfv_params_to_list(cfv_params)
|
||||
Map.put(acc, "custom_field_values", custom_field_values)
|
||||
else
|
||||
acc
|
||||
end
|
||||
end
|
||||
|
||||
# Gets custom_field_values from form params
|
||||
defp get_custom_field_values_params(form) do
|
||||
ash_form = form.source
|
||||
|
||||
if ash_form && Map.has_key?(ash_form, :params) && ash_form.params["custom_field_values"] do
|
||||
ash_form.params["custom_field_values"]
|
||||
else
|
||||
%{}
|
||||
end
|
||||
end
|
||||
|
||||
# Converts custom field values map to sorted list
|
||||
defp convert_cfv_params_to_list(cfv_params) do
|
||||
cfv_params
|
||||
|> Map.to_list()
|
||||
|> Enum.sort_by(&parse_numeric_key/1)
|
||||
|> Enum.map(&build_custom_field_value/1)
|
||||
end
|
||||
|
||||
# Parses numeric key for sorting
|
||||
defp parse_numeric_key({key, _}) do
|
||||
case Integer.parse(key) do
|
||||
{num, _} -> num
|
||||
:error -> 999_999
|
||||
end
|
||||
end
|
||||
|
||||
# Builds a custom field value map from params
|
||||
defp build_custom_field_value({_key, cfv_map}) do
|
||||
%{
|
||||
"custom_field_id" => Map.get(cfv_map, "custom_field_id", ""),
|
||||
"value" => extract_custom_field_value_from_map(Map.get(cfv_map, "value", %{}))
|
||||
}
|
||||
end
|
||||
|
||||
# Extracts the value map structure from a custom field value
|
||||
# Handles both map format and Ash.Union struct format
|
||||
defp extract_custom_field_value_from_map(%Ash.Union{} = union) do
|
||||
union_type = Atom.to_string(union.type)
|
||||
|
||||
%{
|
||||
"_union_type" => union_type,
|
||||
"type" => union_type,
|
||||
"value" => format_custom_field_value(union.value)
|
||||
}
|
||||
end
|
||||
|
||||
defp extract_custom_field_value_from_map(value_map) when is_map(value_map) do
|
||||
union_type = extract_union_type_from_map(value_map)
|
||||
value = Map.get(value_map, "value") || Map.get(value_map, :value)
|
||||
|
||||
%{
|
||||
"_union_type" => union_type,
|
||||
"type" => union_type,
|
||||
"value" => format_custom_field_value(value)
|
||||
}
|
||||
end
|
||||
|
||||
defp extract_custom_field_value_from_map(_),
|
||||
do: %{"_union_type" => "", "type" => "", "value" => ""}
|
||||
|
||||
# Extracts union type from map, checking various possible locations
|
||||
defp extract_union_type_from_map(value_map) do
|
||||
cond do
|
||||
has_non_empty_string(value_map, "_union_type") ->
|
||||
Map.get(value_map, "_union_type")
|
||||
|
||||
has_non_empty_atom(value_map, :_union_type) ->
|
||||
to_string(Map.get(value_map, :_union_type))
|
||||
|
||||
has_atom_type(value_map) ->
|
||||
Atom.to_string(Map.get(value_map, :type))
|
||||
|
||||
has_string_type(value_map) ->
|
||||
Map.get(value_map, "type")
|
||||
|
||||
true ->
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
# Helper to check if map has non-empty string value
|
||||
defp has_non_empty_string(map, key) do
|
||||
value = Map.get(map, key)
|
||||
value && value != ""
|
||||
end
|
||||
|
||||
# Helper to check if map has non-empty atom value
|
||||
defp has_non_empty_atom(map, key) do
|
||||
value = Map.get(map, key)
|
||||
value && value != ""
|
||||
end
|
||||
|
||||
# Helper to check if map has atom type
|
||||
defp has_atom_type(map) do
|
||||
value = Map.get(map, :type)
|
||||
value && is_atom(value)
|
||||
end
|
||||
|
||||
# Helper to check if map has string type
|
||||
defp has_string_type(map) do
|
||||
value = Map.get(map, "type")
|
||||
value && is_binary(value)
|
||||
end
|
||||
|
||||
# Formats custom field value based on its type
|
||||
defp format_custom_field_value(%Date{} = date), do: Date.to_iso8601(date)
|
||||
defp format_custom_field_value(%Decimal{} = decimal), do: Decimal.to_string(decimal, :normal)
|
||||
defp format_custom_field_value(value) when is_boolean(value), do: to_string(value)
|
||||
defp format_custom_field_value(value) when is_binary(value), do: value
|
||||
defp format_custom_field_value(value), do: to_string(value)
|
||||
|
||||
# Formats date value (Date or string) to string
|
||||
defp format_date_value(%Date{} = date), do: Date.to_iso8601(date)
|
||||
defp format_date_value(value) when is_binary(value), do: value
|
||||
defp format_date_value(_), do: ""
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue