Files
Kotyata/apps/client/pages/create/withdraw/[projectId]/[assetCode].vue
2026-03-17 13:24:22 +03:00

452 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<form
class="create-withdraw"
@submit="onSubmit"
>
<Transition
name="shift"
mode="out-in"
>
<div
v-if="!showSummary"
class="withdraw-form"
title="Withdrawal request"
>
<FormHeader
class="withdraw-form__header"
title="Withdrawal request"
:back-link="`/projects/${$route.params.projectId}`"
:back-text="$t('back_to', [project.name])"
/>
<div class="d-flex align-items-center justify-content-between mb-16">
<CurrencyName
:code="route.params.assetCode"
name="Tether"
class="create-withdraw__currency-name"
/>
<p class="create-withdraw__balance">
{{ balance }}
</p>
</div>
<div
class="p-16 bg-clr-grey-200"
style="border-radius: 16px"
>
<UiInput
id="paymentAddress"
rules="required"
label="Withdrawal wallet"
/>
<NetworkSelect
id="network"
:asset-code="route.params.assetCode"
model-value="TRC20"
class="mt-6"
/>
</div>
<div
class="mt-16 p-16 bg-clr-grey-200"
style="border-radius: 16px"
>
<UiInput
id="amount"
rules="required"
:label="`Минимальная сумма ${minAmount} ${route.params.assetCode}`"
:mask="{
mask: Number,
min: minAmount,
max: maxAmount,
scale: exponent,
}"
>
<template #suffix>
<UiButton
type="link"
size="small"
@click="setValues({ amount: maxAmount }, false)"
>
{{ $t('maximum') }}
</UiButton>
</template>
<template #caption>
<div class="create-withdraw__limit">
<span>Доступный лимит на вывод - 1000 USDT</span>
<UiButton
class="create-withdraw__upgrade"
type="link"
size="small"
href="/verification"
>
Повысить лимит
</UiButton>
</div>
</template>
</UiInput>
</div>
<div class="withdraw-form__bottom">
<div class="withdraw-amount withdraw-amount--fee">
<div class="withdraw-amount__title">
{{ $t('fee') }}
</div>
<div class="withdraw-amount__value">
{{ fee }}
</div>
</div>
<div class="withdraw-amount">
<div class="withdraw-amount__title">
К отправке
</div>
<div class="withdraw-amount__value">
{{ total }}
</div>
</div>
<UiButton
size="large"
class="create-withdraw__continue"
native-type="submit"
:disabled="balance <= 0"
>
{{ $t('continue') }}
</UiButton>
</div>
</div>
<div
v-else
class="withdraw-summary"
>
<FormHeader
class="withdraw-summary__header"
title="Confirm the withdrawal"
:back-link="`/projects/${$route.params.projectId}`"
:back-text="$t('back_to', [project.name])"
/>
<dl class="withdraw-summary__details withdraw-details">
<dt>Withdrawal wallet</dt>
<dd>
<TextShortener :text="values.paymentAddress" />
</dd>
<dt>Network</dt>
<dd class="text-clr-green-500">
<CurrencyName
:code="route.params.assetCode"
size="small"
/>
</dd>
<dt>{{ $t('fee') }}</dt>
<dd>{{ fee }}</dd>
<dt>К отправке</dt>
<dd class="withdraw-details__amount">
{{ total }}
</dd>
</dl>
<UiAlert class="withdraw-summary__alert">
Пожалуйста, подтвердите, что операцию инициировали именно вы.
</UiAlert>
<UiCodeInput
id="pincode"
class="withdraw-summary__pincode"
title="2FA-код"
/>
<div class="withdraw-summary__actions">
<UiButton
color="secondary"
size="large"
@click="showSummary = false"
>
{{ $t('back') }}
</UiButton>
<UiButton
size="large"
native-type="submit"
:loading="isSubmitting"
>
{{ $t('withdraw') }}
</UiButton>
</div>
</div>
</Transition>
</form>
</template>
<script setup>
import { v4 as uuidv4 } from 'uuid'
import { useForm } from 'vee-validate'
import { computedAsync } from '@vueuse/core'
import Decimal from 'decimal.js'
definePageMeta({
middleware: ['auth'],
centerContent: true,
})
const route = useRoute()
const { data: project } = await useAsyncData(
'project',
() => {
return $api(`/online_stores/${route.params.projectId}`, {
method: 'GET',
})
},
)
if (!project.value) {
throw createError({
statusCode: 404,
statusMessage: 'Page Not Found',
})
}
const notify = useNotify()
const { t } = useI18n()
const { data: tariff } = await useAsyncData('tariff', () => {
return $api(`/tariffs/current?currency=${route.params.assetCode}`)
})
const { data: account } = await useAsyncData('account', () =>
$api(`/accounts/${project.value.accountIds[0]}`, {
method: 'GET',
}))
const showSummary = ref(false)
const { isSubmitting, values, setValues, handleSubmit } = useForm({ keepValuesOnUnmount: true })
const commission = computedAsync(async () => {
if (values.amount) {
try {
const result = await $api(`/withdrawals/commission`, { method: 'GET', params: { amount: values.amount, currency: route.params.assetCode } })
return result?.commission ?? 0
}
catch {
return 0
}
}
else {
return 0
}
}, 0, { lazy: true })
const fee = computed(() => $money.fullFormat(commission.value, route.params.assetCode))
const balance = computed(() => $money.format(account.value.balance, route.params.assetCode))
const maxAmount = computed(() => Math.min(1000, account.value.balance))
const minAmount = computed(() => tariff.value?.data.minAmount)
const exponent = computed(() => $money.getExponent(route.params.assetCode))
const total = computed(() => $money.fullFormat(Decimal.sub(values.amount || 0, commission.value), route.params.assetCode))
const onSubmit = handleSubmit(async (values) => {
if (!showSummary.value) {
showSummary.value = true
return
}
if (values.pincode !== '123123') {
notify({
id: 'withdraw_error',
text: t('invalid_otp_code'),
type: 'negative',
})
return
}
try {
// const idempotencyKey = `${route.params.projectId + values.paymentAddress + route.params.assetCode}TRON${values.amount}`
await $api('/withdrawals', {
method: 'POST',
body: {
...values,
idempotencyKey: uuidv4().substring(0, 20),
amount: +values.amount,
onlineStoreId: +route.params.projectId,
currencyCode: route.params.assetCode,
blockchainCode: 'TRON',
saveWallet: false,
pincode: undefined,
network: undefined,
},
})
notify({
id: 'withdraw_success',
type: 'positive',
text: 'The withdrawal request has been successfully processed',
})
navigateTo(`/projects/${route.params.projectId}`)
}
catch {
notify({
id: 'withdraw_error',
text: t('something_went_wrong'),
type: 'negative',
})
}
})
</script>
<style lang="scss">
.create-withdraw {
&__currency-name {
@include txt-l-sb('currency-name', true);
@include txt-r-m('currency-name-code', true);
--currency-name-gap: 2px 16px;
}
&__balance {
@include txt-l-sb;
}
&__continue {
flex: 1;
}
&__limit {
@include txt-r-m;
display: flex;
gap: 8px;
align-items: center;
color: $clr-grey-500;
margin-top: 8px;
}
}
.withdraw-form,
.withdraw-summary {
width: 500px;
margin: 0 auto;
&__header {
margin-bottom: 32px;
}
}
.withdraw-form {
&__bottom {
display: flex;
gap: 16px;
margin-top: 32px;
}
}
.withdraw-summary {
&__details {
margin-bottom: 16px;
}
&__alert {
margin-bottom: 16px;
}
&__pincode {
padding-block: 16px;
}
&__actions {
display: flex;
gap: 16px;
margin-top: 32px;
> * {
flex: 1;
}
}
}
.withdraw-amount {
$self: &;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 8px;
height: 48px;
flex: 0 0 146px;
&--fee {
flex: 0 0 100px
}
&__title {
@include txt-r-b;
color: $clr-grey-500;
}
&__value {
@include txt-l-sb;
white-space: nowrap;
#{$self}--fee & {
@include txt-i-sb;
color: $clr-grey-600;
}
}
& + & {
border-left: 1px solid $clr-grey-300;
padding-left: 16px;
}
}
.withdraw-details {
@include txt-i-m;
@include txt-i-m('text-shortener', true);
@include txt-i-m('currency-name-code', true);
border-radius: 12px;
background-color: $clr-grey-200;
display: grid;
grid-template-columns: repeat(2, 1fr);
row-gap: 16px;
color: $clr-grey-500;
padding: 16px;
dt, dd {
margin: 0;
}
dt {
color: $clr-grey-600;
}
dd {
text-align: right;
}
&__amount {
@include txt-l-sb;
color: $clr-black;
}
}
</style>