Add new Link sharing upgrade prompt in project page (#8775)

GitOrigin-RevId: 984ab0770b3b920daa945ed8b190e7debf9e4a1d
This commit is contained in:
M Fahru 2022-07-15 09:44:47 -04:00 committed by Copybot
parent d0d1791b04
commit a2e2dcccda
7 changed files with 132 additions and 29 deletions

View file

@ -968,6 +968,21 @@ const ProjectController = {
} }
}, },
], ],
linkSharingUpgradePromptAssignment(cb) {
SplitTestHandler.getAssignment(
req,
res,
'link-sharing-upgrade-prompt',
(error, assignment) => {
// do not fail editor load if assignment fails
if (error) {
cb(null, { variant: 'default' })
} else {
cb(null, assignment)
}
}
)
},
}, },
( (
err, err,

View file

@ -347,6 +347,7 @@
"please_set_main_file": "", "please_set_main_file": "",
"plus_upgraded_accounts_receive": "", "plus_upgraded_accounts_receive": "",
"premium_feature": "", "premium_feature": "",
"premium_makes_collab_easier_with_features": "",
"press_shortcut_to_open_advanced_reference_search": "", "press_shortcut_to_open_advanced_reference_search": "",
"private": "", "private": "",
"processing": "", "processing": "",
@ -485,6 +486,7 @@
"too_recently_compiled": "", "too_recently_compiled": "",
"total_words": "", "total_words": "",
"try_again": "", "try_again": "",
"try_for_free": "",
"try_it_for_free": "", "try_it_for_free": "",
"try_premium_for_free": "", "try_premium_for_free": "",
"try_recompile_project_or_troubleshoot": "", "try_recompile_project_or_troubleshoot": "",

View file

@ -1,7 +1,7 @@
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Button, Col, Row } from 'react-bootstrap' import { Button, Col, Row } from 'react-bootstrap'
import { Trans } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import Tooltip from '../../../shared/components/tooltip' import Tooltip from '../../../shared/components/tooltip'
import Icon from '../../../shared/components/icon' import Icon from '../../../shared/components/icon'
import { useShareProjectContext } from './share-project-modal' import { useShareProjectContext } from './share-project-modal'
@ -10,8 +10,10 @@ import CopyLink from '../../../shared/components/copy-link'
import { useProjectContext } from '../../../shared/context/project-context' import { useProjectContext } from '../../../shared/context/project-context'
import * as eventTracking from '../../../infrastructure/event-tracking' import * as eventTracking from '../../../infrastructure/event-tracking'
import { useUserContext } from '../../../shared/context/user-context' import { useUserContext } from '../../../shared/context/user-context'
import StartFreeTrialButton from '../../../shared/components/start-free-trial-button'
import { useSplitTestContext } from '../../../shared/context/split-test-context'
export default function LinkSharing() { export default function LinkSharing({ canAddCollaborators }) {
const [inflight, setInflight] = useState(false) const [inflight, setInflight] = useState(false)
const { monitorRequest } = useShareProjectContext() const { monitorRequest } = useShareProjectContext()
@ -51,6 +53,7 @@ export default function LinkSharing() {
<TokenBasedSharing <TokenBasedSharing
setAccessLevel={setAccessLevel} setAccessLevel={setAccessLevel}
inflight={inflight} inflight={inflight}
canAddCollaborators={canAddCollaborators}
/> />
) )
@ -70,6 +73,10 @@ export default function LinkSharing() {
} }
} }
LinkSharing.propTypes = {
canAddCollaborators: PropTypes.bool,
}
function PrivateSharing({ setAccessLevel, inflight }) { function PrivateSharing({ setAccessLevel, inflight }) {
return ( return (
<Row className="public-access-level"> <Row className="public-access-level">
@ -100,7 +107,7 @@ PrivateSharing.propTypes = {
inflight: PropTypes.bool, inflight: PropTypes.bool,
} }
function TokenBasedSharing({ setAccessLevel, inflight }) { function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) {
const { tokens } = useProjectContext() const { tokens } = useProjectContext()
return ( return (
@ -121,6 +128,7 @@ function TokenBasedSharing({ setAccessLevel, inflight }) {
<span>&nbsp;&nbsp;</span> <span>&nbsp;&nbsp;</span>
<LinkSharingInfo /> <LinkSharingInfo />
</Col> </Col>
<LinkSharingUpgradePrompt canAddCollaborators={canAddCollaborators} />
<Col xs={12} className="access-token-display-area"> <Col xs={12} className="access-token-display-area">
<div className="access-token-wrapper"> <div className="access-token-wrapper">
<strong> <strong>
@ -150,6 +158,7 @@ function TokenBasedSharing({ setAccessLevel, inflight }) {
TokenBasedSharing.propTypes = { TokenBasedSharing.propTypes = {
setAccessLevel: PropTypes.func.isRequired, setAccessLevel: PropTypes.func.isRequired,
inflight: PropTypes.bool, inflight: PropTypes.bool,
canAddCollaborators: PropTypes.bool,
} }
function LegacySharing({ accessLevel, setAccessLevel, inflight }) { function LegacySharing({ accessLevel, setAccessLevel, inflight }) {
@ -257,3 +266,51 @@ function LinkSharingInfo() {
</Tooltip> </Tooltip>
) )
} }
function LinkSharingUpgradePrompt({ canAddCollaborators }) {
const [startedFreeTrial, setStartedFreeTrial] = useState(false)
const { t } = useTranslation()
const { splitTestVariants } = useSplitTestContext()
const linkSharingUpgradePromptActive =
splitTestVariants['link-sharing-upgrade-prompt'] === 'active'
const user = useUserContext({
allowedFreeTrial: PropTypes.bool,
})
const showLinkSharingUpgradePrompt =
linkSharingUpgradePromptActive &&
user.allowedFreeTrial &&
canAddCollaborators
if (!showLinkSharingUpgradePrompt) {
return null
}
return (
<>
<Col xs={12} className="link-sharing-upgrade-prompt">
<p>
<Trans
i18nKey="premium_makes_collab_easier_with_features"
components={[<b />, <b />, <b />]} // eslint-disable-line react/jsx-key
/>
</p>
<StartFreeTrialButton
buttonStyle="success"
setStartedFreeTrial={setStartedFreeTrial}
source="link-sharing"
/>
</Col>
{startedFreeTrial && (
<p className="small teaser-refresh-label">
{t('refresh_page_after_starting_free_trial')}
</p>
)}
</>
)
}
LinkSharingUpgradePrompt.propTypes = {
canAddCollaborators: PropTypes.bool,
}

View file

@ -1,29 +1,16 @@
import { useMemo } from 'react'
import { Row } from 'react-bootstrap' import { Row } from 'react-bootstrap'
import AddCollaborators from './add-collaborators' import AddCollaborators from './add-collaborators'
import AddCollaboratorsUpgrade from './add-collaborators-upgrade' import AddCollaboratorsUpgrade from './add-collaborators-upgrade'
import { useProjectContext } from '../../../shared/context/project-context' import PropTypes from 'prop-types'
export default function SendInvites() {
const { members, invites, features } = useProjectContext()
// whether the project has not reached the collaborator limit
const canAddCollaborators = useMemo(() => {
if (!features) {
return false
}
if (features.collaborators === -1) {
// infinite collaborators
return true
}
return members.length + invites.length < features.collaborators
}, [members, invites, features])
export default function SendInvites({ canAddCollaborators }) {
return ( return (
<Row className="invite-controls"> <Row className="invite-controls">
{canAddCollaborators ? <AddCollaborators /> : <AddCollaboratorsUpgrade />} {canAddCollaborators ? <AddCollaborators /> : <AddCollaboratorsUpgrade />}
</Row> </Row>
) )
} }
SendInvites.propTypes = {
canAddCollaborators: PropTypes.bool,
}

View file

@ -8,6 +8,7 @@ import SendInvitesNotice from './send-invites-notice'
import { useEditorContext } from '../../../shared/context/editor-context' import { useEditorContext } from '../../../shared/context/editor-context'
import { useProjectContext } from '../../../shared/context/project-context' import { useProjectContext } from '../../../shared/context/project-context'
import { useSplitTestContext } from '../../../shared/context/split-test-context' import { useSplitTestContext } from '../../../shared/context/split-test-context'
import { useMemo } from 'react'
import { Row } from 'react-bootstrap' import { Row } from 'react-bootstrap'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import RecaptchaConditions from '../../../shared/components/recaptcha-conditions' import RecaptchaConditions from '../../../shared/components/recaptcha-conditions'
@ -17,8 +18,22 @@ export default function ShareModalBody() {
splitTestVariants: PropTypes.object, splitTestVariants: PropTypes.object,
}) })
const { members, invites, features } = useProjectContext()
const { isProjectOwner } = useEditorContext() const { isProjectOwner } = useEditorContext()
const { invites, members } = useProjectContext()
// whether the project has not reached the collaborator limit
const canAddCollaborators = useMemo(() => {
if (!isProjectOwner || !features) {
return false
}
if (features.collaborators === -1) {
// infinite collaborators
return true
}
return members.length + invites.length < features.collaborators
}, [members, invites, features, isProjectOwner])
switch (splitTestVariants['project-share-modal-paywall']) { switch (splitTestVariants['project-share-modal-paywall']) {
case 'new-copy-top': case 'new-copy-top':
@ -26,7 +41,7 @@ export default function ShareModalBody() {
<> <>
{isProjectOwner ? ( {isProjectOwner ? (
<> <>
<SendInvites /> <SendInvites canAddCollaborators={canAddCollaborators} />
<Row className="public-access-level" /> <Row className="public-access-level" />
</> </>
) : ( ) : (
@ -54,7 +69,7 @@ export default function ShareModalBody() {
{isProjectOwner && ( {isProjectOwner && (
<> <>
<br /> <br />
<LinkSharing /> <LinkSharing canAddCollaborators={canAddCollaborators} />
</> </>
)} )}
@ -84,12 +99,16 @@ export default function ShareModalBody() {
/> />
))} ))}
{isProjectOwner ? <SendInvites /> : <SendInvitesNotice />} {isProjectOwner ? (
<SendInvites canAddCollaborators={canAddCollaborators} />
) : (
<SendInvitesNotice />
)}
{isProjectOwner && ( {isProjectOwner && (
<> <>
<br /> <br />
<LinkSharing /> <LinkSharing canAddCollaborators={canAddCollaborators} />
</> </>
)} )}
@ -103,7 +122,9 @@ export default function ShareModalBody() {
default: default:
return ( return (
<> <>
{isProjectOwner && <LinkSharing />} {isProjectOwner && (
<LinkSharing canAddCollaborators={canAddCollaborators} />
)}
<OwnerInfo /> <OwnerInfo />
@ -123,7 +144,11 @@ export default function ShareModalBody() {
/> />
))} ))}
{isProjectOwner ? <SendInvites /> : <SendInvitesNotice />} {isProjectOwner ? (
<SendInvites canAddCollaborators={canAddCollaborators} />
) : (
<SendInvitesNotice />
)}
{!window.ExposedSettings.recaptchaDisabled?.invite && ( {!window.ExposedSettings.recaptchaDisabled?.invite && (
<RecaptchaConditions /> <RecaptchaConditions />

View file

@ -45,6 +45,22 @@
} }
} }
} }
.link-sharing-upgrade-prompt {
background-color: @ol-blue-gray-0;
margin-top: @margin-sm;
padding: @padding-sm 15px;
display: flex;
align-items: center;
border-radius: 3px;
p {
margin-bottom: 0;
margin-right: 15px;
color: @ol-blue-gray-3;
font-size: 14px;
line-height: 23px;
}
}
} }
.public-access-level.public-access-level--notice { .public-access-level.public-access-level--notice {

View file

@ -1790,6 +1790,7 @@
"trial_last_day": "This is the last day of your <b>Overleaf Premium</b> trial", "trial_last_day": "This is the last day of your <b>Overleaf Premium</b> trial",
"trial_remaining_days": "__days__ more days on your <b>Overleaf Premium</b> trial", "trial_remaining_days": "__days__ more days on your <b>Overleaf Premium</b> trial",
"get_most_subscription_by_checking_premium_features": "Get the most out of your __appName__ subscription by checking out the list of <0>__appName__s premium features</0>.", "get_most_subscription_by_checking_premium_features": "Get the most out of your __appName__ subscription by checking out the list of <0>__appName__s premium features</0>.",
"premium_makes_collab_easier_with_features": "<0>Overleaf Premium</0> makes collaboration with others easier with features such as <1>track changes</1> and <2>full document history.</2>",
"would_you_like_to_see_a_university_subscription": "Would you like to see a university-wide __appName__ subscription at your university?", "would_you_like_to_see_a_university_subscription": "Would you like to see a university-wide __appName__ subscription at your university?",
"student_and_faculty_support_make_difference": "Student and faculty support make a difference! We can share this information with our contacts at your university when discussing an Overleaf institutional account.", "student_and_faculty_support_make_difference": "Student and faculty support make a difference! We can share this information with our contacts at your university when discussing an Overleaf institutional account.",
"show_your_support": "Show your support", "show_your_support": "Show your support",