migrate remaining admin pages to inline feedback

Replace put_flash with inline feedback for form saves:
- Media library: metadata save shows "Saved" checkmark
- Product show: storefront controls save shows "Saved" checkmark
- Newsletter campaign form: draft save shows "Saved" checkmark

Page-level outcomes (uploads, deletes, async operations) remain as
flash/banner messages — these are the correct pattern for non-form
actions.

Completes Task 4 of notification overhaul.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-03-08 07:34:17 +00:00
parent 0834437340
commit db28cb8d9f
4 changed files with 23 additions and 14 deletions

View File

@ -76,7 +76,7 @@ Replace floating toast/flash messages with inline feedback and persistent top ba
| 1 | Build inline feedback component | 1.5h | done | | 1 | Build inline feedback component | 1.5h | done |
| 2 | Build persistent top banner component (replaces flash) | 1.5h | done | | 2 | Build persistent top banner component (replaces flash) | 1.5h | done |
| 3 | Migrate admin forms to inline feedback (theme, pages, settings, email, providers) | 3h | done | | 3 | Migrate admin forms to inline feedback (theme, pages, settings, email, providers) | 3h | done |
| 4 | Migrate remaining admin pages (media, products, activity, newsletter, redirects, nav) | 2h | planned | | 4 | Migrate remaining admin pages (media, products, activity, newsletter, redirects, nav) | 2h | done |
| 5 | Migrate shop pages (cart, contact, checkout, auth) | 2h | planned | | 5 | Migrate shop pages (cart, contact, checkout, auth) | 2h | planned |
| 6 | Migrate setup wizard notifications | 1h | planned | | 6 | Migrate setup wizard notifications | 1h | planned |
| 7 | Remove old flash/toast CSS and JS | 30m | planned | | 7 | Remove old flash/toast CSS and JS | 30m | planned |

View File

@ -17,6 +17,7 @@ defmodule BerrypodWeb.Admin.Media do
|> assign(:edit_form, nil) |> assign(:edit_form, nil)
|> assign(:upload_alt, "") |> assign(:upload_alt, "")
|> assign(:confirm_delete, false) |> assign(:confirm_delete, false)
|> assign(:metadata_status, :idle)
|> allow_upload(:media_upload, |> allow_upload(:media_upload,
accept: ~w(.png .jpg .jpeg .webp .svg .gif), accept: ~w(.png .jpg .jpeg .webp .svg .gif),
max_entries: 1, max_entries: 1,
@ -114,7 +115,8 @@ defmodule BerrypodWeb.Admin.Media do
|> assign(:selected_image, Map.put(image, :data, nil)) |> assign(:selected_image, Map.put(image, :data, nil))
|> assign(:selected_usages, usages) |> assign(:selected_usages, usages)
|> assign(:edit_form, form) |> assign(:edit_form, form)
|> assign(:confirm_delete, false)} |> assign(:confirm_delete, false)
|> assign(:metadata_status, :idle)}
else else
{:noreply, socket} {:noreply, socket}
end end
@ -141,10 +143,10 @@ defmodule BerrypodWeb.Admin.Media do
socket socket
|> stream_insert(:images, updated_no_blob) |> stream_insert(:images, updated_no_blob)
|> assign(:selected_image, updated_no_blob) |> assign(:selected_image, updated_no_blob)
|> put_flash(:info, "Metadata updated")} |> assign(:metadata_status, :saved)}
{:error, _changeset} -> {:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Failed to update metadata")} {:noreply, assign(socket, :metadata_status, :error)}
end end
end end
@ -398,9 +400,12 @@ defmodule BerrypodWeb.Admin.Media do
<.input field={@edit_form[:alt]} label="Alt text" placeholder="Describe this image..." /> <.input field={@edit_form[:alt]} label="Alt text" placeholder="Describe this image..." />
<.input field={@edit_form[:caption]} label="Caption" placeholder="Optional caption..." /> <.input field={@edit_form[:caption]} label="Caption" placeholder="Optional caption..." />
<.input field={@edit_form[:tags]} label="Tags" placeholder="hero, homepage, banner..." /> <.input field={@edit_form[:tags]} label="Tags" placeholder="hero, homepage, banner..." />
<div class="admin-form-actions-sm">
<button type="submit" class="admin-btn admin-btn-primary admin-btn-sm"> <button type="submit" class="admin-btn admin-btn-primary admin-btn-sm">
Save metadata Save metadata
</button> </button>
<.inline_feedback status={@metadata_status} />
</div>
</.form> </.form>
<%= if @selected_usages != [] do %> <%= if @selected_usages != [] do %>

View File

@ -10,7 +10,8 @@ defmodule BerrypodWeb.Admin.Newsletter.CampaignForm do
|> assign(:page_title, "New campaign") |> assign(:page_title, "New campaign")
|> assign(:campaign, nil) |> assign(:campaign, nil)
|> assign(:subscriber_count, Newsletter.confirmed_subscriber_count()) |> assign(:subscriber_count, Newsletter.confirmed_subscriber_count())
|> assign(:form, to_form(%{"subject" => "", "body" => ""}, as: :campaign))} |> assign(:form, to_form(%{"subject" => "", "body" => ""}, as: :campaign))
|> assign(:save_status, :idle)}
end end
@impl true @impl true
@ -42,7 +43,7 @@ defmodule BerrypodWeb.Admin.Newsletter.CampaignForm do
@impl true @impl true
def handle_event("validate", %{"campaign" => params}, socket) do def handle_event("validate", %{"campaign" => params}, socket) do
{:noreply, assign(socket, :form, to_form(params, as: :campaign))} {:noreply, assign(socket, form: to_form(params, as: :campaign), save_status: :idle)}
end end
def handle_event("save_draft", %{"campaign" => params}, socket) do def handle_event("save_draft", %{"campaign" => params}, socket) do
@ -51,10 +52,10 @@ defmodule BerrypodWeb.Admin.Newsletter.CampaignForm do
{:noreply, {:noreply,
socket socket
|> assign(:campaign, campaign) |> assign(:campaign, campaign)
|> put_flash(:info, "Campaign saved")} |> assign(:save_status, :saved)}
{:error, _changeset} -> {:error, _changeset} ->
{:noreply, put_flash(socket, :error, "Please fill in subject and body")} {:noreply, assign(socket, :save_status, :error)}
end end
end end
@ -211,6 +212,7 @@ defmodule BerrypodWeb.Admin.Newsletter.CampaignForm do
<.button type="submit"> <.button type="submit">
Save draft Save draft
</.button> </.button>
<.inline_feedback status={@save_status} />
<button <button
type="button" type="button"

View File

@ -27,6 +27,7 @@ defmodule BerrypodWeb.Admin.ProductShow do
|> assign(:page_title, product.title) |> assign(:page_title, product.title)
|> assign(:product, product) |> assign(:product, product)
|> assign(:form, form) |> assign(:form, form)
|> assign(:save_status, :idle)
{:ok, socket} {:ok, socket}
end end
@ -40,7 +41,7 @@ defmodule BerrypodWeb.Admin.ProductShow do
|> Map.put(:action, :validate) |> Map.put(:action, :validate)
|> to_form(as: "product") |> to_form(as: "product")
{:noreply, assign(socket, :form, form)} {:noreply, assign(socket, form: form, save_status: :idle)}
end end
@impl true @impl true
@ -63,12 +64,12 @@ defmodule BerrypodWeb.Admin.ProductShow do
socket socket
|> assign(:product, product) |> assign(:product, product)
|> assign(:form, form) |> assign(:form, form)
|> put_flash(:info, "Product updated") |> assign(:save_status, :saved)
{:noreply, socket} {:noreply, socket}
{:error, changeset} -> {:error, changeset} ->
{:noreply, assign(socket, :form, to_form(changeset, as: "product"))} {:noreply, assign(socket, form: to_form(changeset, as: "product"), save_status: :error)}
end end
end end
@ -206,6 +207,7 @@ defmodule BerrypodWeb.Admin.ProductShow do
/> />
</label> </label>
<.button type="submit" variant="primary" class="admin-btn-sm">Save</.button> <.button type="submit" variant="primary" class="admin-btn-sm">Save</.button>
<.inline_feedback status={@save_status} />
</.form> </.form>
</div> </div>
</div> </div>