Files
Verso/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx
T
dd44f4e2e8 [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
2026-05-01 08:06:41 +00:00

132 lines
4.7 KiB
TypeScript

import { Trans, useTranslation } from 'react-i18next'
import ContactSupport from './contact-support-for-custom-subscription'
import GroupSubscriptionMemberships from './group-subscription-memberships'
import InstitutionMemberships from './institution-memberships'
import FreePlan from './free-plan'
import ManagedPublishers from './managed-publishers'
import PersonalSubscription from './personal-subscription'
import ManagedGroupSubscriptions from './managed-group-subscriptions'
import ManagedInstitutions from './managed-institutions'
import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
import getMeta from '../../../../utils/meta'
import PremiumFeaturesLink from './premium-features-link'
import OLPageContentCard from '@/shared/components/ol/ol-page-content-card'
import OLRow from '@/shared/components/ol/ol-row'
import OLCol from '@/shared/components/ol/ol-col'
import OLNotification from '@/shared/components/ol/ol-notification'
import WritefullManagedBundleAddOn from './states/active/change-plan/modals/writefull-bundle-management-modal'
import RedirectAlerts from './redirect-alerts'
import { PaidSubscription } from '@ol-types/subscription/dashboard/subscription'
function SubscriptionDashboard() {
const { t } = useTranslation()
const {
hasDisplayedSubscription,
hasSubscription,
hasValidActiveSubscription,
personalSubscription,
} = useSubscriptionDashboardContext()
const fromPlansPage = new URLSearchParams(window.location.search).has(
'hasSubscription'
)
const subscription = personalSubscription as PaidSubscription
const hasAiAssistViaWritefull = getMeta('ol-hasAiAssistViaWritefull')
const hasRedirectedPaymentError = Boolean(
getMeta('ol-subscriptionPaymentErrorCode')
)
const hasPendingPlan =
subscription &&
subscription.pendingPlan &&
subscription.pendingPlan.name !== subscription.plan.name
const nextPaymentDueDate = subscription?.payment?.nextPaymentDueDate
return (
<div className="container">
<OLRow>
<OLCol lg={{ span: 8, offset: 2 }}>
{hasPendingPlan && (
<OLNotification
className="mb-4"
aria-live="polite"
content={
<div>
<Trans
i18nKey="pending_subscription_message"
values={{
planName: personalSubscription?.pendingPlan?.name,
activationDate: nextPaymentDueDate,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
components={[
// eslint-disable-next-line react/jsx-key
<strong />,
]}
/>
</div>
}
type="success"
/>
)}
{fromPlansPage && (
<OLNotification
className="mb-4"
aria-live="polite"
content={t('you_already_have_a_subscription')}
type="warning"
/>
)}
{hasRedirectedPaymentError && (
<OLNotification
className="mb-4"
aria-live="polite"
content={
<Trans
i18nKey="payment_error_generic"
components={[
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
<a href="/contact" target="_blank" />,
]}
/>
}
type="error"
/>
)}
<RedirectAlerts />
<OLPageContentCard>
<div className="page-header">
<h1>{t('your_subscriptions')}</h1>
</div>
<div>
<PersonalSubscription />
<ManagedGroupSubscriptions />
<ManagedInstitutions />
<ManagedPublishers />
<GroupSubscriptionMemberships />
<InstitutionMemberships />
{!personalSubscription && hasAiAssistViaWritefull && (
<div className="mb-4">
<h2 className="h3 fw-bold">{t('add_ons')}</h2>
<WritefullManagedBundleAddOn />
</div>
)}
{hasValidActiveSubscription && (
<PremiumFeaturesLink subscription={personalSubscription} />
)}
{!hasDisplayedSubscription &&
(hasSubscription ? <ContactSupport /> : <FreePlan />)}
</div>
</OLPageContentCard>
</OLCol>
</OLRow>
</div>
)
}
export default SubscriptionDashboard