Files
medreport_mrb2b/packages/billing/gateway/src/components/current-subscription-card.tsx
2025-07-09 13:31:37 +03:00

150 lines
4.2 KiB
TypeScript

import { formatDate } from 'date-fns';
import { BadgeCheck } from 'lucide-react';
import { BillingConfig, getProductPlanPairByVariantId } from '@kit/billing';
import { Tables } from '@kit/supabase/database';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@kit/ui/card';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
import { CurrentPlanAlert } from './current-plan-alert';
import { CurrentPlanBadge } from './current-plan-badge';
import { LineItemDetails } from './line-item-details';
type Subscription = Tables<{ schema: 'medreport' }, 'subscriptions'>;
type LineItem = Tables<{ schema: 'medreport' }, 'subscription_items'>;
interface Props {
subscription: Subscription & {
items: LineItem[];
};
config: BillingConfig;
}
export function CurrentSubscriptionCard({
subscription,
config,
}: React.PropsWithChildren<Props>) {
const lineItems = subscription.items;
const firstLineItem = lineItems[0];
if (!firstLineItem) {
throw new Error('No line items found in subscription');
}
const { product, plan } = getProductPlanPairByVariantId(
config,
firstLineItem.variant_id,
);
if (!product || !plan) {
throw new Error(
'Product or plan not found. Did you forget to add it to the billing config?',
);
}
const productLineItems = plan.lineItems;
return (
<Card>
<CardHeader>
<CardTitle>
<Trans i18nKey="billing:planCardTitle" />
</CardTitle>
<CardDescription>
<Trans i18nKey="billing:planCardDescription" />
</CardDescription>
</CardHeader>
<CardContent className={'space-y-4 border-t pt-4 text-sm'}>
<div className={'flex flex-col space-y-1'}>
<div className={'flex items-center gap-x-3 text-lg font-semibold'}>
<BadgeCheck
className={
's-6 fill-green-500 text-white dark:fill-white dark:text-black'
}
/>
<span data-test={'current-plan-card-product-name'}>
<Trans i18nKey={product.name} defaults={product.name} />
</span>
<CurrentPlanBadge status={subscription.status} />
</div>
<div>
<p className={'text-muted-foreground'}>
<Trans
i18nKey={product.description}
defaults={product.description}
/>
</p>
</div>
</div>
{/*
Only show the alert if the subscription requires action
(e.g. trial ending soon, subscription canceled, etc.)
*/}
<If condition={!subscription.active}>
<div data-test={'current-plan-card-status-alert'}>
<CurrentPlanAlert status={subscription.status} />
</div>
</If>
<If condition={subscription.status === 'trialing'}>
<div className="flex flex-col gap-y-1">
<span className="font-semibold">
<Trans i18nKey="billing:trialEndsOn" />
</span>
<div className={'text-muted-foreground'}>
<span>
{subscription.trial_ends_at
? formatDate(subscription.trial_ends_at, 'P')
: ''}
</span>
</div>
</div>
</If>
<If condition={subscription.cancel_at_period_end}>
<Alert variant={'warning'}>
<AlertTitle>
<Trans i18nKey="billing:subscriptionCancelled" />
</AlertTitle>
<AlertDescription>
<Trans i18nKey="billing:cancelSubscriptionDate" />:
<span className={'ml-1'}>
{formatDate(subscription.period_ends_at ?? '', 'P')}
</span>
</AlertDescription>
</Alert>
</If>
<div className="flex flex-col gap-y-1">
<span className="font-semibold">
<Trans i18nKey="billing:detailsLabel" />
</span>
<LineItemDetails
lineItems={productLineItems}
currency={subscription.currency}
selectedInterval={firstLineItem.interval}
/>
</div>
</CardContent>
</Card>
);
}