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>
601 lines
18 KiB
Elixir
601 lines
18 KiB
Elixir
defmodule SimpleshopTheme.Theme.PreviewData do
|
||
@moduledoc """
|
||
Provides preview data for theme customization.
|
||
|
||
Uses real product data when available, falls back to mock data otherwise.
|
||
This allows users to preview themes before adding products to their shop.
|
||
"""
|
||
|
||
alias SimpleshopTheme.Products
|
||
|
||
@doc """
|
||
Returns products for preview.
|
||
|
||
Uses real products if available, otherwise returns mock product data.
|
||
"""
|
||
def products do
|
||
if has_real_products?() do
|
||
get_real_products()
|
||
else
|
||
mock_products()
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns cart items for preview.
|
||
|
||
Always returns mock data as cart is session-specific.
|
||
"""
|
||
def cart_items do
|
||
mock_cart_items()
|
||
end
|
||
|
||
@doc """
|
||
Returns cart drawer items formatted for the cart drawer component.
|
||
"""
|
||
def cart_drawer_items do
|
||
[
|
||
%{
|
||
name: "Mountain Sunrise Art Print",
|
||
variant: "12″ x 18″ / Matte",
|
||
price: "£24.00",
|
||
image: "/mockups/mountain-sunrise-print-1.jpg"
|
||
},
|
||
%{
|
||
name: "Fern Leaf Mug",
|
||
variant: "11oz / White",
|
||
price: "£14.99",
|
||
image: "/mockups/fern-leaf-mug-1.jpg"
|
||
}
|
||
]
|
||
end
|
||
|
||
@doc """
|
||
Returns testimonials for preview.
|
||
|
||
Always returns mock data.
|
||
"""
|
||
def testimonials do
|
||
mock_testimonials()
|
||
end
|
||
|
||
@doc """
|
||
Returns product reviews for preview.
|
||
|
||
Always returns mock data formatted for the reviews_section component.
|
||
"""
|
||
def reviews do
|
||
[
|
||
%{
|
||
rating: 5,
|
||
title: "Absolutely beautiful",
|
||
body:
|
||
"The quality exceeded my expectations. The colours are vibrant and the paper feels premium. It's now pride of place in my living room.",
|
||
author: "Sarah M.",
|
||
date: "2 weeks ago",
|
||
verified: true
|
||
},
|
||
%{
|
||
rating: 4,
|
||
title: "Great gift",
|
||
body:
|
||
"Bought this as a gift and it arrived beautifully packaged. Fast shipping too. Would definitely order again.",
|
||
author: "James T.",
|
||
date: "1 month ago",
|
||
verified: true
|
||
}
|
||
]
|
||
end
|
||
|
||
@doc """
|
||
Returns about page content for preview.
|
||
|
||
Returns structured content blocks for the rich_text component.
|
||
"""
|
||
def about_content do
|
||
[
|
||
%{
|
||
type: :lead,
|
||
text:
|
||
"I'm Emma, a nature photographer based in the UK. What started as weekend walks with my camera has grown into something I never expected – a little shop where I can share my favourite captures with others."
|
||
},
|
||
%{
|
||
type: :paragraph,
|
||
text:
|
||
"Every design in this shop comes from my own photography. Whether it's early morning mist over the hills, autumn leaves in the local woods, or the quiet beauty of wildflower meadows, I'm drawn to the peaceful moments that nature offers."
|
||
},
|
||
%{
|
||
type: :paragraph,
|
||
text:
|
||
"I work with quality print partners to bring these images to life on products you can actually use and enjoy – from art prints for your walls to mugs for your morning tea."
|
||
},
|
||
%{type: :heading, text: "Quality you can trust"},
|
||
%{
|
||
type: :paragraph,
|
||
text:
|
||
"I've carefully chosen print partners who share my commitment to quality. Every product is made to order using premium materials and printing techniques that ensure vibrant colours and lasting quality."
|
||
},
|
||
%{type: :heading, text: "Printed sustainably"},
|
||
%{
|
||
type: :paragraph,
|
||
text:
|
||
"Because each item is printed on demand, there's no waste from unsold stock. My print partners use eco-friendly inks where possible, and products are shipped directly to you to minimise unnecessary handling."
|
||
},
|
||
%{type: :closing, text: "Thank you for visiting. It means a lot that you're here."}
|
||
]
|
||
end
|
||
|
||
@doc """
|
||
Returns categories for preview.
|
||
|
||
Uses real categories if available, otherwise returns mock data.
|
||
"""
|
||
def categories do
|
||
if has_real_categories?() do
|
||
get_real_categories()
|
||
else
|
||
mock_categories()
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Returns a category by its slug.
|
||
|
||
Returns nil if not found.
|
||
"""
|
||
def category_by_slug(slug) do
|
||
Enum.find(categories(), fn cat -> cat.slug == slug end)
|
||
end
|
||
|
||
@doc """
|
||
Returns products filtered by category slug.
|
||
|
||
If slug is nil or "all", returns all products.
|
||
"""
|
||
def products_by_category(nil), do: products()
|
||
def products_by_category("all"), do: products()
|
||
|
||
def products_by_category(slug) do
|
||
case category_by_slug(slug) do
|
||
nil ->
|
||
[]
|
||
|
||
category ->
|
||
products()
|
||
|> Enum.filter(fn product -> product.category == category.name end)
|
||
end
|
||
end
|
||
|
||
@doc """
|
||
Checks if the shop has real products.
|
||
|
||
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
|
||
try do
|
||
Products.list_products(visible: true, status: "active") |> Enum.any?()
|
||
rescue
|
||
DBConnection.OwnershipError -> false
|
||
end
|
||
end
|
||
|
||
defp has_real_categories? do
|
||
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)
|
||
@mockup_source_width 1200
|
||
|
||
defp mock_products do
|
||
[
|
||
# Art Prints
|
||
%{
|
||
id: "1",
|
||
name: "Mountain Sunrise Art Print",
|
||
description: "Capture the magic of dawn with this stunning mountain landscape print",
|
||
price: 2400,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/mountain-sunrise-print-1",
|
||
hover_image_url: "/mockups/mountain-sunrise-print-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Art Prints",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "2",
|
||
name: "Ocean Waves Art Print",
|
||
description: "A calming sunset over ocean waves to bring peace to any room",
|
||
price: 2400,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/ocean-waves-print-1",
|
||
hover_image_url: "/mockups/ocean-waves-print-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Art Prints",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "3",
|
||
name: "Wildflower Meadow Art Print",
|
||
description: "Beautiful wildflower meadow captured in the summer sunshine",
|
||
price: 2400,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/wildflower-meadow-print-1",
|
||
hover_image_url: "/mockups/wildflower-meadow-print-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Art Prints",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "4",
|
||
name: "Geometric Abstract Art Print",
|
||
description: "Modern minimalist design with bold geometric shapes",
|
||
price: 2800,
|
||
compare_at_price: 3200,
|
||
image_url: "/mockups/geometric-abstract-print-1",
|
||
hover_image_url: "/mockups/geometric-abstract-print-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Art Prints",
|
||
in_stock: true,
|
||
on_sale: true
|
||
},
|
||
%{
|
||
id: "5",
|
||
name: "Botanical Illustration Print",
|
||
description: "Vintage-inspired botanical drawing with intricate detail",
|
||
price: 2400,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/botanical-illustration-print-1",
|
||
hover_image_url: "/mockups/botanical-illustration-print-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Art Prints",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
# Apparel
|
||
%{
|
||
id: "6",
|
||
name: "Forest Silhouette T-Shirt",
|
||
description: "Soft cotton tee featuring a peaceful forest silhouette design",
|
||
price: 2999,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/forest-silhouette-tshirt-1",
|
||
hover_image_url: "/mockups/forest-silhouette-tshirt-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Apparel",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "7",
|
||
name: "Forest Light Hoodie",
|
||
description: "Cosy fleece hoodie with stunning forest light photography",
|
||
price: 4499,
|
||
compare_at_price: 4999,
|
||
image_url: "/mockups/forest-light-hoodie-1",
|
||
hover_image_url: "/mockups/forest-light-hoodie-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Apparel",
|
||
in_stock: true,
|
||
on_sale: true
|
||
},
|
||
%{
|
||
id: "8",
|
||
name: "Wildflower Meadow Tote Bag",
|
||
description: "Sturdy cotton tote bag with vibrant wildflower design",
|
||
price: 1999,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/wildflower-meadow-tote-1",
|
||
hover_image_url: "/mockups/wildflower-meadow-tote-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Apparel",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "9",
|
||
name: "Sunset Gradient Tote Bag",
|
||
description: "Beautiful ocean sunset printed on durable canvas tote",
|
||
price: 1999,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/sunset-gradient-tote-1",
|
||
hover_image_url: "/mockups/sunset-gradient-tote-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Apparel",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
# Homewares
|
||
%{
|
||
id: "10",
|
||
name: "Fern Leaf Mug",
|
||
description: "Start your morning right with this nature-inspired ceramic mug",
|
||
price: 1499,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/fern-leaf-mug-1",
|
||
hover_image_url: "/mockups/fern-leaf-mug-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Homewares",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "11",
|
||
name: "Ocean Waves Cushion",
|
||
description: "Soft polyester cushion featuring a stunning ocean sunset",
|
||
price: 2999,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/ocean-waves-cushion-1",
|
||
hover_image_url: "/mockups/ocean-waves-cushion-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Homewares",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "12",
|
||
name: "Night Sky Blanket",
|
||
description: "Cosy sherpa fleece blanket with mesmerising milky way print",
|
||
price: 5999,
|
||
compare_at_price: 6999,
|
||
image_url: "/mockups/night-sky-blanket-1",
|
||
hover_image_url: "/mockups/night-sky-blanket-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Homewares",
|
||
in_stock: true,
|
||
on_sale: true
|
||
},
|
||
# Stationery
|
||
%{
|
||
id: "13",
|
||
name: "Autumn Leaves Notebook",
|
||
description: "Hardcover journal with beautiful autumn foliage design",
|
||
price: 1999,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/autumn-leaves-notebook-1",
|
||
hover_image_url: "/mockups/autumn-leaves-notebook-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Stationery",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "14",
|
||
name: "Monstera Leaf Notebook",
|
||
description: "Tropical-inspired hardcover journal for your thoughts",
|
||
price: 1999,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/monstera-leaf-notebook-1",
|
||
hover_image_url: "/mockups/monstera-leaf-notebook-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Stationery",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
# Accessories
|
||
%{
|
||
id: "15",
|
||
name: "Monstera Leaf Phone Case",
|
||
description: "Tough phone case with stunning monstera leaf photography",
|
||
price: 2499,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/monstera-leaf-phone-case-1",
|
||
hover_image_url: "/mockups/monstera-leaf-phone-case-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Accessories",
|
||
in_stock: true,
|
||
on_sale: false
|
||
},
|
||
%{
|
||
id: "16",
|
||
name: "Blue Waves Laptop Sleeve",
|
||
description: "Protective laptop sleeve with abstract blue gradient design",
|
||
price: 3499,
|
||
compare_at_price: nil,
|
||
image_url: "/mockups/blue-waves-laptop-sleeve-1",
|
||
hover_image_url: "/mockups/blue-waves-laptop-sleeve-2",
|
||
source_width: @mockup_source_width,
|
||
hover_source_width: @mockup_source_width,
|
||
category: "Accessories",
|
||
in_stock: true,
|
||
on_sale: false
|
||
}
|
||
]
|
||
end
|
||
|
||
defp mock_cart_items do
|
||
products = mock_products()
|
||
|
||
[
|
||
%{
|
||
product: Enum.at(products, 0),
|
||
quantity: 1,
|
||
variant: "12″ x 18″ / Matte"
|
||
},
|
||
%{
|
||
product: Enum.at(products, 9),
|
||
quantity: 2,
|
||
variant: "11oz / White"
|
||
},
|
||
%{
|
||
product: Enum.at(products, 5),
|
||
quantity: 1,
|
||
variant: "Large / Black"
|
||
}
|
||
]
|
||
end
|
||
|
||
defp mock_testimonials do
|
||
[
|
||
%{
|
||
id: "1",
|
||
author: "Sarah M.",
|
||
content:
|
||
"The print quality is absolutely stunning - colours are exactly as shown online. My living room looks so much better now!",
|
||
rating: 5,
|
||
date: "2025-01-15"
|
||
},
|
||
%{
|
||
id: "2",
|
||
author: "James L.",
|
||
content:
|
||
"Bought the forest hoodie as a gift. The packaging was lovely and the quality exceeded expectations. Will order again!",
|
||
rating: 5,
|
||
date: "2025-01-10"
|
||
},
|
||
%{
|
||
id: "3",
|
||
author: "Emily R.",
|
||
content:
|
||
"My new favourite mug! I love sipping my morning tea while looking at that beautiful fern design.",
|
||
rating: 5,
|
||
date: "2025-01-05"
|
||
},
|
||
%{
|
||
id: "4",
|
||
author: "Michael T.",
|
||
content:
|
||
"The tote bag is so sturdy - I use it for everything now. Great print quality that hasn't faded at all.",
|
||
rating: 5,
|
||
date: "2024-12-28"
|
||
},
|
||
%{
|
||
id: "5",
|
||
author: "Lisa K.",
|
||
content:
|
||
"The night sky blanket is gorgeous and so cosy. Perfect for film nights on the sofa. Highly recommend!",
|
||
rating: 5,
|
||
date: "2024-12-20"
|
||
},
|
||
%{
|
||
id: "6",
|
||
author: "David P.",
|
||
content:
|
||
"Ordered several prints for my new flat. They arrived well packaged and look amazing on the wall.",
|
||
rating: 5,
|
||
date: "2024-12-15"
|
||
}
|
||
]
|
||
end
|
||
|
||
defp mock_categories do
|
||
[
|
||
%{
|
||
id: "1",
|
||
name: "Art Prints",
|
||
slug: "art-prints",
|
||
product_count: 5,
|
||
image_url: "/mockups/mountain-sunrise-print-2-thumb.jpg"
|
||
},
|
||
%{
|
||
id: "2",
|
||
name: "Apparel",
|
||
slug: "apparel",
|
||
product_count: 4,
|
||
image_url: "/mockups/forest-silhouette-tshirt-1-thumb.jpg"
|
||
},
|
||
%{
|
||
id: "3",
|
||
name: "Homewares",
|
||
slug: "homewares",
|
||
product_count: 3,
|
||
image_url: "/mockups/fern-leaf-mug-1-thumb.jpg"
|
||
},
|
||
%{
|
||
id: "4",
|
||
name: "Stationery",
|
||
slug: "stationery",
|
||
product_count: 2,
|
||
image_url: "/mockups/autumn-leaves-notebook-1-thumb.jpg"
|
||
},
|
||
%{
|
||
id: "5",
|
||
name: "Accessories",
|
||
slug: "accessories",
|
||
product_count: 2,
|
||
image_url: "/mockups/monstera-leaf-phone-case-1-thumb.jpg"
|
||
}
|
||
]
|
||
end
|
||
end
|