diff --git a/packages/hoppscotch-sh-admin/locales/en.json b/packages/hoppscotch-sh-admin/locales/en.json index ad3210122..0427a80cb 100644 --- a/packages/hoppscotch-sh-admin/locales/en.json +++ b/packages/hoppscotch-sh-admin/locales/en.json @@ -168,7 +168,9 @@ "remove_admin_from_users_failure": "Failed to remove admin status from selected users!!", "remove_admin_from_users_success": "Admin status removed from selected users!!", "remove_admin_to_delete_user": "Remove admin privilege to delete the user!!", + "remove_owner_to_delete_user": "Remove team ownership status to delete the user!!", "remove_admin_for_deletion": "Remove admin status before attempting deletion!!", + "remove_owner_for_deletion": "One or more users are team owners. Update ownership before deletion!!", "remove_invitee_failure": "Removal of invitee failed!!", "remove_invitee_success": "Removal of invitee is successfull!!", "remove_member_failure": "Member couldn't be removed!!", diff --git a/packages/hoppscotch-sh-admin/src/helpers/errors.ts b/packages/hoppscotch-sh-admin/src/helpers/errors.ts index cd45cea93..d1ba9bdfe 100644 --- a/packages/hoppscotch-sh-admin/src/helpers/errors.ts +++ b/packages/hoppscotch-sh-admin/src/helpers/errors.ts @@ -17,3 +17,6 @@ export const ADMIN_CANNOT_BE_DELETED = // When trying to invite a user that is already invited export const USER_ALREADY_INVITED = '[GraphQL] admin/user_already_invited' as const; + +// When attempting to delete a user who is an owner of a team +export const USER_IS_OWNER = 'user/is_owner' as const; diff --git a/packages/hoppscotch-sh-admin/src/pages/users/_id.vue b/packages/hoppscotch-sh-admin/src/pages/users/_id.vue index d01c2c6f6..a4f68ce18 100644 --- a/packages/hoppscotch-sh-admin/src/pages/users/_id.vue +++ b/packages/hoppscotch-sh-admin/src/pages/users/_id.vue @@ -73,7 +73,7 @@ import { RemoveUsersByAdminDocument, UserInfoDocument, } from '~/helpers/backend/graphql'; -import { ADMIN_CANNOT_BE_DELETED } from '~/helpers/errors'; +import { ADMIN_CANNOT_BE_DELETED, USER_IS_OWNER } from '~/helpers/errors'; const t = useI18n(); const toast = useToast(); @@ -210,13 +210,35 @@ const deleteUserMutation = async (id: string | null) => { } else { const deletedUsers = result.data?.removeUsersByAdmin || []; - const isAdminError = deletedUsers.some( - (user) => user.errorMessage === ADMIN_CANNOT_BE_DELETED - ); + const uniqueErrorMessages = new Set( + deletedUsers.map(({ errorMessage }) => errorMessage).filter(Boolean) + ) as Set; - isAdminError - ? toast.error(t('state.delete_user_failed_only_one_admin')) - : toast.success(t('state.delete_user_success')); + if (uniqueErrorMessages.size > 0) { + const errMsgMap = { + [ADMIN_CANNOT_BE_DELETED]: t('state.remove_admin_to_delete_user'), + [USER_IS_OWNER]: t('state.remove_owner_to_delete_user'), + }; + + const errMsgMapKeys = Object.keys(errMsgMap); + + uniqueErrorMessages.forEach((errorMessage) => { + if (errMsgMapKeys.includes(errorMessage)) { + toast.error(errMsgMap[errorMessage as keyof typeof errMsgMap]); + } + }); + + // Fallback for the case where the error message is not in the compiled list + if ( + Array.from(uniqueErrorMessages).some( + (key) => !((key as string) in errMsgMap) + ) + ) { + toast.error(t('state.delete_user_failure')); + } + } else { + toast.success(t('state.delete_user_success')); + } } confirmDeletion.value = false; deleteUserUID.value = null; diff --git a/packages/hoppscotch-sh-admin/src/pages/users/index.vue b/packages/hoppscotch-sh-admin/src/pages/users/index.vue index b8b7e66c3..ac3f0a12c 100644 --- a/packages/hoppscotch-sh-admin/src/pages/users/index.vue +++ b/packages/hoppscotch-sh-admin/src/pages/users/index.vue @@ -210,7 +210,7 @@ (null); const usersToAdmin = useMutation(MakeUsersAdminDocument); -const AreMultipleUsersSelected = computed(() => selectedRows.value.length > 1); +const areMultipleUsersSelected = computed(() => selectedRows.value.length > 1); const confirmUserToAdmin = (id: string | null) => { confirmUsersToAdmin.value = true; @@ -482,11 +482,15 @@ const makeUsersToAdmin = async (id: string | null) => { if (result.error) { toast.error( - id ? t('state.admin_failure') : t('state.users_to_admin_failure') + areMultipleUsersSelected.value + ? t('state.users_to_admin_failure') + : t('state.admin_failure') ); } else { toast.success( - id ? t('state.admin_success') : t('state.users_to_admin_success') + areMultipleUsersSelected.value + ? t('state.users_to_admin_success') + : t('state.admin_success') ); usersList.value = usersList.value.map((user) => ({ ...user, @@ -514,7 +518,7 @@ const resetConfirmAdminToUser = () => { adminsToUserUID.value = null; }; -const AreMultipleUsersSelectedToAdmin = computed( +const areMultipleUsersSelectedToAdmin = computed( () => selectedRows.value.length > 1 ); @@ -525,15 +529,15 @@ const makeAdminsToUsers = async (id: string | null) => { const result = await adminsToUser.executeMutation(variables); if (result.error) { toast.error( - id - ? t('state.remove_admin_failure') - : t('state.remove_admin_from_users_failure') + areMultipleUsersSelected.value + ? t('state.remove_admin_from_users_failure') + : t('state.remove_admin_failure') ); } else { toast.success( - id - ? t('state.remove_admin_success') - : t('state.remove_admin_from_users_success') + areMultipleUsersSelected.value + ? t('state.remove_admin_from_users_success') + : t('state.remove_admin_success') ); usersList.value = usersList.value.map((user) => ({ ...user, @@ -562,7 +566,7 @@ const resetConfirmUserDeletion = () => { deleteUserUID.value = null; }; -const AreMultipleUsersSelectedForDeletion = computed( +const areMultipleUsersSelectedForDeletion = computed( () => selectedRows.value.length > 1 ); @@ -572,12 +576,9 @@ const deleteUsers = async (id: string | null) => { const result = await usersDeletion.executeMutation(variables); if (result.error) { - const errorMessage = - result.error.message === DELETE_USER_FAILED_ONLY_ONE_ADMIN - ? t('state.delete_user_failed_only_one_admin') - : id - ? t('state.delete_user_failure') - : t('state.delete_users_failure'); + const errorMessage = areMultipleUsersSelected.value + ? t('state.delete_users_failure') + : t('state.delete_user_failure'); toast.error(errorMessage); } else { const deletedUsers = result.data?.removeUsersByAdmin || []; @@ -585,32 +586,63 @@ const deleteUsers = async (id: string | null) => { .filter((user) => user.isDeleted) .map((user) => user.userUID); - const isAdminError = deletedUsers.some( - (user) => user.errorMessage === ADMIN_CANNOT_BE_DELETED - ); + const uniqueErrorMessages = new Set( + deletedUsers.map(({ errorMessage }) => errorMessage).filter(Boolean) + ) as Set; + + if (uniqueErrorMessages.size > 0) { + const errMsgMap = { + [ADMIN_CANNOT_BE_DELETED]: t('state.remove_admin_for_deletion'), + [USER_IS_OWNER]: t('state.remove_owner_for_deletion'), + }; + + const errMsgMapKeys = Object.keys(errMsgMap); + + // Show toast messages with the count of users deleted only if multiple users are selected + if (areMultipleUsersSelected.value) { + toast.success( + t('state.delete_some_users_success', { count: deletedIDs.length }) + ); + toast.error( + t('state.delete_some_users_failure', { + count: deletedUsers.length - deletedIDs.length, + }) + ); + } + + uniqueErrorMessages.forEach((errorMessage) => { + if (errMsgMapKeys.includes(errorMessage)) { + toastTimeout = setTimeout( + () => { + toast.error(errMsgMap[errorMessage as keyof typeof errMsgMap]); + }, + areMultipleUsersSelected.value ? 2000 : 0 + ); + } + }); + + // Fallback for the case where the error message is not in the compiled list + if ( + Array.from(uniqueErrorMessages).some( + (key) => !((key as string) in errMsgMap) + ) + ) { + areMultipleUsersSelected.value + ? t('state.delete_users_failure') + : t('state.delete_user_failure'); + } + } else { + toast.success( + areMultipleUsersSelected.value + ? t('state.delete_users_success') + : t('state.delete_user_success') + ); + } usersList.value = usersList.value.filter( (user) => !deletedIDs.includes(user.uid) ); - if (isAdminError) { - toast.success( - t('state.delete_some_users_success', { count: deletedIDs.length }) - ); - toast.error( - t('state.delete_some_users_failure', { - count: deletedUsers.length - deletedIDs.length, - }) - ); - toastTimeout = setTimeout(() => { - toast.error(t('state.remove_admin_for_deletion')); - }, 2000); - } else { - toast.success( - id ? t('state.delete_user_success') : t('state.delete_users_success') - ); - } - selectedRows.value.splice(0, selectedRows.value.length); } confirmUsersDeletion.value = false;