- category_nav pulls first product image per category from DB - ProductImageScroll JS hook resets to index 0 on updated() - collection filter bar gets CSS fade gradient scroll hint on mobile - sync_product_images and delete_product_images now clean up orphaned Media.Image records to prevent DB bloat from repeated syncs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1232 lines
35 KiB
Elixir
1232 lines
35 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.
|
||
|
||
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: 5–8 business days",
|
||
"Europe: 8–12 business days",
|
||
"United States & Canada: 8–14 business days",
|
||
"Rest of world: 10–20 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: []]
|
||
)
|
||
end
|
||
|
||
defp get_real_categories do
|
||
Products.list_categories()
|
||
end
|
||
|
||
defp mock_products do
|
||
[
|
||
# Art Prints
|
||
%{
|
||
id: "1",
|
||
slug: "1",
|
||
title: "Mountain Sunrise Art Print",
|
||
description: "Capture the magic of dawn with this stunning mountain landscape print",
|
||
cheapest_price: 1999,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/mountain-sunrise-print-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/mountain-sunrise-print-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "2",
|
||
title: "Ocean Waves Art Print",
|
||
description: "A calming sunset over ocean waves to bring peace to any room",
|
||
cheapest_price: 2400,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/ocean-waves-print-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/ocean-waves-print-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "3",
|
||
title: "Wildflower Meadow Art Print",
|
||
description: "Beautiful wildflower meadow captured in the summer sunshine",
|
||
cheapest_price: 2400,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/wildflower-meadow-print-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/wildflower-meadow-print-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "4",
|
||
title: "Geometric Abstract Art Print",
|
||
description: "Modern minimalist design with bold geometric shapes",
|
||
cheapest_price: 2800,
|
||
compare_at_price: 3200,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/geometric-abstract-print-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/geometric-abstract-print-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "5",
|
||
title: "Botanical Illustration Print",
|
||
description: "Vintage-inspired botanical drawing with intricate detail",
|
||
cheapest_price: 2400,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/botanical-illustration-print-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/botanical-illustration-print-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "6",
|
||
title: "Forest Silhouette T-Shirt",
|
||
description: "Soft cotton tee featuring a peaceful forest silhouette design",
|
||
cheapest_price: 2999,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/forest-silhouette-tshirt-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/forest-silhouette-tshirt-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "7",
|
||
title: "Forest Light Hoodie",
|
||
description: "Cosy fleece hoodie with stunning forest light photography",
|
||
cheapest_price: 4499,
|
||
compare_at_price: 4999,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/forest-light-hoodie-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/forest-light-hoodie-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "8",
|
||
title: "Wildflower Meadow Tote Bag",
|
||
description: "Sturdy cotton tote bag with vibrant wildflower design",
|
||
cheapest_price: 1999,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/wildflower-meadow-tote-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/wildflower-meadow-tote-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
category: "Apparel",
|
||
in_stock: true,
|
||
on_sale: false,
|
||
variants: [
|
||
%{id: "tb1", title: "One size", price: 1999, options: %{}, is_available: true}
|
||
]
|
||
},
|
||
%{
|
||
id: "9",
|
||
slug: "9",
|
||
title: "Sunset Gradient Tote Bag",
|
||
description: "Beautiful ocean sunset printed on durable canvas tote",
|
||
cheapest_price: 1999,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/sunset-gradient-tote-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/sunset-gradient-tote-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
category: "Apparel",
|
||
in_stock: true,
|
||
on_sale: false,
|
||
variants: [
|
||
%{id: "tb2", title: "One size", price: 1999, options: %{}, is_available: true}
|
||
]
|
||
},
|
||
# Homewares
|
||
%{
|
||
id: "10",
|
||
slug: "10",
|
||
title: "Fern Leaf Mug",
|
||
description: "Start your morning right with this nature-inspired ceramic mug",
|
||
cheapest_price: 1499,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/fern-leaf-mug-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/fern-leaf-mug-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "11",
|
||
title: "Ocean Waves Cushion",
|
||
description: "Soft polyester cushion featuring a stunning ocean sunset",
|
||
cheapest_price: 2999,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/ocean-waves-cushion-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/ocean-waves-cushion-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
category: "Homewares",
|
||
in_stock: true,
|
||
on_sale: false,
|
||
variants: [
|
||
%{id: "cu1", title: "18×18", price: 2999, options: %{}, is_available: true}
|
||
]
|
||
},
|
||
%{
|
||
id: "12",
|
||
slug: "12",
|
||
title: "Night Sky Blanket",
|
||
description: "Cosy sherpa fleece blanket with mesmerising milky way print",
|
||
cheapest_price: 5999,
|
||
compare_at_price: 6999,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/night-sky-blanket-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/night-sky-blanket-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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",
|
||
slug: "13",
|
||
title: "Autumn Leaves Notebook",
|
||
description: "Hardcover journal with beautiful autumn foliage design",
|
||
cheapest_price: 1999,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/autumn-leaves-notebook-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/autumn-leaves-notebook-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
category: "Stationery",
|
||
in_stock: true,
|
||
on_sale: false,
|
||
variants: [
|
||
%{id: "nb1", title: "Journal", price: 1999, options: %{}, is_available: true}
|
||
]
|
||
},
|
||
%{
|
||
id: "14",
|
||
slug: "14",
|
||
title: "Monstera Leaf Notebook",
|
||
description: "Tropical-inspired hardcover journal for your thoughts",
|
||
cheapest_price: 1999,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/monstera-leaf-notebook-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/monstera-leaf-notebook-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
category: "Stationery",
|
||
in_stock: true,
|
||
on_sale: false,
|
||
variants: [
|
||
%{id: "nb2", title: "Journal", price: 1999, options: %{}, is_available: true}
|
||
]
|
||
},
|
||
# Accessories
|
||
%{
|
||
id: "15",
|
||
slug: "15",
|
||
title: "Monstera Leaf Phone Case",
|
||
description: "Tough phone case with stunning monstera leaf photography",
|
||
cheapest_price: 2499,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/monstera-leaf-phone-case-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/monstera-leaf-phone-case-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
category: "Accessories",
|
||
in_stock: true,
|
||
on_sale: false,
|
||
variants: [
|
||
%{id: "pc1", title: "iPhone 15", price: 2499, options: %{}, is_available: true}
|
||
]
|
||
},
|
||
%{
|
||
id: "16",
|
||
slug: "16",
|
||
title: "Blue Waves Laptop Sleeve",
|
||
description: "Protective laptop sleeve with abstract blue gradient design",
|
||
cheapest_price: 3499,
|
||
compare_at_price: nil,
|
||
images: [
|
||
%{
|
||
position: 0,
|
||
image_id: nil,
|
||
src: "/mockups/blue-waves-laptop-sleeve-1",
|
||
image: %{source_width: 1200}
|
||
},
|
||
%{
|
||
position: 1,
|
||
image_id: nil,
|
||
src: "/mockups/blue-waves-laptop-sleeve-2",
|
||
image: %{source_width: 1200}
|
||
}
|
||
],
|
||
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
|