feat: block action on user pages + shared instance flows

This commit is contained in:
Calum H. (IMB11)
2026-07-28 16:08:25 +01:00
parent 90bfe88dac
commit a8b41382ff
8 changed files with 286 additions and 12 deletions
@@ -120,8 +120,17 @@
:max-height="240"
/>
</div>
<div v-if="reportOnly" class="flex flex-col gap-2">
<Checkbox v-model="deleteInstance" :label="formatMessage(messages.deleteInstance)" />
<div v-if="reportOnly || blockTargetUserId" class="flex flex-col gap-2">
<Checkbox
v-if="reportOnly"
v-model="deleteInstance"
:label="formatMessage(messages.deleteInstance)"
/>
<Checkbox
v-if="blockTargetUserId"
v-model="blockUser"
:label="formatMessage(messages.blockUser)"
/>
</div>
</div>
</Transition>
@@ -249,12 +258,14 @@ import {
Admonition,
AutoLink,
Avatar,
blockedUsersQueryKey,
ButtonStyled,
Checkbox,
Combobox,
type ComboboxOption,
commonMessages,
defineMessages,
injectAuth,
injectModrinthClient,
injectNotificationManager,
IntlFormatted,
@@ -266,6 +277,7 @@ import {
useScrollIndicator,
useVIntl,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, nextTick, ref } from 'vue'
@@ -274,6 +286,7 @@ import { hide_ads_window, show_ads_window } from '@/helpers/ads'
import { toError } from '@/helpers/errors'
import type { SharedInstanceInstallPreview } from '@/helpers/install'
import { create_report } from '@/helpers/reports'
import { block_user } from '@/helpers/users'
import SharedInstanceInstallSummary from './shared-instance-install-summary.vue'
import { useSharedInstancePreviewContent } from './use-shared-instance-preview-content'
@@ -284,6 +297,7 @@ type ExternalFileRow = {
name: string
}
type SharedInstanceCreator = {
id: string | null
username: string
avatarUrl: string | null
}
@@ -300,13 +314,17 @@ type ReportReason = 'malicious' | 'inappropriate' | 'spam'
const reportReason = ref<ReportReason>('malicious')
const additionalContext = ref('')
const deleteInstance = ref(true)
const blockUser = ref(true)
const blockTargetUserId = ref<string | null>(null)
const submitLoading = ref(false)
const uploadedImageIDs = ref<string[]>([])
const emit = defineEmits<{
reported: [deleteInstance: boolean]
}>()
const { formatMessage } = useVIntl()
const auth = injectAuth()
const client = injectModrinthClient()
const queryClient = useQueryClient()
const { addNotification, handleError } = injectNotificationManager()
const { load } = useSharedInstancePreviewContent()
const {
@@ -365,13 +383,41 @@ async function submitReport() {
submitLoading.value = true
try {
const uploadedImages = uploadedImageIDs.value.slice(-10)
await create_report({
report_type: reportReason.value,
item_type: 'shared-instance',
item_id: `${reportPreview.sharedInstanceId}/${reportPreview.version}`,
body,
uploaded_images: uploadedImages,
})
const blockTarget = blockUser.value ? blockTargetUserId.value : null
const [reportResult, blockResult] = await Promise.allSettled([
create_report({
report_type: reportReason.value,
item_type: 'shared-instance',
item_id: `${reportPreview.sharedInstanceId}/${reportPreview.version}`,
body,
uploaded_images: uploadedImages,
}),
blockTarget ? block_user(blockTarget) : Promise.resolve(),
])
if (blockTarget) {
if (blockResult.status === 'fulfilled') {
blockUser.value = false
const authUserId = auth.user.value?.id
if (authUserId) {
queryClient.setQueryData<Labrinth.BlockedUsers.v3.BlockedUserId[]>(
blockedUsersQueryKey(authUserId),
(blockedUsers = []) =>
blockedUsers.includes(blockTarget)
? blockedUsers
: [...blockedUsers, blockTarget],
)
}
addNotification({
type: 'success',
title: formatMessage(messages.userBlocked),
})
} else {
handleError(toError(blockResult.reason))
}
}
if (reportResult.status === 'rejected') throw reportResult.reason
const shouldDeleteInstance = reportOnly.value && deleteInstance.value
hide()
@@ -426,6 +472,8 @@ function resetReportState() {
reportReason.value = 'malicious'
additionalContext.value = ''
deleteInstance.value = true
blockUser.value = true
blockTargetUserId.value = null
submitLoading.value = false
uploadedImageIDs.value = []
}
@@ -437,12 +485,18 @@ function show(
) {
resetReportState()
creator.value = creatorValue ?? null
blockTargetUserId.value = creatorValue?.id ?? null
install.value = installValue
showPreview(previewValue, event)
}
function showReport(previewValue: SharedInstanceInstallPreview, event?: MouseEvent) {
function showReport(
previewValue: SharedInstanceInstallPreview,
blockTargetUserIdValue?: string | null,
event?: MouseEvent,
) {
resetReportState()
creator.value = null
blockTargetUserId.value = blockTargetUserIdValue ?? null
reportMode.value = true
reportOnly.value = true
install.value = () => {}
@@ -537,6 +591,14 @@ const messages = defineMessages({
id: 'app.modal.install-to-play.delete-instance',
defaultMessage: 'Delete instance',
},
blockUser: {
id: 'app.modal.install-to-play.block-user',
defaultMessage: 'Block user',
},
userBlocked: {
id: 'app.modal.install-to-play.user-blocked',
defaultMessage: 'User blocked',
},
unknownFilesWarning: {
id: 'app.modal.install-to-play.unknown-files-warning',
defaultMessage: 'Unknown files warning',
@@ -32,6 +32,7 @@ type InstallModal = {
}
type SharedInstanceCreator = {
id: string | null
username: string
avatarUrl: string | null
}
@@ -190,6 +191,7 @@ export function useSharedInstanceInviteHandler(
},
invite.invitedByUsername
? {
id: invite.invitedById,
username: invite.invitedByUsername,
avatarUrl: invite.invitedByAvatarUrl,
}
@@ -294,6 +296,7 @@ export function useSharedInstanceInviteHandler(
},
manager
? {
id: manager.id,
username: manager.username,
avatarUrl: manager.avatar_url ?? null,
}
+6
View File
@@ -18,11 +18,14 @@ import { computed, watch } from 'vue'
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
import {
block_user,
get_blocked_users,
get_user_collections,
get_user_organizations,
get_user_profile,
get_user_projects,
patch_user,
unblock_user,
} from '@/helpers/users'
import { useBreadcrumbs } from '@/store/breadcrumbs'
@@ -35,6 +38,9 @@ const userProfile = provideUserProfile({
getOrganizations: get_user_organizations,
getCollections: get_user_collections,
patchUser: patch_user,
getBlockedUsers: get_blocked_users,
blockUser: block_user,
unblockUser: unblock_user,
})
const userId = computed(() => {
@@ -701,7 +701,7 @@ async function reportSharedInstance(event?: MouseEvent, closeUpdateModal = false
)
if (instance.value?.id !== reportInstance.id) return
if (closeUpdateModal) sharedInstanceUpdateModal.value?.hide()
sharedInstanceReportModal.value?.showReport(preview, event)
sharedInstanceReportModal.value?.showReport(preview, sharedInstance.manager_id, event)
} catch (error) {
notifySharedInstanceError(error)
}
+3
View File
@@ -34,6 +34,9 @@ const userProfile = provideUserProfile({
getOrganizations: (userId) => client.labrinth.users_v2.getOrganizations(userId),
getCollections: (userId) => client.labrinth.users_v2.getCollections(userId),
patchUser: (userId, patch) => client.labrinth.users_v2.patch(userId, patch),
getBlockedUsers: () => client.labrinth.blocked_users_v3.list(),
blockUser: (userId) => client.labrinth.blocked_users_v3.block(userId),
unblockUser: (userId) => client.labrinth.blocked_users_v3.unblock(userId),
})
const auth = await useAuth()
const cosmetics = useCosmetics()