import { zodResolver } from '@hookform/resolvers/zod' import { PropsWithChildren, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import { Badge, Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogSection, DialogSectionSeparator, DialogTitle, DialogTrigger, Form, FormControl, FormField, TextArea, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import z from 'zod' import { useOrganizationRolesV2Query } from '@/data/organization-members/organization-roles-query' import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query' import { PlanRequest, useSendUpgradeRequestMutation, } from '@/data/organizations/request-upgrade-mutation' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { useTrack } from '@/lib/telemetry/track' const FormSchema = z.object({ note: z.string().optional(), }) const formId = 'request-upgrade-form' interface RequestUpgradeToBillingOwnersProps { block?: boolean plan?: PlanRequest addon?: 'pitr' | 'customDomain' | 'ipv4' | 'spendCap' | 'computeSize' /** Used in the default message template, e.g: "Upgrade to ..." */ featureProposition?: string className?: string type?: 'primary' | 'default' } export const RequestUpgradeToBillingOwners = ({ block = false, plan = 'Pro', addon, featureProposition, children, className, type = 'primary', }: PropsWithChildren) => { const [open, setOpen] = useState(false) const track = useTrack() const { data: project } = useSelectedProjectQuery() const { data: organization } = useSelectedOrganizationQuery() const slug = organization?.slug const currentPlan = organization?.plan?.id const isFreePlan = currentPlan === 'free' const { data: members = [] } = useOrganizationMembersQuery({ slug: organization?.slug }) const { data: roles } = useOrganizationRolesV2Query({ slug: organization?.slug }) const orgRoles = roles?.org_scoped_roles ?? [] const { mutate: sendUpgradeRequest, isPending: isSubmitting } = useSendUpgradeRequestMutation({ onSuccess: () => { track('request_upgrade_submitted', { requestedPlan: plan, addon, currentPlan, }) toast.success('Successfully sent request to billing owners!') setOpen(false) }, }) const formattedAddonName = addon === 'pitr' ? 'PITR' : addon === 'customDomain' ? 'Custom domain' : addon === 'ipv4' ? 'dedicated IPv4 address' : '' const target = !!project ? `for the project "${project?.name}"` : !!organization ? `for the organization "${organization.name}"` : '' const action = addon === 'spendCap' ? `disable spend cap` : addon === 'computeSize' ? `change the compute size` : `enable the ${formattedAddonName} add-on` const titleText = !!addon ? addon === 'spendCap' ? `Request to disable spend cap` : addon === 'computeSize' ? 'Request to change compute size' : `Request to enable the ${formattedAddonName} add-on` : `Request an upgrade for the ${plan} Plan` const buttonText = !!children ? children : !!addon ? addon === 'spendCap' ? 'Request to disable spend cap' : addon === 'computeSize' ? 'Request to change compute' : 'Request to enable addon' : `Request upgrade to ${plan}` const defaultValues = { note: !!addon ? `We'd like to ${isFreePlan ? 'upgrade to Pro and ' : ''}${action} ${target} so that we can ${featureProposition}` : `We'd like to upgrade to the ${plan} plan ${!!featureProposition ? `to ${featureProposition} ` : ''}${target}`, } const form = useForm>({ resolver: zodResolver(FormSchema as any), defaultValues, values: defaultValues, }) // [Joshen] This is a pretty naive way of checking billing owners by raw role names // Ideally we derive billing owners using permissions checking - but the current permissions // logic is only contextualized to that of the current user, not other members const billingOwners = members.filter((member) => { const roles = member.role_ids .map((x) => orgRoles.find((role) => role.id === x)?.name) .filter(Boolean) return !member.invited_id && (roles.includes('Owner') || roles.includes('Administrator')) }) const onSubmit: SubmitHandler> = async (values) => { if (!slug) return console.error('Slug is required') sendUpgradeRequest({ slug, plan, note: values.note }) } const handleOpenChange = (isOpen: boolean) => { if (isOpen) { track('request_upgrade_modal_opened', { requestedPlan: plan, addon, currentPlan, featureProposition, }) } setOpen(isOpen) } return (
{titleText} Let your organization's billing owners know your interest in this

Your request will be sent to the following emails, who are billing owners of your organization:

{billingOwners .slice(0, 2) .map((x) => x.primary_email) .join(', ')}

{billingOwners.length > 2 && ( +1 others
    {billingOwners.slice(2).map((x) => (
  • {x.primary_email}
  • ))}
)}
(