mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #11231 from overleaf/jel-react-subscriptions-dash-commons
[web] Migrate institution memberships dash to React GitOrigin-RevId: 75bb8e7d7338418733a836a8e654fe5b0a9fceff
This commit is contained in:
parent
dddf9b7042
commit
c8ef5e6f65
9 changed files with 279 additions and 1 deletions
|
@ -6,6 +6,7 @@ block entrypointVar
|
|||
block append meta
|
||||
meta(name="ol-managedInstitutions", data-type="json", content=managedInstitutions)
|
||||
meta(name="ol-planCodesChangingAtTermEnd", data-type="json", content=plans.planCodesChangingAtTermEnd)
|
||||
meta(name="ol-currentInstitutionsWithLicence", data-type="json" content=currentInstitutionsWithLicence)
|
||||
if (personalSubscription && personalSubscription.recurly)
|
||||
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
|
||||
meta(name="ol-subscription" data-type="json" content=personalSubscription)
|
||||
|
|
|
@ -252,6 +252,8 @@
|
|||
"generic_linked_file_compile_error": "",
|
||||
"generic_something_went_wrong": "",
|
||||
"get_collaborative_benefits": "",
|
||||
"get_most_subscription_by_checking_features": "",
|
||||
"get_most_subscription_by_checking_premium_features": "",
|
||||
"git": "",
|
||||
"git_bridge_modal_description": "",
|
||||
"github_commit_message_placeholder": "",
|
||||
|
@ -745,12 +747,14 @@
|
|||
"word_count": "",
|
||||
"work_offline": "",
|
||||
"work_with_non_overleaf_users": "",
|
||||
"you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "",
|
||||
"you_can_now_log_in_sso": "",
|
||||
"you_dont_have_any_repositories": "",
|
||||
"your_affiliation_is_confirmed": "",
|
||||
"your_browser_does_not_support_this_feature": "",
|
||||
"your_message_to_collaborators": "",
|
||||
"your_projects": "",
|
||||
"your_subscription": "",
|
||||
"zotero_groups_loading_error": "",
|
||||
"zotero_groups_relink": "",
|
||||
"zotero_integration": "",
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { Trans } from 'react-i18next'
|
||||
import { Institution } from '../../../../../../types/institution'
|
||||
|
||||
type InstitutionMembershipsProps = {
|
||||
memberships?: Array<Institution>
|
||||
}
|
||||
|
||||
function InstitutionMemberships({ memberships }: InstitutionMembershipsProps) {
|
||||
// memberships is undefined when data failed to load. If user has no memberships, then an empty array is returned
|
||||
|
||||
if (!memberships) {
|
||||
return (
|
||||
<div className="alert alert-warning">
|
||||
<p>
|
||||
Sorry, something went wrong. Subscription information related to
|
||||
institutional affiliations may not be displayed. Please try again
|
||||
later.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{memberships.map((institution: Institution) => (
|
||||
<div key={`${institution.id}`}>
|
||||
<Trans
|
||||
i18nKey="you_are_on_x_plan_as_a_confirmed_member_of_institution_y"
|
||||
values={{
|
||||
planName: 'Professional',
|
||||
institutionName: institution.name || '',
|
||||
}}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
|
||||
<a href="/user/subscription/plans" rel="noopener" />,
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
<hr />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default InstitutionMemberships
|
|
@ -0,0 +1,46 @@
|
|||
import { Trans } from 'react-i18next'
|
||||
import getMeta from '../../../../utils/meta'
|
||||
import * as eventTracking from '../../../../infrastructure/event-tracking'
|
||||
|
||||
function PremiumFeaturesLink() {
|
||||
const featuresPageVariant =
|
||||
getMeta('ol-splitTestVariants')?.['features-page'] || 'default'
|
||||
|
||||
function handleLinkClick() {
|
||||
eventTracking.sendMB('features-page-link', {
|
||||
splitTest: 'features-page',
|
||||
splitTestVariant: featuresPageVariant,
|
||||
})
|
||||
}
|
||||
|
||||
const featuresPageLink = (
|
||||
// translation adds content
|
||||
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
||||
<a
|
||||
href={
|
||||
featuresPageVariant === 'new'
|
||||
? '/about/features-overview'
|
||||
: '/learn/how-to/Overleaf_premium_features'
|
||||
}
|
||||
onClick={handleLinkClick}
|
||||
/>
|
||||
)
|
||||
|
||||
if (featuresPageVariant === 'new') {
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="get_most_subscription_by_checking_features"
|
||||
components={[featuresPageLink]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="get_most_subscription_by_checking_premium_features"
|
||||
components={[featuresPageLink]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default PremiumFeaturesLink
|
|
@ -1,4 +1,5 @@
|
|||
import useWaitForI18n from '../../../../shared/hooks/use-wait-for-i18n'
|
||||
import SubscriptionDashboard from './subscription-dashboard'
|
||||
|
||||
function Root() {
|
||||
const { isReady } = useWaitForI18n()
|
||||
|
@ -7,7 +8,7 @@ function Root() {
|
|||
return null
|
||||
}
|
||||
|
||||
return <h2>React Subscription Dashboard</h2>
|
||||
return <SubscriptionDashboard />
|
||||
}
|
||||
|
||||
export default Root
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import getMeta from '../../../../utils/meta'
|
||||
import InstitutionMemberships from './institution-memberships'
|
||||
import PremiumFeaturesLink from './premium-features-link'
|
||||
|
||||
function SubscriptionDashboard() {
|
||||
const { t } = useTranslation()
|
||||
const institutionMemberships = getMeta('ol-currentInstitutionsWithLicence')
|
||||
const hasDisplayedSubscription = institutionMemberships?.length > 0
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-md-8 col-md-offset-2">
|
||||
<div className="card">
|
||||
<div className="page-header">
|
||||
<h1>{t('your_subscription')}</h1>
|
||||
</div>
|
||||
|
||||
<InstitutionMemberships memberships={institutionMemberships} />
|
||||
{hasDisplayedSubscription ? <PremiumFeaturesLink /> : <></>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SubscriptionDashboard
|
|
@ -0,0 +1,58 @@
|
|||
import { expect } from 'chai'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import InstitutionMemberships from '../../../../../../frontend/js/features/subscription/components/dashboard/institution-memberships'
|
||||
|
||||
const memberships = [
|
||||
{
|
||||
id: 9258,
|
||||
name: 'Test University',
|
||||
commonsAccount: true,
|
||||
isUniversity: true,
|
||||
confirmed: true,
|
||||
ssoBeta: false,
|
||||
ssoEnabled: false,
|
||||
maxConfirmationMonths: 6,
|
||||
},
|
||||
{
|
||||
id: 9259,
|
||||
name: 'Example Institution',
|
||||
commonsAccount: true,
|
||||
isUniversity: true,
|
||||
confirmed: true,
|
||||
ssoBeta: false,
|
||||
ssoEnabled: true,
|
||||
maxConfirmationMonths: 12,
|
||||
},
|
||||
]
|
||||
|
||||
describe('<InstitutionMemberships />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
})
|
||||
|
||||
it('renders all insitutions with license', function () {
|
||||
render(<InstitutionMemberships memberships={memberships} />)
|
||||
|
||||
const elements = screen.getAllByText('You are on our', {
|
||||
exact: false,
|
||||
})
|
||||
expect(elements.length).to.equal(2)
|
||||
expect(elements[0].textContent).to.equal(
|
||||
'You are on our Professional plan as a confirmed member of Test University'
|
||||
)
|
||||
expect(elements[1].textContent).to.equal(
|
||||
'You are on our Professional plan as a confirmed member of Example Institution'
|
||||
)
|
||||
})
|
||||
|
||||
it('renders error message when failed to check commons licenses', function () {
|
||||
render(<InstitutionMemberships memberships={undefined} />)
|
||||
screen.getByText(
|
||||
'Sorry, something went wrong. Subscription information related to institutional affiliations may not be displayed. Please try again later.'
|
||||
)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,59 @@
|
|||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { fireEvent, render, screen, within } from '@testing-library/react'
|
||||
import * as eventTracking from '../../../../../../frontend/js/infrastructure/event-tracking'
|
||||
import PremiumFeaturesLink from '../../../../../../frontend/js/features/subscription/components/dashboard/premium-features-link'
|
||||
|
||||
describe('<PremiumFeaturesLink />', function () {
|
||||
const originalLocation = window.location
|
||||
|
||||
let sendMBSpy: sinon.SinonSpy
|
||||
|
||||
const variants = [
|
||||
{ name: 'default', link: '/learn/how-to/Overleaf_premium_features' },
|
||||
{ name: 'new', link: '/about/features-overview' },
|
||||
]
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
sendMBSpy = sinon.spy(eventTracking, 'sendMB')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
sendMBSpy.restore()
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: originalLocation,
|
||||
})
|
||||
})
|
||||
|
||||
for (const variant of variants) {
|
||||
describe(`${variant.name} variant`, function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-splitTestVariants', {
|
||||
'features-page': variant.name,
|
||||
})
|
||||
})
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache.delete('ol-splitTestVariants')
|
||||
})
|
||||
|
||||
it('renders the premium features link and sends analytics event', function () {
|
||||
render(<PremiumFeaturesLink />)
|
||||
const premiumText = screen.getByText('Get the most out of your', {
|
||||
exact: false,
|
||||
})
|
||||
const link = within(premiumText).getByRole('link')
|
||||
|
||||
fireEvent.click(link)
|
||||
|
||||
expect(sendMBSpy).to.be.calledOnce
|
||||
expect(sendMBSpy).calledWith('features-page-link', {
|
||||
splitTest: 'features-page',
|
||||
splitTestVariant: variant.name,
|
||||
page: originalLocation.pathname,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
|
@ -0,0 +1,29 @@
|
|||
import { render, screen } from '@testing-library/react'
|
||||
import SubscriptionDashboard from '../../../../../../frontend/js/features/subscription/components/dashboard/subscription-dashboard'
|
||||
|
||||
describe('<SubscriptionDashboard />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
window.metaAttributesCache.set('ol-currentInstitutionsWithLicence', [
|
||||
{
|
||||
id: 9258,
|
||||
name: 'Test University',
|
||||
commonsAccount: true,
|
||||
isUniversity: true,
|
||||
confirmed: true,
|
||||
ssoBeta: false,
|
||||
ssoEnabled: false,
|
||||
maxConfirmationMonths: 6,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.metaAttributesCache = new Map()
|
||||
})
|
||||
|
||||
it('renders the premium features text when a user has a subscription', function () {
|
||||
render(<SubscriptionDashboard />)
|
||||
screen.getByText('Get the most out of your', { exact: false })
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue