Integrate Member policies in LiveViews
- Add on_mount hook to ensure user role is loaded in all Member LiveViews - Pass actor parameter to all Ash operations (read, get, create, update, destroy, load)
This commit is contained in:
parent
dc3268cbf4
commit
bc87893134
7 changed files with 167 additions and 74 deletions
|
|
@ -21,6 +21,8 @@ defmodule MvWeb.MemberLive.Form do
|
|||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
||||
|
||||
alias Mv.MembershipFees
|
||||
alias Mv.MembershipFees.MembershipFeeType
|
||||
alias MvWeb.Helpers.MembershipFeeHelpers
|
||||
|
|
@ -222,6 +224,8 @@ 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
|
||||
{:ok, custom_fields} = Mv.Membership.list_custom_fields()
|
||||
|
||||
initial_custom_field_values =
|
||||
|
|
@ -239,14 +243,14 @@ defmodule MvWeb.MemberLive.Form do
|
|||
member =
|
||||
case params["id"] do
|
||||
nil -> nil
|
||||
id -> Ash.get!(Mv.Membership.Member, id, load: [:membership_fee_type])
|
||||
id -> Ash.get!(Mv.Membership.Member, id, load: [:membership_fee_type], actor: actor)
|
||||
end
|
||||
|
||||
page_title =
|
||||
if is_nil(member), do: gettext("Create Member"), else: gettext("Edit Member")
|
||||
|
||||
# Load available membership fee types
|
||||
available_fee_types = load_available_fee_types(member)
|
||||
available_fee_types = load_available_fee_types(member, actor)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|
|
@ -283,35 +287,59 @@ defmodule MvWeb.MemberLive.Form do
|
|||
end
|
||||
|
||||
def handle_event("save", %{"member" => member_params}, socket) do
|
||||
case AshPhoenix.Form.submit(socket.assigns.form, params: member_params) do
|
||||
{:ok, member} ->
|
||||
notify_parent({:saved, member})
|
||||
try do
|
||||
actor = socket.assigns[:current_user] || socket.assigns.current_user
|
||||
|
||||
action =
|
||||
case socket.assigns.form.source.type do
|
||||
:create -> gettext("create")
|
||||
:update -> gettext("update")
|
||||
other -> to_string(other)
|
||||
end
|
||||
case AshPhoenix.Form.submit(socket.assigns.form, params: member_params, actor: actor) do
|
||||
{:ok, member} ->
|
||||
handle_save_success(socket, member)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> put_flash(:info, gettext("Member %{action} successfully", action: action))
|
||||
|> push_navigate(to: return_path(socket.assigns.return_to, member))
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
{:error, form} ->
|
||||
{:noreply, assign(socket, form: form)}
|
||||
{:error, form} ->
|
||||
{:noreply, assign(socket, form: form)}
|
||||
end
|
||||
rescue
|
||||
_e in [Ash.Error.Forbidden, Ash.Error.Forbidden.Policy] ->
|
||||
handle_save_forbidden(socket)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_save_success(socket, member) do
|
||||
notify_parent({:saved, member})
|
||||
|
||||
action = get_action_name(socket.assigns.form.source.type)
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> put_flash(:info, gettext("Member %{action} successfully", action: action))
|
||||
|> push_navigate(to: return_path(socket.assigns.return_to, member))
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
defp handle_save_forbidden(socket) do
|
||||
# Handle policy violations that aren't properly displayed in forms
|
||||
# AshPhoenix.Form doesn't implement FormData.Error protocol for Forbidden errors
|
||||
action = get_action_name(socket.assigns.form.source.type)
|
||||
|
||||
error_message =
|
||||
gettext("You do not have permission to %{action} members.", action: action)
|
||||
|
||||
{:noreply, put_flash(socket, :error, error_message)}
|
||||
end
|
||||
|
||||
defp get_action_name(:create), do: gettext("create")
|
||||
defp get_action_name(:update), do: gettext("update")
|
||||
defp get_action_name(other), do: to_string(other)
|
||||
|
||||
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
|
||||
|
||||
defp assign_form(%{assigns: %{member: member}} = socket) do
|
||||
defp assign_form(%{assigns: assigns} = socket) do
|
||||
member = assigns.member
|
||||
actor = assigns[:current_user] || assigns.current_user
|
||||
|
||||
form =
|
||||
if member do
|
||||
{:ok, member} = Ash.load(member, custom_field_values: [:custom_field])
|
||||
{:ok, member} = Ash.load(member, custom_field_values: [:custom_field], actor: actor)
|
||||
|
||||
existing_custom_field_values =
|
||||
member.custom_field_values
|
||||
|
|
@ -342,7 +370,8 @@ defmodule MvWeb.MemberLive.Form do
|
|||
api: Mv.Membership,
|
||||
as: "member",
|
||||
params: params,
|
||||
forms: [auto?: true]
|
||||
forms: [auto?: true],
|
||||
actor: actor
|
||||
)
|
||||
|
||||
missing_custom_field_values =
|
||||
|
|
@ -360,7 +389,8 @@ defmodule MvWeb.MemberLive.Form do
|
|||
api: Mv.Membership,
|
||||
as: "member",
|
||||
params: %{"custom_field_values" => socket.assigns[:initial_custom_field_values]},
|
||||
forms: [auto?: true]
|
||||
forms: [auto?: true],
|
||||
actor: actor
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -375,11 +405,11 @@ defmodule MvWeb.MemberLive.Form do
|
|||
# Helper Functions
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
defp load_available_fee_types(member) do
|
||||
defp load_available_fee_types(member, actor) do
|
||||
all_types =
|
||||
MembershipFeeType
|
||||
|> Ash.Query.sort(name: :asc)
|
||||
|> Ash.read!(domain: MembershipFees)
|
||||
|> Ash.read!(domain: MembershipFees, actor: actor)
|
||||
|
||||
# If member has a fee type, filter to same interval
|
||||
if member && member.membership_fee_type do
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ defmodule MvWeb.MemberLive.Index do
|
|||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
||||
|
||||
require Ash.Query
|
||||
import Ash.Expr
|
||||
|
||||
|
|
@ -58,17 +60,19 @@ defmodule MvWeb.MemberLive.Index do
|
|||
# Note: Using Ash.read! (bang version) - errors will be handled by Phoenix LiveView
|
||||
# and result in a 500 error page. This is appropriate for LiveViews where errors
|
||||
# should be visible to the user rather than silently failing.
|
||||
actor = socket.assigns[:current_user]
|
||||
|
||||
custom_fields_visible =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Query.filter(expr(show_in_overview == true))
|
||||
|> Ash.Query.sort(name: :asc)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: actor)
|
||||
|
||||
# Load ALL custom fields for the dropdown (to show all available fields)
|
||||
all_custom_fields =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Query.sort(name: :asc)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: actor)
|
||||
|
||||
# Load settings once to avoid N+1 queries
|
||||
settings =
|
||||
|
|
@ -132,8 +136,9 @@ defmodule MvWeb.MemberLive.Index do
|
|||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
# Note: Using bang versions (!) - errors will be handled by Phoenix LiveView
|
||||
# This ensures users see error messages if deletion fails (e.g., permission denied)
|
||||
member = Ash.get!(Mv.Membership.Member, id)
|
||||
Ash.destroy!(member)
|
||||
actor = socket.assigns[:current_user]
|
||||
member = Ash.get!(Mv.Membership.Member, id, actor: actor)
|
||||
Ash.destroy!(member, actor: actor)
|
||||
|
||||
updated_members = Enum.reject(socket.assigns.members, &(&1.id == id))
|
||||
{:noreply, assign(socket, :members, updated_members)}
|
||||
|
|
@ -678,7 +683,8 @@ defmodule MvWeb.MemberLive.Index do
|
|||
|
||||
# Note: Using Ash.read! - errors will be handled by Phoenix LiveView
|
||||
# This is appropriate for data loading in LiveViews
|
||||
members = Ash.read!(query)
|
||||
actor = socket.assigns[:current_user]
|
||||
members = Ash.read!(query, actor: actor)
|
||||
|
||||
# Custom field values are already filtered at the database level in load_custom_field_values/2
|
||||
# No need for in-memory filtering anymore
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ defmodule MvWeb.MemberLive.Show do
|
|||
use MvWeb, :live_view
|
||||
import Ash.Query
|
||||
|
||||
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
||||
|
||||
alias MvWeb.Helpers.MembershipFeeHelpers
|
||||
|
||||
@impl true
|
||||
|
|
@ -148,9 +150,9 @@ defmodule MvWeb.MemberLive.Show do
|
|||
</div>
|
||||
|
||||
<%!-- Custom Fields Section --%>
|
||||
<%= if is_list(@custom_fields) && Enum.any?(@custom_fields) do %>
|
||||
<%= if Enum.any?(@custom_fields) do %>
|
||||
<div>
|
||||
<.section_box title={gettext("Additional Data Fields")}>
|
||||
<.section_box title={gettext("Custom Fields")}>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<%= for custom_field <- @custom_fields do %>
|
||||
<% cfv = find_custom_field_value(@member.custom_field_values, custom_field.id) %>
|
||||
|
|
@ -220,6 +222,7 @@ defmodule MvWeb.MemberLive.Show do
|
|||
module={MvWeb.MemberLive.Show.MembershipFeesComponent}
|
||||
id={"membership-fees-#{@member.id}"}
|
||||
member={@member}
|
||||
current_user={@current_user}
|
||||
/>
|
||||
<% end %>
|
||||
</Layouts.app>
|
||||
|
|
@ -233,15 +236,15 @@ defmodule MvWeb.MemberLive.Show do
|
|||
|
||||
@impl true
|
||||
def handle_params(%{"id" => id}, _, socket) do
|
||||
# Load custom fields for display
|
||||
# Note: Each page load starts a new LiveView process, so caching with
|
||||
# assign_new is not necessary here (mount creates a fresh socket each time)
|
||||
custom_fields =
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Query.sort(name: :asc)
|
||||
|> Ash.read!()
|
||||
actor = socket.assigns[:current_user]
|
||||
|
||||
socket = assign(socket, :custom_fields, custom_fields)
|
||||
# Load custom fields once using assign_new to avoid repeated queries
|
||||
socket =
|
||||
assign_new(socket, :custom_fields, fn ->
|
||||
Mv.Membership.CustomField
|
||||
|> Ash.Query.sort(name: :asc)
|
||||
|> Ash.read!(actor: actor)
|
||||
end)
|
||||
|
||||
query =
|
||||
Mv.Membership.Member
|
||||
|
|
@ -253,7 +256,7 @@ defmodule MvWeb.MemberLive.Show do
|
|||
membership_fee_cycles: [:membership_fee_type]
|
||||
])
|
||||
|
||||
member = Ash.read_one!(query)
|
||||
member = Ash.read_one!(query, actor: actor)
|
||||
|
||||
# Calculate last and current cycle status from loaded cycles
|
||||
last_cycle_status = get_last_cycle_status(member)
|
||||
|
|
|
|||
|
|
@ -388,6 +388,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
@impl true
|
||||
def update(assigns, socket) do
|
||||
member = assigns.member
|
||||
actor = assigns.current_user
|
||||
|
||||
# Load cycles if not already loaded
|
||||
cycles =
|
||||
|
|
@ -401,7 +402,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
cycles = Enum.sort_by(cycles, & &1.cycle_start, {:desc, Date})
|
||||
|
||||
# Get available fee types (filtered to same interval if member has a type)
|
||||
available_fee_types = get_available_fee_types(member)
|
||||
available_fee_types = get_available_fee_types(member, actor)
|
||||
|
||||
{:ok,
|
||||
socket
|
||||
|
|
@ -422,7 +423,9 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
@impl true
|
||||
def handle_event("change_membership_fee_type", %{"value" => ""}, socket) do
|
||||
# Remove membership fee type
|
||||
case update_member_fee_type(socket.assigns.member, nil) do
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
case update_member_fee_type(socket.assigns.member, nil, actor) do
|
||||
{:ok, updated_member} ->
|
||||
send(self(), {:member_updated, updated_member})
|
||||
|
||||
|
|
@ -430,7 +433,10 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
socket
|
||||
|> assign(:member, updated_member)
|
||||
|> assign(:cycles, [])
|
||||
|> assign(:available_fee_types, get_available_fee_types(updated_member))
|
||||
|> assign(
|
||||
:available_fee_types,
|
||||
get_available_fee_types(updated_member, socket.assigns.current_user)
|
||||
)
|
||||
|> assign(:interval_warning, nil)
|
||||
|> put_flash(:info, gettext("Membership fee type removed"))}
|
||||
|
||||
|
|
@ -441,7 +447,8 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
|
||||
def handle_event("change_membership_fee_type", %{"value" => fee_type_id}, socket) do
|
||||
member = socket.assigns.member
|
||||
new_fee_type = Ash.get!(MembershipFeeType, fee_type_id, domain: MembershipFees)
|
||||
actor = socket.assigns.current_user
|
||||
new_fee_type = Ash.get!(MembershipFeeType, fee_type_id, domain: MembershipFees, actor: actor)
|
||||
|
||||
# Check if interval matches
|
||||
interval_warning =
|
||||
|
|
@ -459,15 +466,22 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
if interval_warning do
|
||||
{:noreply, assign(socket, :interval_warning, interval_warning)}
|
||||
else
|
||||
case update_member_fee_type(member, fee_type_id) do
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
case update_member_fee_type(member, fee_type_id, actor) do
|
||||
{:ok, updated_member} ->
|
||||
# Reload member with cycles
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
updated_member =
|
||||
updated_member
|
||||
|> Ash.load!([
|
||||
:membership_fee_type,
|
||||
membership_fee_cycles: [:membership_fee_type]
|
||||
])
|
||||
|> Ash.load!(
|
||||
[
|
||||
:membership_fee_type,
|
||||
membership_fee_cycles: [:membership_fee_type]
|
||||
],
|
||||
actor: actor
|
||||
)
|
||||
|
||||
cycles =
|
||||
Enum.sort_by(
|
||||
|
|
@ -482,7 +496,10 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
socket
|
||||
|> assign(:member, updated_member)
|
||||
|> assign(:cycles, cycles)
|
||||
|> assign(:available_fee_types, get_available_fee_types(updated_member))
|
||||
|> assign(
|
||||
:available_fee_types,
|
||||
get_available_fee_types(updated_member, socket.assigns.current_user)
|
||||
)
|
||||
|> assign(:interval_warning, nil)
|
||||
|> put_flash(:info, gettext("Membership fee type updated. Cycles regenerated."))}
|
||||
|
||||
|
|
@ -503,7 +520,9 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
:suspended -> :mark_as_suspended
|
||||
end
|
||||
|
||||
case Ash.update(cycle, action: action, domain: MembershipFees) do
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
case Ash.update(cycle, action: action, domain: MembershipFees, actor: actor) do
|
||||
{:ok, updated_cycle} ->
|
||||
updated_cycles = replace_cycle(socket.assigns.cycles, updated_cycle)
|
||||
|
||||
|
|
@ -537,12 +556,17 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
case CycleGenerator.generate_cycles_for_member(member.id) do
|
||||
{:ok, _new_cycles, _notifications} ->
|
||||
# Reload member with cycles
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
updated_member =
|
||||
member
|
||||
|> Ash.load!([
|
||||
:membership_fee_type,
|
||||
membership_fee_cycles: [:membership_fee_type]
|
||||
])
|
||||
|> Ash.load!(
|
||||
[
|
||||
:membership_fee_type,
|
||||
membership_fee_cycles: [:membership_fee_type]
|
||||
],
|
||||
actor: actor
|
||||
)
|
||||
|
||||
cycles =
|
||||
Enum.sort_by(
|
||||
|
|
@ -572,7 +596,8 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
cycle = find_cycle(socket.assigns.cycles, cycle_id)
|
||||
|
||||
# Load cycle with membership_fee_type for display
|
||||
cycle = Ash.load!(cycle, :membership_fee_type)
|
||||
actor = socket.assigns.current_user
|
||||
cycle = Ash.load!(cycle, :membership_fee_type, actor: actor)
|
||||
|
||||
{:noreply, assign(socket, :editing_cycle, cycle)}
|
||||
end
|
||||
|
|
@ -589,9 +614,11 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
|
||||
case Decimal.parse(normalized_amount_str) do
|
||||
{amount, _} when is_struct(amount, Decimal) ->
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
case cycle
|
||||
|> Ash.Changeset.for_update(:update, %{amount: amount})
|
||||
|> Ash.update(domain: MembershipFees) do
|
||||
|> Ash.update(domain: MembershipFees, actor: actor) do
|
||||
{:ok, updated_cycle} ->
|
||||
updated_cycles = replace_cycle(socket.assigns.cycles, updated_cycle)
|
||||
|
||||
|
|
@ -616,7 +643,8 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
cycle = find_cycle(socket.assigns.cycles, cycle_id)
|
||||
|
||||
# Load cycle with membership_fee_type for display
|
||||
cycle = Ash.load!(cycle, :membership_fee_type)
|
||||
actor = socket.assigns.current_user
|
||||
cycle = Ash.load!(cycle, :membership_fee_type, actor: actor)
|
||||
|
||||
{:noreply, assign(socket, :deleting_cycle, cycle)}
|
||||
end
|
||||
|
|
@ -627,8 +655,9 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
|
||||
def handle_event("confirm_delete_cycle", %{"cycle_id" => cycle_id}, socket) do
|
||||
cycle = find_cycle(socket.assigns.cycles, cycle_id)
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
case Ash.destroy(cycle, domain: MembershipFees) do
|
||||
case Ash.destroy(cycle, domain: MembershipFees, actor: actor) do
|
||||
:ok ->
|
||||
updated_cycles = Enum.reject(socket.assigns.cycles, &(&1.id == cycle_id))
|
||||
|
||||
|
|
@ -699,12 +728,17 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
|
||||
if deleted_count > 0 do
|
||||
# Reload member to get updated cycles
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
updated_member =
|
||||
member
|
||||
|> Ash.load!([
|
||||
:membership_fee_type,
|
||||
membership_fee_cycles: [:membership_fee_type]
|
||||
])
|
||||
|> Ash.load!(
|
||||
[
|
||||
:membership_fee_type,
|
||||
membership_fee_cycles: [:membership_fee_type]
|
||||
],
|
||||
actor: actor
|
||||
)
|
||||
|
||||
updated_cycles =
|
||||
Enum.sort_by(
|
||||
|
|
@ -786,15 +820,20 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
membership_fee_type_id: member.membership_fee_type_id
|
||||
}
|
||||
|
||||
case Ash.create(MembershipFeeCycle, attrs, domain: MembershipFees) do
|
||||
actor = socket.assigns.current_user
|
||||
|
||||
case Ash.create(MembershipFeeCycle, attrs, domain: MembershipFees, actor: actor) do
|
||||
{:ok, _new_cycle} ->
|
||||
# Reload member with cycles
|
||||
updated_member =
|
||||
member
|
||||
|> Ash.load!([
|
||||
:membership_fee_type,
|
||||
membership_fee_cycles: [:membership_fee_type]
|
||||
])
|
||||
|> Ash.load!(
|
||||
[
|
||||
:membership_fee_type,
|
||||
membership_fee_cycles: [:membership_fee_type]
|
||||
],
|
||||
actor: actor
|
||||
)
|
||||
|
||||
cycles =
|
||||
Enum.sort_by(
|
||||
|
|
@ -842,11 +881,11 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
|
||||
# Helper functions
|
||||
|
||||
defp get_available_fee_types(member) do
|
||||
defp get_available_fee_types(member, actor) do
|
||||
all_types =
|
||||
MembershipFeeType
|
||||
|> Ash.Query.sort(name: :asc)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(domain: MembershipFees, actor: actor)
|
||||
|
||||
# If member has a fee type, filter to same interval
|
||||
if member.membership_fee_type do
|
||||
|
|
@ -858,12 +897,12 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|
|||
end
|
||||
end
|
||||
|
||||
defp update_member_fee_type(member, fee_type_id) do
|
||||
defp update_member_fee_type(member, fee_type_id, actor) do
|
||||
attrs = %{membership_fee_type_id: fee_type_id}
|
||||
|
||||
member
|
||||
|> Ash.Changeset.for_update(:update_member, attrs, domain: Membership)
|
||||
|> Ash.update(domain: Membership)
|
||||
|> Ash.update(domain: Membership, actor: actor)
|
||||
end
|
||||
|
||||
defp find_cycle(cycles, cycle_id) do
|
||||
|
|
|
|||
|
|
@ -2125,6 +2125,11 @@ msgstr "Zusätzliche Datenfelder"
|
|||
#~ msgid "Pending"
|
||||
#~ msgstr "Ausstehend"
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You do not have permission to %{action} members."
|
||||
msgstr ""
|
||||
|
||||
#~ #: lib/mv_web/live/member_live/form.ex
|
||||
#~ #: lib/mv_web/live/member_live/show.ex
|
||||
#~ #: lib/mv_web/translations/member_fields.ex
|
||||
|
|
|
|||
|
|
@ -2063,3 +2063,8 @@ msgstr ""
|
|||
#, elixir-autogen, elixir-format
|
||||
msgid "Additional Data Fields"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You do not have permission to %{action} members."
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -2064,6 +2064,11 @@ msgstr ""
|
|||
msgid "Additional Data Fields"
|
||||
msgstr ""
|
||||
|
||||
#: lib/mv_web/live/member_live/form.ex
|
||||
#, elixir-autogen, elixir-format
|
||||
msgid "You do not have permission to %{action} members."
|
||||
msgstr ""
|
||||
|
||||
#~ #: lib/mv_web/live/components/payment_filter_component.ex
|
||||
#~ #, elixir-autogen, elixir-format
|
||||
#~ msgid "All payment statuses"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue