From d2fb80017466a99e62a2967f0a002ba550385aaa Mon Sep 17 00:00:00 2001 From: Alexandre Bourdin Date: Wed, 22 Feb 2023 13:33:17 +0100 Subject: [PATCH] Merge pull request #11885 from overleaf/ab-personal-sub-custom-react [web] Display a message for custom personal subscriptions in React dash GitOrigin-RevId: c70986ffacfdfdeccd3675385e2e5dba1a2ad61f --- .../views/subscriptions/dashboard-react.pug | 2 +- .../web/frontend/extracted-translations.json | 1 + .../dashboard/personal-subscription.tsx | 25 ++++-- .../dashboard/states/active/active.tsx | 4 +- .../active/cancel-subscription-button.tsx | 4 +- .../states/active/pending-plan-change.tsx | 4 +- .../components/dashboard/states/canceled.tsx | 4 +- .../components/dashboard/states/expired.tsx | 5 +- .../subscription-dashboard-context.tsx | 15 ++-- services/web/locales/en.json | 1 + .../dashboard/personal-subscription.test.tsx | 15 +++- .../dashboard/states/active/active.test.tsx | 4 +- .../subscription/fixtures/subscriptions.tsx | 38 +++++++-- .../helpers/render-active-subscription.tsx | 4 +- .../subscription/dashboard/subscription.ts | 79 +++++++++++-------- 15 files changed, 135 insertions(+), 70 deletions(-) diff --git a/services/web/app/views/subscriptions/dashboard-react.pug b/services/web/app/views/subscriptions/dashboard-react.pug index 04c6368794..7d6a7a3bcf 100644 --- a/services/web/app/views/subscriptions/dashboard-react.pug +++ b/services/web/app/views/subscriptions/dashboard-react.pug @@ -7,6 +7,7 @@ block head-scripts script(type="text/javascript", nonce=scriptNonce, src="https://js.recurly.com/v4/recurly.js") block append meta + meta(name="ol-subscription" data-type="json" content=personalSubscription) meta(name="ol-managedGroupSubscriptions" data-type="json" content=managedGroupSubscriptions) meta(name="ol-memberGroupSubscriptions" data-type="json" content=memberGroupSubscriptions) meta(name="ol-managedInstitutions" data-type="json" content=managedInstitutions) @@ -17,7 +18,6 @@ block append meta meta(name="ol-plans", data-type="json" content=plans) if (personalSubscription && personalSubscription.recurly) meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey) - meta(name="ol-subscription" data-type="json" content=personalSubscription) meta(name="ol-recommendedCurrency" content=personalSubscription.recurly.currency) meta(name="ol-groupPlans" data-type="json" content=groupPlans) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 2d27bcdb04..cfe2c47de2 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -564,6 +564,7 @@ "please_compile_pdf_before_word_count": "", "please_confirm_email": "", "please_confirm_your_email_before_making_it_default": "", + "please_contact_support_to_makes_change_to_your_plan": "", "please_get_in_touch": "", "please_link_before_making_primary": "", "please_reconfirm_institutional_email": "", 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 a625b2efda..d17ad66f15 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,5 +1,5 @@ -import { useTranslation } from 'react-i18next' -import { Subscription } from '../../../../../../types/subscription/dashboard/subscription' +import { Trans, useTranslation } from 'react-i18next' +import { RecurlySubscription } from '../../../../../../types/subscription/dashboard/subscription' import { ActiveSubscription } from './states/active/active' import { CanceledSubscription } from './states/canceled' import { ExpiredSubscription } from './states/expired' @@ -8,7 +8,7 @@ import { useSubscriptionDashboardContext } from '../../context/subscription-dash function PastDueSubscriptionAlert({ subscription, }: { - subscription: Subscription + subscription: RecurlySubscription }) { const { t } = useTranslation() return ( @@ -30,10 +30,10 @@ function PastDueSubscriptionAlert({ function PersonalSubscriptionStates({ subscription, }: { - subscription: Subscription + subscription: RecurlySubscription }) { const { t } = useTranslation() - const state = subscription?.recurly?.state + const state = subscription?.recurly.state if (state === 'active') { return @@ -53,13 +53,26 @@ function PersonalSubscription() { if (!personalSubscription) return null + if (!('recurly' in personalSubscription)) { + return ( +

+ ]} // eslint-disable-line react/jsx-key, jsx-a11y/anchor-has-content + /> +

+ ) + } + return ( <> {personalSubscription.recurly.account.has_past_due_invoice._ === 'true' && ( )} - + {recurlyLoadError && (
{t('payment_provider_unreachable_error')} 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 f44d4fa9f6..1e4683a45d 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 @@ -2,7 +2,7 @@ import { useTranslation, Trans } from 'react-i18next' import PremiumFeaturesLink from '../../premium-features-link' import { PriceExceptions } from '../../../shared/price-exceptions' import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context' -import { Subscription } from '../../../../../../../../types/subscription/dashboard/subscription' +import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription' import { CancelSubscriptionButton } from './cancel-subscription-button' import { CancelSubscription } from './cancel-subscription' import { PendingPlanChange } from './pending-plan-change' @@ -14,7 +14,7 @@ import { ContactSupportToChangeGroupPlan } from './contact-support-to-change-gro export function ActiveSubscription({ subscription, }: { - subscription: Subscription + subscription: RecurlySubscription }) { const { t } = useTranslation() const { recurlyLoadError, setShowChangePersonalPlan, showCancellation } = diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx index bb961f2d31..542402fd4d 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx @@ -1,12 +1,12 @@ import { useTranslation, Trans } from 'react-i18next' import * as eventTracking from '../../../../../../infrastructure/event-tracking' -import { Subscription } from '../../../../../../../../types/subscription/dashboard/subscription' +import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription' import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context' export function CancelSubscriptionButton({ subscription, }: { - subscription: Subscription + subscription: RecurlySubscription }) { const { t } = useTranslation() const { recurlyLoadError, setShowCancellation } = diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx index 00e2a401d0..b5c99fd627 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx @@ -1,10 +1,10 @@ import { Trans } from 'react-i18next' -import { Subscription } from '../../../../../../../../types/subscription/dashboard/subscription' +import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription' export function PendingPlanChange({ subscription, }: { - subscription: Subscription + subscription: RecurlySubscription }) { if (!subscription.pendingPlan) return null diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx index 067656faf1..b920e74a3c 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx @@ -1,11 +1,11 @@ import { useTranslation, Trans } from 'react-i18next' -import { Subscription } from '../../../../../../../types/subscription/dashboard/subscription' +import { RecurlySubscription } from '../../../../../../../types/subscription/dashboard/subscription' import PremiumFeaturesLink from '../premium-features-link' export function CanceledSubscription({ subscription, }: { - subscription: Subscription + subscription: RecurlySubscription }) { const { t } = useTranslation() diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/expired.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/expired.tsx index 86c3e67f81..72a61eb6c0 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/expired.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/expired.tsx @@ -1,12 +1,13 @@ import { useTranslation } from 'react-i18next' -import { Subscription } from '../../../../../../../types/subscription/dashboard/subscription' +import { RecurlySubscription } from '../../../../../../../types/subscription/dashboard/subscription' export function ExpiredSubscription({ subscription, }: { - subscription: Subscription + subscription: RecurlySubscription }) { const { t } = useTranslation() + return ( <>

{t('your_subscription_has_expired')}

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 e57b3272e4..7a7cff615d 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 @@ -8,9 +8,10 @@ import { useState, } from 'react' import { + CustomSubscription, ManagedGroupSubscription, MemberGroupSubscription, - Subscription, + RecurlySubscription, } from '../../../../../types/subscription/dashboard/subscription' import { Plan, @@ -46,7 +47,7 @@ type SubscriptionDashboardContextValue = { managedPublishers: ManagedPublisher[] updateManagedInstitution: (institution: ManagedInstitution) => void modalIdShown?: SubscriptionDashModalIds - personalSubscription?: Subscription + personalSubscription?: RecurlySubscription | CustomSubscription hasSubscription: boolean plans: Plan[] planCodeToChangeTo?: string @@ -111,7 +112,7 @@ export function SubscriptionDashboardProvider({ const institutionMemberships: Institution[] = getMeta( 'ol-currentInstitutionsWithLicence' ) - const personalSubscription: Subscription = getMeta('ol-subscription') + const personalSubscription = getMeta('ol-subscription') const managedGroupSubscriptions: ManagedGroupSubscription[] = getMeta( 'ol-managedGroupSubscriptions' ) @@ -143,7 +144,11 @@ export function SubscriptionDashboardProvider({ }, [recurlyApiKey, setRecurlyLoadError]) useEffect(() => { - if (isRecurlyLoaded() && plansWithoutDisplayPrice && personalSubscription) { + if ( + isRecurlyLoaded() && + plansWithoutDisplayPrice && + personalSubscription?.recurly + ) { const { currency, taxRate } = personalSubscription.recurly const fetchPlansDisplayPrices = async () => { for (const plan of plansWithoutDisplayPrice) { @@ -173,7 +178,7 @@ export function SubscriptionDashboardProvider({ groupPlanToChangeToCode && groupPlanToChangeToSize && groupPlanToChangeToUsage && - personalSubscription + personalSubscription?.recurly ) { setQueryingGroupPlanToChangeToPrice(true) diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 15bac77170..c2630cb01d 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1090,6 +1090,7 @@ "please_compile_pdf_before_word_count": "Please compile your project before performing a word count", "please_confirm_email": "Please confirm your email __emailAddress__ by clicking on the link in the confirmation email ", "please_confirm_your_email_before_making_it_default": "Please confirm your email before making it the primary.", + "please_contact_support_to_makes_change_to_your_plan": "Please <0>contact support to make changes to your plan", "please_enter_email": "Please enter your email address", "please_get_in_touch": "Please get in touch", "please_link_before_making_primary": "Please confirm your email by linking to your institutional account before making it the primary email.", diff --git a/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx index 7fc31a9aad..e888396c05 100644 --- a/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx +++ b/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx @@ -4,6 +4,7 @@ import PersonalSubscription from '../../../../../../frontend/js/features/subscri import { annualActiveSubscription, canceledSubscription, + customSubscription, pastDueExpiredSubscription, } from '../../fixtures/subscriptions' import { @@ -25,6 +26,18 @@ describe('', function () { }) }) + describe('custom subscription', function () { + it('displays contact support message', function () { + renderWithSubscriptionDashContext(, { + metaTags: [{ name: 'ol-subscription', value: customSubscription }], + }) + + screen.getByText('Please', { exact: false }) + screen.getByText('contact support', { exact: false }) + screen.getByText('to make changes to your plan', { exact: false }) + }) + }) + describe('subscription states ', function () { it('renders the active dash', function () { renderWithSubscriptionDashContext(, { @@ -44,7 +57,7 @@ describe('', function () { 'Your subscription has been canceled and will terminate on', { exact: false } ) - screen.getByText(canceledSubscription.recurly.nextPaymentDueAt, { + screen.getByText(canceledSubscription.recurly!.nextPaymentDueAt, { exact: false, }) 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 12697d52f4..4dc4c157b1 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 @@ -1,7 +1,7 @@ import { expect } from 'chai' import { fireEvent, screen } from '@testing-library/react' import * as eventTracking from '../../../../../../../../frontend/js/infrastructure/event-tracking' -import { Subscription } from '../../../../../../../../types/subscription/dashboard/subscription' +import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription' import { annualActiveSubscription, groupActiveSubscription, @@ -26,7 +26,7 @@ describe('', function () { sendMBSpy.restore() }) - function expectedInActiveSubscription(subscription: Subscription) { + function expectedInActiveSubscription(subscription: RecurlySubscription) { // sentence broken up by bolding screen.getByText('You are currently subscribed to the', { exact: false }) screen.getByText(subscription.plan.name, { exact: false }) diff --git a/services/web/test/frontend/features/subscription/fixtures/subscriptions.tsx b/services/web/test/frontend/features/subscription/fixtures/subscriptions.tsx index 0fe2129dda..3d46da083f 100644 --- a/services/web/test/frontend/features/subscription/fixtures/subscriptions.tsx +++ b/services/web/test/frontend/features/subscription/fixtures/subscriptions.tsx @@ -1,6 +1,7 @@ import { + CustomSubscription, GroupSubscription, - Subscription, + RecurlySubscription, } from '../../../../../types/subscription/dashboard/subscription' const dateformat = require('dateformat') const today = new Date() @@ -12,7 +13,7 @@ const sevenDaysFromTodayFormatted = dateformat( 'dS mmmm yyyy' ) -export const annualActiveSubscription: Subscription = { +export const annualActiveSubscription: RecurlySubscription = { manager_ids: ['abc123'], member_ids: [], invited_emails: [], @@ -51,7 +52,7 @@ export const annualActiveSubscription: Subscription = { }, } -export const annualActiveSubscriptionEuro: Subscription = { +export const annualActiveSubscriptionEuro: RecurlySubscription = { manager_ids: ['abc123'], member_ids: [], invited_emails: [], @@ -90,7 +91,7 @@ export const annualActiveSubscriptionEuro: Subscription = { }, } -export const annualActiveSubscriptionPro: Subscription = { +export const annualActiveSubscriptionPro: RecurlySubscription = { manager_ids: ['abc123'], member_ids: [], invited_emails: [], @@ -128,7 +129,7 @@ export const annualActiveSubscriptionPro: Subscription = { }, } -export const pastDueExpiredSubscription: Subscription = { +export const pastDueExpiredSubscription: RecurlySubscription = { manager_ids: ['abc123'], member_ids: [], invited_emails: [], @@ -167,7 +168,7 @@ export const pastDueExpiredSubscription: Subscription = { }, } -export const canceledSubscription: Subscription = { +export const canceledSubscription: RecurlySubscription = { manager_ids: ['abc123'], member_ids: [], invited_emails: [], @@ -206,7 +207,7 @@ export const canceledSubscription: Subscription = { }, } -export const pendingSubscriptionChange: Subscription = { +export const pendingSubscriptionChange: RecurlySubscription = { manager_ids: ['abc123'], member_ids: [], invited_emails: [], @@ -362,7 +363,7 @@ export const groupActiveSubscriptionWithPendingLicenseChange: GroupSubscription }, } -export const trialSubscription: Subscription = { +export const trialSubscription: RecurlySubscription = { manager_ids: ['abc123'], member_ids: [], invited_emails: [], @@ -410,3 +411,24 @@ export const trialSubscription: Subscription = { displayPrice: '$14.00', }, } + +export const customSubscription: CustomSubscription = { + manager_ids: ['abc123'], + member_ids: [], + invited_emails: [], + groupPlan: false, + membersLimit: 0, + _id: 'def456', + admin_id: 'abc123', + teamInvites: [], + planCode: 'collaborator-annual', + recurlySubscription_id: 'ghi789', + plan: { + planCode: 'collaborator-annual', + name: 'Standard (Collaborator) Annual', + price_in_cents: 21900, + annual: true, + featureDescription: [], + }, + customAccount: true, +} diff --git a/services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx b/services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx index 30dac83b56..f790dac2dd 100644 --- a/services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx +++ b/services/web/test/frontend/features/subscription/helpers/render-active-subscription.tsx @@ -1,10 +1,10 @@ import { ActiveSubscription } from '../../../../../frontend/js/features/subscription/components/dashboard/states/active/active' -import { Subscription } from '../../../../../types/subscription/dashboard/subscription' +import { RecurlySubscription } from '../../../../../types/subscription/dashboard/subscription' import { groupPlans, plans } from '../fixtures/plans' import { renderWithSubscriptionDashContext } from './render-with-subscription-dash-context' export function renderActiveSubscription( - subscription: Subscription, + subscription: RecurlySubscription, tags: { name: string; value: string | object | Array }[] = [], currencyCode?: string ) { diff --git a/services/web/types/subscription/dashboard/subscription.ts b/services/web/types/subscription/dashboard/subscription.ts index d10ab0953d..0a2b6e3482 100644 --- a/services/web/types/subscription/dashboard/subscription.ts +++ b/services/web/types/subscription/dashboard/subscription.ts @@ -5,6 +5,40 @@ import { User } from '../../../types/user' type SubscriptionState = 'active' | 'canceled' | 'expired' +type Recurly = { + tax: number + taxRate: number + billingDetailsLink: string + accountManagementLink: string + additionalLicenses: number + totalLicenses: number + nextPaymentDueAt: string + currency: CurrencyCode + state?: SubscriptionState + trialEndsAtFormatted: Nullable + trial_ends_at: Nullable + activeCoupons: any[] // TODO: confirm type in array + account: { + // data via Recurly API + has_canceled_subscription: { + _: 'false' | 'true' + $: { + type: 'boolean' + } + } + has_past_due_invoice: { + _: 'false' | 'true' + $: { + type: 'boolean' + } + } + } + displayPrice: string + currentPlanDisplayPrice?: string + pendingAdditionalLicenses?: number + pendingTotalLicenses?: number +} + export type Subscription = { _id: string admin_id: string @@ -17,44 +51,19 @@ export type Subscription = { planCode: string recurlySubscription_id: string plan: Plan - recurly: { - tax: number - taxRate: number - billingDetailsLink: string - accountManagementLink: string - additionalLicenses: number - totalLicenses: number - nextPaymentDueAt: string - currency: CurrencyCode - state?: SubscriptionState - trialEndsAtFormatted: Nullable - trial_ends_at: Nullable - activeCoupons: any[] // TODO: confirm type in array - account: { - // data via Recurly API - has_canceled_subscription: { - _: 'false' | 'true' - $: { - type: 'boolean' - } - } - has_past_due_invoice: { - _: 'false' | 'true' - $: { - type: 'boolean' - } - } - } - displayPrice: string - currentPlanDisplayPrice?: string - pendingAdditionalLicenses?: number - pendingTotalLicenses?: number - } pendingPlan?: Plan } -export type GroupSubscription = Subscription & { - teamName?: string +export type RecurlySubscription = Subscription & { + recurly: Recurly +} + +export type CustomSubscription = Subscription & { + customAccount: boolean +} + +export type GroupSubscription = RecurlySubscription & { + teamName: string teamNotice?: string }