Commit Graph

1136 Commits

Author SHA1 Message Date
fee4cb5b74 docs(handoff): capture Phase 2 (frontend cutover) code completion
Tasks 27–44 implemented across 18 commits on this branch. Phase O (Stripe
live setup, internal validation, flag flip) is the manual operational
follow-up and is the resume point.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 23:46:15 -04:00
c75ce0c9a3 feat(sales): redirect beta-signup to /register; queue waitlist emails
Phase 2 retires the public beta-signup form in favor of the self-serve
register flow. The /api/v1/beta-signup POST endpoint stays mounted but
now responds with 307 to /register?from=beta so any external links keep
working and analytics can tag signup origin via the from query param.

Note: there is no beta_signup table in the schema — the original
endpoint only fired an email notification, so there is no waitlist to
read and no migration to run for the email-sent_at field. The one-off
admin script in the spec is therefore a no-op and is intentionally not
added here.

- Replace POST /beta-signup handler with RedirectResponse(307)
- Drop the EmailService.send_beta_signup_notification call (the user is
  now redirected into the register flow, which has its own email path)
- Add tests/test_beta_signup_redirect.py covering the 307 + Location

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 23:43:35 -04:00
db2478dd89 feat(sales): add /contact-sales form + landing page CTA
Public Talk-to-Sales surface and a "See pricing" hero CTA on the marketing
landing page. Phase 2 Task 43 of self-serve signup.

- frontend/src/api/sales.ts: salesApi.createLead -> POST /sales-leads.
- ContactSalesPage at /contact-sales (public, gated by self_serve_enabled
  with a 404-style fallback). Form fields: name, work email, company,
  team size (1-2 / 3-5 / 6-10 / 11-25 / 26+), and an optional
  "what brought you here?" textarea -> message. Submit button disabled
  while in flight to block duplicate submissions.
- Confirmation surface replaces the form on success. Calendly block is
  hidden when VITE_CALENDLY_URL is unset.
- detectSource(): 'pricing_page' if document.referrer contains '/pricing',
  else 'landing_page'. Server emits the canonical PostHog
  talk_to_sales_form_submitted event with this source.
- LandingPage: new "See pricing" hero CTA gated by useAppConfig().
  self_serve_enabled.
- frontend/.env.example + Dockerfile: VITE_CALENDLY_URL ARG/ENV.
- Tests: ContactSalesPage submit/confirmation, Calendly hide-when-unset,
  in-flight de-dup, 404 when self-serve off; LandingPage CTA on/off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:31:56 -04:00
67fae91087 feat(pricing): add /pricing page (B-style)
Phase 2 Task 42: public pricing page gated by SELF_SERVE_ENABLED.

Backend:
- New `GET /api/v1/plans/public` (no auth) returns plan_billing rows
  joined with plan_limits.max_users (as `max_seats`), filtered to
  is_public=true AND is_archived=false, ordered by sort_order ASC,
  plan ASC. Uses get_admin_db (cross-tenant catalog read, same pattern
  as /config/public).
- `PublicPlanResponse` schema in app/schemas/billing.py.
- Registered as PUBLIC in api router.

Frontend:
- `plansApi.getPublic()` client (frontend/src/api/plans.ts).
- `PricingPage` at /pricing with hero / 3 plan cards (Pro recommended,
  Enterprise hides price) / hardcoded v1 comparison table / testimonial
  placeholder / soft trust strip.
- Reads `useAppConfig().self_serve_enabled`; renders a 404 fallback
  when disabled, never calls the API in that path.
- Start free trial CTAs link to /register?plan=starter|pro; Talk to sales
  links to /contact-sales (page wired in Task 43).

Tests:
- Backend: only-public-rows + sort-order ordering.
- Frontend (Vitest): three plan cards with API prices, /register?plan=pro
  CTA, /contact-sales CTA, 404 when self_serve_enabled is false, soft
  trust language (no SOC2 claim).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:26:27 -04:00
0c326d0616 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>
2026-05-06 23:19:58 -04:00
99343ab7a9 feat(dashboard): add TrialPill in AppLayout topbar
Mounts a billing-state pill in the topbar that reads useTrialBanner() and
renders the appropriate label / tone / CTA per spec:

- pristine / warning  → "Pro trial · Nd"   (info → warning amber as days drop)
- urgent              → "Pro trial · today" (warning amber, semibold)
- expired             → "Trial expired — pick a plan" → /account/billing/select-plan
- paid                → planBilling.display_name (quiet)
- complimentary       → "Complimentary Pro" (accent, no CTA)
- past_due            → "Payment failed — update card" → /account/billing
- canceled            → "Reactivate" → /account/billing/select-plan
- null                → hidden

