# 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:** ```elixir # 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.OAuth` → `SimpleshopTheme.Printify.OAuth` - Adapt `Simpleshop.Printify.Client` → `SimpleshopTheme.Printify.Client` - Adapt `Simpleshop.Printify.TokenStore` → `SimpleshopTheme.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:** ```elixir # 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:** ```elixir # mix.exs {:stripity_stripe, "~> 3.0"} ``` **Config:** ```elixir # config/runtime.exs config :stripity_stripe, api_key: System.get_env("STRIPE_SECRET_KEY") # Also need STRIPE_WEBHOOK_SECRET for webhook verification ``` **Implementation:** ```elixir # 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:** ```elixir # 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:** ```elixir 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:** ```elixir # 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:** ```elixir # 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:** ```elixir # 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:** ```elixir # 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) *(None pending)* --- ## 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 (`SimpleshopTheme` → `SimpleShop`) - `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 (/, /collections/:slug, /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 ### Navigation Links Between Admin and Shop ✅ - "View Shop" button in theme editor header - Collapsible navigation sidebar in theme editor ### Collection Routes with Filtering & Sorting ✅ - `/collections/:slug` routes for category filtering - `/collections/all` for all products - Product sorting (featured, newest, price, name) - Sort parameter preserved in URL across navigation - Category filter pills with sort persistence ### Header Navigation Accessibility ✅ - Current page is not a link (avoids self-links) - Logo links to home except when on home page - `aria-current="page"` with visual underline indicator ### Enhanced Contact Page ✅ - `newsletter_card` component with `:card` and `:inline` variants (shared with footer) - `social_links_card` component with icon + text label cards - Contact form with integrated email link and response time - Reorganized layout: contact form left, info cards right