Merge branch 'main' into feature/209_hide_field_dropdown
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Moritz 2025-12-03 18:58:40 +01:00
commit 5ae4450444
Signed by: moritz
GPG key ID: 1020A035E5DD0824
14 changed files with 727 additions and 554 deletions

View file

@ -1,142 +0,0 @@
defmodule MvWeb.CustomFieldLive.Form do
@moduledoc """
LiveView form for creating and editing custom fields (admin).
## Features
- Create new custom field definitions
- Edit existing custom fields
- Select value type from supported types
- Set immutable and required flags
- Real-time validation
## Form Fields
**Required:**
- name - Unique identifier (e.g., "phone_mobile", "emergency_contact")
- value_type - Data type (:string, :integer, :boolean, :date, :email)
**Optional:**
- description - Human-readable explanation
- immutable - If true, values cannot be changed after creation (default: false)
- required - If true, all members must have this custom field (default: false)
- show_in_overview - If true, this custom field will be displayed in the member overview table (default: true)
## Value Type Selection
- `:string` - Text data (unlimited length)
- `:integer` - Numeric data
- `:boolean` - True/false flags
- `:date` - Date values
- `:email` - Validated email addresses
## Events
- `validate` - Real-time form validation
- `save` - Submit form (create or update custom field)
## Security
Custom field management is restricted to admin users.
"""
use MvWeb, :live_view
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{@page_title}
<:subtitle>
{gettext("Use this form to manage custom_field records in your database.")}
</:subtitle>
</.header>
<.form for={@form} id="custom_field-form" phx-change="validate" phx-submit="save">
<.input field={@form[:name]} type="text" label={gettext("Name")} />
<.input
field={@form[:value_type]}
type="select"
label={gettext("Value type")}
options={
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of]
}
/>
<.input field={@form[:description]} type="text" label={gettext("Description")} />
<.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} />
<.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
<.input field={@form[:show_in_overview]} type="checkbox" label={gettext("Show in overview")} />
<.button phx-disable-with={gettext("Saving...")} variant="primary">
{gettext("Save Custom field")}
</.button>
<.button navigate={return_path(@return_to, @custom_field)}>{gettext("Cancel")}</.button>
</.form>
</Layouts.app>
"""
end
@impl true
def mount(params, _session, socket) do
custom_field =
case params["id"] do
nil -> nil
id -> Ash.get!(Mv.Membership.CustomField, id)
end
action = if is_nil(custom_field), do: "New", else: "Edit"
page_title = action <> " " <> "Custom field"
{:ok,
socket
|> assign(:return_to, return_to(params["return_to"]))
|> assign(custom_field: custom_field)
|> assign(:page_title, page_title)
|> assign_form()}
end
defp return_to("show"), do: "show"
defp return_to(_), do: "index"
@impl true
def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do
{:noreply,
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))}
end
def handle_event("save", %{"custom_field" => custom_field_params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form, params: custom_field_params) do
{:ok, custom_field} ->
notify_parent({:saved, custom_field})
action =
case socket.assigns.form.source.type do
:create -> gettext("create")
:update -> gettext("update")
other -> to_string(other)
end
socket =
socket
|> put_flash(:info, gettext("Custom field %{action} successfully", action: action))
|> push_navigate(to: return_path(socket.assigns.return_to, custom_field))
{:noreply, socket}
{:error, form} ->
{:noreply, assign(socket, form: form)}
end
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
defp assign_form(%{assigns: %{custom_field: custom_field}} = socket) do
form =
if custom_field do
AshPhoenix.Form.for_update(custom_field, :update, as: "custom_field")
else
AshPhoenix.Form.for_create(Mv.Membership.CustomField, :create, as: "custom_field")
end
assign(socket, form: to_form(form))
end
defp return_path("index", _custom_field), do: ~p"/custom_fields"
defp return_path("show", custom_field), do: ~p"/custom_fields/#{custom_field.id}"
end

View file

@ -0,0 +1,127 @@
defmodule MvWeb.CustomFieldLive.FormComponent do
@moduledoc """
LiveComponent form for creating and editing custom fields (embedded in settings).
## Features
- Create new custom field definitions
- Edit existing custom fields
- Select value type from supported types
- Set immutable and required flags
- Real-time validation
## Props
- `custom_field` - The custom field to edit (nil for new)
- `on_save` - Callback function to call when form is saved
- `on_cancel` - Callback function to call when form is cancelled
"""
use MvWeb, :live_component
@impl true
def render(assigns) do
~H"""
<div id={@id} class="mb-8 border shadow-xl card border-base-300">
<div class="card-body">
<div class="flex items-center gap-4 mb-4">
<.button
type="button"
phx-click="cancel"
phx-target={@myself}
aria-label={gettext("Back to custom field overview")}
>
<.icon name="hero-arrow-left" class="w-4 h-4" />
</.button>
<h3 class="card-title">
{if @custom_field, do: gettext("Edit Custom Field"), else: gettext("New Custom Field")}
</h3>
</div>
<.form
for={@form}
id={@id <> "-form"}
phx-change="validate"
phx-submit="save"
phx-target={@myself}
>
<.input field={@form[:name]} type="text" label={gettext("Name")} />
<.input
field={@form[:value_type]}
type="select"
label={gettext("Value type")}
options={
Ash.Resource.Info.attribute(Mv.Membership.CustomField, :value_type).constraints[:one_of]
}
/>
<.input field={@form[:description]} type="text" label={gettext("Description")} />
<.input field={@form[:immutable]} type="checkbox" label={gettext("Immutable")} />
<.input field={@form[:required]} type="checkbox" label={gettext("Required")} />
<.input
field={@form[:show_in_overview]}
type="checkbox"
label={gettext("Show in overview")}
/>
<div class="justify-end mt-4 card-actions">
<.button type="button" phx-click="cancel" phx-target={@myself}>
{gettext("Cancel")}
</.button>
<.button phx-disable-with={gettext("Saving...")} variant="primary">
{gettext("Save Custom field")}
</.button>
</div>
</.form>
</div>
</div>
"""
end
@impl true
def update(assigns, socket) do
{:ok,
socket
|> assign(assigns)
|> assign_form()}
end
@impl true
def handle_event("validate", %{"custom_field" => custom_field_params}, socket) do
{:noreply,
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, custom_field_params))}
end
@impl true
def handle_event("save", %{"custom_field" => custom_field_params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form, params: custom_field_params) do
{:ok, custom_field} ->
action =
case socket.assigns.form.source.type do
:create -> gettext("create")
:update -> gettext("update")
other -> to_string(other)
end
socket.assigns.on_save.(custom_field, action)
{:noreply, socket}
{:error, form} ->
{:noreply, assign(socket, form: form)}
end
end
@impl true
def handle_event("cancel", _params, socket) do
socket.assigns.on_cancel.()
{:noreply, socket}
end
defp assign_form(%{assigns: %{custom_field: custom_field}} = socket) do
form =
if custom_field do
AshPhoenix.Form.for_update(custom_field, :update, as: "custom_field")
else
AshPhoenix.Form.for_create(Mv.Membership.CustomField, :create, as: "custom_field")
end
assign(socket, form: to_form(form))
end
end

View file

@ -1,199 +0,0 @@
defmodule MvWeb.CustomFieldLive.Index do
@moduledoc """
LiveView for managing custom field definitions (admin).
## Features
- List all custom fields
- Display type information (name, value type, description)
- Show immutable and required flags
- Create new custom fields
- Edit existing custom fields
- Delete custom fields with confirmation (cascades to all custom field values)
## Displayed Information
- Name: Unique identifier for the custom field
- Value type: Data type constraint (string, integer, boolean, date, email)
- Description: Human-readable explanation
- Immutable: Whether custom field values can be changed after creation
- Required: Whether all members must have this custom field (future feature)
## Events
- `prepare_delete` - Opens deletion confirmation modal with member count
- `confirm_delete` - Executes deletion after slug verification
- `cancel_delete` - Cancels deletion and closes modal
- `update_slug_confirmation` - Updates slug input state
## Security
Custom field management is restricted to admin users.
Deletion requires entering the custom field's slug to prevent accidental deletions.
"""
use MvWeb, :live_view
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
Listing Custom fields
<:actions>
<.button variant="primary" navigate={~p"/custom_fields/new"}>
<.icon name="hero-plus" /> New Custom field
</.button>
</:actions>
</.header>
<.table
id="custom_fields"
rows={@streams.custom_fields}
row_click={fn {_id, custom_field} -> JS.navigate(~p"/custom_fields/#{custom_field}") end}
>
<:col :let={{_id, custom_field}} label="Name">{custom_field.name}</:col>
<:col :let={{_id, custom_field}} label="Description">{custom_field.description}</:col>
<:action :let={{_id, custom_field}}>
<div class="sr-only">
<.link navigate={~p"/custom_fields/#{custom_field}"}>Show</.link>
</div>
<.link navigate={~p"/custom_fields/#{custom_field}/edit"}>Edit</.link>
</:action>
<:action :let={{_id, custom_field}}>
<.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id})}>
Delete
</.link>
</:action>
</.table>
<%!-- Delete Confirmation Modal --%>
<dialog :if={@show_delete_modal} id="delete-custom-field-modal" class="modal modal-open">
<div class="modal-box">
<h3 class="font-bold text-lg">{gettext("Delete Custom Field")}</h3>
<div class="py-4 space-y-4">
<div class="alert alert-warning">
<.icon name="hero-exclamation-triangle" class="h-5 w-5" />
<div>
<p class="font-semibold">
{ngettext(
"%{count} member has a value assigned for this custom field.",
"%{count} members have values assigned for this custom field.",
@custom_field_to_delete.assigned_members_count,
count: @custom_field_to_delete.assigned_members_count
)}
</p>
<p class="text-sm mt-2">
{gettext(
"All custom field values will be permanently deleted when you delete this custom field."
)}
</p>
</div>
</div>
<div>
<label for="slug-confirmation" class="label">
<span class="label-text">
{gettext("To confirm deletion, please enter this text:")}
</span>
</label>
<div class="font-mono font-bold text-lg mb-2 p-2 bg-base-200 rounded break-all">
{@custom_field_to_delete.slug}
</div>
<form phx-change="update_slug_confirmation">
<input
id="slug-confirmation"
name="slug"
type="text"
value={@slug_confirmation}
placeholder={gettext("Enter the text above to confirm")}
autocomplete="off"
phx-mounted={JS.focus()}
class="input input-bordered w-full"
/>
</form>
</div>
</div>
<div class="modal-action">
<button phx-click="cancel_delete" class="btn">
{gettext("Cancel")}
</button>
<button
phx-click="confirm_delete"
class="btn btn-error"
disabled={@slug_confirmation != @custom_field_to_delete.slug}
>
{gettext("Delete Custom Field and All Values")}
</button>
</div>
</div>
</dialog>
</Layouts.app>
"""
end
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:page_title, "Listing Custom fields")
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")
|> stream(:custom_fields, Ash.read!(Mv.Membership.CustomField))}
end
@impl true
def handle_event("prepare_delete", %{"id" => id}, socket) do
custom_field = Ash.get!(Mv.Membership.CustomField, id, load: [:assigned_members_count])
{:noreply,
socket
|> assign(:custom_field_to_delete, custom_field)
|> assign(:show_delete_modal, true)
|> assign(:slug_confirmation, "")}
end
@impl true
def handle_event("update_slug_confirmation", %{"slug" => slug}, socket) do
{:noreply, assign(socket, :slug_confirmation, slug)}
end
@impl true
def handle_event("confirm_delete", _params, socket) do
custom_field = socket.assigns.custom_field_to_delete
if socket.assigns.slug_confirmation == custom_field.slug do
# Delete the custom field (CASCADE will handle custom field values)
case Ash.destroy(custom_field) do
:ok ->
{:noreply,
socket
|> put_flash(:info, "Custom field deleted successfully")
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")
|> stream_delete(:custom_fields, custom_field)}
{:error, error} ->
{:noreply,
socket
|> put_flash(:error, "Failed to delete custom field: #{inspect(error)}")}
end
else
{:noreply,
socket
|> put_flash(:error, "Slug does not match. Deletion cancelled.")}
end
end
@impl true
def handle_event("cancel_delete", _params, socket) do
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")}
end
end

