Merge pull request #11844 from overleaf/jel-subscription-dash-change-to-group-submit

[web] Submit group plan change on subscription dash

GitOrigin-RevId: 5bc2644351c80189774ad3ac4cdcd622d354770f
This commit is contained in:
Jessica Lawshe 2023-02-21 09:24:57 -06:00 committed by Copybot
parent 124306d7ac
commit 89ef1681e8
8 changed files with 108 additions and 28 deletions

View file

@ -1,13 +1,16 @@
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { Modal } from 'react-bootstrap'
import { useTranslation, Trans } from 'react-i18next'
import { GroupPlans } from '../../../../../../../../../../types/subscription/dashboard/group-plans'
import { Subscription } from '../../../../../../../../../../types/subscription/dashboard/subscription'
import { PriceForDisplayData } from '../../../../../../../../../../types/subscription/plan'
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 GenericErrorAlert from '../../../../generic-error-alert'
import { subscriptionUpdateUrl } from '../../../../../../data/subscription-url'
import { getRecurlyGroupPlanCode } from '../../../../../../util/recurly-group-plan-code'
const educationalPercentDiscount = 40
const groupSizeForEducationalDiscount = 10
@ -138,14 +141,34 @@ export function ChangeToGroupModal() {
} = useSubscriptionDashboardContext()
const groupPlans: GroupPlans = getMeta('ol-groupPlans')
const personalSubscription: Subscription = getMeta('ol-subscription')
const [error, setError] = useState(false)
const [inflight, setInflight] = useState(false)
async function upgrade() {
setError(false)
setInflight(true)
try {
await postJSON(subscriptionUpdateUrl, {
body: {
plan_code: getRecurlyGroupPlanCode(
groupPlanToChangeToCode,
groupPlanToChangeToSize,
groupPlanToChangeToUsage
),
},
})
window.location.reload()
} catch (e) {
setError(true)
setInflight(false)
}
}
useEffect(() => {
const defaultPlanOption = personalSubscription.plan.planCode.includes(
'professional'
)
? 'professional'
: 'collaborator'
setGroupPlanToChangeToCode(defaultPlanOption)
if (personalSubscription.plan.planCode.includes('professional')) {
setGroupPlanToChangeToCode('professional')
}
}, [personalSubscription, setGroupPlanToChangeToCode])
function handleGetInTouchButton() {
@ -331,13 +354,22 @@ export function ChangeToGroupModal() {
<strong>{t('new_subscription_will_be_billed_immediately')}</strong>
</p>
<hr className="thin" />
{error && (
<div className="alert alert-danger" aria-live="polite">
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
{t('generic_if_problem_continues_contact_us')}.
</div>
)}
<button
className="btn btn-primary btn-lg"
disabled={
queryingGroupPlanToChangeToPrice || !groupPlanToChangeToPrice
queryingGroupPlanToChangeToPrice ||
!groupPlanToChangeToPrice ||
inflight
}
onClick={upgrade}
>
{t('upgrade_now')}
{!inflight ? t('upgrade_now') : t('processing_uppercase') + '…'}
</button>
<hr className="thin" />
<button className="btn-inline-link" onClick={handleGetInTouchButton}>

View file

@ -6,7 +6,7 @@ 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'
import { subscriptionUpdateUrl } from '../../../../../../data/subscription-url'
export function ConfirmChangePlanModal() {
const modalId: SubscriptionDashModalIds = 'change-to-plan'
@ -22,16 +22,16 @@ export function ConfirmChangePlanModal() {
setInflight(true)
try {
await postJSON(`${subscriptionUrl}?origin=confirmChangePlan`, {
await postJSON(`${subscriptionUpdateUrl}?origin=confirmChangePlan`, {
body: {
plan_code: planCodeToChangeTo,
},
})
window.location.reload()
} catch (e) {
setError(true)
setInflight(false)
}
window.location.reload()
}
if (modalIdShown !== modalId || !planCodeToChangeTo) return null
@ -56,7 +56,7 @@ export function ConfirmChangePlanModal() {
<Modal.Body>
{error && (
<div className="alert alert-warning">
<div className="alert alert-danger" aria-live="polite">
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
{t('generic_if_problem_continues_contact_us')}.
</div>

View file

@ -21,11 +21,11 @@ export function KeepCurrentPlanModal() {
try {
await postJSON(cancelPendingSubscriptionChangeUrl)
window.location.reload()
} catch (e) {
setError(true)
setInflight(false)
}
window.location.reload()
}
if (modalIdShown !== modalId || !personalSubscription) return null
@ -44,7 +44,7 @@ export function KeepCurrentPlanModal() {
<Modal.Body>
{error && (
<div className="alert alert-warning">
<div className="alert alert-danger" aria-live="polite">
{t('generic_something_went_wrong')}. {t('try_again')}.{' '}
{t('generic_if_problem_continues_contact_us')}.
</div>

View file

@ -26,9 +26,9 @@ import { isRecurlyLoaded } from '../util/is-recurly-loaded'
import { SubscriptionDashModalIds } from '../../../../../types/subscription/dashboard/modal-ids'
type SubscriptionDashboardContextValue = {
groupPlanToChangeToCode?: string
groupPlanToChangeToCode: string
groupPlanToChangeToSize: string
groupPlanToChangeToUsage?: string
groupPlanToChangeToUsage: string
groupPlanToChangeToPrice?: PriceForDisplayData
groupPlanToChangeToPriceError?: boolean
handleCloseModal: () => void
@ -48,9 +48,7 @@ type SubscriptionDashboardContextValue = {
queryingGroupPlanToChangeToPrice: boolean
queryingIndividualPlansData: boolean
recurlyLoadError: boolean
setGroupPlanToChangeToCode: React.Dispatch<
React.SetStateAction<string | undefined>
>
setGroupPlanToChangeToCode: React.Dispatch<React.SetStateAction<string>>
setGroupPlanToChangeToSize: React.Dispatch<React.SetStateAction<string>>
setGroupPlanToChangeToUsage: React.Dispatch<React.SetStateAction<string>>
setModalIdShown: React.Dispatch<
@ -88,9 +86,8 @@ export function SubscriptionDashboardProvider({
string | undefined
>()
const [groupPlanToChangeToSize, setGroupPlanToChangeToSize] = useState('10')
const [groupPlanToChangeToCode, setGroupPlanToChangeToCode] = useState<
string | undefined
>()
const [groupPlanToChangeToCode, setGroupPlanToChangeToCode] =
useState('collaborator')
const [groupPlanToChangeToUsage, setGroupPlanToChangeToUsage] =
useState('enterprise')
const [

View file

@ -1,3 +1,3 @@
export const subscriptionUrl = '/user/subscription/update'
export const subscriptionUpdateUrl = '/user/subscription/update'
export const cancelPendingSubscriptionChangeUrl =
'/user/subscription/cancel-pending'

View file

@ -0,0 +1,7 @@
export function getRecurlyGroupPlanCode(
planCode: string,
size: string,
usage: string
) {
return `group_${planCode}_${size}_${usage}`
}

View file

@ -1,6 +1,7 @@
import { SubscriptionPricingState } from '@recurly/recurly-js'
import { PriceForDisplayData } from '../../../../../types/subscription/plan'
import { currencies, CurrencyCode } from '../data/currency'
import { getRecurlyGroupPlanCode } from './recurly-group-plan-code'
function queryRecurlyPlanPrice(planCode: string, currency: CurrencyCode) {
return new Promise(resolve => {
@ -79,7 +80,7 @@ export async function loadGroupDisplayPriceWithTaxPromise(
) {
if (!recurly) return
const planCode = `group_${groupPlanCode}_${size}_${usage}`
const planCode = getRecurlyGroupPlanCode(groupPlanCode, size, usage)
const price = await loadDisplayPriceWithTaxPromise(
planCode,
currencyCode,

View file

@ -18,7 +18,7 @@ import sinon from 'sinon'
import fetchMock from 'fetch-mock'
import {
cancelPendingSubscriptionChangeUrl,
subscriptionUrl,
subscriptionUpdateUrl,
} from '../../../../../../../../../frontend/js/features/subscription/data/subscription-url'
import { renderActiveSubscription } from '../../../../../helpers/render-active-subscription'
@ -183,7 +183,7 @@ describe('<ChangePlan />', function () {
status: 200,
}
fetchMock.post(
`${subscriptionUrl}?origin=confirmChangePlan`,
`${subscriptionUpdateUrl}?origin=confirmChangePlan`,
endPointResponse
)
@ -221,7 +221,7 @@ describe('<ChangePlan />', function () {
status: 500,
}
fetchMock.post(
`${subscriptionUrl}?origin=confirmChangePlan`,
`${subscriptionUpdateUrl}?origin=confirmChangePlan`,
endPointResponse
)
@ -520,5 +520,48 @@ describe('<ChangePlan />', function () {
) as HTMLInputElement
expect(standardPlanRadioInput.checked).to.be.true
})
it('submits the changes and reloads the page', async function () {
const endPointResponse = {
status: 200,
}
fetchMock.post(subscriptionUpdateUrl, endPointResponse)
renderActiveSubscription(annualActiveSubscriptionPro)
await openModal()
const buttonConfirm = screen.getByRole('button', { name: 'Upgrade Now' })
fireEvent.click(buttonConfirm)
screen.getByText('processing', { exact: false })
// // page is reloaded on success
await waitFor(() => {
expect(reloadStub).to.have.been.called
})
})
it('shows message if error after submitting form', async function () {
const endPointResponse = {
status: 500,
}
fetchMock.post(subscriptionUpdateUrl, endPointResponse)
renderActiveSubscription(annualActiveSubscriptionPro)
await openModal()
const buttonConfirm = screen.getByRole('button', { name: 'Upgrade Now' })
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,
})
})
})
})