mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-08 22:10:44 +00:00
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
This commit is contained in:
parent
8b00a496e7
commit
d2fb800174
15 changed files with 135 additions and 70 deletions
services/web
app/views/subscriptions
frontend
extracted-translations.json
js/features/subscription
locales
test/frontend/features/subscription
components/dashboard
fixtures
helpers
types/subscription/dashboard
|
@ -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)
|
||||
|
||||
|
|
|
@ -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": "",
|
||||
|
|
|
@ -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 <ActiveSubscription subscription={subscription} />
|
||||
|
@ -53,13 +53,26 @@ function PersonalSubscription() {
|
|||
|
||||
if (!personalSubscription) return null
|
||||
|
||||
if (!('recurly' in personalSubscription)) {
|
||||
return (
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="please_contact_support_to_makes_change_to_your_plan"
|
||||
components={[<a href="/contact" />]} // eslint-disable-line react/jsx-key, jsx-a11y/anchor-has-content
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{personalSubscription.recurly.account.has_past_due_invoice._ ===
|
||||
'true' && (
|
||||
<PastDueSubscriptionAlert subscription={personalSubscription} />
|
||||
)}
|
||||
<PersonalSubscriptionStates subscription={personalSubscription} />
|
||||
<PersonalSubscriptionStates
|
||||
subscription={personalSubscription as RecurlySubscription}
|
||||
/>
|
||||
{recurlyLoadError && (
|
||||
<div className="alert alert-warning" role="alert">
|
||||
<strong>{t('payment_provider_unreachable_error')}</strong>
|
||||
|
|
|
@ -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 } =
|
||||
|
|
|
@ -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 } =
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<p>{t('your_subscription_has_expired')}</p>
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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</0> 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.",
|
||||
|
|
|
@ -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('<PersonalSubscription />', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('custom subscription', function () {
|
||||
it('displays contact support message', function () {
|
||||
renderWithSubscriptionDashContext(<PersonalSubscription />, {
|
||||
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(<PersonalSubscription />, {
|
||||
|
@ -44,7 +57,7 @@ describe('<PersonalSubscription />', function () {
|
|||
'Your subscription has been canceled and will terminate on',
|
||||
{ exact: false }
|
||||
)
|
||||
screen.getByText(canceledSubscription.recurly.nextPaymentDueAt, {
|
||||
screen.getByText(canceledSubscription.recurly!.nextPaymentDueAt, {
|
||||
exact: false,
|
||||
})
|
||||
|
||||
|
|
|
@ -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('<ActiveSubscription />', 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 })
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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<object> }[] = [],
|
||||
currencyCode?: string
|
||||
) {
|
||||
|
|
|
@ -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<string>
|
||||
trial_ends_at: Nullable<string>
|
||||
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<string>
|
||||
trial_ends_at: Nullable<string>
|
||||
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
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue