1
0
Fork 0
mirror of https://github.com/overleaf/overleaf.git synced 2025-04-08 22:10:44 +00:00

Merge pull request 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:
Alexandre Bourdin 2023-02-22 13:33:17 +01:00 committed by Copybot
parent 8b00a496e7
commit d2fb800174
15 changed files with 135 additions and 70 deletions
services/web
app/views/subscriptions
frontend
locales
test/frontend/features/subscription
types/subscription/dashboard

View file

@ -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)

View file

@ -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": "",

View file

@ -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>

View file

@ -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 } =

View file

@ -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 } =

View file

@ -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

View file

@ -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()

View file

@ -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>

View file

@ -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)

View file

@ -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.",

View file

@ -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,
})

View file

@ -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 })

View file

@ -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,
}

View file

@ -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
) {

View file

@ -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
}