feat: wire shop LiveViews to real product data

PreviewData now queries the Products context when real products exist,
falling back to mock data otherwise. Shop pages automatically display
synced Printify products.

Fixes:
- Printify image position was string ("front"), now uses index
- Category extraction improved to match more Printify tags
- ProductShow finds products by slug for real data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-01-31 23:07:37 +00:00
parent 81520754ee
commit c818d0399c
4 changed files with 118 additions and 32 deletions

View File

@@ -6,6 +6,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
This allows users to preview themes before adding products to their shop.
"""
alias SimpleshopTheme.Products
@doc """
Returns products for preview.
@@ -167,22 +169,81 @@ defmodule SimpleshopTheme.Theme.PreviewData do
@doc """
Checks if the shop has real products.
Returns true if at least one product exists in the database.
Returns true if at least one visible, active product exists in the database.
Returns false if the database is unavailable (e.g., in tests without sandbox).
"""
def has_real_products? do
false
try do
Products.list_products(visible: true, status: "active") |> Enum.any?()
rescue
DBConnection.OwnershipError -> false
end
end
defp has_real_categories? do
false
has_real_products?()
end
defp get_real_products do
[]
Products.list_products(visible: true, status: "active", preload: [:images, :variants])
|> Enum.map(&product_to_map/1)
end
defp get_real_categories do
[]
Products.list_products(visible: true, status: "active")
|> Enum.map(& &1.category)
|> Enum.reject(&is_nil/1)
|> Enum.frequencies()
|> Enum.map(fn {name, count} ->
%{
id: Slug.slugify(name),
name: name,
slug: Slug.slugify(name),
product_count: count,
image_url: nil
}
end)
|> Enum.sort_by(& &1.name)
end
# Transform a Product struct to the map format expected by shop components
defp product_to_map(product) do
# Get images sorted by position
images = Enum.sort_by(product.images, & &1.position)
first_image = List.first(images)
second_image = Enum.at(images, 1)
# Get available variants for pricing
available_variants =
product.variants
|> Enum.filter(& &1.is_enabled)
|> Enum.filter(& &1.is_available)
# Get the cheapest available variant for display price
cheapest_variant =
available_variants
|> Enum.min_by(& &1.price, fn -> nil end)
# Determine stock and sale status
in_stock = Enum.any?(available_variants)
on_sale = Enum.any?(product.variants, &SimpleshopTheme.Products.ProductVariant.on_sale?/1)
%{
id: product.slug,
name: product.title,
description: product.description,
price: if(cheapest_variant, do: cheapest_variant.price, else: 0),
compare_at_price: if(cheapest_variant, do: cheapest_variant.compare_at_price, else: nil),
image_url: if(first_image, do: first_image.src, else: nil),
hover_image_url: if(second_image, do: second_image.src, else: nil),
source_width: nil,
hover_source_width: nil,
category: product.category,
slug: product.slug,
in_stock: in_stock,
on_sale: on_sale,
inserted_at: product.inserted_at
}
end
# Default source width for mockup variants (max generated size)