Bläddra i källkod

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

[web] Submit group plan change on subscription dash

GitOrigin-RevId: 5bc2644351c80189774ad3ac4cdcd622d354770f
Jessica Lawshe 3 år sedan
förälder
incheckning
89ef1681e8

+ 41 - 9
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/change-to-group-modal.tsx

@@ -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}>

+ 4 - 4
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/confirm-change-plan-modal.tsx

@@ -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>

+ 2 - 2
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/keep-current-plan-modal.tsx

@@ -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>

+ 5 - 8
services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx

@@ -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 - 1
services/web/frontend/js/features/subscription/data/subscription-url.ts

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

+ 7 - 0
services/web/frontend/js/features/subscription/util/recurly-group-plan-code.ts

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

+ 2 - 1
services/web/frontend/js/features/subscription/util/recurly-pricing.ts

@@ -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,

+ 46 - 3
services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx

@@ -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,
+      })
+    })
   })
 })