add admin products list and detail pages

Read-mostly admin views for synced products: filterable/sortable list
with inline visibility toggle, and detail page with images grid,
variants table, storefront controls form, and provider edit links.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-16 08:48:51 +00:00
parent ccc14aa5e1
commit 81e94d0d65
9 changed files with 890 additions and 2 deletions

View File

@@ -97,7 +97,7 @@ defmodule SimpleshopThemeWeb.Admin.DashboardTest do
{:ok, view, _html} = live(conn, ~p"/admin")
assert has_element?(view, "a[href='/admin/orders']", "Orders")
assert has_element?(view, "a[href='/admin/settings']", "Products")
assert has_element?(view, "a[href='/admin/products']", "Products")
end
test "shows zero state for orders", %{conn: conn} do

View File

@@ -0,0 +1,190 @@
defmodule SimpleshopThemeWeb.Admin.ProductsTest do
use SimpleshopThemeWeb.ConnCase, async: false
import Phoenix.LiveViewTest
import SimpleshopTheme.AccountsFixtures
import SimpleshopTheme.ProductsFixtures
setup do
user = user_fixture()
conn = provider_connection_fixture(%{provider_type: "printify", name: "Test Shop"})
product = complete_product_fixture(%{provider_connection: conn})
%{user: user, connection: conn, product: product}
end
describe "unauthenticated" do
test "product list redirects to login", %{conn: conn} do
{:error, redirect} = live(conn, ~p"/admin/products")
assert {:redirect, %{to: path}} = redirect
assert path == ~p"/users/log-in"
end
test "product detail redirects to login", %{conn: conn, product: product} do
{:error, redirect} = live(conn, ~p"/admin/products/#{product}")
assert {:redirect, %{to: path}} = redirect
assert path == ~p"/users/log-in"
end
end
describe "product list" do
setup %{conn: conn, user: user} do
%{conn: log_in_user(conn, user)}
end
test "renders product list", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/admin/products")
assert html =~ "Products"
assert html =~ product.title
end
test "shows provider badge", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/admin/products")
assert html =~ "Printify"
end
test "shows category", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/admin/products")
assert html =~ product.category
end
test "links to product detail", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/admin/products")
assert html =~ ~p"/admin/products/#{product}"
end
test "toggle visibility updates product", %{conn: conn, product: product} do
{:ok, view, _html} = live(conn, ~p"/admin/products")
assert product.visible
view
|> element("button[phx-value-id='#{product.id}']")
|> render_click()
updated = SimpleshopTheme.Products.get_product(product.id)
refute updated.visible
end
test "filters by visibility", %{conn: conn, product: product} do
SimpleshopTheme.Products.update_storefront(product, %{visible: false})
{:ok, view, _html} = live(conn, ~p"/admin/products")
html =
view
|> element("form")
|> render_change(%{"visibility" => "hidden"})
assert html =~ product.title
html =
view
|> element("form")
|> render_change(%{"visibility" => "visible"})
refute html =~ product.title
end
test "filters by category", %{conn: conn, product: product} do
{:ok, view, _html} = live(conn, ~p"/admin/products")
html =
view
|> element("form")
|> render_change(%{"category" => product.category})
assert html =~ product.title
html =
view
|> element("form")
|> render_change(%{"category" => "Nonexistent"})
refute html =~ product.title
end
test "sorts by name", %{conn: conn, product: product} do
{:ok, view, _html} = live(conn, ~p"/admin/products")
html =
view
|> element("form")
|> render_change(%{"sort" => "name_asc"})
assert html =~ product.title
end
test "shows empty state when no products", %{conn: conn, product: product} do
SimpleshopTheme.Products.delete_product(product)
{:ok, _view, html} = live(conn, ~p"/admin/products")
assert html =~ "No products yet"
assert html =~ "Connect a provider"
end
end
describe "product detail" do
setup %{conn: conn, user: user} do
%{conn: log_in_user(conn, user)}
end
test "renders product detail", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/admin/products/#{product}")
assert html =~ product.title
assert html =~ "Details"
assert html =~ "Storefront controls"
assert html =~ "Variants"
end
test "shows provider link", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/admin/products/#{product}")
assert html =~ "Edit on Printify"
end
test "shows view on shop link", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/admin/products/#{product}")
assert html =~ ~p"/products/#{product.slug}"
end
test "shows variant details", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/admin/products/#{product}")
assert html =~ "£25.00"
end
test "saves storefront changes", %{conn: conn, product: product} do
{:ok, view, _html} = live(conn, ~p"/admin/products/#{product}")
view
|> element("form")
|> render_submit(%{"product" => %{"visible" => "false", "category" => "New Category"}})
updated = SimpleshopTheme.Products.get_product(product.id)
refute updated.visible
assert updated.category == "New Category"
end
test "redirects on not found", %{conn: conn} do
{:error, {:live_redirect, %{to: path}}} =
live(conn, "/admin/products/00000000-0000-0000-0000-000000000000")
assert path == "/admin/products"
end
test "back link navigates to list", %{conn: conn, product: product} do
{:ok, _view, html} = live(conn, ~p"/admin/products/#{product}")
assert html =~ ~s(href="/admin/products")
assert html =~ "Products"
end
end
end