Skip to content

Commit 49bc253

Browse files
authored
[PLA-1677] transfer balance feature (#85)
1 parent bfa6bd3 commit 49bc253

File tree

6 files changed

+189
-11
lines changed

6 files changed

+189
-11
lines changed

resources/js/api/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,19 @@ export class ApiService {
213213

214214
return ApiService.sendPlatformRequest(data);
215215
}
216+
217+
static async transferBalance(transferBalanceData: Record<string, unknown>) {
218+
const data = {
219+
query: mutations.TransferBalance,
220+
variables: {
221+
recipient: transferBalanceData.recipient,
222+
amount: transferBalanceData.amount,
223+
keepAlive: transferBalanceData.keepAlive,
224+
idempotencyKey: transferBalanceData.idempotencyKey,
225+
skipValidation: transferBalanceData.skipValidation,
226+
},
227+
};
228+
229+
return ApiService.sendPlatformRequest(data);
230+
}
216231
}

resources/js/api/mutations.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import RemoveAllAttributes from '~/graphql/mutation/RemoveAllAttributes';
2121
import Freeze from '~/graphql/mutation/Freeze';
2222
import Thaw from '~/graphql/mutation/Thaw';
2323
import UpdateTransaction from '~/graphql/mutation/UpdateTransaction';
24+
import TransferBalance from '~/graphql/mutation/TransferBalance';
2425

2526
import BatchMint from '~/graphql/mutation/batch/BatchMint';
2627
import BatchTransfer from '~/graphql/mutation/batch/BatchTransfer';
@@ -87,6 +88,7 @@ export default {
8788
Freeze,
8889
Thaw,
8990
UpdateTransaction,
91+
TransferBalance,
9092

9193
BatchMint,
9294
BatchTransfer,

resources/js/components/FormInput.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
`block flex-1 flex-shrink-0 border-0 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-primary text-sm leading-6 overflow-hidden transition-all disabled:bg-gray-100 ${inputClass}`,
3838
]"
3939
:placeholder="placeholder"
40+
autocomplete="off"
4041
oninput="validity.valid||(value=value.replace(/\D+/g, ''))"
4142
@input="inputChange"
4243
@keyup.enter="emit('submit')"

