From 022b374f2dbd7dab46b3fd5aab92d827b7fa4059 Mon Sep 17 00:00:00 2001 From: Danny White Date: Mon, 3 Aug 2026 14:48:46 +1000 Subject: [PATCH] show Stripe Projects errors inline (#48472) ## What kind of change does this PR introduce? Bug fix. ## What is the current behavior? Stripe Projects confirmation failures only appear in a toast. The existing **Unable to load authorization** Admonition is a separate error state shown when the account request itself cannot be loaded. ## What is the new behavior? Confirmation failures remain visible below the authorisation actions, clear on retry, and do not also trigger a toast. They use the shared `InterstitialActionError`. | Before | After | | --- | --- | | Authorize Stripe Projects
Supabase | Authorize Stripe Projects
Supabase | ## To test These instructions visually check the after state on this branch. Opening an invalid `ar_id` without the hardcodes only exercises the existing load-error Admonition, which this PR does not change. ### After on this branch 1. In `apps/studio/pages/partners/stripe/projects/login.tsx`, replace the `confirmationError` assignment with: ```tsx const confirmationError = 'Failed to authorize Stripe Projects: Test error' ``` 2. In the same file, replace the block beginning with `const linkedOrg` and ending with `interstitialDescription` with: ```tsx const linkedOrg = { name: 'Example Organization' } const emailMatches = true const displayName = primaryEmail ?? username ?? 'reviewer@example.com' const isPending = false const isConfirmed = false const isConfirming = false const isError = false const showAuthorizationState = true const interstitialDescription = 'This will create an organization on your behalf in Supabase' ``` 3. While signed in locally, open `http://localhost:8082/partners/stripe/projects/login?ar_id=test`. 4. Confirm the error appears below **Authorize Stripe Projects** and **Cancel**. No real Stripe request is required. 5. Revert both temporary edits. ### Before on master (optional) 1. Check out `master`. 2. In `apps/studio/pages/partners/stripe/projects/login.tsx`, replace the block beginning with `const linkedOrg` and ending with `interstitialDescription` with the same block from step 2 above. Do not add `confirmationError`. 3. While signed in locally, open `http://localhost:8082/partners/stripe/projects/login?ar_id=test`. 4. Click **Authorize Stripe Projects**. 5. Confirm the failed confirmation appears in a toast beginning **Failed to confirm account request**. 6. Revert the temporary edit before changing branches. ## Additional context Stacked on #48471. ## Summary by CodeRabbit ## Summary by CodeRabbit * **Bug Fixes** * Authorization errors during Stripe Projects login are now displayed inline in both authorization flows. * Authorization remains available after a failed confirmation attempt. * Failed authorization requests no longer trigger an additional toast notification. * Previous errors are cleared when retrying authorization. --- .../pages/partners/stripe/projects/login.tsx | 15 +++- .../pages/stripe-projects-login.test.tsx | 81 +++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 apps/studio/tests/pages/stripe-projects-login.test.tsx 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() +})