simpleshop_theme/ROADMAP.md
Jamey Greenwood fae851159a docs: move CSS cache warming to completed (already implemented)
The CSSCache.init/1 already calls warm() on startup.
Remove from Quick Wins, add to Completed section.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 21:12:13 +00:00

14 KiB

SimpleShop Roadmap

This document tracks future improvements, features, and known gaps.


Core MVP: Real Products & Checkout (Priority)

This section covers the work needed to turn SimpleShop from a theme demo into a working e-commerce storefront. Estimated total effort: 3-4 days (leveraging existing Printify demo code).

Phase A: Products Context + Printify Sync

Status: Not implemented Effort: 1-1.5 days Dependencies: None

Replace PreviewData with real products synced from Printify.

Schemas:

# products table
field :printify_id, :string
field :title, :string
field :description, :text
field :images, {:array, :map}  # [{src, position}]
field :print_provider_id, :integer
field :blueprint_id, :integer
field :synced_at, :utc_datetime
field :published, :boolean, default: true
timestamps()

# product_variants table
field :printify_variant_id, :integer
field :title, :string  # e.g. "Black / M"
field :sku, :string
field :price_cents, :integer      # selling price
field :cost_cents, :integer       # Printify cost (for profit calc)
field :options, :map              # %{"Color" => "Black", "Size" => "M"}
field :is_available, :boolean
belongs_to :product

# product_cost_history table (append-only for future analytics)
field :cost_cents, :integer
field :recorded_at, :utc_datetime
belongs_to :product_variant

Implementation:

  1. Migrate OAuth + Client modules from simpleshop_printify demo
    • Adapt Simpleshop.Printify.OAuthSimpleshopTheme.Printify.OAuth
    • Adapt Simpleshop.Printify.ClientSimpleshopTheme.Printify.Client
    • Adapt Simpleshop.Printify.TokenStoreSimpleshopTheme.Printify.TokenStore
  2. Create SimpleshopTheme.Products context with schemas
  3. Add mix sync_products task to pull products from Printify
  4. Add webhook endpoint for product:publish:started events
  5. Replace PreviewData calls in LiveViews with Products context queries
  6. Store cost history on each sync for future profit analytics

Webhook flow:

  1. Seller clicks "Publish to SimpleShop" in Printify dashboard
  2. Printify fires product:publish:started with product data
  3. SimpleShop stores product locally in SQLite
  4. SimpleShop calls Printify "publish succeeded" endpoint

