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:
@@ -353,7 +353,7 @@ defmodule SimpleshopThemeWeb.Admin.Theme.Index do
|
||||
end)
|
||||
|
||||
display_price =
|
||||
if selected_variant, do: selected_variant.price, else: product.price
|
||||
if selected_variant, do: selected_variant.price, else: product.cheapest_price
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
@@ -374,7 +374,9 @@ defmodule SimpleshopThemeWeb.Admin.Theme.Index do
|
||||
cart_items = assigns.preview_data.cart_items
|
||||
|
||||
subtotal =
|
||||
Enum.reduce(cart_items, 0, fn item, acc -> acc + item.product.price * item.quantity end)
|
||||
Enum.reduce(cart_items, 0, fn item, acc ->
|
||||
acc + item.product.cheapest_price * item.quantity
|
||||
end)
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
@@ -464,6 +466,15 @@ defmodule SimpleshopThemeWeb.Admin.Theme.Index do
|
||||
end
|
||||
|
||||
defp build_gallery_images(product) do
|
||||
[product.image_url, product.hover_image_url, product.image_url, product.hover_image_url]
|
||||
alias SimpleshopTheme.Products.ProductImage
|
||||
|
||||
(product[:images] || [])
|
||||
|> Enum.sort_by(& &1.position)
|
||||
|> Enum.map(fn img -> ProductImage.direct_url(img, 1200) end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> case do
|
||||
[] -> []
|
||||
urls -> urls
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -75,10 +75,13 @@ defmodule SimpleshopThemeWeb.Shop.Collection do
|
||||
|
||||
defp sort_products(products, "featured"), do: products
|
||||
defp sort_products(products, "newest"), do: Enum.reverse(products)
|
||||
defp sort_products(products, "price_asc"), do: Enum.sort_by(products, & &1.price)
|
||||
defp sort_products(products, "price_desc"), do: Enum.sort_by(products, & &1.price, :desc)
|
||||
defp sort_products(products, "name_asc"), do: Enum.sort_by(products, & &1.name)
|
||||
defp sort_products(products, "name_desc"), do: Enum.sort_by(products, & &1.name, :desc)
|
||||
defp sort_products(products, "price_asc"), do: Enum.sort_by(products, & &1.cheapest_price)
|
||||
|
||||
defp sort_products(products, "price_desc"),
|
||||
do: Enum.sort_by(products, & &1.cheapest_price, :desc)
|
||||
|
||||
defp sort_products(products, "name_asc"), do: Enum.sort_by(products, & &1.title)
|
||||
defp sort_products(products, "name_desc"), do: Enum.sort_by(products, & &1.title, :desc)
|
||||
defp sort_products(products, _), do: products
|
||||
|
||||
defp collection_path(slug, "featured"), do: ~p"/collections/#{slug}"
|
||||
|
||||
@@ -2,6 +2,7 @@ defmodule SimpleshopThemeWeb.Shop.ProductShow do
|
||||
use SimpleshopThemeWeb, :live_view
|
||||
|
||||
alias SimpleshopTheme.Cart
|
||||
alias SimpleshopTheme.Products.{Product, ProductImage}
|
||||
alias SimpleshopTheme.Theme.PreviewData
|
||||
|
||||
@impl true
|
||||
@@ -19,14 +20,13 @@ defmodule SimpleshopThemeWeb.Shop.ProductShow do
|
||||
|
||||
# Build gallery images from local image_id or external URL
|
||||
gallery_images =
|
||||
[
|
||||
image_src(product[:image_id], product[:image_url]),
|
||||
image_src(product[:hover_image_id], product[:hover_image_url])
|
||||
]
|
||||
(product[:images] || [])
|
||||
|> Enum.sort_by(& &1.position)
|
||||
|> Enum.map(fn img -> ProductImage.direct_url(img, 1200) end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
# Initialize variant selection
|
||||
option_types = product[:option_types] || []
|
||||
option_types = Product.option_types(product)
|
||||
variants = product[:variants] || []
|
||||
{selected_options, selected_variant} = initialize_variant_selection(variants)
|
||||
available_options = compute_available_options(option_types, variants, selected_options)
|
||||
@@ -34,7 +34,7 @@ defmodule SimpleshopThemeWeb.Shop.ProductShow do
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:page_title, product.name)
|
||||
|> assign(:page_title, product.title)
|
||||
|> assign(:product, product)
|
||||
|> assign(:gallery_images, gallery_images)
|
||||
|> assign(:related_products, related_products)
|
||||
@@ -56,16 +56,6 @@ defmodule SimpleshopThemeWeb.Shop.ProductShow do
|
||||
List.first(products)
|
||||
end
|
||||
|
||||
# Build image source URL - prefer local image_id, fall back to external URL
|
||||
defp image_src(image_id, _url) when is_binary(image_id) do
|
||||
"/images/#{image_id}/variant/1200.webp"
|
||||
end
|
||||
|
||||
# Mock data uses base paths like "/mockups/product-1" — append size + format
|
||||
defp image_src(_, "/mockups/" <> _ = url), do: "#{url}-1200.webp"
|
||||
defp image_src(_, url) when is_binary(url), do: url
|
||||
defp image_src(_, _), do: nil
|
||||
|
||||
# Select first available variant by default
|
||||
defp initialize_variant_selection([first | _] = _variants) do
|
||||
{first.options, first}
|
||||
@@ -98,7 +88,7 @@ defmodule SimpleshopThemeWeb.Shop.ProductShow do
|
||||
end
|
||||
|
||||
defp variant_price(%{price: price}, _product) when is_integer(price), do: price
|
||||
defp variant_price(_, %{price: price}), do: price
|
||||
defp variant_price(_, %{cheapest_price: price}), do: price
|
||||
defp variant_price(_, _), do: 0
|
||||
|
||||
defp find_variant(variants, selected_options) do
|
||||
@@ -154,7 +144,7 @@ defmodule SimpleshopThemeWeb.Shop.ProductShow do
|
||||
|> SimpleshopThemeWeb.CartHook.broadcast_and_update(cart)
|
||||
|> assign(:quantity, 1)
|
||||
|> assign(:cart_drawer_open, true)
|
||||
|> assign(:cart_status, "#{socket.assigns.product.name} added to cart")
|
||||
|> assign(:cart_status, "#{socket.assigns.product.title} added to cart")
|
||||
|
||||
{:noreply, socket}
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user