feat(frontend): Improve revenue information (#3250)

* Improve revenue information

* Improve NET 60 period info + show next period if current period is over.

* invert period check

* %

* Finalize changes

* Cleanup

* Remove .idea

* Discard changes to .idea/discord.xml

* Discard changes to .idea/code.iml

* Discard changes to .idea/.gitignore

* Discard changes to .idea/libraries/KotlinJavaRuntime.xml

* Discard changes to .idea/vcs.xml

* Discard changes to .idea/modules.xml

* Discard changes to .idea/.gitignore

* fix lint issues

* table fix, lint fix and media sizing fix

* fix responsiveness

* Remove comment

* utc comment

* fix lint
This commit is contained in:
Calum H.
2025-02-21 01:52:10 +00:00
committed by GitHub
parent 067f471766
commit c77f3395b2
4 changed files with 269 additions and 69 deletions

View File

@@ -2,38 +2,87 @@
<div>
<section class="universal-card">
<h2 class="text-2xl">Revenue</h2>
<div v-if="userBalance.available >= minWithdraw">
<p>
You have
<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 class="grid-display">
<div class="grid-display__item">
<div class="label">Available now</div>
<div class="value">
{{ $formatMoney(userBalance.available) }}
</div>
</div>
<div class="grid-display__item">
<div class="label">
Total pending
<nuxt-link
v-tooltip="`Click to read about how Modrinth handles your revenue.`"
class="align-middle text-link"
to="/legal/cmp-info#pending"
>
<UnknownIcon />
</nuxt-link>
</div>
<div class="value">
{{ $formatMoney(userBalance.pending) }}
</div>
</div>
<div class="grid-display__item available-soon">
<h3 class="label">
Available soon
<nuxt-link
v-tooltip="`Click to read about how Modrinth handles your revenue.`"
class="align-middle text-link"
to="/legal/cmp-info#pending"
>
<UnknownIcon />
</nuxt-link>
</h3>
<ul class="available-soon-list">
<li v-for="date in availableSoonDateKeys" :key="date" class="available-soon-item">
<span class="amount">
{{ $formatMoney(availableSoonDates[date]) }}
<small
v-if="availableSoonDateKeys.indexOf(date) === availableSoonDateKeys.length - 1"
></small
>
</span>
<span class="date">
{{ formatDate(dayjs(date)) }}
</span>
</li>
</ul>
</div>
</div>
<p v-else>
You have made
<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="userBalance.available >= minWithdraw"
class="iconified-button brand-button"
to="/dashboard/revenue/withdraw"
>
<TransferIcon /> Withdraw
</nuxt-link>
<span :class="{ 'disabled-cursor-wrapper': userBalance.available < minWithdraw }">
<nuxt-link
:aria-disabled="userBalance.available < minWithdraw ? 'true' : 'false'"
:class="{ 'disabled-link': userBalance.available < minWithdraw }"
:disabled="userBalance.available < minWithdraw ? 'true' : 'false'"
:tabindex="userBalance.available < minWithdraw ? -1 : undefined"
class="iconified-button brand-button"
to="/dashboard/revenue/withdraw"
>
<TransferIcon /> Withdraw
</nuxt-link>
</span>
<NuxtLink class="iconified-button" to="/dashboard/revenue/transfers">
<HistoryIcon /> View transfer history
<HistoryIcon />
View transfer history
</NuxtLink>
</div>
<p>
By uploading projects to Modrinth and withdrawing money from your account, you agree to the
<nuxt-link to="/legal/cmp" class="text-link">Rewards Program Terms</nuxt-link>. For more
information on how the rewards system works, see our information page
<nuxt-link to="/legal/cmp-info" class="text-link">here</nuxt-link>.
<small>
By uploading projects to Modrinth and withdrawing money from your account, you agree to
the
<nuxt-link class="text-link" to="/legal/cmp">Rewards Program Terms</nuxt-link>. For more
information on how the rewards system works, see our information page
<nuxt-link class="text-link" to="/legal/cmp-info">here</nuxt-link>.
</small>
</p>
<p>
<small>
Ongoing revenue period, subject to change. The finalized amount will be available to
view on the last day of the current month.
</small>
</p>
</section>
<section class="universal-card">
@@ -46,12 +95,13 @@
{{ auth.user.payout_data.paypal_address }}
</p>
<button class="btn mt-4" @click="removeAuthProvider('paypal')">
<XIcon /> Disconnect account
<XIcon />
Disconnect account
</button>
</template>
<template v-else>
<p>Connect your PayPal account to enable withdrawing to your PayPal balance.</p>
<a class="btn mt-4" :href="`${getAuthUrl('paypal')}&token=${auth.token}`">
<a :href="`${getAuthUrl('paypal')}&token=${auth.token}`" class="btn mt-4">
<PayPalIcon />
Sign in with PayPal
</a>
@@ -60,7 +110,8 @@
<p>
Tremendous payments are sent to your Modrinth email. To change/set your Modrinth email,
visit
<nuxt-link to="/settings/account" class="text-link">here</nuxt-link>.
<nuxt-link class="text-link" to="/settings/account">here</nuxt-link>
.
</p>
<h3>Venmo</h3>
<p>Enter your Venmo username below to enable withdrawing to your Venmo balance.</p>
@@ -68,18 +119,31 @@
<input
id="venmo"
v-model="auth.user.payout_data.venmo_handle"
autocomplete="off"
class="mt-4"
type="search"
name="search"
placeholder="@example"
autocomplete="off"
type="search"
/>
<button class="btn btn-secondary" @click="updateVenmo"><SaveIcon /> Save information</button>
<button class="btn btn-secondary" @click="updateVenmo">
<SaveIcon />
Save information
</button>
</section>
</div>
</template>
<script setup>
import { TransferIcon, HistoryIcon, PayPalIcon, SaveIcon, XIcon } from "@modrinth/assets";
import {
HistoryIcon,
PayPalIcon,
SaveIcon,
TransferIcon,
UnknownIcon,
XIcon,
} from "@modrinth/assets";
import { formatDate } from "@modrinth/utils";
import dayjs from "dayjs";
import { computed } from "vue";
const auth = await useAuth();
const minWithdraw = ref(0.01);
@@ -88,6 +152,33 @@ const { data: userBalance } = await useAsyncData(`payout/balance`, () =>
useBaseFetch(`payout/balance`, { apiVersion: 3 }),
);
const deadlineEnding = computed(() => {
let deadline = dayjs().subtract(2, "month").endOf("month").add(60, "days");
if (deadline.isBefore(dayjs().startOf("day"))) {
deadline = dayjs().subtract(1, "month").endOf("month").add(60, "days");
}
return deadline;
});
const availableSoonDates = computed(() => {
// Get the next 3 dates from userBalance.dates that are from now to the deadline + 4 months to make sure we get all the pending ones.
const dates = Object.keys(userBalance.value.dates)
.filter((date) => {
const dateObj = dayjs(date);
return (
dateObj.isAfter(dayjs()) && dateObj.isBefore(dayjs(deadlineEnding.value).add(4, "month"))
);
})
.sort((a, b) => dayjs(a).diff(dayjs(b)));
return dates.reduce((acc, date) => {
acc[date] = userBalance.value.dates[date];
return acc;
}, {});
});
const availableSoonDateKeys = computed(() => Object.keys(availableSoonDates.value));
async function updateVenmo() {
startLoading();
try {
@@ -118,4 +209,57 @@ strong {
color: var(--color-text-dark);
font-weight: 500;
}
.disabled-cursor-wrapper {
cursor: not-allowed;
}
.disabled-link {
pointer-events: none;
}
.grid-display {
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}
.available-soon {
padding-top: 0;
.label {
margin: 0;
}
&-list {
list-style-type: none;
padding: 0;
margin: 0;
}
&-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.2rem 0 0;
border-bottom: 1px solid var(--color-divider);
.amount {
font-weight: 600;
small {
vertical-align: top;
margin: 0;
padding: 0;
}
}
.date {
color: var(--color-text-secondary);
font-size: 0.9em;
}
&:last-child {
border-bottom: none;
}
}
}
</style>