Skip to content

Commit 8658b8d

Browse files
committed
[dashboard] Implement a PaymentContext and use it to hide payment features when payment is disabled
1 parent 9b17c59 commit 8658b8d

12 files changed

+160
-95
lines changed

components/dashboard/src/App.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -288,8 +288,8 @@ function App() {
288288

289289
<Route path="/admin/users" component={UserSearch} />
290290
<Route path="/admin/workspaces" component={WorkspacesSearch} />
291-
<Route path="/admin/settings" component={AdminSettings} />
292291
<Route path="/admin/projects" component={ProjectsSearch} />
292+
<Route path="/admin/settings" component={AdminSettings} />
293293

294294
<Route path={["/", "/login"]} exact>
295295
<Redirect to={workspacesPathMain} />
@@ -316,6 +316,9 @@ function App() {
316316
<Route exact path={projectsPathMain} component={Projects} />
317317
<Route exact path={projectsPathMainWithParams} render={(props) => {
318318
const { resourceOrPrebuild } = props.match.params;
319+
if (resourceOrPrebuild === "prebuilds") {
320+
return <Prebuilds />;
321+
}
319322
if (resourceOrPrebuild === "settings") {
320323
return <ProjectSettings />;
321324
}
@@ -325,9 +328,6 @@ function App() {
325328
if (resourceOrPrebuild === "variables") {
326329
return <ProjectVariables />;
327330
}
328-
if (resourceOrPrebuild === "prebuilds") {
329-
return <Prebuilds />;
330-
}
331331
return resourceOrPrebuild ? <Prebuild /> : <Project />;
332332
}} />
333333
</Route>
@@ -355,6 +355,9 @@ function App() {
355355
if (maybeProject === "settings") {
356356
return <TeamSettings />;
357357
}
358+
if (resourceOrPrebuild === "prebuilds") {
359+
return <Prebuilds />;
360+
}
358361
if (resourceOrPrebuild === "settings") {
359362
return <ProjectSettings />;
360363
}
@@ -364,9 +367,6 @@ function App() {
364367
if (resourceOrPrebuild === "variables") {
365368
return <ProjectVariables />;
366369
}
367-
if (resourceOrPrebuild === "prebuilds") {
368-
return <Prebuilds />;
369-
}
370370
return resourceOrPrebuild ? <Prebuild /> : <Project />;
371371
}} />
372372
</Route>)}

components/dashboard/src/Menu.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import CaretUpDown from "./icons/CaretUpDown.svg";
1515
import { getGitpodService, gitpodHostUrl } from "./service/service";
1616
import { UserContext } from "./user-context";
1717
import { TeamsContext, getCurrentTeam } from "./teams/teams-context";
18-
import settingsMenu from './settings/settings-menu';
18+
import getSettingsMenu from './settings/settings-menu';
1919
import { adminMenu } from './admin/admin-menu';
2020
import ContextMenu from "./components/ContextMenu";
2121
import Separator from "./components/Separator";
@@ -24,6 +24,7 @@ import TabMenuItem from "./components/TabMenuItem";
2424
import { getTeamSettingsMenu } from "./teams/TeamSettings";
2525
import { getProjectSettingsMenu } from "./projects/ProjectSettings";
2626
import { ProjectContext } from "./projects/project-context";
27+
import { PaymentContext } from "./payment-context";
2728

2829
interface Entry {
2930
title: string,
@@ -36,18 +37,19 @@ export default function Menu() {
3637
const { teams } = useContext(TeamsContext);
3738
const location = useLocation();
3839
const team = getCurrentTeam(location, teams);
40+
const { showPaymentUI } = useContext(PaymentContext);
3941
const { project, setProject } = useContext(ProjectContext);
4042

4143
const match = useRouteMatch<{ segment1?: string, segment2?: string, segment3?: string }>("/(t/)?:segment1/:segment2?/:segment3?");
4244
const projectSlug = (() => {
4345
const resource = match?.params?.segment2;
44-
if (resource && !["projects", "members", "users", "workspaces", "settings"].includes(resource)) {
46+
if (resource && ![/* team sub-pages */ "projects", "members", "settings", /* admin sub-pages */ "users", "workspaces"].includes(resource)) {
4547
return resource;
4648
}
4749
})();
4850
const prebuildId = (() => {
4951
const resource = projectSlug && match?.params?.segment3;
50-
if (resource !== "workspaces" && resource !== "prebuilds" && resource !== "settings" && resource !== "configure" && resource !== "variables") {
52+
if (resource && ![/* project sub-pages */ "prebuilds", "settings", "configure", "variables"].includes(resource)) {
5153
return resource;
5254
}
5355
})();
@@ -152,6 +154,10 @@ export default function Menu() {
152154

153155
return teamSettingsList;
154156
}
157+
// Admin has no top-level menu
158+
if (adminMenu.flatMap(e => e.link).includes(location.pathname)) {
159+
return [];
160+
}
155161
// User menu
156162
return [
157163
{
@@ -166,7 +172,7 @@ export default function Menu() {
166172
{
167173
title: 'Settings',
168174
link: '/settings',
169-
alternatives: settingsMenu.flatMap(e => e.link)
175+
alternatives: getSettingsMenu({ showPaymentUI }).flatMap(e => e.link)
170176
}
171177
];
172178
})();

components/dashboard/src/index.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import ReactDOM from 'react-dom';
99
import App from './App';
1010
import { UserContextProvider } from './user-context';
1111
import { AdminContextProvider } from './admin-context';
12+
import { PaymentContextProvider } from './payment-context';
1213
import { TeamsContextProvider } from './teams/teams-context';
1314
import { ProjectContextProvider } from './projects/project-context';
1415
import { ThemeContextProvider } from './theme-context';
@@ -20,15 +21,17 @@ ReactDOM.render(
2021
<React.StrictMode>
2122
<UserContextProvider>
2223
<AdminContextProvider>
23-
<TeamsContextProvider>
24-
<ProjectContextProvider>
25-
<ThemeContextProvider>
26-
<BrowserRouter>
27-
<App />
28-
</BrowserRouter>
29-
</ThemeContextProvider>
30-
</ProjectContextProvider>
31-
</TeamsContextProvider>
24+
<PaymentContextProvider>
25+
<TeamsContextProvider>
26+
<ProjectContextProvider>
27+
<ThemeContextProvider>
28+
<BrowserRouter>
29+
<App />
30+
</BrowserRouter>
31+
</ThemeContextProvider>
32+
</ProjectContextProvider>
33+
</TeamsContextProvider>
34+
</PaymentContextProvider>
3235
</AdminContextProvider>
3336
</UserContextProvider>
3437
</React.StrictMode>,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import React, { createContext, useEffect, useState } from 'react';
8+
import { countries } from 'countries-list';
9+
import { Currency } from '@gitpod/gitpod-protocol/lib/plans';
10+
import { getGitpodService } from './service/service';
11+
12+
const PaymentContext = createContext<{
13+
showPaymentUI?: boolean,
14+
setShowPaymentUI: React.Dispatch<boolean>,
15+
currency: Currency,
16+
setCurrency: React.Dispatch<Currency>,
17+
isStudent?: boolean,
18+
setIsStudent: React.Dispatch<boolean>,
19+
isChargebeeCustomer?: boolean,
20+
setIsChargebeeCustomer: React.Dispatch<boolean>,
21+
}>({
22+
setShowPaymentUI: () => null,
23+
currency: 'USD',
24+
setCurrency: () => null,
25+
setIsStudent: () => null,
26+
setIsChargebeeCustomer: () => null,
27+
});
28+
29+
const PaymentContextProvider: React.FC = ({ children }) => {
30+
const [ showPaymentUI, setShowPaymentUI ] = useState<boolean>(false);
31+
const [ currency, setCurrency ] = useState<Currency>('USD');
32+
const [ isStudent, setIsStudent ] = useState<boolean>();
33+
const [ isChargebeeCustomer, setIsChargebeeCustomer ] = useState<boolean>();
34+
35+
useEffect(() => {
36+
const { server } = getGitpodService();
37+
Promise.all([
38+
server.getShowPaymentUI().then(v => () => setShowPaymentUI(v)),
39+
server.getClientRegion().then(v => () => {
40+
// @ts-ignore
41+
setCurrency(countries[v]?.currency === 'EUR' ? 'EUR' : 'USD');
42+
}),
43+
server.isStudent().then(v => () => setIsStudent(v)),
44+
server.isChargebeeCustomer().then(v => () => setIsChargebeeCustomer(v)),
45+
]).then(setters => setters.forEach(s => s()));
46+
}, []);
47+
48+
return (
49+
<PaymentContext.Provider value={{
50+
showPaymentUI,
51+
setShowPaymentUI,
52+
currency,
53+
setCurrency,
54+
isStudent,
55+
setIsStudent,
56+
isChargebeeCustomer,
57+
setIsChargebeeCustomer,
58+
}}>
59+
{children}
60+
</PaymentContext.Provider>
61+
)
62+
}
63+
64+
export { PaymentContext, PaymentContextProvider };

components/dashboard/src/settings/Account.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import { useContext, useState } from "react";
99
import { PageWithSubMenu } from "../components/PageWithSubMenu";
1010
import { getGitpodService, gitpodHostUrl } from "../service/service";
1111
import { UserContext } from "../user-context";
12-
import settingsMenu from "./settings-menu";
12+
import getSettingsMenu from "./settings-menu";
1313
import ConfirmationModal from "../components/ConfirmationModal";
1414
import CodeText from "../components/CodeText";
15+
import { PaymentContext } from "../payment-context";
1516

1617
export default function Account() {
1718
const { user } = useContext(UserContext);
19+
const { showPaymentUI } = useContext(PaymentContext);
1820

1921
const [modal, setModal] = useState(false);
2022
const [typedEmail, setTypedEmail] = useState('');
@@ -45,7 +47,7 @@ export default function Account() {
4547
<input autoFocus className="w-full" type="text" onChange={e => setTypedEmail(e.target.value)}></input>
4648
</ConfirmationModal>
4749

48-
<PageWithSubMenu subMenu={settingsMenu} title='Account' subtitle='Manage account and Git configuration.'>
50+
<PageWithSubMenu subMenu={getSettingsMenu({showPaymentUI})} title='Account' subtitle='Manage account and Git configuration.'>
4951
<h3>Profile</h3>
5052
<p className="text-base text-gray-500 pb-4 max-w-2xl">The following information will be used to set up Git configuration. You can override Git author name and email per project by using the default environment variables <CodeText>GIT_AUTHOR_NAME</CodeText>, <CodeText>GIT_COMMITTER_NAME</CodeText>, <CodeText>GIT_AUTHOR_EMAIL</CodeText> and <CodeText>GIT_COMMITTER_EMAIL</CodeText>.</p>
5153
<div className="flex flex-col lg:flex-row">

components/dashboard/src/settings/EnvironmentVariables.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
*/
66

77
import { UserEnvVarValue } from "@gitpod/gitpod-protocol";
8-
import { useEffect, useRef, useState } from "react";
8+
import { useContext, useEffect, useRef, useState } from "react";
99
import ConfirmationModal from "../components/ConfirmationModal";
1010
import { Item, ItemField, ItemFieldContextMenu, ItemsList } from "../components/ItemsList";
1111
import Modal from "../components/Modal";
1212
import { PageWithSubMenu } from "../components/PageWithSubMenu";
1313
import { getGitpodService } from "../service/service";
14-
import settingsMenu from "./settings-menu";
14+
import getSettingsMenu from "./settings-menu";
1515
import CodeText from "../components/CodeText";
16+
import { PaymentContext } from "../payment-context";
1617

1718
interface EnvVarModalProps {
1819
envVar: UserEnvVarValue;
@@ -108,6 +109,7 @@ function sortEnvVars(a: UserEnvVarValue, b: UserEnvVarValue) {
108109
}
109110

110111
export default function EnvVars() {
112+
const { showPaymentUI } = useContext(PaymentContext);
111113
const [envVars, setEnvVars] = useState([] as UserEnvVarValue[]);
112114
const [currentEnvVar, setCurrentEnvVar] = useState({ name: '', value: '', repositoryPattern: '' } as UserEnvVarValue);
113115
const [isAddEnvVarModalVisible, setAddEnvVarModalVisible] = useState(false);
@@ -181,7 +183,7 @@ export default function EnvVars() {
181183
return '';
182184
};
183185

184-
return <PageWithSubMenu subMenu={settingsMenu} title='Variables' subtitle='Configure environment variables for all workspaces.'>
186+
return <PageWithSubMenu subMenu={getSettingsMenu({ showPaymentUI })} title='Variables' subtitle='Configure environment variables for all workspaces.'>
185187
{isAddEnvVarModalVisible && <AddEnvVarModal
186188
save={save}
187189
envVar={currentEnvVar}

components/dashboard/src/settings/Integrations.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ import Modal from "../components/Modal";
1616
import { PageWithSubMenu } from "../components/PageWithSubMenu";
1717
import copy from '../images/copy.svg';
1818
import exclamation from '../images/exclamation.svg';
19+
import { PaymentContext } from "../payment-context";
1920
import { openAuthorizeWindow } from "../provider-utils";
2021
import { getGitpodService, gitpodHostUrl } from "../service/service";
2122
import { UserContext } from "../user-context";
2223
import { SelectAccountModal } from "./SelectAccountModal";
23-
import settingsMenu from "./settings-menu";
24+
import getSettingsMenu from "./settings-menu";
2425

2526
export default function Integrations() {
27+
const { showPaymentUI } = useContext(PaymentContext);
2628

2729
return (<div>
28-
<PageWithSubMenu subMenu={settingsMenu} title='Integrations' subtitle='Manage permissions for Git providers and integrations.'>
30+
<PageWithSubMenu subMenu={getSettingsMenu({ showPaymentUI })} title='Integrations' subtitle='Manage permissions for Git providers and integrations.'>
2931
<GitProviders />
3032
<div className="h-12"></div>
3133
<GitIntegrations />
@@ -35,7 +37,6 @@ export default function Integrations() {
3537

3638

3739
function GitProviders() {
38-
3940
const { user, setUser } = useContext(UserContext);
4041

4142
const [authProviders, setAuthProviders] = useState<AuthProviderInfo[]>([]);

components/dashboard/src/settings/Notifications.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import { getGitpodService } from "../service/service";
99
import { UserContext } from "../user-context";
1010
import CheckBox from "../components/CheckBox";
1111
import { PageWithSubMenu } from "../components/PageWithSubMenu";
12-
import settingsMenu from "./settings-menu";
12+
import getSettingsMenu from "./settings-menu";
13+
import { PaymentContext } from "../payment-context";
1314

1415
export default function Notifications() {
1516
const { user, setUser } = useContext(UserContext);
17+
const { showPaymentUI } = useContext(PaymentContext);
1618
const [isOnboardingMail, setOnboardingMail] = useState(!!user?.additionalData?.emailNotificationSettings?.allowsOnboardingMail);
1719
const [isChangelogMail, setChangelogMail] = useState(!!user?.additionalData?.emailNotificationSettings?.allowsChangelogMail);
1820
const [isDevXMail, setDevXMail] = useState(!!user?.additionalData?.emailNotificationSettings?.allowsDevXMail);
@@ -82,7 +84,7 @@ export default function Notifications() {
8284

8385
return (
8486
<div>
85-
<PageWithSubMenu subMenu={settingsMenu} title='Notifications' subtitle='Choose when to be notified.'>
87+
<PageWithSubMenu subMenu={getSettingsMenu({ showPaymentUI })} title='Notifications' subtitle='Choose when to be notified.'>
8688
<h3>Email Notification Preferences</h3>
8789
<CheckBox
8890
title="Account Notifications [required]"

components/dashboard/src/settings/Plans.tsx

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
*/
66

77
import React, { useState, useEffect, useContext } from "react";
8-
import { countries } from 'countries-list';
98
import { AccountStatement, Subscription, UserPaidSubscription, AssignedTeamSubscription, CreditDescription } from "@gitpod/gitpod-protocol/lib/accounting-protocol";
109
import { PlanCoupon, GithubUpgradeURL } from "@gitpod/gitpod-protocol/lib/payment-protocol";
11-
import { Plans, Plan, Currency, PlanType } from "@gitpod/gitpod-protocol/lib/plans";
10+
import { Plans, Plan, PlanType } from "@gitpod/gitpod-protocol/lib/plans";
1211
import { ChargebeeClient } from "../chargebee/chargebee-client";
1312
import AlertBox from "../components/AlertBox";
1413
import InfoBox from "../components/InfoBox";
@@ -18,7 +17,8 @@ import { getGitpodService } from "../service/service";
1817
import { UserContext } from "../user-context";
1918
import { PageWithSubMenu } from "../components/PageWithSubMenu";
2019
import Tooltip from "../components/Tooltip";
21-
import settingsMenu from "./settings-menu";
20+
import getSettingsMenu from "./settings-menu";
21+
import { PaymentContext } from "../payment-context";
2222

2323
type PlanWithOriginalPrice = Plan & { originalPrice?: number };
2424
type Pending = { pendingSince: number };
@@ -36,11 +36,8 @@ type TeamClaimModal = {
3636

3737
export default function () {
3838
const { user } = useContext(UserContext);
39-
const [ showPaymentUI, setShowPaymentUI ] = useState<boolean>(false);
39+
const { showPaymentUI, currency, setCurrency, isStudent, isChargebeeCustomer } = useContext(PaymentContext);
4040
const [ accountStatement, setAccountStatement ] = useState<AccountStatement>();
41-
const [ isChargebeeCustomer, setIsChargebeeCustomer ] = useState<boolean>();
42-
const [ isStudent, setIsStudent ] = useState<boolean>();
43-
const [ currency, setCurrency ] = useState<Currency>('USD');
4441
const [ availableCoupons, setAvailableCoupons ] = useState<PlanCoupon[]>();
4542
const [ appliedCoupons, setAppliedCoupons ] = useState<PlanCoupon[]>();
4643
const [ gitHubUpgradeUrls, setGitHubUpgradeUrls ] = useState<GithubUpgradeURL[]>();
@@ -52,14 +49,7 @@ export default function () {
5249
useEffect(() => {
5350
const { server } = getGitpodService();
5451
Promise.all([
55-
server.getShowPaymentUI().then(v => () => setShowPaymentUI(v)),
5652
server.getAccountStatement({}).then(v => () => setAccountStatement(v)),
57-
server.isChargebeeCustomer().then(v => () => setIsChargebeeCustomer(v)),
58-
server.isStudent().then(v => () => setIsStudent(v)),
59-
server.getClientRegion().then(v => () => {
60-
// @ts-ignore
61-
setCurrency(countries[v]?.currency === 'EUR' ? 'EUR' : 'USD');
62-
}),
6353
server.getAvailableCoupons().then(v => () => setAvailableCoupons(v)),
6454
server.getAppliedCoupons().then(v => () => setAppliedCoupons(v)),
6555
server.getGithubUpgradeUrls().then(v => () => setGitHubUpgradeUrls(v))
@@ -425,7 +415,7 @@ export default function () {
425415
}
426416

427417
return <div>
428-
<PageWithSubMenu subMenu={settingsMenu} title='Plans' subtitle='Manage account usage and billing.'>
418+
<PageWithSubMenu subMenu={getSettingsMenu({ showPaymentUI })} title='Plans' subtitle='Manage account usage and billing.'>
429419
{showPaymentUI && <div className="w-full text-center">
430420
<p className="text-xl text-gray-500">You are currently using the <span className="font-bold">{Plans.getById(assignedTs?.planId)?.name || currentPlan.name}</span> plan.</p>
431421
{!assignedTs && (

0 commit comments

Comments
 (0)