- Create settings table for site-wide key-value configuration - Create images table for BLOB storage of logo/header images - Add Setting schema with JSON/string/integer/boolean support - Add ThemeSettings embedded schema with all theme options - Add Settings context with get/put/update operations - Add Media context for image uploads and retrieval - Add Image schema with SVG detection and storage - Add 9 curated theme presets (gallery, studio, boutique, etc.) - Add comprehensive tests for Settings and Media contexts - Add seeds with default Studio preset - All tests passing (29 tests, 0 failures)
102 lines
3.6 KiB
Elixir
102 lines
3.6 KiB
Elixir
defmodule SimpleshopTheme.Settings.ThemeSettings do
|
|
use Ecto.Schema
|
|
import Ecto.Changeset
|
|
|
|
@derive Jason.Encoder
|
|
@primary_key false
|
|
embedded_schema do
|
|
# Core theme tokens
|
|
field :mood, :string, default: "neutral"
|
|
field :typography, :string, default: "clean"
|
|
field :shape, :string, default: "soft"
|
|
field :density, :string, default: "balanced"
|
|
field :grid_columns, :string, default: "4"
|
|
field :header_layout, :string, default: "standard"
|
|
field :accent_color, :string, default: "#f97316"
|
|
|
|
# Branding
|
|
field :logo_mode, :string, default: "text-only"
|
|
field :logo_image_id, :binary_id
|
|
field :header_image_id, :binary_id
|
|
field :logo_size, :integer, default: 36
|
|
field :logo_recolor, :boolean, default: false
|
|
field :logo_color, :string, default: "#171717"
|
|
field :header_zoom, :integer, default: 100
|
|
field :header_position_x, :integer, default: 50
|
|
field :header_position_y, :integer, default: 50
|
|
|
|
# Advanced customization
|
|
field :secondary_accent_color, :string, default: "#ea580c"
|
|
field :sale_color, :string, default: "#dc2626"
|
|
field :font_size, :string, default: "medium"
|
|
field :heading_weight, :string, default: "bold"
|
|
field :layout_width, :string, default: "wide"
|
|
field :button_style, :string, default: "filled"
|
|
field :card_shadow, :string, default: "none"
|
|
field :product_text_align, :string, default: "left"
|
|
field :image_aspect_ratio, :string, default: "square"
|
|
|
|
# Feature toggles
|
|
field :announcement_bar, :boolean, default: true
|
|
field :sticky_header, :boolean, default: false
|
|
field :hover_image, :boolean, default: true
|
|
field :quick_add, :boolean, default: true
|
|
field :show_prices, :boolean, default: true
|
|
field :pdp_trust_badges, :boolean, default: true
|
|
field :pdp_reviews, :boolean, default: true
|
|
field :pdp_related_products, :boolean, default: true
|
|
end
|
|
|
|
@doc false
|
|
def changeset(settings, attrs) do
|
|
settings
|
|
|> cast(attrs, [
|
|
:mood,
|
|
:typography,
|
|
:shape,
|
|
:density,
|
|
:grid_columns,
|
|
:header_layout,
|
|
:accent_color,
|
|
:logo_mode,
|
|
:logo_image_id,
|
|
:header_image_id,
|
|
:logo_size,
|
|
:logo_recolor,
|
|
:logo_color,
|
|
:header_zoom,
|
|
:header_position_x,
|
|
:header_position_y,
|
|
:secondary_accent_color,
|
|
:sale_color,
|
|
:font_size,
|
|
:heading_weight,
|
|
:layout_width,
|
|
:button_style,
|
|
:card_shadow,
|
|
:product_text_align,
|
|
:image_aspect_ratio,
|
|
:announcement_bar,
|
|
:sticky_header,
|
|
:hover_image,
|
|
:quick_add,
|
|
:show_prices,
|
|
:pdp_trust_badges,
|
|
:pdp_reviews,
|
|
:pdp_related_products
|
|
])
|
|
|> 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(:shape, ~w(sharp soft round pill))
|
|
|> validate_inclusion(:density, ~w(spacious balanced compact))
|
|
|> validate_inclusion(:grid_columns, ~w(2 3 4))
|
|
|> validate_inclusion(:header_layout, ~w(standard centered minimal))
|
|
|> validate_inclusion(:logo_mode, ~w(text-only logo-text logo-only header-image logo-header))
|
|
|> 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)
|
|
end
|
|
end
|