Skip to content

Commit 2975e79

Browse files
committed
[dashboard] Prevent upgrading to Chargebee once Usage-Based Pricing is enabled
1 parent 75a7e4f commit 2975e79

File tree

3 files changed

+93
-114
lines changed

3 files changed

+93
-114
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: 77 additions & 89 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[]>();
@@ -435,7 +437,7 @@ export default function () {
435437
) : undefined;
436438
planCards.push(
437439
<PlanCard
438-
isDisabled={!!assignedTs || pendingChargebeeCallback}
440+
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
439441
plan={applyCoupons(personalPlan, appliedCoupons)}
440442
isCurrent={true}
441443
bottomLabel={bottomLabel}
@@ -474,7 +476,7 @@ export default function () {
474476
}
475477
planCards.push(
476478
<PlanCard
477-
isDisabled={!!assignedTs || pendingChargebeeCallback}
479+
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
478480
plan={targetPlan}
479481
isCurrent={false}
480482
onUpgrade={onUpgrade}
@@ -505,7 +507,7 @@ export default function () {
505507
) : undefined;
506508
planCards.push(
507509
<PlanCard
508-
isDisabled={!!assignedTs || pendingChargebeeCallback}
510+
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
509511
plan={applyCoupons(professionalPlan, appliedCoupons)}
510512
isCurrent={true}
511513
bottomLabel={bottomLabel}
@@ -545,7 +547,7 @@ export default function () {
545547
}
546548
planCards.push(
547549
<PlanCard
548-
isDisabled={!!assignedTs || pendingChargebeeCallback}
550+
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
549551
plan={targetPlan}
550552
isCurrent={!!assignedProfessionalTs}
551553
onUpgrade={onUpgrade}
@@ -583,7 +585,7 @@ export default function () {
583585
) : undefined;
584586
planCards.push(
585587
<PlanCard
586-
isDisabled={!!assignedTs || pendingChargebeeCallback}
588+
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
587589
plan={applyCoupons(studentUnleashedPlan, appliedCoupons)}
588590
isCurrent={true}
589591
bottomLabel={bottomLabel}
@@ -599,7 +601,7 @@ export default function () {
599601
) : undefined;
600602
planCards.push(
601603
<PlanCard
602-
isDisabled={!!assignedTs || pendingChargebeeCallback}
604+
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
603605
plan={applyCoupons(unleashedPlan, appliedCoupons)}
604606
isCurrent={true}
605607
bottomLabel={bottomLabel}
@@ -618,7 +620,7 @@ export default function () {
618620
}
619621
planCards.push(
620622
<PlanCard
621-
isDisabled={!!assignedTs || pendingChargebeeCallback}
623+
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
622624
plan={targetPlan}
623625
isCurrent={!!isUnleashedTsAssigned}
624626
onUpgrade={onUpgrade}
@@ -629,101 +631,87 @@ export default function () {
629631
);
630632
}
631633

632-
const showPlans = userBillingMode && userBillingMode.mode === "chargebee";
633634
return (
634635
<div>
635636
<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.
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">{Plans.getById(assignedTs?.planId)?.name || currentPlan.name}</span>{" "}
641+
plan.
642+
</p>
643+
{!isUsageBasedBillingEnabled && !assignedTs && (
644+
<p className="text-base w-96 m-auto">
645+
Upgrade your plan to get more hours and more parallel workspaces.
644646
</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(" - ")}`}
647+
)}
648+
<Tooltip
649+
content={`Current billing cycle: ${guessCurrentBillingCycle(currentPlan, accountStatement)
650+
.map((d) => d.toLocaleDateString())
651+
.join(" - ")}`}
652+
>
653+
<p className="mt-2 font-semibold text-gray-500">
654+
Remaining hours:{" "}
655+
{typeof accountStatement?.remainingHours === "number"
656+
? Math.floor(accountStatement.remainingHours * 10) / 10
657+
: accountStatement?.remainingHours}
658+
</p>
659+
</Tooltip>
660+
{typeof accountStatement?.remainingHours === "number" &&
661+
typeof currentPlan.hoursPerMonth === "number" ? (
662+
<progress
663+
value={currentPlan.hoursPerMonth - accountStatement.remainingHours}
664+
max={currentPlan.hoursPerMonth}
665+
/>
666+
) : (
667+
<progress value="0" max="100" />
668+
)}
669+
<p className="text-sm">
670+
<a
671+
className={`gp-link ${isChargebeeCustomer ? "" : "invisible"}`}
672+
href="javascript:void(0)"
673+
onClick={() => {
674+
ChargebeeClient.getOrCreate().then((chargebeeClient) => chargebeeClient.openPortal());
675+
}}
654676
>
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" />
677+
Billing
678+
</a>
679+
{!!accountStatement && Plans.isFreePlan(currentPlan.chargebeeId) && (
680+
<span className="pl-6">
681+
{currency === "EUR" ? (
682+
<>
683+
€ /{" "}
684+
<a
685+
className="text-blue-light hover:underline"
686+
href="javascript:void(0)"
687+
onClick={() => setCurrency("USD")}
688+
>
689+
$
690+
</a>
691+
</>
692+
) : (
693+
<>
694+
<a
695+
className="text-blue-light hover:underline"
696+
href="javascript:void(0)"
697+
onClick={() => setCurrency("EUR")}
698+
>
699+
700+
</a>{" "}
701+
/ $
702+
</>
703+
)}
704+
</span>
670705
)}
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-
)}
706+
</p>
707+
</div>
713708
<div className="mt-4 flex justify-center space-x-3 2xl:space-x-7">{planCards}</div>
714709
{assignedTs && userBillingMode?.mode === "chargebee" && !!userBillingMode.teamNames && (
715710
<Alert type="info" className="mt-10 mx-auto">
716711
<p>Assigned Team Seats</p>
717712
<ul>{userBillingMode.teamNames.join(", ")}</ul>
718713
</Alert>
719714
)}
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>
727715
{!!confirmUpgradeToPlan && (
728716
// TODO: Use title and buttons props
729717
<Modal visible={true} onClose={() => setConfirmUpgradeToPlan(undefined)}>

components/dashboard/src/settings/Teams.tsx

Lines changed: 11 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,8 @@ function AllTeams() {
453452
return pendingSlotsPurchase && pendingSlotsPurchase.tsId === ts.id;
454453
};
455454

456-
const renderTeams = () => (
457-
<React.Fragment>
455+
return (
456+
<div>
458457
<div className="flex flex-row">
459458
<div className="flex-grow ">
460459
<h3 className="self-center">All Team Plans</h3>
@@ -469,7 +468,11 @@ function AllTeams() {
469468
{getActiveSubs().length > 0 && (
470469
<button
471470
className="self-end my-auto"
472-
disabled={!!pendingPlanPurchase || getAvailableSubTypes().length === 0}
471+
disabled={
472+
!!isUsageBasedBillingEnabled ||
473+
!!pendingPlanPurchase ||
474+
getAvailableSubTypes().length === 0
475+
}
473476
onClick={() => showCreateTeamModal()}
474477
>
475478
Create Team Plan
@@ -501,7 +504,7 @@ function AllTeams() {
501504
<AddMembersModal onClose={() => setAddMembersModal(undefined)} onBuy={onBuy} {...addMembersModal} />
502505
)}
503506

504-
{getActiveSubs().length === 0 && !pendingPlanPurchase && (
507+
{getActiveSubs().length === 0 && !pendingPlanPurchase && !isUsageBasedBillingEnabled && (
505508
<div className="w-full flex h-80 mt-2 rounded-xl bg-gray-100 dark:bg-gray-900">
506509
<div className="m-auto text-center">
507510
<h3 className="self-center text-gray-500 dark:text-gray-400 mb-4">No Active Team Plans</h3>
@@ -602,23 +605,6 @@ function AllTeams() {
602605
))}
603606
</div>
604607
)}
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-
)}
622608
</div>
623609
);
624610
}

0 commit comments

Comments
 (0)