feat: add monetization toggle for projects (#5961)
* Add monetization toggle for projects * add flag for monetization toggle * remove feature flag toggle
This commit is contained in:
@@ -271,6 +271,60 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!isServerProject" class="mt-4 flex flex-col gap-2">
|
||||||
|
<div class="grid grid-cols-[1fr_auto] items-center gap-6">
|
||||||
|
<label for="project-monetization-toggle">
|
||||||
|
<span class="mb-1 block text-lg font-semibold text-contrast">Monetization</span>
|
||||||
|
<span class="block">
|
||||||
|
When enabled, this project can earn revenue through Modrinth's
|
||||||
|
<nuxt-link to="/legal/cmp-info" target="_blank" class="text-link"
|
||||||
|
>Rewards Program</nuxt-link
|
||||||
|
>. If you don't want to (or can't for legal reasons) earn revenue from this project,
|
||||||
|
you can turn it off here.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<Toggle
|
||||||
|
id="project-monetization-toggle"
|
||||||
|
v-model="monetizationEnabled"
|
||||||
|
:disabled="monetizationToggleDisabled"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="isForceDemonetized" class="mt-2 flex flex-wrap items-center gap-2 text-orange">
|
||||||
|
<TriangleAlertIcon aria-hidden="true" />
|
||||||
|
<span>
|
||||||
|
Your project is not eligible for monetization. If you think this is a mistake, please
|
||||||
|
<a
|
||||||
|
class="text-orange underline hover:brightness-110"
|
||||||
|
href="https://support.modrinth.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
contact support</a
|
||||||
|
>.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="isStaff" class="mt-2">
|
||||||
|
<ButtonStyled color="orange">
|
||||||
|
<button
|
||||||
|
v-if="!isForceDemonetized"
|
||||||
|
:disabled="loadingModeratorMonetization"
|
||||||
|
@click="updateMonetizationStatus('force-demonetized')"
|
||||||
|
>
|
||||||
|
<ScaleIcon aria-hidden="true" />
|
||||||
|
Disable monetization
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
:disabled="loadingModeratorMonetization"
|
||||||
|
@click="updateMonetizationStatus('monetized')"
|
||||||
|
>
|
||||||
|
<ScaleIcon aria-hidden="true" />
|
||||||
|
Allow monetization
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="universal-card">
|
<section class="universal-card">
|
||||||
@@ -306,6 +360,7 @@ import {
|
|||||||
CheckIcon,
|
CheckIcon,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
IssuesIcon,
|
IssuesIcon,
|
||||||
|
ScaleIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
TriangleAlertIcon,
|
TriangleAlertIcon,
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
@@ -322,14 +377,18 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
injectProjectPageContext,
|
injectProjectPageContext,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
Toggle,
|
||||||
UnsavedChangesPopup,
|
UnsavedChangesPopup,
|
||||||
usePageLeaveSafety,
|
usePageLeaveSafety,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { fileIsValid, formatProjectStatus, formatProjectType } from '@modrinth/utils'
|
import { fileIsValid, formatProjectStatus, formatProjectType } from '@modrinth/utils'
|
||||||
|
|
||||||
import FileInput from '~/components/ui/FileInput.vue'
|
import FileInput from '~/components/ui/FileInput.vue'
|
||||||
|
import { useAuth } from '~/composables/auth.js'
|
||||||
import { useFeatureFlags } from '~/composables/featureFlags.ts'
|
import { useFeatureFlags } from '~/composables/featureFlags.ts'
|
||||||
|
|
||||||
|
const auth = await useAuth()
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const {
|
const {
|
||||||
projectV2: project,
|
projectV2: project,
|
||||||
@@ -360,6 +419,22 @@ const visibility = ref(
|
|||||||
: project.value.requested_status,
|
: 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
|
// Server project specific refs
|
||||||
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
|
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
|
||||||
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
const isServerProject = computed(() => projectV3.value?.minecraft_server != null)
|
||||||
@@ -374,6 +449,8 @@ const hasPermission = computed(() => {
|
|||||||
return ((currentMember.value?.permissions ?? 0) & EDIT_DETAILS) === EDIT_DETAILS
|
return ((currentMember.value?.permissions ?? 0) & EDIT_DETAILS) === EDIT_DETAILS
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const monetizationToggleDisabled = computed(() => !hasPermission.value || isForceDemonetized.value)
|
||||||
|
|
||||||
const hasDeletePermission = computed(() => {
|
const hasDeletePermission = computed(() => {
|
||||||
const DELETE_PROJECT = 1 << 7
|
const DELETE_PROJECT = 1 << 7
|
||||||
return ((currentMember.value?.permissions ?? 0) & DELETE_PROJECT) === DELETE_PROJECT
|
return ((currentMember.value?.permissions ?? 0) & DELETE_PROJECT) === DELETE_PROJECT
|
||||||
@@ -446,6 +523,13 @@ const basePatchData = computed(() => {
|
|||||||
data.requested_status = visibility.value
|
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
|
return data
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -464,6 +548,7 @@ const original = computed(() => ({
|
|||||||
deletedIcon: false,
|
deletedIcon: false,
|
||||||
bannerFile: null,
|
bannerFile: null,
|
||||||
deletedBanner: false,
|
deletedBanner: false,
|
||||||
|
monetizationEnabled: project.value.monetization_status === 'monetized',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const modified = computed(() => ({
|
const modified = computed(() => ({
|
||||||
@@ -477,6 +562,7 @@ const modified = computed(() => ({
|
|||||||
deletedIcon: deletedIcon.value,
|
deletedIcon: deletedIcon.value,
|
||||||
bannerFile: bannerFile.value,
|
bannerFile: bannerFile.value,
|
||||||
deletedBanner: deletedBanner.value,
|
deletedBanner: deletedBanner.value,
|
||||||
|
monetizationEnabled: monetizationEnabled.value,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const hasChanges = computed(() =>
|
const hasChanges = computed(() =>
|
||||||
@@ -500,6 +586,16 @@ function resetChanges() {
|
|||||||
bannerFile.value = null
|
bannerFile.value = null
|
||||||
bannerPreview.value = null
|
bannerPreview.value = null
|
||||||
deletedBanner.value = false
|
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 = () => {
|
const hasModifiedVisibility = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user