/* eslint-disable @typescript-eslint/no-loop-func */ import './BillingContainer.styles.scss'; import { CheckCircleOutlined } from '@ant-design/icons'; import { Color } from '@signozhq/design-tokens'; import { Alert, Button, Card, Col, Flex, Row, Skeleton, Table, Tag, Typography, } from 'antd'; import { ColumnsType } from 'antd/es/table'; import updateCreditCardApi from 'api/billing/checkout'; import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage'; import manageCreditCardApi from 'api/billing/manage'; import Spinner from 'components/Spinner'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import useAnalytics from 'hooks/analytics/useAnalytics'; import useAxiosError from 'hooks/useAxiosError'; import useLicense from 'hooks/useLicense'; import { useNotifications } from 'hooks/useNotifications'; import { pick } from 'lodash-es'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useMutation, useQuery } from 'react-query'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout'; import { License } from 'types/api/licenses/def'; import AppReducer from 'types/reducer/app'; import { isCloudUser } from 'utils/app'; import { getFormattedDate, getRemainingDays } from 'utils/timeUtils'; import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph'; interface DataType { key: string; name: string; unit: string; dataIngested: string; pricePerUnit: string; cost: string; } enum SubscriptionStatus { PastDue = 'past_due', Active = 'active', } const renderSkeletonInput = (): JSX.Element => ( ); const dummyData: DataType[] = [ { key: '1', name: 'Logs', unit: '', dataIngested: '', pricePerUnit: '', cost: '', }, { key: '2', name: 'Traces', unit: '', dataIngested: '', pricePerUnit: '', cost: '', }, { key: '3', name: 'Metrics', unit: '', dataIngested: '', pricePerUnit: '', cost: '', }, ]; const dummyColumns: ColumnsType = [ { title: '', dataIndex: 'name', key: 'name', render: renderSkeletonInput, }, { title: 'Unit', dataIndex: 'unit', key: 'unit', render: renderSkeletonInput, }, { title: 'Data Ingested', dataIndex: 'dataIngested', key: 'dataIngested', render: renderSkeletonInput, }, { title: 'Price per Unit', dataIndex: 'pricePerUnit', key: 'pricePerUnit', render: renderSkeletonInput, }, { title: 'Cost (Billing period to date)', dataIndex: 'cost', key: 'cost', render: renderSkeletonInput, }, ]; // eslint-disable-next-line sonarjs/cognitive-complexity export default function BillingContainer(): JSX.Element { const { t } = useTranslation(['billings']); const daysRemainingStr = t('days_remaining'); const [headerText, setHeaderText] = useState(''); const [billAmount, setBillAmount] = useState(0); const [activeLicense, setActiveLicense] = useState(null); const [daysRemaining, setDaysRemaining] = useState(0); const [isFreeTrial, setIsFreeTrial] = useState(false); const [data, setData] = useState([]); const [apiResponse, setApiResponse] = useState< Partial >({}); const { trackEvent } = useAnalytics(); const { isFetching, data: licensesData, error: licenseError } = useLicense(); const { user, org } = useSelector((state) => state.app); const { notifications } = useNotifications(); const handleError = useAxiosError(); const isCloudUserVal = isCloudUser(); const processUsageData = useCallback( (data: any): void => { const { details: { breakdown = [], billTotal }, billingPeriodStart, billingPeriodEnd, } = data?.payload || {}; const formattedUsageData: any[] = []; if (breakdown && Array.isArray(breakdown)) { for (let index = 0; index < breakdown.length; index += 1) { const element = breakdown[index]; element?.tiers.forEach( ( tier: { quantity: number; unitPrice: number; tierCost: number }, i: number, ) => { formattedUsageData.push({ key: `${index}${i}`, name: i === 0 ? element?.type : '', dataIngested: `${tier.quantity} ${element?.unit}`, pricePerUnit: tier.unitPrice, cost: `$ ${tier.tierCost}`, }); }, ); } } setData(formattedUsageData); if (!licensesData?.payload?.onTrial) { const remainingDays = getRemainingDays(billingPeriodEnd) - 1; setHeaderText( `Your current billing period is from ${getFormattedDate( billingPeriodStart, )} to ${getFormattedDate(billingPeriodEnd)}`, ); setDaysRemaining(remainingDays > 0 ? remainingDays : 0); setBillAmount(billTotal); } setApiResponse(data?.payload || {}); }, [licensesData?.payload?.onTrial], ); const isSubscriptionPastDue = apiResponse.subscriptionStatus === SubscriptionStatus.PastDue; const { isLoading, isFetching: isFetchingBillingData } = useQuery( [REACT_QUERY_KEY.GET_BILLING_USAGE, user?.userId], { queryFn: () => getUsage(activeLicense?.key || ''), onError: handleError, enabled: activeLicense !== null, onSuccess: processUsageData, }, ); useEffect(() => { const activeValidLicense = licensesData?.payload?.licenses?.find( (license) => license.isCurrent === true, ) || null; setActiveLicense(activeValidLicense); if (!isFetching && licensesData?.payload?.onTrial && !licenseError) { const remainingDays = getRemainingDays(licensesData?.payload?.trialEnd); setIsFreeTrial(true); setBillAmount(0); setDaysRemaining(remainingDays > 0 ? remainingDays : 0); setHeaderText( `You are in free trial period. Your free trial will end on ${getFormattedDate( licensesData?.payload?.trialEnd, )}`, ); } }, [isFetching, licensesData?.payload, licenseError]); const columns: ColumnsType = [ { title: '', dataIndex: 'name', key: 'name', render: (text): JSX.Element =>
{text}
, }, { title: 'Data Ingested', dataIndex: 'dataIngested', key: 'dataIngested', }, { title: 'Price per Unit', dataIndex: 'pricePerUnit', key: 'pricePerUnit', }, { title: 'Cost (Billing period to date)', dataIndex: 'cost', key: 'cost', }, ]; const renderTableSkeleton = (): JSX.Element => ( ( )), }} /> ); const handleBillingOnSuccess = ( data: ErrorResponse | SuccessResponse, ): void => { if (data?.payload?.redirectURL) { const newTab = document.createElement('a'); newTab.href = data.payload.redirectURL; newTab.target = '_blank'; newTab.rel = 'noopener noreferrer'; newTab.click(); } }; const handleBillingOnError = (): void => { notifications.error({ message: SOMETHING_WENT_WRONG, }); }; const { mutate: updateCreditCard, isLoading: isLoadingBilling } = useMutation( updateCreditCardApi, { onSuccess: (data) => { handleBillingOnSuccess(data); }, onError: handleBillingOnError, }, ); const { mutate: manageCreditCard, isLoading: isLoadingManageBilling, } = useMutation(manageCreditCardApi, { onSuccess: (data) => { handleBillingOnSuccess(data); }, onError: handleBillingOnError, }); const handleBilling = useCallback(async () => { if (isFreeTrial && !licensesData?.payload?.trialConvertedToSubscription) { trackEvent('Billing : Upgrade Plan', { user: pick(user, ['email', 'userId', 'name']), org, }); updateCreditCard({ licenseKey: activeLicense?.key || '', successURL: window.location.href, cancelURL: window.location.href, }); } else { trackEvent('Billing : Manage Billing', { user: pick(user, ['email', 'userId', 'name']), org, }); manageCreditCard({ licenseKey: activeLicense?.key || '', successURL: window.location.href, cancelURL: window.location.href, }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ activeLicense?.key, isFreeTrial, licensesData?.payload?.trialConvertedToSubscription, manageCreditCard, updateCreditCard, ]); const BillingUsageGraphCallback = useCallback( () => !isLoading && !isFetchingBillingData ? ( ) : ( ), [apiResponse, billAmount, isLoading, isFetchingBillingData], ); const { Text } = Typography; const subscriptionPastDueMessage = (): JSX.Element => ( {`We were not able to process payments for your account. Please update your card details `} {t('here')} {` if your payment information has changed. Email us at `} cloud-support@signoz.io {` otherwise. Be sure to provide this information immediately to avoid interruption to your service.`} ); return (
{t('billing')} {t('manage_billing_and_costs')} {isCloudUserVal ? t('enterprise_cloud') : t('enterprise')}{' '} {isFreeTrial ? Free Trial : ''} {!isLoading && !isFetchingBillingData ? ( {daysRemaining} {daysRemainingStr} ) : null} {licensesData?.payload?.onTrial && licensesData?.payload?.trialConvertedToSubscription && ( {t('card_details_recieved_and_billing_info')} )} {!isLoading && !isFetchingBillingData ? ( ) : ( )} {isSubscriptionPastDue && (!isLoading && !isFetchingBillingData ? ( ) : ( ))}
{!isLoading && !isFetchingBillingData && (
)} {(isLoading || isFetchingBillingData) && renderTableSkeleton()} {isFreeTrial && !licensesData?.payload?.trialConvertedToSubscription && (
{t('upgrade_now_text')} {t('Your billing will start only after the trial period')} {t('checkout_plans')}   {t('here')} )} ); }