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, }, },