feat: add dynamic variant selector with color swatches
- Fix Printify options parsing (Color/Size were swapped) - Add extract_option_types/1 for frontend display with hex colors - Filter option types to only published variants (not full catalog) - Track selected variant in LiveView with price updates - Color swatches for color-type options, text buttons for size - Disable unavailable combinations - Add startup recovery for stale sync status Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -174,23 +174,76 @@ defmodule SimpleshopTheme.Providers.Printify do
|
||||
end
|
||||
end
|
||||
|
||||
# =============================================================================
|
||||
# Option Types Extraction (for frontend)
|
||||
# =============================================================================
|
||||
|
||||
@doc """
|
||||
Extracts option types from Printify provider_data for frontend display.
|
||||
|
||||
Returns a list of option type maps with normalized names, types, and values
|
||||
including hex color codes for color-type options.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> extract_option_types(%{"options" => [
|
||||
...> %{"name" => "Colors", "type" => "color", "values" => [
|
||||
...> %{"id" => 1, "title" => "Black", "colors" => ["#000000"]}
|
||||
...> ]}
|
||||
...> ]})
|
||||
[%{name: "Color", type: :color, values: [%{id: 1, title: "Black", hex: "#000000"}]}]
|
||||
"""
|
||||
def extract_option_types(%{"options" => options}) when is_list(options) do
|
||||
Enum.map(options, fn opt ->
|
||||
%{
|
||||
name: singularize_option_name(opt["name"]),
|
||||
type: option_type_atom(opt["type"]),
|
||||
values: extract_option_values(opt)
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
def extract_option_types(_), do: []
|
||||
|
||||
defp option_type_atom("color"), do: :color
|
||||
defp option_type_atom("size"), do: :size
|
||||
defp option_type_atom(_), do: :other
|
||||
|
||||
defp extract_option_values(%{"values" => values, "type" => "color"}) do
|
||||
Enum.map(values, fn val ->
|
||||
%{
|
||||
id: val["id"],
|
||||
title: val["title"],
|
||||
hex: List.first(val["colors"])
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp extract_option_values(%{"values" => values}) do
|
||||
Enum.map(values, fn val ->
|
||||
%{id: val["id"], title: val["title"]}
|
||||
end)
|
||||
end
|
||||
|
||||
# =============================================================================
|
||||
# Data Normalization
|
||||
# =============================================================================
|
||||
|
||||
defp normalize_product(raw) do
|
||||
options = raw["options"] || []
|
||||
|
||||
%{
|
||||
provider_product_id: to_string(raw["id"]),
|
||||
title: raw["title"],
|
||||
description: raw["description"],
|
||||
category: extract_category(raw),
|
||||
images: normalize_images(raw["images"] || []),
|
||||
variants: normalize_variants(raw["variants"] || []),
|
||||
variants: normalize_variants(raw["variants"] || [], options),
|
||||
provider_data: %{
|
||||
blueprint_id: raw["blueprint_id"],
|
||||
print_provider_id: raw["print_provider_id"],
|
||||
tags: raw["tags"] || [],
|
||||
options: raw["options"] || [],
|
||||
options: options,
|
||||
raw: raw
|
||||
}
|
||||
}
|
||||
@@ -210,7 +263,9 @@ defmodule SimpleshopTheme.Providers.Printify do
|
||||
end)
|
||||
end
|
||||
|
||||
defp normalize_variants(variants) do
|
||||
defp normalize_variants(variants, options) do
|
||||
option_names = extract_option_names(options)
|
||||
|
||||
Enum.map(variants, fn var ->
|
||||
%{
|
||||
provider_variant_id: to_string(var["id"]),
|
||||
@@ -218,24 +273,30 @@ defmodule SimpleshopTheme.Providers.Printify do
|
||||
sku: var["sku"],
|
||||
price: var["price"],
|
||||
cost: var["cost"],
|
||||
options: normalize_variant_options(var),
|
||||
options: normalize_variant_options(var, option_names),
|
||||
is_enabled: var["is_enabled"] == true,
|
||||
is_available: var["is_available"] == true
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
defp normalize_variant_options(variant) do
|
||||
# Printify variants have options as a list of option value IDs
|
||||
# We need to build the human-readable map from the variant title
|
||||
# Format: "Size / Color" -> %{"Size" => "Large", "Color" => "Blue"}
|
||||
# Extract option names from product options, singularizing common plurals
|
||||
defp extract_option_names(options) do
|
||||
Enum.map(options, fn opt ->
|
||||
singularize_option_name(opt["name"])
|
||||
end)
|
||||
end
|
||||
|
||||
defp singularize_option_name("Colors"), do: "Color"
|
||||
defp singularize_option_name("Sizes"), do: "Size"
|
||||
defp singularize_option_name(name), do: name
|
||||
|
||||
defp normalize_variant_options(variant, option_names) do
|
||||
# Build human-readable map from variant title
|
||||
# Title format matches product options order: "Navy / S" for [Colors, Sizes]
|
||||
title = variant["title"] || ""
|
||||
parts = String.split(title, " / ")
|
||||
|
||||
# Common option names based on position
|
||||
option_names = ["Size", "Color", "Style"]
|
||||
|
||||
parts
|
||||
|> Enum.with_index()
|
||||
|> Enum.reduce(%{}, fn {value, index}, acc ->
|
||||
|
||||
Reference in New Issue
Block a user