diff --git a/apps/frontend/src/components/ui/dashboard/CreatorWithdrawModal.vue b/apps/frontend/src/components/ui/dashboard/CreatorWithdrawModal.vue
index b356c7118..f248bb025 100644
--- a/apps/frontend/src/components/ui/dashboard/CreatorWithdrawModal.vue
+++ b/apps/frontend/src/components/ui/dashboard/CreatorWithdrawModal.vue
@@ -159,6 +159,7 @@ interface UserBalanceResponse {
const props = defineProps<{
balance: UserBalanceResponse | null
preloadedPaymentData?: { country: string; methods: PayoutMethod[] } | null
+ userBadges?: number
}>()
const emit = defineEmits<{
@@ -194,6 +195,7 @@ const { addNotification } = injectNotificationManager()
const withdrawContext = createWithdrawContext(
props.balance,
props.preloadedPaymentData || undefined,
+ props.userBadges,
)
provideWithdrawContext(withdrawContext)
diff --git a/apps/frontend/src/layouts/default.vue b/apps/frontend/src/layouts/default.vue
index dd4a34c5d..8d0b00f9a 100644
--- a/apps/frontend/src/layouts/default.vue
+++ b/apps/frontend/src/layouts/default.vue
@@ -761,13 +761,19 @@ const { data: payoutBalance } = await useAsyncData('payout/balance', () => {
const showTaxComplianceBanner = computed(() => {
if (flags.value.testTaxForm && auth.value.user) return true
+ const user = auth.value.user
+ if (!user) return false
const bal = payoutBalance.value
- if (!bal) return false
- const thresholdMet = (bal.withdrawn_ytd ?? 0) >= 600
- const status = bal.form_completion_status ?? 'unknown'
+ const status = bal?.form_completion_status ?? 'unknown'
const isComplete = status === 'complete'
const isTinMismatch = status === 'tin-mismatch'
- return !!auth.value.user && thresholdMet && !isComplete && !isTinMismatch
+ if (isComplete || isTinMismatch) return false
+ // Show banner if user has the 2025 tax form badge override
+ if (user.badges & UserBadge.TAX_FORM_2025_REQUIRED) return true
+ // Otherwise, show if threshold met
+ if (!bal) return false
+ const thresholdMet = (bal.withdrawn_ytd ?? 0) >= 600
+ return thresholdMet
})
const showTinMismatchBanner = computed(() => {
diff --git a/apps/frontend/src/pages/dashboard/revenue/index.vue b/apps/frontend/src/pages/dashboard/revenue/index.vue
index f3ec3ab4e..978227190 100644
--- a/apps/frontend/src/pages/dashboard/revenue/index.vue
+++ b/apps/frontend/src/pages/dashboard/revenue/index.vue
@@ -3,6 +3,7 @@
ref="withdrawModal"
:balance="userBalance"
:preloaded-payment-data="preloadedPaymentMethods"
+ :user-badges="auth.user?.badges"
@refresh-data="refreshData"
/>
@@ -272,7 +273,7 @@ import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue
const { formatMessage } = useVIntl()
-await useAuth()
+const auth = await useAuth()
// TODO: Deduplicate these types & interfaces in @modrinth/api-client PR.
type FormCompletionStatus = 'unknown' | 'unrequested' | 'unsigned' | 'tin-mismatch' | 'complete'
diff --git a/apps/frontend/src/providers/creator-withdraw.ts b/apps/frontend/src/providers/creator-withdraw.ts
index 0f16842bc..39b81c10d 100644
--- a/apps/frontend/src/providers/creator-withdraw.ts
+++ b/apps/frontend/src/providers/creator-withdraw.ts
@@ -8,6 +8,7 @@ import {
} from '@modrinth/assets'
import type { MessageDescriptor } from '@modrinth/ui'
import { createContext, getCurrencyIcon, paymentMethodMessages, useDebugLogger } from '@modrinth/ui'
+import { UserBadge } from '@modrinth/utils'
import { type Component, computed, type ComputedRef, type Ref, ref } from 'vue'
import { getRailConfig } from '@/utils/muralpay-rails'
@@ -379,6 +380,7 @@ const STATE_EXPIRY_MS = 15 * 60 * 1000 // 15 minutes
export function createWithdrawContext(
balance: any,
preloadedPaymentData?: { country: string; methods: PayoutMethod[] },
+ userBadges?: number,
): WithdrawContextValue {
const debug = useDebugLogger('CreatorWithdraw')
const currentStage = ref()
@@ -420,15 +422,18 @@ export function createWithdrawContext(
const usedLimit = balance?.withdrawn_ytd ?? 0
const available = balance?.available ?? 0
+ const hasBadgeOverride = !!(userBadges && userBadges & UserBadge.TAX_FORM_2025_REQUIRED)
const needsTaxForm =
- balance?.form_completion_status !== 'complete' && usedLimit + available >= 600
+ balance?.form_completion_status !== 'complete' &&
+ (usedLimit + available >= 600 || hasBadgeOverride)
debug('Tax form check:', {
usedLimit,
available,
total: usedLimit + available,
status: balance?.form_completion_status,
+ hasBadgeOverride,
needsTaxForm,
})
@@ -817,6 +822,7 @@ export function createWithdrawContext(
calculation: {
amount: 0,
fee: null,
+ netUsd: null,
exchangeRate: null,
},
providerData: {
diff --git a/apps/labrinth/src/models/v3/users.rs b/apps/labrinth/src/models/v3/users.rs
index 0f276fc8f..c3e8cbf10 100644
--- a/apps/labrinth/src/models/v3/users.rs
+++ b/apps/labrinth/src/models/v3/users.rs
@@ -18,6 +18,7 @@ bitflags::bitflags! {
const CONTRIBUTOR = 1 << 5;
const TRANSLATOR = 1 << 6;
const AFFILIATE = 1 << 7;
+ const TAX_FORM_2025_REQUIRED = 1 << 8;
}
}
diff --git a/packages/utils/types.ts b/packages/utils/types.ts
index 03dbd1009..79556830f 100644
--- a/packages/utils/types.ts
+++ b/packages/utils/types.ts
@@ -342,6 +342,7 @@ export enum UserBadge {
CONTRIBUTOR = 1 << 5,
TRANSLATOR = 1 << 6,
AFFILIATE = 1 << 7,
+ TAX_FORM_2025_REQUIRED = 1 << 8,
}
export type UserBadges = number