- 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>
15 KiB
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:
%{
# 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.
@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:
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)— callsAccounts.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 existingStripeSetup.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")withProducts.get_first_provider_connection/0 - Rename
printify_connected→provider_connected - Add
provider_typefield (e.g. "printify", "printful") - Add
setup_completefield:admin_created and provider_connected and stripe_connected - Add
theme_customisedfield: true if theme preset != default - Add
has_ordersfield:Orders.count_paid_orders() > 0 - Add
checklist_dismissedfield 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):
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_openassign (!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 ── */:
/* /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
- Fresh install: Delete local DB, run migrations, visit
localhost:4000→ redirects to/setup. Complete account + provider + payments → lands on/adminwith launch checklist. - Launch checklist: Sync products → item auto-completes. Change theme → item auto-completes. Progress bar updates.
- Go live: Click "Go live" on checklist → celebration → visitors see real shop.
- Coming soon: Before go-live, unauthenticated visitors see branded holding page.
- Dismiss: Admin can dismiss the checklist and use the normal dashboard without going live.
- Provider agnostic: Connect Printful instead of Printify — works the same.
- Dead ends:
/users/registerredirects to/setup. Login page hides "Sign up" when admin exists. mix precommitpasses.
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")