diff --git a/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx
new file mode 100644
index 0000000000..4709bbcd65
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/dashboard/reactivate-subscription.tsx
@@ -0,0 +1,31 @@
+import { useTranslation } from 'react-i18next'
+import { postJSON } from '../../../../infrastructure/fetch-json'
+import { reactivateSubscriptionUrl } from '../../data/subscription-url'
+import { reload } from '../../../../shared/components/location'
+import useAsync from '../../../../shared/hooks/use-async'
+
+function ReactivateSubscription() {
+ const { t } = useTranslation()
+ const { isLoading, isSuccess, runAsync } = useAsync()
+
+ const handleReactivate = () => {
+ runAsync(postJSON(reactivateSubscriptionUrl)).catch(console.error)
+ }
+
+ if (isSuccess) {
+ reload()
+ }
+
+ return (
+
+ )
+}
+
+export default ReactivateSubscription
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx
index b920e74a3c..e7c6cd430a 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx
@@ -1,6 +1,7 @@
import { useTranslation, Trans } from 'react-i18next'
import { RecurlySubscription } from '../../../../../../../types/subscription/dashboard/subscription'
import PremiumFeaturesLink from '../premium-features-link'
+import ReactivateSubscription from '../reactivate-subscription'
export function CanceledSubscription({
subscription,
@@ -46,12 +47,7 @@ export function CanceledSubscription({
{t('view_your_invoices')}
-
+
>
)
}
diff --git a/services/web/frontend/js/features/subscription/data/subscription-url.ts b/services/web/frontend/js/features/subscription/data/subscription-url.ts
index 075635c8d6..01ad6a0fe5 100644
--- a/services/web/frontend/js/features/subscription/data/subscription-url.ts
+++ b/services/web/frontend/js/features/subscription/data/subscription-url.ts
@@ -4,3 +4,4 @@ export const cancelPendingSubscriptionChangeUrl =
export const cancelSubscriptionUrl = '/user/subscription/cancel'
export const redirectAfterCancelSubscriptionUrl = '/user/subscription/canceled'
export const extendTrialUrl = '/user/subscription/extend'
+export const reactivateSubscriptionUrl = '/user/subscription/reactivate'
diff --git a/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx
index 89ed466425..9d8d2f32e9 100644
--- a/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx
+++ b/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx
@@ -16,7 +16,10 @@ import {
cleanUpContext,
renderWithSubscriptionDashContext,
} from '../../helpers/render-with-subscription-dash-context'
+import { reactivateSubscriptionUrl } from '../../../../../../frontend/js/features/subscription/data/subscription-url'
+import * as locationModule from '../../../../../../frontend/js/shared/components/location'
import fetchMock from 'fetch-mock'
+import sinon from 'sinon'
describe('', function () {
afterEach(function () {
@@ -78,6 +81,37 @@ describe('', function () {
screen.getByRole('button', { name: 'Reactivate your subscription' })
})
+ it('reactivates canceled plan', async function () {
+ const reload = sinon.stub(locationModule, 'reload')
+
+ renderWithSubscriptionDashContext(, {
+ metaTags: [{ name: 'ol-subscription', value: canceledSubscription }],
+ })
+
+ const reactivateBtn = screen.getByRole('button', {
+ name: 'Reactivate your subscription',
+ })
+
+ // 1st click - fail
+ fetchMock.postOnce(reactivateSubscriptionUrl, 400)
+ fireEvent.click(reactivateBtn)
+ expect(reactivateBtn.disabled).to.be.true
+ await fetchMock.flush(true)
+ expect(reactivateBtn.disabled).to.be.false
+ expect(reload).not.to.have.been.called
+ fetchMock.reset()
+
+ // 2nd click - success
+ fetchMock.postOnce(reactivateSubscriptionUrl, 200)
+ fireEvent.click(reactivateBtn)
+ await fetchMock.flush(true)
+ expect(reload).to.have.been.calledOnce
+ expect(reactivateBtn.disabled).to.be.true
+ fetchMock.reset()
+
+ reload.restore()
+ })
+
it('renders the expired dash', function () {
renderWithSubscriptionDashContext(, {
metaTags: [
diff --git a/services/web/test/frontend/features/subscription/fixtures/subscriptions.tsx b/services/web/test/frontend/features/subscription/fixtures/subscriptions.tsx
index 5926e86ccc..bbdbec3515 100644
--- a/services/web/test/frontend/features/subscription/fixtures/subscriptions.tsx
+++ b/services/web/test/frontend/features/subscription/fixtures/subscriptions.tsx
@@ -475,6 +475,7 @@ export const trialCollaboratorSubscription: RecurlySubscription = {
trial_ends_at: new Date(sevenDaysFromToday).toString(),
activeCoupons: [],
account: {
+ email: 'foo@example.com',
has_canceled_subscription: {
_: 'false',
$: {
@@ -523,6 +524,7 @@ export const monthlyActiveCollaborator: RecurlySubscription = {
trial_ends_at: null,
activeCoupons: [],
account: {
+ email: 'foo@example.com',
has_canceled_subscription: { _: 'false', $: { type: 'boolean' } },
has_past_due_invoice: { _: 'false', $: { type: 'boolean' } },
},