diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json
index 545b756df1..c7d5babc3b 100644
--- a/services/web/frontend/extracted-translations.json
+++ b/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": "",
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx
index 2bd80e849c..a625b2efda 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx
+++ b/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 (
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx
index 04e0903b8c..3cc3c3d0cc 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx
+++ b/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 && (
-
+ <>
+ {' '}
+
+ >
)}
{!subscription.pendingPlan &&
subscription.recurly.additionalLicenses > 0 && (
-
+ <>
+ {' '}
+
+ >
)}
{!recurlyLoadError &&
!subscription.groupPlan &&
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-plan.tsx
new file mode 100644
index 0000000000..2dbda78ae5
--- /dev/null
+++ b/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 (
+ <>
+
{t('change_plan')}
+
+
+ >
+ )
+}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/change-to-group-plan.tsx
new file mode 100644
index 0000000000..098542d022
--- /dev/null
+++ b/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 (
+ <>
+ {t('looking_multiple_licenses')}
+ {/* todo: if/else isValidCurrencyForUpgrade and modal */}
+ >
+ )
+}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx
similarity index 73%
rename from services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan.tsx
rename to services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx
index d39f37f69b..68ff59beeb 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan.tsx
+++ b/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
} else if (isCurrentPlanForUser && !personalSubscription.pendingPlan) {
return (
-
+
+ {t('your_plan')}
+
)
} else if (
personalSubscription?.pendingPlan?.planCode?.split('_')[0] === plan.planCode
) {
return (
-
+
+ {t('your_new_plan')}
+
)
} else {
return
@@ -91,27 +94,21 @@ function PlansRows({ plans }: { plans: Array }) {
)
}
-export function ChangePlan() {
+export function IndividualPlansTable({ plans }: { plans: Array }) {
const { t } = useTranslation()
- const { plans, showChangePersonalPlan } = useSubscriptionDashboardContext()
-
- if (!showChangePersonalPlan || !plans) return null
return (
- <>
- {t('change_plan')}
-
-
-
- | {t('name')} |
- {t('price')} |
- |
-
-
-
-
-
-
- >
+
+
+
+ | {t('name')} |
+ {t('price')} |
+ |
+
+
+
+
+
+
)
}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-additional-licenses.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-additional-licenses.tsx
index 83f5f652f2..ee58117152 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-additional-licenses.tsx
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-additional-licenses.tsx
@@ -8,21 +8,18 @@ export function PendingAdditionalLicenses({
totalLicenses: number
}) {
return (
- <>
- {' '}
- ,
- // eslint-disable-next-line react/jsx-key
- ,
- ]}
- />
- >
+ ,
+ // eslint-disable-next-line react/jsx-key
+ ,
+ ]}
+ />
)
}
diff --git a/services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx b/services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx
index 8ba1a52513..297950c665 100644
--- a/services/web/frontend/js/features/subscription/context/subscription-dashboard-context.tsx
+++ b/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(
() => ({
hasDisplayedSubscription,
diff --git a/services/web/test/frontend/features/subscription/components/dashboard/states/active.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
similarity index 92%
rename from services/web/test/frontend/features/subscription/components/dashboard/states/active.test.tsx
rename to services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
index ad37cf164f..fcb67b99d4 100644
--- a/services/web/test/frontend/features/subscription/components/dashboard/states/active.test.tsx
+++ b/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('', function () {
let sendMBSpy: sinon.SinonSpy
@@ -20,11 +20,15 @@ describe('', 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('', 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'
diff --git a/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx
similarity index 59%
rename from services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan.test.tsx
rename to services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx
index 72eb1ee376..f3acca284d 100644
--- a/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan.test.tsx
+++ b/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('', 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('', 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(
@@ -45,7 +49,7 @@ describe('', 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('', function () {
)
})
+ it('renders the change to group plan UI', function () {
+ window.metaAttributesCache.set('ol-subscription', annualActiveSubscription)
+ render(
+
+
+
+ )
+
+ 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('', 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(
+
+
+
+ )
+ expect(container).not.to.be.null
+ })
})
diff --git a/services/web/types/window.ts b/services/web/types/window.ts
index 9f02651ea4..7706f37c0c 100644
--- a/services/web/types/window.ts
+++ b/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