1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-10-27 11:02:16 +01:00

feat: flag traffic billing display feature (#10718)

Feature flag and initial changes in Billing UI
This commit is contained in:
Tymoteusz Czech 2025-10-03 09:18:39 +02:00 committed by GitHub
parent df67c041fc
commit 9b5324ac92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 96 additions and 12 deletions

View File

@ -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 (
<Box
sx={(theme) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(4),
})}
>
<BillingDashboard />
<BillingInvoices />
</Box>
);
}
return (
<div>
<PageContent header='Billing' isLoading={loading}>

View File

@ -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 (
<Grid container spacing={4}>
<Grid container spacing={trafficBillingDisplay ? 2 : 4}>
<BillingInformation />
<BillingPlan />
</Grid>

View File

@ -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 (
<Grid item xs={12} md={5}>
<StyledInfoBox data-loading sx={{ flex: 1, height: '400px' }} />
<StyledWrapper data-loading sx={{ flex: 1, height: '400px' }} />
</Grid>
);
@ -50,7 +65,7 @@ export const BillingInformation = () => {
return (
<Grid item xs={12} md={5}>
<StyledInfoBox>
<StyledWrapper>
<StyledTitle variant='body1'>Billing information</StyledTitle>
<ConditionallyRender
condition={Boolean(isCustomBilling)}
@ -89,7 +104,7 @@ export const BillingInformation = () => {
</a>{' '}
for any clarification
</StyledInfoLabel>
</StyledInfoBox>
</StyledWrapper>
</Grid>
);
};

View File

@ -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 (
<Grid item xs={12} md={7}>
<StyledPlanBox data-loading sx={{ flex: 1, height: '400px' }} />
<StyledWrapper data-loading sx={{ flex: 1, height: '400px' }} />
</Grid>
);
@ -85,7 +101,7 @@ export const BillingPlan = () => {
return (
<Grid item xs={12} md={7}>
<StyledPlanBox>
<StyledWrapper>
<ConditionallyRender
condition={inactive}
show={
@ -161,7 +177,7 @@ export const BillingPlan = () => {
isPAYG={isPAYG}
isEnterpriseConsumption={isEnterpriseConsumption}
/>
</StyledPlanBox>
</StyledWrapper>
</Grid>
);
};

View File

@ -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<BillingInvoicesProps> = () => {
return (
<StyledContainer>
<StyledHeader>Usage and invoices</StyledHeader>
</StyledContainer>
);
};

View File

@ -89,6 +89,7 @@ export type UiFlags = {
newStrategyModal?: boolean;
globalChangeRequestList?: boolean;
flagsUiFilterRefactor?: boolean;
trafficBillingDisplay?: boolean;
milestoneProgression?: boolean;
};

View File

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

View File

@ -57,6 +57,7 @@ process.nextTick(async () => {
globalChangeRequestList: true,
newUiConfigService: true,
flagsUiFilterRefactor: true,
trafficBillingDisplay: true,
milestoneProgression: true,
},
},