diff --git a/apps/frontend/src/pages/[type]/[id]/settings/index.vue b/apps/frontend/src/pages/[type]/[id]/settings/index.vue
index e5e4f32d8..b73efe46a 100644
--- a/apps/frontend/src/pages/[type]/[id]/settings/index.vue
+++ b/apps/frontend/src/pages/[type]/[id]/settings/index.vue
@@ -271,6 +271,60 @@
+
+
+
+
+
+
+
+
+
+ Your project is not eligible for monetization. If you think this is a mistake, please
+
+ contact support.
+
+
+
+
+
+
+
+
+
@@ -306,6 +360,7 @@ import {
CheckIcon,
ImageIcon,
IssuesIcon,
+ ScaleIcon,
TrashIcon,
TriangleAlertIcon,
UploadIcon,
@@ -322,14 +377,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,
@@ -360,6 +419,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)
@@ -374,6 +449,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
@@ -446,6 +523,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
})
@@ -464,6 +548,7 @@ const original = computed(() => ({
deletedIcon: false,
bannerFile: null,
deletedBanner: false,
+ monetizationEnabled: project.value.monetization_status === 'monetized',
}))
const modified = computed(() => ({
@@ -477,6 +562,7 @@ const modified = computed(() => ({
deletedIcon: deletedIcon.value,
bannerFile: bannerFile.value,
deletedBanner: deletedBanner.value,
+ monetizationEnabled: monetizationEnabled.value,
}))
const hasChanges = computed(() =>
@@ -500,6 +586,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 = () => {