Merge pull request #27999 from overleaf/em-reactivate-add-on

Add option to reactivate an add-on

GitOrigin-RevId: a1795f37dac5141996d626d87ba3a9bae1d218dd
This commit is contained in:
Eric Mc Sween
2025-09-11 08:05:12 +00:00
committed by Copybot
parent ae9d84c279
commit daba09c96f
9 changed files with 241 additions and 54 deletions
@@ -72,6 +72,7 @@ export function ActiveSubscription({
}
const handlePlanChange = () => setModalIdShown('change-plan')
const handleCancelClick = (addOnCode: string) => {
if (
[AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE, AI_ADD_ON_CODE].includes(
@@ -81,6 +82,7 @@ export function ActiveSubscription({
setModalIdShown('cancel-ai-add-on')
}
}
const hasPendingPause = Boolean(
subscription.payment.state === 'active' &&
subscription.payment.remainingPauseCycles &&
@@ -1,8 +1,13 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import getMeta from '@/utils/meta'
import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap'
import { postJSON } from '@/infrastructure/fetch-json'
import { debugConsole } from '@/utils/debugging'
import OLDropdownMenuItem from '@/shared/components/ol/ol-dropdown-menu-item'
import OLSpinner from '@/shared/components/ol/ol-spinner'
import MaterialIcon from '@/shared/components/material-icon'
import { useLocation } from '@/shared/hooks/use-location'
import {
ADD_ON_NAME,
AI_ADD_ON_CODE,
@@ -38,6 +43,8 @@ function resolveAddOnName(addOnCode: string) {
}
}
type ReactivateState = 'ready' | 'reactivating' | 'error'
function AddOn({
addOnCode,
displayPrice,
@@ -47,6 +54,22 @@ function AddOn({
nextBillingDate,
}: AddOnProps) {
const { t } = useTranslation()
const location = useLocation()
const [reactivateState, setReactivateState] =
useState<ReactivateState>('ready')
const handleReactivateClick = (addOnCode: string) => {
setReactivateState('reactivating')
postJSON(`/user/subscription/addon/${addOnCode}/reactivate`)
.then(() => {
location.reload()
})
.catch(err => {
debugConsole.error(err)
setReactivateState('error')
})
}
return (
<div className="add-on-card">
<div>
@@ -60,17 +83,28 @@ function AddOn({
<div className="add-on-card-content">
<div className="heading">{resolveAddOnName(addOnCode)}</div>
<div className="description small mt-1">
{pendingCancellation
? t(
'your_add_on_has_been_cancelled_and_will_remain_active_until_your_billing_cycle_ends_on',
{ nextBillingDate }
)
: isAnnual
? t('x_price_per_year', { price: displayPrice })
: t('x_price_per_month', { price: displayPrice })}
{reactivateState === 'reactivating' ? (
<>
{t('reactivating')} <OLSpinner size="sm" />
</>
) : pendingCancellation ? (
t(
'your_add_on_has_been_cancelled_and_will_remain_active_until_your_billing_cycle_ends_on',
{ nextBillingDate }
)
) : isAnnual ? (
t('x_price_per_year', { price: displayPrice })
) : (
t('x_price_per_month', { price: displayPrice })
)}
</div>
{reactivateState === 'error' && (
<div className="small mt-1 text-danger">
{t('reactivate_add_on_failed')}
</div>
)}
</div>
{!pendingCancellation && (
{reactivateState !== 'reactivating' && (
<div className="ms-auto">
<Dropdown align="end">
<DropdownToggle
@@ -84,14 +118,24 @@ function AddOn({
/>
</DropdownToggle>
<DropdownMenu flip={false}>
<OLDropdownMenuItem
onClick={() => handleCancelClick(addOnCode)}
as="button"
tabIndex={-1}
variant="danger"
>
{t('cancel')}
</OLDropdownMenuItem>
{pendingCancellation ? (
<OLDropdownMenuItem
onClick={() => handleReactivateClick(addOnCode)}
as="button"
tabIndex={-1}
>
{t('reactivate')}
</OLDropdownMenuItem>
) : (
<OLDropdownMenuItem
onClick={() => handleCancelClick(addOnCode)}
as="button"
tabIndex={-1}
variant="danger"
>
{t('cancel')}
</OLDropdownMenuItem>
)}
</DropdownMenu>
</Dropdown>
</div>