1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-15 01:16:22 +02:00
unleash.unleash/frontend/src/component/admin/billing/BillingDashboard/BillingPlan/BillingPlan.tsx
David Leek bc6b23c740
fix: attempt a react friendly fix of summing (#7151)
## About the changes

Summing on Billing page got a little wonky after changing how the
summing worked when the estimation flag is off. This attempts to return
it to previous way of showing numbers when flag is off

If you go directly to the billing page it will not add user calculations
to the total. If you however interact with the UI, like change tabs back
and forth, it will suddenly show the correct sum:


![image](https://github.com/Unleash/unleash/assets/707867/af6eeddf-be3f-42ae-a588-f57c30d739ca)


![image](https://github.com/Unleash/unleash/assets/707867/b4a0b832-a550-4e87-aa69-7b27f96d3beb)

---------

Co-authored-by: Nuno Góis <github@nunogois.com>
Co-authored-by: Gastón Fournier <gaston@getunleash.io>
2024-05-28 09:08:23 +02:00

330 lines
14 KiB
TypeScript

import type { FC } from 'react';
import { useMemo } from 'react';
import { Alert, Divider, Grid, styled, Typography } from '@mui/material';
import { Link } from 'react-router-dom';
import CheckIcon from '@mui/icons-material/Check';
import { useUsers } from 'hooks/api/getters/useUsers/useUsers';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import {
type IInstanceStatus,
InstanceState,
InstancePlan,
} from 'interfaces/instance';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { trialHasExpired, isTrialInstance } from 'utils/instanceTrial';
import { GridRow } from 'component/common/GridRow/GridRow';
import { GridCol } from 'component/common/GridCol/GridCol';
import { Badge } from 'component/common/Badge/Badge';
import { GridColLink } from './GridColLink/GridColLink';
import { useTrafficDataEstimation } from 'hooks/useTrafficData';
import { useUiFlag } from 'hooks/useUiFlag';
import { useInstanceTrafficMetrics } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
const StyledPlanBox = styled('aside')(({ theme }) => ({
padding: theme.spacing(2.5),
height: '100%',
borderRadius: theme.shape.borderRadiusLarge,
boxShadow: theme.boxShadows.elevated,
[theme.breakpoints.up('md')]: {
padding: theme.spacing(6.5),
},
}));
const StyledInfoLabel = styled(Typography)(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
color: theme.palette.text.secondary,
}));
const StyledPlanSpan = styled('span')(({ theme }) => ({
fontSize: '3.25rem',
lineHeight: 1,
color: theme.palette.primary.main,
fontWeight: 800,
}));
const StyledTrialSpan = styled('span')(({ theme }) => ({
marginLeft: theme.spacing(1.5),
fontWeight: theme.fontWeight.bold,
}));
const StyledPriceSpan = styled('span')(({ theme }) => ({
color: theme.palette.primary.main,
fontSize: theme.fontSizes.mainHeader,
fontWeight: theme.fontWeight.bold,
}));
const StyledAlert = styled(Alert)(({ theme }) => ({
fontSize: theme.fontSizes.smallerBody,
marginBottom: theme.spacing(3),
marginTop: theme.spacing(-1.5),
[theme.breakpoints.up('md')]: {
marginTop: theme.spacing(-4.5),
},
}));
const StyledCheckIcon = styled(CheckIcon)(({ theme }) => ({
fontSize: '1rem',
marginRight: theme.spacing(1),
}));
const StyledDivider = styled(Divider)(({ theme }) => ({
margin: `${theme.spacing(3)} 0`,
}));
interface IBillingPlanProps {
instanceStatus: IInstanceStatus;
}
const proPlanIncludedRequests = 53_000_000;
export const BillingPlan: FC<IBillingPlanProps> = ({ instanceStatus }) => {
const { users, loading } = useUsers();
const expired = trialHasExpired(instanceStatus);
const { isPro } = useUiConfig();
const {
currentPeriod,
toChartData,
toTrafficUsageSum,
endpointsInfo,
getDayLabels,
calculateOverageCost,
} = useTrafficDataEstimation();
const eligibleUsers = users.filter((user: any) => user.email);
const price = {
[InstancePlan.PRO]: 80,
[InstancePlan.COMPANY]: 0,
[InstancePlan.TEAM]: 0,
[InstancePlan.ENTERPRISE]: 0,
[InstancePlan.UNKNOWN]: 0,
user: 15,
};
const planPrice = price[instanceStatus.plan];
const seats = instanceStatus.seats ?? 5;
const freeAssigned = Math.min(eligibleUsers.length, seats);
const paidAssigned = eligibleUsers.length - freeAssigned;
const paidAssignedPrice = price.user * paidAssigned;
const displayTrafficDataUsageEnabled = useUiFlag('displayTrafficDataUsage');
const includedTraffic = isPro() ? proPlanIncludedRequests : 0;
const traffic = useInstanceTrafficMetrics(currentPeriod.key);
const overageCost = useMemo(() => {
if (!displayTrafficDataUsageEnabled || !includedTraffic) {
return 0;
}
const trafficData = toChartData(
getDayLabels(currentPeriod.dayCount),
traffic,
endpointsInfo,
);
const totalTraffic = toTrafficUsageSum(trafficData);
return calculateOverageCost(totalTraffic, includedTraffic);
}, [
displayTrafficDataUsageEnabled,
includedTraffic,
traffic,
currentPeriod,
endpointsInfo,
]);
const totalCost = planPrice + paidAssignedPrice + overageCost;
const inactive = instanceStatus.state !== InstanceState.ACTIVE;
if (loading) return null;
return (
<Grid item xs={12} md={7}>
<StyledPlanBox>
<ConditionallyRender
condition={inactive}
show={
<StyledAlert severity='info'>
After you have sent your billing information, your
instance will be upgraded - you don't have to do
anything.{' '}
<a href='mailto:elise@getunleash.ai?subject=PRO plan clarifications'>
Get in touch with us
</a>{' '}
for any clarification
</StyledAlert>
}
/>
<Badge color='success'>Current plan</Badge>
<Grid container>
<GridRow
sx={(theme) => ({ marginBottom: theme.spacing(3) })}
>
<GridCol>
<StyledPlanSpan>
{instanceStatus.plan}
</StyledPlanSpan>
<ConditionallyRender
condition={isTrialInstance(instanceStatus)}
show={
<StyledTrialSpan
sx={(theme) => ({
color: expired
? theme.palette.error.dark
: theme.palette.warning.dark,
})}
>
{expired
? 'Trial expired'
: instanceStatus.trialExtended
? 'Extended Trial'
: 'Trial'}
</StyledTrialSpan>
}
/>
</GridCol>
<GridCol>
<ConditionallyRender
condition={planPrice > 0}
show={
<StyledPriceSpan>
${planPrice.toFixed(2)}
</StyledPriceSpan>
}
/>
</GridCol>
</GridRow>
</Grid>
<ConditionallyRender
condition={Boolean(
instanceStatus.plan === InstancePlan.PRO,
)}
show={
<>
<Grid container>
<GridRow
sx={(theme) => ({
marginBottom: theme.spacing(1.5),
})}
>
<GridCol vertical>
<Typography>
<strong>Included members</strong>
<GridColLink>
<Link to='/admin/users'>
{freeAssigned} of 5 assigned
</Link>
</GridColLink>
</Typography>
<StyledInfoLabel>
You have 5 team members included in
your PRO plan
</StyledInfoLabel>
</GridCol>
<GridCol>
<StyledCheckIcon />
<Typography variant='body2'>
included
</Typography>
</GridCol>
</GridRow>
<GridRow
sx={(theme) => ({
marginBottom: theme.spacing(1.5),
})}
>
<GridCol vertical>
<Typography>
<strong>Paid members</strong>
<GridColLink>
<Link to='/admin/users'>
{paidAssigned} assigned
</Link>
</GridColLink>
</Typography>
<StyledInfoLabel>
$15/month per paid member
</StyledInfoLabel>
</GridCol>
<GridCol>
<Typography
sx={(theme) => ({
fontSize:
theme.fontSizes.mainHeader,
})}
>
${paidAssignedPrice.toFixed(2)}
</Typography>
</GridCol>
</GridRow>
<ConditionallyRender
condition={overageCost > 0}
show={
<GridRow>
<GridCol vertical>
<Typography>
<strong>
Accrued traffic charges
</strong>
<GridColLink>
<Link to='/admin/network/data-usage'>
view details
</Link>
</GridColLink>
</Typography>
<StyledInfoLabel>
$5 dollar per 1 million
started above included data
</StyledInfoLabel>
</GridCol>
<GridCol>
<Typography
sx={(theme) => ({
fontSize:
theme.fontSizes
.mainHeader,
})}
>
${overageCost.toFixed(2)}
</Typography>
</GridCol>
</GridRow>
}
/>
</Grid>
<StyledDivider />
<Grid container>
<GridRow>
<GridCol>
<Typography
sx={(theme) => ({
fontWeight:
theme.fontWeight.bold,
fontSize:
theme.fontSizes.mainHeader,
})}
>
Total
</Typography>
</GridCol>
<GridCol>
<Typography
sx={(theme) => ({
fontWeight:
theme.fontWeight.bold,
fontSize: '2rem',
})}
>
${totalCost.toFixed(2)}
</Typography>
</GridCol>
</GridRow>
</Grid>
</>
}
/>
</StyledPlanBox>
</Grid>
);
};