Merge commit '3b8963fad01131c0946a4c23b2a6a67c2531f10b' into feature-clean

This commit is contained in:
2024-09-20 22:24:17 +03:00
10 changed files with 114 additions and 177 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "@modrinth/app-frontend",
"private": true,
"version": "0.8.701",
"version": "0.8.702",
"development_build": true,
"type": "module",
"scripts": {

View File

@@ -1,7 +1,7 @@
<template>
<div class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised">
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
<p class="m-0 text-2xl font-bold text-contrast">90% of ad revenue goes to creators</p>
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
<nuxt-link to="/plus" class="mt-auto items-center gap-1 text-purple hover:underline">
<span>
Support creators and Modrinth ad-free with

View File

@@ -231,6 +231,7 @@
</template>
<template #revenue> <CurrencyIcon aria-hidden="true" /> Revenue </template>
<template #analytics> <ChartIcon aria-hidden="true" /> Analytics </template>
<template #moderation> <ModerationIcon aria-hidden="true" /> Moderation </template>
<template #sign-out> <LogOutIcon aria-hidden="true" /> Sign out </template>
</OverflowMenu>
<ButtonStyled v-else color="brand">
@@ -778,6 +779,7 @@ const userMenuOptions = computed(() => {
link: "/settings",
},
];
// TODO: Only show if user has projects
options = [
...options,
@@ -801,6 +803,24 @@ const userMenuOptions = computed(() => {
link: "/dashboard/analytics",
},
];
if (
(auth.value && auth.value.user && auth.value.user.role === "moderator") ||
auth.value.user.role === "admin"
) {
options = [
...options,
{
divider: true,
},
{
id: "moderation",
color: "orange",
link: "/moderation/review",
},
];
}
options = [
...options,
{

View File

@@ -88,20 +88,6 @@
></span
>
</div>
<div class="grid-display__item">
<div class="label">Current balance</div>
<div class="value">
{{ $formatMoney(auth.user.payout_data.balance, true) }}
</div>
<NuxtLink
v-if="auth.user.payout_data.balance > 0"
class="goto-link"
to="/dashboard/revenue"
>
Withdraw earnings
<ChevronRightIcon class="featured-header-chevron" aria-hidden="true" />
</NuxtLink>
</div>
</div>
</section>
</div>

View File

@@ -2,21 +2,24 @@
<div>
<section class="universal-card">
<h2 class="text-2xl">Revenue</h2>
<div v-if="auth.user.payout_data.balance >= minWithdraw">
<div v-if="userBalance.available >= minWithdraw">
<p>
You have
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong>
available to withdraw.
<strong>{{ $formatMoney(userBalance.available) }}</strong>
available to withdraw. <strong>{{ $formatMoney(userBalance.pending) }}</strong> of your
balance is <nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
</p>
</div>
<p v-else>
You have made
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong
<strong>{{ $formatMoney(userBalance.available) }}</strong
>, which is under the minimum of ${{ minWithdraw }} to withdraw.
<strong>{{ $formatMoney(userBalance.pending) }}</strong> of your balance is
<nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
</p>
<div class="input-group mt-4">
<nuxt-link
v-if="auth.user.payout_data.balance >= minWithdraw"
v-if="userBalance.available >= minWithdraw"
class="iconified-button brand-button"
to="/dashboard/revenue/withdraw"
>
@@ -81,6 +84,10 @@ import { TransferIcon, HistoryIcon, PayPalIcon, SaveIcon, XIcon } from "@modrint
const auth = await useAuth();
const minWithdraw = ref(0.01);
const { data: userBalance } = await useAsyncData(`payout/balance`, () =>
useBaseFetch(`payout/balance`, { apiVersion: 3 }),
);
async function updateVenmo() {
startLoading();
try {

View File

@@ -88,7 +88,7 @@
<p>
You are initiating a transfer of your revenue from Modrinth's Creator Monetization Program.
How much of your
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong> balance would you like to
<strong>{{ $formatMoney(userBalance.available) }}</strong> balance would you like to transfer
transfer to {{ selectedMethod.name }}?
</p>
<div class="confirmation-input">
@@ -212,10 +212,13 @@ const country = ref(
countries.value.find((x) => x.id === (auth.value.user.payout_data.paypal_region ?? "US")),
);
const { data: payoutMethods, refresh: refreshPayoutMethods } = await useAsyncData(
`payout/methods?country=${country.value.id}`,
() => useBaseFetch(`payout/methods?country=${country.value.id}`, { apiVersion: 3 }),
);
const [{ data: userBalance }, { data: payoutMethods, refresh: refreshPayoutMethods }] =
await Promise.all([
useAsyncData(`payout/balance`, () => useBaseFetch(`payout/balance`, { apiVersion: 3 })),
useAsyncData(`payout/methods?country=${country.value.id}`, () =>
useBaseFetch(`payout/methods?country=${country.value.id}`, { apiVersion: 3 }),
),
]);
const selectedMethodId = ref(payoutMethods.value[0].id);
const selectedMethod = computed(() =>
@@ -295,10 +298,10 @@ const knownErrors = computed(() => {
if (!parsedAmount.value && amount.value.length > 0) {
errors.push(`${amount.value} is not a valid amount`);
} else if (
parsedAmount.value > auth.value.user.payout_data.balance ||
parsedAmount.value > userBalance.value.available ||
parsedAmount.value > maxWithdrawAmount.value
) {
const maxAmount = Math.min(auth.value.user.payout_data.balance, maxWithdrawAmount.value);
const maxAmount = Math.min(userBalance.value.available, maxWithdrawAmount.value);
errors.push(`The amount must be no more than ${data.$formatMoney(maxAmount)}`);
} else if (parsedAmount.value <= fees.value || parsedAmount.value < minWithdrawAmount.value) {
const minAmount = Math.max(fees.value + 0.01, minWithdrawAmount.value);

View File

@@ -1,7 +1,7 @@
<template>
<div class="markdown-body">
<h1>Rewards Program Information</h1>
<p><em>Last modified: May 13, 2024</em></p>
<p><em>Last modified: Sep 12, 2024</em></p>
<p>
This page was created for transparency for how the rewards program works on Modrinth. Feel
free to join our Discord or email
@@ -15,10 +15,11 @@
<h2>Rewards Distribution</h2>
<p>
We collect ad revenue on our website and app through our ad network
<a href="https://adrinth.com">Adrinth</a>. We then distribute this ad revenue to creators.
<a href="https://adrinth.com">Adrinth</a>, which is powered by
<a href="https://aditude.io">Aditude</a>. We then distribute this ad revenue to creators.
</p>
<p>
The advertising revenue of the entire website and app is split 90% to creators and 10% to
The advertising revenue of the entire website and app is split 75% to creators and 25% to
Modrinth.
</p>
<p>
@@ -42,10 +43,10 @@
</ul>
<p>In this scenario, the earnings for each creator and Modrinth would be as follows:</p>
<ul>
<li>Modrinth: $10 (10% of $100, the site's earnings for the day)</li>
<li>User A: $58.69 ($90 * (10 + 30 + 100 + 10)/230)</li>
<li>User B: $12.52 (0.4 * $90 * (50 + 20 + 10 + 0)/230)</li>
<li>User C: $18.78 (0.6 * $90 * (50 + 20 + 10 + 0)/230)</li>
<li>Modrinth: $25 (25% of $100, the site's earnings for the day)</li>
<li>User A: $48.91 ($75 * (10 + 30 + 100 + 10)/230)</li>
<li>User B: $10.43 (0.4 * $75 * (50 + 20 + 10 + 0)/230)</li>
<li>User C: $15.65 (0.6 * $75 * (50 + 20 + 10 + 0)/230)</li>
<li>Note: 230 is the sum of all page views and in-app downloads from above</li>
</ul>
<p>
@@ -72,19 +73,62 @@
</p>
<h3>What methods can I use withdraw money from my account? Are there any fees?</h3>
<p>
Right now, you can use PayPal or Venmo to withdraw money from your Modrinth account. We are
working on more methods to withdraw money from your account. There are fees to withdraw money
from your Modrinth account—see the revenue page in your dashboard for more information.
Right now, you can use PayPal or Venmo to withdraw money from your Modrinth account. Gift card
withdrawal is also available. We are working on more methods to withdraw money from your
account. There are fees to withdraw money from your Modrinth accountsee the revenue page in
your dashboard for more information.
</p>
<h3>Modrinth used to give 100% of project page revenue to creators. What changed?</h3>
<h3 id="pending">What does "pending" revenue mean in my dashboard?</h3>
<p>
While this is true, our new system (as of 08/05/23) gives more of the site's revenue to
creators, so creators will earn more. In the old system, we would earn revenue through
advertisements in search and user profile pages. This amounted on average each month to about
15-20% of the site's total advertising revenue (so a 80-85% split to creators). The new system
gives creators more revenue and a more favorable split towards creators (90%).
Modrinth receives ad revenue from our ad providers on a NET 60 day basis. Due to this, not all
revenue is not immediately available to withdraw. We pay creators as soon as we receive the
money from our ad providers, which is 60 days after the last day of each month. This table
outlines some example dates of how NET 60 payments are made:
</p>
<table>
<thead>
<tr>
<th>Date</th>
<th>Payment available date</th>
</tr>
</thead>
<tbody>
<tr>
<td>January 1st</td>
<td>March 31st</td>
</tr>
<tr>
<td>January 15th</td>
<td>March 31st</td>
</tr>
<tr>
<td>March 3rd</td>
<td>May 30th</td>
</tr>
<tr>
<td>June 30th</td>
<td>August 29th</td>
</tr>
<tr>
<td>July 14th</td>
<td>September 29th</td>
</tr>
<tr>
<td>October 12th</td>
<td>December 30th</td>
</tr>
</tbody>
</table>
<h3>How do I know Modrinth is being transparent about revenue?</h3>
<p>
We aim to be as transparent as possible with creator revenue. All of our code is open source,
including our
<a href="https://github.com/modrinth/labrinth/blob/master/src/queue/payouts.rs#L561">
revenue distribution system </a
>. We also have an
<a href="https://api.modrinth.com/v3/payout/platform_revenue">API route</a> that allows users
to query exact daily revenue for the site.
</p>
<h3></h3>
</div>
</template>

View File

@@ -37,7 +37,7 @@
border-radius: 1rem;
position: absolute;
left: 0;
bottom: 0;
top: 0;
z-index: 2;
}
</style>
@@ -58,19 +58,18 @@
]);
});
let lastUrl = null
window.addEventListener(
"message",
(event) => {
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__ && lastUrl !== event.data.modrinthOpenUrl) {
lastUrl = event.data.modrinthOpenUrl
// window.__TAURI_INTERNALS__.invoke("plugin:shell|open", {
// path: event.data.modrinthOpenUrl,
// });
if (event.data.modrinthAdClick && window.__TAURI_INTERNALS__) {
window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
}
setTimeout(() => {
lastUrl = null
}, 500)
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__) {
window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
path: event.data.modrinthOpenUrl,
origin: event.origin,
});
}
},
false,

View File

@@ -1,121 +0,0 @@
<template>
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD">
<a href="https://modrinth.com/plus" rel="noopener nofollow sponsored" :target="target"></a>
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-0">
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-1">
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2">
<ModrinthPlusIcon class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-3" />
<span>
<span>
90% of ad revenue goes to creators. Go ad-free while supporting creators with
</span>
<strong>Modrinth Plus.</strong>
<span> Subscribe today!</span>
</span>
</div>
</div>
<div class="GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-4">
<a rel="noopener sponsored" :target="target" href="https://adrinth.com"> Ad via Adrinth </a>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ModrinthPlusIcon } from '@modrinth/assets'
const props = withDefaults(
defineProps<{
external: boolean
}>(),
{
external: true,
},
)
const target = computed(() => (props.external ? '_blank' : '_self'))
</script>
<style lang="scss" scoped>
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD {
position: relative;
margin-bottom: var(--gap-md);
background: var(--color-ad);
border: 1px solid var(--color-ad-raised);
border-radius: var(--radius-lg);
container-type: inline-size;
width: 100%;
> a {
position: absolute;
inset: 0;
}
&:has(> a:first-child:active) {
scale: 0.99;
}
}
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-0 {
font-size: 14px;
line-height: 1.3em;
}
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-1 {
color: var(--color-base);
padding: 1em;
text-align: left;
}
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--color-base);
margin-right: 7.5rem;
&:hover {
text-decoration: none;
}
}
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 b,
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 strong {
color: var(--color-ad-highlight);
}
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-3 {
padding-top: 1px;
height: 1.5rem;
width: auto;
flex-shrink: 0;
}
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-4 a {
position: absolute;
bottom: -1px;
right: -1px;
text-align: center;
font-weight: 600;
text-transform: uppercase;
font-size: 0.8em;
color: var(--color-ad-contrast);
background: var(--color-ad-raised);
letter-spacing: 0.1ch;
margin: 0;
padding: 2px 10px;
border-top-left-radius: var(--radius-lg);
border-bottom-right-radius: var(--radius-lg);
display: flex;
align-items: center;
gap: 0.5rem;
&:hover {
text-decoration: none;
}
}
@media screen and (max-width: 800px) {
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 {
margin-bottom: 0.5rem;
}
.GBBNWLJVGRHFLYVGSZKSSKNTHFYXHMBD-2 a {
align-items: flex-start;
flex-direction: column;
}
}
</style>

View File

@@ -22,7 +22,6 @@ export { default as Page } from './base/Page.vue'
export { default as Pagination } from './base/Pagination.vue'
export { default as PopoutMenu } from './base/PopoutMenu.vue'
export { default as ProjectCard } from './base/ProjectCard.vue'
export { default as Promotion } from './base/Promotion.vue'
export { default as ScrollablePanel } from './base/ScrollablePanel.vue'
export { default as Slider } from './base/Slider.vue'
export { default as StatItem } from './base/StatItem.vue'