Uses existing design-system tokens only (text-info/bg-info-dim,
text-warning/bg-warning-dim, text-danger/bg-danger-dim, text-accent/
bg-accent-dim, text-muted-foreground/bg-elevated). Clickable variants
render as react-router-dom <Link>s and are keyboard-focusable with an
accent focus-visible ring. Mobile collapses the label to a clock icon
with a title attribute carrying the full text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:06:09 -04:00
53dd5f13e5 feat(onboarding): add wizard Steps 2 (PSA) and 3 (Invite team)
Step 2 (`/welcome/step-2`): four PSA tiles (ConnectWise / Autotask /
HaloPSA / No PSA yet). Selecting a real PSA reveals a quiet inline
"Connect now" link to `/account/integrations` — credential entry is
intentionally OUT of the wizard. Continue persists `primary_psa`,
Skip advances without writing.

Step 3 (`/welcome/step-3`): up to 10 email/role rows (default 3,
"+ Add another" extends, role defaults to Tech / engineer with
Viewer alt). "Send invites and continue" filters empty rows, POSTs
`/accounts/me/invites/bulk`, then PATCHes onboarding-step
`{step:3, action:"complete"}` and navigates to `/?welcome=true`.
Per-row `failed[]` errors render inline next to the email and the
wizard does NOT auto-advance — user can fix-and-retry or click
"Continue anyway" to mark step complete. Empty + Skip / empty + Send
both advance without sending.

Adds `accountsApi.bulkInvite` and registers `/welcome/step-{2,3}`
in the router. Vitest: 5 named tests (selecting PSA persists,
Skip advances without primary_psa, valid emails create invites,
partial-failure inline error, empty + Skip no-op) + 5 incidental
coverage tests. tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:02:00 -04:00
9b517d3320 feat(onboarding): add welcome wizard scaffold + Step 1 (Your shop)
Lays the groundwork for the post-signup welcome wizard (Phase 2,
Task 38). Authed users hitting /welcome are routed to the next
incomplete step based on users.onboarding_step_completed +
users.onboarding_dismissed; refresh resumes correctly because every
navigation persists state server-side first.

Backend:
- Expose onboarding_step_completed (Optional[int]) and
  onboarding_dismissed (bool) on UserResponse so /auth/me drives
  client-side routing without a separate fetch.

Frontend:
- WelcomeRouter handles the /welcome decision table (dismissed → /,
  completed >=3 → /, else next step).
- WelcomeStep1 renders the "Your shop" form (company name pre-filled
  from accounts.name, team size 1-2/3-5/6-10/11-25/26+, role
  Owner/Lead Tech/Tech/Other). Continue PATCHes /users/me/onboarding-step
  with action=complete; Skip-this-step PATCHes action=skip; Skip-the-rest
  POSTs /users/me/onboarding-dismiss-rest. Each action refreshes the
  auth store before navigating so the router resumes correctly on the
  next visit.
- onboardingApi.updateStep + dismissRest (typed against backend
  OnboardingStepRequest/Response schemas).
- Routes mounted inside AppLayout so EmailVerificationBanner persists
  above each step per spec.
- 11 vitest cases covering the routing decision table + Continue / Skip
  / Skip-the-rest / persist-failure paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:54:10 -04:00
7d939a4acf feat(auth): add email verification banner, wall, /verify-email page
Wires up the soft 7-day email-verification grace period UX.

- EmailVerificationBanner now uses the design-system warning tokens
  (bg-warning-dim / text-warning) and hides itself once the grace
  period expires, so the wall takes over without double-messaging.
- EmailVerificationWall picks up data-testids on the resend and
  sign-out CTAs.
