Răsfoiți Sursa

Merge pull request #11560 from overleaf/jel-react-personal-subscription-dash-pt-5

[web] Continue migration of personal subscription dash to React

GitOrigin-RevId: fb327592ad5031bff20b2a95a83edee4735112e8
Jessica Lawshe 3 ani în urmă
părinte
comite
c7d8e4867d

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -425,6 +425,7 @@
   "log_viewer_error": "",
   "login_with_service": "",
   "logs_and_output_files": "",
+  "looking_multiple_licenses": "",
   "looks_like_youre_at": "",
   "main_document": "",
   "main_file_not_found": "",

+ 1 - 8
services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx

@@ -1,4 +1,3 @@
-import { useEffect } from 'react'
 import { useTranslation } from 'react-i18next'
 import { Subscription } from '../../../../../../types/subscription/dashboard/subscription'
 import { ActiveSubscription } from './states/active/active'
@@ -49,15 +48,9 @@ function PersonalSubscriptionStates({
 
 function PersonalSubscription() {
   const { t } = useTranslation()
-  const { personalSubscription, recurlyLoadError, setRecurlyLoadError } =
+  const { personalSubscription, recurlyLoadError } =
     useSubscriptionDashboardContext()
 
-  useEffect(() => {
-    if (typeof window.recurly === 'undefined' || !window.recurly) {
-      setRecurlyLoadError(true)
-    }
-  })
-
   if (!personalSubscription) return null
 
   return (

+ 12 - 6
services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx

@@ -7,7 +7,7 @@ import { CancelSubscriptionButton } from './cancel-subscription-button'
 import { CancelSubscription } from './cancel-subscription'
 import { PendingPlanChange } from './pending-plan-change'
 import { TrialEnding } from './trial-ending'
-import { ChangePlan } from './change-plan'
+import { ChangePlan } from './change-plan/change-plan'
 import { PendingAdditionalLicenses } from './pending-additional-licenses'
 import { ContactSupportToChangeGroupPlan } from './contact-support-to-change-group-plan'
 
@@ -36,14 +36,20 @@ export function ActiveSubscription({
           ]}
         />
         {subscription.pendingPlan && (
-          <PendingPlanChange subscription={subscription} />
+          <>
+            {' '}
+            <PendingPlanChange subscription={subscription} />
+          </>
         )}
         {!subscription.pendingPlan &&
           subscription.recurly.additionalLicenses > 0 && (
-            <PendingAdditionalLicenses
-              additionalLicenses={subscription.recurly.additionalLicenses}
-              totalLicenses={subscription.recurly.totalLicenses}
-            />
+            <>
+              {' '}
+              <PendingAdditionalLicenses
+                additionalLicenses={subscription.recurly.additionalLicenses}
+                totalLicenses={subscription.recurly.totalLicenses}
+              />
+            </>
           )}
         {!recurlyLoadError &&
           !subscription.groupPlan &&

+ 20 - 0
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan.tsx

@@ -0,0 +1,20 @@
+import { useTranslation } from 'react-i18next'
+import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
+import { ChangeToGroupPlan } from './change-to-group-plan'
+import { IndividualPlansTable } from './individual-plans-table'
+
+export function ChangePlan() {
+  const { t } = useTranslation()
+  const { plans, recurlyLoadError, showChangePersonalPlan } =
+    useSubscriptionDashboardContext()
+
+  if (!showChangePersonalPlan || !plans || recurlyLoadError) return null
+
+  return (
+    <>
+      <h2>{t('change_plan')}</h2>
+      <IndividualPlansTable plans={plans} />
+      <ChangeToGroupPlan />
+    </>
+  )
+}

+ 11 - 0
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx

@@ -0,0 +1,11 @@
+import { useTranslation } from 'react-i18next'
+
+export function ChangeToGroupPlan() {
+  const { t } = useTranslation()
+  return (
+    <>
+      <h2>{t('looking_multiple_licenses')}</h2>
+      {/* todo: if/else isValidCurrencyForUpgrade and modal */}
+    </>
+  )
+}

+ 22 - 25
services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan.tsx → services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx

@@ -1,6 +1,7 @@
 import { useTranslation } from 'react-i18next'
-import { Plan } from '../../../../../../../../types/subscription/plan'
-import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
+import { Plan } from '../../../../../../../../../types/subscription/plan'
+import Icon from '../../../../../../../shared/components/icon'
+import { useSubscriptionDashboardContext } from '../../../../../context/subscription-dashboard-context'
 
 function ChangeToPlanButton({ plan }: { plan: Plan }) {
   const { t } = useTranslation()
@@ -47,15 +48,17 @@ function ChangePlanButton({ plan }: { plan: Plan }) {
     return <KeepCurrentPlanButton plan={plan} />
   } else if (isCurrentPlanForUser && !personalSubscription.pendingPlan) {
     return (
-      <button className="btn btn-secondary disabled">{t('your_plan')}</button>
+      <b>
+        <Icon type="check" /> {t('your_plan')}
+      </b>
     )
   } else if (
     personalSubscription?.pendingPlan?.planCode?.split('_')[0] === plan.planCode
   ) {
     return (
-      <button className="btn btn-secondary disabled">
-        {t('your_new_plan')}
-      </button>
+      <b>
+        <Icon type="check" /> {t('your_new_plan')}
+      </b>
     )
   } else {
     return <ChangeToPlanButton plan={plan} />
@@ -91,27 +94,21 @@ function PlansRows({ plans }: { plans: Array<Plan> }) {
   )
 }
 
-export function ChangePlan() {
+export function IndividualPlansTable({ plans }: { plans: Array<Plan> }) {
   const { t } = useTranslation()
-  const { plans, showChangePersonalPlan } = useSubscriptionDashboardContext()
-
-  if (!showChangePersonalPlan || !plans) return null
 
   return (
-    <>
-      <h2>{t('change_plan')}</h2>
-      <table className="table">
-        <thead>
-          <tr>
-            <th>{t('name')}</th>
-            <th>{t('price')}</th>
-            <th />
-          </tr>
-        </thead>
-        <tbody>
-          <PlansRows plans={plans} />
-        </tbody>
-      </table>
-    </>
+    <table className="table">
+      <thead>
+        <tr>
+          <th>{t('name')}</th>
+          <th>{t('price')}</th>
+          <th />
+        </tr>
+      </thead>
+      <tbody>
+        <PlansRows plans={plans} />
+      </tbody>
+    </table>
   )
 }

+ 13 - 16
services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-additional-licenses.tsx

@@ -8,21 +8,18 @@ export function PendingAdditionalLicenses({
   totalLicenses: number
 }) {
   return (
-    <>
-      {' '}
-      <Trans
-        i18nKey="additional_licenses"
-        values={{
-          additionalLicenses,
-          totalLicenses,
-        }}
-        components={[
-          // eslint-disable-next-line react/jsx-key
-          <strong />,
-          // eslint-disable-next-line react/jsx-key
-          <strong />,
-        ]}
-      />
-    </>
+    <Trans
+      i18nKey="additional_licenses"
+      values={{
+        additionalLicenses,
+        totalLicenses,
+      }}
+      components={[
+        // eslint-disable-next-line react/jsx-key
+        <strong />,
+        // eslint-disable-next-line react/jsx-key
+        <strong />,
+      ]}
+    />
   )
 }

+ 14 - 1
services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx

@@ -1,4 +1,11 @@
-import { createContext, ReactNode, useContext, useMemo, useState } from 'react'
+import {
+  createContext,
+  ReactNode,
+  useContext,
+  useEffect,
+  useMemo,
+  useState,
+} from 'react'
 import {
   ManagedGroupSubscription,
   Subscription,
@@ -44,6 +51,12 @@ export function SubscriptionDashboardProvider({
     personalSubscription ||
     managedGroupSubscriptions
 
+  useEffect(() => {
+    if (typeof window.recurly === 'undefined' || !window.recurly) {
+      setRecurlyLoadError(true)
+    }
+  }, [setRecurlyLoadError])
+
   const value = useMemo<SubscriptionDashboardContextValue>(
     () => ({
       hasDisplayedSubscription,

+ 11 - 7
services/web/test/frontend/features/subscription/components/dashboard/states/active.test.tsx → services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx

@@ -1,18 +1,18 @@
 import { expect } from 'chai'
 import { fireEvent, render, 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 { SubscriptionDashboardProvider } from '../../../../../../../frontend/js/features/subscription/context/subscription-dashboard-context'
-import { Subscription } from '../../../../../../../types/subscription/dashboard/subscription'
+import * as eventTracking from '../../../../../../../../frontend/js/infrastructure/event-tracking'
+import { ActiveSubscription } from '../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
+import { SubscriptionDashboardProvider } from '../../../../../../../../frontend/js/features/subscription/context/subscription-dashboard-context'
+import { Subscription } from '../../../../../../../../types/subscription/dashboard/subscription'
 import {
   annualActiveSubscription,
   groupActiveSubscription,
   groupActiveSubscriptionWithPendingLicenseChange,
   pendingSubscriptionChange,
   trialSubscription,
-} from '../../../fixtures/subscriptions'
+} from '../../../../fixtures/subscriptions'
 import sinon from 'sinon'
-import { plans } from '../../../fixtures/plans'
+import { plans } from '../../../../fixtures/plans'
 
 describe('<ActiveSubscription />', function () {
   let sendMBSpy: sinon.SinonSpy
@@ -20,11 +20,15 @@ describe('<ActiveSubscription />', function () {
   beforeEach(function () {
     window.metaAttributesCache = new Map()
     window.metaAttributesCache.set('ol-plans', plans)
+    // @ts-ignore
+    window.recurly = {}
     sendMBSpy = sinon.spy(eventTracking, 'sendMB')
   })
 
   afterEach(function () {
     window.metaAttributesCache = new Map()
+    // @ts-ignore
+    delete window.recurly
     sendMBSpy.restore()
   })
 
@@ -117,7 +121,7 @@ describe('<ActiveSubscription />', function () {
     // account is likely in expired state, but be sure to not show option if state is still active
     const activePastDueSubscription = Object.assign(
       {},
-      annualActiveSubscription
+      JSON.parse(JSON.stringify(annualActiveSubscription))
     )
 
     activePastDueSubscription.recurly.account.has_past_due_invoice._ = 'true'

+ 36 - 7
services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan.test.tsx → services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx

@@ -1,22 +1,26 @@
 import { expect } from 'chai'
 import { fireEvent, render, screen } from '@testing-library/react'
-import { SubscriptionDashboardProvider } from '../../../../../../../../frontend/js/features/subscription/context/subscription-dashboard-context'
-import { ChangePlan } from '../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/change-plan'
-import { plans } from '../../../../fixtures/plans'
+import { SubscriptionDashboardProvider } from '../../../../../../../../../frontend/js/features/subscription/context/subscription-dashboard-context'
+import { ChangePlan } from '../../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan'
+import { plans } from '../../../../../fixtures/plans'
 import {
   annualActiveSubscription,
   pendingSubscriptionChange,
-} from '../../../../fixtures/subscriptions'
-import { ActiveSubscription } from '../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
+} from '../../../../../fixtures/subscriptions'
+import { ActiveSubscription } from '../../../../../../../../../frontend/js/features/subscription/components/dashboard/states/active/active'
 
 describe('<ChangePlan />', function () {
   beforeEach(function () {
     window.metaAttributesCache = new Map()
     window.metaAttributesCache.set('ol-plans', plans)
+    // @ts-ignore
+    window.recurly = {}
   })
 
   afterEach(function () {
     window.metaAttributesCache = new Map()
+    // @ts-ignore
+    delete window.recurly
   })
 
   it('does not render the UI when showChangePersonalPlan is false', function () {
@@ -30,7 +34,7 @@ describe('<ChangePlan />', function () {
     expect(container.firstChild).to.be.null
   })
 
-  it('renders the table of plans', function () {
+  it('renders the individual plans table', function () {
     window.metaAttributesCache.set('ol-subscription', annualActiveSubscription)
     render(
       <SubscriptionDashboardProvider>
@@ -45,7 +49,7 @@ describe('<ChangePlan />', function () {
       name: 'Change to this plan',
     })
     expect(changeToPlanButtons.length).to.equal(plans.length - 1)
-    screen.getByRole('button', { name: 'Your plan' })
+    screen.getByText('Your plan')
 
     const annualPlans = plans.filter(plan => plan.annual)
     expect(screen.getAllByText('/ year').length).to.equal(annualPlans.length)
@@ -54,6 +58,20 @@ describe('<ChangePlan />', function () {
     )
   })
 
+  it('renders the change to group plan UI', function () {
+    window.metaAttributesCache.set('ol-subscription', annualActiveSubscription)
+    render(
+      <SubscriptionDashboardProvider>
+        <ActiveSubscription subscription={annualActiveSubscription} />
+      </SubscriptionDashboardProvider>
+    )
+
+    const button = screen.getByRole('button', { name: 'Change plan' })
+    fireEvent.click(button)
+
+    screen.getByText('Looking for multiple licenses?')
+  })
+
   it('renders "Your new plan" and "Keep current plan" when there is a pending plan change', function () {
     window.metaAttributesCache.set('ol-subscription', pendingSubscriptionChange)
     render(
@@ -68,4 +86,15 @@ describe('<ChangePlan />', function () {
     screen.getByText('Your new plan')
     screen.getByRole('button', { name: 'Keep my current plan' })
   })
+
+  it('does not render when Recurly did not load', function () {
+    // @ts-ignore
+    delete window.recurly
+    const { container } = render(
+      <SubscriptionDashboardProvider>
+        <ActiveSubscription subscription={annualActiveSubscription} />
+      </SubscriptionDashboardProvider>
+    )
+    expect(container).not.to.be.null
+  })
 })

+ 1 - 0
services/web/types/window.ts

@@ -2,6 +2,7 @@ import { ExposedSettings } from './exposed-settings'
 import { OAuthProviders } from './oauth-providers'
 import { OverallThemeMeta } from './project-settings'
 import { User } from './user'
+import 'recurly__recurly-js'
 
 declare global {
   // eslint-disable-next-line no-unused-vars