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:
@@ -200,10 +200,12 @@ defmodule SimpleshopTheme.Providers.Printify do
|
||||
images
|
||||
|> Enum.with_index()
|
||||
|> Enum.map(fn {img, index} ->
|
||||
# Printify returns position as a label string (e.g., "front", "back")
|
||||
# We use the index as the numeric position instead
|
||||
%{
|
||||
src: img["src"],
|
||||
position: img["position"] || index,
|
||||
alt: nil
|
||||
position: index,
|
||||
alt: img["position"]
|
||||
}
|
||||
end)
|
||||
end
|
||||
@@ -243,18 +245,27 @@ defmodule SimpleshopTheme.Providers.Printify do
|
||||
end
|
||||
|
||||
defp extract_category(raw) do
|
||||
# Try to extract category from tags
|
||||
tags = raw["tags"] || []
|
||||
# Try to extract category from tags (case-insensitive)
|
||||
tags =
|
||||
(raw["tags"] || [])
|
||||
|> Enum.map(&String.downcase/1)
|
||||
|
||||
cond do
|
||||
"apparel" in tags or "clothing" in tags -> "Apparel"
|
||||
"homeware" in tags or "home" in tags -> "Homewares"
|
||||
"accessories" in tags -> "Accessories"
|
||||
"art" in tags or "print" in tags -> "Art Prints"
|
||||
true -> nil
|
||||
has_tag?(tags, ~w[t-shirt tshirt shirt hoodie sweatshirt apparel clothing]) -> "Apparel"
|
||||
has_tag?(tags, ~w[mug cup blanket pillow cushion homeware homewares home]) -> "Homewares"
|
||||
has_tag?(tags, ~w[notebook journal stationery]) -> "Stationery"
|
||||
has_tag?(tags, ~w[phone case bag tote accessories]) -> "Accessories"
|
||||
has_tag?(tags, ~w[art print poster canvas wall]) -> "Art Prints"
|
||||
true -> List.first(raw["tags"])
|
||||
end
|
||||
end
|
||||
|
||||
defp has_tag?(tags, keywords) do
|
||||
Enum.any?(tags, fn tag ->
|
||||
Enum.any?(keywords, fn keyword -> String.contains?(tag, keyword) end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp normalize_order_status(raw) do
|
||||
%{
|
||||
status: map_order_status(raw["status"]),
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user