mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
[web] Share modal shows downgraded editors (#20015)
* add hasBeenDowngraded prop for EditMember * reduce padding on share modal collab row, add prompt to hasBeenDowngraded Select * share modal select styling tweaks to allow for inline warning icon * always show editor limit subtitle when in downgraded state * add AccessLevelsChanged warning, tweak owner row styling * conditionally set hasBeenDowngraded prop. make invited member row styling more consistent between warning/enforcement * add an info state for access level changed notification * add notification for lost edit access on collaborator share modal, TSify SendInvitesNotice * fix member privilege alignment in collaborator share modal * show blue upgrade CTA when some pending editors have been resolved * automatically show share modal to owners when has pending editors or is over collab limit * only show lost edit access warning in read-only share modal to pending editors --------- Co-authored-by: Thomas <thomas-@users.noreply.github.com> GitOrigin-RevId: e3b88052a48b8f598299ffc55b7c24cb793da151
This commit is contained in:
parent
3e06c0086e
commit
2dcf87e3f6
14 changed files with 280 additions and 61 deletions
|
@ -29,6 +29,7 @@
|
||||||
"accepted_invite": "",
|
"accepted_invite": "",
|
||||||
"accepting_invite_as": "",
|
"accepting_invite_as": "",
|
||||||
"access_denied": "",
|
"access_denied": "",
|
||||||
|
"access_levels_changed": "",
|
||||||
"account_has_been_link_to_institution_account": "",
|
"account_has_been_link_to_institution_account": "",
|
||||||
"account_has_past_due_invoice_change_plan_warning": "",
|
"account_has_past_due_invoice_change_plan_warning": "",
|
||||||
"account_managed_by_group_administrator": "",
|
"account_managed_by_group_administrator": "",
|
||||||
|
@ -745,8 +746,10 @@
|
||||||
"library": "",
|
"library": "",
|
||||||
"license_for_educational_purposes": "",
|
"license_for_educational_purposes": "",
|
||||||
"limited_offer": "",
|
"limited_offer": "",
|
||||||
|
"limited_to_n_editors": "",
|
||||||
"limited_to_n_editors_per_project": "",
|
"limited_to_n_editors_per_project": "",
|
||||||
"limited_to_n_editors_per_project_plural": "",
|
"limited_to_n_editors_per_project_plural": "",
|
||||||
|
"limited_to_n_editors_plural": "",
|
||||||
"line": "",
|
"line": "",
|
||||||
"line_height": "",
|
"line_height": "",
|
||||||
"line_width_is_the_width_of_the_line_in_the_current_environment": "",
|
"line_width_is_the_width_of_the_line_in_the_current_environment": "",
|
||||||
|
@ -985,6 +988,7 @@
|
||||||
"percent_is_the_percentage_of_the_line_width": "",
|
"percent_is_the_percentage_of_the_line_width": "",
|
||||||
"plan": "",
|
"plan": "",
|
||||||
"plan_tooltip": "",
|
"plan_tooltip": "",
|
||||||
|
"please_ask_the_project_owner_to_upgrade_more_editors": "",
|
||||||
"please_ask_the_project_owner_to_upgrade_to_track_changes": "",
|
"please_ask_the_project_owner_to_upgrade_to_track_changes": "",
|
||||||
"please_change_primary_to_remove": "",
|
"please_change_primary_to_remove": "",
|
||||||
"please_check_your_inbox": "",
|
"please_check_your_inbox": "",
|
||||||
|
@ -1228,6 +1232,8 @@
|
||||||
"select_a_project": "",
|
"select_a_project": "",
|
||||||
"select_a_project_figure_modal": "",
|
"select_a_project_figure_modal": "",
|
||||||
"select_a_row_or_a_column_to_delete": "",
|
"select_a_row_or_a_column_to_delete": "",
|
||||||
|
"select_access_level": "",
|
||||||
|
"select_access_levels": "",
|
||||||
"select_all": "",
|
"select_all": "",
|
||||||
"select_all_projects": "",
|
"select_all_projects": "",
|
||||||
"select_an_output_file": "",
|
"select_an_output_file": "",
|
||||||
|
@ -1451,7 +1457,9 @@
|
||||||
"this_field_is_required": "",
|
"this_field_is_required": "",
|
||||||
"this_grants_access_to_features_2": "",
|
"this_grants_access_to_features_2": "",
|
||||||
"this_is_a_labs_experiment": "",
|
"this_is_a_labs_experiment": "",
|
||||||
|
"this_project_already_has_maximum_editors": "",
|
||||||
"this_project_exceeded_compile_timeout_limit_on_free_plan": "",
|
"this_project_exceeded_compile_timeout_limit_on_free_plan": "",
|
||||||
|
"this_project_exceeded_editor_limit": "",
|
||||||
"this_project_has_more_than_max_collabs": "",
|
"this_project_has_more_than_max_collabs": "",
|
||||||
"this_project_is_public": "",
|
"this_project_is_public": "",
|
||||||
"this_project_is_public_read_only": "",
|
"this_project_is_public_read_only": "",
|
||||||
|
@ -1652,6 +1660,7 @@
|
||||||
"view_metrics_commons_subtext": "",
|
"view_metrics_commons_subtext": "",
|
||||||
"view_metrics_group_subtext": "",
|
"view_metrics_group_subtext": "",
|
||||||
"view_more": "",
|
"view_more": "",
|
||||||
|
"view_only_downgraded": "",
|
||||||
"view_options": "",
|
"view_options": "",
|
||||||
"view_pdf": "",
|
"view_pdf": "",
|
||||||
"view_your_invoices": "",
|
"view_your_invoices": "",
|
||||||
|
@ -1722,6 +1731,8 @@
|
||||||
"you_can_only_add_n_people_to_edit_a_project": "",
|
"you_can_only_add_n_people_to_edit_a_project": "",
|
||||||
"you_can_only_add_n_people_to_edit_a_project_plural": "",
|
"you_can_only_add_n_people_to_edit_a_project_plural": "",
|
||||||
"you_can_request_a_maximum_of_limit_fixes_per_day": "",
|
"you_can_request_a_maximum_of_limit_fixes_per_day": "",
|
||||||
|
"you_can_select_or_invite": "",
|
||||||
|
"you_can_select_or_invite_plural": "",
|
||||||
"you_cant_add_or_change_password_due_to_sso": "",
|
"you_cant_add_or_change_password_due_to_sso": "",
|
||||||
"you_cant_join_this_group_subscription": "",
|
"you_cant_join_this_group_subscription": "",
|
||||||
"you_dont_have_any_repositories": "",
|
"you_dont_have_any_repositories": "",
|
||||||
|
@ -1756,6 +1767,7 @@
|
||||||
"your_plan_is_limited_to_n_editors": "",
|
"your_plan_is_limited_to_n_editors": "",
|
||||||
"your_plan_is_limited_to_n_editors_plural": "",
|
"your_plan_is_limited_to_n_editors_plural": "",
|
||||||
"your_project_exceeded_compile_timeout_limit_on_free_plan": "",
|
"your_project_exceeded_compile_timeout_limit_on_free_plan": "",
|
||||||
|
"your_project_exceeded_editor_limit": "",
|
||||||
"your_project_near_compile_timeout_limit": "",
|
"your_project_near_compile_timeout_limit": "",
|
||||||
"your_projects": "",
|
"your_projects": "",
|
||||||
"your_role": "",
|
"your_role": "",
|
||||||
|
@ -1769,6 +1781,7 @@
|
||||||
"youre_joining": "",
|
"youre_joining": "",
|
||||||
"youre_on_free_trial_which_ends_on": "",
|
"youre_on_free_trial_which_ends_on": "",
|
||||||
"youre_signed_in_as_logout": "",
|
"youre_signed_in_as_logout": "",
|
||||||
|
"youve_lost_edit_access": "",
|
||||||
"youve_unlinked_all_users": "",
|
"youve_unlinked_all_users": "",
|
||||||
"zoom_in": "",
|
"zoom_in": "",
|
||||||
"zoom_out": "",
|
"zoom_out": "",
|
||||||
|
|
|
@ -54,7 +54,7 @@ export default function EditMember({ member }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form horizontal id="share-project-form" onSubmit={handleSubmit}>
|
<Form horizontal id="share-project-form" onSubmit={handleSubmit}>
|
||||||
<FormGroup className="project-member">
|
<FormGroup className="project-member row">
|
||||||
<Col xs={7}>
|
<Col xs={7}>
|
||||||
<FormControl.Static>{member.email}</FormControl.Static>
|
<FormControl.Static>{member.email}</FormControl.Static>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Button } from 'react-bootstrap'
|
||||||
|
import Notification from '@/shared/components/notification'
|
||||||
|
import { upgradePlan } from '../../../../main/account-upgrade'
|
||||||
|
import { useProjectContext } from '@/shared/context/project-context'
|
||||||
|
import { useUserContext } from '@/shared/context/user-context'
|
||||||
|
import { sendMB } from '@/infrastructure/event-tracking'
|
||||||
|
import StartFreeTrialButton from '@/shared/components/start-free-trial-button'
|
||||||
|
|
||||||
|
type AccessLevelsChangedProps = {
|
||||||
|
somePendingEditorsResolved: boolean
|
||||||
|
}
|
||||||
|
export default function AccessLevelsChanged({
|
||||||
|
somePendingEditorsResolved,
|
||||||
|
}: AccessLevelsChangedProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { features } = useProjectContext()
|
||||||
|
const user = useUserContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="add-collaborators-upgrade">
|
||||||
|
<Notification
|
||||||
|
isActionBelowContent
|
||||||
|
type={somePendingEditorsResolved ? 'info' : 'warning'}
|
||||||
|
title={
|
||||||
|
somePendingEditorsResolved
|
||||||
|
? t('select_access_levels')
|
||||||
|
: t('access_levels_changed')
|
||||||
|
}
|
||||||
|
content={
|
||||||
|
somePendingEditorsResolved ? (
|
||||||
|
<p>{t('your_project_exceeded_editor_limit')}</p>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
{t('this_project_exceeded_editor_limit')}{' '}
|
||||||
|
{t('you_can_select_or_invite', {
|
||||||
|
count: features.collaborators,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
action={
|
||||||
|
<div className="upgrade-actions">
|
||||||
|
{user.allowedFreeTrial ? (
|
||||||
|
<StartFreeTrialButton
|
||||||
|
buttonProps={{ variant: 'secondary', size: 'small' }}
|
||||||
|
source="project-sharing"
|
||||||
|
variant="exceeds"
|
||||||
|
>
|
||||||
|
{t('upgrade')}
|
||||||
|
</StartFreeTrialButton>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
bsSize="sm"
|
||||||
|
className="btn-secondary"
|
||||||
|
onClick={() => {
|
||||||
|
upgradePlan('project-sharing')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('upgrade')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
href="https://www.overleaf.com/blog/changes-to-project-sharing"
|
||||||
|
bsSize="sm"
|
||||||
|
className="btn-link"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
onClick={() => {
|
||||||
|
sendMB('paywall-info-click', {
|
||||||
|
'paywall-type': 'project-sharing',
|
||||||
|
content: 'blog',
|
||||||
|
variant: 'exceeds',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('read_more')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -13,11 +13,12 @@ import type { ProjectContextMember } from '@/shared/context/types/project-contex
|
||||||
import { PermissionsLevel } from '@/features/ide-react/types/permissions'
|
import { PermissionsLevel } from '@/features/ide-react/types/permissions'
|
||||||
import { linkSharingEnforcementDate } from '../../utils/link-sharing'
|
import { linkSharingEnforcementDate } from '../../utils/link-sharing'
|
||||||
|
|
||||||
type PermissionsOption = PermissionsLevel | 'removeAccess'
|
type PermissionsOption = PermissionsLevel | 'removeAccess' | 'downgraded'
|
||||||
|
|
||||||
type EditMemberProps = {
|
type EditMemberProps = {
|
||||||
member: ProjectContextMember
|
member: ProjectContextMember
|
||||||
hasExceededCollaboratorLimit: boolean
|
hasExceededCollaboratorLimit: boolean
|
||||||
|
hasBeenDowngraded: boolean
|
||||||
canAddCollaborators: boolean
|
canAddCollaborators: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@ type Privilege = {
|
||||||
export default function EditMember({
|
export default function EditMember({
|
||||||
member,
|
member,
|
||||||
hasExceededCollaboratorLimit,
|
hasExceededCollaboratorLimit,
|
||||||
|
hasBeenDowngraded,
|
||||||
canAddCollaborators,
|
canAddCollaborators,
|
||||||
}: EditMemberProps) {
|
}: EditMemberProps) {
|
||||||
const [privileges, setPrivileges] = useState<PermissionsOption>(
|
const [privileges, setPrivileges] = useState<PermissionsOption>(
|
||||||
|
@ -133,7 +135,7 @@ export default function EditMember({
|
||||||
<div className="email-warning">
|
<div className="email-warning">
|
||||||
{member.email}
|
{member.email}
|
||||||
{member.pendingEditor && (
|
{member.pendingEditor && (
|
||||||
<div className="subtitle">Pending editor</div>
|
<div className="subtitle">{t('view_only_downgraded')}</div>
|
||||||
)}
|
)}
|
||||||
{shouldWarnMember() && (
|
{shouldWarnMember() && (
|
||||||
<div className="subtitle">
|
<div className="subtitle">
|
||||||
|
@ -146,7 +148,7 @@ export default function EditMember({
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col xs={2}>
|
<Col xs={1}>
|
||||||
{privileges !== member.privileges && privilegeChangePending && (
|
{privileges !== member.privileges && privilegeChangePending && (
|
||||||
<ChangePrivilegesActions
|
<ChangePrivilegesActions
|
||||||
handleReset={() => setPrivileges(member.privileges)}
|
handleReset={() => setPrivileges(member.privileges)}
|
||||||
|
@ -154,7 +156,9 @@ export default function EditMember({
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col xs={3}>
|
<Col xs={4} className="project-member-select">
|
||||||
|
{hasBeenDowngraded && <Icon type="warning" fw />}
|
||||||
|
|
||||||
<SelectPrivilege
|
<SelectPrivilege
|
||||||
value={privileges}
|
value={privileges}
|
||||||
handleChange={value => {
|
handleChange={value => {
|
||||||
|
@ -162,6 +166,7 @@ export default function EditMember({
|
||||||
handlePrivilegeChange(value.key)
|
handlePrivilegeChange(value.key)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
hasBeenDowngraded={hasBeenDowngraded}
|
||||||
canAddCollaborators={canAddCollaborators}
|
canAddCollaborators={canAddCollaborators}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -182,12 +187,14 @@ EditMember.propTypes = {
|
||||||
type SelectPrivilegeProps = {
|
type SelectPrivilegeProps = {
|
||||||
value: string
|
value: string
|
||||||
handleChange: (item: Privilege | null | undefined) => void
|
handleChange: (item: Privilege | null | undefined) => void
|
||||||
|
hasBeenDowngraded: boolean
|
||||||
canAddCollaborators: boolean
|
canAddCollaborators: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function SelectPrivilege({
|
function SelectPrivilege({
|
||||||
value,
|
value,
|
||||||
handleChange,
|
handleChange,
|
||||||
|
hasBeenDowngraded,
|
||||||
canAddCollaborators,
|
canAddCollaborators,
|
||||||
}: SelectPrivilegeProps) {
|
}: SelectPrivilegeProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
@ -203,25 +210,50 @@ function SelectPrivilege({
|
||||||
[t]
|
[t]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const downgradedPseudoPrivilege: Privilege = {
|
||||||
|
key: 'downgraded',
|
||||||
|
label: t('select_access_level'),
|
||||||
|
}
|
||||||
|
|
||||||
function getPrivilegeSubtitle(privilege: PermissionsOption) {
|
function getPrivilegeSubtitle(privilege: PermissionsOption) {
|
||||||
|
if (!hasBeenDowngraded) {
|
||||||
return !canAddCollaborators &&
|
return !canAddCollaborators &&
|
||||||
privilege === 'readAndWrite' &&
|
privilege === 'readAndWrite' &&
|
||||||
value !== 'readAndWrite'
|
value !== 'readAndWrite'
|
||||||
? t('limited_to_n_editors_per_project', { count: features.collaborators })
|
? t('limited_to_n_editors_per_project', {
|
||||||
|
count: features.collaborators,
|
||||||
|
})
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return privilege === 'readAndWrite'
|
||||||
|
? t('limited_to_n_editors', {
|
||||||
|
count: features.collaborators,
|
||||||
|
})
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivilegeDisabled(privilege: PermissionsOption) {
|
||||||
|
return (
|
||||||
|
!canAddCollaborators &&
|
||||||
|
privilege === 'readAndWrite' &&
|
||||||
|
(hasBeenDowngraded || value !== 'readAndWrite')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
items={privileges}
|
items={privileges}
|
||||||
itemToKey={item => item.key}
|
itemToKey={item => item.key}
|
||||||
itemToString={item => (item ? item.label : '')}
|
itemToString={item => (item ? item.label : '')}
|
||||||
itemToSubtitle={item => (item ? getPrivilegeSubtitle(item.key) : '')}
|
itemToSubtitle={item => (item ? getPrivilegeSubtitle(item.key) : '')}
|
||||||
itemToDisabled={item =>
|
itemToDisabled={item => (item ? isPrivilegeDisabled(item.key) : false)}
|
||||||
item ? getPrivilegeSubtitle(item.key) !== '' : false
|
|
||||||
}
|
|
||||||
defaultItem={privileges.find(item => item.key === value)}
|
defaultItem={privileges.find(item => item.key === value)}
|
||||||
selected={privileges.find(item => item.key === value)}
|
selected={
|
||||||
|
hasBeenDowngraded
|
||||||
|
? downgradedPseudoPrivilege
|
||||||
|
: privileges.find(item => item.key === value)
|
||||||
|
}
|
||||||
name="privileges"
|
name="privileges"
|
||||||
onSelectedItemChanged={handleChange}
|
onSelectedItemChanged={handleChange}
|
||||||
selectedIcon
|
selectedIcon
|
||||||
|
|
|
@ -9,13 +9,13 @@ export default function OwnerInfo() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className="project-member">
|
<Row className="project-member">
|
||||||
<Col xs={9}>
|
<Col xs={8}>
|
||||||
<div className="project-member-email-icon">
|
<div className="project-member-email-icon">
|
||||||
<Icon type="user" fw />
|
<Icon type="user" fw />
|
||||||
<div className="email-warning">{owner?.email}</div>
|
<div className="email-warning">{owner?.email}</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={3} className="text-left">
|
<Col xs={4} className="text-right">
|
||||||
{t('owner')}
|
{t('owner')}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { Col, Row } from 'react-bootstrap'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { useProjectContext } from '@/shared/context/project-context'
|
|
||||||
|
|
||||||
export default function SendInvitesNotice() {
|
|
||||||
const { publicAccessLevel } = useProjectContext()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row className="public-access-level public-access-level--notice">
|
|
||||||
<Col xs={12} className="text-center">
|
|
||||||
<AccessLevel level={publicAccessLevel} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function AccessLevel({ level }) {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
switch (level) {
|
|
||||||
case 'private':
|
|
||||||
return t('to_add_more_collaborators')
|
|
||||||
|
|
||||||
case 'tokenBased':
|
|
||||||
return t('to_change_access_permissions')
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AccessLevel.propTypes = {
|
|
||||||
level: PropTypes.string,
|
|
||||||
}
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { Col, Row } from 'react-bootstrap'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useProjectContext } from '@/shared/context/project-context'
|
||||||
|
import Notification from '@/shared/components/notification'
|
||||||
|
import { PublicAccessLevel } from '../../../../../../types/public-access-level'
|
||||||
|
import { useEditorContext } from '@/shared/context/editor-context'
|
||||||
|
|
||||||
|
export default function SendInvitesNotice() {
|
||||||
|
const { publicAccessLevel } = useProjectContext()
|
||||||
|
const { isPendingEditor } = useEditorContext()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{isPendingEditor && (
|
||||||
|
<Notification
|
||||||
|
isActionBelowContent
|
||||||
|
type="info"
|
||||||
|
title={t('youve_lost_edit_access')}
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
<p>{t('this_project_already_has_maximum_editors')}</p>
|
||||||
|
<p>{t('please_ask_the_project_owner_to_upgrade_more_editors')}</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Row className="public-access-level public-access-level--notice">
|
||||||
|
<Col xs={12} className="text-center">
|
||||||
|
<AccessLevel level={publicAccessLevel} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessLevelProps = {
|
||||||
|
level: PublicAccessLevel | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccessLevel({ level }: AccessLevelProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
switch (level) {
|
||||||
|
case 'private':
|
||||||
|
return <span>{t('to_add_more_collaborators')}</span>
|
||||||
|
|
||||||
|
case 'tokenBased':
|
||||||
|
return <span>{t('to_change_access_permissions')}</span>
|
||||||
|
|
||||||
|
default:
|
||||||
|
return <span>''</span>
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,18 +2,30 @@ 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 CollaboratorsLimitUpgrade from './collaborators-limit-upgrade'
|
import CollaboratorsLimitUpgrade from './collaborators-limit-upgrade'
|
||||||
|
import AccessLevelsChanged from './access-levels-changed'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
|
||||||
export default function SendInvites({
|
export default function SendInvites({
|
||||||
canAddCollaborators,
|
canAddCollaborators,
|
||||||
hasExceededCollaboratorLimit,
|
hasExceededCollaboratorLimit,
|
||||||
|
haveAnyEditorsBeenDowngraded,
|
||||||
|
somePendingEditorsResolved,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Row className="invite-controls">
|
<Row className="invite-controls">
|
||||||
{hasExceededCollaboratorLimit && <AddCollaboratorsUpgrade />}
|
{hasExceededCollaboratorLimit && !haveAnyEditorsBeenDowngraded && (
|
||||||
{!canAddCollaborators && !hasExceededCollaboratorLimit && (
|
<AddCollaboratorsUpgrade />
|
||||||
<CollaboratorsLimitUpgrade />
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{haveAnyEditorsBeenDowngraded && (
|
||||||
|
<AccessLevelsChanged
|
||||||
|
somePendingEditorsResolved={somePendingEditorsResolved}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!canAddCollaborators &&
|
||||||
|
!hasExceededCollaboratorLimit &&
|
||||||
|
!haveAnyEditorsBeenDowngraded && <CollaboratorsLimitUpgrade />}
|
||||||
<AddCollaborators readOnly={!canAddCollaborators} />
|
<AddCollaborators readOnly={!canAddCollaborators} />
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
|
@ -22,4 +34,6 @@ export default function SendInvites({
|
||||||
SendInvites.propTypes = {
|
SendInvites.propTypes = {
|
||||||
canAddCollaborators: PropTypes.bool,
|
canAddCollaborators: PropTypes.bool,
|
||||||
hasExceededCollaboratorLimit: PropTypes.bool,
|
hasExceededCollaboratorLimit: PropTypes.bool,
|
||||||
|
haveAnyEditorsBeenDowngraded: PropTypes.bool,
|
||||||
|
somePendingEditorsResolved: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,26 @@ export default function ShareModalBody() {
|
||||||
)
|
)
|
||||||
}, [members, invites, features, isProjectOwner])
|
}, [members, invites, features, isProjectOwner])
|
||||||
|
|
||||||
|
// determine if some but not all pending editors' permissions have been resolved,
|
||||||
|
// for moving between warning and info notification states etc.
|
||||||
|
const somePendingEditorsResolved = useMemo(() => {
|
||||||
|
return (
|
||||||
|
members.some(member => member.privileges === 'readAndWrite') &&
|
||||||
|
members.some(member => member.pendingEditor)
|
||||||
|
)
|
||||||
|
}, [members])
|
||||||
|
|
||||||
|
const haveAnyEditorsBeenDowngraded = useMemo(() => {
|
||||||
|
if (!isProjectOwner || !features) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (features.collaborators === -1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return members.some(member => member.pendingEditor)
|
||||||
|
}, [features, isProjectOwner, members])
|
||||||
|
|
||||||
const hasExceededCollaboratorLimit = useMemo(() => {
|
const hasExceededCollaboratorLimit = useMemo(() => {
|
||||||
if (!isProjectOwner || !features) {
|
if (!isProjectOwner || !features) {
|
||||||
return false
|
return false
|
||||||
|
@ -58,6 +78,8 @@ export default function ShareModalBody() {
|
||||||
<SendInvites
|
<SendInvites
|
||||||
canAddCollaborators={canAddCollaborators}
|
canAddCollaborators={canAddCollaborators}
|
||||||
hasExceededCollaboratorLimit={hasExceededCollaboratorLimit}
|
hasExceededCollaboratorLimit={hasExceededCollaboratorLimit}
|
||||||
|
haveAnyEditorsBeenDowngraded={haveAnyEditorsBeenDowngraded}
|
||||||
|
somePendingEditorsResolved={somePendingEditorsResolved}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<SendInvitesNotice />
|
<SendInvitesNotice />
|
||||||
|
@ -72,6 +94,7 @@ export default function ShareModalBody() {
|
||||||
key={member._id}
|
key={member._id}
|
||||||
member={member}
|
member={member}
|
||||||
hasExceededCollaboratorLimit={hasExceededCollaboratorLimit}
|
hasExceededCollaboratorLimit={hasExceededCollaboratorLimit}
|
||||||
|
hasBeenDowngraded={member.pendingEditor ?? false}
|
||||||
canAddCollaborators={canAddCollaborators}
|
canAddCollaborators={canAddCollaborators}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -68,7 +68,7 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||||
|
|
||||||
// split test: link-sharing-warning
|
// split test: link-sharing-warning
|
||||||
// show the new share modal if project owner
|
// show the new share modal if project owner
|
||||||
// is over collaborator limit (once every 24 hours)
|
// is over collaborator limit or has pending editors (once every 24 hours)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hasExceededCollaboratorLimit = () => {
|
const hasExceededCollaboratorLimit = () => {
|
||||||
if (!isProjectOwner || !project.features) {
|
if (!isProjectOwner || !project.features) {
|
||||||
|
@ -80,7 +80,8 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
project.members.filter(member => member.privileges === 'readAndWrite')
|
project.members.filter(member => member.privileges === 'readAndWrite')
|
||||||
.length > (project.features.collaborators ?? 1)
|
.length > (project.features.collaborators ?? 1) ||
|
||||||
|
project.members.some(member => member.pendingEditor)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,13 @@ import Icon from '@/shared/components/icon'
|
||||||
export default function ViewMember({ member }) {
|
export default function ViewMember({ member }) {
|
||||||
return (
|
return (
|
||||||
<Row className="project-member">
|
<Row className="project-member">
|
||||||
<Col xs={9}>
|
<Col xs={8}>
|
||||||
<div className="project-member-email-icon">
|
<div className="project-member-email-icon">
|
||||||
<Icon type="user" fw />
|
<Icon type="user" fw />
|
||||||
<div className="email-warning">{member.email}</div>
|
<div className="email-warning">{member.email}</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={3} className="text-left">
|
<Col xs={4} className="text-right">
|
||||||
<MemberPrivileges privileges={member.privileges} />
|
<MemberPrivileges privileges={member.privileges} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -101,12 +101,7 @@ export const EditorProvider: FC = ({ children }) => {
|
||||||
|
|
||||||
const isPendingEditor = useMemo(
|
const isPendingEditor = useMemo(
|
||||||
() =>
|
() =>
|
||||||
members?.some(
|
members?.some(member => member._id === userId && member.pendingEditor),
|
||||||
member =>
|
|
||||||
member._id === userId &&
|
|
||||||
member.pendingEditor &&
|
|
||||||
member.privileges === 'readAndWrite'
|
|
||||||
),
|
|
||||||
[members, userId]
|
[members, userId]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -191,4 +191,28 @@
|
||||||
.invite-warning {
|
.invite-warning {
|
||||||
margin-bottom: @line-height-computed / 2;
|
margin-bottom: @line-height-computed / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-member-select {
|
||||||
|
padding: 0;
|
||||||
|
.fa-warning {
|
||||||
|
color: @brand-warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-member.form-group,
|
||||||
|
.project-member.row {
|
||||||
|
margin: 0 -30px;
|
||||||
|
}
|
||||||
|
.project-member .select-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
.select-trigger {
|
||||||
|
gap: @spacing-02;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.project-member.row {
|
||||||
|
padding-right: 1.28571429em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
"accepted_invite": "Accepted invite",
|
"accepted_invite": "Accepted invite",
|
||||||
"accepting_invite_as": "You are accepting this invite as",
|
"accepting_invite_as": "You are accepting this invite as",
|
||||||
"access_denied": "Access Denied",
|
"access_denied": "Access Denied",
|
||||||
|
"access_levels_changed": "Access levels changed",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"account_has_been_link_to_institution_account": "Your __appName__ account on <b>__email__</b> has been linked to your <b>__institutionName__</b> institutional account.",
|
"account_has_been_link_to_institution_account": "Your __appName__ account on <b>__email__</b> has been linked to your <b>__institutionName__</b> institutional account.",
|
||||||
"account_has_past_due_invoice_change_plan_warning": "Your account currently has a past due invoice. You will not be able to change your plan until this is resolved.",
|
"account_has_past_due_invoice_change_plan_warning": "Your account currently has a past due invoice. You will not be able to change your plan until this is resolved.",
|
||||||
|
@ -1085,8 +1086,10 @@
|
||||||
"license": "License",
|
"license": "License",
|
||||||
"license_for_educational_purposes": "This license is for educational purposes (applies to students or faculty using __appName__ for teaching)",
|
"license_for_educational_purposes": "This license is for educational purposes (applies to students or faculty using __appName__ for teaching)",
|
||||||
"limited_offer": "Limited offer",
|
"limited_offer": "Limited offer",
|
||||||
|
"limited_to_n_editors": "Limited to __count__ editor",
|
||||||
"limited_to_n_editors_per_project": "Limited to __count__ editor per project",
|
"limited_to_n_editors_per_project": "Limited to __count__ editor per project",
|
||||||
"limited_to_n_editors_per_project_plural": "Limited to __count__ editors per project",
|
"limited_to_n_editors_per_project_plural": "Limited to __count__ editors per project",
|
||||||
|
"limited_to_n_editors_plural": "Limited to __count__ editors",
|
||||||
"line_height": "Line Height",
|
"line_height": "Line Height",
|
||||||
"line_width_is_the_width_of_the_line_in_the_current_environment": "Line width is the width of the line in the current environment. e.g. a full page width in single-column layout or half a page width in a two-column layout.",
|
"line_width_is_the_width_of_the_line_in_the_current_environment": "Line width is the width of the line in the current environment. e.g. a full page width in single-column layout or half a page width in a two-column layout.",
|
||||||
"link": "Link",
|
"link": "Link",
|
||||||
|
@ -1455,6 +1458,7 @@
|
||||||
"plans_amper_pricing": "Plans & Pricing",
|
"plans_amper_pricing": "Plans & Pricing",
|
||||||
"plans_and_pricing": "Plans and Pricing",
|
"plans_and_pricing": "Plans and Pricing",
|
||||||
"plans_and_pricing_lowercase": "plans and pricing",
|
"plans_and_pricing_lowercase": "plans and pricing",
|
||||||
|
"please_ask_the_project_owner_to_upgrade_more_editors": "Please ask the project owner to upgrade their plan to allow more editors.",
|
||||||
"please_ask_the_project_owner_to_upgrade_to_track_changes": "Please ask the project owner to upgrade to use track changes",
|
"please_ask_the_project_owner_to_upgrade_to_track_changes": "Please ask the project owner to upgrade to use track changes",
|
||||||
"please_change_primary_to_remove": "Please change your primary email in order to remove",
|
"please_change_primary_to_remove": "Please change your primary email in order to remove",
|
||||||
"please_check_your_inbox": "Please check your inbox",
|
"please_check_your_inbox": "Please check your inbox",
|
||||||
|
@ -1771,6 +1775,8 @@
|
||||||
"select_a_project": "Select a Project",
|
"select_a_project": "Select a Project",
|
||||||
"select_a_project_figure_modal": "Select a project",
|
"select_a_project_figure_modal": "Select a project",
|
||||||
"select_a_row_or_a_column_to_delete": "Select a row or a column to delete",
|
"select_a_row_or_a_column_to_delete": "Select a row or a column to delete",
|
||||||
|
"select_access_level": "Select access level",
|
||||||
|
"select_access_levels": "Select access levels",
|
||||||
"select_all": "Select all",
|
"select_all": "Select all",
|
||||||
"select_all_projects": "Select all projects",
|
"select_all_projects": "Select all projects",
|
||||||
"select_an_output_file": "Select an Output File",
|
"select_an_output_file": "Select an Output File",
|
||||||
|
@ -2064,7 +2070,9 @@
|
||||||
"this_grants_access_to_features_2": "This grants you access to <0>__appName__</0> <0>__featureType__</0> features.",
|
"this_grants_access_to_features_2": "This grants you access to <0>__appName__</0> <0>__featureType__</0> features.",
|
||||||
"this_is_a_labs_experiment": "This is a Labs experiment",
|
"this_is_a_labs_experiment": "This is a Labs experiment",
|
||||||
"this_is_your_template": "This is your template from your project",
|
"this_is_your_template": "This is your template from your project",
|
||||||
|
"this_project_already_has_maximum_editors": "This project already has the maximum number of editors permitted on the owner’s plan. This means you can view but not edit the project.",
|
||||||
"this_project_exceeded_compile_timeout_limit_on_free_plan": "This project exceeded the compile timeout limit on our free plan.",
|
"this_project_exceeded_compile_timeout_limit_on_free_plan": "This project exceeded the compile timeout limit on our free plan.",
|
||||||
|
"this_project_exceeded_editor_limit": "This project exceeded the editor limit for your plan. All collaborators now have view-only access.",
|
||||||
"this_project_has_more_than_max_collabs": "This project has more than the maximum number of collaborators allowed on the project owner’s Overleaf plan. This means you could lose edit access from __linkSharingDate__.",
|
"this_project_has_more_than_max_collabs": "This project has more than the maximum number of collaborators allowed on the project owner’s Overleaf plan. This means you could lose edit access from __linkSharingDate__.",
|
||||||
"this_project_is_public": "This project is public and can be edited by anyone with the URL.",
|
"this_project_is_public": "This project is public and can be edited by anyone with the URL.",
|
||||||
"this_project_is_public_read_only": "This project is public and can be viewed but not edited by anyone with the URL",
|
"this_project_is_public_read_only": "This project is public and can be viewed but not edited by anyone with the URL",
|
||||||
|
@ -2300,6 +2308,7 @@
|
||||||
"view_metrics_commons_subtext": "Monitor and download usage metrics for your Commons subscription",
|
"view_metrics_commons_subtext": "Monitor and download usage metrics for your Commons subscription",
|
||||||
"view_metrics_group_subtext": "Monitor and download usage metrics for your group subscription",
|
"view_metrics_group_subtext": "Monitor and download usage metrics for your group subscription",
|
||||||
"view_more": "View more",
|
"view_more": "View more",
|
||||||
|
"view_only_downgraded": "View only. Upgrade to restore edit access.",
|
||||||
"view_options": "View options",
|
"view_options": "View options",
|
||||||
"view_pdf": "View PDF",
|
"view_pdf": "View PDF",
|
||||||
"view_source": "View Source",
|
"view_source": "View Source",
|
||||||
|
@ -2391,6 +2400,8 @@
|
||||||
"you_can_only_add_n_people_to_edit_a_project_plural": "You can only add __count__ people to edit a project with you on your current plan. Upgrade to add more.",
|
"you_can_only_add_n_people_to_edit_a_project_plural": "You can only add __count__ people to edit a project with you on your current plan. Upgrade to add more.",
|
||||||
"you_can_opt_in_and_out_of_the_program_at_any_time_on_this_page": "You can <0>opt in and out</0> of the program at any time on this page",
|
"you_can_opt_in_and_out_of_the_program_at_any_time_on_this_page": "You can <0>opt in and out</0> of the program at any time on this page",
|
||||||
"you_can_request_a_maximum_of_limit_fixes_per_day": "You can request a maximum of __limit__ fixes per day. Please try again tomorrow.",
|
"you_can_request_a_maximum_of_limit_fixes_per_day": "You can request a maximum of __limit__ fixes per day. Please try again tomorrow.",
|
||||||
|
"you_can_select_or_invite": "You can select or invite __count__ editor on your current plan, or upgrade to get more.",
|
||||||
|
"you_can_select_or_invite_plural": "You can select or invite __count__ editors on your current plan, or upgrade to get more.",
|
||||||
"you_cant_add_or_change_password_due_to_sso": "You can’t add or change your password because your group or organization uses <0>single sign-on (SSO)</0>.",
|
"you_cant_add_or_change_password_due_to_sso": "You can’t add or change your password because your group or organization uses <0>single sign-on (SSO)</0>.",
|
||||||
"you_cant_join_this_group_subscription": "You can’t join this group subscription",
|
"you_cant_join_this_group_subscription": "You can’t join this group subscription",
|
||||||
"you_cant_reset_password_due_to_sso": "You can’t reset your password because your group or organization uses SSO. <0>Log in with SSO</0>.",
|
"you_cant_reset_password_due_to_sso": "You can’t reset your password because your group or organization uses SSO. <0>Log in with SSO</0>.",
|
||||||
|
@ -2434,6 +2445,7 @@
|
||||||
"your_plan_is_limited_to_n_editors": "Your plan allows __count__ collaborator with edit access and unlimited viewers.",
|
"your_plan_is_limited_to_n_editors": "Your plan allows __count__ collaborator with edit access and unlimited viewers.",
|
||||||
"your_plan_is_limited_to_n_editors_plural": "Your plan allows __count__ collaborators with edit access and unlimited viewers.",
|
"your_plan_is_limited_to_n_editors_plural": "Your plan allows __count__ collaborators with edit access and unlimited viewers.",
|
||||||
"your_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on our free plan.",
|
"your_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on our free plan.",
|
||||||
|
"your_project_exceeded_editor_limit": "Your project exceeded the editor limit and access levels were changed. Select a new access level for your collaborators, or upgrade to add more editors.",
|
||||||
"your_project_near_compile_timeout_limit": "Your project is near the compile timeout limit for our free plan.",
|
"your_project_near_compile_timeout_limit": "Your project is near the compile timeout limit for our free plan.",
|
||||||
"your_projects": "Your Projects",
|
"your_projects": "Your Projects",
|
||||||
"your_questions_answered": "Your questions answered",
|
"your_questions_answered": "Your questions answered",
|
||||||
|
@ -2450,6 +2462,7 @@
|
||||||
"youre_on_free_trial_which_ends_on": "You’re on a free trial which ends on <0>__date__</0>.",
|
"youre_on_free_trial_which_ends_on": "You’re on a free trial which ends on <0>__date__</0>.",
|
||||||
"youre_signed_in_as_logout": "You’re signed in as <0>__email__</0>. <1>Log out.</1>",
|
"youre_signed_in_as_logout": "You’re signed in as <0>__email__</0>. <1>Log out.</1>",
|
||||||
"youre_signed_up": "You’re signed up",
|
"youre_signed_up": "You’re signed up",
|
||||||
|
"youve_lost_edit_access": "You’ve lost edit access",
|
||||||
"youve_unlinked_all_users": "You’ve unlinked all users",
|
"youve_unlinked_all_users": "You’ve unlinked all users",
|
||||||
"zh-CN": "Chinese",
|
"zh-CN": "Chinese",
|
||||||
"zip_contents_too_large": "Zip contents too large",
|
"zip_contents_too_large": "Zip contents too large",
|
||||||
|
|
Loading…
Reference in a new issue