From 9b5324ac92999623d417a25977bb1611c9ca0ec0 Mon Sep 17 00:00:00 2001
From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
Date: Fri, 3 Oct 2025 09:18:39 +0200
Subject: [PATCH] feat: flag traffic billing display feature (#10718)
Feature flag and initial changes in Billing UI
---
.../src/component/admin/billing/Billing.tsx | 20 +++++++++++++-
.../BillingDashboard/BillingDashboard.tsx | 5 +++-
.../BillingInformation/BillingInformation.tsx | 25 ++++++++++++++----
.../BillingPlan/BillingPlan.tsx | 26 +++++++++++++++----
.../BillingInvoices/BillingInvoices.tsx | 25 ++++++++++++++++++
frontend/src/interfaces/uiConfig.ts | 1 +
src/lib/types/experimental.ts | 5 ++++
src/server-dev.ts | 1 +
8 files changed, 96 insertions(+), 12 deletions(-)
create mode 100644 frontend/src/component/admin/billing/BillingInvoices/BillingInvoices.tsx
diff --git a/frontend/src/component/admin/billing/Billing.tsx b/frontend/src/component/admin/billing/Billing.tsx
index b32214900e..407c44a17c 100644
--- a/frontend/src/component/admin/billing/Billing.tsx
+++ b/frontend/src/component/admin/billing/Billing.tsx
@@ -4,15 +4,18 @@ import { ADMIN } from 'component/providers/AccessProvider/permissions';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
-import { Alert } from '@mui/material';
+import { Alert, Box } from '@mui/material';
import { BillingDashboard } from './BillingDashboard/BillingDashboard.tsx';
import { BillingHistory } from './BillingHistory/BillingHistory.tsx';
import useInvoices from 'hooks/api/getters/useInvoices/useInvoices';
+import { useUiFlag } from 'hooks/useUiFlag';
+import { BillingInvoices } from './BillingInvoices/BillingInvoices.tsx';
export const Billing = () => {
const { isBilling, refetchInstanceStatus, refresh, loading } =
useInstanceStatus();
const { invoices } = useInvoices();
+ const trafficBillingDisplay = useUiFlag('trafficBillingDisplay');
useEffect(() => {
const hardRefresh = async () => {
@@ -22,6 +25,21 @@ export const Billing = () => {
hardRefresh();
}, [refetchInstanceStatus, refresh]);
+ if (trafficBillingDisplay) {
+ return (
+ ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(4),
+ })}
+ >
+
+
+
+ );
+ }
+
return (
diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingDashboard.tsx b/frontend/src/component/admin/billing/BillingDashboard/BillingDashboard.tsx
index f6dec86c56..40a074c72c 100644
--- a/frontend/src/component/admin/billing/BillingDashboard/BillingDashboard.tsx
+++ b/frontend/src/component/admin/billing/BillingDashboard/BillingDashboard.tsx
@@ -1,10 +1,13 @@
import { Grid } from '@mui/material';
import { BillingInformation } from './BillingInformation/BillingInformation.tsx';
import { BillingPlan } from './BillingPlan/BillingPlan.tsx';
+import { useUiFlag } from 'hooks/useUiFlag.ts';
export const BillingDashboard = () => {
+ const trafficBillingDisplay = useUiFlag('trafficBillingDisplay');
+
return (
-
+
diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingInformation/BillingInformation.tsx b/frontend/src/component/admin/billing/BillingDashboard/BillingInformation/BillingInformation.tsx
index 805adad202..22c18543b6 100644
--- a/frontend/src/component/admin/billing/BillingDashboard/BillingInformation/BillingInformation.tsx
+++ b/frontend/src/component/admin/billing/BillingDashboard/BillingInformation/BillingInformation.tsx
@@ -1,17 +1,28 @@
-import { Alert, Divider, Grid, styled, Typography } from '@mui/material';
+import { Alert, Divider, Grid, Paper, styled, Typography } from '@mui/material';
import { BillingInformationButton } from './BillingInformationButton/BillingInformationButton.tsx';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { InstanceState } from 'interfaces/instance';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
+import { useUiFlag } from 'hooks/useUiFlag.ts';
-const StyledInfoBox = styled('aside')(({ theme }) => ({
+/**
+ * @deprecated remove with `trafficBillingDisplay` flag
+ */
+const LegacyStyledInfoBox = styled('aside')(({ theme }) => ({
padding: theme.spacing(4),
height: '100%',
borderRadius: theme.shape.borderRadiusLarge,
backgroundColor: theme.palette.background.elevation2,
}));
+const StyledInfoBox = styled(Paper)(({ theme }) => ({
+ padding: theme.spacing(4),
+ height: '100%',
+ borderRadius: theme.shape.borderRadiusLarge,
+ backgroundColor: theme.palette.background.paper,
+}));
+
const StyledTitle = styled(Typography)(({ theme }) => ({
marginBottom: theme.spacing(4),
}));
@@ -36,11 +47,15 @@ export const BillingInformation = () => {
uiConfig: { billing },
} = useUiConfig();
const isPAYG = billing === 'pay-as-you-go';
+ const trafficBillingDisplay = useUiFlag('trafficBillingDisplay');
+ const StyledWrapper = trafficBillingDisplay
+ ? StyledInfoBox
+ : LegacyStyledInfoBox;
if (!instanceStatus)
return (
-
+
);
@@ -50,7 +65,7 @@ export const BillingInformation = () => {
return (
-
+
Billing information
{
{' '}
for any clarification
-
+
);
};
diff --git a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx
index a04e082933..bfeda64da0 100644
--- a/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx
+++ b/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx
@@ -1,4 +1,4 @@
-import { Alert, Grid, styled } from '@mui/material';
+import { Alert, Grid, Paper, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { InstancePlan, InstanceState } from 'interfaces/instance';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
@@ -8,6 +8,7 @@ import { GridCol } from 'component/common/GridCol/GridCol';
import { Badge } from 'component/common/Badge/Badge';
import { BillingDetails } from './BillingDetails.tsx';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
+import { useUiFlag } from 'hooks/useUiFlag.ts';
export const BILLING_PRO_BASE_PRICE = 80;
export const BILLING_PRO_SEAT_PRICE = 15;
@@ -18,7 +19,10 @@ export const BILLING_PAYG_DEFAULT_MINIMUM_SEATS = 5;
export const BILLING_PRO_DEFAULT_INCLUDED_SEATS = 5;
export const BILLING_INCLUDED_REQUESTS = 53_000_000;
-const StyledPlanBox = styled('aside')(({ theme }) => ({
+/**
+ * @deprecated remove with `trafficBillingDisplay` flag
+ */
+const LegacyStyledPlanBox = styled('aside')(({ theme }) => ({
padding: theme.spacing(2.5),
height: '100%',
borderRadius: theme.shape.borderRadiusLarge,
@@ -28,6 +32,13 @@ const StyledPlanBox = styled('aside')(({ theme }) => ({
},
}));
+const StyledPlanBox = styled(Paper)(({ theme }) => ({
+ padding: theme.spacing(4),
+ height: '100%',
+ borderRadius: theme.shape.borderRadiusLarge,
+ backgroundColor: theme.palette.background.paper,
+}));
+
const StyledPlanSpan = styled('span')(({ theme }) => ({
fontSize: '3.25rem',
lineHeight: 1,
@@ -63,6 +74,7 @@ export const BillingPlan = () => {
const {
uiConfig: { billing },
} = useUiConfig();
+ const trafficBillingDisplay = useUiFlag('trafficBillingDisplay');
const { instanceStatus } = useInstanceStatus();
const isPro =
@@ -70,10 +82,14 @@ export const BillingPlan = () => {
const isPAYG = billing === 'pay-as-you-go';
const isEnterpriseConsumption = billing === 'enterprise-consumption';
+ const StyledWrapper = trafficBillingDisplay
+ ? StyledPlanBox
+ : LegacyStyledPlanBox;
+
if (!instanceStatus)
return (
-
+
);
@@ -85,7 +101,7 @@ export const BillingPlan = () => {
return (
-
+
{
isPAYG={isPAYG}
isEnterpriseConsumption={isEnterpriseConsumption}
/>
-
+
);
};
diff --git a/frontend/src/component/admin/billing/BillingInvoices/BillingInvoices.tsx b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoices.tsx
new file mode 100644
index 0000000000..95146ce8ee
--- /dev/null
+++ b/frontend/src/component/admin/billing/BillingInvoices/BillingInvoices.tsx
@@ -0,0 +1,25 @@
+import { Box, styled, Typography } from '@mui/material';
+import type { FC } from 'react';
+
+const StyledContainer = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.spacing(3),
+}));
+
+const StyledHeader = styled(Typography)(({ theme }) => ({
+ fontSize: theme.fontSizes.mainHeader,
+ fontWeight: theme.fontWeight.semi,
+ color: theme.palette.text.primary,
+ marginBottom: theme.spacing(2),
+}));
+
+type BillingInvoicesProps = {};
+
+export const BillingInvoices: FC = () => {
+ return (
+
+ Usage and invoices
+
+ );
+};
diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts
index ff4b182832..d2bfeba33c 100644
--- a/frontend/src/interfaces/uiConfig.ts
+++ b/frontend/src/interfaces/uiConfig.ts
@@ -89,6 +89,7 @@ export type UiFlags = {
newStrategyModal?: boolean;
globalChangeRequestList?: boolean;
flagsUiFilterRefactor?: boolean;
+ trafficBillingDisplay?: boolean;
milestoneProgression?: boolean;
};
diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts
index 4cf99a1433..86dc334e70 100644
--- a/src/lib/types/experimental.ts
+++ b/src/lib/types/experimental.ts
@@ -61,6 +61,7 @@ export type IFlagKey =
| 'globalChangeRequestList'
| 'newUiConfigService'
| 'flagsUiFilterRefactor'
+ | 'trafficBillingDisplay'
| 'milestoneProgression';
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
@@ -278,6 +279,10 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_FLAGS_UI_FILTER_REFACTOR,
false,
),
+ trafficBillingDisplay: parseEnvVarBoolean(
+ process.env.UNLEASH_EXPERIMENTAL_TRAFFIC_BILLING_DISPLAY,
+ false,
+ ),
milestoneProgression: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_MILESTONE_PROGRESSION,
false,
diff --git a/src/server-dev.ts b/src/server-dev.ts
index b478388c2c..165f20128e 100644
--- a/src/server-dev.ts
+++ b/src/server-dev.ts
@@ -57,6 +57,7 @@ process.nextTick(async () => {
globalChangeRequestList: true,
newUiConfigService: true,
flagsUiFilterRefactor: true,
+ trafficBillingDisplay: true,
milestoneProgression: true,
},
},