rework email settings UX with guided flow and friendly errors

grouped providers by category, added per-provider key validation
with cross-provider detection, friendly delivery error messages,
retryable vs config error distinction, from-address in general
settings, and "Save settings" button to match admin conventions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-04 17:12:10 +00:00
parent 67a26eb6b4
commit 7547d0d4b8
11 changed files with 1004 additions and 269 deletions

View File

@@ -29,21 +29,20 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
{:ok, _view, html} = live(conn, ~p"/admin/settings/email")
assert html =~ "Email settings"
assert html =~ "Email provider"
assert html =~ "Choose a provider"
# Provider names rendered as radio cards
assert html =~ "Postmark"
assert html =~ "Brevo"
assert html =~ "Mailjet"
assert html =~ "MailPace"
assert html =~ "Postal"
end
test "shows provider descriptions", %{conn: conn} do
test "shows setup guidance", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/admin/settings/email")
assert html =~ "Excellent deliverability tracking"
assert html =~ "All-in-one platform, GDPR-friendly"
assert html =~ "EU data processing"
assert html =~ "needs an email provider"
assert html =~ "Paste your API key"
assert html =~ "300 emails/day free"
end
test "selecting a provider shows its config fields", %{conn: conn} do
@@ -64,72 +63,60 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
test "selecting a different provider shows different fields", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
# Select Mailgun which needs api_key + domain
# Select Brevo which needs just an api_key
html =
view
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "mailgun"}})
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "brevo"}})
|> render_change()
assert html =~ "API key"
assert html =~ "Domain"
assert html =~ "Brevo"
end
test "saving config persists settings", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
# Select Postmark via form change
# Select Brevo via form change
view
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "postmark"}})
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "brevo"}})
|> render_change()
# Submit with an API key (Postmark uses UUID format)
# Submit with an API key
html =
view
|> form("form[phx-submit=\"save\"]", %{
email: %{adapter: "postmark", api_key: "abc12345-abcd-1234-abcd-123456789abc"}
email: %{adapter: "brevo", api_key: "xkeysib-abc123def456"}
})
|> render_submit()
assert html =~ "Email settings saved"
assert Settings.get_setting("email_adapter") == "postmark"
assert html =~ "Settings saved"
assert Settings.get_setting("email_adapter") == "brevo"
end
test "saving without required fields shows error", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
# Select Postmark
# Select Brevo
view
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "postmark"}})
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "brevo"}})
|> render_change()
# Submit without API key
html =
view
|> form("form[phx-submit=\"save\"]", %{email: %{adapter: "postmark", api_key: ""}})
|> form("form[phx-submit=\"save\"]", %{email: %{adapter: "brevo", api_key: ""}})
|> render_submit()
assert html =~ "API key is required"
end
test "disconnect clears email configuration", %{conn: conn} do
Settings.put_setting("email_adapter", "postmark")
Settings.put_secret("email_postmark_api_key", "pm_test_abc")
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
html = render_click(view, "disconnect")
assert html =~ "Email provider disconnected"
assert is_nil(Settings.get_setting("email_adapter"))
end
test "shows test email section when configured", %{conn: conn} do
Settings.put_setting("email_adapter", "postmark")
Settings.put_secret("email_postmark_api_key", "pm_test_abc")
{:ok, _view, html} = live(conn, ~p"/admin/settings/email")
assert html =~ "Test email"
assert html =~ "Send a test email"
assert html =~ "Send test email"
end
@@ -143,7 +130,7 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
refute html =~ "Send test email"
end
test "sending test email sets verified flag", %{conn: conn} do
test "sending test email shows success and sets verified flag", %{conn: conn} do
Settings.put_setting("email_adapter", "postmark")
Settings.put_secret("email_postmark_api_key", "pm_test_abc")
@@ -151,10 +138,38 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
html = render_click(view, "send_test")
assert html =~ "Test email sent"
assert html =~ "Email is working"
assert html =~ "Send again"
assert Mailer.email_verified?()
end
test "switching provider clears old provider settings", %{conn: conn} do
# Save Mailjet config first
Settings.put_setting("email_adapter", "mailjet")
Settings.put_secret("email_mailjet_api_key", "mj-key-123")
Settings.put_secret("email_mailjet_secret", "mj-secret-456")
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
# Switch to Brevo and save
view
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "brevo"}})
|> render_change()
view
|> form("form[phx-submit=\"save\"]", %{
email: %{adapter: "brevo", api_key: "xkeysib-switch-test"}
})
|> render_submit()
# Old Mailjet settings should be deleted
assert Settings.get_setting("email_adapter") == "brevo"
refute Settings.has_secret?("email_mailjet_api_key")
refute Settings.has_secret?("email_mailjet_secret")
# New Brevo key should exist
assert Settings.has_secret?("email_brevo_api_key")
end
test "saving config clears verified flag", %{conn: conn} do
Mailer.mark_email_verified()
assert Mailer.email_verified?()
@@ -162,30 +177,17 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
view
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "postmark"}})
|> form("form[phx-change=\"change_adapter\"]", %{email: %{adapter: "brevo"}})
|> render_change()
view
|> form("form[phx-submit=\"save\"]", %{
email: %{adapter: "postmark", api_key: "def12345-abcd-1234-abcd-123456789def"}
email: %{adapter: "brevo", api_key: "xkeysib-def789ghi012"}
})
|> render_submit()
refute Mailer.email_verified?()
end
test "disconnecting clears verified flag", %{conn: conn} do
Settings.put_setting("email_adapter", "postmark")
Settings.put_secret("email_postmark_api_key", "pm_test_abc")
Mailer.mark_email_verified()
assert Mailer.email_verified?()
{:ok, view, _html} = live(conn, ~p"/admin/settings/email")
render_click(view, "disconnect")
refute Mailer.email_verified?()
end
end
describe "unauthenticated" do

View File

@@ -240,6 +240,32 @@ defmodule BerrypodWeb.Admin.SettingsTest do
end
end
describe "from address" do
setup %{conn: conn, user: user} do
conn = log_in_user(conn, user)
%{conn: conn}
end
test "shows from address section", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/admin/settings")
assert html =~ "From address"
assert html =~ "sender address"
end
test "saves from address", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/admin/settings")
html =
view
|> form("form[phx-submit=\"save_from_address\"]", %{from_address: "shop@example.com"})
|> render_submit()
assert html =~ "From address saved"
assert Settings.get_setting("email_from_address") == "shop@example.com"
end
end
describe "advanced section" do
setup %{conn: conn, user: user} do
conn = log_in_user(conn, user)