import { useCallback, useEffect } from 'react' import moment from 'moment' import { useTranslation, Trans } from 'react-i18next' import { SubscriptionChangePreview } from '../../../../../../types/subscription/subscription-change-preview' import getMeta from '@/utils/meta' import { formatCurrency } from '@/shared/utils/currency' import useAsync from '@/shared/hooks/use-async' import { useLocation } from '@/shared/hooks/use-location' import { debugConsole } from '@/utils/debugging' import { FetchError, postJSON } from '@/infrastructure/fetch-json' import OLCard from '@/shared/components/ol/ol-card' import OLRow from '@/shared/components/ol/ol-row' import OLCol from '@/shared/components/ol/ol-col' import OLButton from '@/shared/components/ol/ol-button' import { subscriptionUpdateUrl } from '@/features/subscription/data/subscription-url' import * as eventTracking from '@/infrastructure/event-tracking' import sparkleText from '@/shared/svgs/ai-sparkle-text.svg' import PaymentErrorNotification from '@/features/subscription/components/shared/payment-error-notification' import handleStripePaymentAction from '../../util/handle-stripe-payment-action' import RedirectedPaymentErrorNotification from '../shared/redirected-payment-error-notification' import TrialDisabledNotification from './trial-disabled-notification' import { getUpgradePlanDisplayName } from '../../util/plan-display-names' function FuturePayments({ preview }: { preview: SubscriptionChangePreview }) { const { t } = useTranslation() return ( <> {preview.nextInvoice.plan.name} {formatCurrency(preview.nextInvoice.plan.amount, preview.currency)} {preview.nextInvoice.addOns.map(addOn => ( {addOn.name} {addOn.quantity > 1 ? ` ×${addOn.quantity}` : ''} {formatCurrency(addOn.amount, preview.currency)} ))} {preview.nextInvoice.tax.rate > 0 && ( {t('vat')} {preview.nextInvoice.tax.rate * 100}% {formatCurrency(preview.nextInvoice.tax.amount, preview.currency)} )} {preview.nextPlan.annual ? t('total_per_year') : t('total_per_month')} {formatCurrency(preview.nextInvoice.total, preview.currency)} ) } function NextPaymentDate({ preview }: { preview: SubscriptionChangePreview }) { return (
}} shouldUnescape tOptions={{ interpolation: { escapeValue: true } }} />
) } function PreviewLayout({ children }: { children: React.ReactNode }) { return (
{children}
) } function DueTodayBlock({ preview, filteredLineItems, singleItemName, headingClassName, }: { preview: SubscriptionChangePreview filteredLineItems: SubscriptionChangePreview['immediateCharge']['lineItems'] singleItemName: string headingClassName?: string }) { const { t } = useTranslation() return ( <>

{t('due_today')}:

{filteredLineItems.length > 1 ? ( filteredLineItems.map((item, index) => ( {item.subtotal < 0 ? t('refund_with_description', { description: item.description, }) : item.description} {formatCurrency(item.subtotal, preview.currency)} )) ) : ( {singleItemName} {formatCurrency( preview.immediateCharge.subtotal, preview.currency )} )} {preview.immediateCharge.tax > 0 && ( {t('vat')} {preview.nextInvoice.tax.rate * 100}% {formatCurrency(preview.immediateCharge.tax, preview.currency)} )} {t('total_today')} {formatCurrency(preview.immediateCharge.total, preview.currency)} ) } function PreviewSubscriptionChange() { const preview = getMeta( 'ol-subscriptionChangePreview' ) as SubscriptionChangePreview const purchaseReferrer = getMeta('ol-purchaseReferrer') const { t } = useTranslation() const payNowTask = useAsync() const location = useLocation() const isPremiumUpgrade = preview.change.type === 'premium-subscription' const filteredLineItems = preview.immediateCharge.lineItems.filter( (item, index, arr) => { if (!item.isAiAssist) return true // TODO: this can be removed when all subscriptions are using Stripe const isCanceledByAnotherItem = arr.some( (otherItem, otherIndex) => otherIndex !== index && otherItem.isAiAssist && otherItem.subtotal + item.subtotal === 0 ) return !isCanceledByAnotherItem } ) useEffect(() => { if (preview.change.type === 'add-on-purchase') { eventTracking.sendMB('preview-subscription-change-view', { plan: preview.change.addOn.code, upgradeType: 'add-on', referrer: purchaseReferrer, }) } }, [preview.change, purchaseReferrer]) const handlePayNowClick = useCallback(() => { if (preview.change.type === 'add-on-purchase') { eventTracking.sendMB('subscription-change-form-submit', { plan: preview.change.addOn.code, upgradeType: 'add-on', referrer: purchaseReferrer, }) eventTracking.sendMB('assistant-add-on-purchase') } payNowTask .runAsync(payNow(preview)) .then(() => { if (preview.change.type === 'add-on-purchase') { eventTracking.sendMB('subscription-change-form-success', { plan: preview.change.addOn.code, upgradeType: 'add-on', referrer: purchaseReferrer, }) } location.replace( isPremiumUpgrade ? '/user/subscription/thank-you?upgrade=true' : '/user/subscription/thank-you' ) }) .catch(debugConsole.error) }, [purchaseReferrer, location, payNowTask, preview, isPremiumUpgrade]) if (preview.change.type === 'premium-subscription') { const change = preview.change const upgradePlanName = getUpgradePlanDisplayName(change.plan.code, t) return (

{t('upgrade_to_plan', { planName: upgradePlanName })}

{payNowTask.isError && ( )}

{t('payment_summary')}


{t('future_payments')}:

{t('upgrade_now')}
) } const aiAddOnChange = preview.change.type === 'add-on-purchase' && preview.change.addOn.code === 'assistant' const changeName = preview.change.type === 'add-on-purchase' ? preview.change.addOn.name : '' return ( {preview.change.type === 'add-on-purchase' ? (

{t('add_add_on_to_your_plan', { addOnName: preview.change.addOn.name, })}

) : null} {payNowTask.isError && ( )} {aiAddOnChange && (
)}
}} shouldUnescape tOptions={{ interpolation: { escapeValue: true } }} />{' '} }} shouldUnescape tOptions={{ interpolation: { escapeValue: true } }} />
{aiAddOnChange && (
*{t('fair_usage_policy_applies')}
)}
{t('pay_now')}

{t('future_payments')}:

) } async function payNow(preview: SubscriptionChangePreview) { const successPath = preview.change.type === 'premium-subscription' ? '/user/subscription/thank-you?upgrade=true' : '/user/subscription/thank-you' try { if (preview.change.type === 'add-on-purchase') { await postJSON( `/user/subscription/addon/${preview.change.addOn.code}/add` ) } else if (preview.change.type === 'premium-subscription') { await postJSON(subscriptionUpdateUrl, { body: { plan_code: preview.change.plan.code }, }) } else { throw new Error( `Unknown subscription change preview type: ${preview.change}` ) } } catch (e) { const { handled } = await handleStripePaymentAction(e as FetchError, { successPath, }) if (!handled) { throw e } } } export default PreviewSubscriptionChange