@@ -2,10 +2,9 @@ import { zodResolver } from '@hookform/resolvers/zod'
22import { Elements } from '@stripe/react-stripe-js'
33import type { PaymentIntentResult , PaymentMethod , StripeElementsOptions } from '@stripe/stripe-js'
44import { loadStripe } from '@stripe/stripe-js'
5- import _ from 'lodash'
5+ import { groupBy } from 'lodash'
66import { HelpCircle } from 'lucide-react'
77import { useTheme } from 'next-themes'
8- import Link from 'next/link'
98import { useRouter } from 'next/router'
109import { parseAsBoolean , parseAsString , useQueryStates } from 'nuqs'
1110import { useEffect , useMemo , useRef , useState } from 'react'
@@ -16,6 +15,10 @@ import { z } from 'zod'
1615import { LOCAL_STORAGE_KEYS } from 'common'
1716import { getStripeElementsAppearanceOptions } from 'components/interfaces/Billing/Payment/Payment.utils'
1817import { PaymentConfirmation } from 'components/interfaces/Billing/Payment/PaymentConfirmation'
18+ import {
19+ NewPaymentMethodElement ,
20+ type PaymentMethodElementRef ,
21+ } from 'components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement'
1922import SpendCapModal from 'components/interfaces/Billing/SpendCapModal'
2023import { InlineLink } from 'components/ui/InlineLink'
2124import Panel from 'components/ui/Panel'
@@ -41,16 +44,9 @@ import {
4144 SelectTrigger_Shadcn_ ,
4245 SelectValue_Shadcn_ ,
4346 Switch ,
44- Tooltip ,
45- TooltipContent ,
46- TooltipTrigger ,
4747} from 'ui'
48- import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
4948import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
50- import {
51- NewPaymentMethodElement ,
52- type PaymentMethodElementRef ,
53- } from '../../Billing/Payment/PaymentMethods/NewPaymentMethodElement'
49+ import { UpgradeExistingOrganizationCallout } from './UpgradeExistingOrganizationCallout'
5450
5551const ORG_KIND_TYPES = {
5652 PERSONAL : 'Personal' ,
@@ -132,12 +128,9 @@ export const NewOrgForm = ({
132128 // in onSubmit below, which isn't a critical functionality imo so am okay for now. But ideally perhaps this data can
133129 // be computed on the API and returned in /profile or something (since this data is on the account level)
134130 const projectsByOrg = useMemo ( ( ) => {
135- return _ . groupBy ( projects , 'organization_slug' )
131+ return groupBy ( projects , 'organization_slug' )
136132 } , [ projects ] )
137133
138- const [ isOrgCreationConfirmationModalVisible , setIsOrgCreationConfirmationModalVisible ] =
139- useState ( false )
140-
141134 const stripeOptionsPaymentMethod : StripeElementsOptions = useMemo (
142135 ( ) =>
143136 ( {
@@ -198,6 +191,11 @@ export const NewOrgForm = ({
198191 const [ showSpendCapHelperModal , setShowSpendCapHelperModal ] = useState ( false )
199192 const [ paymentIntentSecret , setPaymentIntentSecret ] = useState < string | null > ( null )
200193
194+ const hasFreeOrgWithProjects = useMemo (
195+ ( ) => freeOrgs . some ( ( it ) => projectsByOrg [ it . slug ] ?. length > 0 ) ,
196+ [ freeOrgs , projectsByOrg ]
197+ )
198+
201199 const { mutate : createOrganization } = useOrganizationCreateMutation ( {
202200 onSuccess : async ( org ) => {
203201 if ( 'pending_payment_intent_secret' in org && org . pending_payment_intent_secret ) {
@@ -299,13 +297,6 @@ export const NewOrgForm = ({
299297 const paymentRef = useRef < PaymentMethodElementRef | null > ( null )
300298
301299 const onSubmit : SubmitHandler < z . infer < typeof formSchema > > = async ( formValues ) => {
302- const hasFreeOrgWithProjects = freeOrgs . some ( ( it ) => projectsByOrg [ it . slug ] ?. length > 0 )
303-
304- if ( hasFreeOrgWithProjects && formValues . plan !== 'FREE' ) {
305- setIsOrgCreationConfirmationModalVisible ( true )
306- return
307- }
308-
309300 setNewOrgLoading ( true )
310301
311302 if ( formValues . plan === 'FREE' ) {
@@ -478,15 +469,7 @@ export const NewOrgForm = ({
478469 description = {
479470 < >
480471 Which plan fits your organization's needs best?{ ' ' }
481- < InlineLink
482- href = "https://supabase.com/pricing"
483- target = "_blank"
484- rel = "noreferrer"
485- className = "text-inherit hover:text-foreground transition-colors"
486- >
487- Learn more
488- </ InlineLink >
489- .
472+ < InlineLink href = "https://supabase.com/pricing" > Learn more</ InlineLink > .
490473 </ >
491474 }
492475 >
@@ -569,86 +552,13 @@ export const NewOrgForm = ({
569552 </ Elements >
570553 </ Panel . Content >
571554 ) }
555+
556+ { hasFreeOrgWithProjects && form . getValues ( 'plan' ) !== 'FREE' && (
557+ < UpgradeExistingOrganizationCallout />
558+ ) }
572559 </ div >
573560 </ Panel >
574561
575- < ConfirmationModal
576- size = "large"
577- loading = { newOrgLoading }
578- visible = { isOrgCreationConfirmationModalVisible }
579- title = "Confirm organization creation"
580- confirmLabel = "Create new organization"
581- onCancel = { ( ) => setIsOrgCreationConfirmationModalVisible ( false ) }
582- onConfirm = { async ( ) => {
583- await onSubmit ( form . getValues ( ) )
584- setIsOrgCreationConfirmationModalVisible ( false )
585- } }
586- variant = { 'warning' }
587- >
588- < p className = "text-sm text-foreground-light" >
589- Supabase{ ' ' }
590- < InlineLink
591- href = "https://supabase.com/docs/guides/platform/billing-on-supabase"
592- target = "_blank"
593- rel = "noreferrer"
594- className = "text-inherit hover:text-foreground transition-colors"
595- >
596- bills per organization
597- </ InlineLink >
598- . If you want to upgrade your existing projects, upgrade your existing organization
599- instead.
600- </ p >
601-
602- < ul className = "mt-4 divide-y divide-border-muted border-t border-t-border-muted" >
603- { freeOrgs
604- . filter ( ( it ) => projectsByOrg [ it . slug ] ?. length > 0 )
605- . map ( ( org ) => {
606- const orgProjects = projectsByOrg [ org . slug ] . map ( ( it ) => it . name )
607-
608- return (
609- < li
610- key = { `org_${ org . slug } ` }
611- className = "pt-3 [&:not(:last-child)]:pb-3 flex items-center justify-between"
612- >
613- < div className = "flex flex-col" >
614- < h3 className = "text-sm" > { org . name } </ h3 >
615-
616- < div className = "text-foreground-lighter text-xs" >
617- { orgProjects . length <= 2 ? (
618- < span > { orgProjects . join ( ' and ' ) } </ span >
619- ) : (
620- < div >
621- { orgProjects . slice ( 0 , 2 ) . join ( ', ' ) } and{ ' ' }
622- < Tooltip >
623- < TooltipTrigger asChild >
624- < span className = "underline decoration-dotted" >
625- { orgProjects . length - 2 } other{ ' ' }
626- { orgProjects . length === 3 ? 'project' : 'project' }
627- </ span >
628- </ TooltipTrigger >
629- < TooltipContent >
630- < ul className = "list-disc list-inside" >
631- { orgProjects . slice ( 2 ) . map ( ( project ) => (
632- < li > { project } </ li >
633- ) ) }
634- </ ul >
635- </ TooltipContent >
636- </ Tooltip >
637- </ div >
638- ) }
639- </ div >
640- </ div >
641- < Button asChild type = "default" size = "tiny" >
642- < Link href = { `/org/${ org . slug } /billing?panel=subscriptionPlan` } >
643- Change plan
644- </ Link >
645- </ Button >
646- </ li >
647- )
648- } ) }
649- </ ul >
650- </ ConfirmationModal >
651-
652562 { stripePromise && paymentIntentSecret && paymentMethod && (
653563 < Elements stripe = { stripePromise } options = { stripeOptionsConfirm } >
654564 < PaymentConfirmation
0 commit comments