FTS5 prefix matching misses mid-word substrings (e.g. "ebook" in "notebook"). When FTS5 returns zero results, fall back to LIKE query on title and category with proper wildcard escaping. 4 new tests, 757 total. Also marks completed plan files (search, admin-redesign, setup-wizard, products-context) with correct status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
11 KiB
Plan: Setup wizard and "go live" gate
Status: Complete
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:
- Creates the single admin account (replacing the open
/users/registerendpoint) - Guides the admin through connecting Printify and Stripe
- Gates the public shop behind a "coming soon" page until setup is complete
- 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/registeris 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/registerredirects to/users/log-in - The
/setuproute also redirects to/users/log-inif 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?/0—Repo.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, checkAccounts.has_admin?()— if true, redirect to/users/log-inwith 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— readsget_setting("shop", "site_live"), returns boolean (defaultfalse) - Add
set_site_live/1— writesput_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/0returns 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/1and 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
/setuproute 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-soonroute 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_liveis 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/setupinstead of/ - Subsequent logins go to
/as normal (or/admin/setupif 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
PreviewDatacalls withProducts.list_products/1queries - 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?/0returns false with no users, true with one- Registration redirects when admin exists
- Fresh install: all public routes redirect to
/setup /setupcreates admin account and sends magic link/setupredirects 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?/0checks the local DB — naturally scoped to the tenantsite_live?/0is per-DB — each tenant goes live independentlysetup_status/0queries 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/setupredirects 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/setupreplace/admin/settingsor sit alongside it? Alongside, with setup as the landing page post-registration. - Should we cache
has_admin?/0to 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.