simpleshop_theme/docs/plans/setup-wizard.md
jamey 880f63e0b1 add setup wizard plan and update progress roadmap
Detailed plan for setup wizard, go-live gate, and registration
lockdown. Covers single-admin account creation, coming soon page,
admin setup checklist, and platform compatibility notes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:35:25 +00:00

11 KiB

Plan: Setup wizard and "go live" gate

Status: Pending

Overview

The shop currently shows demo/preview data to all visitors with no admin account creation flow, open registration, and no gate between setup and going live. This plan adds a proper first-run setup wizard that:

  1. Creates the single admin account (replacing the open /users/register endpoint)
  2. Guides the admin through connecting Printify and Stripe
  3. Gates the public shop behind a "coming soon" page until setup is complete
  4. Provides a "go live" toggle

Dependency chain

Setup wizard/gate → Real product data on shop pages → Shipping costs at checkout

This plan covers the first piece. The other two are separate tasks that build on top.

Current state

  • /users/register is open to anyone — no user limit, no role system
  • All registered users get full admin access to /admin/* routes
  • No "first run" detection — fresh installs show the demo shop to everyone
  • No way to close registration after the first user

Design: single-admin, closed registration

SimpleShop is single-tenant: one shop, one admin. The setup wizard replaces the generic registration flow entirely.

Fresh install flow

1. Fresh install (no users in DB)
   → All public routes redirect to /setup
   → /setup shows "Create your admin account" form (email input)
   → Magic link email sent → user confirms → account created
   → Registration permanently closed (one user = done)

2. Admin logged in, setup incomplete
   → Admin sees /admin/setup checklist
   → Connect Printify → Sync products → Connect Stripe
   → Public visitors see "coming soon" page

3. Admin clicks "Go live"
   → Public visitors see the real shop
   → Admin can take offline again if needed

Registration lockdown

  • Accounts.has_admin?/0 — checks if any user exists (simple count > 0)
  • When has_admin?() is true, /users/register redirects to /users/log-in
  • The /setup route also redirects to /users/log-in if admin already exists
  • No new users can ever be created after the first one

Prerequisites check

The setup wizard checks three things before allowing "go live":

Step How to check Module/function
Printify connected Products.get_provider_connection_by_type("printify") returns a connection with a non-nil api_key_encrypted SimpleshopTheme.Products
Products synced Products.count_products_for_connection(conn.id) > 0 SimpleshopTheme.Products
Stripe connected Settings.has_secret?("stripe_api_key") SimpleshopTheme.Settings

Optional (nice-to-have, not blocking go-live):

  • Stripe webhook configured: Settings.has_secret?("stripe_webhook_signing_secret")
  • Shop name customised: theme_settings.site_name != "SimpleShop" (or similar default check)

Changes

1. Accounts.has_admin?/0 and registration lockdown

File: lib/simpleshop_theme/accounts.ex

  • Add has_admin?/0Repo.exists?(User) (any user = admin exists)
  • This is the single check that gates registration

File: lib/simpleshop_theme_web/live/user_live/registration.ex

  • In mount/3, check Accounts.has_admin?() — if true, redirect to /users/log-in with a flash like "Registration is closed"

File: lib/simpleshop_theme_web/router.ex

  • No route changes needed yet — the LiveView mount handles the redirect

Estimate: 20 mins

2. Add site_live setting and setup status

File: lib/simpleshop_theme/settings.ex

  • Add site_live?/0 — reads get_setting("shop", "site_live"), returns boolean (default false)
  • Add set_site_live/1 — writes put_setting("shop", "site_live", value)

No migration needed — settings table already stores arbitrary key/value pairs.

File: lib/simpleshop_theme/setup.ex (new module)

  • setup_status/0 returns a map:
    %{
      admin_created: boolean,
      printify_connected: boolean,
      products_synced: boolean,
      product_count: integer,
      stripe_connected: boolean,
      site_live: boolean,
      can_go_live: boolean  # all three service prerequisites met
    }
    
  • Keep it simple — a single function that queries current state, no caching

Estimate: 30 mins

3. Fresh install redirect (no admin exists)

File: lib/simpleshop_theme_web/hooks/theme_hook.ex

ThemeHook already runs on every public shop page mount. Add early check:

  • If Accounts.has_admin?() is false → redirect to /setup
  • This catches the fresh install case before any other logic runs

File: lib/simpleshop_theme_web/live/setup_live.ex (new)

A simple public LiveView at /setup that:

  • If admin already exists → redirect to /users/log-in
  • If no admin → show "Welcome to SimpleShop" with email input form
  • On submit → calls Accounts.register_user/1 and sends magic link
  • Shows "Check your email" confirmation

This reuses the existing registration logic but with a different UI (setup-focused, not generic registration).

File: lib/simpleshop_theme_web/router.ex

  • Add /setup route in a minimal live_session (no ThemeHook, no CartHook — avoids the redirect loop)

Estimate: 1.5 hours

4. "Coming soon" page for public visitors

File: lib/simpleshop_theme_web/hooks/theme_hook.ex

Extend the ThemeHook logic (after the fresh install check):

  • If site_live?() is false AND user is not authenticated → redirect to /coming-soon

File: lib/simpleshop_theme_web/live/shop_live/coming_soon.ex (new)

Minimal LiveView:

  • Uses the shop root layout (gets theme styling) but no nav/footer
  • Shows site name/logo, "Coming soon" heading, optional tagline
  • No redirect loop — this page itself doesn't trigger the gate

File: lib/simpleshop_theme_web/router.ex

  • Add /coming-soon route in the public shop live_session but mark it as exempt from the gate (via assign or separate handling in ThemeHook)

Estimate: 1 hour

5. Admin setup checklist page

File: lib/simpleshop_theme_web/live/admin/setup_live.ex (new)

Admin page at /admin/setup showing:

  • Step 1: Connect Printify — status indicator (done/not done), link to /admin/providers
  • Step 2: Sync products — status (X products synced / none yet), link to /admin/providers
  • Step 3: Connect Stripe — status indicator, link to /admin/settings
  • Go live button — enabled only when can_go_live is true
  • Take offline button — when already live, allows switching back

Each step shows what to do and links to where to do it. Feels like guided onboarding, not a settings dump.

Files:

  • lib/simpleshop_theme_web/live/admin/setup_live.ex
  • Router update to add the route
  • Admin nav update to include "Setup" link (prominent when not live)

Estimate: 2 hours

6. Admin bar "not live" indicator

File: lib/simpleshop_theme_web/components/shop_components/layout.ex

  • When shop is not live, show a banner in the admin bar: "Your shop is not live — [Complete setup →]"
  • When shop is live, the setup page becomes a less prominent settings link

Estimate: 30 mins

7. Post-login redirect for fresh admin

File: lib/simpleshop_theme_web/user_auth.ex

  • After confirming magic link (first login ever), redirect to /admin/setup instead of /
  • Subsequent logins go to / as normal (or /admin/setup if not live yet)

Could be as simple as: if site_live?() is false, signed_in_path returns /admin/setup

Estimate: 20 mins

Task breakdown

# Task Files Estimate
1 has_admin?/0 + lock registration accounts.ex, registration.ex 20 mins
2 site_live?/0, set_site_live/1, Setup.setup_status/0 settings.ex, setup.ex 30 mins
3 Fresh install setup page (/setup) setup_live.ex, router.ex 1.5 hours
4 Coming soon page + ThemeHook gate coming_soon.ex, theme_hook.ex, router.ex 1 hour
5 Admin setup checklist (/admin/setup) admin/setup_live.ex, router.ex 2 hours
6 Admin bar "not live" indicator layout.ex 30 mins
7 Post-login redirect to setup user_auth.ex 20 mins
8 Tests test files 1.5 hours

Total estimate: ~7-8 hours across 3-4 sessions

Downstream tasks (separate plans)

Wire real product data into shop pages

Currently ShopLive.Home and other pages use PreviewData.products() / PreviewData.categories(). Once real products exist:

  • Replace PreviewData calls with Products.list_products/1 queries
  • Fall back to preview data when no real products exist (keeps the theme editor working)
  • Collection page already uses real data via the Products context — verify it works end-to-end

Estimate: 1-2 hours

Shipping costs at checkout

Options:

  • Stripe shipping rates: Define fixed shipping rates in Stripe, pass them to Checkout Session
  • Printify shipping query: Call Printify's shipping API to get real rates based on destination
  • Hybrid: Use Printify rates but configure via Stripe's dynamic shipping option

Estimate: 2-4 hours (needs research)

Testing

  • has_admin?/0 returns false with no users, true with one
  • Registration redirects when admin exists
  • Fresh install: all public routes redirect to /setup
  • /setup creates admin account and sends magic link
  • /setup redirects to login when admin already exists
  • Coming soon page renders when not live + not authenticated
  • Admin bypasses coming soon and sees the full shop
  • Setup checklist shows correct status for each step
  • Go live button works and flips the gate
  • After going live, public visitors see the real shop
  • Take offline button works

Platform compatibility (Tier 5)

This design is forward-compatible with the hosted platform (per-tenant databases):

  • has_admin?/0 checks the local DB — naturally scoped to the tenant
  • site_live?/0 is per-DB — each tenant goes live independently
  • setup_status/0 queries local state — all per-tenant

The only difference is how the admin account arrives:

  • Self-hosted: Admin visits /setup, enters email, confirms magic link
  • Platform: Provisioning layer pre-seeds the admin user in the tenant DB during sign-up. has_admin?() is already true, so /setup redirects to login and the setup wizard starts at service connections

No code changes needed for this — the platform layer just calls Accounts.register_user/1 directly instead of going through the /setup UI. The setup checklist, go-live gate, and coming soon page all work identically in both modes.

Open questions

  • Should the coming soon page be themeable (use current theme colours/fonts) or a fixed design? Leaning themeable — it's a nice preview of the brand.
  • Should /admin/setup replace /admin/settings or sit alongside it? Alongside, with setup as the landing page post-registration.
  • Should we cache has_admin?/0 to avoid a DB query on every page load? Probably fine uncached for single-tenant — one tiny query. Could use an ETS flag if it becomes an issue.