This commit is contained in:
Nadar
2026-03-17 13:24:22 +03:00
commit 82e5ac9d81
554 changed files with 29637 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
<template>
<div class="create-invoice">
<Transition
name="fade"
mode="out-in"
>
<PageForm
v-if="!invoiceLink"
title="Creating invoice"
:back-link="`/projects/${$route.params.projectId}/invoices`"
:back-text="$t('back_to', [project.name])"
:submit-text="$t('create')"
:handler="onSubmit"
>
<UiInput
id="amount"
label="Invoice amount"
rules="required"
:mask="{ mask: Number }"
>
<template #suffix>
<CurrencyName
code="USDT"
size="small"
/>
</template>
</UiInput>
</PageForm>
<div
v-else
class="create-invoice__summary"
>
<UiButton
icon="chevron-left"
color="secondary"
type="link"
class="mb-8"
:href="`/projects/${$route.params.projectId}/invoices`"
>
{{ $t('back_to', [project.name]) }}
</UiButton>
<h1 class="mb-32">
Invoice created
</h1>
<UiAlert
type="neutral"
class="mb-24"
>
<span> Creation time</span>
{{ dayjs().format('DD.MM.YY HH:mm') }}.
<span>It will expires in </span>
<strong>30 minutes.</strong>
</UiAlert>
<UiInput
id="invoice_link"
copyable
inputmode="none"
label="Ссылка на счет"
:model-value="invoiceLink"
readonly
/>
<UiButton
size="large"
class="w-100 mt-24"
:href="`/projects/${route.params.projectId}/invoices`"
>
Done
</UiButton>
</div>
</Transition>
</div>
</template>
<script setup>
import { v4 as uuidv4 } from 'uuid'
import dayjs from 'dayjs'
definePageMeta({
middleware: ['auth'],
centerContent: true,
})
const route = useRoute()
const { data: project } = await useAsyncData(
'project',
() =>
$api(`/online_stores/${route.params.projectId}`, {
method: 'get',
}),
{},
)
if (!project.value) {
throw createError({
statusCode: 404,
statusMessage: 'Page Not Found',
})
}
const notify = useNotify()
const runtimeConfig = useRuntimeConfig()
const invoiceLink = ref('')
async function onSubmit(values) {
try {
const result = await $api('/invoices', {
method: 'post',
body: {
...values,
currencyCode: 'USDT',
onlineStoreId: +route.params.projectId,
orderId: uuidv4(),
},
})
invoiceLink.value = `${runtimeConfig.public.payHost}/${result.invoiceId}`
notify({
type: 'positive',
text: 'Инвойс успешно создан',
})
}
catch (e) {
setStaticError({
status: e.status,
message: 'Something went wrong',
})
}
}
</script>
<style lang="scss">
.create-invoice {
&__summary {
width: 500px;
margin: 0 auto;
}
}
</style>

View File

@@ -0,0 +1,451 @@
<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>