Skip to content

Commit 30106d5

Browse files
triphoraIMB11
andauthored
Provide more specific payout method names on frontend (#4977)
* Provide more specific payout method names on frontend Been getting a lot of confused tickets recently of people withdrawing to PayPal but then not recognizing what "Tremendous" means. This should clarify things. * feat: improve icons + names for withdrawals * Update apps/frontend/src/components/ui/dashboard/RevenueTransaction.vue Co-authored-by: Emma Alexia <emma@modrinth.com> Signed-off-by: Calum H. <hendersoncal117@gmail.com> * fix: icons * fix: object cover * feat: icons for crypto + bank * fix: remove empty null * fix: qa --------- Signed-off-by: Calum H. <hendersoncal117@gmail.com> Co-authored-by: Calum H. <contact@cal.engineer>
1 parent e0d159c commit 30106d5

6 files changed

Lines changed: 164 additions & 11 deletions

File tree

apps/frontend/src/components/ui/dashboard/RevenueTransaction.vue

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
11
<template>
22
<div class="flex flex-row gap-2 md:gap-3">
33
<div
4-
class="flex h-10 min-h-10 w-10 min-w-10 justify-center rounded-full border-[1px] border-solid border-button-bg bg-bg-raised !p-0 shadow-md md:h-12 md:min-h-12 md:w-12 md:min-w-12"
4+
class="flex h-10 min-h-10 w-10 min-w-10 items-center justify-center rounded-full border-[1px] border-solid border-button-bg bg-bg-raised !p-0 shadow-md md:h-12 md:min-h-12 md:w-12 md:min-w-12"
55
>
6-
<ArrowDownIcon v-if="isIncome" class="my-auto size-6 text-secondary md:size-8" />
7-
<ArrowUpIcon v-else class="my-auto size-6 text-secondary md:size-8" />
6+
<img
7+
v-if="methodIconUrl"
8+
:src="methodIconUrl"
9+
alt=""
10+
class="size-6 rounded-full object-cover md:size-8"
11+
/>
12+
<component
13+
:is="methodIconComponent"
14+
v-else-if="methodIconComponent"
15+
class="size-6 md:size-8"
16+
/>
17+
<ArrowDownIcon v-else-if="isIncome" class="size-6 text-secondary md:size-8" />
18+
<ArrowUpIcon v-else class="size-6 text-secondary md:size-8" />
819
</div>
920
<div class="flex w-full flex-row justify-between">
1021
<div class="flex flex-col">
1122
<span class="text-base font-semibold text-contrast md:text-lg">{{
1223
transaction.type === 'payout_available'
1324
? formatPayoutSource(transaction.payout_source)
14-
: formatMethodName(transaction.method_type || transaction.method)
25+
: formatMethodName(transaction.method_type || transaction.method, transaction.method_id)
1526
}}</span>
1627
<span class="text-xs text-secondary md:text-sm">
1728
<template v-if="transaction.type === 'withdrawal'">
@@ -33,8 +44,8 @@
3344
<div class="my-auto flex flex-row items-center gap-2">
3445
<span
3546
class="text-base font-semibold md:text-lg"
36-
:class="transaction.type === 'payout_available' ? 'text-green' : 'text-contrast'"
37-
>{{ formatMoney(transaction.amount) }}</span
47+
:class="isIncome ? 'text-green' : 'text-contrast'"
48+
>{{ isIncome ? '' : '-' }}{{ formatMoney(transaction.amount) }}</span
3849
>
3950
<template v-if="transaction.type === 'withdrawal' && transaction.status === 'in-transit'">
4051
<Tooltip theme="dismissable-prompt" :triggers="['hover', 'focus']" no-auto-focus>
@@ -55,12 +66,27 @@
5566
</template>
5667

5768
<script setup lang="ts">
58-
import { ArrowDownIcon, ArrowUpIcon, XIcon } from '@modrinth/assets'
59-
import { BulletDivider, ButtonStyled, injectNotificationManager } from '@modrinth/ui'
69+
import {
70+
ArrowDownIcon,
71+
ArrowUpIcon,
72+
LandmarkIcon,
73+
PayPalColorIcon,
74+
VenmoColorIcon,
75+
XIcon,
76+
} from '@modrinth/assets'
77+
import {
78+
BulletDivider,
79+
ButtonStyled,
80+
getCurrencyIcon,
81+
injectNotificationManager,
82+
} from '@modrinth/ui'
6083
import { capitalizeString, formatMoney } from '@modrinth/utils'
6184
import dayjs from 'dayjs'
6285
import { Tooltip } from 'floating-vue'
6386
87+
import { useGeneratedState } from '~/composables/generated'
88+
import { findRail } from '~/utils/muralpay-rails'
89+
6490
type PayoutStatus = 'in-transit' | 'cancelling' | 'cancelled' | 'success' | 'failed'
6591
type PayoutMethodType = 'paypal' | 'venmo' | 'tremendous' | 'muralpay'
6692
type PayoutSource = 'creator_rewards' | 'affilites'
@@ -96,25 +122,93 @@ const emit = defineEmits<{
96122
}>()
97123
98124
const { addNotification } = injectNotificationManager()
125+
const generatedState = useGeneratedState()
99126
100127
const isIncome = computed(() => props.transaction.type === 'payout_available')
101128
129+
const methodIconUrl = computed(() => {
130+
if (props.transaction.type !== 'withdrawal') return null
131+
const method = props.transaction.method_type || props.transaction.method
132+
const methodId = props.transaction.method_id
133+
134+
if (method === 'tremendous' && methodId) {
135+
const methodInfo = generatedState.value.tremendousIdMap?.[methodId]
136+
if (methodInfo?.name?.toLowerCase()?.includes('paypal')) return null
137+
return methodInfo?.image_url ?? null
138+
}
139+
140+
return null
141+
})
142+
143+
const methodIconComponent = computed(() => {
144+
if (props.transaction.type !== 'withdrawal') return null
145+
const method = props.transaction.method_type || props.transaction.method
146+
switch (method) {
147+
case 'paypal':
148+
return PayPalColorIcon
149+
case 'tremendous': {
150+
const methodId = props.transaction.method_id
151+
if (methodId) {
152+
const info = generatedState.value.tremendousIdMap?.[methodId]
153+
if (info?.name?.toLowerCase()?.includes('paypal')) {
154+
return PayPalColorIcon
155+
}
156+
}
157+
return null
158+
}
159+
case 'venmo':
160+
return VenmoColorIcon
161+
case 'muralpay': {
162+
const methodId = props.transaction.method_id
163+
if (methodId) {
164+
const rail = findRail(methodId)
165+
if (rail) {
166+
if (rail.type === 'crypto') {
167+
const currencyIcon = getCurrencyIcon(rail.currency)
168+
if (currencyIcon) return currencyIcon
169+
}
170+
if (rail.type === 'fiat') {
171+
const currencyIcon = getCurrencyIcon(rail.currency)
172+
if (currencyIcon) return currencyIcon
173+
return LandmarkIcon
174+
}
175+
}
176+
}
177+
return null
178+
}
179+
default:
180+
return null
181+
}
182+
})
183+
102184
function formatTransactionStatus(status: string): string {
103185
if (status === 'in-transit') return 'In Transit'
104186
return capitalizeString(status)
105187
}
106188
107-
function formatMethodName(method: string | undefined): string {
189+
const { formatMessage } = useVIntl()
190+
191+
function formatMethodName(method: string | undefined, method_id: string | undefined): string {
108192
if (!method) return 'Unknown'
109193
switch (method) {
110194
case 'paypal':
111195
return 'PayPal'
112196
case 'venmo':
113197
return 'Venmo'
114198
case 'tremendous':
199+
if (method_id) {
200+
const info = generatedState.value.tremendousIdMap?.[method_id]
201+
if (info) return `${info.name}`
202+
}
115203
return 'Tremendous'
116204
case 'muralpay':
117-
return 'Muralpay'
205+
if (method_id) {
206+
const rail = findRail(method_id)
207+
if (rail) {
208+
return formatMessage(rail.name)
209+
}
210+
}
211+
return 'Mural Pay (Unknown)'
118212
default:
119213
return capitalizeString(method)
120214
}

apps/frontend/src/composables/generated.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ export const useGeneratedState = () =>
5454
muralBankDetails: generatedState.muralBankDetails as
5555
| Record<string, { bankNames: string[] }>
5656
| undefined,
57+
tremendousIdMap: generatedState.tremendousIdMap as
58+
| Record<string, { name: string; image_url: string | null }>
59+
| undefined,
5760
countries: (generatedState.countries ?? []) as ISO3166.Country[],
5861
subdivisions: (generatedState.subdivisions ?? {}) as Record<string, ISO3166.Subdivision[]>,
5962

apps/frontend/src/pages/dashboard/revenue/transfers.vue

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,11 @@ import { formatMoney } from '@modrinth/utils'
9595
import dayjs from 'dayjs'
9696
9797
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
98+
import { useGeneratedState } from '~/composables/generated'
99+
import { findRail } from '~/utils/muralpay-rails'
98100
99101
const { formatMessage } = useVIntl()
102+
const generatedState = useGeneratedState()
100103
101104
useHead({
102105
title: 'Transaction history - Modrinth',
@@ -216,10 +219,24 @@ function transactionsToCSV() {
216219
methodOrSource = 'Venmo'
217220
break
218221
case 'tremendous':
222+
if (txn.method_id) {
223+
const info = generatedState.value.tremendousIdMap?.[txn.method_id]
224+
if (info) {
225+
methodOrSource = `Tremendous (${info.name})`
226+
break
227+
}
228+
}
219229
methodOrSource = 'Tremendous'
220230
break
221231
case 'muralpay':
222-
methodOrSource = 'Muralpay'
232+
if (txn.method_id) {
233+
const rail = findRail(txn.method_id)
234+
if (rail) {
235+
methodOrSource = `${rail.name.defaultMessage}`
236+
break
237+
}
238+
}
239+
methodOrSource = 'Mural Pay (Unknown)'
223240
break
224241
default:
225242
methodOrSource = method.charAt(0).toUpperCase() + method.slice(1)

apps/frontend/src/utils/muralpay-rails.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,7 @@ export const MURALPAY_RAILS: Record<string, RailConfig> = {
855855
currency: 'USDC',
856856
type: 'crypto',
857857
fee: '≈ 1%',
858+
railCode: 'blockchain-usdc-polygon',
858859
blockchain: 'POLYGON',
859860
warningMessage: defineMessage({
860861
id: 'muralpay.warning.wallet-address',
@@ -888,6 +889,7 @@ export const MURALPAY_RAILS: Record<string, RailConfig> = {
888889
currency: 'USDC',
889890
type: 'crypto',
890891
fee: '≈ 1%',
892+
railCode: 'blockchain-usdc-base',
891893
blockchain: 'BASE',
892894
warningMessage: defineMessage({
893895
id: 'muralpay.warning.wallet-address',
@@ -924,6 +926,7 @@ export const MURALPAY_RAILS: Record<string, RailConfig> = {
924926
currency: 'USDC',
925927
type: 'crypto',
926928
fee: '≈ 1%',
929+
railCode: 'blockchain-usdc-ethereum',
927930
blockchain: 'ETHEREUM',
928931
warningMessage: defineMessage({
929932
id: 'muralpay.warning.wallet-address',
@@ -957,6 +960,7 @@ export const MURALPAY_RAILS: Record<string, RailConfig> = {
957960
currency: 'USDC',
958961
type: 'crypto',
959962
fee: '≈ 1%',
963+
railCode: 'blockchain-usdc-celo',
960964
blockchain: 'CELO',
961965
warningMessage: defineMessage({
962966
id: 'muralpay.warning.wallet-address',
@@ -996,3 +1000,7 @@ export function getRailsByType(type: 'fiat' | 'crypto'): RailConfig[] {
9961000
export function getRailConfig(railId: string): RailConfig | undefined {
9971001
return MURALPAY_RAILS[railId]
9981002
}
1003+
1004+
export function findRail(railCode: string): RailConfig | undefined {
1005+
return Object.values(MURALPAY_RAILS).find((rail) => rail.railCode === railCode)
1006+
}

packages/api-client/src/modules/labrinth/state/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export class LabrinthStateModule extends AbstractModule {
4040
products,
4141
muralBankDetails,
4242
iso3166Data,
43+
payoutMethods,
4344
] = await Promise.all([
4445
// Tag endpoints
4546
this.client
@@ -114,8 +115,23 @@ export class LabrinthStateModule extends AbstractModule {
114115
this.client.iso3166.data
115116
.build()
116117
.catch((err) => handleError(err, { countries: [], subdivisions: {} })),
118+
119+
// Payout methods for tremendous ID mapping
120+
this.client
121+
.request<Labrinth.State.PayoutMethodInfo[]>('/payout/methods', {
122+
api: 'labrinth',
123+
version: 3,
124+
method: 'GET',
125+
})
126+
.catch((err) => handleError(err, [])),
117127
])
118128

129+
const tremendousIdMap = Object.fromEntries(
130+
(payoutMethods as Labrinth.State.PayoutMethodInfo[])
131+
.filter((m) => m.type === 'tremendous')
132+
.map((m) => [m.id, { name: m.name, image_url: m.image_logo_url }]),
133+
)
134+
119135
return {
120136
categories,
121137
loaders,
@@ -127,6 +143,7 @@ export class LabrinthStateModule extends AbstractModule {
127143
homePageNotifs,
128144
products,
129145
muralBankDetails: muralBankDetails?.bankDetails,
146+
tremendousIdMap,
130147
countries: iso3166Data.countries,
131148
subdivisions: iso3166Data.subdivisions,
132149
errors,

packages/api-client/src/modules/labrinth/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,13 @@ export namespace Labrinth {
699699
}
700700

701701
export namespace State {
702+
export interface PayoutMethodInfo {
703+
id: string
704+
type: string
705+
name: string
706+
image_logo_url: string | null
707+
}
708+
702709
export interface GeneratedState {
703710
categories: Tags.v2.Category[]
704711
loaders: Tags.v2.Loader[]
@@ -711,6 +718,13 @@ export namespace Labrinth {
711718
bankNames: string[]
712719
}
713720
>
721+
tremendousIdMap?: Record<
722+
string,
723+
{
724+
name: string
725+
image_url: string | null
726+
}
727+
>
714728

715729
homePageProjects?: Projects.v2.Project[]
716730
homePageSearch?: Search.v2.SearchResults

0 commit comments

Comments
 (0)