Skip to content

Commit 1bfb882

Browse files
committed
[dashboard] Block UI based on BillingMode
1 parent 406d0ca commit 1bfb882

File tree

12 files changed

+117
-74
lines changed

12 files changed

+117
-74
lines changed

components/dashboard/src/Menu.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { ProjectContext } from "./projects/project-context";
2626
import { PaymentContext } from "./payment-context";
2727
import FeedbackFormModal from "./feedback-form/FeedbackModal";
2828
import { inResource, isGitpodIo } from "./utils";
29-
import { FeatureFlagContext } from "./contexts/FeatureFlagContext";
29+
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
3030

3131
interface Entry {
3232
title: string;
@@ -35,13 +35,12 @@ interface Entry {
3535
}
3636

3737
export default function Menu() {
38-
const { user } = useContext(UserContext);
38+
const { user, userBillingMode, refreshUserBillingMode } = useContext(UserContext);
3939
const { teams } = useContext(TeamsContext);
4040
const location = useLocation();
4141
const team = getCurrentTeam(location, teams);
42-
const { showPaymentUI, setShowPaymentUI, setCurrency, setIsStudent, setIsChargebeeCustomer } =
43-
useContext(PaymentContext);
44-
const { showUsageBasedPricingUI } = useContext(FeatureFlagContext);
42+
const { setCurrency, setIsStudent, setIsChargebeeCustomer } = useContext(PaymentContext);
43+
const [teamBillingMode, setTeamBillingMode] = useState<BillingMode | undefined>(undefined);
4544
const { project, setProject } = useContext(ProjectContext);
4645
const [isFeedbackFormVisible, setFeedbackFormVisible] = useState<boolean>(false);
4746

@@ -146,14 +145,19 @@ export default function Menu() {
146145
useEffect(() => {
147146
const { server } = getGitpodService();
148147
Promise.all([
149-
server.getShowPaymentUI().then((v) => () => setShowPaymentUI(v)),
150148
server.getClientRegion().then((v) => () => {
151149
// @ts-ignore
152150
setCurrency(countries[v]?.currency === "EUR" ? "EUR" : "USD");
153151
}),
154152
server.isStudent().then((v) => () => setIsStudent(v)),
155153
server.isChargebeeCustomer().then((v) => () => setIsChargebeeCustomer(v)),
156154
]).then((setters) => setters.forEach((s) => s()));
155+
156+
// Refresh billing mode
157+
refreshUserBillingMode();
158+
if (team) {
159+
server.getBillingModeForTeam(team.id).then(setTeamBillingMode);
160+
}
157161
}, []);
158162

159163
const teamOrUserSlug = !!team ? "/t/" + team.slug : "/projects";
@@ -191,7 +195,7 @@ export default function Menu() {
191195
link: `/t/${team.slug}/members`,
192196
},
193197
];
194-
if (showUsageBasedPricingUI) {
198+
if (teamBillingMode && teamBillingMode.mode === "usage-based") {
195199
teamSettingsList.push({
196200
title: "Usage",
197201
link: `/t/${team.slug}/usage`,
@@ -201,7 +205,7 @@ export default function Menu() {
201205
teamSettingsList.push({
202206
title: "Settings",
203207
link: `/t/${team.slug}/settings`,
204-
alternatives: getTeamSettingsMenu({ team, showPaymentUI }).flatMap((e) => e.link),
208+
alternatives: getTeamSettingsMenu({ team, billingMode: teamBillingMode }).flatMap((e) => e.link),
205209
});
206210
}
207211

@@ -216,7 +220,7 @@ export default function Menu() {
216220
{
217221
title: "Settings",
218222
link: "/settings",
219-
alternatives: getSettingsMenu({ showPaymentUI, showUsageBasedPricingUI }).flatMap((e) => e.link),
223+
alternatives: getSettingsMenu({ userBillingMode }).flatMap((e) => e.link),
220224
},
221225
];
222226
})();

components/dashboard/src/contexts/FeatureFlagContext.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@ interface FeatureFlagConfig {
1616
}
1717

1818
const FeatureFlagContext = createContext<{
19-
showUsageBasedPricingUI: boolean;
2019
showWorkspaceClassesUI: boolean;
2120
showPersistentVolumeClaimUI: boolean;
2221
}>({
23-
showUsageBasedPricingUI: false,
2422
showWorkspaceClassesUI: false,
2523
showPersistentVolumeClaimUI: false,
2624
});
@@ -31,12 +29,10 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
3129
const { project } = useContext(ProjectContext);
3230
const location = useLocation();
3331
const team = getCurrentTeam(location, teams);
34-
const [showUsageBasedPricingUI, setShowUsageBasedPricingUI] = useState<boolean>(false);
3532
const [showWorkspaceClassesUI, setShowWorkspaceClassesUI] = useState<boolean>(false);
3633
const [showPersistentVolumeClaimUI, setShowPersistentVolumeClaimUI] = useState<boolean>(false);
3734

3835
const featureFlags: FeatureFlagConfig = {
39-
isUsageBasedBillingEnabled: { defaultValue: false, setter: setShowUsageBasedPricingUI },
4036
workspace_classes: { defaultValue: true, setter: setShowWorkspaceClassesUI },
4137
persistent_volume_claim: { defaultValue: true, setter: setShowPersistentVolumeClaimUI },
4238
};
@@ -58,9 +54,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
5854
}, [user, teams, team, project]);
5955

6056
return (
61-
<FeatureFlagContext.Provider
62-
value={{ showUsageBasedPricingUI, showWorkspaceClassesUI, showPersistentVolumeClaimUI }}
63-
>
57+
<FeatureFlagContext.Provider value={{ showWorkspaceClassesUI, showPersistentVolumeClaimUI }}>
6458
{children}
6559
</FeatureFlagContext.Provider>
6660
);

components/dashboard/src/payment-context.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,27 @@ import React, { createContext, useState } from "react";
88
import { Currency } from "@gitpod/gitpod-protocol/lib/plans";
99

1010
const PaymentContext = createContext<{
11-
showPaymentUI?: boolean;
12-
setShowPaymentUI: React.Dispatch<boolean>;
1311
currency: Currency;
1412
setCurrency: React.Dispatch<Currency>;
1513
isStudent?: boolean;
1614
setIsStudent: React.Dispatch<boolean>;
1715
isChargebeeCustomer?: boolean;
1816
setIsChargebeeCustomer: React.Dispatch<boolean>;
1917
}>({
20-
setShowPaymentUI: () => null,
2118
currency: "USD",
2219
setCurrency: () => null,
2320
setIsStudent: () => null,
2421
setIsChargebeeCustomer: () => null,
2522
});
2623

2724
const PaymentContextProvider: React.FC = ({ children }) => {
28-
const [showPaymentUI, setShowPaymentUI] = useState<boolean>();
2925
const [currency, setCurrency] = useState<Currency>("USD");
3026
const [isStudent, setIsStudent] = useState<boolean>();
3127
const [isChargebeeCustomer, setIsChargebeeCustomer] = useState<boolean>();
3228

3329
return (
3430
<PaymentContext.Provider
3531
value={{
36-
showPaymentUI,
37-
setShowPaymentUI,
3832
currency,
3933
setCurrency,
4034
isStudent,

components/dashboard/src/settings/PageWithSettingsSubMenu.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66

77
import { useContext } from "react";
88
import { PageWithSubMenu } from "../components/PageWithSubMenu";
9-
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";
10-
import { PaymentContext } from "../payment-context";
9+
import { UserContext } from "../user-context";
1110
import getSettingsMenu from "./settings-menu";
1211

1312
export interface PageWithAdminSubMenuProps {
@@ -17,15 +16,10 @@ export interface PageWithAdminSubMenuProps {
1716
}
1817

1918
export function PageWithSettingsSubMenu({ title, subtitle, children }: PageWithAdminSubMenuProps) {
20-
const { showUsageBasedPricingUI } = useContext(FeatureFlagContext);
21-
const { showPaymentUI } = useContext(PaymentContext);
19+
const { userBillingMode } = useContext(UserContext);
2220

2321
return (
24-
<PageWithSubMenu
25-
subMenu={getSettingsMenu({ showPaymentUI, showUsageBasedPricingUI })}
26-
title={title}
27-
subtitle={subtitle}
28-
>
22+
<PageWithSubMenu subMenu={getSettingsMenu({ userBillingMode })} title={title} subtitle={subtitle}>
2923
{children}
3024
</PageWithSubMenu>
3125
);

components/dashboard/src/settings/Plans.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ type TeamClaimModal =
4343
};
4444

4545
export default function () {
46-
const { user } = useContext(UserContext);
47-
const { showPaymentUI, currency, setCurrency, isStudent, isChargebeeCustomer } = useContext(PaymentContext);
46+
const { user, userBillingMode } = useContext(UserContext);
47+
const { currency, setCurrency, isStudent, isChargebeeCustomer } = useContext(PaymentContext);
4848
const [accountStatement, setAccountStatement] = useState<AccountStatement>();
4949
const [availableCoupons, setAvailableCoupons] = useState<PlanCoupon[]>();
5050
const [appliedCoupons, setAppliedCoupons] = useState<PlanCoupon[]>();
@@ -632,10 +632,11 @@ export default function () {
632632
);
633633
}
634634

635+
const showPlans = userBillingMode && userBillingMode.mode === "chargebee";
635636
return (
636637
<div>
637638
<PageWithSettingsSubMenu title="Plans" subtitle="Manage account usage and billing.">
638-
{showPaymentUI && (
639+
{showPlans && (
639640
<div className="w-full text-center">
640641
<p className="text-xl text-gray-500">
641642
You are currently using the{" "}

components/dashboard/src/settings/Teams.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +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";
2627

2728
export default function Teams() {
2829
return (
@@ -43,8 +44,8 @@ interface Slot extends TeamSubscriptionSlotResolved {
4344
}
4445

4546
function AllTeams() {
46-
const { showPaymentUI, currency, isStudent, isChargebeeCustomer, setIsChargebeeCustomer } =
47-
useContext(PaymentContext);
47+
const { userBillingMode } = useContext(UserContext);
48+
const { currency, isStudent, isChargebeeCustomer, setIsChargebeeCustomer } = useContext(PaymentContext);
4849

4950
const [slots, setSlots] = useState<Slot[]>([]);
5051
const [teamSubscriptions, setTeamSubscriptions] = useState<TeamSubscription[]>([]);
@@ -603,9 +604,13 @@ function AllTeams() {
603604
</React.Fragment>
604605
);
605606

607+
const showTeamPlans =
608+
userBillingMode &&
609+
(userBillingMode.mode === "chargebee" ||
610+
(userBillingMode.mode === "usage-based" && !!userBillingMode.hasChargebeeTeamPlan));
606611
return (
607612
<div>
608-
{showPaymentUI ? (
613+
{showTeamPlans ? (
609614
renderTeams()
610615
) : (
611616
<div className="flex flex-row">

components/dashboard/src/settings/settings-menu.ts

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

7+
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
78
import {
89
settingsPathAccount,
910
settingsPathBilling,
@@ -17,7 +18,7 @@ import {
1718
settingsPathSSHKeys,
1819
} from "./settings.routes";
1920

20-
export default function getSettingsMenu(params: { showPaymentUI?: boolean; showUsageBasedPricingUI?: boolean }) {
21+
export default function getSettingsMenu(params: { userBillingMode?: BillingMode }) {
2122
return [
2223
{
2324
title: "Account",
@@ -27,26 +28,7 @@ export default function getSettingsMenu(params: { showPaymentUI?: boolean; showU
2728
title: "Notifications",
2829
link: [settingsPathNotifications],
2930
},
30-
...(params.showPaymentUI
31-
? [
32-
...(params.showUsageBasedPricingUI
33-
? [
34-
{
35-
title: "Billing",
36-
link: [settingsPathBilling],
37-
},
38-
]
39-
: []),
40-
{
41-
title: "Plans",
42-
link: [settingsPathPlans],
43-
},
44-
{
45-
title: "Team Plans",
46-
link: [settingsPathTeams],
47-
},
48-
]
49-
: []),
31+
...renderBillingMenuEntries(params.userBillingMode),
5032
{
5133
title: "Variables",
5234
link: [settingsPathVariables],
@@ -65,3 +47,40 @@ export default function getSettingsMenu(params: { showPaymentUI?: boolean; showU
6547
},
6648
];
6749
}
50+
51+
function renderBillingMenuEntries(billingMode?: BillingMode) {
52+
if (!billingMode) {
53+
return [];
54+
}
55+
switch (billingMode.mode) {
56+
case "none":
57+
return [];
58+
case "chargebee":
59+
return [
60+
{
61+
title: "Plans",
62+
link: [settingsPathPlans],
63+
},
64+
{
65+
title: "Team Plans",
66+
link: [settingsPathTeams],
67+
},
68+
];
69+
case "usage-based":
70+
return [
71+
// We need to allow access to "Team Plans" here, at least for owners.
72+
...(billingMode.hasChargebeeTeamSubscription
73+
? [
74+
{
75+
title: "Team Plans",
76+
link: [settingsPathTeams],
77+
},
78+
]
79+
: []),
80+
{
81+
title: "Billing",
82+
link: [settingsPathBilling],
83+
},
84+
];
85+
}
86+
}

components/dashboard/src/teams/TeamBilling.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { getGitpodService } from "../service/service";
2222
import { getCurrentTeam, TeamsContext } from "./teams-context";
2323
import { getTeamSettingsMenu } from "./TeamSettings";
2424
import TeamUsageBasedBilling from "./TeamUsageBasedBilling";
25+
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
2526

2627
type PendingPlan = Plan & { pendingSince: number };
2728

@@ -31,7 +32,8 @@ export default function TeamBilling() {
3132
const team = getCurrentTeam(location, teams);
3233
const [members, setMembers] = useState<TeamMemberInfo[]>([]);
3334
const [teamSubscription, setTeamSubscription] = useState<TeamSubscription2 | undefined>();
34-
const { showPaymentUI, currency, setCurrency } = useContext(PaymentContext);
35+
const { currency, setCurrency } = useContext(PaymentContext);
36+
const [teamBillingMode, setTeamBillingMode] = useState<BillingMode | undefined>(undefined);
3537
const [pendingTeamPlan, setPendingTeamPlan] = useState<PendingPlan | undefined>();
3638
const [pollTeamSubscriptionTimeout, setPollTeamSubscriptionTimeout] = useState<NodeJS.Timeout | undefined>();
3739

@@ -40,12 +42,14 @@ export default function TeamBilling() {
4042
return;
4143
}
4244
(async () => {
43-
const [memberInfos, subscription] = await Promise.all([
45+
const [memberInfos, subscription, teamBillingMode] = await Promise.all([
4446
getGitpodService().server.getTeamMembers(team.id),
4547
getGitpodService().server.getTeamSubscription(team.id),
48+
getGitpodService().server.getBillingModeForTeam(team.id),
4649
]);
4750
setMembers(memberInfos);
4851
setTeamSubscription(subscription);
52+
setTeamBillingMode(teamBillingMode);
4953
})();
5054
}, [team]);
5155

@@ -140,7 +144,7 @@ export default function TeamBilling() {
140144

141145
return (
142146
<PageWithSubMenu
143-
subMenu={getTeamSettingsMenu({ team, showPaymentUI })}
147+
subMenu={getTeamSettingsMenu({ team, billingMode: teamBillingMode })}
144148
title="Billing"
145149
subtitle="Manage team billing and plans."
146150
>

0 commit comments

Comments
 (0)