Files to create:

  • lib/simpleshop_theme/printify/oauth.ex
  • lib/simpleshop_theme/printify/token_store.ex
  • lib/simpleshop_theme/products.ex
  • lib/simpleshop_theme/products/product.ex
  • lib/simpleshop_theme/products/variant.ex
  • lib/simpleshop_theme/products/cost_history.ex
  • lib/simpleshop_theme_web/controllers/printify_webhook_controller.ex
  • lib/mix/tasks/sync_products.ex
  • priv/repo/migrations/*_create_products.exs

Phase B: Session-Based Cart

Status: Not implemented Effort: 0.5 days Dependencies: Phase A (Products)

Real cart functionality with session persistence.

Approach: Store cart in Phoenix session (no separate cart table needed for MVP). Cart is a map of %{variant_id => quantity} stored in the session.

Implementation:

# lib/simpleshop_theme/cart.ex
defmodule SimpleshopTheme.Cart do
  alias SimpleshopTheme.Products

  def get(session), do: Map.get(session, "cart", %{})
  
  def add_item(session, variant_id, quantity \\ 1)
  def remove_item(session, variant_id)
  def update_quantity(session, variant_id, quantity)
  def clear(session)
  
  def to_line_items(cart) do
    # Returns list of %{variant: variant, quantity: qty, subtotal: price}
  end
  
  def total(cart)  # Returns total in cents
  def item_count(cart)  # For header badge
end

LiveView integration:

  • Add phx-click="add_to_cart" to product pages
  • Update cart LiveView to use real data
  • Add cart count to header (assign in on_mount)

Files to create/modify:

  • lib/simpleshop_theme/cart.ex
  • Modify lib/simpleshop_theme_web/live/shop_live/product_show.ex
  • Modify lib/simpleshop_theme_web/live/shop_live/cart.ex
  • Modify lib/simpleshop_theme_web/components/layouts.ex (cart count)

Phase C: Stripe Checkout

Status: Not implemented Effort: 0.5-1 day Dependencies: Phase B (Cart)

Stripe Checkout (hosted payment page) integration.

Dependencies to add:

# mix.exs
{:stripity_stripe, "~> 3.0"}

Config:

# config/runtime.exs
config :stripity_stripe,
  api_key: System.get_env("STRIPE_SECRET_KEY")

# Also need STRIPE_WEBHOOK_SECRET for webhook verification

Implementation:

# lib/simpleshop_theme/checkout.ex
defmodule SimpleshopTheme.Checkout do
  def create_session(cart, success_url, cancel_url) do
    line_items = Enum.map(cart, fn {variant_id, qty} ->
      variant = Products.get_variant!(variant_id)
      %{
        price_data: %{
          currency: "gbp",
          unit_amount: variant.price_cents,
          product_data: %{
            name: "#{variant.product.title} - #{variant.title}",
            images: [hd(variant.product.images)["src"]]
          }
        },
        quantity: qty
      }
    end)

    Stripe.Checkout.Session.create(%{
      mode: "payment",
      line_items: line_items,
      success_url: success_url <> "?session_id={CHECKOUT_SESSION_ID}",
      cancel_url: cancel_url,
      shipping_address_collection: %{allowed_countries: ["GB"]},
      metadata: %{cart: Jason.encode!(cart)}
    })
  end
end

Webhook handler:

# lib/simpleshop_theme_web/controllers/stripe_webhook_controller.ex
def handle_event(%Stripe.Event{type: "checkout.session.completed"} = event) do
  session = event.data.object
  cart = Jason.decode!(session.metadata["cart"])
  shipping = session.shipping_details
  
  # Create order and push to Printify (Phase D)
  Orders.create_from_checkout(session, cart, shipping)
end

Files to create:

  • lib/simpleshop_theme/checkout.ex
  • lib/simpleshop_theme_web/controllers/stripe_webhook_controller.ex
  • lib/simpleshop_theme_web/live/shop_live/checkout_success.ex
  • lib/simpleshop_theme_web/live/shop_live/checkout_cancel.ex

Routes:

post "/webhooks/stripe", StripeWebhookController, :handle
live "/checkout/success", ShopLive.CheckoutSuccess
live "/checkout/cancel", ShopLive.CheckoutCancel

Phase D: Orders + Printify Fulfillment

Status: Not implemented Effort: 0.5-1 day Dependencies: Phase C (Stripe Checkout)

Create orders locally and push to Printify for fulfillment.

Schema:

# orders table
field :stripe_session_id, :string
field :stripe_payment_intent_id, :string
field :printify_order_id, :string
field :status, Ecto.Enum, values: [:pending, :paid, :submitted, :in_production, :shipped, :delivered, :cancelled]
field :total_cents, :integer
field :shipping_address, :map
field :customer_email, :string
timestamps()

# order_items table
field :quantity, :integer
field :unit_price_cents, :integer
field :unit_cost_cents, :integer  # For profit tracking
belongs_to :order
belongs_to :product_variant

Implementation:

# lib/simpleshop_theme/orders.ex
def create_from_checkout(stripe_session, cart, shipping) do
  Repo.transaction(fn ->
    # 1. Create local order
    order = create_order(stripe_session, shipping)
    
    # 2. Create order items
    create_order_items(order, cart)
    
    # 3. Push to Printify
    case Printify.Client.create_order(order) do
      {:ok, printify_response} ->
        update_order(order, %{
          printify_order_id: printify_response["id"],
          status: :submitted
        })
      {:error, reason} ->
        # Log error but don't fail - can retry later
        Logger.error("Failed to submit to Printify: #{inspect(reason)}")
        update_order(order, %{status: :paid})  # Mark as paid, needs manual submission
    end
  end)
end

Printify order creation:

# Add to lib/simpleshop_theme/printify/client.ex
def create_order(order) do
  body = %{
    external_id: order.id,
    shipping_method: 1,  # Standard
    address_to: %{
      first_name: order.shipping_address["name"] |> String.split() |> hd(),
      last_name: order.shipping_address["name"] |> String.split() |> List.last(),
      email: order.customer_email,
      address1: order.shipping_address["line1"],
      address2: order.shipping_address["line2"],
      city: order.shipping_address["city"],
      zip: order.shipping_address["postal_code"],
      country: order.shipping_address["country"]
    },
    line_items: Enum.map(order.items, fn item ->
      %{
        product_id: item.variant.product.printify_id,
        variant_id: item.variant.printify_variant_id,
        quantity: item.quantity
      }
    end)
  }
  
  post("/shops/#{shop_id()}/orders.json", body)
end

Files to create:

  • lib/simpleshop_theme/orders.ex
  • lib/simpleshop_theme/orders/order.ex
  • lib/simpleshop_theme/orders/order_item.ex
  • priv/repo/migrations/*_create_orders.exs
  • Update lib/simpleshop_theme/printify/client.ex with create_order/1

Phase E: Cost Verification at Checkout (Safety Net)

Status: Not implemented Effort: 0.25 days Dependencies: Phase D (Orders)

Verify Printify costs haven't changed before completing checkout.

Implementation:

# In checkout flow, before creating Stripe session
def verify_costs(cart) do
  Enum.reduce_while(cart, :ok, fn {variant_id, _qty}, _acc ->
    variant = Products.get_variant!(variant_id)
    
    case Printify.Client.get_product(variant.product.printify_id) do
      {:ok, printify_product} ->
        current_cost = find_variant_cost(printify_product, variant.printify_variant_id)
        
        if current_cost != variant.cost_cents do
          # Update local cost
          Products.update_variant_cost(variant, current_cost)
          
          if cost_increase_exceeds_threshold?(variant.cost_cents, current_cost) do
            {:halt, {:error, :costs_changed, variant}}
          else
            {:cont, :ok}
          end
        else
          {:cont, :ok}
        end
        
      {:error, _} ->
        # Can't verify - proceed but log warning
        Logger.warning("Could not verify costs for variant #{variant_id}")
        {:cont, :ok}
    end
  end)
end

This ensures sellers never unknowingly sell at a loss due to Printify price changes.


Quick Wins (Low Effort)

Status: Not implemented Effort: Small

No links exist to navigate between the theme editor (/admin/theme) and the public shop (/).

Implementation:

  • Add "View Shop" button in theme editor header
  • Add "Edit Theme" link in shop header (when authenticated)

Collection Slug Routes

Status: Partial Effort: Small

Currently we have /products but the original plan included /collections/:slug for filtered views by category.

Enhanced Contact Page

Status: Not implemented Effort: Small

The current contact page has subtle footer social icons that are easy to miss. Improvements:

  1. Newsletter signup card - Prominent card encouraging newsletter subscription as the best way to stay updated (already in footer, but deserves dedicated placement)

  2. Social media links card - Full-width card listing social platforms with icons and text labels:

    [Instagram icon] Instagram
    [Patreon icon]   Patreon
    [TikTok icon]    TikTok
    [Facebook icon]  Facebook
    [Pinterest icon] Pinterest
    

    This makes social links more discoverable than the current small footer icons.

Implementation:

  • Add newsletter_card/1 component to ShopComponents
  • Add social_links_card/1 component with configurable platforms
  • Update contact page template to include both cards
  • Consider making platforms configurable via theme settings

Medium Features

Page Builder (Database-Driven Pages)

Status: Planned (see docs/plans/page-builder.md) Effort: Large

Allow shop owners to build custom pages by combining pre-built sections:

  • Hero, Featured Products, Testimonials, Newsletter, etc.
  • Drag-and-drop section ordering
  • Per-section configuration
  • Database-backed page storage

Future Features (Large Scope)

Multi-Admin Support

Currently single-user authentication:

  • Multiple admin users
  • Role-based permissions
  • Audit logging

Custom Domains

Allow shops to use their own domain:

  • Domain verification
  • SSL certificate provisioning
  • DNS configuration guidance

Theme Export/Import

Backup and restore theme settings:

  • JSON export of all settings
  • Import with validation
  • Preset sharing between shops

Advanced Theme Features

  • Custom CSS injection
  • Custom JavaScript snippets
  • Code-level overrides for developers

Multi-Provider Support (Future)

Support multiple POD providers beyond Printify:

  • Prodigi (better for art prints)
  • Gelato (global fulfillment)
  • Provider-agnostic product model
  • Price comparison across providers

Technical Debt

Test Coverage

Phase 9 testing is basic. Areas needing better coverage:

  • Shop LiveView integration tests
  • CSS cache invalidation flow
  • Theme application across all pages
  • Responsive behaviour
  • Accessibility validation

Error Handling

  • Better error states for missing products
  • Graceful degradation when theme settings are invalid
  • Network error handling in LiveView

Rename Project to SimpleShop

Status: Not implemented Effort: Medium

The project is currently named simpleshop_theme (reflecting its origins as a theme system), but it's now a full e-commerce storefront. Rename to simple_shop or simpleshop to reflect this.

Files to update:

  • mix.exs - app name
  • lib/simpleshop_theme/lib/simple_shop/
  • lib/simpleshop_theme_web/lib/simple_shop_web/
  • All module names (SimpleshopThemeSimpleShop)
  • config/*.exs - endpoint and repo references
  • test/ directories
  • Database file name

Completed (For Reference)

Sample Content ("Wildprint Studio")

  • 16 POD products across 5 categories
  • Nature/botanical theme with testimonials
  • UK-focused (prices in £)
  • Printify API integration for mockup generation (mix generate_mockups)

Phase 1-8: Theme Editor

  • Theme settings schema and persistence
  • CSS three-layer architecture
  • 8 theme presets
  • All customisation controls
  • Logo/header image uploads
  • SVG recolouring
  • Preview system with 7 pages

Phase 9: Storefront Integration

  • Public shop routes (/, /products, /products/:id, /cart, /about, /contact)
  • Shared PageTemplates for shop and preview
  • CSS injection via shop layout
  • Themed error pages (404/500)
  • Dev routes for error page preview

CSS Cache Warming on Startup

  • ETS cache pre-warmed in CSSCache.init/1
  • First request doesn't need to generate CSS