fix: add error message to form
This commit is contained in:
parent
22593af621
commit
3aa1aee11e
2 changed files with 222 additions and 1 deletions
|
|
@ -295,11 +295,14 @@ defmodule MvWeb.MemberLive.Form do
|
||||||
handle_save_success(socket, member)
|
handle_save_success(socket, member)
|
||||||
|
|
||||||
{:error, form} ->
|
{:error, form} ->
|
||||||
{:noreply, assign(socket, form: form)}
|
handle_save_error(socket, form)
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
_e in [Ash.Error.Forbidden, Ash.Error.Forbidden.Policy] ->
|
_e in [Ash.Error.Forbidden, Ash.Error.Forbidden.Policy] ->
|
||||||
handle_save_forbidden(socket)
|
handle_save_forbidden(socket)
|
||||||
|
|
||||||
|
e ->
|
||||||
|
handle_save_exception(socket, e)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -321,6 +324,13 @@ defmodule MvWeb.MemberLive.Form do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_save_error(socket, form) do
|
||||||
|
# Always show a flash message when save fails
|
||||||
|
# Field-level validation errors are displayed in form fields, but flash provides additional feedback
|
||||||
|
error_message = extract_error_message(form)
|
||||||
|
{:noreply, socket |> assign(form: form) |> put_flash(:error, error_message)}
|
||||||
|
end
|
||||||
|
|
||||||
defp handle_save_forbidden(socket) do
|
defp handle_save_forbidden(socket) do
|
||||||
# Handle policy violations that aren't properly displayed in forms
|
# Handle policy violations that aren't properly displayed in forms
|
||||||
# AshPhoenix.Form doesn't implement FormData.Error protocol for Forbidden errors
|
# AshPhoenix.Form doesn't implement FormData.Error protocol for Forbidden errors
|
||||||
|
|
@ -332,6 +342,77 @@ defmodule MvWeb.MemberLive.Form do
|
||||||
{:noreply, put_flash(socket, :error, error_message)}
|
{:noreply, put_flash(socket, :error, error_message)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_save_exception(socket, exception) do
|
||||||
|
# Handle unexpected exceptions (database errors, network issues, etc.)
|
||||||
|
require Logger
|
||||||
|
Logger.error("Unexpected error saving member: #{inspect(exception)}")
|
||||||
|
|
||||||
|
action = get_action_name(socket.assigns.form.source.type)
|
||||||
|
error_message = gettext("Failed to %{action} member.", action: action)
|
||||||
|
|
||||||
|
{:noreply, put_flash(socket, :error, error_message)}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extracts a user-friendly error message from form errors
|
||||||
|
defp extract_error_message(form) do
|
||||||
|
# Try to extract message from source errors first
|
||||||
|
source_errors = get_source_errors(form)
|
||||||
|
|
||||||
|
case source_errors do
|
||||||
|
[%Ash.Error.Invalid{errors: errors} | _] when is_list(errors) ->
|
||||||
|
# Extract first error message
|
||||||
|
case List.first(errors) do
|
||||||
|
%{message: message} when is_binary(message) ->
|
||||||
|
gettext("Validation failed: %{message}", message: message)
|
||||||
|
|
||||||
|
%{field: field, message: message} when is_binary(message) ->
|
||||||
|
gettext("Validation failed: %{field} %{message}", field: field, message: message)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
gettext("Validation failed. Please check your input.")
|
||||||
|
end
|
||||||
|
|
||||||
|
[error | _] ->
|
||||||
|
# Try to extract message from other error types
|
||||||
|
case error do
|
||||||
|
%{message: message} when is_binary(message) -> message
|
||||||
|
error when is_struct(error) ->
|
||||||
|
# Try to use Ash.ErrorKind protocol if available
|
||||||
|
try do
|
||||||
|
Ash.ErrorKind.message(error)
|
||||||
|
rescue
|
||||||
|
Protocol.UndefinedError -> gettext("Failed to save member. Please try again.")
|
||||||
|
end
|
||||||
|
_ -> gettext("Failed to save member. Please try again.")
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
# Check if there are any field errors in the form
|
||||||
|
if has_form_errors?(form) do
|
||||||
|
gettext("Please correct the errors in the form and try again.")
|
||||||
|
else
|
||||||
|
gettext("Failed to save member. Please try again.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Checks if form has any errors
|
||||||
|
defp has_form_errors?(form) do
|
||||||
|
case Map.get(form, :errors) do
|
||||||
|
errors when is_list(errors) and length(errors) > 0 -> true
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Extracts source-level errors from form (Ash errors, etc.)
|
||||||
|
defp get_source_errors(form) do
|
||||||
|
case form.source do
|
||||||
|
%{errors: errors} when is_list(errors) -> errors
|
||||||
|
%Ash.Changeset{errors: errors} when is_list(errors) -> errors
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp get_action_name(:create), do: gettext("create")
|
defp get_action_name(:create), do: gettext("create")
|
||||||
defp get_action_name(:update), do: gettext("update")
|
defp get_action_name(:update), do: gettext("update")
|
||||||
defp get_action_name(other), do: to_string(other)
|
defp get_action_name(other), do: to_string(other)
|
||||||
|
|
|
||||||
140
test/mv_web/member_live/form_error_handling_test.exs
Normal file
140
test/mv_web/member_live/form_error_handling_test.exs
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
defmodule MvWeb.MemberLive.FormErrorHandlingTest do
|
||||||
|
@moduledoc """
|
||||||
|
Tests for error handling in the member form, specifically flash message display.
|
||||||
|
"""
|
||||||
|
use MvWeb.ConnCase, async: false
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
alias Mv.Membership.Member
|
||||||
|
|
||||||
|
require Ash.Query
|
||||||
|
|
||||||
|
describe "error handling - flash messages" do
|
||||||
|
test "shows flash message when member creation fails with validation error", %{conn: conn} do
|
||||||
|
# Create a member with the same email to trigger uniqueness error
|
||||||
|
{:ok, _existing_member} =
|
||||||
|
Member
|
||||||
|
|> Ash.Changeset.for_create(:create_member, %{
|
||||||
|
first_name: "Existing",
|
||||||
|
last_name: "Member",
|
||||||
|
email: "duplicate@example.com"
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, view, _html} = live(conn, "/members/new")
|
||||||
|
|
||||||
|
# Try to create member with duplicate email
|
||||||
|
form_data = %{
|
||||||
|
"member[first_name]" => "New",
|
||||||
|
"member[last_name]" => "Member",
|
||||||
|
"member[email]" => "duplicate@example.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> form("#member-form", form_data)
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
# Should show flash error message
|
||||||
|
assert has_element?(view, "#flash-group")
|
||||||
|
assert html =~ "error" or html =~ "Error" or html =~ "Fehler" or
|
||||||
|
html =~ "failed" or html =~ "fehlgeschlagen" or
|
||||||
|
html =~ "Validation failed" or html =~ "Validierung fehlgeschlagen"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows flash message when member creation fails with missing required fields", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, view, _html} = live(conn, "/members/new")
|
||||||
|
|
||||||
|
# Submit form with missing required fields (e.g., email)
|
||||||
|
form_data = %{
|
||||||
|
"member[first_name]" => "Test",
|
||||||
|
"member[last_name]" => "User"
|
||||||
|
# email is missing
|
||||||
|
}
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> form("#member-form", form_data)
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
# Should show flash error message
|
||||||
|
assert has_element?(view, "#flash-group")
|
||||||
|
assert html =~ "error" or html =~ "Error" or html =~ "Fehler" or
|
||||||
|
html =~ "failed" or html =~ "fehlgeschlagen" or
|
||||||
|
html =~ "Validation failed" or html =~ "Validierung fehlgeschlagen" or
|
||||||
|
html =~ "Please correct" or html =~ "Bitte korrigieren"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows flash message when member update fails", %{conn: conn} do
|
||||||
|
# Create a member to edit
|
||||||
|
{:ok, member} =
|
||||||
|
Member
|
||||||
|
|> Ash.Changeset.for_create(:create_member, %{
|
||||||
|
first_name: "Original",
|
||||||
|
last_name: "Member",
|
||||||
|
email: "original@example.com"
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
# Create another member with different email
|
||||||
|
{:ok, _other_member} =
|
||||||
|
Member
|
||||||
|
|> Ash.Changeset.for_create(:create_member, %{
|
||||||
|
first_name: "Other",
|
||||||
|
last_name: "Member",
|
||||||
|
email: "other@example.com"
|
||||||
|
})
|
||||||
|
|> Ash.create()
|
||||||
|
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, view, _html} = live(conn, "/members/#{member.id}/edit")
|
||||||
|
|
||||||
|
# Try to update with duplicate email
|
||||||
|
form_data = %{
|
||||||
|
"member[first_name]" => "Updated",
|
||||||
|
"member[last_name]" => "Member",
|
||||||
|
"member[email]" => "other@example.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> form("#member-form", form_data)
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
# Should show flash error message
|
||||||
|
assert has_element?(view, "#flash-group")
|
||||||
|
assert html =~ "error" or html =~ "Error" or html =~ "Fehler" or
|
||||||
|
html =~ "failed" or html =~ "fehlgeschlagen" or
|
||||||
|
html =~ "Validation failed" or html =~ "Validierung fehlgeschlagen"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "form still displays field-level validation errors when flash message is shown", %{
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
conn = conn_with_oidc_user(conn)
|
||||||
|
{:ok, view, _html} = live(conn, "/members/new")
|
||||||
|
|
||||||
|
# Submit form with invalid email format
|
||||||
|
form_data = %{
|
||||||
|
"member[first_name]" => "Test",
|
||||||
|
"member[last_name]" => "User",
|
||||||
|
"member[email]" => "invalid-email-format"
|
||||||
|
}
|
||||||
|
|
||||||
|
html =
|
||||||
|
view
|
||||||
|
|> form("#member-form", form_data)
|
||||||
|
|> render_submit()
|
||||||
|
|
||||||
|
# Should show both flash message and field-level error
|
||||||
|
assert has_element?(view, "#flash-group")
|
||||||
|
# Field-level errors should also be visible in the form
|
||||||
|
assert html =~ "email" or html =~ "Email"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue