diff --git a/assets/css/admin/components.css b/assets/css/admin/components.css index 8e85097..27f1e3d 100644 --- a/assets/css/admin/components.css +++ b/assets/css/admin/components.css @@ -1510,6 +1510,9 @@ } .card-radio-name { + display: flex; + align-items: baseline; + gap: 0.25rem; font-size: 0.875rem; font-weight: 600; } @@ -3391,26 +3394,21 @@ margin-inline: auto; } -/* Sidebar header (logo + user email) */ -.admin-sidebar-header { - padding: 1rem; - border-bottom: 1px solid var(--t-border-default); - - & .admin-text-secondary { margin-top: 0.125rem; } -} - -/* View shop link below sidebar header */ -.admin-sidebar-view-shop { - padding: 0.5rem; - border-bottom: 1px solid var(--t-border-default); -} - /* Sidebar footer (dev tools, log out) */ .admin-sidebar-footer { padding: 0.5rem; border-top: 1px solid var(--t-border-default); } +.admin-sidebar-email { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + font-size: 0.75rem; + color: var(--t-text-secondary); +} + /* Dev tools disclosure in sidebar footer */ .admin-dev-tools { & > summary { @@ -3446,14 +3444,6 @@ padding: 0.5rem; } -/* Brand wordmark in sidebar */ -.admin-brand { - font-size: 1.125rem; - line-height: 1.75rem; - font-weight: 700; - letter-spacing: -0.025em; -} - /* Secondary text for user email, help text, meta info */ .admin-text-secondary { font-size: 0.75rem; @@ -4294,10 +4284,16 @@ gap: 0.5rem; } +.admin-setup-step-spaced { margin-top: 1.5rem; } + .admin-setup-step-header { display: flex; align-items: center; gap: 0.625rem; + font-size: 0.9375rem; + font-weight: 600; + color: var(--admin-text-primary); + margin: 0; } .admin-setup-step-number { @@ -4314,13 +4310,6 @@ flex-shrink: 0; } -.admin-setup-step-title { - font-size: 0.9375rem; - font-weight: 600; - color: var(--admin-text-primary); - margin: 0; -} - .admin-setup-step-desc { font-size: 0.8125rem; color: var(--admin-text-secondary); @@ -4379,6 +4368,9 @@ gap: 1.5rem; } +/* Hide the
summary — CSS :has(:checked) controls visibility instead */ +.admin-adapter-config-summary { display: none; } + /* Show only the adapter config matching the checked radio */ .admin-adapter-config[data-adapter] { display: none; } diff --git a/lib/berrypod_web/components/core_components.ex b/lib/berrypod_web/components/core_components.ex index e8b5377..ea24d01 100644 --- a/lib/berrypod_web/components/core_components.ex +++ b/lib/berrypod_web/components/core_components.ex @@ -288,7 +288,7 @@ defmodule BerrypodWeb.CoreComponents do ~H""" """ end diff --git a/lib/berrypod_web/components/layouts/admin.html.heex b/lib/berrypod_web/components/layouts/admin.html.heex index 3246914..80a1fa8 100644 --- a/lib/berrypod_web/components/layouts/admin.html.heex +++ b/lib/berrypod_web/components/layouts/admin.html.heex @@ -1,5 +1,5 @@
- + <%!-- main content area --%>
@@ -20,6 +20,7 @@ <%!-- page content --%>
+ <.flash_group flash={@flash} />
{@inner_content}
@@ -30,23 +31,6 @@
- -<.flash_group flash={@flash} /> diff --git a/lib/berrypod_web/controllers/email_settings_controller.ex b/lib/berrypod_web/controllers/email_settings_controller.ex index bb7e3d2..04d4c72 100644 --- a/lib/berrypod_web/controllers/email_settings_controller.ex +++ b/lib/berrypod_web/controllers/email_settings_controller.ex @@ -21,10 +21,9 @@ defmodule BerrypodWeb.EmailSettingsController do |> redirect(to: ~p"/admin/settings/email") {:error, field_errors} when is_map(field_errors) -> - message = field_errors |> Map.values() |> Enum.join(". ") - conn - |> put_flash(:error, message) + |> put_flash(:field_errors, field_errors) + |> put_flash(:error_adapter, adapter_key) |> redirect(to: ~p"/admin/settings/email") end end diff --git a/lib/berrypod_web/live/admin/email_settings.ex b/lib/berrypod_web/live/admin/email_settings.ex index 9ef0c19..7d084cd 100644 --- a/lib/berrypod_web/live/admin/email_settings.ex +++ b/lib/berrypod_web/live/admin/email_settings.ex @@ -11,7 +11,11 @@ defmodule BerrypodWeb.Admin.EmailSettings do {current_adapter, _current_values} = Mailer.current_config() saved_adapter = Settings.get_setting("email_adapter") - adapter_key = current_adapter || saved_adapter + # Controller fallback passes errors via flash after failed validation + flash_adapter = Phoenix.Flash.get(socket.assigns.flash, :error_adapter) + flash_errors = Phoenix.Flash.get(socket.assigns.flash, :field_errors) || %{} + + adapter_key = flash_adapter || current_adapter || saved_adapter grouped = Adapters.grouped() all_adapters = Adapters.all() all_values = load_all_adapter_values() @@ -32,7 +36,7 @@ defmodule BerrypodWeb.Admin.EmailSettings do |> assign(:test_error, nil) |> assign(:test_retryable, false) |> assign(:from_checklist, false) - |> assign(:field_errors, %{}) + |> assign(:field_errors, flash_errors) |> assign(:form, to_form(%{}, as: :email))} end @@ -188,10 +192,9 @@ defmodule BerrypodWeb.Admin.EmailSettings do > <%!-- Step 1: Choose a provider --%>
-
- 1 -

Choose a provider

-
+

+ 1 Choose a provider +

Email provider @@ -205,7 +208,10 @@ defmodule BerrypodWeb.Admin.EmailSettings do />
-
+
Already have your own email server? @@ -237,10 +243,9 @@ defmodule BerrypodWeb.Admin.EmailSettings do
-
+

-

- <%= cond do %> - <% @test_result == :ok -> %> - Email is working - <% @test_result == :error -> %> - Test failed - <% true -> %> - Send a test email - <% end %> -

-
+ <%= cond do %> + <% @test_result == :ok -> %> + Email is working + <% @test_result == :error -> %> + Test failed + <% true -> %> + Send a test email + <% end %> + <%= if @test_result == :ok do %>

@@ -308,35 +311,17 @@ defmodule BerrypodWeb.Admin.EmailSettings do

Send a test to {@current_scope.user.email} to check everything works.

-
- <%!-- JS: async send via LiveView --%> - - <.button - type="button" - phx-click="send_test" - disabled={@sending_test} - phx-disable-with="Sending..." - > - <.icon name="hero-paper-airplane" class="size-4" /> Send test email - - - <%!-- No-JS: form POST fallback (hides the JS button above) --%> - -
+ <.form + for={%{}} + action={~p"/admin/settings/email/test"} + phx-submit="send_test" + method="post" + class="admin-row admin-row-sm" + > + <.button disabled={@sending_test} phx-disable-with="Sending..."> + <.icon name="hero-paper-airplane" class="size-4" /> Send test email + + <% end %> <% end %>
@@ -354,13 +339,13 @@ defmodule BerrypodWeb.Admin.EmailSettings do defp adapter_config(assigns) do ~H""" - +
""" end @@ -435,12 +420,12 @@ defmodule BerrypodWeb.Admin.EmailSettings do disabled={@disabled} class="card-radio-input" /> -
- {@adapter.name} + + {@adapter.name} Recommended -
+
{@adapter.free_tier}
{@adapter.setup_hint}
diff --git a/test/berrypod_web/controllers/email_settings_controller_test.exs b/test/berrypod_web/controllers/email_settings_controller_test.exs index 3caa374..87c54f3 100644 --- a/test/berrypod_web/controllers/email_settings_controller_test.exs +++ b/test/berrypod_web/controllers/email_settings_controller_test.exs @@ -33,14 +33,18 @@ defmodule BerrypodWeb.EmailSettingsControllerTest do assert Settings.get_setting("email_adapter") == "brevo" end - test "redirects with error on validation failure", %{conn: conn} do + test "redirects with field errors on validation failure", %{conn: conn} do conn = post(conn, ~p"/admin/settings/email", %{ email: %{adapter: "brevo", brevo: %{api_key: ""}} }) assert redirected_to(conn) =~ ~p"/admin/settings/email" - assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "required" + + field_errors = Phoenix.Flash.get(conn.assigns.flash, :field_errors) + assert is_map(field_errors) + assert field_errors |> Map.values() |> Enum.any?(&String.contains?(&1, "required")) + assert Phoenix.Flash.get(conn.assigns.flash, :error_adapter) == "brevo" end end diff --git a/test/berrypod_web/live/admin/email_settings_test.exs b/test/berrypod_web/live/admin/email_settings_test.exs index 0b3fe29..5863096 100644 --- a/test/berrypod_web/live/admin/email_settings_test.exs +++ b/test/berrypod_web/live/admin/email_settings_test.exs @@ -252,7 +252,7 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do assert html =~ "sr-only" end - test "only selected adapter config is visible (hidden attribute)", %{conn: conn} do + test "only selected adapter config is open (details element)", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/settings/email") # Select SMTP @@ -260,12 +260,12 @@ defmodule BerrypodWeb.Admin.EmailSettingsTest do |> form("form[phx-change=\"form_change\"]", %{email: %{adapter: "smtp"}}) |> render_change() - # SMTP should not be hidden - refute has_element?(view, ~s([data-adapter="smtp"][hidden])) - # Others should be hidden - assert has_element?(view, ~s([data-adapter="brevo"][hidden])) - assert has_element?(view, ~s([data-adapter="sendgrid"][hidden])) - assert has_element?(view, ~s([data-adapter="postal"][hidden])) + # SMTP details should be open + assert has_element?(view, ~s(details[data-adapter="smtp"][open])) + # Others should not be open + refute has_element?(view, ~s(details[data-adapter="brevo"][open])) + refute has_element?(view, ~s(details[data-adapter="sendgrid"][open])) + refute has_element?(view, ~s(details[data-adapter="postal"][open])) end end diff --git a/test/berrypod_web/live/admin/layout_test.exs b/test/berrypod_web/live/admin/layout_test.exs index 0ead09b..74a596b 100644 --- a/test/berrypod_web/live/admin/layout_test.exs +++ b/test/berrypod_web/live/admin/layout_test.exs @@ -43,10 +43,10 @@ defmodule BerrypodWeb.Admin.LayoutTest do assert html =~ user.email end - test "shows view shop and log out links", %{conn: conn} do + test "shows shop and log out links", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/admin/orders") - assert has_element?(view, ~s(a[href="/"]), "View shop") + assert has_element?(view, ~s(a[href="/"]), "Shop") assert has_element?(view, ~s(a[href="/users/log-out"]), "Log out") end end