chore: apply mix format to codebase
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d97918d66a
commit
336b2bb81d
@ -194,7 +194,9 @@ defmodule Mix.Tasks.GenerateMockups do
|
||||
""")
|
||||
|
||||
if failed > 0 do
|
||||
Mix.shell().error("Some products failed to generate. Check the output above for details.")
|
||||
Mix.shell().error(
|
||||
"Some products failed to generate. Check the output above for details."
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -31,7 +31,8 @@ defmodule SimpleshopTheme.Images.Optimizer do
|
||||
{width, _height, _} <- Image.shape(image),
|
||||
{:ok, resized} <- maybe_resize(image, width),
|
||||
{final_width, final_height, _} <- Image.shape(resized),
|
||||
{:ok, webp_data} <- Image.write(resized, :memory, suffix: ".webp", quality: @storage_quality) do
|
||||
{:ok, webp_data} <-
|
||||
Image.write(resized, :memory, suffix: ".webp", quality: @storage_quality) do
|
||||
{:ok, webp_data, final_width, final_height}
|
||||
end
|
||||
rescue
|
||||
@ -191,9 +192,20 @@ defmodule SimpleshopTheme.Images.Optimizer do
|
||||
widths = applicable_widths(source_width)
|
||||
|
||||
tasks = [
|
||||
Task.async(fn -> generate_variant_to_dir(vips_image, output_basename, output_dir, "thumb", :jpg, @thumb_size) end)
|
||||
Task.async(fn ->
|
||||
generate_variant_to_dir(
|
||||
vips_image,
|
||||
output_basename,
|
||||
output_dir,
|
||||
"thumb",
|
||||
:jpg,
|
||||
@thumb_size
|
||||
)
|
||||
end)
|
||||
| for w <- widths, fmt <- @pregenerated_formats do
|
||||
Task.async(fn -> generate_variant_to_dir(vips_image, output_basename, output_dir, w, fmt, w) end)
|
||||
Task.async(fn ->
|
||||
generate_variant_to_dir(vips_image, output_basename, output_dir, w, fmt, w)
|
||||
end)
|
||||
end
|
||||
]
|
||||
|
||||
|
||||
@ -59,7 +59,9 @@ defmodule SimpleshopTheme.Images.VariantCache do
|
||||
if to_process == [] do
|
||||
Logger.info("[VariantCache] All database image variants up to date")
|
||||
else
|
||||
Logger.info("[VariantCache] Enqueueing #{length(to_process)} database images for processing")
|
||||
Logger.info(
|
||||
"[VariantCache] Enqueueing #{length(to_process)} database images for processing"
|
||||
)
|
||||
|
||||
Enum.each(to_process, fn image ->
|
||||
image
|
||||
|
||||
@ -119,7 +119,12 @@ defmodule SimpleshopTheme.Media do
|
||||
|
||||
"""
|
||||
def get_logo do
|
||||
Repo.one(from i in ImageSchema, where: i.image_type == "logo", order_by: [desc: i.inserted_at], limit: 1)
|
||||
Repo.one(
|
||||
from i in ImageSchema,
|
||||
where: i.image_type == "logo",
|
||||
order_by: [desc: i.inserted_at],
|
||||
limit: 1
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
@ -132,7 +137,12 @@ defmodule SimpleshopTheme.Media do
|
||||
|
||||
"""
|
||||
def get_header do
|
||||
Repo.one(from i in ImageSchema, where: i.image_type == "header", order_by: [desc: i.inserted_at], limit: 1)
|
||||
Repo.one(
|
||||
from i in ImageSchema,
|
||||
where: i.image_type == "header",
|
||||
order_by: [desc: i.inserted_at],
|
||||
limit: 1
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@ -46,7 +46,8 @@ defmodule SimpleshopTheme.Media.Image do
|
||||
defp detect_svg(changeset) do
|
||||
content_type = get_change(changeset, :content_type)
|
||||
|
||||
if content_type == "image/svg+xml" or String.ends_with?(get_change(changeset, :filename) || "", ".svg") do
|
||||
if content_type == "image/svg+xml" or
|
||||
String.ends_with?(get_change(changeset, :filename) || "", ".svg") do
|
||||
changeset
|
||||
|> put_change(:is_svg, true)
|
||||
|> maybe_store_svg_content()
|
||||
|
||||
@ -23,7 +23,8 @@ defmodule SimpleshopTheme.Media.SVGRecolorer do
|
||||
|
||||
"""
|
||||
@spec recolor(String.t(), String.t()) :: String.t()
|
||||
def recolor(svg_content, target_color) when is_binary(svg_content) and is_binary(target_color) do
|
||||
def recolor(svg_content, target_color)
|
||||
when is_binary(svg_content) and is_binary(target_color) do
|
||||
svg_content
|
||||
|> recolor_fill_attributes(target_color)
|
||||
|> recolor_stroke_attributes(target_color)
|
||||
|
||||
@ -163,9 +163,17 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
hoodie: %{blueprint_id: nil, print_provider_id: nil, search_term: "Pullover Hoodie"},
|
||||
tote: %{blueprint_id: nil, print_provider_id: nil, search_term: "Cotton Tote Bag"},
|
||||
mug: %{blueprint_id: nil, print_provider_id: nil, search_term: "Mug 11oz"},
|
||||
cushion: %{blueprint_id: nil, print_provider_id: nil, search_term: "Spun Polyester Square Pillow"},
|
||||
cushion: %{
|
||||
blueprint_id: nil,
|
||||
print_provider_id: nil,
|
||||
search_term: "Spun Polyester Square Pillow"
|
||||
},
|
||||
blanket: %{blueprint_id: nil, print_provider_id: nil, search_term: "Sherpa Fleece Blanket"},
|
||||
notebook: %{blueprint_id: nil, print_provider_id: nil, search_term: "Hardcover Journal Matte"},
|
||||
notebook: %{
|
||||
blueprint_id: nil,
|
||||
print_provider_id: nil,
|
||||
search_term: "Hardcover Journal Matte"
|
||||
},
|
||||
phone_case: %{blueprint_id: nil, print_provider_id: nil, search_term: "Tough Phone Cases"},
|
||||
laptop_sleeve: %{blueprint_id: nil, print_provider_id: nil, search_term: "Laptop Sleeve"}
|
||||
}
|
||||
@ -258,7 +266,7 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
placeholder_width > 0 and placeholder_height > 0 do
|
||||
# For cover: use the larger scale to ensure full coverage
|
||||
width_scale = 1.0
|
||||
height_scale = (placeholder_height * artwork_width) / (artwork_height * placeholder_width)
|
||||
height_scale = placeholder_height * artwork_width / (artwork_height * placeholder_width)
|
||||
max(width_scale, height_scale)
|
||||
end
|
||||
|
||||
@ -267,21 +275,36 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
@doc """
|
||||
Create a product with the uploaded artwork.
|
||||
"""
|
||||
def create_product(shop_id, product_def, image_id, image_width, image_height, blueprint_id, print_provider_id, variants) do
|
||||
def create_product(
|
||||
shop_id,
|
||||
product_def,
|
||||
image_id,
|
||||
image_width,
|
||||
image_height,
|
||||
blueprint_id,
|
||||
print_provider_id,
|
||||
variants
|
||||
) do
|
||||
# Get the first variant for simplicity (typically a standard size/color)
|
||||
variant = hd(variants)
|
||||
variant_id = variant["id"]
|
||||
|
||||
# Get placeholder info
|
||||
placeholders = variant["placeholders"] || []
|
||||
front_placeholder = Enum.find(placeholders, fn p -> p["position"] == "front" end) || hd(placeholders)
|
||||
|
||||
front_placeholder =
|
||||
Enum.find(placeholders, fn p -> p["position"] == "front" end) || hd(placeholders)
|
||||
|
||||
# Extract placeholder dimensions and calculate cover scale
|
||||
placeholder_width = front_placeholder["width"]
|
||||
placeholder_height = front_placeholder["height"]
|
||||
scale = calculate_cover_scale(image_width, image_height, placeholder_width, placeholder_height)
|
||||
|
||||
IO.puts(" Scale calculation: artwork #{image_width}x#{image_height}, placeholder #{placeholder_width}x#{placeholder_height} -> scale #{Float.round(scale, 3)}")
|
||||
scale =
|
||||
calculate_cover_scale(image_width, image_height, placeholder_width, placeholder_height)
|
||||
|
||||
IO.puts(
|
||||
" Scale calculation: artwork #{image_width}x#{image_height}, placeholder #{placeholder_width}x#{placeholder_height} -> scale #{Float.round(scale, 3)}"
|
||||
)
|
||||
|
||||
product_data = %{
|
||||
title: product_def.name,
|
||||
@ -439,7 +462,17 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
image_height = upload["height"],
|
||||
_ = IO.puts(" Artwork uploaded (ID: #{image_id}, #{image_width}x#{image_height})"),
|
||||
_ = IO.puts(" Creating product..."),
|
||||
{:ok, product} <- create_product(shop_id, product_def, image_id, image_width, image_height, blueprint_id, provider_id, variants),
|
||||
{:ok, product} <-
|
||||
create_product(
|
||||
shop_id,
|
||||
product_def,
|
||||
image_id,
|
||||
image_width,
|
||||
image_height,
|
||||
blueprint_id,
|
||||
provider_id,
|
||||
variants
|
||||
),
|
||||
product_id = product["id"],
|
||||
mockup_urls = extract_mockup_urls(product),
|
||||
_ = IO.puts(" Product created (ID: #{product_id})"),
|
||||
|
||||
@ -287,14 +287,17 @@ defmodule SimpleshopTheme.Products do
|
||||
|
||||
if MapSet.size(removed_ids) > 0 do
|
||||
from(v in ProductVariant,
|
||||
where: v.product_id == ^product_id and v.provider_variant_id in ^MapSet.to_list(removed_ids)
|
||||
where:
|
||||
v.product_id == ^product_id and v.provider_variant_id in ^MapSet.to_list(removed_ids)
|
||||
)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
|
||||
# Upsert incoming variants
|
||||
Enum.map(variants, fn variant_data ->
|
||||
provider_variant_id = variant_data[:provider_variant_id] || variant_data["provider_variant_id"]
|
||||
provider_variant_id =
|
||||
variant_data[:provider_variant_id] || variant_data["provider_variant_id"]
|
||||
|
||||
attrs = Map.put(variant_data, :product_id, product_id)
|
||||
|
||||
case get_variant_by_provider(product_id, provider_variant_id) do
|
||||
|
||||
@ -88,7 +88,8 @@ defmodule SimpleshopTheme.Products.ProductVariant do
|
||||
Formats the options as a human-readable title.
|
||||
E.g., %{"Size" => "Large", "Color" => "Blue"} -> "Large / Blue"
|
||||
"""
|
||||
def options_title(%__MODULE__{options: options}) when is_map(options) and map_size(options) > 0 do
|
||||
def options_title(%__MODULE__{options: options})
|
||||
when is_map(options) and map_size(options) > 0 do
|
||||
options
|
||||
|> Map.values()
|
||||
|> Enum.join(" / ")
|
||||
|
||||
@ -93,7 +93,10 @@ defmodule SimpleshopTheme.Settings.ThemeSettings do
|
||||
])
|
||||
|> validate_required([:mood, :typography, :shape, :density])
|
||||
|> validate_inclusion(:mood, ~w(neutral warm cool dark))
|
||||
|> validate_inclusion(:typography, ~w(clean editorial modern classic friendly minimal impulse))
|
||||
|> validate_inclusion(
|
||||
:typography,
|
||||
~w(clean editorial modern classic friendly minimal impulse)
|
||||
)
|
||||
|> validate_inclusion(:shape, ~w(sharp soft round pill))
|
||||
|> validate_inclusion(:density, ~w(spacious balanced compact))
|
||||
|> validate_inclusion(:grid_columns, ~w(2 3 4))
|
||||
@ -101,8 +104,14 @@ defmodule SimpleshopTheme.Settings.ThemeSettings do
|
||||
|> validate_inclusion(:logo_mode, ~w(text-only logo-text logo-only))
|
||||
|> validate_number(:logo_size, greater_than_or_equal_to: 24, less_than_or_equal_to: 120)
|
||||
|> validate_number(:header_zoom, greater_than_or_equal_to: 100, less_than_or_equal_to: 200)
|
||||
|> validate_number(:header_position_x, greater_than_or_equal_to: 0, less_than_or_equal_to: 100)
|
||||
|> validate_number(:header_position_y, greater_than_or_equal_to: 0, less_than_or_equal_to: 100)
|
||||
|> validate_number(:header_position_x,
|
||||
greater_than_or_equal_to: 0,
|
||||
less_than_or_equal_to: 100
|
||||
)
|
||||
|> validate_number(:header_position_y,
|
||||
greater_than_or_equal_to: 0,
|
||||
less_than_or_equal_to: 100
|
||||
)
|
||||
|> validate_inclusion(:layout_width, ~w(contained wide full))
|
||||
|> validate_inclusion(:card_shadow, ~w(none sm md lg))
|
||||
|> validate_inclusion(:font_size, ~w(small medium large))
|
||||
|
||||
@ -234,5 +234,4 @@ defmodule SimpleshopTheme.Theme.Fonts do
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -67,7 +67,8 @@ defmodule SimpleshopTheme.Theme.PreviewData 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.",
|
||||
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
|
||||
@ -75,7 +76,8 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
||||
%{
|
||||
rating: 4,
|
||||
title: "Great gift",
|
||||
body: "Bought this as a gift and it arrived beautifully packaged. Fast shipping too. Would definitely order again.",
|
||||
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
|
||||
@ -90,13 +92,33 @@ defmodule SimpleshopTheme.Theme.PreviewData do
|
||||
"""
|
||||
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: :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: :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: :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
|
||||
@ -427,42 +449,48 @@ defmodule SimpleshopTheme.Theme.PreviewData 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!",
|
||||
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!",
|
||||
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.",
|
||||
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.",
|
||||
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!",
|
||||
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.",
|
||||
content:
|
||||
"Ordered several prints for my new flat. They arrived well packaged and look amazing on the wall.",
|
||||
rating: 5,
|
||||
date: "2024-12-15"
|
||||
}
|
||||
|
||||
@ -17,7 +17,8 @@ defmodule SimpleshopThemeWeb do
|
||||
those modules here.
|
||||
"""
|
||||
|
||||
def static_paths, do: ~w(assets css fonts images image_cache mockups favicon.ico robots.txt demo.html)
|
||||
def static_paths,
|
||||
do: ~w(assets css fonts images image_cache mockups favicon.ico robots.txt demo.html)
|
||||
|
||||
def router do
|
||||
quote do
|
||||
|
||||
@ -1 +1 @@
|
||||
<%= @inner_content %>
|
||||
{@inner_content}
|
||||
|
||||
@ -4,8 +4,14 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="csrf-token" content={get_csrf_token()} />
|
||||
<meta name="description" content={assigns[:page_description] || @theme_settings.site_description || "Welcome to #{@theme_settings.site_name}"} />
|
||||
<.live_title><%= assigns[:page_title] || @theme_settings.site_name %></.live_title>
|
||||
<meta
|
||||
name="description"
|
||||
content={
|
||||
assigns[:page_description] || @theme_settings.site_description ||
|
||||
"Welcome to #{@theme_settings.site_name}"
|
||||
}
|
||||
/>
|
||||
<.live_title>{assigns[:page_title] || @theme_settings.site_name}</.live_title>
|
||||
<!-- Preload critical fonts for the current typography preset -->
|
||||
<%= for preload <- SimpleshopTheme.Theme.Fonts.preload_links(
|
||||
@theme_settings.typography,
|
||||
@ -17,7 +23,9 @@
|
||||
<script defer phx-track-static src={~p"/assets/js/app.js"}>
|
||||
</script>
|
||||
<!-- Generated theme CSS with @font-face declarations -->
|
||||
<style id="theme-css"><%= Phoenix.HTML.raw(@generated_css) %></style>
|
||||
<style id="theme-css">
|
||||
<%= Phoenix.HTML.raw(@generated_css) %>
|
||||
</style>
|
||||
</head>
|
||||
<body class="h-full">
|
||||
<div
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
<div class="shop-container min-h-screen pb-20 md:pb-0" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
|
||||
<div
|
||||
class="shop-container min-h-screen pb-20 md:pb-0"
|
||||
style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"
|
||||
>
|
||||
<.skip_link />
|
||||
|
||||
<%= if @theme_settings.announcement_bar do %>
|
||||
<.announcement_bar theme_settings={@theme_settings} />
|
||||
<% end %>
|
||||
|
||||
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="about" mode={@mode} cart_count={@cart_count} />
|
||||
<.shop_header
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
active_page="about"
|
||||
mode={@mode}
|
||||
cart_count={@cart_count}
|
||||
/>
|
||||
|
||||
<main id="main-content" class="content-page" style="background-color: var(--t-surface-base);">
|
||||
<.hero_section
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
<div class="shop-container min-h-screen pb-20 md:pb-0" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
|
||||
<div
|
||||
class="shop-container min-h-screen pb-20 md:pb-0"
|
||||
style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"
|
||||
>
|
||||
<.skip_link />
|
||||
|
||||
<%= if @theme_settings.announcement_bar do %>
|
||||
<.announcement_bar theme_settings={@theme_settings} />
|
||||
<% end %>
|
||||
|
||||
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="cart" mode={@mode} cart_count={@cart_count} />
|
||||
<.shop_header
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
active_page="cart"
|
||||
mode={@mode}
|
||||
cart_count={@cart_count}
|
||||
/>
|
||||
|
||||
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<.page_title text="Your basket" />
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
<div class="shop-container min-h-screen pb-20 md:pb-0" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
|
||||
<div
|
||||
class="shop-container min-h-screen pb-20 md:pb-0"
|
||||
style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"
|
||||
>
|
||||
<.skip_link />
|
||||
|
||||
<%= if @theme_settings.announcement_bar do %>
|
||||
<.announcement_bar theme_settings={@theme_settings} />
|
||||
<% end %>
|
||||
|
||||
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="collection" mode={@mode} cart_count={@cart_count} />
|
||||
<.shop_header
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
active_page="collection"
|
||||
mode={@mode}
|
||||
cart_count={@cart_count}
|
||||
/>
|
||||
|
||||
<main id="main-content">
|
||||
<.collection_header title="All Products" product_count={length(@preview_data.products)} />
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
<div class="shop-container min-h-screen pb-20 md:pb-0" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
|
||||
<div
|
||||
class="shop-container min-h-screen pb-20 md:pb-0"
|
||||
style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"
|
||||
>
|
||||
<.skip_link />
|
||||
|
||||
<%= if @theme_settings.announcement_bar do %>
|
||||
<.announcement_bar theme_settings={@theme_settings} />
|
||||
<% end %>
|
||||
|
||||
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="contact" mode={@mode} cart_count={@cart_count} />
|
||||
<.shop_header
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
active_page="contact"
|
||||
mode={@mode}
|
||||
cart_count={@cart_count}
|
||||
/>
|
||||
|
||||
<main id="main-content" class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<.hero_section
|
||||
@ -20,11 +30,14 @@
|
||||
<div class="flex flex-col gap-6">
|
||||
<.order_tracking_card />
|
||||
|
||||
<.info_card title="Handy to know" items={[
|
||||
<.info_card
|
||||
title="Handy to know"
|
||||
items={[
|
||||
%{label: "Printing", value: "2-5 business days"},
|
||||
%{label: "Delivery", value: "3-7 business days after printing"},
|
||||
%{label: "Returns", value: "Happy to help with faulty or damaged items"}
|
||||
]} />
|
||||
]}
|
||||
/>
|
||||
|
||||
<.newsletter_card />
|
||||
|
||||
|
||||
@ -1,13 +1,27 @@
|
||||
<div class="shop-container min-h-screen" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
|
||||
<div
|
||||
class="shop-container min-h-screen"
|
||||
style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"
|
||||
>
|
||||
<.skip_link />
|
||||
|
||||
<%= if @theme_settings.announcement_bar do %>
|
||||
<.announcement_bar theme_settings={@theme_settings} />
|
||||
<% end %>
|
||||
|
||||
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="error" mode={@mode} cart_count={@cart_count} />
|
||||
<.shop_header
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
active_page="error"
|
||||
mode={@mode}
|
||||
cart_count={@cart_count}
|
||||
/>
|
||||
|
||||
<main id="main-content" class="flex items-center justify-center" style="min-height: calc(100vh - 4rem);">
|
||||
<main
|
||||
id="main-content"
|
||||
class="flex items-center justify-center"
|
||||
style="min-height: calc(100vh - 4rem);"
|
||||
>
|
||||
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||
<.hero_section
|
||||
variant={:error}
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
<div class="shop-container min-h-screen pb-20 md:pb-0" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
|
||||
<div
|
||||
class="shop-container min-h-screen pb-20 md:pb-0"
|
||||
style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"
|
||||
>
|
||||
<.skip_link />
|
||||
|
||||
<%= if @theme_settings.announcement_bar do %>
|
||||
<.announcement_bar theme_settings={@theme_settings} />
|
||||
<% end %>
|
||||
|
||||
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="home" mode={@mode} cart_count={@cart_count} />
|
||||
<.shop_header
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
active_page="home"
|
||||
mode={@mode}
|
||||
cart_count={@cart_count}
|
||||
/>
|
||||
|
||||
<main id="main-content">
|
||||
<.hero_section
|
||||
|
||||
@ -1,18 +1,36 @@
|
||||
<div class="shop-container min-h-screen pb-20 md:pb-0" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
|
||||
<div
|
||||
class="shop-container min-h-screen pb-20 md:pb-0"
|
||||
style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"
|
||||
>
|
||||
<.skip_link />
|
||||
|
||||
<%= if @theme_settings.announcement_bar do %>
|
||||
<.announcement_bar theme_settings={@theme_settings} />
|
||||
<% end %>
|
||||
|
||||
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="pdp" mode={@mode} cart_count={@cart_count} />
|
||||
<.shop_header
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
active_page="pdp"
|
||||
mode={@mode}
|
||||
cart_count={@cart_count}
|
||||
/>
|
||||
|
||||
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<.breadcrumb items={[
|
||||
<.breadcrumb
|
||||
items={[
|
||||
%{label: "Home", page: "home", href: "/"},
|
||||
%{label: @product.category, page: "collection", href: "/collections/#{@product.category |> String.downcase() |> String.replace(" ", "-")}"},
|
||||
%{
|
||||
label: @product.category,
|
||||
page: "collection",
|
||||
href:
|
||||
"/collections/#{@product.category |> String.downcase() |> String.replace(" ", "-")}"
|
||||
},
|
||||
%{label: @product.name, current: true}
|
||||
]} mode={@mode} />
|
||||
]}
|
||||
mode={@mode}
|
||||
/>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-12 mb-16">
|
||||
<.product_gallery images={@gallery_images} product_name={@product.name} />
|
||||
@ -27,7 +45,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<.reviews_section :if={@theme_settings.pdp_reviews} reviews={SimpleshopTheme.Theme.PreviewData.reviews()} average_rating={5} total_count={24} />
|
||||
<.reviews_section
|
||||
:if={@theme_settings.pdp_reviews}
|
||||
reviews={SimpleshopTheme.Theme.PreviewData.reviews()}
|
||||
average_rating={5}
|
||||
total_count={24}
|
||||
/>
|
||||
|
||||
<.related_products_section
|
||||
:if={@theme_settings.pdp_related_products}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -12,13 +12,21 @@ defmodule SimpleshopThemeWeb.ErrorHTML do
|
||||
alias SimpleshopTheme.Theme.{CSSCache, CSSGenerator, PreviewData}
|
||||
|
||||
def render("404.html", assigns) do
|
||||
render_error_page(assigns, "404", "Page Not Found",
|
||||
"Sorry, we couldn't find the page you're looking for. Perhaps you've mistyped the URL or the page has been moved.")
|
||||
render_error_page(
|
||||
assigns,
|
||||
"404",
|
||||
"Page Not Found",
|
||||
"Sorry, we couldn't find the page you're looking for. Perhaps you've mistyped the URL or the page has been moved."
|
||||
)
|
||||
end
|
||||
|
||||
def render("500.html", assigns) do
|
||||
render_error_page(assigns, "500", "Server Error",
|
||||
"Something went wrong on our end. Please try again later or contact support if the problem persists.")
|
||||
render_error_page(
|
||||
assigns,
|
||||
"500",
|
||||
"Server Error",
|
||||
"Something went wrong on our end. Please try again later or contact support if the problem persists."
|
||||
)
|
||||
end
|
||||
|
||||
def render(template, _assigns) do
|
||||
@ -57,14 +65,15 @@ defmodule SimpleshopThemeWeb.ErrorHTML do
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title><%= @error_code %> - <%= @error_title %></title>
|
||||
<title>{@error_code} - {@error_title}</title>
|
||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||
<style id="theme-css">
|
||||
<%= Phoenix.HTML.raw(@generated_css) %>
|
||||
</style>
|
||||
</head>
|
||||
<body class="h-full">
|
||||
<div class="shop-root themed h-full"
|
||||
<div
|
||||
class="shop-root themed h-full"
|
||||
data-mood={@theme_settings.mood}
|
||||
data-typography={@theme_settings.typography}
|
||||
data-shape={@theme_settings.shape}
|
||||
@ -73,7 +82,8 @@ defmodule SimpleshopThemeWeb.ErrorHTML do
|
||||
data-header={@theme_settings.header_layout}
|
||||
data-sticky={to_string(@theme_settings.sticky_header)}
|
||||
data-layout={@theme_settings.layout_width}
|
||||
data-shadow={@theme_settings.card_shadow}>
|
||||
data-shadow={@theme_settings.card_shadow}
|
||||
>
|
||||
<SimpleshopThemeWeb.PageTemplates.error
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
@ -96,14 +106,18 @@ defmodule SimpleshopThemeWeb.ErrorHTML do
|
||||
defp load_theme_data do
|
||||
try do
|
||||
theme_settings = Settings.get_theme_settings()
|
||||
|
||||
generated_css =
|
||||
case CSSCache.get() do
|
||||
{:ok, css} -> css
|
||||
{:ok, css} ->
|
||||
css
|
||||
|
||||
:miss ->
|
||||
css = CSSGenerator.generate(theme_settings)
|
||||
CSSCache.put(css)
|
||||
css
|
||||
end
|
||||
|
||||
{theme_settings, generated_css}
|
||||
rescue
|
||||
_ -> {%ThemeSettings{}, ""}
|
||||
|
||||
@ -11,7 +11,9 @@ defmodule SimpleshopThemeWeb.ShopLive.About do
|
||||
|
||||
generated_css =
|
||||
case CSSCache.get() do
|
||||
{:ok, css} -> css
|
||||
{:ok, css} ->
|
||||
css
|
||||
|
||||
:miss ->
|
||||
css = CSSGenerator.generate(theme_settings)
|
||||
CSSCache.put(css)
|
||||
|
||||
@ -11,7 +11,9 @@ defmodule SimpleshopThemeWeb.ShopLive.Cart do
|
||||
|
||||
generated_css =
|
||||
case CSSCache.get() do
|
||||
{:ok, css} -> css
|
||||
{:ok, css} ->
|
||||
css
|
||||
|
||||
:miss ->
|
||||
css = CSSGenerator.generate(theme_settings)
|
||||
CSSCache.put(css)
|
||||
@ -24,7 +26,9 @@ defmodule SimpleshopThemeWeb.ShopLive.Cart do
|
||||
# For now, use preview data for cart items
|
||||
# In a real implementation, this would come from session/database
|
||||
cart_page_items = PreviewData.cart_items()
|
||||
cart_page_subtotal = Enum.reduce(cart_page_items, 0, fn item, acc ->
|
||||
|
||||
cart_page_subtotal =
|
||||
Enum.reduce(cart_page_items, 0, fn item, acc ->
|
||||
acc + item.product.price * item.quantity
|
||||
end)
|
||||
|
||||
|
||||
@ -20,7 +20,9 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
|
||||
|
||||
generated_css =
|
||||
case CSSCache.get() do
|
||||
{:ok, css} -> css
|
||||
{:ok, css} ->
|
||||
css
|
||||
|
||||
:miss ->
|
||||
css = CSSGenerator.generate(theme_settings)
|
||||
CSSCache.put(css)
|
||||
@ -82,7 +84,9 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
|
||||
|
||||
@impl true
|
||||
def handle_event("sort_changed", %{"sort" => sort}, socket) do
|
||||
slug = if socket.assigns.current_category, do: socket.assigns.current_category.slug, else: "all"
|
||||
slug =
|
||||
if socket.assigns.current_category, do: socket.assigns.current_category.slug, else: "all"
|
||||
|
||||
{:noreply, push_patch(socket, to: ~p"/collections/#{slug}?sort=#{sort}")}
|
||||
end
|
||||
|
||||
@ -100,7 +104,10 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="shop-container min-h-screen pb-20 md:pb-0" style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);">
|
||||
<div
|
||||
class="shop-container min-h-screen pb-20 md:pb-0"
|
||||
style="background-color: var(--t-surface-base); font-family: var(--t-font-body); color: var(--t-text-primary);"
|
||||
>
|
||||
<SimpleshopThemeWeb.ShopComponents.skip_link />
|
||||
|
||||
<%= if @theme_settings.announcement_bar do %>
|
||||
@ -145,7 +152,11 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
|
||||
<%= if @products == [] do %>
|
||||
<div class="text-center py-16" style="color: var(--t-text-secondary);">
|
||||
<p class="text-lg">No products found in this collection.</p>
|
||||
<.link navigate={~p"/collections/all"} class="mt-4 inline-block underline" style="color: var(--t-text-accent);">
|
||||
<.link
|
||||
navigate={~p"/collections/all"}
|
||||
class="mt-4 inline-block underline"
|
||||
style="color: var(--t-text-accent);"
|
||||
>
|
||||
View all products
|
||||
</.link>
|
||||
</div>
|
||||
@ -155,9 +166,15 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
|
||||
|
||||
<SimpleshopThemeWeb.ShopComponents.shop_footer theme_settings={@theme_settings} mode={@mode} />
|
||||
|
||||
<SimpleshopThemeWeb.ShopComponents.cart_drawer cart_items={@cart_items} subtotal={@cart_subtotal} mode={@mode} />
|
||||
<SimpleshopThemeWeb.ShopComponents.cart_drawer
|
||||
cart_items={@cart_items}
|
||||
subtotal={@cart_subtotal}
|
||||
mode={@mode}
|
||||
/>
|
||||
|
||||
<SimpleshopThemeWeb.ShopComponents.search_modal hint_text={~s(Try searching for "mountain", "forest", or "ocean")} />
|
||||
<SimpleshopThemeWeb.ShopComponents.search_modal hint_text={
|
||||
~s(Try searching for "mountain", "forest", or "ocean")
|
||||
} />
|
||||
|
||||
<SimpleshopThemeWeb.ShopComponents.mobile_bottom_nav active_page="collection" mode={@mode} />
|
||||
</div>
|
||||
@ -176,10 +193,12 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
|
||||
"px-4 py-2 rounded-full text-sm transition-colors",
|
||||
if(@current_slug == nil, do: "font-medium", else: "hover:opacity-80")
|
||||
]}
|
||||
style={if(@current_slug == nil,
|
||||
style={
|
||||
if(@current_slug == nil,
|
||||
do: "background-color: var(--t-accent); color: var(--t-text-on-accent);",
|
||||
else: "background-color: var(--t-surface-raised); color: var(--t-text-primary);"
|
||||
)}
|
||||
)
|
||||
}
|
||||
>
|
||||
All
|
||||
</.link>
|
||||
@ -192,10 +211,12 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
|
||||
"px-4 py-2 rounded-full text-sm transition-colors",
|
||||
if(@current_slug == category.slug, do: "font-medium", else: "hover:opacity-80")
|
||||
]}
|
||||
style={if(@current_slug == category.slug,
|
||||
style={
|
||||
if(@current_slug == category.slug,
|
||||
do: "background-color: var(--t-accent); color: var(--t-text-on-accent);",
|
||||
else: "background-color: var(--t-surface-raised); color: var(--t-text-primary);"
|
||||
)}
|
||||
)
|
||||
}
|
||||
>
|
||||
{category.name}
|
||||
</.link>
|
||||
|
||||
@ -11,7 +11,9 @@ defmodule SimpleshopThemeWeb.ShopLive.Contact do
|
||||
|
||||
generated_css =
|
||||
case CSSCache.get() do
|
||||
{:ok, css} -> css
|
||||
{:ok, css} ->
|
||||
css
|
||||
|
||||
:miss ->
|
||||
css = CSSGenerator.generate(theme_settings)
|
||||
CSSCache.put(css)
|
||||
|
||||
@ -11,7 +11,9 @@ defmodule SimpleshopThemeWeb.ShopLive.Home do
|
||||
|
||||
generated_css =
|
||||
case CSSCache.get() do
|
||||
{:ok, css} -> css
|
||||
{:ok, css} ->
|
||||
css
|
||||
|
||||
:miss ->
|
||||
css = CSSGenerator.generate(theme_settings)
|
||||
CSSCache.put(css)
|
||||
|
||||
@ -11,7 +11,9 @@ defmodule SimpleshopThemeWeb.ShopLive.ProductShow do
|
||||
|
||||
generated_css =
|
||||
case CSSCache.get() do
|
||||
{:ok, css} -> css
|
||||
{:ok, css} ->
|
||||
css
|
||||
|
||||
:miss ->
|
||||
css = CSSGenerator.generate(theme_settings)
|
||||
CSSCache.put(css)
|
||||
|
||||
@ -10,6 +10,7 @@ defmodule SimpleshopThemeWeb.ThemeLive.Index do
|
||||
theme_settings = Settings.get_theme_settings()
|
||||
generated_css = CSSGenerator.generate(theme_settings)
|
||||
active_preset = Presets.detect_preset(theme_settings)
|
||||
|
||||
preview_data = %{
|
||||
products: PreviewData.products(),
|
||||
cart_items: PreviewData.cart_items(),
|
||||
@ -361,7 +362,9 @@ defmodule SimpleshopThemeWeb.ThemeLive.Index do
|
||||
|
||||
defp preview_page(%{page: :cart} = assigns) do
|
||||
cart_items = assigns.preview_data.cart_items
|
||||
subtotal = Enum.reduce(cart_items, 0, fn item, acc -> acc + item.product.price * item.quantity end)
|
||||
|
||||
subtotal =
|
||||
Enum.reduce(cart_items, 0, fn item, acc -> acc + item.product.price * item.quantity end)
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|
||||
@ -5,7 +5,10 @@
|
||||
id="theme-sidebar"
|
||||
class={[
|
||||
"bg-base-100 border-r border-base-300 lg:h-screen flex-shrink-0 transition-all duration-300",
|
||||
if(@sidebar_collapsed, do: "w-12 overflow-hidden", else: "w-full lg:w-[380px] overflow-y-auto p-6")
|
||||
if(@sidebar_collapsed,
|
||||
do: "w-12 overflow-hidden",
|
||||
else: "w-full lg:w-[380px] overflow-y-auto p-6"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<!-- Collapsed state: just show expand button -->
|
||||
@ -19,7 +22,14 @@
|
||||
aria-expanded="false"
|
||||
aria-controls="theme-sidebar"
|
||||
>
|
||||
<svg class="w-5 h-5 text-base-content/70" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<svg
|
||||
class="w-5 h-5 text-base-content/70"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
@ -28,8 +38,12 @@
|
||||
<!-- Header -->
|
||||
<div class="mb-6 flex items-start justify-between gap-3">
|
||||
<div class="flex-1">
|
||||
<h1 class="text-xl font-semibold tracking-tight mb-2 text-base-content">Theme Studio</h1>
|
||||
<p class="text-sm text-base-content/60 leading-relaxed">One theme, infinite possibilities. Every combination is designed to work beautifully.</p>
|
||||
<h1 class="text-xl font-semibold tracking-tight mb-2 text-base-content">
|
||||
Theme Studio
|
||||
</h1>
|
||||
<p class="text-sm text-base-content/60 leading-relaxed">
|
||||
One theme, infinite possibilities. Every combination is designed to work beautifully.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@ -39,7 +53,14 @@
|
||||
aria-expanded="true"
|
||||
aria-controls="theme-sidebar"
|
||||
>
|
||||
<svg class="w-5 h-5 text-base-content/70" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<svg
|
||||
class="w-5 h-5 text-base-content/70"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
@ -47,7 +68,9 @@
|
||||
|
||||
<!-- Site Name -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Shop name</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Shop name
|
||||
</label>
|
||||
<form phx-change="update_setting" phx-value-field="site_name">
|
||||
<input
|
||||
type="text"
|
||||
@ -61,7 +84,9 @@
|
||||
|
||||
<!-- Branding Section (styled background box) -->
|
||||
<div class="bg-base-200 rounded-xl p-4 mb-6">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-4">Logo & header</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-4">
|
||||
Logo & header
|
||||
</label>
|
||||
|
||||
<!-- Logo Mode Radio Cards -->
|
||||
<div class="flex flex-col gap-2 mb-4">
|
||||
@ -70,15 +95,13 @@
|
||||
{"logo-text", "Logo + shop name", "Your logo image with name beside it"},
|
||||
{"logo-only", "Logo only", "Just your logo (with text built in)"}
|
||||
] do %>
|
||||
<label
|
||||
class={[
|
||||
<label class={[
|
||||
"flex items-center gap-3 p-3 bg-base-100 border-2 rounded-lg cursor-pointer transition-all",
|
||||
if(@theme_settings.logo_mode == value,
|
||||
do: "border-base-content",
|
||||
else: "border-transparent hover:border-base-300"
|
||||
)
|
||||
]}
|
||||
>
|
||||
]}>
|
||||
<input
|
||||
type="radio"
|
||||
name="logo_mode"
|
||||
@ -98,12 +121,16 @@
|
||||
]}>
|
||||
<span class={[
|
||||
"w-2 h-2 rounded-full bg-base-content transition-all",
|
||||
if(@theme_settings.logo_mode == value, do: "scale-100 opacity-100", else: "scale-0 opacity-0")
|
||||
]}></span>
|
||||
if(@theme_settings.logo_mode == value,
|
||||
do: "scale-100 opacity-100",
|
||||
else: "scale-0 opacity-0"
|
||||
)
|
||||
]}>
|
||||
</span>
|
||||
</span>
|
||||
<div class="flex-1">
|
||||
<div class="text-sm font-medium text-base-content"><%= title %></div>
|
||||
<div class="text-xs text-base-content/60"><%= desc %></div>
|
||||
<div class="text-sm font-medium text-base-content">{title}</div>
|
||||
<div class="text-xs text-base-content/60">{desc}</div>
|
||||
</div>
|
||||
</label>
|
||||
<% end %>
|
||||
@ -112,7 +139,9 @@
|
||||
<!-- Logo Upload (for logo-text and logo-only modes) -->
|
||||
<%= if @theme_settings.logo_mode in ["logo-text", "logo-only"] do %>
|
||||
<div class="mt-4 pt-4 border-t border-base-300">
|
||||
<span class="block text-xs font-medium text-base-content/70 mb-2">Upload logo (SVG or PNG)</span>
|
||||
<span class="block text-xs font-medium text-base-content/70 mb-2">
|
||||
Upload logo (SVG or PNG)
|
||||
</span>
|
||||
<div class="flex items-center gap-3">
|
||||
<form phx-change="noop" phx-submit="noop" class="flex-1">
|
||||
<label class="flex-1 bg-base-100 border border-dashed border-base-300 rounded-lg p-3 text-sm text-base-content/60 text-center cursor-pointer hover:border-base-content/40 hover:text-base-content/80 transition-all">
|
||||
@ -132,7 +161,9 @@
|
||||
phx-click="remove_logo"
|
||||
class="absolute -top-1.5 -right-1.5 w-[18px] h-[18px] bg-base-content text-base-100 rounded-full text-xs flex items-center justify-center leading-none"
|
||||
title="Remove logo"
|
||||
>×</button>
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -140,24 +171,30 @@
|
||||
<%= for entry <- @uploads.logo_upload.entries do %>
|
||||
<div class="flex items-center gap-2 mt-2">
|
||||
<div class="flex-1 h-1.5 bg-base-300 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-primary transition-all" style={"width: #{entry.progress}%"}></div>
|
||||
<div
|
||||
class="h-full bg-primary transition-all"
|
||||
style={"width: #{entry.progress}%"}
|
||||
>
|
||||
</div>
|
||||
<span class="text-xs text-base-content/60"><%= entry.progress %>%</span>
|
||||
</div>
|
||||
<span class="text-xs text-base-content/60">{entry.progress}%</span>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="cancel_upload"
|
||||
phx-value-ref={entry.ref}
|
||||
phx-value-upload="logo_upload"
|
||||
class="text-base-content/40 hover:text-base-content/70"
|
||||
>×</button>
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<%= for err <- upload_errors(@uploads.logo_upload, entry) do %>
|
||||
<p class="text-error text-xs mt-1"><%= error_to_string(err) %></p>
|
||||
<p class="text-error text-xs mt-1">{error_to_string(err)}</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= for err <- upload_errors(@uploads.logo_upload) do %>
|
||||
<p class="text-error text-xs mt-1"><%= error_to_string(err) %></p>
|
||||
<p class="text-error text-xs mt-1">{error_to_string(err)}</p>
|
||||
<% end %>
|
||||
|
||||
<!-- Logo Size Slider -->
|
||||
@ -165,7 +202,9 @@
|
||||
<form phx-change="update_setting" phx-value-field="logo_size" class="mt-3">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-xs font-medium text-base-content/70">Logo size</span>
|
||||
<span class="text-xs font-mono text-base-content/60"><%= @theme_settings.logo_size %>px</span>
|
||||
<span class="text-xs font-mono text-base-content/60">
|
||||
{@theme_settings.logo_size}px
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
@ -186,21 +225,31 @@
|
||||
checked={@theme_settings.logo_recolor}
|
||||
phx-click="update_setting"
|
||||
phx-value-field="logo_recolor"
|
||||
phx-value-setting_value={if @theme_settings.logo_recolor, do: "false", else: "true"}
|
||||
phx-value-setting_value={
|
||||
if @theme_settings.logo_recolor, do: "false", else: "true"
|
||||
}
|
||||
class="toggle toggle-sm toggle-primary"
|
||||
/>
|
||||
<span class="text-sm text-base-content/70">Recolour logo</span>
|
||||
</label>
|
||||
|
||||
<%= if @theme_settings.logo_recolor do %>
|
||||
<form id="logo-color-form" phx-change="update_color" phx-value-field="logo_color" phx-hook="ColorSync" class="flex items-center gap-3 mt-2">
|
||||
<form
|
||||
id="logo-color-form"
|
||||
phx-change="update_color"
|
||||
phx-value-field="logo_color"
|
||||
phx-hook="ColorSync"
|
||||
class="flex items-center gap-3 mt-2"
|
||||
>
|
||||
<input
|
||||
type="color"
|
||||
name="value"
|
||||
value={@theme_settings.logo_color}
|
||||
class="w-9 h-9 rounded-lg cursor-pointer border-0 p-0"
|
||||
/>
|
||||
<span class="font-mono text-sm text-base-content/70"><%= @theme_settings.logo_color %></span>
|
||||
<span class="font-mono text-sm text-base-content/70">
|
||||
{@theme_settings.logo_color}
|
||||
</span>
|
||||
</form>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -218,7 +267,9 @@
|
||||
checked={@theme_settings.header_background_enabled}
|
||||
phx-click="update_setting"
|
||||
phx-value-field="header_background_enabled"
|
||||
phx-value-setting_value={if @theme_settings.header_background_enabled, do: "false", else: "true"}
|
||||
phx-value-setting_value={
|
||||
if @theme_settings.header_background_enabled, do: "false", else: "true"
|
||||
}
|
||||
class="toggle toggle-sm toggle-primary"
|
||||
/>
|
||||
<span class="text-sm text-base-content/80">Header background image</span>
|
||||
@ -228,7 +279,9 @@
|
||||
<!-- Header Image Upload (only when enabled) -->
|
||||
<%= if @theme_settings.header_background_enabled do %>
|
||||
<div class="bg-base-200 rounded-xl p-4 mb-6">
|
||||
<span class="block text-xs font-medium text-base-content/70 mb-2">Upload header image</span>
|
||||
<span class="block text-xs font-medium text-base-content/70 mb-2">
|
||||
Upload header image
|
||||
</span>
|
||||
<form phx-change="noop" phx-submit="noop">
|
||||
<label class="block bg-base-100 border border-dashed border-base-300 rounded-lg p-3 text-sm text-base-content/60 text-center cursor-pointer hover:border-base-content/40 hover:text-base-content/80 transition-all">
|
||||
<span>Choose file...</span>
|
||||
@ -248,7 +301,9 @@
|
||||
phx-click="remove_header"
|
||||
class="absolute -top-1.5 -right-1.5 w-[18px] h-[18px] bg-base-content text-base-100 rounded-full text-xs flex items-center justify-center leading-none"
|
||||
title="Remove header background"
|
||||
>×</button>
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Header Image Controls -->
|
||||
@ -256,7 +311,9 @@
|
||||
<form phx-change="update_setting" phx-value-field="header_zoom">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-xs font-medium text-base-content/70">Zoom</span>
|
||||
<span class="text-xs font-mono text-base-content/60"><%= @theme_settings.header_zoom %>%</span>
|
||||
<span class="text-xs font-mono text-base-content/60">
|
||||
{@theme_settings.header_zoom}%
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
@ -269,8 +326,12 @@
|
||||
</form>
|
||||
<form phx-change="update_setting" phx-value-field="header_position_x">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-xs font-medium text-base-content/70">Horizontal position</span>
|
||||
<span class="text-xs font-mono text-base-content/60"><%= @theme_settings.header_position_x %>%</span>
|
||||
<span class="text-xs font-medium text-base-content/70">
|
||||
Horizontal position
|
||||
</span>
|
||||
<span class="text-xs font-mono text-base-content/60">
|
||||
{@theme_settings.header_position_x}%
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
@ -283,8 +344,12 @@
|
||||
</form>
|
||||
<form phx-change="update_setting" phx-value-field="header_position_y">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-xs font-medium text-base-content/70">Vertical position</span>
|
||||
<span class="text-xs font-mono text-base-content/60"><%= @theme_settings.header_position_y %>%</span>
|
||||
<span class="text-xs font-medium text-base-content/70">
|
||||
Vertical position
|
||||
</span>
|
||||
<span class="text-xs font-mono text-base-content/60">
|
||||
{@theme_settings.header_position_y}%
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
@ -301,31 +366,39 @@
|
||||
<%= for entry <- @uploads.header_upload.entries do %>
|
||||
<div class="flex items-center gap-2 mt-2">
|
||||
<div class="flex-1 h-1.5 bg-base-300 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-primary transition-all" style={"width: #{entry.progress}%"}></div>
|
||||
<div
|
||||
class="h-full bg-primary transition-all"
|
||||
style={"width: #{entry.progress}%"}
|
||||
>
|
||||
</div>
|
||||
<span class="text-xs text-base-content/60"><%= entry.progress %>%</span>
|
||||
</div>
|
||||
<span class="text-xs text-base-content/60">{entry.progress}%</span>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="cancel_upload"
|
||||
phx-value-ref={entry.ref}
|
||||
phx-value-upload="header_upload"
|
||||
class="text-base-content/40 hover:text-base-content/70"
|
||||
>×</button>
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<%= for err <- upload_errors(@uploads.header_upload, entry) do %>
|
||||
<p class="text-error text-xs mt-1"><%= error_to_string(err) %></p>
|
||||
<p class="text-error text-xs mt-1">{error_to_string(err)}</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= for err <- upload_errors(@uploads.header_upload) do %>
|
||||
<p class="text-error text-xs mt-1"><%= error_to_string(err) %></p>
|
||||
<p class="text-error text-xs mt-1">{error_to_string(err)}</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<!-- Presets Section -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Start with a preset</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Start with a preset
|
||||
</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<%= for {preset_name, description} <- @presets_with_descriptions do %>
|
||||
<button
|
||||
@ -340,8 +413,10 @@
|
||||
)
|
||||
]}
|
||||
>
|
||||
<div class="font-semibold text-sm capitalize text-base-content"><%= preset_name %></div>
|
||||
<div class="text-xs text-base-content/60"><%= description %></div>
|
||||
<div class="font-semibold text-sm capitalize text-base-content">
|
||||
{preset_name}
|
||||
</div>
|
||||
<div class="text-xs text-base-content/60">{description}</div>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -349,8 +424,15 @@
|
||||
|
||||
<!-- Accent Colors (stays in essentials) -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Accent colour</label>
|
||||
<form id="accent-color-form" phx-change="update_color" phx-value-field="accent_color" phx-hook="ColorSync">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Accent colour
|
||||
</label>
|
||||
<form
|
||||
id="accent-color-form"
|
||||
phx-change="update_color"
|
||||
phx-value-field="accent_color"
|
||||
phx-hook="ColorSync"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
@ -359,14 +441,23 @@
|
||||
value={@theme_settings.accent_color}
|
||||
class="w-12 h-12 rounded-lg cursor-pointer border-0 p-0"
|
||||
/>
|
||||
<span class="font-mono text-sm text-base-content/70"><%= @theme_settings.accent_color %></span>
|
||||
<span class="font-mono text-sm text-base-content/70">
|
||||
{@theme_settings.accent_color}
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Hover colour</label>
|
||||
<form id="secondary-accent-color-form" phx-change="update_color" phx-value-field="secondary_accent_color" phx-hook="ColorSync">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Hover colour
|
||||
</label>
|
||||
<form
|
||||
id="secondary-accent-color-form"
|
||||
phx-change="update_color"
|
||||
phx-value-field="secondary_accent_color"
|
||||
phx-hook="ColorSync"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
@ -375,14 +466,23 @@
|
||||
value={@theme_settings.secondary_accent_color}
|
||||
class="w-12 h-12 rounded-lg cursor-pointer border-0 p-0"
|
||||
/>
|
||||
<span class="font-mono text-sm text-base-content/70"><%= @theme_settings.secondary_accent_color %></span>
|
||||
<span class="font-mono text-sm text-base-content/70">
|
||||
{@theme_settings.secondary_accent_color}
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Sale colour</label>
|
||||
<form id="sale-color-form" phx-change="update_color" phx-value-field="sale_color" phx-hook="ColorSync">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Sale colour
|
||||
</label>
|
||||
<form
|
||||
id="sale-color-form"
|
||||
phx-change="update_color"
|
||||
phx-value-field="sale_color"
|
||||
phx-hook="ColorSync"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<input
|
||||
type="color"
|
||||
@ -391,16 +491,33 @@
|
||||
value={@theme_settings.sale_color}
|
||||
class="w-12 h-12 rounded-lg cursor-pointer border-0 p-0"
|
||||
/>
|
||||
<span class="font-mono text-sm text-base-content/70"><%= @theme_settings.sale_color %></span>
|
||||
<span class="font-mono text-sm text-base-content/70">
|
||||
{@theme_settings.sale_color}
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Customise Section (collapsible accordion using native details/summary) -->
|
||||
<details class="border-t border-base-300 mt-6 pt-4 group" id="customise-section" open={@customise_open}>
|
||||
<summary class="flex items-center justify-between w-full py-3 cursor-pointer list-none [&::-webkit-details-marker]:hidden" phx-click="toggle_customise">
|
||||
<span class="text-sm font-semibold text-base-content/70 group-hover:text-base-content transition-colors">Customise</span>
|
||||
<svg class="w-5 h-5 text-base-content/50 transition-transform group-open:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<details
|
||||
class="border-t border-base-300 mt-6 pt-4 group"
|
||||
id="customise-section"
|
||||
open={@customise_open}
|
||||
>
|
||||
<summary
|
||||
class="flex items-center justify-between w-full py-3 cursor-pointer list-none [&::-webkit-details-marker]:hidden"
|
||||
phx-click="toggle_customise"
|
||||
>
|
||||
<span class="text-sm font-semibold text-base-content/70 group-hover:text-base-content transition-colors">
|
||||
Customise
|
||||
</span>
|
||||
<svg
|
||||
class="w-5 h-5 text-base-content/50 transition-transform group-open:rotate-180"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</summary>
|
||||
@ -409,7 +526,13 @@
|
||||
<!-- Typography Group -->
|
||||
<div class="mb-6 pb-6 border-b border-base-200">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<svg class="w-4 h-4 text-base-content/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-4 h-4 text-base-content/50"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<polyline points="4 7 4 4 20 4 20 7"></polyline>
|
||||
<line x1="9" y1="20" x2="15" y2="20"></line>
|
||||
<line x1="12" y1="4" x2="12" y2="20"></line>
|
||||
@ -418,7 +541,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Font style</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Font style
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for typo <- ["clean", "editorial", "modern", "classic", "friendly", "minimal"] do %>
|
||||
<button
|
||||
@ -430,18 +555,21 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
|
||||
if(@theme_settings.typography == typo,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= typo %>
|
||||
{typo}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Font size</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Font size
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for {value, label} <- [{"small", "Small"}, {"medium", "Medium"}, {"large", "Large"}] do %>
|
||||
<button
|
||||
@ -453,18 +581,21 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
|
||||
if(@theme_settings.font_size == value,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= label %>
|
||||
{label}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Heading weight</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Heading weight
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for {value, label} <- [{"regular", "Regular"}, {"medium", "Medium"}, {"bold", "Bold"}] do %>
|
||||
<button
|
||||
@ -476,11 +607,12 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
|
||||
if(@theme_settings.heading_weight == value,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= label %>
|
||||
{label}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -490,7 +622,13 @@
|
||||
<!-- Colours Group -->
|
||||
<div class="mb-6 pb-6 border-b border-base-200">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<svg class="w-4 h-4 text-base-content/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-4 h-4 text-base-content/50"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
</svg>
|
||||
@ -498,7 +636,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Colour mood</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Colour mood
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for mood <- ["warm", "neutral", "cool", "dark"] do %>
|
||||
<button
|
||||
@ -510,11 +650,12 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
|
||||
if(@theme_settings.mood == mood,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= mood %>
|
||||
{mood}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -524,7 +665,13 @@
|
||||
<!-- Layout Group -->
|
||||
<div class="mb-6 pb-6 border-b border-base-200">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<svg class="w-4 h-4 text-base-content/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-4 h-4 text-base-content/50"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="3" y1="9" x2="21" y2="9"></line>
|
||||
<line x1="9" y1="21" x2="9" y2="9"></line>
|
||||
@ -533,7 +680,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Product grid</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Product grid
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for cols <- ["2", "3", "4"] do %>
|
||||
<button
|
||||
@ -545,18 +694,21 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
|
||||
if(@theme_settings.grid_columns == cols,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= cols %> columns
|
||||
{cols} columns
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Density</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Density
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for density <- ["spacious", "balanced", "compact"] do %>
|
||||
<button
|
||||
@ -568,18 +720,21 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
|
||||
if(@theme_settings.density == density,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= density %>
|
||||
{density}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Header layout</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Header layout
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for layout <- ["standard", "centered", "left"] do %>
|
||||
<button
|
||||
@ -591,11 +746,12 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
|
||||
if(@theme_settings.header_layout == layout,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= layout %>
|
||||
{layout}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -631,14 +787,22 @@
|
||||
<!-- Shape Group -->
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<svg class="w-4 h-4 text-base-content/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-4 h-4 text-base-content/50"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
</svg>
|
||||
<span class="text-sm font-semibold text-base-content">Shape</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Corner style</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Corner style
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for shape <- ["sharp", "soft", "round", "pill"] do %>
|
||||
<button
|
||||
@ -650,18 +814,21 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
|
||||
if(@theme_settings.shape == shape,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= shape %>
|
||||
{shape}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Card shadow</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Card shadow
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for {value, label} <- [{"none", "None"}, {"sm", "Subtle"}, {"md", "Medium"}, {"lg", "Strong"}] do %>
|
||||
<button
|
||||
@ -673,18 +840,21 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
|
||||
if(@theme_settings.card_shadow == value,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= label %>
|
||||
{label}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Button style</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Button style
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for {value, label} <- [{"filled", "Filled"}, {"outline", "Outline"}, {"soft", "Soft"}] do %>
|
||||
<button
|
||||
@ -696,11 +866,12 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
|
||||
if(@theme_settings.button_style == value,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= label %>
|
||||
{label}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -710,7 +881,13 @@
|
||||
<!-- Products Group -->
|
||||
<div class="mb-6 pb-6 border-b border-base-200">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<svg class="w-4 h-4 text-base-content/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-4 h-4 text-base-content/50"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="3" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="3" width="7" height="7"></rect>
|
||||
<rect x="14" y="14" width="7" height="7"></rect>
|
||||
@ -720,7 +897,9 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Content width</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Content width
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for width <- ["contained", "wide", "full"] do %>
|
||||
<button
|
||||
@ -732,18 +911,21 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all capitalize",
|
||||
if(@theme_settings.layout_width == width,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= width %>
|
||||
{width}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Image aspect ratio</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Image aspect ratio
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for {value, label} <- [{"square", "Square"}, {"portrait", "Portrait"}, {"landscape", "Landscape"}] do %>
|
||||
<button
|
||||
@ -755,18 +937,21 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
|
||||
if(@theme_settings.image_aspect_ratio == value,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= label %>
|
||||
{label}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">Product text alignment</label>
|
||||
<label class="block text-xs font-semibold uppercase tracking-wider text-base-content/60 mb-3">
|
||||
Product text alignment
|
||||
</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<%= for {value, label} <- [{"left", "Left"}, {"center", "Centre"}] do %>
|
||||
<button
|
||||
@ -778,11 +963,12 @@
|
||||
"px-3 py-2 text-sm rounded-lg border-2 transition-all",
|
||||
if(@theme_settings.product_text_align == value,
|
||||
do: "border-base-content bg-base-100 text-base-content",
|
||||
else: "border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
else:
|
||||
"border-transparent bg-base-200 hover:bg-base-300 text-base-content"
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= label %>
|
||||
{label}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -818,7 +1004,13 @@
|
||||
<!-- Product Page Group -->
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<svg class="w-4 h-4 text-base-content/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-4 h-4 text-base-content/50"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
||||
<line x1="3" y1="9" x2="21" y2="9"></line>
|
||||
</svg>
|
||||
@ -869,11 +1061,21 @@
|
||||
|
||||
<!-- Current Combination Display -->
|
||||
<div class="bg-base-200 rounded-xl p-4 mt-6">
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-base-content/50 mb-2">Current combination</div>
|
||||
<div class="text-sm text-base-content leading-relaxed">
|
||||
<%= String.capitalize(@theme_settings.mood) %> · <%= String.capitalize(@theme_settings.typography) %> · <%= String.capitalize(@theme_settings.shape) %> · <%= String.capitalize(@theme_settings.density) %> · <%= @theme_settings.grid_columns %>-up · <%= String.capitalize(@theme_settings.header_layout) %>
|
||||
<div class="text-xs font-semibold uppercase tracking-wider text-base-content/50 mb-2">
|
||||
Current combination
|
||||
</div>
|
||||
<div class="text-sm text-base-content leading-relaxed">
|
||||
{String.capitalize(@theme_settings.mood)} · {String.capitalize(
|
||||
@theme_settings.typography
|
||||
)} · {String.capitalize(@theme_settings.shape)} · {String.capitalize(
|
||||
@theme_settings.density
|
||||
)} · {@theme_settings.grid_columns}-up · {String.capitalize(
|
||||
@theme_settings.header_layout
|
||||
)}
|
||||
</div>
|
||||
<div class="text-xs text-base-content/40 mt-2">
|
||||
One of 100,000+ possible combinations
|
||||
</div>
|
||||
<div class="text-xs text-base-content/40 mt-2">One of 100,000+ possible combinations</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -904,7 +1106,7 @@
|
||||
)
|
||||
]}
|
||||
>
|
||||
<%= label %>
|
||||
{label}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -917,16 +1119,25 @@
|
||||
<div class="w-3 h-3 rounded-full bg-[#28c940] border border-[#1aab29]"></div>
|
||||
</div>
|
||||
<div class="flex-1 flex items-center gap-2 bg-base-100 border border-base-content/20 rounded-md px-3 py-[5px]">
|
||||
<svg class="w-[14px] h-[14px] text-base-content/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<svg
|
||||
class="w-[14px] h-[14px] text-base-content/50"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
<span class="text-sm text-base-content/60 truncate"><%= @theme_settings.site_name |> String.downcase() |> String.replace(" ", "") %>.myshopify.com</span>
|
||||
<span class="text-sm text-base-content/60 truncate">
|
||||
{@theme_settings.site_name |> String.downcase() |> String.replace(" ", "")}.myshopify.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Frame -->
|
||||
<div class="themed preview-frame bg-white overflow-auto flex-1 rounded-b-lg border border-t-0 border-base-content/20"
|
||||
<div
|
||||
class="themed preview-frame bg-white overflow-auto flex-1 rounded-b-lg border border-t-0 border-base-content/20"
|
||||
data-mood={@theme_settings.mood}
|
||||
data-typography={@theme_settings.typography}
|
||||
data-shape={@theme_settings.shape}
|
||||
@ -936,7 +1147,8 @@
|
||||
data-sticky={to_string(@theme_settings.sticky_header)}
|
||||
data-layout={@theme_settings.layout_width}
|
||||
data-shadow={@theme_settings.card_shadow}
|
||||
data-button-style={@theme_settings.button_style}>
|
||||
data-button-style={@theme_settings.button_style}
|
||||
>
|
||||
<style>
|
||||
/* All font faces for theme switching */
|
||||
<%= Phoenix.HTML.raw(SimpleshopTheme.Theme.Fonts.generate_all_font_faces(
|
||||
|
||||
@ -126,6 +126,7 @@ defmodule SimpleshopThemeWeb.UserLive.Login do
|
||||
end
|
||||
|
||||
defp local_mail_adapter? do
|
||||
Application.get_env(:simpleshop_theme, SimpleshopTheme.Mailer)[:adapter] == Swoosh.Adapters.Local
|
||||
Application.get_env(:simpleshop_theme, SimpleshopTheme.Mailer)[:adapter] ==
|
||||
Swoosh.Adapters.Local
|
||||
end
|
||||
end
|
||||
|
||||
@ -4,7 +4,10 @@ defmodule SimpleshopTheme.Repo.Migrations.CreateProducts do
|
||||
def change do
|
||||
create table(:products, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :provider_connection_id, references(:provider_connections, type: :binary_id, on_delete: :delete_all), null: false
|
||||
|
||||
add :provider_connection_id,
|
||||
references(:provider_connections, type: :binary_id, on_delete: :delete_all), null: false
|
||||
|
||||
add :provider_product_id, :string, null: false
|
||||
add :title, :string, null: false
|
||||
add :description, :text
|
||||
|
||||
@ -4,7 +4,10 @@ defmodule SimpleshopTheme.Repo.Migrations.CreateProductImages do
|
||||
def change do
|
||||
create table(:product_images, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :product_id, references(:products, type: :binary_id, on_delete: :delete_all), null: false
|
||||
|
||||
add :product_id, references(:products, type: :binary_id, on_delete: :delete_all),
|
||||
null: false
|
||||
|
||||
add :src, :string, null: false
|
||||
add :position, :integer, default: 0, null: false
|
||||
add :alt, :string
|
||||
|
||||
@ -4,7 +4,10 @@ defmodule SimpleshopTheme.Repo.Migrations.CreateProductVariants do
|
||||
def change do
|
||||
create table(:product_variants, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :product_id, references(:products, type: :binary_id, on_delete: :delete_all), null: false
|
||||
|
||||
add :product_id, references(:products, type: :binary_id, on_delete: :delete_all),
|
||||
null: false
|
||||
|
||||
add :provider_variant_id, :string, null: false
|
||||
add :title, :string, null: false
|
||||
add :sku, :string
|
||||
|
||||
@ -70,6 +70,7 @@ defmodule SimpleshopTheme.Media.SVGRecolorerTest do
|
||||
svg = """
|
||||
<svg><style>.st0{fill:#FFFFFF;}.st1{fill:#EF1D1D;stroke:#000000;}</style><path class="st0"/></svg>
|
||||
"""
|
||||
|
||||
result = SVGRecolorer.recolor(svg, "#ff6600")
|
||||
assert result =~ "fill:#ff6600"
|
||||
refute result =~ "#FFFFFF"
|
||||
@ -80,6 +81,7 @@ defmodule SimpleshopTheme.Media.SVGRecolorerTest do
|
||||
svg = """
|
||||
<svg><style>.st1{stroke:#000000;stroke-miterlimit:10;}</style><path class="st1"/></svg>
|
||||
"""
|
||||
|
||||
result = SVGRecolorer.recolor(svg, "#ff6600")
|
||||
assert result =~ "stroke:#ff6600"
|
||||
refute result =~ "#000000"
|
||||
@ -89,6 +91,7 @@ defmodule SimpleshopTheme.Media.SVGRecolorerTest do
|
||||
svg = """
|
||||
<svg><style>.st0{fill:none;stroke:#000;}</style><path class="st0"/></svg>
|
||||
"""
|
||||
|
||||
result = SVGRecolorer.recolor(svg, "#ff6600")
|
||||
assert result =~ "fill:none"
|
||||
assert result =~ "stroke:#ff6600"
|
||||
@ -98,6 +101,7 @@ defmodule SimpleshopTheme.Media.SVGRecolorerTest do
|
||||
svg = """
|
||||
<svg><style>.icon{fill:black;stroke:white;}</style><path class="icon"/></svg>
|
||||
"""
|
||||
|
||||
result = SVGRecolorer.recolor(svg, "#ff6600")
|
||||
assert result =~ "fill:#ff6600"
|
||||
assert result =~ "stroke:#ff6600"
|
||||
|
||||
@ -96,7 +96,10 @@ defmodule SimpleshopTheme.Products.ProductTest do
|
||||
|
||||
test "stores provider_data as map", %{conn: conn} do
|
||||
provider_data = %{"blueprint_id" => 145, "print_provider_id" => 29, "extra" => "value"}
|
||||
attrs = valid_product_attrs(%{provider_connection_id: conn.id, provider_data: provider_data})
|
||||
|
||||
attrs =
|
||||
valid_product_attrs(%{provider_connection_id: conn.id, provider_data: provider_data})
|
||||
|
||||
changeset = Product.changeset(%Product{}, attrs)
|
||||
|
||||
assert changeset.valid?
|
||||
|
||||
@ -437,7 +437,9 @@ defmodule SimpleshopTheme.ProductsTest do
|
||||
|
||||
test "updates existing variants" do
|
||||
product = product_fixture()
|
||||
existing = product_variant_fixture(%{product: product, provider_variant_id: "v1", price: 2000})
|
||||
|
||||
existing =
|
||||
product_variant_fixture(%{product: product, provider_variant_id: "v1", price: 2000})
|
||||
|
||||
variants = [
|
||||
%{provider_variant_id: "v1", title: "Small Updated", price: 2200}
|
||||
|
||||
@ -76,13 +76,15 @@ defmodule SimpleshopTheme.SettingsTest do
|
||||
# Cache should now contain new CSS with the red accent color
|
||||
{:ok, updated_css} = CSSCache.get()
|
||||
assert updated_css =~ ".themed {"
|
||||
assert updated_css =~ "--t-accent-h: 0" # Red = hue 0
|
||||
# Red = hue 0
|
||||
assert updated_css =~ "--t-accent-h: 0"
|
||||
|
||||
# Change to blue
|
||||
{:ok, _settings} = Settings.update_theme_settings(%{accent_color: "#0000ff"})
|
||||
|
||||
{:ok, blue_css} = CSSCache.get()
|
||||
assert blue_css =~ "--t-accent-h: 240" # Blue = hue 240
|
||||
# Blue = hue 240
|
||||
assert blue_css =~ "--t-accent-h: 240"
|
||||
|
||||
# Restore default
|
||||
CSSCache.warm()
|
||||
|
||||
@ -61,7 +61,9 @@ defmodule SimpleshopTheme.Sync.ProductSyncWorkerTest do
|
||||
|
||||
test "new/2 with scheduled_at creates scheduled job" do
|
||||
future = DateTime.add(DateTime.utc_now(), 60, :second)
|
||||
changeset = ProductSyncWorker.new(%{provider_connection_id: "test-id"}, scheduled_at: future)
|
||||
|
||||
changeset =
|
||||
ProductSyncWorker.new(%{provider_connection_id: "test-id"}, scheduled_at: future)
|
||||
|
||||
assert changeset.changes.scheduled_at == future
|
||||
end
|
||||
|
||||
@ -51,6 +51,7 @@ defmodule SimpleshopTheme.Theme.PreviewDataTest do
|
||||
|
||||
if product.hover_image_url do
|
||||
assert is_binary(product.hover_image_url)
|
||||
|
||||
assert String.starts_with?(product.hover_image_url, "/") or
|
||||
String.starts_with?(product.hover_image_url, "http")
|
||||
end
|
||||
|
||||
@ -2,7 +2,9 @@ defmodule SimpleshopThemeWeb.ErrorJSONTest do
|
||||
use SimpleshopThemeWeb.ConnCase, async: true
|
||||
|
||||
test "renders 404" do
|
||||
assert SimpleshopThemeWeb.ErrorJSON.render("404.json", %{}) == %{errors: %{detail: "Not Found"}}
|
||||
assert SimpleshopThemeWeb.ErrorJSON.render("404.json", %{}) == %{
|
||||
errors: %{detail: "Not Found"}
|
||||
}
|
||||
end
|
||||
|
||||
test "renders 500" do
|
||||
|
||||
Loading…
Reference in New Issue
Block a user