View file

@ -0,0 +1,261 @@
defmodule MvWeb.CustomFieldLive.IndexComponent do
@moduledoc """
LiveComponent for managing custom field definitions (embedded in settings).
## Features
- List all custom fields
- Display type information (name, value type, description)
- Show immutable and required flags
- Create new custom fields
- Edit existing custom fields
- Delete custom fields with confirmation (cascades to all custom field values)
"""
use MvWeb, :live_component
@impl true
def render(assigns) do
~H"""
<div id={@id}>
<.header>
{gettext("Custom Fields")}
<:subtitle>
{gettext("These will appear in addition to other data when adding new members.")}
</:subtitle>
<:actions>
<.button variant="primary" phx-click="new_custom_field" phx-target={@myself}>
<.icon name="hero-plus" /> {gettext("New Custom field")}
</.button>
</:actions>
</.header>
<%!-- Show form when creating or editing --%>
<div :if={@show_form} class="mb-8">
<.live_component
module={MvWeb.CustomFieldLive.FormComponent}
id={@form_id}
custom_field={@editing_custom_field}
on_save={
fn custom_field, action -> send(self(), {:custom_field_saved, custom_field, action}) end
}
on_cancel={fn -> send_update(__MODULE__, id: @id, show_form: false) end}
/>
</div>
<%!-- Hide table when form is visible --%>
<.table
:if={!@show_form}
id="custom_fields"
rows={@streams.custom_fields}
row_click={
fn {_id, custom_field} ->
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
end
}
>
<:col :let={{_id, custom_field}} label={gettext("Name")}>{custom_field.name}</:col>
<:col :let={{_id, custom_field}} label={gettext("Value Type")}>
{custom_field.value_type}
</:col>
<:col :let={{_id, custom_field}} label={gettext("Description")}>
{custom_field.description}
</:col>
<:col :let={{_id, custom_field}} label={gettext("Show in Overview")}>
<span :if={custom_field.show_in_overview} class="badge badge-success">
{gettext("Yes")}
</span>
<span :if={!custom_field.show_in_overview} class="badge badge-ghost">
{gettext("No")}
</span>
</:col>
<:action :let={{_id, custom_field}}>
<.link phx-click={
JS.push("edit_custom_field", value: %{id: custom_field.id}, target: @myself)
}>
{gettext("Edit")}
</.link>
</:action>
<:action :let={{_id, custom_field}}>
<.link phx-click={JS.push("prepare_delete", value: %{id: custom_field.id}, target: @myself)}>
{gettext("Delete")}
</.link>
</:action>
</.table>
<%!-- Delete Confirmation Modal --%>
<dialog :if={@show_delete_modal} id="delete-custom-field-modal" class="modal modal-open">
<div class="modal-box">
<h3 class="text-lg font-bold">{gettext("Delete Custom Field")}</h3>
<div class="py-4 space-y-4">
<div class="alert alert-warning">
<.icon name="hero-exclamation-triangle" class="w-5 h-5" />
<div>
<p class="font-semibold">
{ngettext(
"%{count} member has a value assigned for this custom field.",
"%{count} members have values assigned for this custom field.",
@custom_field_to_delete.assigned_members_count,
count: @custom_field_to_delete.assigned_members_count
)}
</p>
<p class="mt-2 text-sm">
{gettext(
"All custom field values will be permanently deleted when you delete this custom field."
)}
</p>
</div>
</div>
<div>
<label for="slug-confirmation" class="label">
<span class="label-text">
{gettext("To confirm deletion, please enter this text:")}
</span>
</label>
<div class="p-2 mb-2 font-mono text-lg font-bold break-all rounded bg-base-200">
{@custom_field_to_delete.slug}
</div>
<form phx-change="update_slug_confirmation" phx-target={@myself}>
<input
id="slug-confirmation"
name="slug"
type="text"
value={@slug_confirmation}
placeholder={gettext("Enter the text above to confirm")}
autocomplete="off"
phx-mounted={JS.focus()}
class="w-full input input-bordered"
/>
</form>
</div>
</div>
<div class="modal-action">
<button phx-click="cancel_delete" phx-target={@myself} class="btn">
{gettext("Cancel")}
</button>
<button
phx-click="confirm_delete"
phx-target={@myself}
class="btn btn-error"
disabled={@slug_confirmation != @custom_field_to_delete.slug}
>
{gettext("Delete Custom Field and All Values")}
</button>
</div>
</div>
</dialog>
</div>
"""
end
@impl true
def update(assigns, socket) do
# If show_form is explicitly provided in assigns, reset editing state
socket =
if Map.has_key?(assigns, :show_form) and assigns.show_form == false do
socket
|> assign(:editing_custom_field, nil)
|> assign(:form_id, "custom-field-form-new")
else
socket
end
{:ok,
socket
|> assign(assigns)
|> assign_new(:show_form, fn -> false end)
|> assign_new(:form_id, fn -> "custom-field-form-new" end)
|> assign_new(:editing_custom_field, fn -> nil end)
|> assign_new(:show_delete_modal, fn -> false end)
|> assign_new(:custom_field_to_delete, fn -> nil end)
|> assign_new(:slug_confirmation, fn -> "" end)
|> stream(:custom_fields, Ash.read!(Mv.Membership.CustomField), reset: true)}
end
@impl true
def handle_event("new_custom_field", _params, socket) do
{:noreply,
socket
|> assign(:show_form, true)
|> assign(:editing_custom_field, nil)
|> assign(:form_id, "custom-field-form-new")}
end
@impl true
def handle_event("edit_custom_field", %{"id" => id}, socket) do
custom_field = Ash.get!(Mv.Membership.CustomField, id)
{:noreply,
socket
|> assign(:show_form, true)
|> assign(:editing_custom_field, custom_field)
|> assign(:form_id, "custom-field-form-#{id}")}
end
@impl true
def handle_event("prepare_delete", %{"id" => id}, socket) do
custom_field = Ash.get!(Mv.Membership.CustomField, id, load: [:assigned_members_count])
{:noreply,
socket
|> assign(:custom_field_to_delete, custom_field)
|> assign(:show_delete_modal, true)
|> assign(:slug_confirmation, "")}
end
@impl true
def handle_event("update_slug_confirmation", %{"slug" => slug}, socket) do
{:noreply, assign(socket, :slug_confirmation, slug)}
end
@impl true
def handle_event("confirm_delete", _params, socket) do
custom_field = socket.assigns.custom_field_to_delete
if socket.assigns.slug_confirmation == custom_field.slug do
case Ash.destroy(custom_field) do
:ok ->
send(self(), {:custom_field_deleted, custom_field})
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")
|> stream_delete(:custom_fields, custom_field)}
{:error, error} ->
send(self(), {:custom_field_delete_error, error})
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")}
end
else
send(self(), :custom_field_slug_mismatch)
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")}
end
end
@impl true
def handle_event("cancel_delete", _params, socket) do
{:noreply,
socket
|> assign(:show_delete_modal, false)
|> assign(:custom_field_to_delete, nil)
|> assign(:slug_confirmation, "")}
end
end

