mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -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
|
block append meta
|
||||||
meta(name="ol-managedInstitutions", data-type="json", content=managedInstitutions)
|
meta(name="ol-managedInstitutions", data-type="json", content=managedInstitutions)
|
||||||
meta(name="ol-planCodesChangingAtTermEnd", data-type="json", content=plans.planCodesChangingAtTermEnd)
|
meta(name="ol-planCodesChangingAtTermEnd", data-type="json", content=plans.planCodesChangingAtTermEnd)
|
||||||
|
meta(name="ol-currentInstitutionsWithLicence", data-type="json" content=currentInstitutionsWithLicence)
|
||||||
if (personalSubscription && personalSubscription.recurly)
|
if (personalSubscription && personalSubscription.recurly)
|
||||||
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
|
meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
|
||||||
meta(name="ol-subscription" data-type="json" content=personalSubscription)
|
meta(name="ol-subscription" data-type="json" content=personalSubscription)
|
||||||
|
|
|
@ -252,6 +252,8 @@
|
||||||
"generic_linked_file_compile_error": "",
|
"generic_linked_file_compile_error": "",
|
||||||
"generic_something_went_wrong": "",
|
"generic_something_went_wrong": "",
|
||||||
"get_collaborative_benefits": "",
|
"get_collaborative_benefits": "",
|
||||||
|
"get_most_subscription_by_checking_features": "",
|
||||||
|
"get_most_subscription_by_checking_premium_features": "",
|
||||||
"git": "",
|
"git": "",
|
||||||
"git_bridge_modal_description": "",
|
"git_bridge_modal_description": "",
|
||||||
"github_commit_message_placeholder": "",
|
"github_commit_message_placeholder": "",
|
||||||
|
@ -745,12 +747,14 @@
|
||||||
"word_count": "",
|
"word_count": "",
|
||||||
"work_offline": "",
|
"work_offline": "",
|
||||||
"work_with_non_overleaf_users": "",
|
"work_with_non_overleaf_users": "",
|
||||||
|
"you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "",
|
||||||
"you_can_now_log_in_sso": "",
|
"you_can_now_log_in_sso": "",
|
||||||
"you_dont_have_any_repositories": "",
|
"you_dont_have_any_repositories": "",
|
||||||
"your_affiliation_is_confirmed": "",
|
"your_affiliation_is_confirmed": "",
|
||||||
"your_browser_does_not_support_this_feature": "",
|
"your_browser_does_not_support_this_feature": "",
|
||||||
"your_message_to_collaborators": "",
|
"your_message_to_collaborators": "",
|
||||||
"your_projects": "",
|
"your_projects": "",
|
||||||
|
"your_subscription": "",
|
||||||
"zotero_groups_loading_error": "",
|
"zotero_groups_loading_error": "",
|
||||||
"zotero_groups_relink": "",
|
"zotero_groups_relink": "",
|
||||||
"zotero_integration": "",
|
"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 useWaitForI18n from '../../../../shared/hooks/use-wait-for-i18n'
|
||||||
|
import SubscriptionDashboard from './subscription-dashboard'
|
||||||
|
|
||||||
function Root() {
|
function Root() {
|
||||||
const { isReady } = useWaitForI18n()
|
const { isReady } = useWaitForI18n()
|
||||||
|
@ -7,7 +8,7 @@ function Root() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return <h2>React Subscription Dashboard</h2>
|
return <SubscriptionDashboard />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Root
|
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