You've already forked AstralRinth
forked from didirus/AstralRinth
polish: qa changes for non-usd cards (#4926)
* polish: qa changes for non-usd cards * fix: always show worth * fix: padding
This commit is contained in:
@@ -8,10 +8,21 @@
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
>
|
||||
<div v-if="amount > 0" class="flex flex-col gap-2.5 rounded-[20px] bg-surface-2 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownAmount) }}</span>
|
||||
<span class="font-semibold text-contrast">{{ formatMoney(amount || 0) }}</span>
|
||||
</div>
|
||||
<template v-if="isGiftCard && shouldShowExchangeRate">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownGiftCardValue) }}</span>
|
||||
<span class="font-semibold text-contrast"
|
||||
>{{ formatMoney(amount || 0) }} ({{ formattedLocalCurrency }})</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownAmount) }}</span>
|
||||
<span class="font-semibold text-contrast">{{ formatMoney(amount || 0) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownFee) }}</span>
|
||||
<span class="h-4 font-semibold text-contrast">
|
||||
@@ -21,6 +32,7 @@
|
||||
<template v-else>-{{ formatMoney(fee || 0) }}</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="h-px bg-surface-5" />
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownNetAmount) }}</span>
|
||||
@@ -31,7 +43,7 @@
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<template v-if="shouldShowExchangeRate">
|
||||
<template v-if="shouldShowExchangeRate && !isGiftCard">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-primary">{{ formatMessage(messages.feeBreakdownExchangeRate) }}</span>
|
||||
<span class="text-secondary"
|
||||
@@ -56,10 +68,12 @@ const props = withDefaults(
|
||||
feeLoading: boolean
|
||||
exchangeRate?: number | null
|
||||
localCurrency?: string
|
||||
isGiftCard?: boolean
|
||||
}>(),
|
||||
{
|
||||
exchangeRate: null,
|
||||
localCurrency: undefined,
|
||||
isGiftCard: false,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -115,5 +129,13 @@ const messages = defineMessages({
|
||||
id: 'dashboard.creator-withdraw-modal.fee-breakdown-exchange-rate',
|
||||
defaultMessage: 'FX rate',
|
||||
},
|
||||
feeBreakdownGiftCardValue: {
|
||||
id: 'dashboard.creator-withdraw-modal.fee-breakdown-gift-card-value',
|
||||
defaultMessage: 'Gift card value',
|
||||
},
|
||||
feeBreakdownUsdEquivalent: {
|
||||
id: 'dashboard.creator-withdraw-modal.fee-breakdown-usd-equivalent',
|
||||
defaultMessage: 'USD equivalent',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -161,24 +161,32 @@
|
||||
|
||||
<div class="flex flex-col gap-2.5">
|
||||
<label>
|
||||
<span class="text-md font-semibold text-contrast"
|
||||
>{{ formatMessage(formFieldLabels.amount) }}
|
||||
<template v-if="useDenominationSuggestions"> ({{ selectedMethodCurrencyCode }})</template>
|
||||
<span class="text-red">*</span></span
|
||||
>
|
||||
<span class="text-md font-semibold text-contrast">
|
||||
<template v-if="useDenominationSuggestions">
|
||||
{{ formatMessage(messages.searchAmountLabel) }} ({{ selectedMethodCurrencyCode }})
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatMessage(formFieldLabels.amount) }}
|
||||
</template>
|
||||
<span class="text-red">*</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div v-if="showGiftCardSelector && useFixedDenominations" class="flex flex-col gap-2.5">
|
||||
<template v-if="useDenominationSuggestions">
|
||||
<input
|
||||
v-model.number="denominationSearchInput"
|
||||
type="number"
|
||||
step="0.01"
|
||||
:min="0"
|
||||
:placeholder="formatMessage(messages.enterDenominationPlaceholder)"
|
||||
class="w-full rounded-[14px] bg-surface-4 px-4 py-3 text-contrast placeholder:text-secondary sm:py-2.5"
|
||||
@input="hasTouchedSuggestions = true"
|
||||
/>
|
||||
<div class="iconified-input w-full">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
<input
|
||||
v-model.number="denominationSearchInput"
|
||||
type="number"
|
||||
step="0.01"
|
||||
:min="0"
|
||||
:disabled="effectiveMinAmount > roundedMaxAmount"
|
||||
:placeholder="formatMessage(messages.enterDenominationPlaceholder)"
|
||||
class="!bg-surface-4"
|
||||
@input="hasTouchedSuggestions = true"
|
||||
/>
|
||||
</div>
|
||||
<Transition
|
||||
enter-active-class="transition-opacity duration-200 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
@@ -187,8 +195,24 @@
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<span v-if="!denominationSearchInput" class="text-sm text-secondary">
|
||||
{{ formatMessage(messages.enterAmountHint) }}
|
||||
<span
|
||||
v-if="
|
||||
selectedMethodCurrencyCode &&
|
||||
selectedMethodCurrencyCode !== 'USD' &&
|
||||
selectedMethodExchangeRate
|
||||
"
|
||||
class="text-sm text-secondary"
|
||||
>
|
||||
{{
|
||||
formatMessage(messages.balanceWorthHint, {
|
||||
usdBalance: formatMoney(roundedMaxAmount),
|
||||
localBalance: formatAmountForDisplay(
|
||||
roundedMaxAmount,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</Transition>
|
||||
</template>
|
||||
@@ -202,24 +226,60 @@
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
>
|
||||
<div
|
||||
v-if="!useDenominationSuggestions || displayedSuggestions.length > 0"
|
||||
class="overflow-hidden"
|
||||
:class="[useDenominationSuggestions ? 'p-[2px]' : '']"
|
||||
v-if="
|
||||
!useDenominationSuggestions ||
|
||||
(denominationSearchInput && displayedSuggestions.length > 0)
|
||||
"
|
||||
class="overflow-hidden pt-0"
|
||||
>
|
||||
<Chips
|
||||
v-model="selectedDenomination"
|
||||
:items="useDenominationSuggestions ? displayedSuggestions : denominationOptions"
|
||||
:format-label="
|
||||
(amt: number) =>
|
||||
formatAmountForDisplay(
|
||||
amt,
|
||||
<span
|
||||
v-if="useDenominationSuggestions"
|
||||
class="mb-1 block text-sm font-medium text-secondary"
|
||||
>
|
||||
{{ formatMessage(messages.availableDenominationsLabel) }}
|
||||
</span>
|
||||
<div class="p-[2px]">
|
||||
<Chips
|
||||
v-model="selectedDenomination"
|
||||
:items="useDenominationSuggestions ? displayedSuggestions : denominationOptions"
|
||||
:format-label="
|
||||
(amt: number) =>
|
||||
formatAmountForDisplay(
|
||||
amt,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
)
|
||||
"
|
||||
:never-empty="false"
|
||||
:capitalize="false"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
v-if="useDenominationSuggestions && hasTouchedSuggestions && !hasSelectedDenomination"
|
||||
class="mt-2.5 block text-sm text-orange"
|
||||
>
|
||||
{{ formatMessage(messages.selectDenominationRequired) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
!useDenominationSuggestions &&
|
||||
selectedMethodCurrencyCode &&
|
||||
selectedMethodCurrencyCode !== 'USD' &&
|
||||
selectedMethodExchangeRate
|
||||
"
|
||||
class="mt-2 block text-sm text-secondary"
|
||||
>
|
||||
{{
|
||||
formatMessage(messages.balanceWorthHint, {
|
||||
usdBalance: formatMoney(roundedMaxAmount),
|
||||
localBalance: formatAmountForDisplay(
|
||||
roundedMaxAmount,
|
||||
selectedMethodCurrencyCode,
|
||||
selectedMethodExchangeRate,
|
||||
)
|
||||
"
|
||||
:never-empty="false"
|
||||
:capitalize="false"
|
||||
/>
|
||||
),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
@@ -243,27 +303,6 @@
|
||||
</span>
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
enter-active-class="transition-opacity duration-200 ease-out"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-active-class="transition-opacity duration-150 ease-in"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<span
|
||||
v-if="
|
||||
useDenominationSuggestions &&
|
||||
hasTouchedSuggestions &&
|
||||
displayedSuggestions.length > 0 &&
|
||||
!hasSelectedDenomination
|
||||
"
|
||||
class="text-sm text-orange"
|
||||
>
|
||||
{{ formatMessage(messages.selectDenominationRequired) }}
|
||||
</span>
|
||||
</Transition>
|
||||
|
||||
<span
|
||||
v-if="!useDenominationSuggestions && denominationOptions.length === 0"
|
||||
class="text-error text-sm"
|
||||
@@ -284,12 +323,15 @@
|
||||
</div>
|
||||
|
||||
<WithdrawFeeBreakdown
|
||||
v-if="allRequiredFieldsFilled"
|
||||
v-if="allRequiredFieldsFilled && formData.amount && formData.amount > 0"
|
||||
:amount="formData.amount || 0"
|
||||
:fee="calculatedFee"
|
||||
:fee-loading="feeLoading"
|
||||
:exchange-rate="giftCardExchangeRate"
|
||||
:local-currency="giftCardCurrencyCode"
|
||||
:exchange-rate="showGiftCardSelector ? selectedMethodExchangeRate : giftCardExchangeRate"
|
||||
:local-currency="
|
||||
showGiftCardSelector ? (selectedMethodCurrencyCode ?? undefined) : giftCardCurrencyCode
|
||||
"
|
||||
:is-gift-card="showGiftCardSelector"
|
||||
/>
|
||||
|
||||
<Checkbox v-model="agreedTerms">
|
||||
@@ -308,6 +350,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SearchIcon } from '@modrinth/assets'
|
||||
import {
|
||||
Admonition,
|
||||
Checkbox,
|
||||
@@ -607,23 +650,23 @@ const useDenominationSuggestions = computed(() => {
|
||||
if (!useFixedDenominations.value) return false
|
||||
const interval = selectedMethodDetails.value?.interval
|
||||
if (!interval?.fixed?.values) return false
|
||||
return interval.fixed.values.length > 35
|
||||
return interval.fixed.values.length > 10
|
||||
})
|
||||
|
||||
const denominationSuggestions = computed(() => {
|
||||
const input = denominationSearchInput.value
|
||||
if (!input || input <= 0) return []
|
||||
|
||||
const allDenominations = denominationOptions.value
|
||||
if (allDenominations.length === 0) return []
|
||||
|
||||
// convert local currency input to USD for filtering (denominations are stored in USD)
|
||||
const exchangeRate = selectedMethodExchangeRate.value
|
||||
const inputInUsd = exchangeRate ? input / exchangeRate : input
|
||||
const input = denominationSearchInput.value
|
||||
|
||||
const rangeSize = inputInUsd * 0.2
|
||||
let lowerBound = inputInUsd - rangeSize / 2
|
||||
let upperBound = inputInUsd + rangeSize / 2
|
||||
// When no search input, use the user's balance as the target
|
||||
const exchangeRate = selectedMethodExchangeRate.value
|
||||
const targetInUsd =
|
||||
input && input > 0 ? (exchangeRate ? input / exchangeRate : input) : roundedMaxAmount.value
|
||||
|
||||
const rangeSize = targetInUsd * 0.2
|
||||
let lowerBound = targetInUsd - rangeSize / 2
|
||||
let upperBound = targetInUsd + rangeSize / 2
|
||||
|
||||
const minAvailable = allDenominations[0]
|
||||
const maxAvailable = allDenominations[allDenominations.length - 1]
|
||||
@@ -650,14 +693,15 @@ const displayedSuggestions = computed(() => {
|
||||
if (all.length <= maxDisplayedSuggestions) return all
|
||||
|
||||
const input = denominationSearchInput.value
|
||||
if (!input) return all.slice(0, maxDisplayedSuggestions)
|
||||
|
||||
const exchangeRate = selectedMethodExchangeRate.value
|
||||
const inputInUsd = exchangeRate ? input / exchangeRate : input
|
||||
|
||||
// select values closest to input, then sort ascending for display
|
||||
// Use balance as target when no search input
|
||||
const targetInUsd =
|
||||
input && input > 0 ? (exchangeRate ? input / exchangeRate : input) : roundedMaxAmount.value
|
||||
|
||||
// select values closest to target, then sort ascending for display
|
||||
const closest = [...all]
|
||||
.sort((a, b) => Math.abs(a - inputInUsd) - Math.abs(b - inputInUsd))
|
||||
.sort((a, b) => Math.abs(a - targetInUsd) - Math.abs(b - targetInUsd))
|
||||
.slice(0, maxDisplayedSuggestions)
|
||||
|
||||
return closest.sort((a, b) => a - b)
|
||||
@@ -896,13 +940,29 @@ watch(
|
||||
|
||||
watch(selectedGiftCardId, (newId, oldId) => {
|
||||
if (oldId && newId !== oldId) {
|
||||
// Reset denomination search when gift card changes
|
||||
denominationSearchInput.value = undefined
|
||||
// Reset state when gift card changes
|
||||
hasTouchedSuggestions.value = false
|
||||
formData.value.amount = undefined
|
||||
// denominationSearchInput will be prefilled by the watch below
|
||||
denominationSearchInput.value = undefined
|
||||
}
|
||||
})
|
||||
|
||||
// Prefill denomination search with balance in local currency when suggestions mode is enabled
|
||||
watch(
|
||||
[useDenominationSuggestions, selectedMethodExchangeRate],
|
||||
([showSuggestions, exchangeRate]) => {
|
||||
if (showSuggestions && denominationSearchInput.value === undefined) {
|
||||
const balanceInLocal = exchangeRate
|
||||
? roundedMaxAmount.value * exchangeRate
|
||||
: roundedMaxAmount.value
|
||||
denominationSearchInput.value = Math.floor(balanceInLocal * 100) / 100
|
||||
hasTouchedSuggestions.value = true
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
async function switchToDirectPaypal() {
|
||||
withdrawData.value.selection.country = {
|
||||
id: 'US',
|
||||
@@ -985,7 +1045,19 @@ const messages = defineMessages({
|
||||
},
|
||||
enterAmountHint: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.enter-amount-hint',
|
||||
defaultMessage: 'Enter an amount to see available gift card denominations',
|
||||
defaultMessage: 'Find gift cards near this value.',
|
||||
},
|
||||
balanceWorthHint: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.balance-worth-hint',
|
||||
defaultMessage: 'Your balance of {usdBalance} is currently worth {localBalance}.',
|
||||
},
|
||||
searchAmountLabel: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.search-amount-label',
|
||||
defaultMessage: 'Search amount',
|
||||
},
|
||||
availableDenominationsLabel: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.available-denominations-label',
|
||||
defaultMessage: 'Available denominations',
|
||||
},
|
||||
selectDenominationHint: {
|
||||
id: 'dashboard.creator-withdraw-modal.tremendous-details.select-denomination-hint',
|
||||
|
||||
@@ -668,9 +668,15 @@
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-fee": {
|
||||
"message": "Fee"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-gift-card-value": {
|
||||
"message": "Gift card value"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-net-amount": {
|
||||
"message": "Net amount"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.fee-breakdown-usd-equivalent": {
|
||||
"message": "USD equivalent"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.kyc.business-entity": {
|
||||
"message": "Business entity"
|
||||
},
|
||||
@@ -815,8 +821,14 @@
|
||||
"dashboard.creator-withdraw-modal.tax-form-required.header": {
|
||||
"message": "Tax form required"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.available-denominations-label": {
|
||||
"message": "Available denominations"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.balance-worth-hint": {
|
||||
"message": "Your balance of {usdBalance} is currently worth {localBalance}."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.enter-amount-hint": {
|
||||
"message": "Enter an amount to see available gift card denominations"
|
||||
"message": "Find gift cards near this value."
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.enter-denomination-placeholder": {
|
||||
"message": "Enter amount"
|
||||
@@ -833,6 +845,9 @@
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.reward-plural": {
|
||||
"message": "Rewards"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.search-amount-label": {
|
||||
"message": "Search amount"
|
||||
},
|
||||
"dashboard.creator-withdraw-modal.tremendous-details.select-denomination-hint": {
|
||||
"message": "Select a denomination:"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user