From b2d40af9cde4f442deb92ec75278b34d12f3d190 Mon Sep 17 00:00:00 2001 From: Truman Gao <106889354+tdgao@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:56:32 -0700 Subject: [PATCH] feat: confirm transfer project/org modals (#5532) * feat: implement confirm transfer project/org modals * pnpm prepr * update warning banner copy * update warning banner again --- .../components/ui/ConfirmTransferOrgModal.vue | 113 ++++++++++++++++ .../ui/ConfirmTransferProjectModal.vue | 125 ++++++++++++++++++ .../pages/[type]/[id]/settings/members.vue | 51 ++++++- .../organization/[id]/settings/members.vue | 30 ++++- apps/labrinth/src/routes/v3/teams.rs | 2 +- 5 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 apps/frontend/src/components/ui/ConfirmTransferOrgModal.vue create mode 100644 apps/frontend/src/components/ui/ConfirmTransferProjectModal.vue diff --git a/apps/frontend/src/components/ui/ConfirmTransferOrgModal.vue b/apps/frontend/src/components/ui/ConfirmTransferOrgModal.vue new file mode 100644 index 000000000..e4f62b43f --- /dev/null +++ b/apps/frontend/src/components/ui/ConfirmTransferOrgModal.vue @@ -0,0 +1,113 @@ + + + + + Transfer + + {{ organization.name }} + + + + + Do not transfer organizations to buyers. This is a common scam and against our TOS. If you + encounter a buyer, please + contact support. + + + + + + {{ currentOwner.username }} + {{ currentOwner.role }} + + + + + + + {{ transferTo.username }} + {{ transferTo.role }} + + + + + You will immediately lose owner access to this organization + The new owner can modify or delete the organization and all its projects + This action cannot be undone + + + + To confirm this transfer, type + {{ organization.name }} below + + + + + + + + + + Cancel + + + + + + Transfer ownership + + + + + + + + diff --git a/apps/frontend/src/components/ui/ConfirmTransferProjectModal.vue b/apps/frontend/src/components/ui/ConfirmTransferProjectModal.vue new file mode 100644 index 000000000..d47a619fa --- /dev/null +++ b/apps/frontend/src/components/ui/ConfirmTransferProjectModal.vue @@ -0,0 +1,125 @@ + + + + + Transfer + + {{ project.name }} + + + + + Do not transfer projects to buyers. This is a common scam and against our TOS. If you + encounter a buyer, please + contact support. + + + + + + {{ currentOwner.username }} + {{ currentOwner.role }} + + + + + + + + {{ transferTo.username || transferTo.name }} + + {{ transferTo.role || 'Member' }} + + + + + You will immediately lose owner access to this project + The new owner can modify or delete the project at any time + This action cannot be undone + + + + To confirm this transfer, type + {{ project.name }} below + + + + + + + + + + Cancel + + + + + + Transfer ownership + + + + + + + + diff --git a/apps/frontend/src/pages/[type]/[id]/settings/members.vue b/apps/frontend/src/pages/[type]/[id]/settings/members.vue index b22677586..e7df5b7bc 100644 --- a/apps/frontend/src/pages/[type]/[id]/settings/members.vue +++ b/apps/frontend/src/pages/[type]/[id]/settings/members.vue @@ -1,5 +1,17 @@ + Transfer ownership @@ -298,7 +310,11 @@ :options="organizations || []" :disabled="!currentMember?.is_owner || organizations?.length === 0" /> - + Transfer management @@ -553,6 +569,7 @@ import { } from '@modrinth/ui' import { Multiselect } from 'vue-multiselect' +import ConfirmTransferProjectModal from '~/components/ui/ConfirmTransferProjectModal.vue' import { removeSelfFromTeam } from '~/helpers/teams.js' const { addNotification } = injectNotificationManager() @@ -604,6 +621,8 @@ initMembers() const currentUsername = ref('') const openTeamMembers = ref([]) const selectedOrganization = ref(null) +const transferData = ref(null) +const transferModal = ref(null) const { data: organizations } = useAsyncData('organizations', () => { return useBaseFetch('user/' + auth.value?.user.id + '/organizations', { @@ -758,6 +777,34 @@ const updateTeamMember = async (index) => { stopLoading() } +const openTransferModal = (index, e) => { + transferData.value = { + target: { + avatar_url: allTeamMembers.value[index]?.avatar_url, + username: allTeamMembers.value[index]?.user?.username, + role: allTeamMembers.value[index]?.role || 'Member', + }, + onConfirm: () => transferOwnership(index), + } + nextTick(() => { + transferModal.value?.show(e) + }) +} + +const openTransferToOrgModal = (e) => { + if (!selectedOrganization.value) return + transferData.value = { + target: { + avatar_url: selectedOrganization.value.icon_url, + name: selectedOrganization.value.name, + }, + onConfirm: () => onAddToOrg(), + } + nextTick(() => { + transferModal.value?.show(e) + }) +} + const transferOwnership = async (index) => { startLoading() diff --git a/apps/frontend/src/pages/organization/[id]/settings/members.vue b/apps/frontend/src/pages/organization/[id]/settings/members.vue index e387dfa30..cabd6ccc2 100644 --- a/apps/frontend/src/pages/organization/[id]/settings/members.vue +++ b/apps/frontend/src/pages/organization/[id]/settings/members.vue @@ -1,5 +1,21 @@ + @@ -205,7 +221,7 @@ onTransferOwnership(organization.team_id, member.user.id)" + @click="(e) => openTransferModal(member, e)" > Transfer ownership @@ -233,8 +249,9 @@ import { injectNotificationManager, StyledInput, } from '@modrinth/ui' -import { ref } from 'vue' +import { nextTick, ref } from 'vue' +import ConfirmTransferOrgModal from '~/components/ui/ConfirmTransferOrgModal.vue' import { removeTeamMember } from '~/helpers/teams.js' import { injectOrganizationContext } from '~/providers/organization-context.ts' import { isPermission } from '~/utils/permissions.ts' @@ -246,6 +263,8 @@ const auth = await useAuth() const currentUsername = ref('') const openTeamMembers = ref([]) +const transferTargetMember = ref(null) +const transferModal = ref(null) const allTeamMembers = ref(organization.value.members) @@ -344,6 +363,13 @@ const onUpdateTeamMember = useClientTry(async (teamId, member) => { }) }) +const openTransferModal = (member, e) => { + transferTargetMember.value = member + nextTick(() => { + transferModal.value?.show(e) + }) +} + const onTransferOwnership = useClientTry(async (teamId, uid) => { const data = { user_id: uid, diff --git a/apps/labrinth/src/routes/v3/teams.rs b/apps/labrinth/src/routes/v3/teams.rs index 1b5d7e796..aaba5d2f9 100644 --- a/apps/labrinth/src/routes/v3/teams.rs +++ b/apps/labrinth/src/routes/v3/teams.rs @@ -904,7 +904,7 @@ pub async fn transfer_ownership( && project_item.inner.organization_id.is_some() { return Err(ApiError::InvalidInput( - "You cannot transfer ownership of a project team that is owend by an organization" + "You cannot transfer ownership of a project team that is owned by an organization" .to_string(), )); }
+ To confirm this transfer, type + {{ organization.name }} below +
+ To confirm this transfer, type + {{ project.name }} below +