Skip to content

Commit a746f04

Browse files
committed
[dashboard] Prevent upgrading to Chargebee once Usage-Based Pricing is enabled
1 parent 93a68d3 commit a746f04

File tree

3 files changed

+95
-116
lines changed

3 files changed

+95
-116
lines changed

components/dashboard/src/contexts/FeatureFlagContext.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ interface FeatureFlagConfig {
1717

1818
const FeatureFlagContext = createContext<{
1919
showUsageView: boolean;
20+
isUsageBasedBillingEnabled: boolean;
2021
showUseLastSuccessfulPrebuild: boolean;
2122
usePublicApiTeamsService: boolean;
2223
enablePersonalAccessTokens: boolean;
2324
}>({
2425
showUsageView: false,
26+
isUsageBasedBillingEnabled: false,
2527
showUseLastSuccessfulPrebuild: false,
2628
usePublicApiTeamsService: false,
2729
enablePersonalAccessTokens: false,
@@ -34,6 +36,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
3436
const location = useLocation();
3537
const team = getCurrentTeam(location, teams);
3638
const [showUsageView, setShowUsageView] = useState<boolean>(false);
39+
const [isUsageBasedBillingEnabled, setIsUsageBasedBillingEnabled] = useState<boolean>(false);
3740
const [showUseLastSuccessfulPrebuild, setShowUseLastSuccessfulPrebuild] = useState<boolean>(false);
3841
const [usePublicApiTeamsService, setUsePublicApiTeamsService] = useState<boolean>(false);
3942
const [enablePersonalAccessTokens, setPersonalAccessTokensEnabled] = useState<boolean>(false);
@@ -43,6 +46,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
4346
(async () => {
4447
const featureFlags: FeatureFlagConfig = {
4548
usage_view: { defaultValue: false, setter: setShowUsageView },
49+
isUsageBasedBillingEnabled: { defaultValue: false, setter: setIsUsageBasedBillingEnabled },
4650
showUseLastSuccessfulPrebuild: { defaultValue: false, setter: setShowUseLastSuccessfulPrebuild },
4751
publicApiExperimentalTeamsService: { defaultValue: false, setter: setUsePublicApiTeamsService },
4852
personalAccessTokensEnabled: { defaultValue: false, setter: setPersonalAccessTokensEnabled },
@@ -86,6 +90,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
8690
<FeatureFlagContext.Provider
8791
value={{
8892
showUsageView,
93+
isUsageBasedBillingEnabled,
8994
showUseLastSuccessfulPrebuild,
9095
usePublicApiTeamsService,
9196
enablePersonalAccessTokens,

components/dashboard/src/settings/Plans.tsx

Lines changed: 80 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { UserContext } from "../user-context";
2525
import Tooltip from "../components/Tooltip";
2626
import { PaymentContext } from "../payment-context";
2727
import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";
28+
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";
2829

2930
type PlanWithOriginalPrice = Plan & { originalPrice?: number };
3031
type Pending = { pendingSince: number };
@@ -45,6 +46,7 @@ type TeamClaimModal =
4546
export default function () {
4647
const { user, userBillingMode } = useContext(UserContext);
4748
const { currency, setCurrency, isStudent, isChargebeeCustomer } = useContext(PaymentContext);
49+
const { isUsageBasedBillingEnabled } = useContext(FeatureFlagContext);
4850
const [accountStatement, setAccountStatement] = useState<AccountStatement>();
4951
const [availableCoupons, setAvailableCoupons] = useState<PlanCoupon[]>();
5052
const [appliedCoupons, setAppliedCoupons] = useState<PlanCoupon[]>();
@@ -356,6 +358,7 @@ export default function () {
356358
};
357359

358360
const planCards = [];
361+
const canUpgradeToChargebee = !isUsageBasedBillingEnabled;
359362

360363
// Plan card: Free a.k.a. Open Source (or Professional Open Source)
361364
const openSourceFeatures = (
@@ -374,7 +377,7 @@ export default function () {
374377
if (currentPlan.chargebeeId === freePlan.chargebeeId) {
375378
planCards.push(
376379
<PlanCard
377-
isDisabled={!!assignedTs || pendingChargebeeCallback}
380+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
378381
plan={freePlan}
379382
isCurrent={!!accountStatement}
380383
>
@@ -409,7 +412,7 @@ export default function () {
409412
}
410413
planCards.push(
411414
<PlanCard
412-
isDisabled={!!assignedTs || pendingChargebeeCallback}
415+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
413416
plan={targetPlan}
414417
isCurrent={false}
415418
onDowngrade={onDowngrade}
@@ -435,7 +438,7 @@ export default function () {
435438
) : undefined;
436439
planCards.push(
437440
<PlanCard
438-
isDisabled={!!assignedTs || pendingChargebeeCallback}
441+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
439442
plan={applyCoupons(personalPlan, appliedCoupons)}
440443
isCurrent={true}
441444
bottomLabel={bottomLabel}
@@ -474,7 +477,7 @@ export default function () {
474477
}
475478
planCards.push(
476479
<PlanCard
477-
isDisabled={!!assignedTs || pendingChargebeeCallback}
480+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
478481
plan={targetPlan}
479482
isCurrent={false}
480483
onUpgrade={onUpgrade}
@@ -505,7 +508,7 @@ export default function () {
505508
) : undefined;
506509
planCards.push(
507510
<PlanCard
508-
isDisabled={!!assignedTs || pendingChargebeeCallback}
511+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
509512
plan={applyCoupons(professionalPlan, appliedCoupons)}
510513
isCurrent={true}
511514
bottomLabel={bottomLabel}
@@ -545,7 +548,7 @@ export default function () {
545548
}
546549
planCards.push(
547550
<PlanCard
548-
isDisabled={!!assignedTs || pendingChargebeeCallback}
551+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
549552
plan={targetPlan}
550553
isCurrent={!!assignedProfessionalTs}
551554
onUpgrade={onUpgrade}
@@ -583,7 +586,7 @@ export default function () {
583586
) : undefined;
584587
planCards.push(
585588
<PlanCard
586-
isDisabled={!!assignedTs || pendingChargebeeCallback}
589+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
587590
plan={applyCoupons(studentUnleashedPlan, appliedCoupons)}
588591
isCurrent={true}
589592
bottomLabel={bottomLabel}
@@ -599,7 +602,7 @@ export default function () {
599602
) : undefined;
600603
planCards.push(
601604
<PlanCard
602-
isDisabled={!!assignedTs || pendingChargebeeCallback}
605+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
603606
plan={applyCoupons(unleashedPlan, appliedCoupons)}
604607
isCurrent={true}
605608
bottomLabel={bottomLabel}
@@ -618,7 +621,7 @@ export default function () {
618621
}
619622
planCards.push(
620623
<PlanCard
621-
isDisabled={!!assignedTs || pendingChargebeeCallback}
624+
isDisabled={!canUpgradeToChargebee || !!assignedTs || pendingChargebeeCallback}
622625
plan={targetPlan}
623626
isCurrent={!!isUnleashedTsAssigned}
624627
onUpgrade={onUpgrade}
@@ -629,101 +632,87 @@ export default function () {
629632
);
630633
}
631634

632-
const showPlans = userBillingMode && userBillingMode.mode === "chargebee";
633635
return (
634636
<div>
635637
<PageWithSettingsSubMenu title="Plans" subtitle="Manage account usage and billing.">
636-
{showPlans && (
637-
<div className="w-full text-center">
638-
<p className="text-xl text-gray-500">
639-
You are currently using the{" "}
640-
<span className="font-bold">
641-
{Plans.getById(assignedTs?.planId)?.name || currentPlan.name}
642-
</span>{" "}
643-
plan.
638+
<div className="w-full text-center">
639+
<p className="text-xl text-gray-500">
640+
You are currently using the{" "}
641+
<span className="font-bold">{Plans.getById(assignedTs?.planId)?.name || currentPlan.name}</span>{" "}
642+
plan.
643+
</p>
644+
{canUpgradeToChargebee && !assignedTs && (
645+
<p className="text-base w-96 m-auto">
646+
Upgrade your plan to get more hours and more parallel workspaces.
644647
</p>
645-
{!assignedTs && (
646-
<p className="text-base w-96 m-auto">
647-
Upgrade your plan to get more hours and more parallel workspaces.
648-
</p>
649-
)}
650-
<Tooltip
651-
content={`Current billing cycle: ${guessCurrentBillingCycle(currentPlan, accountStatement)
652-
.map((d) => d.toLocaleDateString())
653-
.join(" - ")}`}
648+
)}
649+
<Tooltip
650+
content={`Current billing cycle: ${guessCurrentBillingCycle(currentPlan, accountStatement)
651+
.map((d) => d.toLocaleDateString())
652+
.join(" - ")}`}
653+
>
654+
<p className="mt-2 font-semibold text-gray-500">
655+
Remaining hours:{" "}
656+
{typeof accountStatement?.remainingHours === "number"
657+
? Math.floor(accountStatement.remainingHours * 10) / 10
658+
: accountStatement?.remainingHours}
659+
</p>
660+
</Tooltip>
661+
{typeof accountStatement?.remainingHours === "number" &&
662+
typeof currentPlan.hoursPerMonth === "number" ? (
663+
<progress
664+
value={currentPlan.hoursPerMonth - accountStatement.remainingHours}
665+
max={currentPlan.hoursPerMonth}
666+
/>
667+
) : (
668+
<progress value="0" max="100" />
669+
)}
670+
<p className="text-sm">
671+
<a
672+
className={`gp-link ${isChargebeeCustomer ? "" : "invisible"}`}
673+
href="javascript:void(0)"
674+
onClick={() => {
675+
ChargebeeClient.getOrCreate().then((chargebeeClient) => chargebeeClient.openPortal());
676+
}}
654677
>
655-
<p className="mt-2 font-semibold text-gray-500">
656-
Remaining hours:{" "}
657-
{typeof accountStatement?.remainingHours === "number"
658-
? Math.floor(accountStatement.remainingHours * 10) / 10
659-
: accountStatement?.remainingHours}
660-
</p>
661-
</Tooltip>
662-
{typeof accountStatement?.remainingHours === "number" &&
663-
typeof currentPlan.hoursPerMonth === "number" ? (
664-
<progress
665-
value={currentPlan.hoursPerMonth - accountStatement.remainingHours}
666-
max={currentPlan.hoursPerMonth}
667-
/>
668-
) : (
669-
<progress value="0" max="100" />
678+
Billing
679+
</a>
680+
{!!accountStatement && Plans.isFreePlan(currentPlan.chargebeeId) && (
681+
<span className="pl-6">
682+
{currency === "EUR" ? (
683+
<>
684+
€ /{" "}
685+
<a
686+
className="text-blue-light hover:underline"
687+
href="javascript:void(0)"
688+
onClick={() => setCurrency("USD")}
689+
>
690+
$
691+
</a>
692+
</>
693+
) : (
694+
<>
695+
<a
696+
className="text-blue-light hover:underline"
697+
href="javascript:void(0)"
698+
onClick={() => setCurrency("EUR")}
699+
>
700+
701+
</a>{" "}
702+
/ $
703+
</>
704+
)}
705+
</span>
670706
)}
671-
<p className="text-sm">
672-
<a
673-
className={`gp-link ${isChargebeeCustomer ? "" : "invisible"}`}
674-
href="javascript:void(0)"
675-
onClick={() => {
676-
ChargebeeClient.getOrCreate().then((chargebeeClient) =>
677-
chargebeeClient.openPortal(),
678-
);
679-
}}
680-
>
681-
Billing
682-
</a>
683-
{!!accountStatement && Plans.isFreePlan(currentPlan.chargebeeId) && (
684-
<span className="pl-6">
685-
{currency === "EUR" ? (
686-
<>
687-
€ /{" "}
688-
<a
689-
className="text-blue-light hover:underline"
690-
href="javascript:void(0)"
691-
onClick={() => setCurrency("USD")}
692-
>
693-
$
694-
</a>
695-
</>
696-
) : (
697-
<>
698-
<a
699-
className="text-blue-light hover:underline"
700-
href="javascript:void(0)"
701-
onClick={() => setCurrency("EUR")}
702-
>
703-
704-
</a>{" "}
705-
/ $
706-
</>
707-
)}
708-
</span>
709-
)}
710-
</p>
711-
</div>
712-
)}
707+
</p>
708+
</div>
713709
<div className="mt-4 flex justify-center space-x-3 2xl:space-x-7">{planCards}</div>
714710
{assignedTs && userBillingMode?.mode === "chargebee" && !!userBillingMode.teamNames && (
715711
<Alert type="info" className="mt-10 mx-auto">
716712
<p>Assigned Team Seats</p>
717713
<ul>{userBillingMode.teamNames.join(", ")}</ul>
718714
</Alert>
719715
)}
720-
<InfoBox className="w-2/3 mt-14 mx-auto">
721-
If you are interested in purchasing a plan for a team, purchase a Team plan with one centralized
722-
billing.{" "}
723-
<a className="underline" href="https://www.gitpod.io/docs/teams/">
724-
Learn more
725-
</a>
726-
</InfoBox>
727716
{!!confirmUpgradeToPlan && (
728717
// TODO: Use title and buttons props
729718
<Modal visible={true} onClose={() => setConfirmUpgradeToPlan(undefined)}>

components/dashboard/src/settings/Teams.tsx

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* See License-AGPL.txt in the project root for license information.
55
*/
66

7-
import React, { useContext, useEffect, useRef, useState } from "react";
7+
import { useContext, useEffect, useRef, useState } from "react";
88
import ContextMenu, { ContextMenuEntry } from "../components/ContextMenu";
99
import { getGitpodService } from "../service/service";
1010
import Alert from "../components/Alert";
@@ -23,8 +23,7 @@ import { poll, PollOptions } from "../utils";
2323
import { Disposable } from "@gitpod/gitpod-protocol";
2424
import { PaymentContext } from "../payment-context";
2525
import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";
26-
import { UserContext } from "../user-context";
27-
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
26+
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";
2827

2928
export default function Teams() {
3029
return (
@@ -45,7 +44,7 @@ interface Slot extends TeamSubscriptionSlotResolved {
4544
}
4645

4746
function AllTeams() {
48-
const { userBillingMode } = useContext(UserContext);
47+
const { isUsageBasedBillingEnabled } = useContext(FeatureFlagContext);
4948
const { currency, isStudent, isChargebeeCustomer, setIsChargebeeCustomer } = useContext(PaymentContext);
5049

5150
const [slots, setSlots] = useState<Slot[]>([]);
@@ -453,8 +452,9 @@ function AllTeams() {
453452
return pendingSlotsPurchase && pendingSlotsPurchase.tsId === ts.id;
454453
};
455454

456-
const renderTeams = () => (
457-
<React.Fragment>
455+
const canUpgradeToChargebee = !isUsageBasedBillingEnabled;
456+
return (
457+
<div>
458458
<div className="flex flex-row">
459459
<div className="flex-grow ">
460460
<h3 className="self-center">All Team Plans</h3>
@@ -469,7 +469,9 @@ function AllTeams() {
469469
{getActiveSubs().length > 0 && (
470470
<button
471471
className="self-end my-auto"
472-
disabled={!!pendingPlanPurchase || getAvailableSubTypes().length === 0}
472+
disabled={
473+
!canUpgradeToChargebee || !!pendingPlanPurchase || getAvailableSubTypes().length === 0
474+
}
473475
onClick={() => showCreateTeamModal()}
474476
>
475477
Create Team Plan
@@ -501,7 +503,7 @@ function AllTeams() {
501503
<AddMembersModal onClose={() => setAddMembersModal(undefined)} onBuy={onBuy} {...addMembersModal} />
502504
)}
503505

504-
{getActiveSubs().length === 0 && !pendingPlanPurchase && (
506+
{getActiveSubs().length === 0 && !pendingPlanPurchase && canUpgradeToChargebee && (
505507
<div className="w-full flex h-80 mt-2 rounded-xl bg-gray-100 dark:bg-gray-900">
506508
<div className="m-auto text-center">
507509
<h3 className="self-center text-gray-500 dark:text-gray-400 mb-4">No Active Team Plans</h3>
@@ -602,23 +604,6 @@ function AllTeams() {
602604
))}
603605
</div>
604606
)}
605-
</React.Fragment>
606-
);
607-
608-
// TOOD(gpl) We might want to reduce visibility of those actions similarly to how we guard access to that API, cmp.: https://github.com/gitpod-io/gitpod/blob/db90aefb9f7dc1d062e9cb73241c1fda2eccee4b/components/server/ee/src/workspace/gitpod-server-impl.ts#L2011
609-
const showTeamPlans = BillingMode.showTeamSubscriptionUI(userBillingMode);
610-
return (
611-
<div>
612-
{showTeamPlans ? (
613-
renderTeams()
614-
) : (
615-
<div className="flex flex-row">
616-
<div className="flex-grow ">
617-
<h3 className="self-center">All Team Plans</h3>
618-
<h2>Manage team plans and team members.</h2>
619-
</div>
620-
</div>
621-
)}
622607
</div>
623608
);
624609
}

0 commit comments

Comments
 (0)