feat(dashboard): replace checklist with next-step card + unified list

Phase 2 Task 41 — Dashboard redesign.

Backend:
- Extend GET /users/onboarding-status with email_verified and shop_setup_done.
- tried_ai_assistant kept in payload for backward-compat during deploy.

Frontend:
- New NextStepCard: surfaces the highest-priority incomplete onboarding item
  with a primary CTA. Priority order: verify email > set up shop > run first
  FlowPilot session > connect PSA > invite teammate > pick a plan (gated on
  trial stage warning/urgent/expired). Returns null when all done OR
  onboarding_dismissed.
- New SetupChecklist: unified single list (no SOLO/TEAM bifurcation), drops
  the stale tried_ai_assistant / Script Builder item, surfaces "Pick a plan"
  when trial stage is warning or later.
- Mounted on QuickStartPage below the hero with a "Show all setup steps"
  toggle. The whole onboarding section auto-hides when there's nothing left
  to nudge on, so the dashboard goes back to clean once setup is done.
- Removed the orphaned OnboardingChecklist component (was defined but never
  mounted).
- New useOnboardingStatus hook so page + components share one fetch contract.

Tests:
- Backend: test_onboarding_status_includes_email_verified_and_shop_setup_done.
- Frontend (Vitest): 13 new tests across NextStepCard, SetupChecklist, and
  QuickStartPage covering priority ordering, dismissal, the SOLO/TEAM
  removal, the toggle reveal, and the trial-stage gate on Pick a plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 23:19:58 -04:00
parent 99343ab7a9
commit 0c326d0616
12 changed files with 838 additions and 160 deletions

View File

@@ -90,6 +90,10 @@ async def get_onboarding_status(
)
connected_psa = (psa_q.scalar() or 0) > 0
# New (Phase 2 — Task 41)
email_verified = current_user.email_verified_at is not None
shop_setup_done = (current_user.onboarding_step_completed or 0) >= 1
return OnboardingStatus(
created_flow=created_flow,
ran_session=ran_session,
@@ -99,6 +103,8 @@ async def get_onboarding_status(
connected_psa=connected_psa,
is_team_user=is_team_user,
dismissed=current_user.onboarding_dismissed,
email_verified=email_verified,
shop_setup_done=shop_setup_done,
)

View File

@@ -7,11 +7,15 @@ class OnboardingStatus(BaseModel):
created_flow: bool
ran_session: bool
exported_session: bool
# Kept for backward-compat during deploy; new code paths should not branch on this.
tried_ai_assistant: bool
invited_teammate: bool
connected_psa: bool
is_team_user: bool
dismissed: bool
# New (Phase 2 — Task 41) — drive the unified next-step card + checklist.
email_verified: bool
shop_setup_done: bool
# --- Welcome wizard (Phase 2) ----------------------------------------------

View File

@@ -1,6 +1,11 @@
"""Tests for onboarding status endpoints."""
from datetime import datetime, timezone
import pytest
from sqlalchemy import select
from app.models.user import User
@pytest.mark.asyncio
@@ -21,6 +26,42 @@ async def test_onboarding_status_fresh_user(client, auth_headers):
assert data["connected_psa"] is False
assert data["is_team_user"] is False
assert data["dismissed"] is False
# Phase 2 fields default to false on a fresh, unverified user with no wizard progress.
assert data["email_verified"] is False
assert data["shop_setup_done"] is False
@pytest.mark.asyncio
async def test_onboarding_status_includes_email_verified_and_shop_setup_done(
client, auth_headers, test_user, test_db
):
"""email_verified flips when email_verified_at is set; shop_setup_done flips at step >= 1."""
# Sanity-check baseline.
response = await client.get(
"/api/v1/users/onboarding-status",
headers=auth_headers,
)
assert response.status_code == 200
data = response.json()
assert data["email_verified"] is False
assert data["shop_setup_done"] is False
# Mutate the underlying user, then re-fetch.
user_email = test_user["email"]
result = await test_db.execute(select(User).where(User.email == user_email))
user = result.scalar_one()
user.email_verified_at = datetime.now(tz=timezone.utc)
user.onboarding_step_completed = 1
await test_db.commit()
response = await client.get(
"/api/v1/users/onboarding-status",
headers=auth_headers,
)
assert response.status_code == 200
data = response.json()
assert data["email_verified"] is True
assert data["shop_setup_done"] is True
@pytest.mark.asyncio