Skip to content

Commit 03c1cc0

Browse files
kevcodezjoshenlim
andauthored
fix: confirmation modal on paid org creation (supabase#39857)
* fix: confirmation modal org creation The button when confirming org creation was not working as it was just returning early. The submit handler is triggered both on initially clicking the "Create organization" button and on the confirmation button, but it always early returns if the customer has other free plan orgs with projects, so it doesn't let the customer submit the form 12:44 How to reproduce: - Create a free plan org with an active project - Create a new paid plan organization - Confirmation modal appears - Clicking the button just closes the modal and does not create the org as there is an early return in the onSubmit handler Moved away from ConfirmationModal to an inline info instead * Update NewOrgForm.tsx * Nit refactor --------- Co-authored-by: Joshen Lim <[email protected]>
1 parent e05981c commit 03c1cc0

File tree

3 files changed

+47
-114
lines changed

3 files changed

+47
-114
lines changed

apps/studio/components/interfaces/Organization/NewOrg/NewOrgForm.tsx

Lines changed: 17 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ import { zodResolver } from '@hookform/resolvers/zod'
22
import { Elements } from '@stripe/react-stripe-js'
33
import type { PaymentIntentResult, PaymentMethod, StripeElementsOptions } from '@stripe/stripe-js'
44
import { loadStripe } from '@stripe/stripe-js'
5-
import _ from 'lodash'
5+
import { groupBy } from 'lodash'
66
import { HelpCircle } from 'lucide-react'
77
import { useTheme } from 'next-themes'
8-
import Link from 'next/link'
98
import { useRouter } from 'next/router'
109
import { parseAsBoolean, parseAsString, useQueryStates } from 'nuqs'
1110
import { useEffect, useMemo, useRef, useState } from 'react'
@@ -16,6 +15,10 @@ import { z } from 'zod'
1615
import { LOCAL_STORAGE_KEYS } from 'common'
1716
import { getStripeElementsAppearanceOptions } from 'components/interfaces/Billing/Payment/Payment.utils'
1817
import { PaymentConfirmation } from 'components/interfaces/Billing/Payment/PaymentConfirmation'
18+
import {
19+
NewPaymentMethodElement,
20+
type PaymentMethodElementRef,
21+
} from 'components/interfaces/Billing/Payment/PaymentMethods/NewPaymentMethodElement'
1922
import SpendCapModal from 'components/interfaces/Billing/SpendCapModal'
2023
import { InlineLink } from 'components/ui/InlineLink'
2124
import 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'
4948
import { 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

5551
const 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
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { InlineLink } from 'components/ui/InlineLink'
2+
import Panel from 'components/ui/Panel'
3+
import { Admonition } from 'ui-patterns'
4+
5+
export const UpgradeExistingOrganizationCallout = () => {
6+
return (
7+
<Panel.Content>
8+
<Admonition
9+
type="default"
10+
title="Looking to upgrade an existing project?"
11+
description={
12+
<div>
13+
<p className="text-sm text-foreground-light">
14+
Supabase{' '}
15+
<InlineLink href="https://supabase.com/docs/guides/platform/billing-on-supabase">
16+
bills per organization
17+
</InlineLink>
18+
. If you want to upgrade your existing projects,{' '}
19+
<InlineLink href="/org/_/billing?panel=subscriptionPlan">
20+
upgrade your existing organization
21+
</InlineLink>{' '}
22+
instead.
23+
</p>
24+
</div>
25+
}
26+
/>
27+
</Panel.Content>
28+
)
29+
}

apps/studio/pages/new/[slug].tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import {
4343
ProjectCreateVariables,
4444
useProjectCreateMutation,
4545
} from 'data/projects/project-create-mutation'
46-
import { useTrack } from 'lib/telemetry/track'
4746
import { useCustomContent } from 'hooks/custom-content/useCustomContent'
4847
import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
4948
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
@@ -63,6 +62,7 @@ import {
6362
} from 'lib/constants'
6463
import passwordStrength from 'lib/password-strength'
6564
import { generateStrongPassword } from 'lib/project'
65+
import { useTrack } from 'lib/telemetry/track'
6666
import { AWS_REGIONS, type CloudProvider } from 'shared-data'
6767
import type { NextPageWithLayout } from 'types'
6868
import {
@@ -772,18 +772,12 @@ const Wizard: NextPageWithLayout = () => {
772772
The size for your dedicated database. You can change this later.
773773
Learn more about{' '}
774774
<InlineLink
775-
target="_blank"
776-
rel="noopener noreferrer"
777-
className="text-inherit hover:text-foreground transition-colors"
778775
href={`${DOCS_URL}/guides/platform/compute-add-ons`}
779776
>
780777
compute add-ons
781778
</InlineLink>{' '}
782779
and{' '}
783780
<InlineLink
784-
target="_blank"
785-
rel="noopener noreferrer"
786-
className="text-inherit hover:text-foreground transition-colors"
787781
href={`${DOCS_URL}/guides/platform/manage-your-usage/compute`}
788782
>
789783
compute billing

0 commit comments

Comments
 (0)