- VerifyEmailPage gains a single-fire useRef guard (so React 19
  strict-mode double-invoke doesn't burn the token), an
  already-verified short-circuit that skips the API call, success
  state with auth-store refresh + redirect to /?verified=1, and
  an error state with a resend CTA.

Tests: banner hides past day-7, banner resend triggers API call,
verify success refreshes + redirects, verify short-circuits when
already verified, single-fire guard holds across remount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:46:43 -04:00
39e85c9770 feat(auth): add /accept-invite page + lookup endpoint
Adds the invitee-side flow for self-serve signup Phase 2 (Task 36):

Backend
- Public GET /accounts/invites/{code}/lookup returns
  {account_name, inviter_name, invited_email, role} for a valid invite,
  404 invite_invalid_or_expired_or_revoked otherwise (collapses unknown /
  expired / revoked / used into one anti-enumeration response). Mounted
  in a new account_invite_lookup endpoints module on the public route
  list, uses get_admin_db (BYPASSRLS) since the caller has no tenant.
- OAuthCallbackPayload gains optional account_invite_code + invited_email.
  _sign_in_or_register honors them: a new OAuth user with a valid invite
  joins the invited account (no personal account, no Pro trial), the
  invite is marked used, and OAuth-profile-email vs invite-email mismatch
  raises invite_email_mismatch (matching the email+password register
  contract).

Frontend
- New public route /accept-invite -> AcceptInvitePage. Reads ?code=,
  calls inviteApi.lookupAccountInvite, renders "Join {account} on
  ResolutionFlow" with the invited email locked (rendered as a div, not
  an input), three sign-in options (set password, Google, Microsoft),
  and a clear "ask {inviter} to resend" + mailto: fallback for invalid
  codes.
- OAuth state for invitees is base64url(JSON({csrf, accountInviteCode,
  invitedEmail})). OAuthCallbackPage decodes both shapes, forwards the
  invite fields to the backend, and surfaces invite_email_mismatch /
  invite_invalid_or_expired_or_revoked errors with friendly text.
  Successful invite-OAuth lands on /?welcome=teammate (suppresses the
  welcome wizard for invitees per spec).
- UserCreate type + invite/auth API clients extended for the new fields.

Tests
- Backend: invite lookup happy path + four invalid-state collapse, OAuth
  callback links invite when supplied + rejects on email mismatch.
- Frontend Vitest: AcceptInvitePage renders account name + locked email
  + accept buttons; resend message + mailto on invalid code.

All 43 backend auth/account/invite/email-verification tests green;
frontend Vitest 120/120 green; tsc -b clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:34:22 -04:00
70ab1f34d4 feat(auth): redesign /register with OAuth buttons; hide invite-code under flag
Phase 2 Task 35. Adds OAuth Google/Microsoft sign-in to the register flow,
gated on the public SELF_SERVE_ENABLED flag, and hides the legacy invite-code
field when self-serve is on.

- New `useAppConfig` hook + `configApi`. One-shot module-cached fetch of
  `GET /api/v1/config/public`; falls back to `VITE_SELF_SERVE_ENABLED` env
  var (default false) if the endpoint is unreachable.
- New `OAuthCallbackPage` mounted at `/auth/google/callback` and
  `/auth/microsoft/callback` (public, NOT inside ProtectedRoute). Posts the
  authorization code to the backend, persists tokens, hydrates the auth
  store via fetchUser, and redirects to `/welcome` (new) or `/` (returning).
- `RegisterPage` now renders OAuth buttons + email/password divider when
  `self_serve_enabled` is true and only emits buttons for providers the
  backend reports as configured. Invite-code field hidden in that mode.
  Captures `?plan=pro` into `localStorage.rf-intended-plan` on mount.
- `authApi` gains `googleCallback(code)` / `microsoftCallback(code)`.
- `frontend/.env.example` + `frontend/Dockerfile` document and bake the
  three new VITE_* build-time variables (Lesson 60: Vite needs ARG+ENV).
- Vitest coverage for the three required cases plus the plan-param capture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 21:15:25 -04:00
ece82225f2 feat(billing): add FeatureGate, UpgradePrompt, EmailVerificationGate components
Three drop-in gating components for the self-serve signup flow.

- FeatureGate reads useFeature(flag) and renders children when enabled,
  else a fallback (default UpgradePrompt). UX-only — security boundary
  remains require_feature on the backend.
- UpgradePrompt resolves a feature key to display name + required plan
  via an inline catalog and links to /account/billing/select-plan.
- EmailVerificationGate gates protected content behind a 6-day grace
  period; renders a minimal EmailVerificationWall (resend + sign out)
  on Day 7+ unverified. Wall design will be refined in Task 37.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 21:01:53 -04:00
0b5ed9aa10 feat(billing): add useFeature, useFeatureLimit, useTrialBanner hooks
Phase 2 Task 33. Components can now ask "is this feature on?", "how many
sessions left?", and "what stage is the trial in?" without re-implementing
the read against useBillingStore.

