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

chore: instance status prices

This commit is contained in:
Nuno Góis 2025-03-20 15:41:33 +00:00
parent 70444c2003
commit c61f0a3dfe
No known key found for this signature in database
GPG Key ID: 71ECC689F1091765
8 changed files with 81 additions and 43 deletions

View File

@ -8,8 +8,8 @@ import { useUsers } from 'hooks/api/getters/useUsers/useUsers';
import {
BILLING_INCLUDED_REQUESTS,
BILLING_PAYG_DEFAULT_MINIMUM_SEATS,
BILLING_PAYG_USER_PRICE,
BILLING_TRAFFIC_BUNDLE_PRICE,
BILLING_PAYG_SEAT_PRICE,
BILLING_TRAFFIC_PRICE,
} from './BillingPlan';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { useOverageCost } from './useOverageCost';
@ -34,11 +34,16 @@ export const BillingDetailsPAYG = ({
const eligibleUsers = users.filter((user) => user.email);
const seatPrice =
instanceStatus.prices?.payg?.seat ?? BILLING_PAYG_SEAT_PRICE;
const trafficPrice =
instanceStatus.prices?.payg?.traffic ?? BILLING_TRAFFIC_PRICE;
const minSeats =
instanceStatus.minSeats ?? BILLING_PAYG_DEFAULT_MINIMUM_SEATS;
const billableUsers = Math.max(eligibleUsers.length, minSeats);
const usersCost = BILLING_PAYG_USER_PRICE * billableUsers;
const usersCost = seatPrice * billableUsers;
const includedTraffic = BILLING_INCLUDED_REQUESTS;
const overageCost = useOverageCost(includedTraffic);
@ -66,7 +71,7 @@ export const BillingDetailsPAYG = ({
</GridColLink>
</Typography>
<StyledInfoLabel>
${BILLING_PAYG_USER_PRICE}/month per paid member
${seatPrice}/month per paid member
</StyledInfoLabel>
</GridCol>
<GridCol>
@ -93,8 +98,8 @@ export const BillingDetailsPAYG = ({
</GridColLink>
</Typography>
<StyledInfoLabel>
${BILLING_TRAFFIC_BUNDLE_PRICE} per 1
million started above included data
${trafficPrice} per 1 million started above
included data
</StyledInfoLabel>
</GridCol>
<GridCol>

View File

@ -9,10 +9,10 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit
import { useUsers } from 'hooks/api/getters/useUsers/useUsers';
import {
BILLING_INCLUDED_REQUESTS,
BILLING_PLAN_PRICES,
BILLING_PRO_DEFAULT_INCLUDED_SEATS,
BILLING_PRO_USER_PRICE,
BILLING_TRAFFIC_BUNDLE_PRICE,
BILLING_PRO_BASE_PRICE,
BILLING_PRO_SEAT_PRICE,
BILLING_TRAFFIC_PRICE,
} from './BillingPlan';
import { useOverageCost } from './useOverageCost';
@ -41,12 +41,17 @@ export const BillingDetailsPro = ({
const eligibleUsers = users.filter((user) => user.email);
const planPrice = BILLING_PLAN_PRICES[instanceStatus.plan];
const planPrice =
instanceStatus.prices?.pro?.base ?? BILLING_PRO_BASE_PRICE;
const seatPrice =
instanceStatus.prices?.pro?.seat ?? BILLING_PRO_SEAT_PRICE;
const trafficPrice =
instanceStatus.prices?.pro?.traffic ?? BILLING_TRAFFIC_PRICE;
const seats = BILLING_PRO_DEFAULT_INCLUDED_SEATS;
const freeAssigned = Math.min(eligibleUsers.length, seats);
const paidAssigned = eligibleUsers.length - freeAssigned;
const paidAssignedPrice = BILLING_PRO_USER_PRICE * paidAssigned;
const paidAssignedPrice = seatPrice * paidAssigned;
const includedTraffic = BILLING_INCLUDED_REQUESTS;
const overageCost = useOverageCost(includedTraffic);
@ -96,7 +101,7 @@ export const BillingDetailsPro = ({
</GridColLink>
</Typography>
<StyledInfoLabel>
${BILLING_PRO_USER_PRICE}/month per paid member
${seatPrice}/month per paid member
</StyledInfoLabel>
</GridCol>
<GridCol>
@ -123,8 +128,8 @@ export const BillingDetailsPro = ({
</GridColLink>
</Typography>
<StyledInfoLabel>
${BILLING_TRAFFIC_BUNDLE_PRICE} per 1
million started above included data
${trafficPrice} per 1 million started above
included data
</StyledInfoLabel>
</GridCol>
<GridCol>

View File

@ -1,6 +1,6 @@
import { Alert, Grid, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { InstanceState, InstancePlan } from 'interfaces/instance';
import { InstanceState } from 'interfaces/instance';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { trialHasExpired, isTrialInstance } from 'utils/instanceTrial';
import { GridRow } from 'component/common/GridRow/GridRow';
@ -9,16 +9,14 @@ import { Badge } from 'component/common/Badge/Badge';
import { BillingDetails } from './BillingDetails';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const BILLING_PLAN_PRICES: Record<string, number> = {
[InstancePlan.PRO]: 80,
};
export const BILLING_PRO_BASE_PRICE = 80;
export const BILLING_PRO_SEAT_PRICE = 15;
export const BILLING_PAYG_SEAT_PRICE = 75;
export const BILLING_TRAFFIC_PRICE = 5;
export const BILLING_PAYG_USER_PRICE = 75;
export const BILLING_PAYG_DEFAULT_MINIMUM_SEATS = 5;
export const BILLING_PRO_USER_PRICE = 15;
export const BILLING_PRO_DEFAULT_INCLUDED_SEATS = 5;
export const BILLING_INCLUDED_REQUESTS = 53_000_000;
export const BILLING_TRAFFIC_BUNDLE_PRICE = 5;
const StyledPlanBox = styled('aside')(({ theme }) => ({
padding: theme.spacing(2.5),
@ -77,7 +75,8 @@ export const BillingPlan = () => {
);
const expired = trialHasExpired(instanceStatus);
const planPrice = BILLING_PLAN_PRICES[instanceStatus.plan] ?? 0;
const baseProPrice =
instanceStatus.prices?.pro?.base ?? BILLING_PRO_BASE_PRICE;
const plan = `${instanceStatus.plan}${isPAYG ? ' Pay-as-You-Go' : ''}`;
const inactive = instanceStatus.state !== InstanceState.ACTIVE;
@ -131,10 +130,10 @@ export const BillingPlan = () => {
</GridCol>
<GridCol>
<ConditionallyRender
condition={planPrice > 0}
condition={!isPAYG && baseProPrice > 0}
show={
<StyledPriceSpan>
${planPrice.toFixed(2)}
${baseProPrice.toFixed(2)}
</StyledPriceSpan>
}
/>

View File

@ -5,7 +5,8 @@ import {
calculateOverageCost,
calculateTotalUsage,
} from 'utils/traffic-calculations';
import { BILLING_TRAFFIC_BUNDLE_PRICE } from './BillingPlan';
import { BILLING_TRAFFIC_PRICE } from './BillingPlan';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const useOverageCost = (includedTraffic: number) => {
if (!includedTraffic) {
@ -17,6 +18,12 @@ export const useOverageCost = (includedTraffic: number) => {
const from = formatDate(startOfMonth(now));
const to = formatDate(endOfMonth(now));
const { instanceStatus } = useInstanceStatus();
const trafficPrice =
instanceStatus?.prices?.[
instanceStatus?.billing === 'pay-as-you-go' ? 'payg' : 'pro'
]?.traffic ?? BILLING_TRAFFIC_PRICE;
const { result } = useTrafficSearch('daily', { from, to });
const overageCost = useMemo(() => {
if (result.state !== 'success') {
@ -24,12 +31,8 @@ export const useOverageCost = (includedTraffic: number) => {
}
const totalUsage = calculateTotalUsage(result.data);
return calculateOverageCost(
totalUsage,
includedTraffic,
BILLING_TRAFFIC_BUNDLE_PRICE,
);
}, [includedTraffic, JSON.stringify(result)]);
return calculateOverageCost(totalUsage, includedTraffic, trafficPrice);
}, [includedTraffic, JSON.stringify(result), trafficPrice]);
return overageCost;
};

View File

@ -12,10 +12,11 @@ import {
calculateOverageCost,
calculateTotalUsage,
} from 'utils/traffic-calculations';
import { BILLING_TRAFFIC_BUNDLE_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan';
import { BILLING_TRAFFIC_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan';
import { averageTrafficPreviousMonths } from '../average-traffic-previous-months';
import { useConnectionsConsumption } from 'hooks/api/getters/useConnectionsConsumption/useConnectionsConsumption';
import { useRequestsConsumption } from 'hooks/api/getters/useRequestsConsumption/useRequestsConsumption';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const useTrafficStats = (
includedTraffic: number,
@ -26,6 +27,12 @@ export const useTrafficStats = (
chartDataSelection.grouping,
toDateRange(chartDataSelection, currentDate),
);
const { instanceStatus } = useInstanceStatus();
const trafficPrice =
instanceStatus?.prices?.[
instanceStatus?.billing === 'pay-as-you-go' ? 'payg' : 'pro'
]?.traffic ?? BILLING_TRAFFIC_PRICE;
const results = useMemo(() => {
if (result.state !== 'success') {
return {
@ -43,14 +50,14 @@ export const useTrafficStats = (
const overageCost = calculateOverageCost(
usageTotal,
includedTraffic,
BILLING_TRAFFIC_BUNDLE_PRICE,
trafficPrice,
);
const estimatedMonthlyCost = calculateEstimatedMonthlyCost(
traffic.apiData,
includedTraffic,
currentDate,
BILLING_TRAFFIC_BUNDLE_PRICE,
trafficPrice,
);
const requestSummaryUsage =
@ -69,6 +76,7 @@ export const useTrafficStats = (
JSON.stringify(result),
includedTraffic,
JSON.stringify(chartDataSelection),
trafficPrice,
]);
return results;

View File

@ -2,11 +2,15 @@ import type { VFC } from 'react';
import { Alert } from '@mui/material';
import { useUsersPlan } from 'hooks/useUsersPlan';
import { useUsers } from 'hooks/api/getters/useUsers/useUsers';
import { BILLING_PRO_USER_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
import { BILLING_PRO_SEAT_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
import { useInstanceStatus } from 'hooks/api/getters/useInstanceStatus/useInstanceStatus';
export const SeatCostWarning: VFC = () => {
const { users } = useUsers();
const { isBillingUsers, seats, planUsers } = useUsersPlan(users);
const { instanceStatus } = useInstanceStatus();
const seatPrice =
instanceStatus?.prices?.pro?.seat ?? BILLING_PRO_SEAT_PRICE;
if (!isBillingUsers || planUsers.length < seats) {
return null;
@ -20,9 +24,8 @@ export const SeatCostWarning: VFC = () => {
<p>
<strong>Heads up!</strong> You are exceeding your allocated free
members included in your plan ({planUsers.length} of {seats}).
Creating this user will add{' '}
<strong>${BILLING_PRO_USER_PRICE}/month</strong> to your
invoice, starting with your next payment.
Creating this user will add <strong>${seatPrice}/month</strong>{' '}
to your invoice, starting with your next payment.
</p>
</Alert>
);

View File

@ -6,11 +6,10 @@ import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useUiFlag } from 'hooks/useUiFlag';
import {
BILLING_PAYG_DEFAULT_MINIMUM_SEATS,
BILLING_PAYG_USER_PRICE,
BILLING_PLAN_PRICES,
BILLING_PAYG_SEAT_PRICE,
BILLING_PRO_BASE_PRICE,
BILLING_PRO_DEFAULT_INCLUDED_SEATS,
} from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
import { InstancePlan } from 'interfaces/instance';
const StyledDemoDialog = styled(DemoDialog)(({ theme }) => ({
'& .MuiDialog-paper': {
@ -139,7 +138,7 @@ export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => {
</Typography>
<div>
<Typography variant='h6' fontWeight='normal'>
${BILLING_PAYG_USER_PRICE} per user/month
${BILLING_PAYG_SEAT_PRICE} per user/month
</Typography>
<Typography variant='body2'>
{BILLING_PAYG_DEFAULT_MINIMUM_SEATS} users
@ -174,7 +173,7 @@ export const DemoDialogPlans = ({ open, onClose }: IDemoDialogPlansProps) => {
</Typography>
<div>
<Typography variant='h6' fontWeight='normal'>
${BILLING_PLAN_PRICES[InstancePlan.PRO]}/month
${BILLING_PRO_BASE_PRICE}/month
</Typography>
<Typography variant='body2'>
includes {BILLING_PRO_DEFAULT_INCLUDED_SEATS}{' '}

View File

@ -1,3 +1,17 @@
type InstancePrices = {
pro?: {
base?: number;
seat?: number;
traffic?: number;
};
payg?: {
seat?: number;
traffic?: number;
};
};
type InstanceBilling = 'pay-as-you-go' | 'subscription';
export interface IInstanceStatus {
plan: InstancePlan;
trialExpiry?: string;
@ -8,6 +22,8 @@ export interface IInstanceStatus {
seats?: number;
minSeats?: number;
isCustomBilling?: boolean;
prices?: InstancePrices;
billing?: InstanceBilling;
}
export enum InstanceState {