2026-01-17 23:09:42 +00:00
# SimpleShop Roadmap
2026-01-17 22:39:37 +00:00
This document tracks future improvements, features, and known gaps.
2026-01-19 21:06:31 +00:00
---
## 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.
---
2026-01-17 22:39:37 +00:00
## Quick Wins (Low Effort)
2026-01-17 23:06:04 +00:00
### 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
2026-01-17 22:39:37 +00:00
---
## 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
2026-01-19 21:06:31 +00:00
### 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
2026-01-17 22:39:37 +00:00
---
## 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
2026-01-17 23:09:42 +00:00
### 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
2026-01-17 22:39:37 +00:00
---
## Completed (For Reference)
2026-01-17 22:56:21 +00:00
### 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`)
2026-01-17 22:39:37 +00:00
### 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 ✅
2026-01-19 23:45:02 +00:00
- Public shop routes (/, /collections/:slug, /products/:id, /cart, /about, /contact)
2026-01-17 22:39:37 +00:00
- Shared PageTemplates for shop and preview
- CSS injection via shop layout
- Themed error pages (404/500)
- Dev routes for error page preview
2026-01-19 21:12:13 +00:00
### CSS Cache Warming on Startup ✅
- ETS cache pre-warmed in `CSSCache.init/1`
- First request doesn't need to generate CSS
2026-01-19 23:45:02 +00:00
### 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