Skip to content
Open
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
97 changes: 97 additions & 0 deletions apps/frontend/src/pages/[type]/[id]/settings/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,60 @@
</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 class="universal-card">
Expand Down Expand Up @@ -311,6 +365,7 @@ import {
CheckIcon,
ImageIcon,
IssuesIcon,
ScaleIcon,
TrashIcon,
TriangleAlertIcon,
UploadIcon,
Expand All @@ -319,21 +374,26 @@ import {
import { MIN_SUMMARY_CHARS } from '@modrinth/moderation'
import {
Avatar,
ButtonStyled,
Combobox,
ConfirmLeaveModal,
ConfirmModal,
injectModrinthClient,
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,
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
})

Expand All @@ -468,6 +553,7 @@ const original = computed(() => ({
deletedIcon: false,
bannerFile: null,
deletedBanner: false,
monetizationEnabled: project.value.monetization_status === 'monetized',
}))

const modified = computed(() => ({
Expand All @@ -481,6 +567,7 @@ const modified = computed(() => ({
deletedIcon: deletedIcon.value,
bannerFile: bannerFile.value,
deletedBanner: deletedBanner.value,
monetizationEnabled: monetizationEnabled.value,
}))

const hasChanges = computed(() =>
Expand All @@ -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 = () => {
Expand Down
Loading