feat: use actual data in billing and usage

This commit is contained in:
Ali BARIN
2023-03-26 19:28:15 +00:00
parent a99609e3da
commit f3a8ab289f
9 changed files with 240 additions and 103 deletions

View File

@@ -1,31 +1,11 @@
import { DateTime } from 'luxon';
import { TSubscription } from '@automatisch/types';
import Context from '../../types/express/context';
import Billing from '../../helpers/billing/index.ee';
import Execution from '../../models/execution';
import ExecutionStep from '../../models/execution-step';
import Subscription from '../../models/subscription.ee';
import { DateTime } from 'luxon';
type BillingCardAction = {
type: string;
text: string;
src?: string | null;
};
type ComputedSubscription = {
status: string;
monthlyQuota: {
title: string;
action: BillingCardAction;
};
nextBillDate: {
title: string;
action: BillingCardAction;
};
nextBillAmount: {
title: string;
action: BillingCardAction;
};
};
const getBillingAndUsage = async (
_parent: unknown,
@@ -36,7 +16,7 @@ const getBillingAndUsage = async (
'subscription'
);
const subscription: ComputedSubscription = persistedSubscription
const subscription = persistedSubscription
? paidSubscription(persistedSubscription)
: freeTrialSubscription();
@@ -48,7 +28,7 @@ const getBillingAndUsage = async (
};
};
const paidSubscription = (subscription: Subscription): ComputedSubscription => {
const paidSubscription = (subscription: Subscription): TSubscription => {
const currentPlan = Billing.paddlePlans.find(
(plan) => plan.productId === subscription.paddlePlanId
);
@@ -81,7 +61,7 @@ const paidSubscription = (subscription: Subscription): ComputedSubscription => {
};
};
const freeTrialSubscription = (): ComputedSubscription => {
const freeTrialSubscription = (): TSubscription => {
return {
status: null,
monthlyQuota: {

View File

@@ -339,6 +339,35 @@ export type TPaymentPlan = {
productId: string;
}
export type TSubscription = {
status: string;
monthlyQuota: {
title: string;
action: BillingCardAction;
};
nextBillDate: {
title: string;
action: BillingCardAction;
};
nextBillAmount: {
title: string;
action: BillingCardAction;
};
}
type TBillingCardAction = TBillingTextCardAction | TBillingLinkCardAction;
type TBillingTextCardAction = {
type: 'text';
text: string;
}
type TBillingLinkCardAction = {
type: 'link';
text: string;
src: string;
}
declare module 'axios' {
interface AxiosResponse {
httpError?: IJSONObject;

View File

@@ -12,7 +12,6 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import * as URLS from 'config/urls';
import AccountDropdownMenu from 'components/AccountDropdownMenu';
import UsageAlert from 'components/UsageAlert/index.ee';
import Container from 'components/Container';
import { FormattedMessage } from 'react-intl';
import { Link } from './style';
@@ -82,8 +81,6 @@ export default function AppBar(props: AppBarProps): React.ReactElement {
</Toolbar>
</Container>
<UsageAlert />
<AccountDropdownMenu
anchorEl={accountMenuAnchorElement}
id={accountMenuId}

View File

@@ -78,7 +78,7 @@ function LoginForm() {
sx={{ mb: 1 }}
/>
{isCloud &&<Link
{isCloud && <Link
component={RouterLink}
to={URLS.FORGOT_PASSWORD}
underline="none"

View File

@@ -2,6 +2,7 @@ import * as React from 'react';
import { Link } from 'react-router-dom';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
@@ -9,23 +10,113 @@ import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import { TBillingCardAction } from '@automatisch/types';
import * as URLS from 'config/urls';
import useUsageData from 'hooks/useUsageData.ee';
import useBillingAndUsageData from 'hooks/useBillingAndUsageData.ee';
import useFormatMessage from 'hooks/useFormatMessage';
const capitalize = (str: string) => str[0].toUpperCase() + str.slice(1, str.length);
type BillingCardProps = {
name: string;
title?: string;
action?: TBillingCardAction;
};
function BillingCard(props: BillingCardProps) {
const { name, title = '', action } = props;
return (
<Card
sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
backgroundColor: (theme) => theme.palette.background.default,
}}
>
<CardContent>
<Typography variant="subtitle2" sx={{ pb: 0.5 }}>
{name}
</Typography>
<Typography variant="h6" fontWeight="bold">
{title}
</Typography>
</CardContent>
<CardActions>
<Action action={action} />
</CardActions>
</Card>
);
}
function Action(props: { action?: TBillingCardAction }) {
const { action } = props;
if (!action) return <React.Fragment />;
const { text, type } = action;
if (type === 'link') {
if (action.src.startsWith('http')) {
return (
<Button
size="small"
href={action.src}
target="_blank"
>
{text}
</Button>
)
} else {
return (
<Button
size="small"
component={Link}
to={action.src}
>
{text}
</Button>
);
}
}
if (type === 'text') {
return (
<Typography variant="subtitle2" pb={1}>
{text}
</Typography>
);
}
return <React.Fragment />;
}
export default function UsageDataInformation() {
const usageData = useUsageData();
const formatMessage = useFormatMessage();
const billingAndUsageData = useBillingAndUsageData();
return (
<React.Fragment>
<Card sx={{ mb: 3, p: 2 }}>
<CardContent>
<CardContent sx={{ display: 'flex', flexDirection: 'column' }}>
<Box sx={{ mb: 1, display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="h6" fontWeight="bold">
Subscription plan
{formatMessage('usageDataInformation.subscriptionPlan')}
</Typography>
{/* <Chip label="Active" color="success" /> */}
{billingAndUsageData?.subscription?.status && (
<Chip
label={capitalize(billingAndUsageData?.subscription?.status)}
color="success"
/>
)}
</Box>
<Divider sx={{ mb: 3 }} />
<Grid
container
item
@@ -35,76 +126,33 @@ export default function UsageDataInformation() {
alignItems="stretch"
>
<Grid item xs={12} md={4}>
<Card
sx={{
height: '100%',
backgroundColor: (theme) => theme.palette.background.default,
}}
>
<CardContent>
<Typography variant="subtitle2" sx={{ pb: 0.5 }}>
Monthly quota
</Typography>
<Typography variant="h6" fontWeight="bold">
Free trial
</Typography>
</CardContent>
<CardActions>
<Button size="small">Upgrade plan</Button>
</CardActions>
</Card>
<BillingCard
name={formatMessage('usageDataInformation.monthlyQuota')}
title={billingAndUsageData?.subscription?.monthlyQuota.title}
action={billingAndUsageData?.subscription?.monthlyQuota.action}
/>
</Grid>
<Grid item xs={12} md={4}>
<Card
sx={{
height: '100%',
backgroundColor: (theme) => theme.palette.background.default,
}}
>
<CardContent>
<Typography variant="subtitle2" sx={{ pb: 0.5 }}>
Next bill amount
</Typography>
<Typography variant="h6" fontWeight="bold">
---
</Typography>
</CardContent>
<CardActions>
{/* <Button size="small">Update billing info</Button> */}
</CardActions>
</Card>
<BillingCard
name={formatMessage('usageDataInformation.nextBillAmount')}
title={billingAndUsageData?.subscription?.nextBillAmount.title}
action={billingAndUsageData?.subscription?.nextBillAmount.action}
/>
</Grid>
<Grid item xs={12} md={4}>
<Card
sx={{
height: '100%',
backgroundColor: (theme) => theme.palette.background.default,
}}
>
<CardContent>
<Typography variant="subtitle2" sx={{ pb: 0.5 }}>
Next bill date
</Typography>
<Typography variant="h6" fontWeight="bold">
---
</Typography>
</CardContent>
<CardActions>
{/* <Button disabled size="small">
monthly billing
</Button> */}
</CardActions>
</Card>
<BillingCard
name={formatMessage('usageDataInformation.nextBillDate')}
title={billingAndUsageData?.subscription?.nextBillDate.title}
action={billingAndUsageData?.subscription?.nextBillDate.action}
/>
</Grid>
</Grid>
<Box>
<Typography variant="h6" fontWeight="bold">
Your usage
{formatMessage('usageDataInformation.yourUsage')}
</Typography>
<Box>
@@ -112,7 +160,7 @@ export default function UsageDataInformation() {
variant="subtitle2"
sx={{ color: 'text.secondary', mt: 1 }}
>
Last 30 days total usage
{formatMessage('usageDataInformation.yourUsageDescription')}
</Typography>
</Box>
@@ -129,14 +177,14 @@ export default function UsageDataInformation() {
variant="subtitle2"
sx={{ color: 'text.secondary', mt: 2, fontWeight: 500 }}
>
Tasks
{formatMessage('usageDataInformation.yourUsageTasks')}
</Typography>
<Typography
variant="subtitle2"
sx={{ color: 'text.secondary', mt: 2, fontWeight: 500 }}
>
12300
{billingAndUsageData?.usage.task}
</Typography>
</Box>
@@ -148,9 +196,9 @@ export default function UsageDataInformation() {
to={URLS.SETTINGS_PLAN_UPGRADE}
size="small"
variant="contained"
sx={{ mt: 2 }}
sx={{ mt: 2, alignSelf: 'flex-end' }}
>
Upgrade
{formatMessage('usageDataInformation.upgrade')}
</Button>
</CardContent>
</Card>

View File

@@ -0,0 +1,39 @@
import { gql } from '@apollo/client';
export const GET_BILLING_AND_USAGE = gql`
query GetBillingAndUsage {
getBillingAndUsage {
subscription {
status
monthlyQuota {
title
action {
type
text
src
}
}
nextBillDate {
title
action {
type
text
src
}
}
nextBillAmount {
title
action {
type
text
src
}
}
}
usage {
task
}
}
}
`;

View File

@@ -0,0 +1,37 @@
import { useQuery } from '@apollo/client';
import { DateTime } from 'luxon';
import { TSubscription } from '@automatisch/types';
import { GET_BILLING_AND_USAGE } from 'graphql/queries/get-billing-and-usage.ee';
function transform(billingAndUsageData: NonNullable<UseBillingAndUsageDataReturn>) {
const nextBillDate = billingAndUsageData.subscription.nextBillDate;
const nextBillDateTitle = nextBillDate.title;
const relativeNextBillDateTitle = nextBillDateTitle ? DateTime.fromMillis(Number(nextBillDateTitle)).toFormat('LLL dd, yyyy') as string : '';
return {
...billingAndUsageData,
subscription: {
...billingAndUsageData.subscription,
nextBillDate: {
...billingAndUsageData.subscription.nextBillDate,
title: relativeNextBillDateTitle,
}
}
};
}
type UseBillingAndUsageDataReturn = {
subscription: TSubscription,
usage: {
task: number;
}
} | null;
export default function useBillingAndUsageData(): UseBillingAndUsageDataReturn {
const { data, loading } = useQuery(GET_BILLING_AND_USAGE);
if (loading) return null;
return transform(data.getBillingAndUsage);
}

View File

@@ -140,5 +140,13 @@
"usageAlert.informationText": "Tasks: {consumedTaskCount}/{allowedTaskCount} (Resets {relativeResetDate})",
"usageAlert.viewPlans": "View plans",
"jsonViewer.noDataFound": "We couldn't find anything matching your search",
"planUpgrade.title": "Upgrade your plan"
}
"planUpgrade.title": "Upgrade your plan",
"usageDataInformation.subscriptionPlan": "Subscription plan",
"usageDataInformation.monthlyQuota": "Monthly quota",
"usageDataInformation.nextBillAmount": "Next bill amount",
"usageDataInformation.nextBillDate": "Next bill date",
"usageDataInformation.yourUsage": "Your usage",
"usageDataInformation.yourUsageDescription": "Last 30 days total usage",
"usageDataInformation.yourUsageTasks": "Tasks",
"usageDataInformation.upgrade": "Upgrade"
}

View File

@@ -4,7 +4,6 @@ import Grid from '@mui/material/Grid';
import * as URLS from 'config/urls';
import UsageDataInformation from 'components/UsageDataInformation/index.ee';
import UpgradeFreeTrial from 'components/UpgradeFreeTrial/index.ee';
import PageTitle from 'components/PageTitle';
import Container from 'components/Container';
import useFormatMessage from 'hooks/useFormatMessage';