mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 10:23:46 -05:00
Merge pull request #11681 from overleaf/jel-confirm-subscription-change
[web] Plan change request in subscription dash GitOrigin-RevId: 9afe22b2da035f887c6fa9abe8129711d492108c
This commit is contained in:
parent
4dcdd2e07a
commit
d539aaf226
11 changed files with 523 additions and 75 deletions
|
@ -224,6 +224,7 @@
|
|||
"error": "",
|
||||
"error_performing_request": "",
|
||||
"example_project": "",
|
||||
"existing_plan_active_until_term_end": "",
|
||||
"expand": "",
|
||||
"export_csv": "",
|
||||
"export_project_to_github": "",
|
||||
|
@ -286,6 +287,7 @@
|
|||
"galileo_suggestion_feedback_button": "",
|
||||
"galileo_suggestions_loading_error": "",
|
||||
"galileo_toggle_description": "",
|
||||
"generic_if_problem_continues_contact_us": "",
|
||||
"generic_linked_file_compile_error": "",
|
||||
"generic_something_went_wrong": "",
|
||||
"get_collaborative_benefits": "",
|
||||
|
@ -566,6 +568,7 @@
|
|||
"proceed_to_paypal": "",
|
||||
"proceeding_to_paypal_takes_you_to_the_paypal_site_to_pay": "",
|
||||
"processing": "",
|
||||
"processing_uppercase": "",
|
||||
"professional": "",
|
||||
"project": "",
|
||||
"project_approaching_file_limit": "",
|
||||
|
@ -633,6 +636,7 @@
|
|||
"resend_confirmation_email": "",
|
||||
"resending_confirmation_email": "",
|
||||
"reverse_x_sort_order": "",
|
||||
"revert_pending_plan_change": "",
|
||||
"review": "",
|
||||
"revoke": "",
|
||||
"revoke_invite": "",
|
||||
|
@ -733,6 +737,8 @@
|
|||
"subscription_admins_cannot_be_deleted": "",
|
||||
"subscription_canceled_and_terminate_on_x": "",
|
||||
"subscription_will_remain_active_until_end_of_billing_period_x": "",
|
||||
"sure_you_want_to_cancel_plan_change": "",
|
||||
"sure_you_want_to_change_plan": "",
|
||||
"sure_you_want_to_delete": "",
|
||||
"switch_to_editor": "",
|
||||
"switch_to_pdf": "",
|
||||
|
|
|
@ -2,7 +2,9 @@ import { useTranslation } from 'react-i18next'
|
|||
import LoadingSpinner from '../../../../../../../shared/components/loading-spinner'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
|
||||
import { ChangeToGroupPlan } from './change-to-group-plan'
|
||||
import { ConfirmChangePlanModal } from './confirm-change-plan-modal'
|
||||
import { IndividualPlansTable } from './individual-plans-table'
|
||||
import { KeepCurrentPlanModal } from './keep-current-plan-modal'
|
||||
|
||||
export function ChangePlan() {
|
||||
const { t } = useTranslation()
|
||||
|
@ -28,6 +30,8 @@ export function ChangePlan() {
|
|||
<h2>{t('change_plan')}</h2>
|
||||
<IndividualPlansTable plans={plans} />
|
||||
<ChangeToGroupPlan />
|
||||
<ConfirmChangePlanModal />
|
||||
<KeepCurrentPlanModal />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
import { useState } from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { postJSON } from '../../../../../../../infrastructure/fetch-json'
|
||||
import AccessibleModal from '../../../../../../../shared/components/accessible-modal'
|
||||
import getMeta from '../../../../../../../utils/meta'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
|
||||
import { subscriptionUrl } from '../../../../../data/subscription-url'
|
||||
|
||||
export function ConfirmChangePlanModal() {
|
||||
const modalId = 'change-to-plan'
|
||||
const [error, setError] = useState(false)
|
||||
const [inflight, setInflight] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const { handleCloseModal, modalIdShown, plans, planCodeToChangeTo } =
|
||||
useSubscriptionDashboardContext()
|
||||
const planCodesChangingAtTermEnd = getMeta('ol-planCodesChangingAtTermEnd')
|
||||
|
||||
async function handleConfirmChange() {
|
||||
setError(false)
|
||||
setInflight(true)
|
||||
|
||||
try {
|
||||
await postJSON(`${subscriptionUrl}?origin=confirmChangePlan`, {
|
||||
body: {
|
||||
plan_code: planCodeToChangeTo,
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
setError(true)
|
||||
setInflight(false)
|
||||
}
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
if (modalIdShown !== modalId || !planCodeToChangeTo) return null
|
||||
|
||||
const plan = plans.find(p => p.planCode === planCodeToChangeTo)
|
||||
if (!plan) return null
|
||||
|
||||
const planWillChangeAtTermEnd =
|
||||
planCodesChangingAtTermEnd?.indexOf(planCodeToChangeTo) > -1
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
id={modalId}
|
||||
show
|
||||
animation
|
||||
onHide={handleCloseModal}
|
||||
backdrop="static"
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('change_plan')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body>
|
||||
{error && (
|
||||
<div className="alert alert-warning">
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</div>
|
||||
)}
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="sure_you_want_to_change_plan"
|
||||
values={{
|
||||
planName: plan.name,
|
||||
}}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
{planWillChangeAtTermEnd && (
|
||||
<>
|
||||
<p>{t('existing_plan_active_until_term_end')}</p>
|
||||
<p>{t('want_change_to_apply_before_plan_end')}</p>
|
||||
</>
|
||||
)}
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<button
|
||||
disabled={inflight}
|
||||
className="btn btn-secondary"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
<button
|
||||
disabled={inflight}
|
||||
className="btn btn-primary"
|
||||
onClick={handleConfirmChange}
|
||||
>
|
||||
{!inflight ? t('change_plan') : t('processing_uppercase') + '…'}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
)
|
||||
}
|
|
@ -3,37 +3,33 @@ import { Plan } from '../../../../../../../../../types/subscription/plan'
|
|||
import Icon from '../../../../../../../shared/components/icon'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
|
||||
|
||||
function ChangeToPlanButton({ plan }: { plan: Plan }) {
|
||||
function ChangeToPlanButton({ planCode }: { planCode: string }) {
|
||||
const { t } = useTranslation()
|
||||
// for when the user selected to change a plan, but the plan change is still pending
|
||||
const { handleOpenModal } = useSubscriptionDashboardContext()
|
||||
|
||||
const handleClick = () => {
|
||||
handleOpenModal('change-to-plan', planCode)
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* todo: ng-model="plan_code" */}
|
||||
<input type="hidden" name="plan_code" value={plan.planCode} />
|
||||
{/* todo: handle submit changePlan */}
|
||||
<input
|
||||
type="submit"
|
||||
value={t('change_to_this_plan')}
|
||||
className="btn btn-primary"
|
||||
/>
|
||||
</form>
|
||||
<button className="btn btn-primary" onClick={handleClick}>
|
||||
{t('change_to_this_plan')}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function KeepCurrentPlanButton({ plan }: { plan: Plan }) {
|
||||
const { t } = useTranslation()
|
||||
// for when the user selected to change a plan, but the plan change is still pending
|
||||
const { handleOpenModal } = useSubscriptionDashboardContext()
|
||||
|
||||
const handleClick = () => {
|
||||
handleOpenModal('keep-current-plan')
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
{/* todo: ng-model="plan_code" */}
|
||||
<input type="hidden" name="plan_code" value={plan.planCode} />
|
||||
{/* todo: handle submit cancelPendingPlanChange */}
|
||||
<input
|
||||
type="submit"
|
||||
value={t('keep_current_plan')}
|
||||
className="btn btn-primary"
|
||||
/>
|
||||
</form>
|
||||
<button className="btn btn-primary" onClick={handleClick}>
|
||||
{t('keep_current_plan')}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -61,7 +57,7 @@ function ChangePlanButton({ plan }: { plan: Plan }) {
|
|||
</b>
|
||||
)
|
||||
} else {
|
||||
return <ChangeToPlanButton plan={plan} />
|
||||
return <ChangeToPlanButton planCode={plan.planCode} />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import { useState } from 'react'
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { postJSON } from '../../../../../../../infrastructure/fetch-json'
|
||||
import AccessibleModal from '../../../../../../../shared/components/accessible-modal'
|
||||
import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
|
||||
import { cancelPendingSubscriptionChangeUrl } from '../../../../../data/subscription-url'
|
||||
|
||||
export function KeepCurrentPlanModal() {
|
||||
const modalId = 'keep-current-plan'
|
||||
const [error, setError] = useState(false)
|
||||
const [inflight, setInflight] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const { modalIdShown, handleCloseModal, personalSubscription } =
|
||||
useSubscriptionDashboardContext()
|
||||
|
||||
async function confirmCancelPendingPlanChange() {
|
||||
setError(false)
|
||||
setInflight(true)
|
||||
|
||||
try {
|
||||
await postJSON(cancelPendingSubscriptionChangeUrl)
|
||||
} catch (e) {
|
||||
setError(true)
|
||||
setInflight(false)
|
||||
}
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
if (modalIdShown !== modalId || !personalSubscription) return null
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
id={modalId}
|
||||
show
|
||||
animation
|
||||
onHide={handleCloseModal}
|
||||
backdrop="static"
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('change_plan')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Body>
|
||||
{error && (
|
||||
<div className="alert alert-warning">
|
||||
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
|
||||
{t('generic_if_problem_continues_contact_us')}.
|
||||
</div>
|
||||
)}
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="sure_you_want_to_cancel_plan_change"
|
||||
values={{
|
||||
planName: personalSubscription.plan.name,
|
||||
}}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<button
|
||||
disabled={inflight}
|
||||
className="btn btn-secondary"
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
<button
|
||||
disabled={inflight}
|
||||
className="btn btn-primary"
|
||||
onClick={confirmCancelPendingPlanChange}
|
||||
>
|
||||
{!inflight
|
||||
? t('revert_pending_plan_change')
|
||||
: t('processing_uppercase') + '…'}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
)
|
||||
}
|
|
@ -19,15 +19,23 @@ import { loadDisplayPriceWithTaxPromise } from '../util/recurly-pricing'
|
|||
import { isRecurlyLoaded } from '../util/is-recurly-loaded'
|
||||
|
||||
type SubscriptionDashboardContextValue = {
|
||||
handleCloseModal: () => void
|
||||
handleOpenModal: (modalIdToOpen: string, planCode?: string) => void
|
||||
hasDisplayedSubscription: boolean
|
||||
institutionMemberships?: Institution[]
|
||||
managedGroupSubscriptions: ManagedGroupSubscription[]
|
||||
managedInstitutions: ManagedInstitution[]
|
||||
updateManagedInstitution: (institution: ManagedInstitution) => void
|
||||
modalIdShown?: string
|
||||
personalSubscription?: Subscription
|
||||
plans: Plan[]
|
||||
planCodeToChangeTo?: string
|
||||
queryingIndividualPlansData: boolean
|
||||
recurlyLoadError: boolean
|
||||
setModalIdShown: React.Dispatch<React.SetStateAction<string | undefined>>
|
||||
setPlanCodeToChangeTo: React.Dispatch<
|
||||
React.SetStateAction<string | undefined>
|
||||
>
|
||||
setRecurlyLoadError: React.Dispatch<React.SetStateAction<boolean>>
|
||||
showCancellation: boolean
|
||||
setShowCancellation: React.Dispatch<React.SetStateAction<boolean>>
|
||||
|
@ -44,12 +52,16 @@ export function SubscriptionDashboardProvider({
|
|||
}: {
|
||||
children: ReactNode
|
||||
}) {
|
||||
const [modalIdShown, setModalIdShown] = useState<string | undefined>()
|
||||
const [recurlyLoadError, setRecurlyLoadError] = useState(false)
|
||||
const [showCancellation, setShowCancellation] = useState(false)
|
||||
const [showChangePersonalPlan, setShowChangePersonalPlan] = useState(false)
|
||||
const [plans, setPlans] = useState([])
|
||||
const [queryingIndividualPlansData, setQueryingIndividualPlansData] =
|
||||
useState(true)
|
||||
const [planCodeToChangeTo, setPlanCodeToChangeTo] = useState<
|
||||
string | undefined
|
||||
>()
|
||||
|
||||
const plansWithoutDisplayPrice = getMeta('ol-plans')
|
||||
const institutionMemberships = getMeta('ol-currentInstitutionsWithLicence')
|
||||
|
@ -111,18 +123,36 @@ export function SubscriptionDashboardProvider({
|
|||
},
|
||||
[]
|
||||
)
|
||||
const handleCloseModal = useCallback(() => {
|
||||
setModalIdShown('')
|
||||
setPlanCodeToChangeTo(undefined)
|
||||
}, [setModalIdShown, setPlanCodeToChangeTo])
|
||||
|
||||
const handleOpenModal = useCallback(
|
||||
(id, planCode) => {
|
||||
setModalIdShown(id)
|
||||
setPlanCodeToChangeTo(planCode)
|
||||
},
|
||||
[setModalIdShown, setPlanCodeToChangeTo]
|
||||
)
|
||||
|
||||
const value = useMemo<SubscriptionDashboardContextValue>(
|
||||
() => ({
|
||||
handleCloseModal,
|
||||
handleOpenModal,
|
||||
hasDisplayedSubscription,
|
||||
institutionMemberships,
|
||||
managedGroupSubscriptions,
|
||||
managedInstitutions,
|
||||
updateManagedInstitution,
|
||||
modalIdShown,
|
||||
personalSubscription,
|
||||
plans,
|
||||
planCodeToChangeTo,
|
||||
queryingIndividualPlansData,
|
||||
recurlyLoadError,
|
||||
setModalIdShown,
|
||||
setPlanCodeToChangeTo,
|
||||
setRecurlyLoadError,
|
||||
showCancellation,
|
||||
setShowCancellation,
|
||||
|
@ -130,15 +160,21 @@ export function SubscriptionDashboardProvider({
|
|||
setShowChangePersonalPlan,
|
||||
}),
|
||||
[
|
||||
handleCloseModal,
|
||||
handleOpenModal,
|
||||
hasDisplayedSubscription,
|
||||
institutionMemberships,
|
||||
managedGroupSubscriptions,
|
||||
managedInstitutions,
|
||||
updateManagedInstitution,
|
||||
modalIdShown,
|
||||
personalSubscription,
|
||||
plans,
|
||||
planCodeToChangeTo,
|
||||
queryingIndividualPlansData,
|
||||
recurlyLoadError,
|
||||
setModalIdShown,
|
||||
setPlanCodeToChangeTo,
|
||||
setRecurlyLoadError,
|
||||
showCancellation,
|
||||
setShowCancellation,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const subscriptionUrl = '/user/subscription/update'
|
||||
export const cancelPendingSubscriptionChangeUrl =
|
||||
'/user/subscription/cancel-pending'
|
|
@ -1117,6 +1117,7 @@
|
|||
"proceed_to_paypal": "Proceed to PayPal",
|
||||
"proceeding_to_paypal_takes_you_to_the_paypal_site_to_pay": "Proceeding to PayPal will take you to the PayPal site to pay for your subscription.",
|
||||
"processing": "processing",
|
||||
"processing_uppercase": "Processing",
|
||||
"processing_your_request": "Please wait while we process your request.",
|
||||
"professional": "Professional",
|
||||
"project": "project",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import * as eventTracking from '../../../../../../../../frontend/js/infrastructure/event-tracking'
|
||||
import { ActiveSubscription } from '../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
|
||||
import { Subscription } from '../../../../../../../../types/subscription/dashboard/subscription'
|
||||
import {
|
||||
annualActiveSubscription,
|
||||
|
@ -11,11 +10,8 @@ import {
|
|||
trialSubscription,
|
||||
} from '../../../../fixtures/subscriptions'
|
||||
import sinon from 'sinon'
|
||||
import { plans } from '../../../../fixtures/plans'
|
||||
import {
|
||||
cleanUpContext,
|
||||
renderWithSubscriptionDashContext,
|
||||
} from '../../../../helpers/render-with-subscription-dash-context'
|
||||
import { cleanUpContext } from '../../../../helpers/render-with-subscription-dash-context'
|
||||
import { renderActiveSubscription } from '../../../../helpers/render-active-subscription'
|
||||
|
||||
describe('<ActiveSubscription />', function () {
|
||||
let sendMBSpy: sinon.SinonSpy
|
||||
|
@ -61,19 +57,6 @@ describe('<ActiveSubscription />', function () {
|
|||
screen.getByRole('link', { name: 'View Your Invoices' })
|
||||
}
|
||||
|
||||
function renderActiveSubscription(subscription: Subscription) {
|
||||
const renderOptions = {
|
||||
metaTags: [
|
||||
{ name: 'ol-plans', value: plans },
|
||||
{ name: 'ol-subscription', value: subscription },
|
||||
],
|
||||
}
|
||||
renderWithSubscriptionDashContext(
|
||||
<ActiveSubscription subscription={subscription} />,
|
||||
renderOptions
|
||||
)
|
||||
}
|
||||
|
||||
it('renders the dash annual active subscription', function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
expectedInActiveSubscription(annualActiveSubscription)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import { ChangePlan } from '../../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan'
|
||||
import { plans } from '../../../../../fixtures/plans'
|
||||
import {
|
||||
|
@ -11,40 +11,45 @@ import {
|
|||
cleanUpContext,
|
||||
renderWithSubscriptionDashContext,
|
||||
} from '../../../../../helpers/render-with-subscription-dash-context'
|
||||
import sinon from 'sinon'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import {
|
||||
cancelPendingSubscriptionChangeUrl,
|
||||
subscriptionUrl,
|
||||
} from '../../../../../../../../../frontend/js/features/subscription/data/subscription-url'
|
||||
import { renderActiveSubscription } from '../../../../../helpers/render-active-subscription'
|
||||
|
||||
describe('<ChangePlan />', function () {
|
||||
let reloadStub: () => void
|
||||
const originalLocation = window.location
|
||||
const plansMetaTag = { name: 'ol-plans', value: plans }
|
||||
const renderOptions = { metaTags: [plansMetaTag] }
|
||||
|
||||
beforeEach(function () {
|
||||
reloadStub = sinon.stub()
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { reload: reloadStub },
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
cleanUpContext()
|
||||
fetchMock.reset()
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: originalLocation,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not render the UI when showChangePersonalPlan is false', function () {
|
||||
window.metaAttributesCache.delete('ol-plans')
|
||||
const { container } = renderWithSubscriptionDashContext(
|
||||
<ChangePlan />,
|
||||
renderOptions
|
||||
)
|
||||
const { container } = renderWithSubscriptionDashContext(<ChangePlan />, {
|
||||
metaTags: [plansMetaTag],
|
||||
})
|
||||
|
||||
expect(container.firstChild).to.be.null
|
||||
})
|
||||
|
||||
it('renders the individual plans table and group plans UI', async function () {
|
||||
renderWithSubscriptionDashContext(
|
||||
<ActiveSubscription subscription={annualActiveSubscription} />,
|
||||
{
|
||||
metaTags: [
|
||||
{ name: 'ol-subscription', value: annualActiveSubscription },
|
||||
plansMetaTag,
|
||||
{
|
||||
name: 'ol-recommendedCurrency',
|
||||
value: 'USD',
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
|
@ -68,15 +73,7 @@ describe('<ChangePlan />', function () {
|
|||
})
|
||||
|
||||
it('renders "Your new plan" and "Keep current plan" when there is a pending plan change', async function () {
|
||||
renderWithSubscriptionDashContext(
|
||||
<ActiveSubscription subscription={pendingSubscriptionChange} />,
|
||||
{
|
||||
metaTags: [
|
||||
{ name: 'ol-subscription', value: pendingSubscriptionChange },
|
||||
plansMetaTag,
|
||||
],
|
||||
}
|
||||
)
|
||||
renderActiveSubscription(pendingSubscriptionChange)
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
@ -98,13 +95,12 @@ describe('<ChangePlan />', function () {
|
|||
expect(container).not.to.be.null
|
||||
})
|
||||
|
||||
it('shows a loading message while still querying Recurly for prices', function () {
|
||||
it('shows a loading message while still querying Recurly for prices', async function () {
|
||||
renderWithSubscriptionDashContext(
|
||||
<ActiveSubscription subscription={pendingSubscriptionChange} />,
|
||||
{
|
||||
metaTags: [
|
||||
{ name: 'ol-subscription', value: pendingSubscriptionChange },
|
||||
plansMetaTag,
|
||||
],
|
||||
}
|
||||
)
|
||||
|
@ -112,6 +108,218 @@ describe('<ChangePlan />', function () {
|
|||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
screen.findByText('Loading', { exact: false })
|
||||
await screen.findByText('Loading', { exact: false })
|
||||
})
|
||||
|
||||
describe('Change plan modal', function () {
|
||||
it('open confirmation modal when "Change to this plan" clicked', async function () {
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
const buttons = await screen.findAllByRole('button', {
|
||||
name: 'Change to this plan',
|
||||
})
|
||||
fireEvent.click(buttons[0])
|
||||
|
||||
await screen.findByText('Are you sure you want to change plan to', {
|
||||
exact: false,
|
||||
})
|
||||
screen.getByRole('button', { name: 'Change plan' })
|
||||
|
||||
expect(
|
||||
screen.queryByText(
|
||||
'Your existing plan and its features will remain active until the end of the current billing period.'
|
||||
)
|
||||
).to.be.null
|
||||
|
||||
expect(
|
||||
screen.queryByText(
|
||||
'If you wish this change to apply before the end of your current billing period, please contact us.'
|
||||
)
|
||||
).to.be.null
|
||||
})
|
||||
|
||||
it('shows message in confirmation dialog about plan remaining active until end of term when expected', async function () {
|
||||
let planIndex = 0
|
||||
const planThatWillChange = plans.find((p, i) => {
|
||||
if (p.planCode !== annualActiveSubscription.planCode) {
|
||||
planIndex = i
|
||||
}
|
||||
return p.planCode !== annualActiveSubscription.planCode
|
||||
})
|
||||
|
||||
renderActiveSubscription(annualActiveSubscription, [
|
||||
{
|
||||
name: 'ol-planCodesChangingAtTermEnd',
|
||||
value: [planThatWillChange!.planCode],
|
||||
},
|
||||
])
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
const buttons = await screen.findAllByRole('button', {
|
||||
name: 'Change to this plan',
|
||||
})
|
||||
fireEvent.click(buttons[planIndex])
|
||||
|
||||
const confirmModal = screen.getByRole('dialog')
|
||||
await within(confirmModal).findByText(
|
||||
'Your existing plan and its features will remain active until the end of the current billing period.'
|
||||
)
|
||||
|
||||
screen.getByText(
|
||||
'If you wish this change to apply before the end of your current billing period, please contact us.'
|
||||
)
|
||||
})
|
||||
|
||||
it('changes plan after confirmed in modal', async function () {
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
}
|
||||
fetchMock.post(
|
||||
`${subscriptionUrl}?origin=confirmChangePlan`,
|
||||
endPointResponse
|
||||
)
|
||||
|
||||
renderActiveSubscription(annualActiveSubscription, [
|
||||
{
|
||||
name: 'ol-planCodesChangingAtTermEnd',
|
||||
value: [annualActiveSubscription.planCode],
|
||||
},
|
||||
])
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
const buttons = await screen.findAllByRole('button', {
|
||||
name: 'Change to this plan',
|
||||
})
|
||||
fireEvent.click(buttons[0])
|
||||
|
||||
await screen.findByText('Are you sure you want to change plan to', {
|
||||
exact: false,
|
||||
})
|
||||
const buttonConfirm = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(buttonConfirm)
|
||||
|
||||
screen.getByText('processing', { exact: false })
|
||||
|
||||
// page is reloaded on success
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
it('shows error if changing plan failed', async function () {
|
||||
const endPointResponse = {
|
||||
status: 500,
|
||||
}
|
||||
fetchMock.post(
|
||||
`${subscriptionUrl}?origin=confirmChangePlan`,
|
||||
endPointResponse
|
||||
)
|
||||
|
||||
renderActiveSubscription(annualActiveSubscription)
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
const buttons = await screen.findAllByRole('button', {
|
||||
name: 'Change to this plan',
|
||||
})
|
||||
fireEvent.click(buttons[0])
|
||||
|
||||
await screen.findByText('Are you sure you want to change plan to', {
|
||||
exact: false,
|
||||
})
|
||||
const buttonConfirm = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(buttonConfirm)
|
||||
|
||||
screen.getByText('processing', { exact: false })
|
||||
|
||||
await screen.findByText('Sorry, something went wrong. ', { exact: false })
|
||||
await screen.findByText('Please try again. ', { exact: false })
|
||||
await screen.findByText('If the problem continues please contact us.', {
|
||||
exact: false,
|
||||
})
|
||||
|
||||
expect(
|
||||
within(screen.getByRole('dialog'))
|
||||
.getByRole('button', { name: 'Change plan' })
|
||||
.getAttribute('disabled')
|
||||
).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
describe('Keep current plan modal', function () {
|
||||
let confirmModal: HTMLElement
|
||||
|
||||
beforeEach(async function () {
|
||||
renderActiveSubscription(pendingSubscriptionChange)
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Change plan' })
|
||||
fireEvent.click(button)
|
||||
|
||||
const keepPlanButton = await screen.findByRole('button', {
|
||||
name: 'Keep my current plan',
|
||||
})
|
||||
fireEvent.click(keepPlanButton)
|
||||
|
||||
confirmModal = screen.getByRole('dialog')
|
||||
})
|
||||
|
||||
it('opens confirmation modal when "Keep my current plan" is clicked', async function () {
|
||||
within(confirmModal).getByText(
|
||||
'Are you sure you want to revert your scheduled plan change? You will remain subscribed to the',
|
||||
{
|
||||
exact: false,
|
||||
}
|
||||
)
|
||||
screen.getByRole('button', { name: 'Revert scheduled plan change' })
|
||||
})
|
||||
|
||||
it('keeps current plan when "Revert scheduled plan change" is clicked in modal', async function () {
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
}
|
||||
fetchMock.post(cancelPendingSubscriptionChangeUrl, endPointResponse)
|
||||
const buttonConfirm = within(confirmModal).getByRole('button', {
|
||||
name: 'Revert scheduled plan change',
|
||||
})
|
||||
fireEvent.click(buttonConfirm)
|
||||
|
||||
screen.getByText('processing', { exact: false })
|
||||
|
||||
// page is reloaded on success
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
it('shows error if keeping plan failed', async function () {
|
||||
const endPointResponse = {
|
||||
status: 500,
|
||||
}
|
||||
fetchMock.post(cancelPendingSubscriptionChangeUrl, endPointResponse)
|
||||
const buttonConfirm = within(confirmModal).getByRole('button', {
|
||||
name: 'Revert scheduled plan change',
|
||||
})
|
||||
fireEvent.click(buttonConfirm)
|
||||
|
||||
screen.getByText('processing', { exact: false })
|
||||
await screen.findByText('Sorry, something went wrong. ', { exact: false })
|
||||
await screen.findByText('Please try again. ', { exact: false })
|
||||
await screen.findByText('If the problem continues please contact us.', {
|
||||
exact: false,
|
||||
})
|
||||
expect(
|
||||
within(screen.getByRole('dialog'))
|
||||
.getByRole('button', { name: 'Revert scheduled plan change' })
|
||||
.getAttribute('disabled')
|
||||
).to.not.exist
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { ActiveSubscription } from '../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
|
||||
import { Subscription } from '../../../../../types/subscription/dashboard/subscription'
|
||||
import { plans } from '../fixtures/plans'
|
||||
import { renderWithSubscriptionDashContext } from './render-with-subscription-dash-context'
|
||||
|
||||
export function renderActiveSubscription(
|
||||
subscription: Subscription,
|
||||
tags: { name: string; value: string | object | Array<object> }[] = []
|
||||
) {
|
||||
const renderOptions = {
|
||||
metaTags: [
|
||||
...tags,
|
||||
{ name: 'ol-plans', value: plans },
|
||||
{ name: 'ol-subscription', value: subscription },
|
||||
{
|
||||
name: 'ol-recommendedCurrency',
|
||||
value: 'USD',
|
||||
},
|
||||
],
|
||||
}
|
||||
renderWithSubscriptionDashContext(
|
||||
<ActiveSubscription subscription={subscription} />,
|
||||
renderOptions
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue