diff --git a/app/components/organisms/ConnectRepoDialog.vue b/app/components/organisms/ConnectRepoDialog.vue index d84f053..fa36e5b 100644 --- a/app/components/organisms/ConnectRepoDialog.vue +++ b/app/components/organisms/ConnectRepoDialog.vue @@ -129,6 +129,18 @@ async function connectRepo() { open.value = false } catch (e: unknown) { + const status = (e as { statusCode?: number, data?: { statusCode?: number, requiresCheckout?: boolean } }).statusCode + ?? (e as { data?: { statusCode?: number } }).data?.statusCode + const requiresCheckout = (e as { data?: { requiresCheckout?: boolean } }).data?.requiresCheckout + + // Plan modal is opened globally by resolveApiError. Close this dialog so + // the user lands on the plan picker, not a stacked surface. + if (status === 402 && requiresCheckout) { + open.value = false + resolveApiError(e, t('projects.connected_error')) + return + } + toast.error(resolveApiError(e, t('projects.connected_error'))) } finally { diff --git a/app/composables/usePlanModal.ts b/app/composables/usePlanModal.ts new file mode 100644 index 0000000..46f4c49 --- /dev/null +++ b/app/composables/usePlanModal.ts @@ -0,0 +1,22 @@ +/** + * Global plan-selection modal state. + * + * Mounted once per layout (`default`, `workspace`) and triggered from + * anywhere via `usePlanModal().show()`. The primary trigger is the + * client-side 402 + `requiresCheckout` handler in `resolveApiError`, + * so any API call that hits a billing-locked state surfaces the + * checkout flow instead of dying as a toast. + */ +export function usePlanModal() { + const open = useState('plan-modal-open', () => false) + + function show() { + open.value = true + } + + function hide() { + open.value = false + } + + return { open, show, hide } +} diff --git a/app/layouts/default.vue b/app/layouts/default.vue index 768f5e2..7bc093a 100644 --- a/app/layouts/default.vue +++ b/app/layouts/default.vue @@ -2,6 +2,8 @@ const { t } = useContent() const { open: commandPaletteOpen } = useCommandPalette() const { toggle: toggleMobileSidebar } = useMobileSidebar() +const { open: planModalOpen } = usePlanModal() +const deployment = useDeployment() diff --git a/app/layouts/workspace.vue b/app/layouts/workspace.vue index 956e7d4..c2d52b1 100644 --- a/app/layouts/workspace.vue +++ b/app/layouts/workspace.vue @@ -1,6 +1,6 @@