Просмотр исходного кода

Merge pull request #18910 from overleaf/rh-editor-warning-over-limit

[web] Show warning modal to editors opening a project over collaborator limit

GitOrigin-RevId: d9868c021d0aaf04bffd8afbd8c1c96fbf548755
roo hutton 2 лет назад
Родитель
Сommit
d16fee1afe

+ 7 - 0
services/web/frontend/extracted-translations.json

@@ -307,6 +307,7 @@
   "discount_of": "",
   "dismiss_error_popup": "",
   "display_deleted_user": "",
+  "do_you_need_edit_access": "",
   "do_you_want_to_change_your_primary_email_address_to": "",
   "do_you_want_to_overwrite_it": "",
   "do_you_want_to_overwrite_it_plural": "",
@@ -451,6 +452,7 @@
   "free_plan_label": "",
   "free_plan_tooltip": "",
   "from_another_project": "",
+  "from_enforcement_date": "",
   "from_external_url": "",
   "from_project_files": "",
   "from_provider": "",
@@ -710,6 +712,7 @@
   "license_for_educational_purposes": "",
   "limited_offer": "",
   "limited_to_n_editors_per_project": "",
+  "limited_to_n_editors_per_project_plural": "",
   "line_height": "",
   "line_width_is_the_width_of_the_line_in_the_current_environment": "",
   "link": "",
@@ -1388,6 +1391,7 @@
   "this_grants_access_to_features_2": "",
   "this_is_a_labs_experiment": "",
   "this_project_exceeded_compile_timeout_limit_on_free_plan": "",
+  "this_project_has_more_than_max_collabs": "",
   "this_project_is_public": "",
   "this_project_is_public_read_only": "",
   "this_project_will_appear_in_your_dropbox_folder_at": "",
@@ -1406,6 +1410,7 @@
   "to_fix_this_you_can": "",
   "to_fix_this_you_can_ask_the_github_repository_owner": "",
   "to_insert_or_move_a_caption_make_sure_tabular_is_directly_within_table": "",
+  "to_keep_edit_access": "",
   "to_modify_your_subscription_go_to": "",
   "to_use_text_wrapping_in_your_table_make_sure_you_include_the_array_package": "",
   "toggle_compile_options_menu": "",
@@ -1646,6 +1651,7 @@
   "you_can_now_enable_sso": "",
   "you_can_now_log_in_sso": "",
   "you_can_only_add_n_people_to_edit_a_project": "",
+  "you_can_only_add_n_people_to_edit_a_project_plural": "",
   "you_can_request_a_maximum_of_limit_fixes_per_day": "",
   "you_cant_add_or_change_password_due_to_sso": "",
   "you_cant_join_this_group_subscription": "",
@@ -1678,6 +1684,7 @@
   "your_plan": "",
   "your_plan_is_changing_at_term_end": "",
   "your_plan_is_limited_to_n_editors": "",
+  "your_plan_is_limited_to_n_editors_plural": "",
   "your_project_exceeded_compile_timeout_limit_on_free_plan": "",
   "your_project_near_compile_timeout_limit": "",
   "your_projects": "",

+ 9 - 4
services/web/frontend/js/features/ide-react/components/editor-navigation-toolbar.tsx

@@ -5,6 +5,8 @@ import * as eventTracking from '@/infrastructure/event-tracking'
 import EditorNavigationToolbarRoot from '@/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root'
 import NewShareProjectModal from '@/features/share-project-modal/components/restricted-link-sharing/share-project-modal'
 import ShareProjectModal from '@/features/share-project-modal/components/share-project-modal'
