From 1797842ee52ff4b8bc2703fca12872c9331bf5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Barroso?= Date: Wed, 8 Apr 2026 19:11:28 +0200 Subject: [PATCH] refactor(stripe projects): always create a new org (#44634) ## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Refactor based on https://github.com/supabase/platform/pull/31325 ## What is the current behavior? We presented a page to Stripe users to let them either pick an existing org or create one. ## What is the new behavior? We're forcing them to create a new one (or show that there was one already linked). - It also adds the option to sign out when there's a conflict. Fixes https://linear.app/supabase/issue/API-963/add-a-button-to-logout-from-the-page-you-must-be-logged-in-as-x-to - And adds the link to root from the logo. ## Summary by CodeRabbit * **New Features** * Added organization preview creation endpoint for billing workflows. * **Bug Fixes** * Removed organization-picking flow from Stripe Projects login; users now proceed directly with confirmation. * Added a "Sign out" button on error pages. * **Refactor** * Removed a legacy billing partner option. * Made the Supabase logo clickable for quick navigation. --------- Co-authored-by: Ivan Vasilov --- .../layouts/APIAuthorizationLayout.tsx | 25 +-- .../stripe-projects-confirm-mutation.ts | 10 +- .../pages/partners/stripe/projects/login.tsx | 181 ++---------------- packages/api-types/types/platform.d.ts | 18 +- 4 files changed, 40 insertions(+), 194 deletions(-) diff --git a/apps/studio/components/layouts/APIAuthorizationLayout.tsx b/apps/studio/components/layouts/APIAuthorizationLayout.tsx index 59c16b0d295..80265cd0a12 100644 --- a/apps/studio/components/layouts/APIAuthorizationLayout.tsx +++ b/apps/studio/components/layouts/APIAuthorizationLayout.tsx @@ -1,5 +1,6 @@ import { useTheme } from 'next-themes' import Image from 'next/legacy/image' +import Link from 'next/link' import type { PropsWithChildren } from 'react' import { Separator } from 'ui' @@ -29,17 +30,19 @@ export const APIAuthorizationLayout = ({
- Supabase - Supabase Logo + + Supabase + Supabase Logo +
diff --git a/apps/studio/data/partners/stripe-projects-confirm-mutation.ts b/apps/studio/data/partners/stripe-projects-confirm-mutation.ts index e0ba1a55615..dbc5a55e71f 100644 --- a/apps/studio/data/partners/stripe-projects-confirm-mutation.ts +++ b/apps/studio/data/partners/stripe-projects-confirm-mutation.ts @@ -6,22 +6,16 @@ import type { ResponseError, UseCustomMutationOptions } from '@/types' type ConfirmAccountRequestVariables = { arId: string - organizationId?: number - organizationName?: string } -async function confirmAccountRequest({ - arId, - organizationId, - organizationName, -}: ConfirmAccountRequestVariables) { +async function confirmAccountRequest({ arId }: ConfirmAccountRequestVariables) { if (!arId) throw new Error('Account request ID is required') const { data, error } = await post( '/platform/stripe/projects/provisioning/account_requests/{id}/confirm', { params: { path: { id: arId } }, - body: { organization_id: organizationId, organization_name: organizationName }, + body: {}, } ) diff --git a/apps/studio/pages/partners/stripe/projects/login.tsx b/apps/studio/pages/partners/stripe/projects/login.tsx index 5d6ce68d1de..a2099a1a19e 100644 --- a/apps/studio/pages/partners/stripe/projects/login.tsx +++ b/apps/studio/pages/partners/stripe/projects/login.tsx @@ -2,41 +2,28 @@ import { useQuery } from '@tanstack/react-query' import { useParams } from 'common' import Head from 'next/head' import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' +import { useEffect } from 'react' import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Button, - Input_Shadcn_, LogoLoader, WarningIcon, } from 'ui' import { APIAuthorizationLayout } from '@/components/layouts/APIAuthorizationLayout' -import { OrganizationSelector } from '@/components/ui/org-selector' -import { useOrganizationsQuery } from '@/data/organizations/organizations-query' import { useConfirmAccountRequestMutation } from '@/data/partners/stripe-projects-confirm-mutation' -import { - accountRequestQueryOptions, - type AccountRequestDetails, -} from '@/data/partners/stripe-projects-query' +import { accountRequestQueryOptions } from '@/data/partners/stripe-projects-query' import { withAuth } from '@/hooks/misc/withAuth' import { useSignOut } from '@/lib/auth' -type OrgSummary = NonNullable - const StripeProjectsLoginPage = () => { const router = useRouter() const { ar_id } = useParams() const signOut = useSignOut() - const [selectedOrg, setSelectedOrg] = useState(null) - const [orgConfirmed, setOrgConfirmed] = useState(false) - const [creatingNewOrg, setCreatingNewOrg] = useState(false) - const [newOrgName, setNewOrgName] = useState('') - const { data: accountRequest, isPending, @@ -44,10 +31,9 @@ const StripeProjectsLoginPage = () => { isError, error, } = useQuery(accountRequestQueryOptions({ arId: ar_id })) - const { data: organizations = [] } = useOrganizationsQuery() const { - mutateAsync: confirmAccountRequest, + mutate: confirmAccountRequest, isPending: isConfirming, isSuccess: isConfirmed, } = useConfirmAccountRequestMutation() @@ -61,38 +47,19 @@ const StripeProjectsLoginPage = () => { } }, [router.isReady, ar_id, router]) - const handleApprove = async (organizationId?: number, organizationName?: string) => { + const handleApprove = async () => { if (!ar_id || isConfirming) return - confirmAccountRequest({ arId: ar_id, organizationId, organizationName }) + confirmAccountRequest({ arId: ar_id }) } - // linked_organization is set when an org is already linked to this Stripe account+org pair - // userOrgs is the list of user's orgs to pick from (only when no linked org) const linkedOrg = accountRequest?.linked_organization - const userOrgs = organizations ?? [] const emailMatches = accountRequest?.email_matches ?? false - const orgCount = userOrgs.length - - // isReauth = org already linked, user is just completing the authorization flow again - const isReauth = !!linkedOrg - const isLinking = isReauth || orgCount >= 1 - - const loadingText = isReauth - ? 'Completing authorization...' - : creatingNewOrg || orgCount === 0 - ? 'Creating your organization...' - : 'Linking your organization...' - const successTitle = isReauth - ? 'Authorization Complete' - : creatingNewOrg || orgCount === 0 - ? 'Organization Created' - : 'Organization Linked' - const successDescription = isReauth + const loadingText = linkedOrg ? 'Completing authorization...' : 'Creating your organization...' + const successTitle = linkedOrg ? 'Authorization Complete' : 'Organization Created' + const successDescription = linkedOrg ? null - : creatingNewOrg || orgCount === 0 - ? 'Your Supabase organization has been created and linked to your Stripe account.' - : 'Your Supabase organization has been linked to your Stripe account.' + : 'Your Supabase organization has been created and linked to your Stripe account.' return ( @@ -135,7 +102,7 @@ const StripeProjectsLoginPage = () => { ) : linkedOrg ? ( - // Org already linked to this Stripe account — inform user, no choice + // Org already linked to this Stripe account — inform user and confirm <>

Your organization {linkedOrg.name} is already linked to your @@ -146,14 +113,14 @@ const StripeProjectsLoginPage = () => { size="large" type="primary" disabled={isConfirming} - onClick={() => handleApprove()} + onClick={handleApprove} > Continue - ) : orgCount === 0 ? ( - // No orgs at all — a new one will be created + ) : ( + // No linked org — a new one will be created <>

A new Supabase organization will be created and linked to your Stripe account. @@ -163,133 +130,23 @@ const StripeProjectsLoginPage = () => { size="large" type="primary" disabled={isConfirming} - onClick={() => handleApprove()} + onClick={handleApprove} > Approve - ) : creatingNewOrg ? ( - // User chose to create a new org — show name input - <> -

- A new Free organization will be created and linked to your Stripe - account for provisioning Supabase resources. -

-
- setNewOrgName(e.target.value)} - maxLength={64} - autoFocus - /> -
-
- - -
- - ) : orgCount === 1 ? ( - // Exactly one org — show its name and option to create new - <> -

- Your organization {userOrgs[0].name} will be linked to your - Stripe account. Supabase resources from Stripe will be provisioned into this - organization. -

-
- - -
- - ) : !orgConfirmed ? ( - // 2+ orgs — show picker - <> -

- Select the organization you'd like to link to your Stripe account. Supabase - resources from Stripe will be provisioned into this organization. -

-
- { - const org = userOrgs.find((o) => o.slug === slug) ?? null - setSelectedOrg(org) - if (org) { - setOrgConfirmed(true) - } - }} - maxOrgsToShow={3} - canCreateNewOrg={false} - /> - -
- - ) : ( - // 2+ orgs — org selected, show confirmation - <> -

- Link {selectedOrg!.name} to your Stripe account? -

-
- - -
- )} ) : isError ? ( <>

Error

{error?.message}

+
+ +
) : null} diff --git a/packages/api-types/types/platform.d.ts b/packages/api-types/types/platform.d.ts index 11657238b19..2e033f6d79e 100644 --- a/packages/api-types/types/platform.d.ts +++ b/packages/api-types/types/platform.d.ts @@ -4764,10 +4764,7 @@ export interface components { payment_intent_id: string size?: string } - ConfirmRequestDto: { - organization_id?: number - organization_name?: string - } + ConfirmRequestDto: Record ConfirmResponseDto: { organization_slug: string success: boolean @@ -5139,12 +5136,7 @@ export interface components { | { billing_email: string | null /** @enum {string|null} */ - billing_partner: - | 'fly' - | 'aws_marketplace' - | 'vercel_marketplace' - | 'stripe_projects' - | null + billing_partner: 'fly' | 'aws_marketplace' | 'vercel_marketplace' | null id: number is_owner: boolean name: string @@ -6636,7 +6628,7 @@ export interface components { }[] billing_cycle_anchor: number /** @enum {string} */ - billing_partner?: 'fly' | 'aws_marketplace' | 'vercel_marketplace' | 'stripe_projects' + billing_partner?: 'fly' | 'aws_marketplace' | 'vercel_marketplace' billing_via_partner: boolean current_period_end: number current_period_start: number @@ -7683,7 +7675,7 @@ export interface components { OrganizationResponse: { billing_email: string | null /** @enum {string|null} */ - billing_partner: 'fly' | 'aws_marketplace' | 'vercel_marketplace' | 'stripe_projects' | null + billing_partner: 'fly' | 'aws_marketplace' | 'vercel_marketplace' | null id: number is_owner: boolean name: string @@ -7753,7 +7745,7 @@ export interface components { OrganizationSlugResponse: { billing_email: string | null /** @enum {string|null} */ - billing_partner: 'fly' | 'aws_marketplace' | 'vercel_marketplace' | 'stripe_projects' | null + billing_partner: 'fly' | 'aws_marketplace' | 'vercel_marketplace' | null has_oriole_project: boolean id: number name: string