add denormalized product fields and use Product structs throughout

Adds cheapest_price, compare_at_price, in_stock, on_sale columns to
products table (recomputed from variants after each sync). Shop
components now work with Product structs directly instead of plain
maps from PreviewData. Renames .name to .title, adds Product display
helpers (primary_image, hover_image, option_types) and ProductImage
helpers (display_url, direct_url, source_width). Adds Products context
query functions for storefront use (list_visible_products,
get_visible_product, list_categories with DB-level sort/filter).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-13 01:26:39 +00:00
parent 0b4fe031b7
commit 35e0386abb
20 changed files with 1000 additions and 328 deletions

View File

@@ -32,7 +32,7 @@ defmodule SimpleshopThemeWeb.Shop.CollectionTest do
products = PreviewData.products()
first_product = List.first(products)
assert html =~ first_product.name
assert html =~ first_product.title
end
test "displays category filter buttons", %{conn: conn} do
@@ -71,7 +71,7 @@ defmodule SimpleshopThemeWeb.Shop.CollectionTest do
products = PreviewData.products_by_category(category.slug)
for product <- products do
assert html =~ product.name
assert html =~ product.title
end
end
end

View File

@@ -43,7 +43,7 @@ defmodule SimpleshopThemeWeb.Shop.HomeTest do
products = PreviewData.products()
first_product = List.first(products)
assert html =~ first_product.name
assert html =~ first_product.title
end
test "renders image and text section", %{conn: conn} do

View File

@@ -17,7 +17,7 @@ defmodule SimpleshopThemeWeb.Shop.ProductShowTest do
product = List.first(PreviewData.products())
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
assert html =~ product.name
assert html =~ product.title
end
test "renders product description", %{conn: conn} do
@@ -31,7 +31,7 @@ defmodule SimpleshopThemeWeb.Shop.ProductShowTest do
product = List.first(PreviewData.products())
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
assert html =~ SimpleshopTheme.Cart.format_price(product.price)
assert html =~ SimpleshopTheme.Cart.format_price(product.cheapest_price)
end
test "renders breadcrumb with category link", %{conn: conn} do
@@ -55,7 +55,7 @@ defmodule SimpleshopThemeWeb.Shop.ProductShowTest do
# Should show other products, not the current one
other_product = Enum.at(PreviewData.products(), 1)
assert html =~ other_product.name
assert html =~ other_product.title
end
end
@@ -213,8 +213,8 @@ defmodule SimpleshopThemeWeb.Shop.ProductShowTest do
product = List.first(PreviewData.products())
# Each image should have descriptive alt text
assert html =~ "#{product.name} — image 1 of"
assert html =~ "#{product.name} — image 2 of"
assert html =~ "#{product.title} — image 1 of"
assert html =~ "#{product.title} — image 2 of"
end
test "renders dot indicators for multi-image gallery", %{conn: conn} do
@@ -278,14 +278,14 @@ defmodule SimpleshopThemeWeb.Shop.ProductShowTest do
product = Enum.at(PreviewData.products(), 1)
{:ok, _view, html} = live(conn, ~p"/products/#{product.id}")
assert html =~ product.name
assert html =~ product.title
end
test "falls back to first product for unknown ID", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/products/nonexistent")
first_product = List.first(PreviewData.products())
assert html =~ first_product.name
assert html =~ first_product.title
end
end
end