- useFeature(flagKey): boolean — reads enabledFeatures from store
- useFeatureLimit(field): { used, limit, percentage, isAtLimit, isLoading }
  with non-blocking 60s module-level cache and graceful 404 degradation
- useTrialBanner(): derives stage from subscription status + trial countdown,
  returns null on initial load to prevent flicker
- usageApi.getCount(field) — calls /api/v1/usage/{field}; backend endpoint
  is not yet implemented (planned), so the hook degrades to used=0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:55:58 -04:00
7a9cb4b03b feat(billing): add useBillingStore and /billing/state integration
T32: Single frontend source of truth for subscription / plan / feature
state. New Zustand `useBillingStore` fetches `/billing/state` (auto-fetch
on login via authStore, reset on logout), exposes `refetch` for
post-Checkout refresh, and is supported by a `useBillingPoll` hook
that re-fetches every 60s while authenticated. The new `billingApi`
client transforms the snake_case backend payload to camelCase at a
single boundary so the rest of the frontend never sees `plan_billing`
or `enabled_features`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:47:50 -04:00
80baf89b00 feat(config): add SELF_SERVE_ENABLED flag + GET /config/public
Phase 2 Task 31. Single flag now controls whether the public-facing
self-serve flow is exposed.

- New public endpoint GET /api/v1/config/public returns
  {self_serve_enabled, oauth_providers}. oauth_providers includes
  "google" if GOOGLE_CLIENT_ID is set and "microsoft" if MS_CLIENT_ID
  is set. No auth required; consumed once by the frontend at load.
- POST /auth/register: when SELF_SERVE_ENABLED=true the platform
  invite-code requirement is bypassed even with REQUIRE_INVITE_CODE=true.
  invite_code stays in the schema for backward compat and still applies
  when supplied. With the flag off, the gate behaves exactly as before.
- Adds backend/app/schemas/config.py with PublicConfigResponse and
  registers the new router in the public/unauthenticated section.
- Adds 3 integration tests in tests/test_config_public.py covering the
  flag round-trip, the regression case (flag off keeps the 400), and
  the new behavior (flag on bypasses the gate, creates user + Pro trial).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:38:50 -04:00
d05b475a41 feat(admin): extend /admin/plan-limits to manage plan_billing fields
Task 30 of self-serve signup Phase 2. Super-admins can now manage Stripe
IDs, display names, prices, and public/archived flags via the existing
admin plan-limits endpoints.

- GET /admin/plan-limits now outer-joins plan_billing and returns
  merged PlanLimitWithBillingResponse rows. Plans without a
  plan_billing row return None for the billing fields.
- PUT /admin/plan-limits accepts the new optional billing fields and
  upserts plan_billing in the same transaction. If no plan_billing
  row exists for the plan and the body includes any billing field, a
  row is created (display_name defaults to plan.capitalize() when
  omitted; display_name is never NULLed out on an existing row).
- After commit, the handler queries account_ids on the affected plan
  and calls BillingService.invalidate_billing_cache(account_ids).
  This is a no-op stub today (logs only) — there's no in-process
  billing cache yet. TODO comment marks the wire-up point.
- 3 new integration tests cover GET-with-billing-present, PUT creating
  a plan_billing row, and the invalidation hook being awaited with a
  list of account_ids.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:35:10 -04:00
694279f89e feat(sales): add POST /sales-leads public endpoint
Phase 2 Task 29 — public Talk-to-Sales submission endpoint.

- New POST /api/v1/sales-leads (public, no auth, rate-limited 5/hour per IP).
- Inserts a sales_leads row, fires best-effort notification email and
  PostHog server-side capture; failures are logged but never fail the
  request.
- New EmailService.send_sales_lead_notification static method.
- New SALES_LEAD_RECIPIENT_EMAIL setting (defaults to sales@resolutionflow.com).
- Schemas: SalesLeadCreate / SalesLeadCreateResponse with literal source enum.
- Tests: happy path (row + email), email-failure resilience, and rate-limit
  enforcement (re-enables the slowapi limiter for the rate-limit assertion
  since DEBUG=true disables it by default in tests).

PostHog server-side instrumentation point is wired in but no-ops gracefully
until app.core.analytics.posthog exists — turning it on is a one-line
change when the backend SDK is configured.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:12:03 -04:00
16f5e4ce05 feat(onboarding): add PATCH /users/me/onboarding-step + dismiss-rest
Persists welcome-wizard Step 1/2/3 progress for self-serve signup Phase 2.
PATCH validates step cannot decrease, ignores `data` on action="skip", and
is idempotent on re-PATCH of the same step. POST /users/me/onboarding-dismiss-rest
backs the wizard's "Skip the rest" button.

