berrypod/docs/plans/setup-and-launch.md
jamey c2caeed64d add setup onboarding page, dashboard launch checklist, provider registry
- new /setup page with three-section onboarding (account, provider, payments)
- dashboard launch checklist with progress bar, go-live, dismiss
- provider registry on Provider module (single source of truth for metadata)
- payments registry for Stripe
- setup context made provider-agnostic (provider_connected, theme_customised, etc.)
- admin provider pages now fully registry-driven (no hardcoded provider names)
- auth flow: fresh installs redirect to /setup, signed_in_path respects setup state
- removed old /admin/setup wizard
- 840 tests, 0 failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 00:34:06 +00:00

354 lines
15 KiB
Markdown

# Unified setup and launch readiness
## Context
The current first-run experience is disjointed: `/users/register` is a standalone page that closes after one user, the login page still links to it, and `/admin/setup` is a 3-step wizard hardcoded to Printify that bundles "go live" into the initial setup. Research into Shopify, WooCommerce and Squarespace shows that every successful platform separates initial setup (get the plumbing working) from launch readiness (guide the owner to a shop worth opening).
## Two-phase design
### Phase A: Initial setup (`/setup`)
Quick, focused, one-time. Gets the system functional. 5-10 minutes.
- Create admin account (email + magic link)
- Connect a print provider (provider-agnostic: Printify, Printful, etc.)
- Connect payments (Stripe)
No theme customisation, no "go live", no product sync waiting. Once all three connections are made, redirect to `/admin`. The `/setup` page is never shown again.
### Phase B: Launch checklist (persistent on `/admin` dashboard)
Ongoing guidance that lives on the admin dashboard until all items are complete or dismissed. Inspired by Shopify's setup guide and WooCommerce's task list. Items auto-complete based on real state. Uses the Zeigarnik effect — seeing a partially-complete checklist motivates finishing it.
Checklist items:
| # | Item | Auto-completes when | Links to |
|---|------|-------------------|----------|
| 1 | Sync your products | `product_count > 0` | `/admin/providers` |
| 2 | Customise your theme | Theme preset changed from default | `/admin/theme` |
| 3 | Review your pages | At least one content page edited (future: page editor) | `/admin/settings` |
| 4 | Place a test order | At least one order exists | `/` (the shop) |
| 5 | Go live | `site_live? == true` | Button right on the checklist |
"Go live" is the final step, only enabled when at least items 1 (products synced) and the provider + Stripe connections from setup are still valid. Items 2-4 are recommended but not blocking — the admin can dismiss them or skip straight to go live.
When all items are complete (or dismissed), the checklist is replaced by the normal dashboard stats (orders, revenue, products) — same as what WooCommerce does.
### Coming-soon page (branded holding page)
Until "go live" is flipped, unauthenticated visitors see a themed page using the current theme settings (logo, colours, fonts, site name). Not a sad blank wall — a preview of the brand.
Content:
- Site name / logo
- "We're getting things ready. Check back soon."
- Optional: email signup for launch notification (future enhancement, not MVP)
The admin bypasses this and sees the full shop (existing behaviour via ThemeHook).
## Architecture
### `/setup` page
**One LiveView, one page, one URL: `/setup`.**
Not a stepper wizard — a single scrollable page with independent sections as cards. Each section can be completed in any order. Completed sections collapse to show a checkmark and summary.
**Access rules:**
- No admin exists → page is public, shows all sections including account creation
- Admin exists + logged in + setup incomplete → page is accessible, account section hidden
- Admin exists + not logged in → redirect to `/users/log-in`
- Setup complete (all three connections made) → redirect to `/admin`
- Site is live → redirect to `/`
"Setup complete" means: admin exists + provider connected + Stripe connected. Products don't need to be synced yet — that's a launch checklist item, not a setup gate.
**Why no auth gate for provider/payment setup:** Single-tenant app. During initial setup there's no data to protect. The page is only reachable before setup is complete.
**Sections:**
| Section | Shown when | Content |
|---------|-----------|---------|
| Create admin account | No admin exists | Email field, magic link flow |
| Connect a print provider | No provider connected | Provider cards (Printify, Printful, coming soon), API key form |
| Connect payments | No payment provider | Stripe API key form |
Each completed section collapses to a summary line with a checkmark (e.g. "Connected to Printify").
**No theme/style section here.** Theme customisation is a launch readiness concern, not a setup concern. Keep setup lean.
### Dashboard launch checklist
Lives on the existing `/admin` dashboard page (`lib/berrypod_web/live/admin/dashboard.ex`). Shown as a card above the stats when `site_live?` is false or when checklist items remain incomplete.
**Component:** A function component `<.launch_checklist>` that takes `@setup` assigns and renders the checklist card. Each item is a row with: checkbox/checkmark, label, description, link/button.
**Progress indicator:** "3 of 5 complete" with a simple progress bar. Shopify research shows progress bars motivate completion.
**Dismissal:** The checklist can be dismissed permanently via a "Dismiss" link. Stores `checklist_dismissed` in Settings. Once dismissed or all items complete, shows the normal stats-only dashboard.
**"Go live" button:** Inline in the checklist as the final item. Enabled when products are synced + connections valid. Clicking it sets `site_live` and shows a brief celebration state before transitioning to the normal dashboard.
### Setup status (`Setup.setup_status/0`)
Make provider-agnostic and add launch checklist fields:
```elixir
%{
# Setup phase (connections)
admin_created: boolean,
provider_connected: boolean,
provider_type: string | nil,
stripe_connected: boolean,
setup_complete: boolean, # admin + provider + stripe all connected
# Launch checklist phase
products_synced: boolean,
product_count: integer,
theme_customised: boolean,
has_orders: boolean,
site_live: boolean,
# Derived
can_go_live: boolean, # provider_connected and products_synced and stripe_connected
checklist_dismissed: boolean
}
```
## Files to create
### `lib/berrypod/providers/registry.ex`
Provider metadata. Pure data, no DB.
```elixir
@providers [
%{
type: "printify",
name: "Printify",
tagline: "Largest catalog — 800+ products from 90+ print providers",
features: ["Biggest product selection", "Multi-provider routing", "Competitive pricing"],
setup_hint: "Get your API token from Printify → Account → Connections",
setup_url: "https://printify.com/app/account/connections",
status: :available
},
%{
type: "printful",
name: "Printful",
tagline: "Premium quality with in-house fulfilment",
features: ["In-house production", "Warehousing & branding", "Mockup generator"],
setup_hint: "Get your API token from Printful → Settings → API",
setup_url: "https://www.printful.com/dashboard/developer/api",
status: :available
},
%{type: "gelato", name: "Gelato", tagline: "Local production in 30+ countries", status: :coming_soon},
%{type: "prodigi", name: "Prodigi", tagline: "Fine art and photo products", status: :coming_soon}
]
```
Functions: `all/0`, `available/0`, `get/1`
### `lib/berrypod/payments/registry.ex`
Same pattern for payment providers (just Stripe for now).
### `lib/berrypod_web/live/setup/onboarding.ex`
Single LiveView at `/setup`. Three sections: account, provider, payments.
**Mount:**
```elixir
def mount(_params, _session, socket) do
setup = Setup.setup_status()
cond do
setup.site_live ->
{:ok, push_navigate(socket, to: ~p"/")}
setup.setup_complete ->
{:ok, push_navigate(socket, to: ~p"/admin")}
setup.admin_created and is_nil(get_user(socket)) ->
{:ok, push_navigate(socket, to: ~p"/users/log-in")}
true ->
{:ok, mount_setup(socket, setup)}
end
end
```
**Account section events:**
- `handle_event("register", %{"email" => email}, socket)` — calls `Accounts.register_user/1` + `deliver_login_instructions/2`. Shows "Check your email" note.
**Provider section events:**
- `handle_event("select_provider", %{"type" => type}, socket)` — expands that provider's API key form.
- `handle_event("test_connection", %{"provider" => params}, socket)` — tests API key validity.
- `handle_event("connect_provider", %{"provider" => params}, socket)` — creates connection, enqueues sync in background. Does NOT wait for sync to finish — moves on.
**Payments section events:**
- `handle_event("connect_stripe", %{"stripe" => params}, socket)` — reuses existing `StripeSetup.connect/1`.
**Completion:** When all three sections are done (`setup_complete`), show a brief "You're all set" message with a button to go to the dashboard. Or auto-redirect after a short delay.
## Files to modify
### `lib/berrypod/setup.ex`
- Replace `Products.get_provider_connection_by_type("printify")` with `Products.get_first_provider_connection/0`
- Rename `printify_connected``provider_connected`
- Add `provider_type` field (e.g. "printify", "printful")
- Add `setup_complete` field: `admin_created and provider_connected and stripe_connected`
- Add `theme_customised` field: true if theme preset != default
- Add `has_orders` field: `Orders.count_paid_orders() > 0`
- Add `checklist_dismissed` field from Settings
- Update `can_go_live`: `provider_connected and products_synced and stripe_connected`
### `lib/berrypod/products.ex`
Add `get_first_provider_connection/0` — first connection with non-nil `api_key_encrypted`, ordered by `inserted_at`.
### `lib/berrypod_web/live/admin/dashboard.ex`
- Load `Setup.setup_status()` on mount
- When `!setup.site_live and !setup.checklist_dismissed`, render `<.launch_checklist>` above the stats
- When `setup.site_live or setup.checklist_dismissed`, render the normal stats dashboard
- Handle "go_live" event: `Settings.set_site_live(true)`, show celebration, then reload as normal dashboard
- Handle "dismiss_checklist" event: `Settings.put_setting("shop", "checklist_dismissed", "true")`
### `lib/berrypod_web/router.ex`
Add `/setup` to a minimal live_session (no ThemeHook, no CartHook):
```elixir
live "/setup", Setup.Onboarding, :index
```
Remove `/admin/setup` from admin live_session (or redirect to `/setup` if not yet complete, `/admin` if complete).
### `lib/berrypod_web/user_auth.ex`
Change `signed_in_path/1`:
- If setup not complete → `~p"/setup"`
- If setup complete but not live → `~p"/admin"`
- If live → `~p"/admin"`
### `lib/berrypod_web/live/auth/registration.ex`
Redirect to `/setup` (no admin) or `/users/log-in` (admin exists).
### `lib/berrypod_web/live/auth/login.ex`
- Add `@registration_open` assign (`!Accounts.has_admin?()`)
- Hide "Sign up" link when admin exists
### `lib/berrypod_web/theme_hook.ex`
Change fresh-install redirect from `/users/register` to `/setup`.
### `lib/berrypod_web/components/layouts/admin.html.heex`
- Remove "Setup" sidebar link (setup is now `/setup`, not an admin page)
- The dashboard handles launch readiness now
### `lib/berrypod_web/live/shop/coming_soon.ex`
Keep as-is but potentially enhance later with email signup. Current implementation already uses theme settings for site name.
### `assets/css/admin/components.css`
Add under `/* ── Setup ── */`:
```css
/* /setup page */
.admin-setup-done {
/* collapsed summary: checkmark + text */
}
/* Dashboard launch checklist */
.admin-checklist { }
.admin-checklist-item { }
.admin-checklist-item[data-complete] { }
.admin-checklist-progress { }
```
~30-40 lines total. Everything else uses existing admin component classes.
## Auth flow
```
Fresh install (no admin):
Visit anything → ThemeHook → redirect /setup
/setup shows ALL sections (account, provider, payments)
User creates account via magic link
User clicks magic link → confirmed → signed_in_path = /setup
/setup: account section done, provider + payments still needed
Complete connections → redirect /admin
Dashboard shows launch checklist
Returning admin (setup complete, not yet live):
Visit /admin → dashboard with launch checklist
Checklist: sync products, customise theme, test order, go live
Items auto-complete as admin works through them
Admin clicks "Go live" → site opens to public
Visitor (not yet live):
Visit anything → ThemeHook → redirect /coming-soon
Sees branded holding page with site name
Already live:
/setup → redirect /admin
Dashboard shows stats (no checklist)
Visitors see the real shop
```
## CSS approach
Reuse existing admin components. Small additions needed:
| Need | Reuse | New? |
|------|-------|------|
| Section cards | `.admin-card` + `.admin-card-body` + `.admin-card-title` | No |
| Badges | `.admin-badge` | No |
| Buttons | `.admin-btn-*` | No |
| Forms | `.admin-input`, `.admin-label` | No |
| Alerts/notes | `.admin-alert-info` | No |
| Spinner | `.admin-spinner` | No |
| Section complete state | — | **Yes** — collapsed state with checkmark |
| Checklist items | — | **Yes** — row with checkbox, label, link |
| Progress bar | — | **Yes** — simple bar for "3 of 5 complete" |
All new CSS in `assets/css/admin/components.css`, using `.admin-setup-*` and `.admin-checklist-*` prefixes.
## Task breakdown
| # | Task | Files | Est |
|---|------|-------|-----|
| 1 | Provider + payment registries | `providers/registry.ex`, `payments/registry.ex` | 30m |
| 2 | Make Setup provider-agnostic + add checklist fields | `setup.ex`, `products.ex` | 45m |
| 3 | Setup LiveView (`/setup`) — account, provider, payments | `setup/onboarding.ex` | 2.5h |
| 4 | Dashboard launch checklist component + go-live | `dashboard.ex` | 2h |
| 5 | Router, auth flow, redirects | `router.ex`, `user_auth.ex`, `registration.ex`, `login.ex`, `theme_hook.ex`, `admin.html.heex` | 30m |
| 6 | CSS additions (~40 lines) | `admin/components.css` | 20m |
| 7 | Tests | setup, dashboard checklist, auth flow | 2h |
| 8 | Remove old `/admin/setup` | delete or redirect | 15m |
**Total: ~9 hours across 4-5 sessions**
## Verification
1. **Fresh install:** Delete local DB, run migrations, visit `localhost:4000` → redirects to `/setup`. Complete account + provider + payments → lands on `/admin` with launch checklist.
2. **Launch checklist:** Sync products → item auto-completes. Change theme → item auto-completes. Progress bar updates.
3. **Go live:** Click "Go live" on checklist → celebration → visitors see real shop.
4. **Coming soon:** Before go-live, unauthenticated visitors see branded holding page.
5. **Dismiss:** Admin can dismiss the checklist and use the normal dashboard without going live.
6. **Provider agnostic:** Connect Printful instead of Printify — works the same.
7. **Dead ends:** `/users/register` redirects to `/setup`. Login page hides "Sign up" when admin exists.
8. **`mix precommit`** passes.
## Future enhancements (not in scope)
- **Email signup on coming-soon page** — collect emails for launch notification
- **Test order detection** — auto-complete "Place a test order" when an order with a test Stripe key exists
- **Content page editing** — "Review your pages" becomes more meaningful with a page editor (Tier 4)
- **Preset picker on setup page** — considered and cut. Theme customisation belongs in the launch checklist phase, not initial setup. The theme editor is the right tool for this.
- **Onboarding tooltips** — contextual hints on first visit to admin pages (e.g. "This is where you manage your products")