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)
-`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` |
| 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.