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
index 2dbda78ae5..0316d4de42 100644
--- 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
@@ -1,20 +1,34 @@
import { useTranslation } from 'react-i18next'
+import LoadingSpinner from '../../../../../../../shared/components/loading-spinner'
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()
+ const {
+ plans,
+ queryingIndividualPlansData,
+ recurlyLoadError,
+ showChangePersonalPlan,
+ } = useSubscriptionDashboardContext()
if (!showChangePersonalPlan || !plans || recurlyLoadError) return null
- return (
- <>
-
{t('change_plan')}
-
-
- >
- )
+ if (queryingIndividualPlansData) {
+ return (
+ <>
+ {t('change_plan')}
+
+ >
+ )
+ } else {
+ return (
+ <>
+ {t('change_plan')}
+
+
+ >
+ )
+ }
}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx
index 68ff59beeb..dac9f40f6b 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/individual-plans-table.tsx
@@ -74,7 +74,7 @@ function PlansRow({ plan }: { plan: Plan }) {
{plan.name}
- {/* todo: {{ displayPrice }} */}/ {plan.annual ? t('year') : t('month')}
+ {plan.displayPrice} / {plan.annual ? t('year') : t('month')}
|
@@ -96,9 +96,13 @@ function PlansRows({ plans }: { plans: Array }) {
export function IndividualPlansTable({ plans }: { plans: Array }) {
const { t } = useTranslation()
+ const { recurlyLoadError, showChangePersonalPlan } =
+ useSubscriptionDashboardContext()
+
+ if (!showChangePersonalPlan || !plans || recurlyLoadError) return null
return (
-
+
| {t('name')} |
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 297950c665..8611de86a5 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
@@ -13,6 +13,8 @@ import {
import { Plan } from '../../../../../types/subscription/plan'
import { Institution } from '../../../../../types/institution'
import getMeta from '../../../utils/meta'
+import { loadDisplayPriceWithTaxPromise } from '../util/recurly-pricing'
+import { isRecurlyLoaded } from '../util/is-recurly-loaded'
type SubscriptionDashboardContextValue = {
hasDisplayedSubscription: boolean
@@ -20,6 +22,7 @@ type SubscriptionDashboardContextValue = {
managedGroupSubscriptions: Array
personalSubscription?: Subscription
plans: Array
+ queryingIndividualPlansData: boolean
recurlyLoadError: boolean
setRecurlyLoadError: React.Dispatch>
showCancellation: boolean
@@ -40,11 +43,15 @@ export function SubscriptionDashboardProvider({
const [recurlyLoadError, setRecurlyLoadError] = useState(false)
const [showCancellation, setShowCancellation] = useState(false)
const [showChangePersonalPlan, setShowChangePersonalPlan] = useState(false)
+ const [plans, setPlans] = useState([])
+ const [queryingIndividualPlansData, setQueryingIndividualPlansData] =
+ useState(true)
- const plans = getMeta('ol-plans')
+ const plansWithoutDisplayPrice = getMeta('ol-plans')
const institutionMemberships = getMeta('ol-currentInstitutionsWithLicence')
const personalSubscription = getMeta('ol-subscription')
const managedGroupSubscriptions = getMeta('ol-managedGroupSubscriptions')
+ const recurlyApiKey = getMeta('ol-recurlyApiKey')
const hasDisplayedSubscription =
institutionMemberships?.length > 0 ||
@@ -52,10 +59,37 @@ export function SubscriptionDashboardProvider({
managedGroupSubscriptions
useEffect(() => {
- if (typeof window.recurly === 'undefined' || !window.recurly) {
+ if (!isRecurlyLoaded()) {
setRecurlyLoadError(true)
+ } else {
+ recurly.configure(recurlyApiKey)
}
- }, [setRecurlyLoadError])
+ }, [recurlyApiKey, setRecurlyLoadError])
+
+ useEffect(() => {
+ if (isRecurlyLoaded() && plansWithoutDisplayPrice && personalSubscription) {
+ const { currency, taxRate } = personalSubscription.recurly
+ const fetchPlansDisplayPrices = async () => {
+ for (const plan of plansWithoutDisplayPrice) {
+ try {
+ const priceData = await loadDisplayPriceWithTaxPromise(
+ plan.planCode,
+ currency,
+ taxRate
+ )
+ if (priceData?.totalForDisplay) {
+ plan.displayPrice = priceData.totalForDisplay
+ }
+ } catch (error) {
+ console.error(error)
+ }
+ }
+ setPlans(plansWithoutDisplayPrice)
+ setQueryingIndividualPlansData(false)
+ }
+ fetchPlansDisplayPrices().catch(console.error)
+ }
+ }, [personalSubscription, plansWithoutDisplayPrice])
const value = useMemo(
() => ({
@@ -64,6 +98,7 @@ export function SubscriptionDashboardProvider({
managedGroupSubscriptions,
personalSubscription,
plans,
+ queryingIndividualPlansData,
recurlyLoadError,
setRecurlyLoadError,
showCancellation,
@@ -77,6 +112,7 @@ export function SubscriptionDashboardProvider({
managedGroupSubscriptions,
personalSubscription,
plans,
+ queryingIndividualPlansData,
recurlyLoadError,
setRecurlyLoadError,
showCancellation,
diff --git a/services/web/frontend/js/features/subscription/util/is-recurly-loaded.tsx b/services/web/frontend/js/features/subscription/util/is-recurly-loaded.tsx
new file mode 100644
index 0000000000..500af34d31
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/util/is-recurly-loaded.tsx
@@ -0,0 +1,3 @@
+export function isRecurlyLoaded() {
+ return typeof recurly !== 'undefined'
+}
diff --git a/services/web/frontend/stylesheets/components/tables.less b/services/web/frontend/stylesheets/components/tables.less
index b839468075..d88ba303c8 100755
--- a/services/web/frontend/stylesheets/components/tables.less
+++ b/services/web/frontend/stylesheets/components/tables.less
@@ -62,6 +62,12 @@ th {
word-wrap: break-word;
}
+.table-vertically-centered-cells {
+ > tbody > tr > td {
+ vertical-align: middle;
+ }
+}
+
// Condensed table w/ half padding
.table-condensed {
diff --git a/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
index 20734a4ed7..d46ff04f82 100644
--- a/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
+++ b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx
@@ -79,7 +79,7 @@ describe('', function () {
expectedInActiveSubscription(annualActiveSubscription)
})
- it('shows change plan UI when button clicked', function () {
+ it('shows change plan UI when button clicked', async function () {
renderActiveSubscription(annualActiveSubscription)
const button = screen.getByRole('button', { name: 'Change plan' })
@@ -88,7 +88,7 @@ describe('', function () {
// confirm main dash UI still shown
screen.getByText('You are currently subscribed to the', { exact: false })
- screen.getByRole('heading', { name: 'Change plan' })
+ await screen.findByRole('heading', { name: 'Change plan' })
expect(
screen.getAllByRole('button', { name: 'Change to this plan' }).length > 0
).to.be.true
diff --git a/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx
index f988490e0b..70190af80f 100644
--- a/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx
+++ b/services/web/test/frontend/features/subscription/components/dashboard/states/active/change-plan/change-plan.test.tsx
@@ -30,13 +30,17 @@ describe('', function () {
expect(container.firstChild).to.be.null
})
- it('renders the individual plans table', function () {
+ it('renders the individual plans table and group plans UI', async function () {
renderWithSubscriptionDashContext(
,
{
metaTags: [
{ name: 'ol-subscription', value: annualActiveSubscription },
plansMetaTag,
+ {
+ name: 'ol-recommendedCurrency',
+ value: 'USD',
+ },
],
}
)
@@ -44,6 +48,8 @@ describe('', function () {
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
+ await screen.findByText('Looking for multiple licenses?')
+
const changeToPlanButtons = screen.queryAllByRole('button', {
name: 'Change to this plan',
})
@@ -51,30 +57,17 @@ describe('', function () {
screen.getByText('Your plan')
const annualPlans = plans.filter(plan => plan.annual)
- expect(screen.getAllByText('/ year').length).to.equal(annualPlans.length)
- expect(screen.getAllByText('/ month').length).to.equal(
+ expect(screen.getAllByText('/ year', { exact: false }).length).to.equal(
+ annualPlans.length
+ )
+ expect(screen.getAllByText('/ month', { exact: false }).length).to.equal(
plans.length - annualPlans.length
)
+
+ expect(screen.queryByText('loading', { exact: false })).to.be.null
})
- it('renders the change to group plan UI', function () {
- renderWithSubscriptionDashContext(
- ,
- {
- metaTags: [
- { name: 'ol-subscription', value: annualActiveSubscription },
- plansMetaTag,
- ],
- }
- )
-
- 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 () {
+ it('renders "Your new plan" and "Keep current plan" when there is a pending plan change', async function () {
renderWithSubscriptionDashContext(
,
{
@@ -88,7 +81,7 @@ describe('', function () {
const button = screen.getByRole('button', { name: 'Change plan' })
fireEvent.click(button)
- screen.getByText('Your new plan')
+ await screen.findByText('Your new plan')
screen.getByRole('button', { name: 'Keep my current plan' })
})
@@ -104,4 +97,21 @@ describe('', function () {
)
expect(container).not.to.be.null
})
+
+ it('shows a loading message while still querying Recurly for prices', function () {
+ renderWithSubscriptionDashContext(
+ ,
+ {
+ metaTags: [
+ { name: 'ol-subscription', value: pendingSubscriptionChange },
+ plansMetaTag,
+ ],
+ }
+ )
+
+ const button = screen.getByRole('button', { name: 'Change plan' })
+ fireEvent.click(button)
+
+ screen.findByText('Loading', { exact: false })
+ })
})
diff --git a/services/web/test/frontend/features/subscription/helpers/render-with-subscription-dash-context.tsx b/services/web/test/frontend/features/subscription/helpers/render-with-subscription-dash-context.tsx
index 62a6ea27d7..122c30f89b 100644
--- a/services/web/test/frontend/features/subscription/helpers/render-with-subscription-dash-context.tsx
+++ b/services/web/test/frontend/features/subscription/helpers/render-with-subscription-dash-context.tsx
@@ -1,11 +1,13 @@
import { render } from '@testing-library/react'
import { SubscriptionDashboardProvider } from '../../../../../frontend/js/features/subscription/context/subscription-dashboard-context'
+import { plans } from '../fixtures/plans'
export function renderWithSubscriptionDashContext(
component: React.ReactElement,
options?: {
metaTags?: { name: string; value: string | object | Array |