diff --git a/apps/frontend/src/pages/[type]/[id]/settings/index.vue b/apps/frontend/src/pages/[type]/[id]/settings/index.vue
index e78a94564d..7ef1319512 100644
--- a/apps/frontend/src/pages/[type]/[id]/settings/index.vue
+++ b/apps/frontend/src/pages/[type]/[id]/settings/index.vue
@@ -273,6 +273,60 @@
+
+
+
+
+
+
+
+
+
+ Your project is not eligible for monetization. If you think this is a mistake, please
+
+ contact support.
+
+
+
+
+
+
+
+
+
@@ -311,6 +365,7 @@ import {
CheckIcon,
ImageIcon,
IssuesIcon,
+ ScaleIcon,
TrashIcon,
TriangleAlertIcon,
UploadIcon,
@@ -319,6 +374,7 @@ import {
import { MIN_SUMMARY_CHARS } from '@modrinth/moderation'
import {
Avatar,
+ ButtonStyled,
Combobox,
ConfirmLeaveModal,
ConfirmModal,
@@ -326,14 +382,18 @@ import {
injectNotificationManager,
injectProjectPageContext,
StyledInput,
+ Toggle,
UnsavedChangesPopup,
usePageLeaveSafety,
} from '@modrinth/ui'
import { fileIsValid, formatProjectStatus, formatProjectType } from '@modrinth/utils'
import FileInput from '~/components/ui/FileInput.vue'
+import { useAuth } from '~/composables/auth.js'
import { useFeatureFlags } from '~/composables/featureFlags.ts'
+const auth = await useAuth()
+
const { addNotification } = injectNotificationManager()
const {
projectV2: project,
@@ -364,6 +424,22 @@ const visibility = ref(
: project.value.requested_status,
)
+const monetizationEnabled = ref(project.value.monetization_status === 'monetized')
+const loadingModeratorMonetization = ref(false)
+
+watch(
+ () => project.value.monetization_status,
+ () => {
+ monetizationEnabled.value = project.value.monetization_status === 'monetized'
+ },
+)
+
+const isStaff = computed(
+ () => !!auth.value.user && tags.value.staffRoles.includes(auth.value.user.role),
+)
+
+const isForceDemonetized = computed(() => project.value.monetization_status === 'force-demonetized')
+
// Server project specific refs
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
@@ -378,6 +454,8 @@ const hasPermission = computed(() => {
return ((currentMember.value?.permissions ?? 0) & EDIT_DETAILS) === EDIT_DETAILS
})
+const monetizationToggleDisabled = computed(() => !hasPermission.value || isForceDemonetized.value)
+
const hasDeletePermission = computed(() => {
const DELETE_PROJECT = 1 << 7
return ((currentMember.value?.permissions ?? 0) & DELETE_PROJECT) === DELETE_PROJECT
@@ -450,6 +528,13 @@ const basePatchData = computed(() => {
data.requested_status = visibility.value
}
+ if (project.value.monetization_status !== 'force-demonetized') {
+ const wasMonetized = project.value.monetization_status === 'monetized'
+ if (monetizationEnabled.value !== wasMonetized) {
+ data.monetization_status = monetizationEnabled.value ? 'monetized' : 'demonetized'
+ }
+ }
+
return data
})
@@ -468,6 +553,7 @@ const original = computed(() => ({
deletedIcon: false,
bannerFile: null,
deletedBanner: false,
+ monetizationEnabled: project.value.monetization_status === 'monetized',
}))
const modified = computed(() => ({
@@ -481,6 +567,7 @@ const modified = computed(() => ({
deletedIcon: deletedIcon.value,
bannerFile: bannerFile.value,
deletedBanner: deletedBanner.value,
+ monetizationEnabled: monetizationEnabled.value,
}))
const hasChanges = computed(() =>
@@ -504,6 +591,16 @@ function resetChanges() {
bannerFile.value = null
bannerPreview.value = null
deletedBanner.value = false
+ monetizationEnabled.value = project.value.monetization_status === 'monetized'
+}
+
+async function updateMonetizationStatus(status) {
+ loadingModeratorMonetization.value = true
+ try {
+ await patchProject({ monetization_status: status })
+ } finally {
+ loadingModeratorMonetization.value = false
+ }
}
const hasModifiedVisibility = () => {