Merge pull request #36864 from overleaf/rh-36644-cancel-spinner-fallback

Fix cancellation flow if downgrade upsell plan is not available

GitOrigin-RevId: c34f7f37057d5e0c08cbdd5f8524a7c5f00ae8b4
This commit is contained in:
roo hutton
2026-09-17 12:05:27 +00:00
committed by Copybot
parent b19f7fb6ad
commit e6ceb4fec4
2 changed files with 64 additions and 8 deletions
@@ -141,8 +141,12 @@ function NotCancelOption({
export function CancelSubscription() {
const { t } = useTranslation()
const location = useLocation()
const { personalSubscription, plans, userCanExtendTrial } =
useSubscriptionDashboardContext()
const {
personalSubscription,
plans,
queryingIndividualPlansData,
userCanExtendTrial,
} = useSubscriptionDashboardContext()
const lossMessagingEnabled = useFeatureFlag('cancel-loss-messaging')
const {
isLoading: isLoadingCancel,
@@ -164,14 +168,15 @@ export function CancelSubscription() {
if (!personalSubscription || !('payment' in personalSubscription)) return null
const showDowngrade =
personalSubscription.payment.isEligibleForDowngradeUpsell
const { isEligibleForDowngradeUpsell } = personalSubscription.payment
if (isEligibleForDowngradeUpsell && queryingIndividualPlansData) {
return <LoadingSpinner />
}
const planToDowngradeTo = plans.find(
plan => plan.planCode === planCodeToDowngradeTo
)
if (showDowngrade && !planToDowngradeTo) {
return <LoadingSpinner />
}
const showDowngrade =
isEligibleForDowngradeUpsell && Boolean(planToDowngradeTo)
async function handleCancelSubscription() {
try {
@@ -16,8 +16,13 @@ import {
trialSubscription,
} from '../../../../fixtures/subscriptions'
import sinon from 'sinon'
import { cleanUpContext } from '../../../../helpers/render-with-subscription-dash-context'
import {
cleanUpContext,
renderWithSubscriptionDashContext,
} from '../../../../helpers/render-with-subscription-dash-context'
import { renderActiveSubscription } from '../../../../helpers/render-active-subscription'
import { ActiveSubscription } from '../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
import { plans } from '../../../../fixtures/plans'
import { cloneDeep } from 'lodash'
import fetchMock from 'fetch-mock'
import {
@@ -690,6 +695,52 @@ describe('<ActiveSubscription />', function () {
).to.be.null
})
it('falls back to the cancellation confirmation when the downgrade plan is unavailable', async function () {
renderWithSubscriptionDashContext(
<ActiveSubscription subscription={monthlyActiveCollaborator} />,
{
metaTags: [
{
name: 'ol-plans',
value: plans.filter(plan => plan.planCode !== 'paid-personal'),
},
{ name: 'ol-subscription', value: monthlyActiveCollaborator },
{ name: 'ol-recommendedCurrency', value: 'USD' },
],
}
)
showConfirmCancelUI()
await screen.findByRole('button', { name: 'Cancel my subscription' })
screen.getByRole('button', { name: 'I want to stay' })
expect(
screen.queryByRole('button', {
name: downgradeButtonText,
})
).to.be.null
})
it('shows a loading state while the plans data is being queried', function () {
renderWithSubscriptionDashContext(
<ActiveSubscription subscription={monthlyActiveCollaborator} />,
{
metaTags: [
{ name: 'ol-subscription', value: monthlyActiveCollaborator },
{ name: 'ol-recommendedCurrency', value: 'USD' },
],
queryingRecurly: true,
}
)
showConfirmCancelUI()
screen.getByRole('status')
expect(
screen.queryByRole('button', {
name: downgradeButtonText,
})
).to.be.null
expect(screen.queryByRole('button', { name: 'Cancel my subscription' }))
.to.be.null
})
it('does not show option to extend trial when on a collaborator trial', function () {
const trialPlan = cloneDeep(trialCollaboratorSubscription)
renderActiveSubscription(trialPlan)