Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ const messages = defineMessages({
id: 'project-moderation-nags.submit-for-review-button',
defaultMessage: 'Submit for review',
},
submittedForReview: {
id: 'project-moderation-nags.submitted-for-review',
defaultMessage: 'Pending moderator review',
},
submittedForReviewDesc: {
id: 'project-moderation-nags.submitted-for-review-desc',
defaultMessage: "Your project has been submitted to be reviewed by Modrinth's moderation team.",
},
visitModerationMessages: {
id: 'project-moderation-nags.visit-moderation-messages',
defaultMessage: 'Visit moderation thread',
},
resubmitForReview: {
id: 'project-moderation-nags.resubmit-for-review',
defaultMessage: 'Resubmit for review',
Expand Down Expand Up @@ -233,6 +245,8 @@ const props = withDefaults(defineProps<Props>(), {
validationNags: () => [],
validationLoading: false,
validationAvailable: true,
nags: undefined,
refreshValidation: undefined,
})

const emit = defineEmits<{
Expand Down Expand Up @@ -420,6 +434,20 @@ function isNagComplete(nag: Nag): boolean {
const visibleNags = computed<Nag[]>(() => {
const finalNags = applicableNags.value.filter((nag) => !isNagComplete(nag))

if (isProcessing.value) {
finalNags.push({
id: 'submitted-for-review',
title: messages.submittedForReview,
description: messages.submittedForReviewDesc,
status: 'special-submit-action',
shouldShow: (ctx) => ctx.project.status === 'processing',
link: {
...nagDestinations.moderation,
title: messages.visitModerationMessages,
},
})
}

if (props.project.status === 'draft') {
finalNags.push({
id: 'submit-for-review',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@
</template>
</template>
</div>
<div class="flex flex-wrap items-center gap-2">
<div class="ml-auto flex flex-wrap items-center gap-2">
<slot name="actions" />
<template v-if="report">
<Button
v-if="isStaff(auth.user) && replyBody"
Expand Down
17 changes: 17 additions & 0 deletions apps/frontend/src/components/ui/thread/ThreadMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@
<span v-if="message.body.new_status === 'processing'">
submitted the project for review.
</span>
<span
v-else-if="
message.body.old_status === 'processing' && message.body.new_status === 'draft'
"
>
{{ formatMessage(messages.withdrewFromReview) }}
</span>
<span v-else-if="message.body.old_status === 'processing'">
reviewed the project and set its status to <Badge :type="message.body.new_status" />.
</span>
Expand Down Expand Up @@ -166,14 +173,24 @@ import {
AutoLink,
Avatar,
Badge,
defineMessages,
TeleportOverflowMenu,
useFormatDateTime,
useRelativeTime,
useVIntl,
} from '@modrinth/ui'
import { renderString } from '@modrinth/utils'

import { isStaff } from '~/helpers/users.js'

const { formatMessage } = useVIntl()
const messages = defineMessages({
withdrewFromReview: {
id: 'thread-message.withdrew-from-review',
defaultMessage: 'withdrew the project from review',
},
})

const props = defineProps({
message: {
type: Object,
Expand Down
24 changes: 24 additions & 0 deletions apps/frontend/src/locales/en-US/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -3446,6 +3446,9 @@
"project-moderation-nags.resubmit-for-review-desc": {
"message": "Your project has been {status, select, rejected {rejected} withheld {withheld} other {{status}}} by Modrinth's staff. In most cases, you can resubmit for review after addressing the staff's message."
},
"project-moderation-nags.submission-withdrawn": {
"message": "Your submission has been withdrawn. "
},
"project-moderation-nags.submit-checklist-tooltip": {
"message": "You must complete the required steps in the publishing checklist!"
},
Expand All @@ -3458,9 +3461,18 @@
"project-moderation-nags.submit-for-review-desc": {
"message": "Your project is only viewable by members of the project. It must be reviewed by moderators in order to be published."
},
"project-moderation-nags.submitted-for-review": {
"message": "Pending moderator review"
},
"project-moderation-nags.submitted-for-review-desc": {
"message": "Your project has been submitted to be reviewed by Modrinth's moderation team."
},
"project-moderation-nags.suggestion": {
"message": "Suggestion"
},
"project-moderation-nags.visit-moderation-messages": {
"message": "Visit moderation thread"
},
"project-moderation-nags.visit-moderation-page": {
"message": "Visit moderation page"
},
Expand Down Expand Up @@ -3827,6 +3839,15 @@
"project.moderation.title": {
"message": "Moderation"
},
"project.moderation.withdraw.button": {
"message": "Un-submit from review"
},
"project.moderation.withdraw.description": {
"message": "If your project is not ready, or you no longer want it to be reviewed, you can withdraw it from the review queue. When you submit your project again, you will not retain your current position in the moderation queue."
},
"project.moderation.withdraw.title": {
"message": "Un-submit your project"
},
"project.modpack-archive-warning.description": {
"message": "Importing this .mrpack might be broken."
},
Expand Down Expand Up @@ -5489,6 +5510,9 @@
"shared-instance.invite.unavailable.title": {
"message": "This invite isn't available"
},
"thread-message.withdrew-from-review": {
"message": "withdrew the project from review"
},
"ui.latest-news-row.latest-news": {
"message": "Latest news from Modrinth"
},
Expand Down
29 changes: 25 additions & 4 deletions apps/frontend/src/pages/[type]/[project].vue
Original file line number Diff line number Diff line change
Expand Up @@ -1463,10 +1463,12 @@ const patchStatusMutation = useMutation({

const previousProject = queryClient.getQueryData(['project', 'v2', projectId])

queryClient.setQueryData(['project', 'v2', projectId], (old) => {
if (!old) return old
return { ...old, status }
})
if (status !== 'draft') {
queryClient.setQueryData(['project', 'v2', projectId], (old) => {
if (!old) return old
return { ...old, status }
})
}

return { previousProject, projectId }
},
Expand Down Expand Up @@ -2161,6 +2163,24 @@ watch(
{ immediate: true },
)

async function withdrawSubmission() {
if (patchStatusMutation.isPending.value || project.value.status !== 'processing') return false

startLoading()
try {
await patchStatusMutation.mutateAsync({
projectId: project.value.id,
status: 'draft',
threadId: project.value.thread_id,
})
return true
} catch {
return false
} finally {
stopLoading()
}
}

async function setProcessing() {
// Guard against multiple submissions while mutation is pending
if (patchStatusMutation.isPending.value) return
Expand Down Expand Up @@ -2493,6 +2513,7 @@ provideProjectPageContext({
patchProjectV3,
patchIcon,
setProcessing,
withdrawSubmission,

// Gallery mutation functions
createGalleryItem,
Expand Down
71 changes: 68 additions & 3 deletions apps/frontend/src/pages/[type]/[project]/moderation.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
<template>
<template v-if="canAccess">
<ConfirmModal
ref="withdrawModal"
:title="formatMessage(messages.withdrawTitle)"
:description="formatMessage(messages.withdrawDescription)"
:proceed-label="formatMessage(messages.withdrawButton)"
:proceed-icon="XCircleIcon"
:markdown="false"
@proceed="handleWithdrawSubmission"
/>
<Admonition
v-if="userFacingUiVisible && moderationAdmonition"
:type="moderationAdmonition.type"
Expand Down Expand Up @@ -117,7 +126,18 @@
:auth="auth"
class="overflow-clip rounded-b-2xl border-0 border-t border-solid border-surface-4 bg-surface-2"
@update-thread="updateThread"
/>
>
<template #actions>
<Button
v-if="canWithdrawSubmission"
:disabled="withdrawingSubmission"
@click="withdrawModal?.show()"
>
<XCircleIcon />
{{ formatMessage(messages.withdrawButton) }}
</Button>
</template>
</ConversationThread>
<div
v-else
class="flex items-center justify-center gap-2 rounded-b-2xl border-0 border-t border-solid border-surface-4 bg-surface-2 py-12"
Expand All @@ -134,10 +154,12 @@
</template>
<script setup lang="ts">
import type { Labrinth } from '@modrinth/api-client'
import { IssuesIcon, SpinnerIcon } from '@modrinth/assets'
import { IssuesIcon, SpinnerIcon, XCircleIcon } from '@modrinth/assets'
import {
Admonition,
Button,
commonMessages,
ConfirmModal,
defineMessage,
defineMessages,
injectModrinthClient,
Expand All @@ -152,7 +174,7 @@ import {
import { isStaff } from '@modrinth/utils'
import { useQueryClient } from '@tanstack/vue-query'
import dayjs from 'dayjs'
import { computed, watch } from 'vue'
import { computed, ref, watch } from 'vue'

import ConversationThread from '~/components/ui/thread/ConversationThread.vue'
import { getProjectLink, isApproved, isRejected, isUnderReview } from '~/helpers/projects.js'
Expand All @@ -173,6 +195,23 @@ type ModerationAdmonitionSection =
}

const messages = defineMessages({
withdrawTitle: {
id: 'project.moderation.withdraw.title',
defaultMessage: 'Un-submit your project',
},
withdrawDescription: {
id: 'project.moderation.withdraw.description',
defaultMessage:
'If your project is not ready, or you no longer want it to be reviewed, you can withdraw it from the review queue. When you submit your project again, you will not retain your current position in the moderation queue.',
},
withdrawButton: {
id: 'project.moderation.withdraw.button',
defaultMessage: 'Un-submit from review',
},
submissionWithdrawn: {
id: 'project-moderation-nags.submission-withdrawn',
defaultMessage: 'Your submission has been withdrawn. ',
},
admonitionRejectedSpamNotice: {
id: 'project.moderation.admonition.rejected.spam-notice',
defaultMessage:
Expand Down Expand Up @@ -216,6 +255,7 @@ const {
invalidate,
allMembers,
thread,
withdrawSubmission,
} = injectProjectPageContext()

const THREADS_RELEASE_DATE = '2023-08-05T12:00:00-07:00'
Expand Down Expand Up @@ -244,6 +284,31 @@ const userFacingUiVisible = computed(
() => !!currentMember.value && (!staff.value || moderatorSeeUserUi.value),
)

const withdrawModal = ref<InstanceType<typeof ConfirmModal>>()
const withdrawingSubmission = ref(false)
const canWithdrawSubmission = computed(
() =>
userFacingUiVisible.value &&
project.value.status === 'processing' &&
(staff.value || ((currentMember.value?.permissions ?? 0) & (1 << 2)) !== 0),
)

async function handleWithdrawSubmission() {
if (!canWithdrawSubmission.value || withdrawingSubmission.value) return

withdrawingSubmission.value = true
try {
if (!(await withdrawSubmission())) return
addNotification({
type: 'success',
title: formatMessage(commonMessages.successLabel),
text: formatMessage(messages.submissionWithdrawn),
})
} finally {
withdrawingSubmission.value = false
}
}

const approvedAdmonitionMessage = computed<MessageDescriptor | null>(() => {
switch (project.value?.status) {
case 'approved':
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/pages/[type]/[project]/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const {
versions,
currentMember,
setProcessing,
withdrawSubmission,
projectValidation,
projectValidationLoading,
refreshProjectValidation,
Expand Down Expand Up @@ -183,6 +184,7 @@ const moderatorSeeUserUi = computed<boolean>({
:validation-loading="projectValidationLoading"
:validation-available="projectValidation !== null"
:refresh-validation="refreshProjectValidation"
:withdraw-submission="withdrawSubmission"
@toggle-collapsed="() => (collapsedChecklist = !collapsedChecklist)"
@set-processing="setProcessing"
/>
Expand Down
2 changes: 2 additions & 0 deletions apps/labrinth/src/routes/v3/projects/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,9 +610,11 @@
.wrap_internal_err("failed to upsert archival disclosure")?;
}
} else {
if !(user.role.is_mod()

Check warning on line 613 in apps/labrinth/src/routes/v3/projects/mod.rs

View workflow job for this annotation

GitHub Actions / Lint and Test

Diff in /home/runner/work/code/code/apps/labrinth/src/routes/v3/projects/mod.rs
|| !project_item.inner.status.is_approved()
&& status == &ProjectStatus::Processing
|| project_item.inner.status == ProjectStatus::Processing
&& status == &ProjectStatus::Draft
|| project_item.inner.status.is_approved()
&& status.can_be_requested())
{
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/providers/project-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface ProjectPageContext {
patchProjectV3: (data: Record<string, unknown>, quiet?: boolean) => Promise<boolean>
patchIcon: (icon: File) => Promise<boolean>
setProcessing: () => Promise<void>
withdrawSubmission: () => Promise<boolean>
createGalleryItem: (
file: File,
title?: string,
Expand Down
Loading