@@ -12,6 +12,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
1212import { z } from "zod" ;
1313import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons" ;
1414import { BranchesNoBranchableEnvironment , BranchesNoBranches } from "~/components/BlankStatePanels" ;
15+ import { Feedback } from "~/components/Feedback" ;
1516import { GitMetadata } from "~/components/GitMetadata" ;
1617import { V4Title } from "~/components/V4Badge" ;
1718import { AdminDebugTooltip } from "~/components/admin/debugTooltip" ;
@@ -56,13 +57,15 @@ import {
5657} from "~/components/primitives/Table" ;
5758import { InfoIconTooltip , SimpleTooltip } from "~/components/primitives/Tooltip" ;
5859import { useEnvironment } from "~/hooks/useEnvironment" ;
60+ import { useShowSelfServe } from "~/hooks/useShowSelfServe" ;
5961import { useOrganization } from "~/hooks/useOrganizations" ;
6062import { useProject } from "~/hooks/useProject" ;
6163
6264import { findProjectBySlug } from "~/models/project.server" ;
6365import { redirectWithErrorMessage , redirectWithSuccessMessage } from "~/models/message.server" ;
6466import { BranchesPresenter } from "~/presenters/v3/BranchesPresenter.server" ;
6567import { logger } from "~/services/logger.server" ;
68+ import { getCurrentPlan , getSelfServePurchaseBlockReason } from "~/services/platform.v3.server" ;
6669import { requireUserId } from "~/services/session.server" ;
6770import { UpsertBranchService } from "~/services/upsertBranch.server" ;
6871import { cn } from "~/utils/cn" ;
@@ -155,6 +158,21 @@ export async function action({ request, params }: ActionFunctionArgs) {
155158 throw redirectWithErrorMessage ( redirectPath , request , "Project not found" ) ;
156159 }
157160
161+ const currentPlan = await getCurrentPlan ( project . organizationId ) ;
162+ const purchaseBlockReason = getSelfServePurchaseBlockReason ( currentPlan ) ;
163+ if ( purchaseBlockReason === "plan_unavailable" ) {
164+ return json (
165+ { ok : false , error : "Unable to verify billing status. Please try again." } as const ,
166+ { status : 503 }
167+ ) ;
168+ }
169+ if ( purchaseBlockReason === "managed_billing" ) {
170+ return json (
171+ { ok : false , error : "Contact us to request more branches." } as const ,
172+ { status : 403 }
173+ ) ;
174+ }
175+
158176 const submission = parse ( formData , { schema : PurchaseSchema } ) ;
159177
160178 if ( ! submission . value || submission . intent !== "submit" ) {
@@ -237,6 +255,7 @@ export default function Page() {
237255 const environment = useEnvironment ( ) ;
238256
239257 const plan = useCurrentPlan ( ) ;
258+ const showSelfServe = useShowSelfServe ( ) ;
240259 const requiresUpgrade =
241260 plan ?. v3Subscription ?. plan &&
242261 limits . used >= plan . v3Subscription . plan . limits . branches . number &&
@@ -254,7 +273,7 @@ export default function Page() {
254273 </ NavBar >
255274 < PageBody >
256275 < MainCenteredContainer className = "max-w-md" >
257- < BranchesNoBranchableEnvironment />
276+ < BranchesNoBranchableEnvironment showSelfServe = { showSelfServe } />
258277 </ MainCenteredContainer >
259278 </ PageBody >
260279 </ PageContainer >
@@ -322,6 +341,7 @@ export default function Page() {
322341 parentEnvironment = { branchableEnvironment }
323342 limits = { limits }
324343 canUpgrade = { canUpgrade ?? false }
344+ showSelfServe = { showSelfServe }
325345 />
326346 </ MainCenteredContainer >
327347 ) : (
@@ -484,19 +504,26 @@ export default function Page() {
484504 planBranchLimit = { planBranchLimit }
485505 />
486506 ) : canUpgrade ? (
487- < div className = "flex items-center gap-3" >
488- < Paragraph variant = "small" className = "whitespace-nowrap text-text-dimmed" >
489- Upgrade plan for more Preview Branches
490- </ Paragraph >
491- < LinkButton
492- to = { v3BillingPath ( organization ) }
493- variant = "secondary/small"
494- LeadingIcon = { ArrowUpCircleIcon }
495- leadingIconClassName = "text-indigo-500"
496- >
497- Upgrade
498- </ LinkButton >
499- </ div >
507+ showSelfServe ? (
508+ < div className = "flex items-center gap-3" >
509+ < Paragraph variant = "small" className = "whitespace-nowrap text-text-dimmed" >
510+ Upgrade plan for more Preview Branches
511+ </ Paragraph >
512+ < LinkButton
513+ to = { v3BillingPath ( organization ) }
514+ variant = "secondary/small"
515+ LeadingIcon = { ArrowUpCircleIcon }
516+ leadingIconClassName = "text-indigo-500"
517+ >
518+ Upgrade
519+ </ LinkButton >
520+ </ div >
521+ ) : (
522+ < Feedback
523+ defaultValue = "enterprise"
524+ button = { < Button variant = "secondary/small" > Request more</ Button > }
525+ />
526+ )
500527 ) : null }
501528 </ div >
502529 </ div >
@@ -559,6 +586,7 @@ function UpgradePanel({
559586 planBranchLimit : number ;
560587} ) {
561588 const organization = useOrganization ( ) ;
589+ const showSelfServe = useShowSelfServe ( ) ;
562590
563591 if ( canPurchaseBranches && branchPricing ) {
564592 return (
@@ -604,9 +632,16 @@ function UpgradePanel({
604632 </ div >
605633 < DialogFooter >
606634 { canUpgrade ? (
607- < LinkButton variant = "primary/small" to = { v3BillingPath ( organization ) } >
608- Upgrade
609- </ LinkButton >
635+ showSelfServe ? (
636+ < LinkButton variant = "primary/small" to = { v3BillingPath ( organization ) } >
637+ Upgrade
638+ </ LinkButton >
639+ ) : (
640+ < Feedback
641+ defaultValue = "enterprise"
642+ button = { < Button variant = "secondary/small" > Request more</ Button > }
643+ />
644+ )
610645 ) : null }
611646 </ DialogFooter >
612647 </ DialogContent >
@@ -632,6 +667,7 @@ function PurchaseBranchesModal({
632667 planBranchLimit : number ;
633668 triggerButton ?: React . ReactNode ;
634669} ) {
670+ const showSelfServe = useShowSelfServe ( ) ;
635671 const fetcher = useFetcher ( ) ;
636672 const lastSubmission =
637673 fetcher . data && typeof fetcher . data === "object" && "intent" in fetcher . data
@@ -679,6 +715,15 @@ function PurchaseBranchesModal({
679715 const pricePerBranch = branchPricing . centsPerStep / branchPricing . stepSize / 100 ;
680716 const title = extraBranches === 0 ? "Purchase extra branches…" : "Add/remove extra branches…" ;
681717
718+ if ( ! showSelfServe ) {
719+ return (
720+ < Feedback
721+ defaultValue = "enterprise"
722+ button = { < Button variant = "secondary/small" > Request more</ Button > }
723+ />
724+ ) ;
725+ }
726+
682727 return (
683728 < Dialog open = { open } onOpenChange = { setOpen } >
684729 < DialogTrigger asChild >
0 commit comments