Skip to content

fix(billing): redirect free workspace to checkout instead of alerting#44

Merged
ABB65 merged 1 commit into
mainfrom
fix/billing-checkout-redirect
May 13, 2026
Merged

fix(billing): redirect free workspace to checkout instead of alerting#44
ABB65 merged 1 commit into
mainfrom
fix/billing-checkout-redirect

Conversation

@ABB65
Copy link
Copy Markdown
Member

@ABB65 ABB65 commented May 13, 2026

Summary

  • A free workspace clicking Connect repository hit POST /api/workspaces/:id/projects and the server correctly returned 402 { requiresCheckout: true } — but ConnectRepoDialog only surfaced a toast.error, never opened the plan-selection modal. User saw an alert and got stuck.
  • Same shape applied to every other workspace-scoped composable: useChat, useBranches, useContentEditor, useMembers, ContentCollectionView, etc. all funnel API errors through resolveApiError → toast.error. A billing-locked workspace (trial_expired / grace_expired / canceled_expired) emitted by server/middleware/03.billing.ts would silently toast across the app.
  • Fix centralizes the redirect in one place:
    • New usePlanModal composable — useState-backed global open state.
    • resolveApiError opens the modal as a side effect when status is 402 + requiresCheckout. All existing catch sites now route locked workspaces through checkout for free.
    • default layout mounts OrganismsPlanSelectionModal (previously only mounted in workspace layout / settings panels / error.vue).
    • workspace layout swaps its local planModalOpen ref for the shared composable so TrialBanner + middleware 402s drive the same modal.
    • ConnectRepoDialog closes its own dialog on 402 + requiresCheckout so the plan modal lands cleanly instead of stacking.

Why this is the right shape

resolveApiError is already the single client-side entry point for API errors. Adding the side effect there (instead of patching every composable or introducing a custom $fetch instance) keeps the change small and ensures any new call site that follows the existing pattern is covered without extra wiring.

Test plan

  • Sign up → land on free workspace → click Connect repository → after picking a repo, Connect opens PlanSelectionModal (not a toast). The repo dialog is closed.
  • Choose Starter — redirected to Polar checkout.
  • Subscribed/trial workspace: connect flow still works end-to-end (no regression).
  • Trial-expired workspace: any workspace-scoped action (send message, save content, invite member, merge branch) opens the plan modal.
  • Community / on-premise profiles: hasManagedBilling is false → modal is not mounted; UI degrades to existing toast behavior.

A free workspace clicking "Connect repository" hit `POST /api/workspaces/:id/projects`,
got a 402 with `requiresCheckout: true`, but the dialog only surfaced a toast — the
plan-selection modal was never opened, leaving the user stuck.

Make 402 + `requiresCheckout` open the plan modal globally via a shared
`usePlanModal` composable, triggered from `resolveApiError` as a side effect so
every existing catch site (ConnectRepoDialog, useChat, useBranches,
useContentEditor, useMembers, ContentCollectionView…) routes locked workspaces
through the checkout flow rather than dying as a toast.

- New `usePlanModal` composable backed by `useState` for SSR-safe shared state
- `resolveApiError` opens the modal on 402 + `requiresCheckout`
- `default` layout mounts `PlanSelectionModal` (was only on `workspace` layout)
- `workspace` layout switches to the same global state for consistency
- `ConnectRepoDialog` closes its own dialog on 402 so the modal lands on top
@ABB65 ABB65 merged commit 98d96f5 into main May 13, 2026
1 check passed
@ABB65 ABB65 deleted the fix/billing-checkout-redirect branch May 13, 2026 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant