mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
[web] Remove stale "You already have a subscription" notification (#33187)
* Remove stale "You already have a subscription" notification after cancel/plan change The notification was derived from a server-rendered meta tag set at page load, so it persisted through cancel and plan-change flows. Now derived directly from the URL param on the client; the param is stripped on cancel button click (replaceState) and before plan-change reloads (location.replace via reloadWithoutHasSubscription helper). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix format * Update services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix change-plan tests after location.reload → location.replace migration reloadWithoutHasSubscription calls location.replace() not location.reload(), so update assertions accordingly. Also stub toString() to return the jsdom origin so FlashMessage's replaceState call doesn't throw a SecurityError. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Guard reloadWithoutHasSubscription against empty URL When called after component unmount, useLocation's toString() returns '', causing new URL('') to throw. No-op early to avoid the exception. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Guard against empty URL in history state replacement for subscription cancellation --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> GitOrigin-RevId: 8408ee971adf038e2d819eae5df060ace62a7e14
This commit is contained in:
@@ -217,7 +217,6 @@ async function userSubscriptionPage(req, res) {
|
||||
const userCanExtendTrial = (
|
||||
await Modules.promises.hooks.fire('userCanExtendTrial', user)
|
||||
)?.[0]
|
||||
const fromPlansPage = req.query.hasSubscription
|
||||
const redirectedPaymentErrorCode = req.query.errorCode
|
||||
const isInTrial = SubscriptionHelper.isInTrial(
|
||||
personalSubscription?.payment?.trialEndsAt
|
||||
@@ -321,7 +320,6 @@ async function userSubscriptionPage(req, res) {
|
||||
planCodesChangingAtTermEnd: plansData?.planCodesChangingAtTermEnd,
|
||||
user,
|
||||
hasSubscription,
|
||||
fromPlansPage,
|
||||
redirectedPaymentErrorCode,
|
||||
personalSubscription,
|
||||
userCanExtendTrial,
|
||||
|
||||
@@ -47,7 +47,6 @@ block append meta
|
||||
content=currentInstitutionsWithLicence
|
||||
)
|
||||
meta(name='ol-hasSubscription' data-type='boolean' content=hasSubscription)
|
||||
meta(name='ol-fromPlansPage' data-type='boolean' content=fromPlansPage)
|
||||
meta(
|
||||
name='ol-subscriptionPaymentErrorCode'
|
||||
data-type='string'
|
||||
|
||||
@@ -4,9 +4,12 @@ import { useSubscriptionDashboardContext } from '../../../../context/subscriptio
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
import { stripHasSubscription } from '../../../../data/subscription-url'
|
||||
|
||||
export function CancelSubscriptionButton() {
|
||||
const { t } = useTranslation()
|
||||
const location = useLocation()
|
||||
const {
|
||||
recurlyLoadError,
|
||||
personalSubscription,
|
||||
@@ -35,6 +38,10 @@ export function CancelSubscriptionButton() {
|
||||
plan_code: subscription?.planCode,
|
||||
is_trial: isInTrial,
|
||||
})
|
||||
const url = location.toString()
|
||||
if (url) {
|
||||
window.history.replaceState(null, '', stripHasSubscription(url))
|
||||
}
|
||||
if (enablePause) {
|
||||
setModalIdShown('pause-subscription')
|
||||
} else {
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
import getMeta from '../../../../../../../../utils/meta'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../../context/subscription-dashboard-context'
|
||||
import GenericErrorAlert from '../../../../generic-error-alert'
|
||||
import { subscriptionUpdateUrl } from '../../../../../../data/subscription-url'
|
||||
import {
|
||||
subscriptionUpdateUrl,
|
||||
reloadWithoutHasSubscription,
|
||||
} from '../../../../../../data/subscription-url'
|
||||
import { getRecurlyGroupPlanCode } from '../../../../../../util/recurly-group-plan-code'
|
||||
import { useLocation } from '../../../../../../../../shared/hooks/use-location'
|
||||
import {
|
||||
@@ -139,11 +142,11 @@ export function ChangeToGroupModal() {
|
||||
),
|
||||
},
|
||||
})
|
||||
location.reload()
|
||||
reloadWithoutHasSubscription(location)
|
||||
} catch (e) {
|
||||
const { handled } = await handleStripePaymentAction(e as FetchError)
|
||||
if (handled) {
|
||||
location.reload()
|
||||
reloadWithoutHasSubscription(location)
|
||||
return
|
||||
}
|
||||
setError(true)
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
} from '../../../../../../../../infrastructure/fetch-json'
|
||||
import getMeta from '../../../../../../../../utils/meta'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../../context/subscription-dashboard-context'
|
||||
import { subscriptionUpdateUrl } from '../../../../../../data/subscription-url'
|
||||
import {
|
||||
subscriptionUpdateUrl,
|
||||
reloadWithoutHasSubscription,
|
||||
} from '../../../../../../data/subscription-url'
|
||||
import { useLocation } from '../../../../../../../../shared/hooks/use-location'
|
||||
import {
|
||||
OLModal,
|
||||
@@ -40,12 +43,12 @@ export function ConfirmChangePlanModal() {
|
||||
plan_code: planCodeToChangeTo,
|
||||
},
|
||||
})
|
||||
location.reload()
|
||||
reloadWithoutHasSubscription(location)
|
||||
} catch (e) {
|
||||
const fetchError = e as FetchError
|
||||
const { handled } = await handleStripePaymentAction(fetchError)
|
||||
if (handled) {
|
||||
location.reload()
|
||||
reloadWithoutHasSubscription(location)
|
||||
} else {
|
||||
setError(fetchError)
|
||||
setInflight(false)
|
||||
|
||||
@@ -3,7 +3,10 @@ import { useTranslation, Trans } from 'react-i18next'
|
||||
import { SubscriptionDashModalIds } from '../../../../../../../../../../types/subscription/dashboard/modal-ids'
|
||||
import { postJSON } from '../../../../../../../../infrastructure/fetch-json'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../../context/subscription-dashboard-context'
|
||||
import { cancelPendingSubscriptionChangeUrl } from '../../../../../../data/subscription-url'
|
||||
import {
|
||||
cancelPendingSubscriptionChangeUrl,
|
||||
reloadWithoutHasSubscription,
|
||||
} from '../../../../../../data/subscription-url'
|
||||
import { useLocation } from '../../../../../../../../shared/hooks/use-location'
|
||||
import {
|
||||
OLModal,
|
||||
@@ -30,7 +33,7 @@ export function KeepCurrentPlanModal() {
|
||||
|
||||
try {
|
||||
await postJSON(cancelPendingSubscriptionChangeUrl)
|
||||
location.reload()
|
||||
reloadWithoutHasSubscription(location)
|
||||
} catch (e) {
|
||||
setError(true)
|
||||
setInflight(false)
|
||||
|
||||
@@ -27,10 +27,13 @@ function SubscriptionDashboard() {
|
||||
personalSubscription,
|
||||
} = useSubscriptionDashboardContext()
|
||||
|
||||
const fromPlansPage = new URLSearchParams(window.location.search).has(
|
||||
'hasSubscription'
|
||||
)
|
||||
|
||||
const subscription = personalSubscription as PaidSubscription
|
||||
|
||||
const hasAiAssistViaWritefull = getMeta('ol-hasAiAssistViaWritefull')
|
||||
const fromPlansPage = getMeta('ol-fromPlansPage')
|
||||
const hasRedirectedPaymentError = Boolean(
|
||||
getMeta('ol-subscriptionPaymentErrorCode')
|
||||
)
|
||||
|
||||
@@ -6,3 +6,18 @@ export const redirectAfterCancelSubscriptionUrl = '/user/subscription/canceled'
|
||||
export const extendTrialUrl = '/user/subscription/extend'
|
||||
export const reactivateSubscriptionUrl = '/user/subscription/reactivate'
|
||||
export const billingPortalUrl = '/user/subscription/payment/account-management'
|
||||
|
||||
export function stripHasSubscription(url: string): string {
|
||||
const parsed = new URL(url)
|
||||
parsed.searchParams.delete('hasSubscription')
|
||||
return parsed.toString()
|
||||
}
|
||||
|
||||
export function reloadWithoutHasSubscription(location: {
|
||||
toString(): string
|
||||
replace(url: string): void
|
||||
}) {
|
||||
const url = location.toString()
|
||||
if (!url) return
|
||||
location.replace(stripHasSubscription(url))
|
||||
}
|
||||
|
||||
@@ -131,7 +131,6 @@ export interface Meta {
|
||||
'ol-featureUsage': FeatureUsage
|
||||
'ol-features': Features
|
||||
'ol-footer': FooterMetadata
|
||||
'ol-fromPlansPage': boolean
|
||||
'ol-galleryTagName': string
|
||||
'ol-gitBridgeEnabled': boolean
|
||||
'ol-gitBridgePublicBaseUrl': string
|
||||
|
||||
@@ -27,6 +27,7 @@ describe('<ChangePlanModal />', function () {
|
||||
beforeEach(function () {
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
this.locationWrapperStub.toString.returns('https://www.test-overleaf.com/')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
@@ -214,9 +215,9 @@ describe('<ChangePlanModal />', function () {
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// page is reloaded on success
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
const replaceStub = this.locationWrapperStub.replace
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
expect(replaceStub).to.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
@@ -304,9 +305,9 @@ describe('<ChangePlanModal />', function () {
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// page is reloaded on success
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
const replaceStub = this.locationWrapperStub.replace
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
expect(replaceStub).to.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
@@ -536,9 +537,9 @@ describe('<ChangePlanModal />', function () {
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// page is reloaded on success
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
const replaceStub = this.locationWrapperStub.replace
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
expect(replaceStub).to.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -84,15 +84,14 @@ describe('<SubscriptionDashboard />', function () {
|
||||
})
|
||||
|
||||
it('Show a warning when coming from plans page', function () {
|
||||
renderWithSubscriptionDashContext(<SubscriptionDashboard />, {
|
||||
metaTags: [
|
||||
{
|
||||
name: 'ol-fromPlansPage',
|
||||
value: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
window.history.pushState({}, '', '?hasSubscription=true')
|
||||
|
||||
screen.getByText('You already have a subscription')
|
||||
try {
|
||||
renderWithSubscriptionDashContext(<SubscriptionDashboard />)
|
||||
|
||||
screen.getByText('You already have a subscription')
|
||||
} finally {
|
||||
window.history.replaceState({}, '', window.location.pathname)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user