import { Button } from '@/components/ui/button'; import { useNumber } from '@/hooks/use-numer-formatter'; import useWS from '@/hooks/use-ws'; import { useTRPC } from '@/integrations/trpc/react'; import { pushModal, useOnPushModal } from '@/modals'; import { formatDate } from '@/utils/date'; import type { IServiceOrganization } from '@openpanel/db'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { differenceInDays } from 'date-fns'; import { useQueryState } from 'nuqs'; import { useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { Progress } from '../ui/progress'; import { Widget, WidgetBody, WidgetHead } from '../widget'; import { BillingFaq } from './billing-faq'; import BillingUsage from './billing-usage'; type Props = { organization: IServiceOrganization; }; export default function Billing({ organization }: Props) { const [success, setSuccess] = useQueryState('customer_session_token'); const queryClient = useQueryClient(); const trpc = useTRPC(); const number = useNumber(); const productsQuery = useQuery( trpc.subscription.products.queryOptions({ organizationId: organization.id, }), ); const currentProductQuery = useQuery( trpc.subscription.getCurrent.queryOptions({ organizationId: organization.id, }), ); const portalMutation = useMutation( trpc.subscription.portal.mutationOptions({ onSuccess(data) { if (data?.url) { window.location.href = data.url; } }, onError(error) { toast.error(error.message); }, }), ); useWS(`/live/organization/${organization.id}`, () => { queryClient.invalidateQueries(trpc.organization.pathFilter()); }); const [recurringInterval, setRecurringInterval] = useState<'year' | 'month'>( (organization.subscriptionInterval as 'year' | 'month') || 'month', ); const products = useMemo(() => { return (productsQuery.data || []) .filter((product) => product.recurringInterval === recurringInterval) .filter((product) => product.prices.some((p) => p.amountType !== 'free')); }, [productsQuery.data, recurringInterval]); const currentProduct = currentProductQuery.data ?? null; const currentPrice = currentProduct?.prices.flatMap((p) => p.type === 'recurring' && p.amountType === 'fixed' ? [p] : [], )[0]; const renderStatus = () => { if (organization.isActive && organization.subscriptionCurrentPeriodEnd) { return (

Your subscription will be renewed on{' '} {formatDate(organization.subscriptionCurrentPeriodEnd)}

); } if (organization.isCanceled && organization.subscriptionCanceledAt) { return (

Your subscription was canceled on{' '} {formatDate(organization.subscriptionCanceledAt)}

); } if ( organization.isWillBeCanceled && organization.subscriptionCurrentPeriodEnd ) { return (

Your subscription will be canceled on{' '} {formatDate(organization.subscriptionCurrentPeriodEnd)}

); } if ( organization.subscriptionStatus === 'expired' && organization.subscriptionCurrentPeriodEnd ) { return (

Your subscription expired on{' '} {formatDate(organization.subscriptionCurrentPeriodEnd)}

); } if ( organization.subscriptionStatus === 'trialing' && organization.subscriptionEndsAt ) { return (

Your trial will end on {formatDate(organization.subscriptionEndsAt)}

); } return null; }; useEffect(() => { if (success) { pushModal('BillingSuccess'); } }, [success]); // Clear query state when modal is closed useOnPushModal('BillingSuccess', (open) => { if (!open) { setSuccess(null); } }); return (
{currentProduct && currentPrice ? (
{currentProduct.name}
{number.currency(currentPrice.priceAmount / 100)} {' / '} {recurringInterval === 'year' ? 'year' : 'month'}
{renderStatus()}
{number.format(organization.subscriptionPeriodEventsCount)} /{' '} {number.format(Number(currentProduct.metadata.eventsLimit))}
) : (
{organization.isTrial ? 'Get started' : 'No active subscription'}
{organization.isTrial ? '30 days free trial' : ''}
{organization.isTrial && organization.subscriptionEndsAt ? (

Your trial will end on{' '} {formatDate(organization.subscriptionEndsAt)} ( {differenceInDays( organization.subscriptionEndsAt, new Date(), ) + 1}{' '} days left)

) : (

Your trial has expired. Please upgrade your account to continue using Openpanel.

)}
{number.format(organization.subscriptionPeriodEventsCount)} /{' '} {number.format( Number(organization.subscriptionPeriodEventsLimit), )}
)}
); }