From a4c815443849ce748968d145b18b1edb2df4426d Mon Sep 17 00:00:00 2001 From: Prospector <6166773+Prospector@users.noreply.github.com> Date: Sun, 3 May 2026 11:56:08 -0700 Subject: [PATCH] feat: add monetization toggle for projects (#5961) * Add monetization toggle for projects * add flag for monetization toggle * remove feature flag toggle --- .../src/pages/[type]/[id]/settings/index.vue | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) 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 @@ + +
+
+ + +
+
+
+
+ + + + +
+
@@ -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 = () => {