From d16fee1afeaf144576aa68f48d1d44809b144401 Mon Sep 17 00:00:00 2001 From: roo hutton Date: Thu, 27 Jun 2024 12:53:29 +0100 Subject: [PATCH] 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 --- .../web/frontend/extracted-translations.json | 7 ++ .../components/editor-navigation-toolbar.tsx | 13 ++-- .../add-collaborators-upgrade.tsx | 14 +++- .../collaborators-limit-upgrade.tsx | 10 ++- .../restricted-link-sharing/edit-member.tsx | 6 +- .../editor-over-limit-modal-content.tsx | 44 +++++++++++++ .../editor-over-limit-modal.tsx | 65 +++++++++++++++++++ .../share-project-modal/utils/link-sharing.ts | 6 ++ services/web/locales/en.json | 13 +++- 9 files changed, 167 insertions(+), 11 deletions(-) create mode 100644 services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal-content.tsx create mode 100644 services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal.tsx create mode 100644 services/web/frontend/js/features/share-project-modal/utils/link-sharing.ts diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 3769b05e32..6b7411eb2c 100644 --- a/services/web/frontend/extracted-translations.json +++ b/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": "", diff --git a/services/web/frontend/js/features/ide-react/components/editor-navigation-toolbar.tsx b/services/web/frontend/js/features/ide-react/components/editor-navigation-toolbar.tsx index f9441d0d94..cabdf5ae3a 100644 --- a/services/web/frontend/js/features/ide-react/components/editor-navigation-toolbar.tsx +++ b/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 ? ( - + <> + + + ) : ( {t('your_plan_is_limited_to_n_editors')}

} + content={ +

+ {t('your_plan_is_limited_to_n_editors', { + count: features.collaborators, + })}{' '} + {t('from_enforcement_date', { + enforcementDate: linkSharingEnforcementDate, + })} +

+ } action={
)} @@ -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 }) : '' } diff --git a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal-content.tsx b/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal-content.tsx new file mode 100644 index 0000000000..6b36f8538e --- /dev/null +++ b/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 ( + <> + + {t('do_you_need_edit_access')} + + + +

+ {t('this_project_has_more_than_max_collabs', { + linkSharingDate: linkSharingEnforcementDate, + })} +

+

{t('to_keep_edit_access')}

+
+ + + + + + ) +} diff --git a/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal.tsx b/services/web/frontend/js/features/share-project-modal/components/restricted-link-sharing/editor-over-limit-modal.tsx new file mode 100644 index 0000000000..c76f2ed290 --- /dev/null +++ b/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 ? ( + + + + ) : null +} + +export default EditorOverLimitModal diff --git a/services/web/frontend/js/features/share-project-modal/utils/link-sharing.ts b/services/web/frontend/js/features/share-project-modal/utils/link-sharing.ts new file mode 100644 index 0000000000..62368a24ff --- /dev/null +++ b/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 diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 27f5ae8a0e..6e51592850 100644 --- a/services/web/locales/en.json +++ b/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 __appName__ account, or if you don’t want to link to your __institutionName__ account, please click __clickText__.", "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 __email__?", "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__) 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: To use text wrapping in your table, make sure you include the <1>array 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 (you will lose edit access) or <1>leave the project.", "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.", - "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 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).", @@ -2339,7 +2345,8 @@ "your_password_was_detected": "Your password is on a <0>public list of known compromised passwords. 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__ 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",