import { PaymentElement, useElements, useStripe } from '@stripe/react-stripe-js' import type { PaymentMethod } from '@stripe/stripe-js' import { useQueryClient } from '@tanstack/react-query' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' import toast from 'react-hot-toast' import { useParams } from 'common' import SpendCapModal from 'components/interfaces/Billing/SpendCapModal' import Panel from 'components/ui/Panel' import { useOrganizationCreateMutation } from 'data/organizations/organization-create-mutation' import { invalidateOrganizationsQuery } from 'data/organizations/organizations-query' import { BASE_PATH, PRICING_TIER_LABELS_ORG } from 'lib/constants' import { getURL } from 'lib/helpers' import { Button, IconEdit2, IconHelpCircle, Input, Listbox, Toggle } from 'ui' const ORG_KIND_TYPES = { PERSONAL: 'Personal', EDUCATIONAL: 'Educational', STARTUP: 'Startup', AGENCY: 'Agency', COMPANY: 'Company', UNDISCLOSED: 'N/A', } const ORG_KIND_DEFAULT = 'PERSONAL' const ORG_SIZE_TYPES = { '1': '1 - 10', '10': '10 - 49', '50': '50 - 99', '100': '100 - 299', '300': 'More than 300', } const ORG_SIZE_DEFAULT = '1' interface NewOrgFormProps { onPaymentMethodReset: () => void } /** * No org selected yet, create a new one */ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => { const router = useRouter() const stripe = useStripe() const elements = useElements() const queryClient = useQueryClient() const { plan, name, kind, size, spend_cap } = useParams() const [orgName, setOrgName] = useState(name || '') const [orgKind, setOrgKind] = useState( kind && Object.keys(ORG_KIND_TYPES).includes(kind) ? kind : ORG_KIND_DEFAULT ) const [orgSize, setOrgSize] = useState(size || ORG_SIZE_DEFAULT) // [Joshen] Separate loading state here as there's 2 async processes const [newOrgLoading, setNewOrgLoading] = useState(false) const [paymentMethod, setPaymentMethod] = useState() // URL param support for passing plan const [dbPricingTierKey, setDbPricingTierKey] = useState( plan && ['free', 'team', 'pro'].includes(plan) ? plan.toUpperCase() : 'FREE' ) const [showSpendCapHelperModal, setShowSpendCapHelperModal] = useState(false) const [isSpendCapEnabled, setIsSpendCapEnabled] = useState(spend_cap ? Boolean(spend_cap) : true) useEffect(() => { const query: Record = {} query.plan = dbPricingTierKey.toLowerCase() if (orgName) query.name = orgName if (orgKind) query.kind = orgKind if (orgSize) query.size = orgSize if (isSpendCapEnabled) query.spend_cap = isSpendCapEnabled.toString() router.push({ query }) }, [dbPricingTierKey, orgName, orgKind, orgSize, isSpendCapEnabled]) const { mutate: createOrganization } = useOrganizationCreateMutation({ onSuccess: async (org: any) => { await invalidateOrganizationsQuery(queryClient) router.push(`/new/${org.slug}`) }, onError: () => { resetPaymentMethod() setNewOrgLoading(false) }, }) function validateOrgName(name: any) { const value = name ? name.trim() : '' return value.length >= 1 } function onOrgNameChange(e: any) { setOrgName(e.target.value) } function onOrgKindChange(value: any) { setOrgKind(value) } function onOrgSizeChange(value: any) { setOrgSize(value) } function onDbPricingPlanChange(value: string) { setDbPricingTierKey(value) } async function createOrg(paymentMethodId?: string) { const dbTier = dbPricingTierKey === 'PRO' && !isSpendCapEnabled ? 'PAYG' : dbPricingTierKey createOrganization({ name: orgName, kind: orgKind, tier: ('tier_' + dbTier.toLowerCase()) as | 'tier_payg' | 'tier_pro' | 'tier_free' | 'tier_team' | 'tier_enterprise', ...(orgKind == 'COMPANY' ? { size: orgSize } : {}), payment_method: paymentMethodId, }) } const handleSubmit = async (event: any) => { event.preventDefault() const isOrgNameValid = validateOrgName(orgName) if (!isOrgNameValid) { return toast.error('Organization name is empty') } if (!stripe || !elements) { return console.error('Stripe.js has not loaded') } setNewOrgLoading(true) if (dbPricingTierKey === 'FREE') { await createOrg() } else if (!paymentMethod) { const { error, setupIntent } = await stripe.confirmSetup({ elements, redirect: 'if_required', confirmParams: { return_url: `${getURL()}/new`, expand: ['payment_method'], }, }) if (error || !setupIntent.payment_method) { toast.error(error?.message ?? ' Failed to save card details') setNewOrgLoading(false) return } const paymentMethodFromSetup = setupIntent.payment_method as PaymentMethod setPaymentMethod(paymentMethodFromSetup) createOrg(paymentMethodFromSetup.id) } else { createOrg(paymentMethod.id) } } const resetPaymentMethod = () => { setPaymentMethod(undefined) return onPaymentMethodReset() } return (

Create a new organization

} footer={

You can rename your organization later

} >

This is your organization within Supabase.

For example, you can use the name of your company or department.

{Object.entries(ORG_KIND_TYPES).map(([k, v]) => { return ( {v} ) })} {orgKind == 'COMPANY' ? ( {Object.entries(ORG_SIZE_TYPES).map(([k, v]) => { return ( {v} ) })} ) : ( <> )} Select a plan that suits your needs.  More details } > {Object.entries(PRICING_TIER_LABELS_ORG).map(([k, v]) => { return ( {v} ) })} {dbPricingTierKey === 'PRO' && ( <> Spend Cap setShowSpendCapHelperModal(true)} /> } checked={isSpendCapEnabled} onChange={() => setIsSpendCapEnabled(!isSpendCapEnabled)} descriptionText={

By default, Pro plan organizations have a spend cap to control costs. When enabled, usage is limited to the plan's quota, with restrictions when limits are exceeded. To scale beyond Pro limits without restrictions, disable the spend cap and pay for over-usage beyond the quota.

} />
setShowSpendCapHelperModal(false)} /> )} {dbPricingTierKey !== 'FREE' && ( {paymentMethod?.card !== undefined ? (
Card

Expires: {paymentMethod.card.exp_month}/{paymentMethod.card.exp_year}

) : ( )}
)}
) } export default NewOrgForm