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

@@ -15,6 +15,7 @@ defmodule BerrypodWeb.Admin.Settings do
|> assign(:page_title, "Settings")
|> assign(:site_live, Settings.site_live?())
|> assign(:cart_recovery_enabled, Settings.abandoned_cart_recovery_enabled?())
|> assign(:from_address, Settings.get_setting("email_from_address") || user.email)
|> assign_stripe_state()
|> assign_products_state()
|> assign_account_state(user)}
@@ -108,6 +109,23 @@ defmodule BerrypodWeb.Admin.Settings do
|> put_flash(:info, message)}
end
# -- Events: from address --
def handle_event("save_from_address", %{"from_address" => address}, socket) do
address = String.trim(address)
if address != "" do
Settings.put_setting("email_from_address", address)
{:noreply,
socket
|> assign(:from_address, address)
|> put_flash(:info, "From address saved")}
else
{:noreply, put_flash(socket, :error, "From address can't be blank")}
end
end
# -- Events: Stripe --
def handle_event("connect_stripe", %{"stripe" => %{"api_key" => api_key}}, socket) do
@@ -415,6 +433,25 @@ defmodule BerrypodWeb.Admin.Settings do
</div>
</section>
<%!-- From address --%>
<section class="admin-section">
<h2 class="admin-section-title">From address</h2>
<p class="admin-section-desc">
The sender address on all emails from your shop.
</p>
<div class="admin-section-body">
<form phx-submit="save_from_address" class="admin-row admin-row-lg">
<.input
name="from_address"
value={@from_address}
type="email"
placeholder="noreply@yourshop.com"
/>
<.button phx-disable-with="Saving...">Save</.button>
</form>
</div>
</section>
<%!-- Account --%>
<section class="admin-section">
<h2 class="admin-section-title">Account</h2>