berrypod/lib/simpleshop_theme/theme/preview_data.ex
jamey 19b4a5bd59 add variants to all mock products and fix CSSCache race condition
All 16 mock products now have at least one variant so add-to-cart works
in demo mode. CSSCache.invalidate/0 rescues ArgumentError when the ETS
table doesn't exist yet (seed_defaults runs before CSSCache starts).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 18:12:57 +00:00

1180 lines
36 KiB
Elixir
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.
Format matches Cart.hydrate/1 output for consistency between preview and live modes.
"""
def cart_drawer_items do
[
%{
variant_id: "preview-1",
product_id: "preview-product-1",
name: "Mountain Sunrise Art Print",
variant: "12″ x 18″ / Matte",
price: 2400,
quantity: 1,
image: "/mockups/mountain-sunrise-print-1-400.webp"
},
%{
variant_id: "preview-2",
product_id: "preview-product-2",
name: "Fern Leaf Mug",
variant: "11oz / White",
price: 1499,
quantity: 2,
image: "/mockups/fern-leaf-mug-1-400.webp"
}
]
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: "Sample review five stars",
body:
"This is an example review for the demo store. Real reviews from your customers will appear here once you start getting orders.",
author: "Demo Customer",
date: "2 weeks ago",
verified: true
},
%{
rating: 4,
title: "Sample review four stars",
body:
"Another example review to show how the layout handles multiple entries. You can customise the review section in the theme settings.",
author: "Demo Shopper",
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:
"This is a sample about page for your SimpleShop store. You're reading it as Robin, a fictional nature photographer but this is where your own story goes."
},
%{
type: :paragraph,
text:
"Tell your customers who you are and what inspires your work. Maybe it started as a hobby that grew into something bigger, or maybe you've been creating for years. Either way, this is the place to share it."
},
%{
type: :paragraph,
text:
"In Robin's version, every design comes from their own photography early morning mist over the hills, autumn leaves in the local woods, the quiet beauty of wildflower meadows. Your story will be different, and that's the point."
},
%{type: :heading, text: "Quality you can trust"},
%{
type: :paragraph,
text:
"This section is a good place to talk about your products and what makes them worth buying. Print on demand means every item is made to order using quality materials and printing techniques tell your customers about that."
},
%{type: :heading, text: "Printed sustainably"},
%{
type: :paragraph,
text:
"And here's where you can mention anything else that matters to you sustainability, packaging, your creative process, whatever sets your shop apart. No unsold stock means no waste, and your customers will appreciate knowing that."
},
%{type: :closing, text: "Thanks for checking out the demo. Make this page your own."}
]
end
@doc """
Returns delivery & returns page content for preview.
"""
def delivery_content do
[
%{
type: :lead,
text:
"This is sample content for the demo shop. Replace it with your own delivery and returns information."
},
%{type: :heading, text: "Shipping"},
%{
type: :paragraph,
text:
"All products are printed on demand and shipped directly from the print facility. Typical delivery times depend on your location:"
},
%{
type: :list,
items: [
"United Kingdom: 58 business days",
"Europe: 812 business days",
"United States & Canada: 814 business days",
"Rest of world: 1020 business days"
]
},
%{
type: :paragraph,
text:
"You'll receive a shipping confirmation email with tracking details once your order has been dispatched."
},
%{type: :heading, text: "Returns & exchanges"},
%{
type: :paragraph,
text:
"Because every item is made to order, we can't accept returns for change of mind. However, if your order arrives damaged or with a printing defect, we'll sort it out — just get in touch within 14 days of delivery."
},
%{
type: :paragraph,
text:
"Please include your order number and a photo of the issue so we can resolve things quickly."
},
%{type: :heading, text: "Cancellations"},
%{
type: :paragraph,
text:
"Orders can be cancelled within 2 hours of being placed. After that, production will have started and we won't be able to make changes. Contact us as soon as possible if you need to cancel."
}
]
end
@doc """
Returns privacy policy page content for preview.
"""
def privacy_content do
[
%{
type: :lead,
text: "This is sample content for the demo shop. Replace it with your own privacy policy."
},
%{type: :heading, text: "What we collect"},
%{
type: :paragraph,
text:
"When you place an order, we collect the information needed to fulfil it — your name, email address, shipping address, and payment details. Payment is processed securely by Stripe; we never see or store your card number."
},
%{type: :heading, text: "How we use it"},
%{
type: :paragraph,
text: "We use your information to:"
},
%{
type: :list,
items: [
"Process and deliver your order",
"Send order confirmation and shipping updates",
"Respond to any queries or issues you raise"
]
},
%{
type: :paragraph,
text:
"We won't send you marketing emails unless you've opted in, and we'll never sell your data to third parties."
},
%{type: :heading, text: "Third parties"},
%{
type: :paragraph,
text:
"To fulfil orders, we share your shipping details with our print-on-demand provider. Payment is handled by Stripe. Both process data in accordance with their own privacy policies."
},
%{type: :heading, text: "Your rights"},
%{
type: :paragraph,
text:
"You can request a copy of your data, ask us to correct it, or ask us to delete it at any time. Just get in touch and we'll sort it out."
},
%{type: :heading, text: "Cookies"},
%{
type: :paragraph,
text:
"We use essential cookies to keep your session and cart working. We don't use tracking cookies or third-party analytics that follow you around the web."
}
]
end
@doc """
Returns terms of service page content for preview.
"""
def terms_content do
[
%{
type: :lead,
text:
"This is sample content for the demo shop. Replace it with your own terms of service."
},
%{type: :heading, text: "Overview"},
%{
type: :paragraph,
text:
"By placing an order through this shop, you agree to the following terms. Please read them before making a purchase."
},
%{type: :heading, text: "Products"},
%{
type: :paragraph,
text:
"All products are made to order using print-on-demand services. Colours may vary slightly between screens and the finished product. We do our best to represent products accurately, but minor differences are normal."
},
%{type: :heading, text: "Orders & payment"},
%{
type: :paragraph,
text:
"Payment is taken at the time of purchase via Stripe. Once an order is confirmed and production has started, it cannot be modified. Prices include VAT where applicable."
},
%{type: :heading, text: "Delivery"},
%{
type: :paragraph,
text:
"Delivery times are estimates and may vary. We're not responsible for delays caused by postal services or customs. See our delivery page for current timeframes."
},
%{type: :heading, text: "Returns"},
%{
type: :paragraph,
text:
"As items are made to order, we don't accept returns for change of mind. If an item arrives damaged or defective, contact us within 14 days for a replacement. See our delivery & returns page for full details."
},
%{type: :heading, text: "Intellectual property"},
%{
type: :paragraph,
text:
"All designs, images, and content on this site are owned by the shop owner and may not be reproduced without permission."
},
%{type: :heading, text: "Liability"},
%{
type: :paragraph,
text:
"We do our best to keep the shop running smoothly, but we can't guarantee uninterrupted service. Our liability is limited to the value of your order."
},
%{type: :heading, text: "Changes"},
%{
type: :paragraph,
text:
"We may update these terms from time to time. Any changes apply to orders placed after the update."
}
]
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: :image, 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 and &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)
# Use local image if available, fall back to CDN URL
{image_url, image_id, source_width} = image_attrs(first_image)
{hover_image_url, hover_image_id, hover_source_width} = image_attrs(second_image)
# Map variants to frontend format (only enabled/published ones)
variants =
product.variants
|> Enum.filter(& &1.is_enabled)
|> Enum.map(fn v ->
%{
id: v.id,
provider_variant_id: v.provider_variant_id,
title: v.title,
price: v.price,
compare_at_price: v.compare_at_price,
options: v.options,
is_available: v.is_available
}
end)
# Extract option types, filtered to only values present in enabled variants
option_types =
SimpleshopTheme.Providers.Printify.extract_option_types(product.provider_data)
|> filter_option_types_by_variants(variants)
%{
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: image_url,
image_id: image_id,
hover_image_url: hover_image_url,
hover_image_id: hover_image_id,
source_width: source_width,
hover_source_width: hover_source_width,
category: product.category,
slug: product.slug,
in_stock: in_stock,
on_sale: on_sale,
inserted_at: product.inserted_at,
option_types: option_types,
variants: variants
}
end
# Extract image attributes, preferring local Media.Image when available
defp image_attrs(nil), do: {nil, nil, nil}
defp image_attrs(%{image_id: image_id, image: %{source_width: source_width}})
when not is_nil(image_id) do
# Local image available - use image_id for responsive <picture> element
{nil, image_id, source_width}
end
defp image_attrs(%{src: src}) do
# Fall back to CDN URL
{src, nil, nil}
end
# Filter option types to only include values present in enabled variants
# This ensures we don't show unpublished options from the Printify catalog
defp filter_option_types_by_variants(option_types, variants) do
# Collect all option values present in the enabled variants
values_in_use =
Enum.reduce(variants, %{}, fn variant, acc ->
Enum.reduce(variant.options, acc, fn {opt_name, opt_value}, inner_acc ->
Map.update(inner_acc, opt_name, MapSet.new([opt_value]), &MapSet.put(&1, opt_value))
end)
end)
# Filter each option type's values to only those in use
option_types
|> Enum.map(fn opt_type ->
used_values = Map.get(values_in_use, opt_type.name, MapSet.new())
filtered_values =
opt_type.values
|> Enum.filter(fn v -> MapSet.member?(used_values, v.title) end)
%{opt_type | values: filtered_values}
end)
|> Enum.reject(fn opt_type -> opt_type.values == [] end)
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: 1999,
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,
option_types: [
%{
name: "Size",
type: :size,
values: [
%{id: 1, title: "8×10"},
%{id: 2, title: "12×18"},
%{id: 3, title: "18×24"}
]
}
],
variants: [
%{
id: "p1",
title: "8×10",
price: 1999,
options: %{"Size" => "8×10"},
is_available: true
},
%{
id: "p2",
title: "12×18",
price: 2400,
options: %{"Size" => "12×18"},
is_available: true
},
%{
id: "p3",
title: "18×24",
price: 3200,
options: %{"Size" => "18×24"},
is_available: true
}
]
},
%{
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,
option_types: [
%{name: "Size", type: :size, values: [%{id: 1, title: "12×18"}]}
],
variants: [
%{
id: "p4",
title: "12×18",
price: 2400,
options: %{"Size" => "12×18"},
is_available: true
}
]
},
%{
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,
option_types: [
%{name: "Size", type: :size, values: [%{id: 1, title: "12×18"}]}
],
variants: [
%{
id: "p5",
title: "12×18",
price: 2400,
options: %{"Size" => "12×18"},
is_available: true
}
]
},
%{
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,
option_types: [
%{name: "Size", type: :size, values: [%{id: 1, title: "12×18"}]}
],
variants: [
%{
id: "p6",
title: "12×18",
price: 2800,
compare_at_price: 3200,
options: %{"Size" => "12×18"},
is_available: 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,
option_types: [
%{name: "Size", type: :size, values: [%{id: 1, title: "12×18"}]}
],
variants: [
%{
id: "p7",
title: "12×18",
price: 2400,
options: %{"Size" => "12×18"},
is_available: true
}
]
},
# 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,
option_types: [
%{
name: "Color",
type: :color,
values: [
%{id: 1, title: "Black", hex: "#000000"},
%{id: 2, title: "Navy", hex: "#1A2237"},
%{id: 3, title: "White", hex: "#FFFFFF"},
%{id: 4, title: "Sport Grey", hex: "#9CA3AF"}
]
},
%{
name: "Size",
type: :size,
values: [
%{id: 10, title: "S"},
%{id: 11, title: "M"},
%{id: 12, title: "L"},
%{id: 13, title: "XL"},
%{id: 14, title: "2XL"}
]
}
],
variants: [
# Black variants
%{
id: "t1",
title: "Black / S",
price: 2999,
options: %{"Color" => "Black", "Size" => "S"},
is_available: true
},
%{
id: "t2",
title: "Black / M",
price: 2999,
options: %{"Color" => "Black", "Size" => "M"},
is_available: true
},
%{
id: "t3",
title: "Black / L",
price: 2999,
options: %{"Color" => "Black", "Size" => "L"},
is_available: true
},
%{
id: "t4",
title: "Black / XL",
price: 2999,
options: %{"Color" => "Black", "Size" => "XL"},
is_available: true
},
%{
id: "t5",
title: "Black / 2XL",
price: 3299,
options: %{"Color" => "Black", "Size" => "2XL"},
is_available: true
},
# Navy variants
%{
id: "t6",
title: "Navy / S",
price: 2999,
options: %{"Color" => "Navy", "Size" => "S"},
is_available: true
},
%{
id: "t7",
title: "Navy / M",
price: 2999,
options: %{"Color" => "Navy", "Size" => "M"},
is_available: true
},
%{
id: "t8",
title: "Navy / L",
price: 2999,
options: %{"Color" => "Navy", "Size" => "L"},
is_available: true
},
%{
id: "t9",
title: "Navy / XL",
price: 2999,
options: %{"Color" => "Navy", "Size" => "XL"},
is_available: true
},
%{
id: "t10",
title: "Navy / 2XL",
price: 3299,
options: %{"Color" => "Navy", "Size" => "2XL"},
is_available: false
},
# White variants
%{
id: "t11",
title: "White / S",
price: 2999,
options: %{"Color" => "White", "Size" => "S"},
is_available: true
},
%{
id: "t12",
title: "White / M",
price: 2999,
options: %{"Color" => "White", "Size" => "M"},
is_available: true
},
%{
id: "t13",
title: "White / L",
price: 2999,
options: %{"Color" => "White", "Size" => "L"},
is_available: true
},
%{
id: "t14",
title: "White / XL",
price: 2999,
options: %{"Color" => "White", "Size" => "XL"},
is_available: false
},
%{
id: "t15",
title: "White / 2XL",
price: 3299,
options: %{"Color" => "White", "Size" => "2XL"},
is_available: false
},
# Sport Grey variants
%{
id: "t16",
title: "Sport Grey / S",
price: 2999,
options: %{"Color" => "Sport Grey", "Size" => "S"},
is_available: true
},
%{
id: "t17",
title: "Sport Grey / M",
price: 2999,
options: %{"Color" => "Sport Grey", "Size" => "M"},
is_available: true
},
%{
id: "t18",
title: "Sport Grey / L",
price: 2999,
options: %{"Color" => "Sport Grey", "Size" => "L"},
is_available: true
},
%{
id: "t19",
title: "Sport Grey / XL",
price: 2999,
options: %{"Color" => "Sport Grey", "Size" => "XL"},
is_available: true
},
%{
id: "t20",
title: "Sport Grey / 2XL",
price: 3299,
options: %{"Color" => "Sport Grey", "Size" => "2XL"},
is_available: true
}
]
},
%{
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,
option_types: [
%{
name: "Size",
type: :size,
values: [%{id: 1, title: "M"}, %{id: 2, title: "L"}, %{id: 3, title: "XL"}]
}
],
variants: [
%{
id: "h1",
title: "M",
price: 4499,
compare_at_price: 4999,
options: %{"Size" => "M"},
is_available: true
},
%{
id: "h2",
title: "L",
price: 4499,
compare_at_price: 4999,
options: %{"Size" => "L"},
is_available: true
},
%{
id: "h3",
title: "XL",
price: 4499,
compare_at_price: 4999,
options: %{"Size" => "XL"},
is_available: 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,
variants: [
%{id: "tb1", title: "One size", price: 1999, options: %{}, is_available: true}
]
},
%{
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,
variants: [
%{id: "tb2", title: "One size", price: 1999, options: %{}, is_available: true}
]
},
# 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,
option_types: [
%{name: "Size", type: :size, values: [%{id: 1, title: "11oz"}, %{id: 2, title: "15oz"}]}
],
variants: [
%{
id: "m1",
title: "11oz",
price: 1499,
options: %{"Size" => "11oz"},
is_available: true
},
%{
id: "m2",
title: "15oz",
price: 1699,
options: %{"Size" => "15oz"},
is_available: true
}
]
},
%{
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,
variants: [
%{id: "cu1", title: "18×18", price: 2999, options: %{}, is_available: true}
]
},
%{
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,
variants: [
%{
id: "bl1",
title: "50×60",
price: 5999,
compare_at_price: 6999,
options: %{},
is_available: 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,
variants: [
%{id: "nb1", title: "Journal", price: 1999, options: %{}, is_available: true}
]
},
%{
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,
variants: [
%{id: "nb2", title: "Journal", price: 1999, options: %{}, is_available: true}
]
},
# 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,
variants: [
%{id: "pc1", title: "iPhone 15", price: 2499, options: %{}, is_available: true}
]
},
%{
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,
variants: [
%{id: "ls1", title: "13 inch", price: 3499, options: %{}, is_available: true}
]
}
]
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: "Sample Customer A",
content:
"This is sample testimonial text for the demo store. Your real customer quotes will appear here try to include specific details about what they loved.",
rating: 5,
date: "2025-01-15"
},
%{
id: "2",
author: "Sample Customer B",
content:
"Another example testimonial. A mix of short and longer quotes works well for the layout. This one's a bit longer to show how the card handles more text.",
rating: 5,
date: "2025-01-10"
},
%{
id: "3",
author: "Sample Customer C",
content:
"Short and sweet works too. Customers love seeing real feedback from other buyers.",
rating: 5,
date: "2025-01-05"
},
%{
id: "4",
author: "Sample Customer D",
content:
"Sample feedback showing how the grid handles four or more testimonials. Replace these with real quotes once you have them.",
rating: 5,
date: "2024-12-28"
},
%{
id: "5",
author: "Sample Customer E",
content:
"One more example to fill out the testimonials section. You can add as many as you like the layout adapts to fit.",
rating: 5,
date: "2024-12-20"
},
%{
id: "6",
author: "Sample Customer F",
content:
"Final sample testimonial. When you connect your store, real reviews and feedback from customers will replace these placeholder entries.",
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