Skip to content

Commit bc48352

Browse files
samejrmatt-aitken
authored andcommitted
Adds missing upgrade buttons
1 parent 54c8678 commit bc48352

File tree

2 files changed

+82
-41
lines changed

2 files changed

+82
-41
lines changed

apps/webapp/app/presenters/v3/LimitsPresenter.server.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type QuotaInfo = {
3939
limit: number | null;
4040
currentUsage: number;
4141
source: "default" | "plan" | "override";
42+
canExceed?: boolean;
4243
};
4344

4445
// Types for feature flags
@@ -76,6 +77,7 @@ export type LimitsResult = {
7677
};
7778
planName: string | null;
7879
organizationId: string;
80+
isOnTopPlan: boolean;
7981
};
8082

8183
export class LimitsPresenter extends BasePresenter {
@@ -116,6 +118,7 @@ export class LimitsPresenter extends BasePresenter {
116118
// Get current plan from billing service
117119
const currentPlan = await getCurrentPlan(organizationId);
118120
const limits = currentPlan?.v3Subscription?.plan?.limits;
121+
const isOnTopPlan = currentPlan?.v3Subscription?.plan?.code === "v3_pro_1";
119122

120123
// Resolve API rate limit config
121124
const apiRateLimitConfig = resolveApiRateLimitConfig(organization.apiRateLimiterConfig);
@@ -192,6 +195,7 @@ export class LimitsPresenter extends BasePresenter {
192195
const supportLevel = limits?.support ?? "community";
193196

194197
return {
198+
isOnTopPlan,
195199
rateLimits: {
196200
api: {
197201
name: "API Rate Limit",
@@ -224,6 +228,7 @@ export class LimitsPresenter extends BasePresenter {
224228
limit: schedulesLimit,
225229
currentUsage: scheduleCount,
226230
source: "plan",
231+
canExceed: limits?.schedules?.canExceed,
227232
}
228233
: null,
229234
teamMembers:
@@ -234,6 +239,7 @@ export class LimitsPresenter extends BasePresenter {
234239
limit: teamMembersLimit,
235240
currentUsage: organization._count.members,
236241
source: "plan",
242+
canExceed: limits?.teamMembers?.canExceed,
237243
}
238244
: null,
239245
alerts:
@@ -244,6 +250,7 @@ export class LimitsPresenter extends BasePresenter {
244250
limit: alertsLimit,
245251
currentUsage: alertChannelCount,
246252
source: "plan",
253+
canExceed: limits?.alerts?.canExceed,
247254
}
248255
: null,
249256
branches:
@@ -254,6 +261,7 @@ export class LimitsPresenter extends BasePresenter {
254261
limit: branchesLimit,
255262
currentUsage: activeBranchCount,
256263
source: "plan",
264+
canExceed: limits?.branches?.canExceed,
257265
}
258266
: null,
259267
logRetentionDays:
@@ -270,10 +278,11 @@ export class LimitsPresenter extends BasePresenter {
270278
realtimeConnectionsLimit !== null
271279
? {
272280
name: "Realtime connections",
273-
description: "Maximum concurrent realtime connections",
281+
description: "Maximum concurrent Realtime connections",
274282
limit: realtimeConnectionsLimit,
275283
currentUsage: 0, // Would need to query realtime service for this
276284
source: "plan",
285+
canExceed: limits?.realtimeConcurrentConnections?.canExceed,
277286
}
278287
: null,
279288
devQueueSize: {

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.limits/route.tsx

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ export default function Page() {
125125
{/* Current Plan Section */}
126126
{data.planName && (
127127
<CurrentPlanSection
128-
planName={`${data.planName}Pro`}
128+
planName={data.planName}
129+
isOnTopPlan={data.isOnTopPlan}
129130
billingPath={organizationBillingPath(organization)}
130131
/>
131132
)}
@@ -147,14 +148,14 @@ export default function Page() {
147148
<QuotasSection
148149
quotas={data.quotas}
149150
batchConcurrency={data.batchConcurrency}
150-
planName={data.planName}
151+
isOnTopPlan={data.isOnTopPlan}
151152
billingPath={organizationBillingPath(organization)}
152153
/>
153154

154155
{/* Features Section */}
155156
<FeaturesSection
156157
features={data.features}
157-
planName={data.planName}
158+
isOnTopPlan={data.isOnTopPlan}
158159
billingPath={organizationBillingPath(organization)}
159160
/>
160161
</div>
@@ -164,9 +165,15 @@ export default function Page() {
164165
);
165166
}
166167

167-
function CurrentPlanSection({ planName, billingPath }: { planName: string; billingPath: string }) {
168-
const isPro = planName === "Pro";
169-
168+
function CurrentPlanSection({
169+
planName,
170+
isOnTopPlan,
171+
billingPath,
172+
}: {
173+
planName: string;
174+
isOnTopPlan: boolean;
175+
billingPath: string;
176+
}) {
170177
return (
171178
<div className="flex flex-col gap-3">
172179
<Header2>Current plan</Header2>
@@ -175,7 +182,7 @@ function CurrentPlanSection({ planName, billingPath }: { planName: string; billi
175182
<TableRow>
176183
<TableCell className="w-full text-sm">{planName}</TableCell>
177184
<TableCell alignment="right">
178-
{isPro ? (
185+
{isOnTopPlan ? (
179186
<Feedback
180187
button={<Button variant="secondary/small">Request Enterprise</Button>}
181188
defaultValue="help"
@@ -429,15 +436,14 @@ function RateLimitConfigDisplay({ config }: { config: RateLimitInfo["config"] })
429436
function QuotasSection({
430437
quotas,
431438
batchConcurrency,
432-
planName,
439+
isOnTopPlan,
433440
billingPath,
434441
}: {
435442
quotas: LimitsResult["quotas"];
436443
batchConcurrency: LimitsResult["batchConcurrency"];
437-
planName: string | null;
444+
isOnTopPlan: boolean;
438445
billingPath: string;
439446
}) {
440-
const isPro = planName === "Pro";
441447
// Collect all quotas that should be shown
442448
const quotaRows: QuotaInfo[] = [];
443449

@@ -480,8 +486,7 @@ function QuotasSection({
480486
<QuotaRow
481487
key={quota.name}
482488
quota={quota}
483-
showUpgrade={quota.name === "Projects"}
484-
isPro={isPro}
489+
isOnTopPlan={isOnTopPlan}
485490
billingPath={billingPath}
486491
/>
487492
))}
@@ -504,7 +509,7 @@ function QuotasSection({
504509
</TableCell>
505510
<TableCell alignment="right">
506511
<div className="flex justify-end">
507-
{isPro ? (
512+
{isOnTopPlan ? (
508513
<Feedback
509514
button={<Button variant="tertiary/small">Contact us</Button>}
510515
defaultValue="help"
@@ -525,20 +530,62 @@ function QuotasSection({
525530

526531
function QuotaRow({
527532
quota,
528-
showUpgrade,
529-
isPro,
533+
isOnTopPlan,
530534
billingPath,
531535
}: {
532536
quota: QuotaInfo;
533-
showUpgrade: boolean;
534-
isPro: boolean;
537+
isOnTopPlan: boolean;
535538
billingPath: string;
536539
}) {
537540
// For log retention, we don't show current usage as it's a duration, not a count
538541
const isRetentionQuota = quota.name === "Log retention";
539542
const percentage =
540543
!isRetentionQuota && quota.limit && quota.limit > 0 ? quota.currentUsage / quota.limit : null;
541544

545+
// Quotas that support upgrade options
546+
const upgradableQuotas = [
547+
"Projects",
548+
"Schedules",
549+
"Team members",
550+
"Alert channels",
551+
"Preview branches",
552+
"Realtime connections",
553+
];
554+
555+
const isUpgradable = upgradableQuotas.includes(quota.name);
556+
557+
const renderUpgrade = () => {
558+
if (!isUpgradable) {
559+
return null;
560+
}
561+
562+
// Not on top plan - show View plans
563+
if (!isOnTopPlan) {
564+
return (
565+
<div className="flex justify-end">
566+
<LinkButton to={billingPath} variant="tertiary/small">
567+
View plans
568+
</LinkButton>
569+
</div>
570+
);
571+
}
572+
573+
// On top plan - show Contact us if canExceed is true (or for Projects which is always exceedable)
574+
if (quota.canExceed || quota.name === "Projects") {
575+
return (
576+
<div className="flex justify-end">
577+
<Feedback
578+
button={<Button variant="tertiary/small">Contact us</Button>}
579+
defaultValue="help"
580+
/>
581+
</div>
582+
);
583+
}
584+
585+
// On top plan but cannot exceed - no upgrade option
586+
return null;
587+
};
588+
542589
return (
543590
<TableRow>
544591
<TableCell className="flex w-full items-center gap-1">
@@ -564,37 +611,22 @@ function QuotaRow({
564611
<TableCell alignment="right">
565612
<SourceBadge source={quota.source} />
566613
</TableCell>
567-
<TableCell alignment="right">
568-
{showUpgrade ? (
569-
<div className="flex justify-end">
570-
{isPro ? (
571-
<Feedback
572-
button={<Button variant="tertiary/small">Contact us</Button>}
573-
defaultValue="help"
574-
/>
575-
) : (
576-
<LinkButton to={billingPath} variant="tertiary/small">
577-
View plans
578-
</LinkButton>
579-
)}
580-
</div>
581-
) : null}
582-
</TableCell>
614+
<TableCell alignment="right">{renderUpgrade()}</TableCell>
583615
</TableRow>
584616
);
585617
}
586618

587619
function FeaturesSection({
588620
features,
589-
planName,
621+
isOnTopPlan,
590622
billingPath,
591623
}: {
592624
features: LimitsResult["features"];
593-
planName: string | null;
625+
isOnTopPlan: boolean;
594626
billingPath: string;
595627
}) {
596-
const isPro = planName === "Pro";
597-
const isFree = planName === "Free" || planName === null;
628+
// For staging environment: show View plans if not enabled (i.e., on Free plan)
629+
const stagingUpgradeType = features.hasStagingEnvironment.enabled ? "none" : "view-plans";
598630

599631
return (
600632
<div className="flex flex-col gap-3">
@@ -610,17 +642,17 @@ function FeaturesSection({
610642
<TableBody>
611643
<FeatureRow
612644
feature={features.hasStagingEnvironment}
613-
upgradeType={isFree ? "view-plans" : "none"}
645+
upgradeType={stagingUpgradeType}
614646
billingPath={billingPath}
615647
/>
616648
<FeatureRow
617649
feature={features.support}
618-
upgradeType={isPro ? "contact-us" : "view-plans"}
650+
upgradeType={isOnTopPlan ? "contact-us" : "view-plans"}
619651
billingPath={billingPath}
620652
/>
621653
<FeatureRow
622654
feature={features.includedUsage}
623-
upgradeType={isPro ? "contact-us" : "view-plans"}
655+
upgradeType={isOnTopPlan ? "contact-us" : "view-plans"}
624656
billingPath={billingPath}
625657
/>
626658
</TableBody>

0 commit comments

Comments
 (0)