resources/js/components/pages/Wallets.vue

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@
22
<div class="px-4 sm:px-6 lg:px-8 bg-white m-4 sm:m-8 shadow rounded-lg overflow-y-auto h-full transition-all">
33
<div class="mt-4 flow-root">
44
<div class="md:-mx-6 lg:-mx-8 transition-all">
5-
<div class="sm:py-2 sm:px-6 lg:px-8 mb-2 w-full">
6-
<div class="flex flex-wrap gap-4">
7-
<div class="" v-for="searchInput in searchInputs" :key="searchInput.name">
8-
<FormInput
9-
v-model="searchInput.value"
10-
:name="searchInput.name"
11-
:label="searchInput.label"
12-
:placeholder="searchInput.placeholder"
13-
@input-change="searchChange"
14-
/>
5+
<div class="flex md:flex-row flex-col-reverse justify-between">
6+
<div class="sm:py-2 sm:px-6 lg:px-8 mb-2 w-full flex justify-between">
7+
<div class="flex flex-wrap gap-4">
8+
<div class="" v-for="searchInput in searchInputs" :key="searchInput.name">
9+
<FormInput
10+
v-model="searchInput.value"
11+
:name="searchInput.name"
12+
:label="searchInput.label"
13+
:placeholder="searchInput.placeholder"
14+
@input-change="searchChange"
15+
/>
16+
</div>
1517
</div>
1618
</div>
19+
<div class="flex md:px-6 lg:px-8 py-2 mb-2 items-end">
20+
<Btn primary @click="openModalSlide('TransferBalanceSlideover')"> Transfer Balance </Btn>
21+
</div>
1722
</div>
23+
1824
<LoadingContent
1925
class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8"
2026
:is-loading="isLoading"
@@ -98,6 +104,7 @@ import snackbar, { events } from '~/util/snackbar';
98104
import FormInput from '~/components/FormInput.vue';
99105
import NoItems from '~/components/NoItems.vue';
100106
import { TransactionState } from '~/types/types.enums';
107+
import Btn from '../Btn.vue';
101108
102109
const isLoading = ref(false);
103110
const isPaginationLoading = ref(false);
@@ -199,7 +206,7 @@ const getWallet = async () => {
199206
}
200207
};
201208
202-
const openModalSlide = (componentName: string, wallet) => {
209+
const openModalSlide = (componentName: string, wallet?: any) => {
203210
let componentPath = 'common';
204211
slideComponent.value = { componentName, componentPath, ...wallet };
205212
modalSlide.value = true;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<template>
2+
<Form
3+
ref="formRef"
4+
class="flex h-full flex-col divide-y divide-gray-200 bg-white shadow-xl"
5+
:validation-schema="validation"
6+
@submit="transfer"
7+
>
8+
<h3 class="text-xl font-semibold px-4 sm:px-6 py-4 text-gray-900">Transfer Balance</h3>
9+
<div class="h-0 flex-1 overflow-y-auto">
10+
<div class="flex flex-1 flex-col justify-between">
11+
<div class="divide-y divide-gray-200 px-4 sm:px-6">
12+
<div class="space-y-6 pt-6 pb-5">
13+
<FormInput
14+
v-model="recipient"
15+
name="recipient"
16+
label="Recipient"
17+
description="The recipient account who is going to receive the transfer."
18+
required
19+
tooltip="Wallet Address"
20+
/>
21+
<FormInput
22+
v-model="amount"
23+
name="amount"
24+
label="Amount"
25+
description="The amount to transfer."
26+
type="number"
27+
required
28+
:prefix="currencySymbol"
29+
/>
30+
<FormInput
31+
v-if="useAppStore().advanced"
32+
v-model="idempotencyKey"
33+
name="idempotencyKey"
34+
label="Idempotency Key"
35+
description="The idempotency key to set. It is recommended to use a UUID for this."
36+
tooltip="In mathematical and computer science terms, idempotency is a property of certain operations that can be applied repeated times without changing the initial result of the application."
37+
readmore="Idempotency Key"
38+
/>
39+
<FormCheckbox
40+
v-if="useAppStore().advanced"
41+
v-model="keepAlive"
42+
name="keepAlive"
43+
label="Keep Alive"
44+
description="If true, the transaction will fail if the balance drops below the minimum requirement. Defaults to False."
45+
/>
46+
<FormCheckbox
47+
v-if="useAppStore().advanced"
48+
v-model="skipValidation"
49+
name="skipValidation"
50+
label="Skip validation"
51+
description="Skip all validation rules, use with caution. Defaults to false."
52+
/>
53+
</div>
54+
</div>
55+
</div>
56+
</div>
57+
<div class="flex space-x-3 flex-shrink-0 justify-end px-4 py-4">
58+
<Btn @click="closeSlide">Cancel</Btn>
59+
<Btn :loading="isLoading" :disabled="isLoading" primary is-submit>Transfer</Btn>
60+
</div>
61+
</Form>
62+
</template>
63+
64+
<script setup lang="ts">
65+
import { Form } from 'vee-validate';
66+
import * as yup from 'yup';
67+
import { computed, ref } from 'vue';
68+
import FormInput from '~/components/FormInput.vue';
69+
import FormCheckbox from '~/components/FormCheckbox.vue';
70+
import Btn from '~/components/Btn.vue';
71+
import { ApiService } from '~/api';
72+
import snackbar from '~/util/snackbar';
73+
import { currencySymbolByNetwork, formatData, formatPriceToENJ, snackbarErrors } from '~/util';
74+
import { useAppStore } from '~/store';
75+
import {
76+
addressRequiredSchema,
77+
booleanNotRequiredSchema,
78+
numberRequiredSchema,
79+
stringNotRequiredSchema,
80+
} from '~/util/schemas';
81+
82+
const emit = defineEmits(['close']);
83+
84+
const isLoading = ref(false);
85+
const recipient = ref('');
86+
const amount = ref('');
87+
const idempotencyKey = ref('');
88+
const keepAlive = ref(false);
89+
const skipValidation = ref(false);
90+
const formRef = ref();
91+
92+
const currencySymbol = computed(() => currencySymbolByNetwork(useAppStore().config.network));
93+
94+
const validation = yup.object({
95+
recipient: addressRequiredSchema,
96+
amount: numberRequiredSchema.typeError('Amount must be a number').min(0),
97+
idempotencyKey: stringNotRequiredSchema,
98+
keepALive: booleanNotRequiredSchema,
99+
skipValidation: booleanNotRequiredSchema,
100+
});
101+
102+
const transfer = async () => {
103+
await formRef.value?.validate();
104+
if (!formRef.value?.getMeta().valid) return;
105+
106+
try {
107+
isLoading.value = true;
108+
const res = await ApiService.transferBalance(
109+
formatData({
110+
recipient: recipient.value,
111+
amount: formatPriceToENJ(amount.value) ?? null,
112+
keepAlive: keepAlive.value,
113+
idempotencyKey: idempotencyKey.value,
114+
skipValidation: skipValidation.value,
115+
})
116+
);
117+
118+
const id = res.data?.TransferBalance?.id;
119+
120+
if (id) {
121+
snackbar.success({
122+
title: 'Transfer Balance',
123+
text: `Transfer Balance successfully with transaction id: ${id}`,
124+
event: id,
125+
});
126+
closeSlide();
127+
}
128+
} catch (e) {
129+
if (snackbarErrors(e)) return;
130+
snackbar.error({
131+
title: 'Transfer Balance',
132+
text: 'Transfer Balance failed',
133+
});
134+
} finally {
135+
isLoading.value = false;
136+
}
137+
};
138+
139+
const closeSlide = () => {
140+
emit('close');
141+
};
142+
</script>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default `mutation TransferBalance($recipient: String!, $amount: BigInt!, $keepAlive: Boolean = false, $idempotencyKey: String, $skipValidation: Boolean = false) {
2+
TransferBalance(
3+
recipient: $recipient
4+
amount: $amount
5+
keepAlive: $keepAlive
6+
idempotencyKey: $idempotencyKey
7+
skipValidation: $skipValidation
8+
) {
9+
id
10+
}
11+
}`;

0 commit comments

Comments
 (0)