mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Add new Link sharing upgrade prompt in project page (#8775)
GitOrigin-RevId: 984ab0770b3b920daa945ed8b190e7debf9e4a1d
This commit is contained in:
parent
d0d1791b04
commit
a2e2dcccda
7 changed files with 132 additions and 29 deletions
|
@ -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,
|
||||||
|
|
|
@ -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": "",
|
||||||
|
|
|
@ -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> </span>
|
<span> </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,
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue