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' } }, },