View file

@ -1,75 +0,0 @@
defmodule MvWeb.CustomFieldLive.Show do
@moduledoc """
LiveView for displaying a single custom field's details (admin).
## Features
- Display custom field definition
- Show all attributes (name, value type, description, flags)
- Navigate to edit form
- Return to custom field list
## Displayed Information
- ID: Internal UUID identifier
- Slug: URL-friendly identifier (auto-generated, immutable)
- Name: Unique identifier
- Value type: Data type constraint
- Description: Optional explanation
- Immutable flag: Whether values can be changed
- Required flag: Whether all members need this custom field
## Navigation
- Back to custom field list
- Edit custom field
## Security
Custom field details are restricted to admin users.
"""
use MvWeb, :live_view
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
Custom field {@custom_field.slug}
<:subtitle>This is a custom_field record from your database.</:subtitle>
<:actions>
<.button navigate={~p"/custom_fields"}>
<.icon name="hero-arrow-left" />
</.button>
<.button
variant="primary"
navigate={~p"/custom_fields/#{@custom_field}/edit?return_to=show"}
>
<.icon name="hero-pencil-square" /> Edit Custom field
</.button>
</:actions>
</.header>
<.list>
<:item title="Id">{@custom_field.id}</:item>
<:item title="Slug">
{@custom_field.slug}
<p class="mt-2 text-sm leading-6 text-zinc-600">
{gettext("Auto-generated identifier (immutable)")}
</p>
</:item>
<:item title="Name">{@custom_field.name}</:item>
<:item title="Description">{@custom_field.description}</:item>
</.list>
</Layouts.app>
"""
end
@impl true
def mount(%{"id" => id}, _session, socket) do
{:ok,
socket
|> assign(:page_title, "Show Custom field")
|> assign(:custom_field, Ash.get!(Mv.Membership.CustomField, id))}
end
end

View file

@ -4,6 +4,7 @@ defmodule MvWeb.GlobalSettingsLive do
## Features
- Edit the association/club name
- Manage custom fields
- Real-time form validation
- Success/error feedback
@ -28,7 +29,7 @@ defmodule MvWeb.GlobalSettingsLive do
{:ok,
socket
|> assign(:page_title, gettext("Club Settings"))
|> assign(:page_title, gettext("Settings"))
|> assign(:settings, settings)
|> assign_form()}
end
@ -38,12 +39,16 @@ defmodule MvWeb.GlobalSettingsLive do
~H"""
<Layouts.app flash={@flash} current_user={@current_user}>
<.header>
{gettext("Club Settings")}
{gettext("Settings")}
<:subtitle>
{gettext("Manage global settings for the association.")}
</:subtitle>
</.header>
<%!-- Club Settings Section --%>
<.header>
{gettext("Club Settings")}
</.header>
<.form for={@form} id="settings-form" phx-change="validate" phx-submit="save">
<.input
field={@form[:club_name]}
@ -56,6 +61,12 @@ defmodule MvWeb.GlobalSettingsLive do
{gettext("Save Settings")}
</.button>
</.form>
<%!-- Custom Fields Section --%>
<.live_component
module={MvWeb.CustomFieldLive.IndexComponent}
id="custom-fields-component"
/>
</Layouts.app>
"""
end
@ -66,6 +77,7 @@ defmodule MvWeb.GlobalSettingsLive do
assign(socket, form: AshPhoenix.Form.validate(socket.assigns.form, setting_params))}
end
@impl true
def handle_event("save", %{"setting" => setting_params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form, params: setting_params) do
{:ok, updated_settings} ->
@ -82,6 +94,37 @@ defmodule MvWeb.GlobalSettingsLive do
end
end
@impl true
def handle_info({:custom_field_saved, _custom_field, action}, socket) do
send_update(MvWeb.CustomFieldLive.IndexComponent,
id: "custom-fields-component",
show_form: false
)
{:noreply,
put_flash(socket, :info, gettext("Custom field %{action} successfully", action: action))}
end
@impl true
def handle_info({:custom_field_deleted, _custom_field}, socket) do
{:noreply, put_flash(socket, :info, gettext("Custom field deleted successfully"))}
end
@impl true
def handle_info({:custom_field_delete_error, error}, socket) do
{:noreply,
put_flash(
socket,
:error,
gettext("Failed to delete custom field: %{error}", error: inspect(error))
)}
end
@impl true
def handle_info(:custom_field_slug_mismatch, socket) do
{:noreply, put_flash(socket, :error, gettext("Slug does not match. Deletion cancelled."))}
end
defp assign_form(%{assigns: %{settings: settings}} = socket) do
form =
AshPhoenix.Form.for_update(