import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Progress } from '@/components/ui/progress'; import { useNumber } from '@/hooks/use-numer-formatter'; import { useTRPC } from '@/integrations/trpc/react'; import { cn } from '@/utils/cn'; import { op } from '@/utils/op'; import type { IServiceOrganization } from '@openpanel/db'; import type { IPolarProduct } from '@openpanel/payments'; import { current } from '@reduxjs/toolkit'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { CheckIcon, ShuffleIcon } from 'lucide-react'; import { Fragment, useEffect, useState } from 'react'; import { toast } from 'sonner'; import { popModal } from '.'; import { ModalContent, ModalHeader } from './Modal/Container'; interface Props { organization: IServiceOrganization; currentProduct: IPolarProduct | null; } const getPrice = (product: IPolarProduct) => { return product.prices[0] && 'priceAmount' in product.prices[0] ? product.prices[0].priceAmount / 100 : 0; }; export default function SelectBillingPlan({ organization, currentProduct, }: Props) { const number = useNumber(); const trpc = useTRPC(); const queryClient = useQueryClient(); const productsQuery = useQuery( trpc.subscription.products.queryOptions({ organizationId: organization.id, }), ); const [recurringInterval, setRecurringInterval] = useState<'year' | 'month'>( (organization.subscriptionInterval as 'year' | 'month') || 'month', ); const [selectedProductId, setSelectedProductId] = useState( organization.subscriptionProductId || null, ); const products = productsQuery.data || []; const selectedProduct = products.find( (product) => product.id === selectedProductId, ); const checkoutMutation = useMutation( trpc.subscription.checkout.mutationOptions({ onSuccess(data) { if (data?.url) { window.location.href = data.url; } else { queryClient.invalidateQueries( trpc.organization.get.queryOptions({ organizationId: organization.id, }), ); queryClient.invalidateQueries( trpc.subscription.getCurrent.queryOptions({ organizationId: organization.id, }), ); toast.success('Subscription updated', { description: 'It might take a few seconds to update', }); popModal(); } }, onError(error) { toast.error(error.message); }, }), ); const cancelSubscription = useMutation( trpc.subscription.cancelSubscription.mutationOptions({ onSuccess() { queryClient.invalidateQueries( trpc.organization.get.queryOptions({ organizationId: organization.id, }), ); queryClient.invalidateQueries( trpc.subscription.getCurrent.queryOptions({ organizationId: organization.id, }), ); toast.success('Subscription canceled', { description: 'It might take a few seconds to update', }); popModal(); }, onError(error) { toast.error(error.message); }, }), ); const handleCheckout = () => { if (!selectedProduct) return; op.track('subscription_checkout_started', { organizationId: organization.id, limit: selectedProduct.metadata.eventsLimit, price: getPrice(selectedProduct), }); checkoutMutation.mutate({ organizationId: organization.id, productPriceId: selectedProduct.prices[0].id, productId: selectedProduct.id, }); }; const handleCancelSubscription = () => { if (!selectedProduct) return; op.track('subscription_canceled', { organizationId: organization.id, limit: selectedProduct.metadata.eventsLimit, price: getPrice(selectedProduct), }); cancelSubscription.mutate({ organizationId: organization.id, }); }; const renderAction = () => { if (!selectedProduct) { return null; } const isCurrentProduct = selectedProduct.id === currentProduct?.id; if (isCurrentProduct && organization.isActive) { return ( ); } const payLabel = (() => { if ( organization.isCanceled || organization.isWillBeCanceled || organization.isExpired ) { return isCurrentProduct ? 'Reactivate subscription' : 'Change subscription'; } if (currentProduct) { return 'Change subscription'; } return 'Pay with Polar'; })(); return ( ); }; return (
{currentProduct && (
Your current usage is{' '} {number.format(organization.subscriptionPeriodEventsCount)} out of{' '} {number.format(Number(currentProduct?.metadata.eventsLimit))}{' '} events.{' '} You cannot downgrade if your usage exceeds the limit of the new plan.
)}
{recurringInterval === 'year' ? ( 'Switch to monthly' ) : ( <> Switch to yearly and get{' '} 2 months for free )}
{products .filter((product) => product.prices.some((p) => p.amountType !== 'free'), ) .filter((product) => product.metadata.eventsLimit) .filter((product) => product.recurringInterval === recurringInterval) .map((product) => { const price = getPrice(product); const limit = product.metadata.eventsLimit ? Number(product.metadata.eventsLimit) : 0; const isProductDisabled = (limit > 0 && organization.subscriptionPeriodEventsCount >= limit) || !!product.disabled; return ( ); })}
{renderAction()}
); }