+import EditorOverLimitModal from '@/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal'
+
 import getMeta from '@/utils/meta'
 
 function EditorNavigationToolbar() {
@@ -31,10 +33,13 @@ function EditorNavigationToolbar() {
         openShareProjectModal={handleOpenShareModal}
       />
       {showNewShareModal ? (
-        <NewShareProjectModal
-          show={showShareModal}
-          handleHide={handleHideShareModal}
-        />
+        <>
+          <EditorOverLimitModal />
+          <NewShareProjectModal
+            show={showShareModal}
+            handleHide={handleHideShareModal}
+          />
+        </>
       ) : (
         <ShareProjectModal
           show={showShareModal}

+ 13 - 1
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/add-collaborators-upgrade.tsx

@@ -2,16 +2,28 @@ import { useTranslation } from 'react-i18next'
 import { Button } from 'react-bootstrap'
 import Notification from '@/shared/components/notification'
 import { upgradePlan } from '../../../../main/account-upgrade'
+import { linkSharingEnforcementDate } from '../../utils/link-sharing'
+import { useProjectContext } from '@/shared/context/project-context'
 
 export default function AddCollaboratorsUpgrade() {
   const { t } = useTranslation()
+  const { features } = useProjectContext()
 
   return (
     <div className="add-collaborators-upgrade">
       <Notification
         type="warning"
         title={t('editor_limit_exceeded_in_this_project')}
-        content={<p>{t('your_plan_is_limited_to_n_editors')}</p>}
+        content={
+          <p>
+            {t('your_plan_is_limited_to_n_editors', {
+              count: features.collaborators,
+            })}{' '}
+            {t('from_enforcement_date', {
+              enforcementDate: linkSharingEnforcementDate,
+            })}
+          </p>
+        }
         action={
           <div className="upgrade-actions">
             <Button

+ 9 - 1
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/collaborators-limit-upgrade.tsx

@@ -2,9 +2,11 @@ import { Button } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
 import Notification from '@/shared/components/notification'
 import { upgradePlan } from '@/main/account-upgrade'
+import { useProjectContext } from '@/shared/context/project-context'
 
 export default function CollaboratorsLimitUpgrade() {
   const { t } = useTranslation()
+  const { features } = useProjectContext()
 
   return (
     <div>
@@ -12,7 +14,13 @@ export default function CollaboratorsLimitUpgrade() {
         type="info"
         customIcon={<div />}
         title={t('upgrade_to_add_more_editors')}
-        content={<p>{t('you_can_only_add_n_people_to_edit_a_project')}</p>}
+        content={
+          <p>
+            {t('you_can_only_add_n_people_to_edit_a_project', {
+              count: features.collaborators,
+            })}
+          </p>
+        }
         action={
           <Button
             bsSize="sm"

+ 4 - 2
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/edit-member.tsx

@@ -11,6 +11,7 @@ import { sendMB } from '@/infrastructure/event-tracking'
 import { Select } from '@/shared/components/select'
 import type { ProjectContextMember } from '@/shared/context/types/project-context'
 import { PermissionsLevel } from '@/features/ide-react/types/permissions'
+import { linkSharingEnforcementDate } from '../../utils/link-sharing'
 
 type PermissionsOption = PermissionsLevel | 'removeAccess'
 
@@ -129,7 +130,7 @@ export default function EditMember({
               {shouldWarnMember() && (
                 <div className="subtitle">
                   {t('will_lose_edit_access_on_date', {
-                    date: '[date]',
+                    date: linkSharingEnforcementDate,
                   })}
                 </div>
               )}
@@ -180,6 +181,7 @@ function SelectPrivilege({
   canAddCollaborators,
 }: SelectPrivilegeProps) {
   const { t } = useTranslation()
+  const { features } = useProjectContext()
 
   const privileges = useMemo(
     (): Privilege[] => [
@@ -195,7 +197,7 @@ function SelectPrivilege({
     return !canAddCollaborators &&
       privilege === 'readAndWrite' &&
       value !== 'readAndWrite'
-      ? t('limited_to_n_editors_per_project')
+      ? t('limited_to_n_editors_per_project', { count: features.collaborators })
       : ''
   }
 

+ 44 - 0
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal-content.tsx

@@ -0,0 +1,44 @@
+import { Button, Modal } from 'react-bootstrap'
+import { useTranslation } from 'react-i18next'
+import { linkSharingEnforcementDate } from '../../utils/link-sharing'
+
+type EditorOverLimitModalContentProps = {
+  handleHide: () => void
+}
+
+export default function EditorOverLimitModalContent({
+  handleHide,
+}: EditorOverLimitModalContentProps) {
+  const { t } = useTranslation()
+
+  return (
+    <>
+      <Modal.Header closeButton>
+        <Modal.Title>{t('do_you_need_edit_access')}</Modal.Title>
+      </Modal.Header>
+
+      <Modal.Body>
+        <p>
+          {t('this_project_has_more_than_max_collabs', {
+            linkSharingDate: linkSharingEnforcementDate,
+          })}
+        </p>
+        <p>{t('to_keep_edit_access')}</p>
+      </Modal.Body>
+      <Modal.Footer>
+        <Button
+          bsStyle={null}
+          className="btn-secondary"
+          href="/blog/changes-to-project-sharing"
+          target="_blank"
+          rel="noreferrer"
+        >
+          {t('learn_more')}
+        </Button>
+        <Button className="btn-primary" onClick={handleHide}>
+          {t('ok')}
+        </Button>
+      </Modal.Footer>
+    </>
+  )
+}

+ 65 - 0
services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal.tsx

@@ -0,0 +1,65 @@
+import { useEffect, useState } from 'react'
+import AccessibleModal from '@/shared/components/accessible-modal'
+import EditorOverLimitModalContent from './editor-over-limit-modal-content'
+import customLocalStorage from '@/infrastructure/local-storage'
+import { useProjectContext } from '@/shared/context/project-context'
+import { useEditorContext } from '@/shared/context/editor-context'
+
+const EditorOverLimitModal = () => {
+  const [show, setShow] = useState(false)
+
+  const { isProjectOwner, permissionsLevel } = useEditorContext()
+  const { members, features, _id: projectId } = useProjectContext()
+
+  const handleHide = () => {
+    setShow(false)
+  }
+
+  // split test: link-sharing-warning
+  // show the over-limit warning if user
+  // is editor on a project over
+  // collaborator limit (once every 24 hours)
+  useEffect(() => {
+    const showModalCooldownHours = 24
+    const hasExceededCollaboratorLimit = () => {
+      if (isProjectOwner || !features || permissionsLevel === 'readOnly') {
+        return false
+      }
+
+      if (features.collaborators === -1) {
+        return false
+      }
+      return (
+        members.filter(member => member.privileges === 'readAndWrite').length >
+        (features.collaborators ?? 1)
+      )
+    }
+
+    if (hasExceededCollaboratorLimit()) {
+      const localStorageKey = `last-shown-need-edit-modal.${projectId}`
+      const lastShownNeedEditModalTime =
+        customLocalStorage.getItem(localStorageKey)
+      if (
+        !lastShownNeedEditModalTime ||
+        lastShownNeedEditModalTime + showModalCooldownHours * 60 * 60 * 1000 <
+          Date.now()
+      ) {
+        setShow(true)
+        customLocalStorage.setItem(localStorageKey, Date.now())
+      }
+    }
+  }, [features, isProjectOwner, members, permissionsLevel, projectId])
+
+  return show ? (
+    <AccessibleModal
+      animation
+      show={show}
+      onHide={handleHide}
+      id="editor-over-limit-modal"
+    >
+      <EditorOverLimitModalContent handleHide={handleHide} />
+    </AccessibleModal>
+  ) : null
+}
+
+export default EditorOverLimitModal

+ 6 - 0
services/web/frontend/js/features/share-project-modal/utils/link-sharing.ts

@@ -0,0 +1,6 @@
+import { formatDate } from '@/utils/dates'
+
+export const linkSharingEnforcementDate = formatDate(
+  new Date(2024, 6, 29),
+  'MMMM Do'
+) // July 29th 2024

+ 10 - 3
services/web/locales/en.json

@@ -435,6 +435,7 @@
   "display_deleted_user": "Display deleted users",
   "do_not_have_acct_or_do_not_want_to_link": "If you don’t have an <b>__appName__</b> account, or if you don’t want to link to your <b>__institutionName__</b> account, please click <b>__clickText__</b>.",
   "do_not_link_accounts": "Don’t link accounts",
+  "do_you_need_edit_access": "Do you need edit access?",
   "do_you_want_to_change_your_primary_email_address_to": "Do you want to change your primary email address to <b>__email__</b>?",
   "do_you_want_to_overwrite_it": "Do you want to overwrite it?",
   "do_you_want_to_overwrite_it_plural": "Do you want to overwrite them?",
@@ -676,6 +677,7 @@
   "free_plan_tooltip": "Click to find out how you could benefit from Overleaf premium features.",
   "frequently_asked_questions": "frequently asked questions",
   "from_another_project": "From another project",
+  "from_enforcement_date": "From __enforcementDate__ any additional editors on this project will be made viewers.",
   "from_external_url": "From external URL",
   "from_project_files": "From project files",
   "from_provider": "From __provider__",
@@ -1040,7 +1042,8 @@
   "license": "License",
   "license_for_educational_purposes": "This license is for educational purposes (applies to students or faculty using __appName__ for teaching)",
   "limited_offer": "Limited offer",
-  "limited_to_n_editors_per_project": "Limited to n editors 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",
   "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.",
   "link": "Link",
@@ -1987,6 +1990,7 @@
   "this_is_a_labs_experiment": "This is a Labs experiment",
   "this_is_your_template": "This is your template from your project",
   "this_project_exceeded_compile_timeout_limit_on_free_plan": "This project exceeded the compile timeout limit on our free plan.",
+  "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_read_only": "This project is public and can be viewed but not edited by anyone with the URL",
   "this_project_will_appear_in_your_dropbox_folder_at": "This project will appear in your Dropbox folder at ",
@@ -2009,6 +2013,7 @@
   "to_fix_this_you_can": "To fix this, you can:",
   "to_fix_this_you_can_ask_the_github_repository_owner": "To fix this, you can ask the GitHub repository owner (<0>__repoOwnerEmail__</0>) to renew their __appName__ subscription and reconnect the project.",
   "to_insert_or_move_a_caption_make_sure_tabular_is_directly_within_table": "To insert or move a caption, make sure \\begin{tabular} is directly within a table environment",
+  "to_keep_edit_access": "To keep edit access, ask the project owner to upgrade their plan or reduce the number of people with edit access.",
   "to_many_login_requests_2_mins": "This account has had too many login requests. Please wait 2 minutes before trying to log in again",
   "to_modify_your_subscription_go_to": "To modify your subscription go to",
   "to_use_text_wrapping_in_your_table_make_sure_you_include_the_array_package": "<0>Please note:</0> To use text wrapping in your table, make sure you include the <1>array</1> package in your document preamble:",
@@ -2297,7 +2302,8 @@
   "you_can_also_choose_to_view_anonymously_or_leave_the_project": "You can also choose to <0>view anonymously</0> (you will lose edit access) or <1>leave the project</1>.",
   "you_can_now_enable_sso": "You can now enable SSO on your Group settings page.",
   "you_can_now_log_in_sso": "You can now log in through your institution and if eligible you will receive <0>__appName__ Professional features</0>.",
-  "you_can_only_add_n_people_to_edit_a_project": "You can only add X 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": "You can only add __count__ person 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_request_a_maximum_of_limit_fixes_per_day": "You can request a maximum of __limit__ fixes per day. Please try again tomorrow.",
   "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>.",
@@ -2339,7 +2345,8 @@
   "your_password_was_detected": "Your password is on a <0>public list of known compromised passwords</0>. Keep your account safe by changing your password now.",
   "your_plan": "Your plan",
   "your_plan_is_changing_at_term_end": "Your plan is changing to <0>__pendingPlanName__</0> at the end of the current billing period.",
-  "your_plan_is_limited_to_n_editors": "Your plan allows [n] collaborators with edit access and unlimited viewers. From [date] any additional editors on this project will be made 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_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on 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",