From dea5ff5dd47065fedffeac0bc85eac0065a8e459 Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:50:43 +0100 Subject: [PATCH] Merge pull request #13604 from overleaf/jk-managed-users-offboarding-ui [web] Managed Users offboarding UI GitOrigin-RevId: ee4a1ae7cdb0022839ef232836ef6933443400fc --- .../Collaborators/OwnershipTransferHandler.js | 6 +- .../app/src/Features/Email/EmailBuilder.js | 24 +++ .../web/app/src/Features/Tags/TagsHandler.js | 12 +- .../web/frontend/extracted-translations.json | 12 ++ .../components/group-members.tsx | 1 + .../managed-user-dropdown-button.tsx | 55 +++--- .../managed-users/managed-user-row.tsx | 11 +- .../managed-users/managed-users-list.tsx | 125 +++++++------ .../offboard-managed-user-modal.tsx | 164 ++++++++++++++++++ .../stylesheets/components/group-members.less | 7 +- services/web/locales/en.json | 12 ++ .../managed-user-dropdown-button.spec.tsx | 15 +- .../managed-users/managed-user-row.spec.tsx | 6 + .../managed-users/managed-users-list.spec.tsx | 3 + .../offboard-managed-user-modal.spec.tsx | 107 ++++++++++++ .../OwnershipTransferHandlerTests.js | 9 + .../test/unit/src/Tags/TagsHandlerTests.js | 23 +++ 17 files changed, 508 insertions(+), 84 deletions(-) create mode 100644 services/web/frontend/js/features/group-management/components/managed-users/offboard-managed-user-modal.tsx create mode 100644 services/web/test/frontend/features/group-management/components/managed-users/offboard-managed-user-modal.spec.tsx diff --git a/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js b/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js index b9ab413b56..e440b4288c 100644 --- a/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js +++ b/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js @@ -15,7 +15,7 @@ module.exports = { } async function transferOwnership(projectId, newOwnerId, options = {}) { - const { allowTransferToNonCollaborators, sessionUserId } = options + const { allowTransferToNonCollaborators, sessionUserId, skipEmails } = options // Fetch project and user const [project, newOwner] = await Promise.all([ @@ -58,7 +58,9 @@ async function transferOwnership(projectId, newOwnerId, options = {}) { // Send confirmation emails const previousOwner = await UserGetter.promises.getUser(previousOwnerId) - await _sendEmails(project, previousOwner, newOwner) + if (!skipEmails) { + await _sendEmails(project, previousOwner, newOwner) + } } async function _getProject(projectId) { diff --git a/services/web/app/src/Features/Email/EmailBuilder.js b/services/web/app/src/Features/Email/EmailBuilder.js index 40abeeb1a7..6b314246fc 100644 --- a/services/web/app/src/Features/Email/EmailBuilder.js +++ b/services/web/app/src/Features/Email/EmailBuilder.js @@ -498,6 +498,30 @@ templates.userOnboardingEmail = NoCTAEmailTemplate({ }, }) +templates.managedUserOffboarded = ctaTemplate({ + subject() { + return `Your account has been deleted - ${settings.appName}` + }, + title() { + return `Your account has been deleted` + }, + message() { + return [ + 'Your group administrator has deleted your account. Contact your administrator to learn more about it.', + 'You can create a new account if you want to use a different email address.', + ] + }, + secondaryMessage() { + return [] + }, + ctaText() { + return 'Create a new account' + }, + ctaURL() { + return `${settings.siteUrl}/register` + }, +}) + templates.securityAlert = NoCTAEmailTemplate({ subject(opts) { return `Overleaf security note: ${opts.action}` diff --git a/services/web/app/src/Features/Tags/TagsHandler.js b/services/web/app/src/Features/Tags/TagsHandler.js index 5c82281b85..e8e4ffc65c 100644 --- a/services/web/app/src/Features/Tags/TagsHandler.js +++ b/services/web/app/src/Features/Tags/TagsHandler.js @@ -7,12 +7,20 @@ function getAllTags(userId, callback) { Tag.find({ user_id: userId }, callback) } -function createTag(userId, name, color, callback) { +function createTag(userId, name, color, options, callback) { + if (typeof options === 'function') { + callback = options + options = {} + } if (!callback) { callback = function () {} } if (name.length > MAX_TAG_LENGTH) { - return callback(new Error('Exceeded max tag length')) + if (options.truncate) { + name = name.slice(0, MAX_TAG_LENGTH) + } else { + return callback(new Error('Exceeded max tag length')) + } } Tag.create({ user_id: userId, name, color }, function (err, tag) { // on duplicate key error return existing tag diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 515de386bf..cd86fb00cb 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -10,6 +10,7 @@ "about_to_delete_tag": "", "about_to_delete_the_following_project": "", "about_to_delete_the_following_projects": "", + "about_to_delete_user_preamble": "", "about_to_enable_managed_users": "", "about_to_leave_projects": "", "about_to_trash_projects": "", @@ -53,6 +54,7 @@ "all_premium_features": "", "all_premium_features_including": "", "all_projects": "", + "all_projects_will_be_transferred_immediately": "", "also": "", "an_error_occurred_when_verifying_the_coupon_code": "", "anonymous": "", @@ -132,6 +134,7 @@ "checking_dropbox_status": "", "checking_project_github_status": "", "choose_a_custom_color": "", + "choose_from_group_members": "", "clear_cached_files": "", "clear_search": "", "click_here_to_view_sl_in_lng": "", @@ -163,6 +166,7 @@ "confirm": "", "confirm_affiliation": "", "confirm_affiliation_to_relink_dropbox": "", + "confirm_delete_user_type_email_address": "", "confirm_new_password": "", "confirm_primary_email_change": "", "conflicting_paths_found": "", @@ -537,6 +541,7 @@ "layout_processing": "", "learn_more": "", "learn_more_about_link_sharing": "", + "learn_more_about_managed_users": "", "leave": "", "leave_any_group_subscriptions": "", "leave_group": "", @@ -887,6 +892,7 @@ "see_changes_in_your_documents_live": "", "select_a_file": "", "select_a_file_figure_modal": "", + "select_a_new_owner_for_projects": "", "select_a_payment_method": "", "select_a_project": "", "select_a_project_figure_modal": "", @@ -1006,9 +1012,12 @@ "thanks_settings_updated": "", "the_following_files_already_exist_in_this_project": "", "the_width_you_choose_here_is_based_on_the_width_of_the_text_in_your_document": "", + "their_projects_will_be_transferred_to_another_user": "", "then_x_price_per_month": "", "then_x_price_per_year": "", "there_are_lots_of_options_to_edit_and_customize_your_figures": "", + "they_lose_access_to_account": "", + "this_action_cannot_be_reversed": "", "this_action_cannot_be_undone": "", "this_address_will_be_shown_on_the_invoice": "", "this_field_is_required": "", @@ -1070,6 +1079,8 @@ "transfer_management_of_your_account": "", "transfer_management_of_your_account_to_x": "", "transfer_management_resolve_following_issues": "", + "transfer_this_users_projects": "", + "transfer_this_users_projects_description": "", "transferring": "", "trash": "", "trash_projects": "", @@ -1187,6 +1198,7 @@ "you_have_added_x_of_group_size_y": "", "you_have_been_invited_to_transfer_management_of_your_account": "", "you_have_been_invited_to_transfer_management_of_your_account_to": "", + "you_will_be_able_to_reassign_subscription": "", "your_affiliation_is_confirmed": "", "your_browser_does_not_support_this_feature": "", "your_git_access_info": "", diff --git a/services/web/frontend/js/features/group-management/components/group-members.tsx b/services/web/frontend/js/features/group-management/components/group-members.tsx index 1c86373bba..681f71b1e3 100644 --- a/services/web/frontend/js/features/group-management/components/group-members.tsx +++ b/services/web/frontend/js/features/group-management/components/group-members.tsx @@ -203,6 +203,7 @@ export default function GroupMembers() { users={users} selectUser={selectUser} unselectUser={unselectUser} + groupId={groupId} /> ) : ( void } export default function ManagedUserDropdownButton({ user, + openOffboardingModalForUser, }: ManagedUserDropdownButtonProps) { const { t } = useTranslation() + + const onDeleteUserClick = () => { + openOffboardingModalForUser(user) + } + return ( - - -