mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-15 00:55:23 +00:00
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:
parent
124306d7ac
commit
89ef1681e8
8 changed files with 108 additions and 28 deletions
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 [
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export const subscriptionUrl = '/user/subscription/update'
|
||||
export const subscriptionUpdateUrl = '/user/subscription/update'
|
||||
export const cancelPendingSubscriptionChangeUrl =
|
||||
'/user/subscription/cancel-pending'
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export function getRecurlyGroupPlanCode(
|
||||
planCode: string,
|
||||
size: string,
|
||||
usage: string
|
||||
) {
|
||||
return `group_${planCode}_${size}_${usage}`
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue