You've already forked AstralRinth
forked from didirus/AstralRinth
polish: withdraw flow fixes (#4713)
* fix: negative value stuff * fix: mobile responsiveness for modal min-w * feat: better error handling on withdraw * fix: empty state positioning + svg sizing * fix: title case -> sentence case * fix: re-add virtual visa under gift cards * fix: hide <1% segments
This commit is contained in:
@@ -38,7 +38,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full max-w-[496px] lg:min-w-[496px]">
|
||||
<div class="mx-auto w-full max-w-[496px] sm:mx-0 sm:min-w-[496px]">
|
||||
<TaxFormStage
|
||||
v-if="currentStage === 'tax-form'"
|
||||
:balance="balance"
|
||||
@@ -326,6 +326,73 @@ function continueWithLimit() {
|
||||
setStage(nextStep.value)
|
||||
}
|
||||
|
||||
// TODO: God we need better errors from the backend (e.g error ids), this shit is insane
|
||||
function getWithdrawalError(error: any): { title: string; text: string } {
|
||||
const description = error?.data?.description?.toLowerCase() || ''
|
||||
|
||||
// Tax form error
|
||||
if (description.includes('tax form')) {
|
||||
return {
|
||||
title: formatMessage(messages.errorTaxFormTitle),
|
||||
text: formatMessage(messages.errorTaxFormText),
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid crypto wallet address
|
||||
if (
|
||||
(description.includes('wallet') && description.includes('invalid')) ||
|
||||
description.includes('wallet_address') ||
|
||||
(description.includes('blockchain') && description.includes('invalid'))
|
||||
) {
|
||||
return {
|
||||
title: formatMessage(messages.errorInvalidWalletTitle),
|
||||
text: formatMessage(messages.errorInvalidWalletText),
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid bank details
|
||||
if (
|
||||
(description.includes('bank') || description.includes('account')) &&
|
||||
(description.includes('invalid') || description.includes('failed'))
|
||||
) {
|
||||
return {
|
||||
title: formatMessage(messages.errorInvalidBankTitle),
|
||||
text: formatMessage(messages.errorInvalidBankText),
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid/fraudulent address
|
||||
if (
|
||||
description.includes('address') &&
|
||||
(description.includes('invalid') ||
|
||||
description.includes('verification') ||
|
||||
description.includes('fraudulent'))
|
||||
) {
|
||||
return {
|
||||
title: formatMessage(messages.errorInvalidAddressTitle),
|
||||
text: formatMessage(messages.errorInvalidAddressText),
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum amount not met
|
||||
if (
|
||||
description.includes('payoutminimumnotmeterror') ||
|
||||
description.includes('minimum') ||
|
||||
(description.includes('amount') && description.includes('less'))
|
||||
) {
|
||||
return {
|
||||
title: formatMessage(messages.errorMinimumNotMetTitle),
|
||||
text: formatMessage(messages.errorMinimumNotMetText),
|
||||
}
|
||||
}
|
||||
|
||||
// Generic fallback
|
||||
return {
|
||||
title: formatMessage(messages.errorGenericTitle),
|
||||
text: formatMessage(messages.errorGenericText),
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWithdraw() {
|
||||
if (isSubmitting.value) return
|
||||
|
||||
@@ -336,19 +403,12 @@ async function handleWithdraw() {
|
||||
} catch (error) {
|
||||
console.error('Withdrawal failed:', error)
|
||||
|
||||
if ((error as any)?.data?.description?.toLower?.()?.includes('Tax form')) {
|
||||
addNotification({
|
||||
title: 'Please complete tax form',
|
||||
text: 'You must complete a tax form to submit your withdrawal request.',
|
||||
type: 'error',
|
||||
})
|
||||
} else {
|
||||
addNotification({
|
||||
title: 'Unable to withdraw',
|
||||
text: 'We were unable to submit your withdrawal request, please check your details or contact support.',
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
const { title, text } = getWithdrawalError(error)
|
||||
addNotification({
|
||||
title,
|
||||
text,
|
||||
type: 'error',
|
||||
})
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
@@ -453,5 +513,58 @@ const messages = defineMessages({
|
||||
id: 'dashboard.withdraw.completion.transactions-button',
|
||||
defaultMessage: 'Transactions',
|
||||
},
|
||||
errorTaxFormTitle: {
|
||||
id: 'dashboard.withdraw.error.tax-form.title',
|
||||
defaultMessage: 'Please complete tax form',
|
||||
},
|
||||
errorTaxFormText: {
|
||||
id: 'dashboard.withdraw.error.tax-form.text',
|
||||
defaultMessage: 'You must complete a tax form to submit your withdrawal request.',
|
||||
},
|
||||
errorInvalidWalletTitle: {
|
||||
id: 'dashboard.withdraw.error.invalid-wallet.title',
|
||||
defaultMessage: 'Invalid wallet address',
|
||||
},
|
||||
errorInvalidWalletText: {
|
||||
id: 'dashboard.withdraw.error.invalid-wallet.text',
|
||||
defaultMessage:
|
||||
'The crypto wallet address you provided is invalid. Please double-check and try again.',
|
||||
},
|
||||
errorInvalidBankTitle: {
|
||||
id: 'dashboard.withdraw.error.invalid-bank.title',
|
||||
defaultMessage: 'Invalid bank details',
|
||||
},
|
||||
errorInvalidBankText: {
|
||||
id: 'dashboard.withdraw.error.invalid-bank.text',
|
||||
defaultMessage:
|
||||
'The bank account details you provided are invalid. Please verify your information.',
|
||||
},
|
||||
errorInvalidAddressTitle: {
|
||||
id: 'dashboard.withdraw.error.invalid-address.title',
|
||||
defaultMessage: 'Address verification failed',
|
||||
},
|
||||
errorInvalidAddressText: {
|
||||
id: 'dashboard.withdraw.error.invalid-address.text',
|
||||
defaultMessage:
|
||||
'The address you provided could not be verified. Please check your address details.',
|
||||
},
|
||||
errorMinimumNotMetTitle: {
|
||||
id: 'dashboard.withdraw.error.minimum-not-met.title',
|
||||
defaultMessage: 'Amount too low',
|
||||
},
|
||||
errorMinimumNotMetText: {
|
||||
id: 'dashboard.withdraw.error.minimum-not-met.text',
|
||||
defaultMessage:
|
||||
"The withdrawal amount (after fees) doesn't meet the minimum requirement. Please increase your withdrawal amount.",
|
||||
},
|
||||
errorGenericTitle: {
|
||||
id: 'dashboard.withdraw.error.generic.title',
|
||||
defaultMessage: 'Unable to withdraw',
|
||||
},
|
||||
errorGenericText: {
|
||||
id: 'dashboard.withdraw.error.generic.text',
|
||||
defaultMessage:
|
||||
'We were unable to submit your withdrawal request, please check your details or contact support.',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
type="number"
|
||||
step="0.01"
|
||||
:min="minAmount"
|
||||
:max="maxAmount"
|
||||
:max="safeMaxAmount"
|
||||
:disabled="isDisabled"
|
||||
:placeholder="formatMessage(formFieldPlaceholders.amountPlaceholder)"
|
||||
class="w-full rounded-[14px] bg-surface-4 py-2.5 pl-4 pr-4 text-contrast placeholder:text-secondary"
|
||||
@input="handleInput"
|
||||
@@ -26,13 +27,13 @@
|
||||
</template>
|
||||
</Combobox>
|
||||
<ButtonStyled>
|
||||
<button class="px-4 py-2" @click="setMaxAmount">
|
||||
<button class="px-4 py-2" :disabled="isDisabled" @click="setMaxAmount">
|
||||
{{ formatMessage(commonMessages.maxButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div>
|
||||
<span class="my-1 mt-0 text-secondary">{{ formatMoney(maxAmount) }} available.</span>
|
||||
<span class="my-1 mt-0 text-secondary">{{ formatMoney(safeMaxAmount) }} available.</span>
|
||||
<Transition name="fade">
|
||||
<span v-if="isBelowMinimum" class="text-red">
|
||||
Amount must be at least {{ formatMoney(minAmount) }}.
|
||||
@@ -40,7 +41,7 @@
|
||||
</Transition>
|
||||
<Transition name="fade">
|
||||
<span v-if="isAboveMaximum" class="text-red">
|
||||
Amount cannot exceed {{ formatMoney(maxAmount) }}.
|
||||
Amount cannot exceed {{ formatMoney(safeMaxAmount) }}.
|
||||
</span>
|
||||
</Transition>
|
||||
</div>
|
||||
@@ -77,6 +78,14 @@ const emit = defineEmits<{
|
||||
const { formatMessage } = useVIntl()
|
||||
const amountInput = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const safeMaxAmount = computed(() => {
|
||||
return Math.max(0, props.maxAmount)
|
||||
})
|
||||
|
||||
const isDisabled = computed(() => {
|
||||
return safeMaxAmount.value < 0.01
|
||||
})
|
||||
|
||||
const isBelowMinimum = computed(() => {
|
||||
return (
|
||||
props.modelValue !== undefined && props.modelValue > 0 && props.modelValue < props.minAmount
|
||||
@@ -84,11 +93,11 @@ const isBelowMinimum = computed(() => {
|
||||
})
|
||||
|
||||
const isAboveMaximum = computed(() => {
|
||||
return props.modelValue !== undefined && props.modelValue > props.maxAmount
|
||||
return props.modelValue !== undefined && props.modelValue > safeMaxAmount.value
|
||||
})
|
||||
|
||||
async function setMaxAmount() {
|
||||
const maxValue = props.maxAmount
|
||||
const maxValue = safeMaxAmount.value
|
||||
emit('update:modelValue', maxValue)
|
||||
|
||||
await nextTick()
|
||||
@@ -119,11 +128,11 @@ watch(
|
||||
() => props.modelValue,
|
||||
async (newAmount) => {
|
||||
if (newAmount !== undefined && newAmount !== null) {
|
||||
if (newAmount > props.maxAmount) {
|
||||
emit('update:modelValue', props.maxAmount)
|
||||
if (newAmount > safeMaxAmount.value) {
|
||||
emit('update:modelValue', safeMaxAmount.value)
|
||||
await nextTick()
|
||||
if (amountInput.value) {
|
||||
amountInput.value.value = props.maxAmount.toFixed(2)
|
||||
amountInput.value.value = safeMaxAmount.value.toFixed(2)
|
||||
}
|
||||
} else if (newAmount < 0) {
|
||||
emit('update:modelValue', 0)
|
||||
|
||||
@@ -325,7 +325,7 @@ const dynamicDocumentNumberField = computed(() => {
|
||||
return {
|
||||
name: 'documentNumber',
|
||||
type: 'text' as const,
|
||||
label: labelMap[documentType] || 'Document Number',
|
||||
label: labelMap[documentType] || 'Document number',
|
||||
placeholder: placeholderMap[documentType] || 'Enter document number',
|
||||
required: true,
|
||||
}
|
||||
@@ -474,23 +474,23 @@ const messages = defineMessages({
|
||||
},
|
||||
documentNumberNationalId: {
|
||||
id: 'dashboard.creator-withdraw-modal.muralpay-details.document-number-national-id',
|
||||
defaultMessage: 'National ID Number',
|
||||
defaultMessage: 'National ID number',
|
||||
},
|
||||
documentNumberPassport: {
|
||||
id: 'dashboard.creator-withdraw-modal.muralpay-details.document-number-passport',
|
||||
defaultMessage: 'Passport Number',
|
||||
defaultMessage: 'Passport number',
|
||||
},
|
||||
documentNumberResidentId: {
|
||||
id: 'dashboard.creator-withdraw-modal.muralpay-details.document-number-resident-id',
|
||||
defaultMessage: 'Resident ID Number',
|
||||
defaultMessage: 'Resident ID number',
|
||||
},
|
||||
documentNumberRuc: {
|
||||
id: 'dashboard.creator-withdraw-modal.muralpay-details.document-number-ruc',
|
||||
defaultMessage: 'RUC Number',
|
||||
defaultMessage: 'RUC number',
|
||||
},
|
||||
documentNumberTaxId: {
|
||||
id: 'dashboard.creator-withdraw-modal.muralpay-details.document-number-tax-id',
|
||||
defaultMessage: 'Tax ID Number',
|
||||
defaultMessage: 'Tax ID number',
|
||||
},
|
||||
documentNumberNationalIdPlaceholder: {
|
||||
id: 'dashboard.creator-withdraw-modal.muralpay-details.document-number-national-id-placeholder',
|
||||
|
||||
@@ -529,7 +529,11 @@ onMounted(async () => {
|
||||
|
||||
rewardOptions.value = methods
|
||||
.filter((m) => m.type === 'tremendous')
|
||||
.filter((m) => m.category === selectedMethod)
|
||||
.filter(
|
||||
(m) =>
|
||||
m.category === selectedMethod ||
|
||||
(selectedMethod === 'merchant_card' && m.category === 'visa_card'),
|
||||
)
|
||||
.map((m) => ({
|
||||
value: m.id,
|
||||
label: m.name,
|
||||
|
||||
Reference in New Issue
Block a user