diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql
index cd7026e8..f3db6a75 100644
--- a/packages/backend/src/graphql/schema.graphql
+++ b/packages/backend/src/graphql/schema.graphql
@@ -466,7 +466,7 @@ type AppHealth {
}
type GetAutomatischInfo {
- isCloud: String
+ isCloud: Boolean
}
type GetUsageData {
diff --git a/packages/web/src/components/PaymentInformation/index.ee.tsx b/packages/web/src/components/PaymentInformation/index.ee.tsx
new file mode 100644
index 00000000..e0f8e22e
--- /dev/null
+++ b/packages/web/src/components/PaymentInformation/index.ee.tsx
@@ -0,0 +1,28 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+
+import PageTitle from 'components/PageTitle';
+import { generateExternalLink } from 'helpers/translation-values';
+import usePaymentPortalUrl from 'hooks/usePaymentPortalUrl.ee';
+import useFormatMessage from 'hooks/useFormatMessage';
+
+export default function PaymentInformation() {
+ const paymentPortal = usePaymentPortalUrl();
+ const formatMessage = useFormatMessage();
+
+ return (
+
+
+ {formatMessage('billingAndUsageSettings.paymentInformation')}
+
+
+
+ {formatMessage(
+ 'billingAndUsageSettings.paymentPortalInformation',
+ { link: generateExternalLink(paymentPortal.url) })}
+
+
+ );
+}
diff --git a/packages/web/src/components/SettingsLayout/index.tsx b/packages/web/src/components/SettingsLayout/index.tsx
index 7e8f1be8..488349ea 100644
--- a/packages/web/src/components/SettingsLayout/index.tsx
+++ b/packages/web/src/components/SettingsLayout/index.tsx
@@ -5,8 +5,10 @@ import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
+import PaymentIcon from '@mui/icons-material/Payment';
import * as URLS from 'config/urls';
+import useAutomatischInfo from 'hooks/useAutomatischInfo';
import AppBar from 'components/AppBar';
import Drawer from 'components/Drawer';
@@ -14,13 +16,25 @@ type SettingsLayoutProps = {
children: React.ReactNode;
};
-const drawerLinks = [
- {
- Icon: AccountCircleIcon,
- primary: 'settingsDrawer.myProfile',
- to: URLS.SETTINGS_PROFILE,
- },
-];
+function createDrawerLinks({ isCloud }: { isCloud: boolean }) {
+ const items = [
+ {
+ Icon: AccountCircleIcon,
+ primary: 'settingsDrawer.myProfile',
+ to: URLS.SETTINGS_PROFILE,
+ }
+ ]
+
+ if (isCloud) {
+ items.push({
+ Icon: PaymentIcon,
+ primary: 'settingsDrawer.billingAndUsage',
+ to: URLS.SETTINGS_BILLING_AND_USAGE,
+ });
+ }
+
+ return items;
+}
const drawerBottomLinks = [
{
@@ -33,6 +47,7 @@ const drawerBottomLinks = [
export default function SettingsLayout({
children,
}: SettingsLayoutProps): React.ReactElement {
+ const { isCloud } = useAutomatischInfo();
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), {
noSsr: true,
@@ -41,6 +56,7 @@ export default function SettingsLayout({
const openDrawer = () => setDrawerOpen(true);
const closeDrawer = () => setDrawerOpen(false);
+ const drawerLinks = createDrawerLinks({ isCloud });
return (
<>
diff --git a/packages/web/src/config/urls.ts b/packages/web/src/config/urls.ts
index 5500a6ab..07f3c448 100644
--- a/packages/web/src/config/urls.ts
+++ b/packages/web/src/config/urls.ts
@@ -64,8 +64,10 @@ export const FLOW_PATTERN = '/flows/:flowId';
export const SETTINGS = '/settings';
export const SETTINGS_DASHBOARD = SETTINGS;
export const PROFILE = 'profile';
+export const BILLING_AND_USAGE = 'billing';
export const UPDATES = '/updates';
export const SETTINGS_PROFILE = `${SETTINGS}/${PROFILE}`;
+export const SETTINGS_BILLING_AND_USAGE = `${SETTINGS}/${BILLING_AND_USAGE}`;
export const DASHBOARD = FLOWS;
diff --git a/packages/web/src/contexts/AutomatischInfo.tsx b/packages/web/src/contexts/AutomatischInfo.tsx
new file mode 100644
index 00000000..d6647635
--- /dev/null
+++ b/packages/web/src/contexts/AutomatischInfo.tsx
@@ -0,0 +1,39 @@
+import * as React from 'react';
+import { useQuery } from '@apollo/client';
+
+import { GET_AUTOMATISCH_INFO } from 'graphql/queries/get-automatisch-info';
+
+export type AutomatischInfoContextParams = {
+ isCloud: boolean;
+};
+
+export const AutomatischInfoContext =
+ React.createContext({
+ isCloud: false,
+ });
+
+type AutomatischInfoProviderProps = {
+ children: React.ReactNode;
+};
+
+export const AutomatischInfoProvider = (
+ props: AutomatischInfoProviderProps
+): React.ReactElement => {
+ const { children } = props;
+ const { data, loading } = useQuery(GET_AUTOMATISCH_INFO);
+
+ const isCloud = data?.getAutomatischInfo?.isCloud;
+
+ const value = React.useMemo(() => {
+ return {
+ isCloud,
+ loading
+ };
+ }, [isCloud, loading]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/web/src/graphql/queries/get-automatisch-info.ts b/packages/web/src/graphql/queries/get-automatisch-info.ts
new file mode 100644
index 00000000..e8dcdd90
--- /dev/null
+++ b/packages/web/src/graphql/queries/get-automatisch-info.ts
@@ -0,0 +1,10 @@
+import { gql } from '@apollo/client';
+
+export const GET_AUTOMATISCH_INFO = gql`
+ query GetAutomatischInfo {
+ getAutomatischInfo {
+ isCloud
+ }
+ }
+`;
+
diff --git a/packages/web/src/graphql/queries/get-payment-portal-url.ee.ts b/packages/web/src/graphql/queries/get-payment-portal-url.ee.ts
new file mode 100644
index 00000000..9d7ec0da
--- /dev/null
+++ b/packages/web/src/graphql/queries/get-payment-portal-url.ee.ts
@@ -0,0 +1,10 @@
+import { gql } from '@apollo/client';
+
+export const GET_PAYMENT_PORTAL_URL = gql`
+ query GetPaymentPortalUrl {
+ getPaymentPortalUrl {
+ url
+ }
+ }
+`;
+
diff --git a/packages/web/src/helpers/translation-values.tsx b/packages/web/src/helpers/translation-values.tsx
index 86e84ce5..1d2925ff 100644
--- a/packages/web/src/helpers/translation-values.tsx
+++ b/packages/web/src/helpers/translation-values.tsx
@@ -1,6 +1,8 @@
+import Link from '@mui/material/Link';
+
export const generateExternalLink = (link: string) => (str: string) =>
(
-
+
{str}
-
+
);
diff --git a/packages/web/src/hooks/useAutomatischInfo.ts b/packages/web/src/hooks/useAutomatischInfo.ts
new file mode 100644
index 00000000..8c33f38b
--- /dev/null
+++ b/packages/web/src/hooks/useAutomatischInfo.ts
@@ -0,0 +1,14 @@
+import * as React from 'react';
+import { AutomatischInfoContext } from 'contexts/AutomatischInfo';
+
+type UseAutomatischInfoReturn = {
+ isCloud: boolean;
+};
+
+export default function useAutomatischInfo(): UseAutomatischInfoReturn {
+ const automatischInfoContext = React.useContext(AutomatischInfoContext);
+
+ return {
+ isCloud: automatischInfoContext.isCloud,
+ };
+}
diff --git a/packages/web/src/hooks/useCloud.ts b/packages/web/src/hooks/useCloud.ts
new file mode 100644
index 00000000..43c1360f
--- /dev/null
+++ b/packages/web/src/hooks/useCloud.ts
@@ -0,0 +1,7 @@
+import useAutomatischInfo from './useAutomatischInfo';
+
+export default function useCloud(): boolean {
+ const { isCloud } = useAutomatischInfo();
+
+ return isCloud;
+}
diff --git a/packages/web/src/hooks/usePaymentPortalUrl.ee.ts b/packages/web/src/hooks/usePaymentPortalUrl.ee.ts
new file mode 100644
index 00000000..323cdedd
--- /dev/null
+++ b/packages/web/src/hooks/usePaymentPortalUrl.ee.ts
@@ -0,0 +1,16 @@
+import { useQuery } from '@apollo/client';
+import { GET_PAYMENT_PORTAL_URL } from 'graphql/queries/get-payment-portal-url.ee';
+
+type UsePaymentPortalUrlReturn = {
+ url: string;
+ loading: boolean;
+};
+
+export default function usePaymentPortalUrl(): UsePaymentPortalUrlReturn {
+ const { data, loading } = useQuery(GET_PAYMENT_PORTAL_URL);
+
+ return {
+ url: data?.getPaymentPortalUrl?.url,
+ loading
+ };
+}
diff --git a/packages/web/src/index.tsx b/packages/web/src/index.tsx
index bb89e0a5..946c4beb 100644
--- a/packages/web/src/index.tsx
+++ b/packages/web/src/index.tsx
@@ -5,6 +5,7 @@ import IntlProvider from 'components/IntlProvider';
import ApolloProvider from 'components/ApolloProvider';
import SnackbarProvider from 'components/SnackbarProvider';
import { AuthenticationProvider } from 'contexts/Authentication';
+import { AutomatischInfoProvider } from 'contexts/AutomatischInfo';
import Router from 'components/Router';
import routes from 'routes';
import reportWebVitals from './reportWebVitals';
@@ -13,11 +14,13 @@ ReactDOM.render(
-
-
- {routes}
-
-
+
+
+
+ {routes}
+
+
+
,
diff --git a/packages/web/src/locales/en.json b/packages/web/src/locales/en.json
index 87192f13..2612facf 100644
--- a/packages/web/src/locales/en.json
+++ b/packages/web/src/locales/en.json
@@ -11,6 +11,7 @@
"settingsDrawer.myProfile": "My Profile",
"settingsDrawer.goBack": "Go to the dashboard",
"settingsDrawer.notifications": "Notifications",
+ "settingsDrawer.billingAndUsage": "Billing and usage",
"app.connectionCount": "{count} connections",
"app.flowCount": "{count} flows",
"app.addConnection": "Add connection",
@@ -96,6 +97,9 @@
"profileSettings.deleteAccountResult2": "All your flows",
"profileSettings.deleteAccountResult3": "All your connections",
"profileSettings.deleteAccountResult4": "All execution history",
+ "billingAndUsageSettings.title": "Billing and usage",
+ "billingAndUsageSettings.paymentInformation": "Payment information",
+ "billingAndUsageSettings.paymentPortalInformation": "To manage your subscription, click here to go to the payment portal.",
"deleteAccountDialog.title": "Delete account?",
"deleteAccountDialog.description": "This will permanently delete your account and all the associated data with it.",
"deleteAccountDialog.cancel": "Cancel?",
diff --git a/packages/web/src/pages/BillingAndUsageSettings/index.ee.tsx b/packages/web/src/pages/BillingAndUsageSettings/index.ee.tsx
new file mode 100644
index 00000000..0386fc68
--- /dev/null
+++ b/packages/web/src/pages/BillingAndUsageSettings/index.ee.tsx
@@ -0,0 +1,40 @@
+import * as React from 'react';
+import { Navigate } from 'react-router-dom';
+import Grid from '@mui/material/Grid';
+
+import * as URLS from 'config/urls'
+import PaymentInformation from 'components/PaymentInformation/index.ee';
+import PageTitle from 'components/PageTitle';
+import Container from 'components/Container';
+import useFormatMessage from 'hooks/useFormatMessage';
+import useCloud from 'hooks/useCloud';
+
+function BillingAndUsageSettings() {
+ const isCloud = useCloud();
+ const formatMessage = useFormatMessage();
+
+ // redirect to the initial settings page
+ if (isCloud === false) {
+ return ()
+ }
+
+ // render nothing until we know if it's cloud or not
+ // here, `isCloud` is not `false`, but `undefined`
+ if (!isCloud) return
+
+ return (
+
+
+
+ {formatMessage('billingAndUsageSettings.title')}
+
+
+
+
+
+
+
+ );
+}
+
+export default BillingAndUsageSettings;
diff --git a/packages/web/src/pages/ProfileSettings/index.tsx b/packages/web/src/pages/ProfileSettings/index.tsx
index 7ec20ca5..f13968f1 100644
--- a/packages/web/src/pages/ProfileSettings/index.tsx
+++ b/packages/web/src/pages/ProfileSettings/index.tsx
@@ -101,7 +101,7 @@ function ProfileSettings() {
+
+
+
+ }
+ />
+
}