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 (
)
}
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')}:
)
}
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('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