Both routes added to _EMAIL_VERIFICATION_ALLOWLIST and _SUBSCRIPTION_GUARD_ALLOWLIST
so the wizard runs before email verification and during the trial. 4 integration
tests cover field writes, skip semantics, decrease guard, and dismiss-rest.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:04:43 -04:00
2f8ec3775e feat(billing): add BillingService.open_customer_portal + GET endpoint
Authed users can now request a Stripe-hosted Customer Portal URL for card
updates and cancellation via GET /api/v1/billing/portal-session. The path is
already in both _SUBSCRIPTION_GUARD_ALLOWLIST and _EMAIL_VERIFICATION_ALLOWLIST
so canceled or unverified-past-grace users can still update billing.

- Returns 503 with {"error": "stripe_not_configured"} when STRIPE_SECRET_KEY unset.
- Returns 400 with {"error": "no_stripe_customer"} when account has no
  stripe_customer_id (must complete checkout first).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 20:00:08 -04:00
f918b766b0 feat: self-serve signup backend (Phase 1) (#161)
All checks were successful
CI / frontend (push) Successful in 5m16s
Mirror to GitHub / mirror (push) Successful in 6s
CI / e2e (push) Successful in 10m22s
CI / backend (push) Successful in 10m55s
2026-05-06 23:46:34 +00:00
fbb41e789c docs(handoff): capture Phase 1 backend completion + followups
All checks were successful
Mirror to GitHub / mirror (push) Successful in 5s
CI / frontend (pull_request) Successful in 6m0s
CI / backend (pull_request) Successful in 11m15s
CI / e2e (pull_request) Successful in 10m4s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
97d36dd400 test(kb-accelerator): downgrade kb_setup user to free plan
The kb_setup fixture asserts free-plan quota numbers (lifetime_conversions_limit=3),
but Phase 1 conftest seeds test_user on Pro. Downgrade explicitly inside kb_setup
to preserve the original test intent without affecting other suites.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
f26f468878 feat(billing): pilot user backfill — set existing accounts to complimentary
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
79942c3fd3 feat(billing): add GET /billing/state aggregating subscription + plan + features
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
4768ae0648 feat(invites): add bulk-create and soft-revoke invite endpoints
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
e54d6c586a feat(invites): wire EmailService.send_account_invite_email into create handler
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
86893562b9 feat(auth): auto-send verification email on register; enforce invite email match
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
b0708ed650 feat(auth): guard login/password paths against OAuth-only users
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
2ef2350de7 feat(auth): add Microsoft OAuth callback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
f4606f073a feat(auth): add Google OAuth callback with oauth_identities linking
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
9b709488d9 feat(billing): extend Stripe webhook stub with concrete event handlers
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
18180bc57f feat(billing): apply_subscription_event with stripe_events idempotency
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
f683bb5720 feat(billing): add /billing/checkout-session via BillingService
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
9851d56633 feat(billing): add BillingService.start_trial; wire into /auth/register
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
519c7eb5ce feat(deps): add require_verified_email_after_grace guard
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
9ec208f6e7 feat(deps): add require_active_subscription guard with allowlist
Mounts on Pro routers (trees, sessions, scripts, FlowPilot, etc.) and
returns 402 with structured detail when an account's subscription is
missing or locked. Allowlist bypasses billing/account/auth flows so
users can recover from a lapsed subscription.

Conftest now seeds a default Pro/active Subscription on test_user and
test_admin (delete-then-insert because the register endpoint already
creates a free/active sub by default). Two existing tests adapted to
the new seeded plan; tenant-isolation tests seed Subscription rows for
the accounts they create directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
cfe0e6cae6 refactor(deps): remove trial auto-downgrade; expiry now non-mutating per spec
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
e3f5ed4985 feat(billing): add complimentary status, fix is_paid, add has_pro_entitlement
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
5105eaf529 feat(billing): add sales_leads and stripe_events tables
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
974b188c1e feat(billing): add plan_billing sibling table for Stripe + catalog metadata
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
a28b635b19 feat(invites): add revoked_at + email_sent_at to account_invites
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
50e7763380 feat(onboarding): add accounts.team_size_bucket and primary_psa for wizard
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
b3ed76c203 feat(onboarding): add users.role_at_signup and onboarding_step_completed
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
453ba3fefc feat(auth): make users.password_hash nullable for OAuth-only accounts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
143c979975 feat(auth): add oauth_identities table for Google/Microsoft sign-in
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
ab0d40c1e2 docs(plan): self-serve signup & onboarding implementation plans
Adds two phase plans alongside the spec at
docs/superpowers/specs/2026-05-05-self-serve-signup-onboarding-design.md:

- Phase 1 (backend foundation, 26 tasks across 8 sub-phases A-H):
  schema migrations, subscription model + new guards, BillingService,
  Stripe webhook handler extension, OAuth callbacks, email verification
  auto-send + email-match enforcement, account-invite extensions,
  GET /billing/state, pilot user backfill. Step-by-step granularity
  with full code blocks per writing-plans skill.

- Phase 2 (frontend + cutover, 21 tasks across 7 sub-phases I-O):
  Phase-1-deferred endpoints, useBillingStore + hooks + gating
  components, register redesign + OAuth buttons + accept-invite,
  welcome wizard, dashboard redesign, pricing page + contact-sales,
  beta-signup deprecation, cutover. Higher-altitude — defines
  contracts, acceptance criteria, integration tests; leaves
  component-detail decisions to implementer.

Each phase ends in a mergeable PR. Cutover is gated behind
SELF_SERVE_ENABLED + VITE_SELF_SERVE_ENABLED. Execution deferred to
a future session.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:30 -04:00
278b9342b4 docs(spec): self-serve signup & onboarding design
Adds docs/superpowers/specs/2026-05-05-self-serve-signup-onboarding-design.md.
Six-section design for opening ResolutionFlow to public self-serve registration
with a 14-day reverse trial on Pro, Stripe-backed billing, sales-assist
Enterprise lane, and a hybrid welcome wizard + dashboard onboarding.

Reuses existing infrastructure (subscriptions, plan_limits, feature_flags,
plan_feature_defaults, account_feature_overrides, account_invites,
email_verification_tokens, /admin/plan-limits, /admin/feature-flags,
/accounts/me/transfer-ownership, /webhooks/stripe stub). New schema is
intentionally small: oauth_identities, plan_billing (sibling to plan_limits),
sales_leads, stripe_events, plus column additions for OAuth identity model
nullability, wizard step state, and pilot-account complimentary status.

Replaces deps.py:109 trial auto-downgrade with a non-mutating computed
expiry check enforced by a new require_active_subscription dep. Adds a
sibling require_verified_email_after_grace dep to enforce the 7-day email
verification grace at the API layer (frontend wall is UX over the same rule).

Defers promo codes from v1. No new combined /admin/plans surface — existing
admin endpoints handle plan/feature configuration with extended response
shape.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 19:14:29 -04:00
a8b22cfa0b feat: post-PR-159 UI cleanup — sidebar IA + account redesign (#160)
All checks were successful
CI / frontend (push) Successful in 5m11s
Mirror to GitHub / mirror (push) Successful in 6s
CI / backend (push) Successful in 10m19s
CI / e2e (push) Successful in 10m31s
2026-05-06 23:14:16 +00:00
b544a7a462 test(e2e): update account page heading assertion to match redesign
All checks were successful
Mirror to GitHub / mirror (push) Successful in 7s
CI / frontend (pull_request) Successful in 5m14s
CI / backend (pull_request) Successful in 9m57s
CI / e2e (pull_request) Successful in 10m21s
8612042 dropped the static "Account Management" heading in favor of the
account name (rendered as a dynamic h1). Switch the smoke test to the
"Settings" SectionLabel — a stable h2 that survives the redesign.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-06 18:54:53 -04:00
07a3f01184 fix(qa): ISSUE-001 — fall back to members.length when usage.user_count is missing
Some checks failed
Mirror to GitHub / mirror (push) Successful in 12s
CI / frontend (pull_request) Successful in 5m30s
CI / e2e (pull_request) Failing after 11m2s
CI / backend (pull_request) Successful in 14m47s
The /subscription endpoint returns usage as {tree_count, session_count_this_month}
without user_count, so the Seats UsageRow rendered as " / ∞" (blank current value).
The TS type declared user_count: number, hiding this API/type drift; the old
card-stack design hid it visually because each stat had its own border. The new
flat layout surfaced the gap.

Owners get a fallback to members.length (already fetched). Non-owners can't
fetch members and don't need seat-count info, so the row hides entirely for
them. Verified live: owner now sees Seats 2 / ∞.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 01:02:44 -04:00