275 lines
11 KiB
Markdown
275 lines
11 KiB
Markdown
|
|
# 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?/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`, 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:
|
||
|
|
```elixir
|
||
|
|
%{
|
||
|
|
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.
|