diff --git a/apps/studio/pages/partners/stripe/projects/login.tsx b/apps/studio/pages/partners/stripe/projects/login.tsx
index 4f1d396404e..dd19e0ca54a 100644
--- a/apps/studio/pages/partners/stripe/projects/login.tsx
+++ b/apps/studio/pages/partners/stripe/projects/login.tsx
@@ -10,6 +10,7 @@ import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import {
InterstitialAccountRow,
+ InterstitialActionError,
InterstitialLayout,
LogoPair,
PartnerLogo,
@@ -27,7 +28,7 @@ import type { NextPageWithLayout } from '@/types'
const PAGE_TITLE = buildStudioPageTitle({ section: 'Authorize Stripe Projects', brand: 'Supabase' })
-const StripeProjectsLoginPage: NextPageWithLayout = () => {
+export const StripeProjectsLoginPage: NextPageWithLayout = () => {
const router = useRouter()
const { ar_id } = useParams()
const signOut = useSignOut()
@@ -48,7 +49,14 @@ const StripeProjectsLoginPage: NextPageWithLayout = () => {
mutate: confirmAccountRequest,
isPending: isConfirmationPending,
isSuccess: isConfirmationSuccess,
- } = useConfirmAccountRequestMutation()
+ error: confirmationMutationError,
+ reset: resetConfirmationError,
+ } = useConfirmAccountRequestMutation({
+ onError: () => undefined,
+ })
+ const confirmationError = confirmationMutationError
+ ? `Failed to authorize Stripe Projects: ${confirmationMutationError.message}`
+ : undefined
useEffect(() => {
if (!router.isReady) return
@@ -60,6 +68,7 @@ const StripeProjectsLoginPage: NextPageWithLayout = () => {
const handleApprove = async () => {
if (!ar_id || isConfirmationPending) return
+ resetConfirmationError()
confirmAccountRequest({ arId: ar_id })
}
@@ -155,6 +164,7 @@ const StripeProjectsLoginPage: NextPageWithLayout = () => {
+
)}
@@ -195,6 +205,7 @@ const StripeProjectsLoginPage: NextPageWithLayout = () => {
+
)}
diff --git a/apps/studio/tests/pages/stripe-projects-login.test.tsx b/apps/studio/tests/pages/stripe-projects-login.test.tsx
new file mode 100644
index 00000000000..be2c55fdf11
--- /dev/null
+++ b/apps/studio/tests/pages/stripe-projects-login.test.tsx
@@ -0,0 +1,81 @@
+import { screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { HttpResponse } from 'msw'
+import { toast } from 'sonner'
+import { beforeEach, expect, test, vi } from 'vitest'
+
+import { StripeProjectsLoginPage } from '@/pages/partners/stripe/projects/login'
+import { customRender } from '@/tests/lib/custom-render'
+import { addAPIMock, type APIErrorBody } from '@/tests/lib/msw'
+
+const mocks = vi.hoisted(() => ({
+ routerPush: vi.fn(),
+}))
+
+vi.mock('@tanstack/react-query', async (importOriginal) => {
+ const actual = await importOriginal()
+ return {
+ ...actual,
+ useQuery: () => ({
+ data: {
+ email: 'alex@example.com',
+ email_matches: true,
+ linked_organization: { name: 'Acme', slug: 'acme' },
+ },
+ isPending: false,
+ isSuccess: true,
+ isError: false,
+ error: undefined,
+ }),
+ }
+})
+
+vi.mock('common', async (importOriginal) => {
+ const actual = await importOriginal()
+ return { ...actual, useParams: () => ({ ar_id: 'request-id' }) }
+})
+
+vi.mock('next/router', () => ({
+ useRouter: () => ({ isReady: true, push: mocks.routerPush }),
+}))
+
+vi.mock('sonner', () => ({
+ toast: { error: vi.fn() },
+}))
+
+vi.mock('@/lib/auth', () => ({
+ useSignOut: () => vi.fn(),
+}))
+
+vi.mock('@/lib/profile', () => ({
+ useProfileNameAndPicture: () => ({
+ username: 'alex',
+ primaryEmail: 'alex@example.com',
+ avatarUrl: undefined,
+ }),
+}))
+
+beforeEach(() => {
+ vi.clearAllMocks()
+})
+
+test('shows confirmation failures inline and keeps authorization available', async () => {
+ const user = userEvent.setup()
+ addAPIMock({
+ method: 'post',
+ path: '/platform/stripe/projects/provisioning/account_requests/:id/confirm',
+ response: () =>
+ HttpResponse.json({ message: 'Confirmation failed' }, { status: 500 }),
+ })
+
+ customRender()
+
+ await user.click(screen.getByRole('button', { name: 'Authorize Stripe Projects' }))
+
+ const errorMessage = await screen.findByText(
+ 'Failed to authorize Stripe Projects: Confirmation failed'
+ )
+ expect(errorMessage).toHaveAttribute('role', 'alert')
+ expect(toast.error).not.toHaveBeenCalled()
+ expect(screen.getByRole('button', { name: 'Authorize Stripe Projects' })).toBeEnabled()
+})