feat: add withdraw functionality and related enums
- Introduced WithdrawMethodEnum and ChainEnum in enum.ts for withdrawal methods and blockchain types. - Updated types.ts to include WithdrawBody type for withdrawal requests. - Created a new useResetRef composable for managing form state resets. - Added a withdraw page with form handling in index.vue, including validation and submission logic. - Integrated the new withdraw functionality into the wallet card component. - Updated the main.ts file to include Pinia for state management. - Created a wallet store to manage user balances. - Modified the deposit page to improve user experience and validation. - Added number pattern validation for input fields. - Updated the router to include a new route for the withdraw page. - Refactored input-label component styles for better layout. - Added a new rules.ts file for future validation rules.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<script lang='ts' setup>
|
||||
import type { DepositFiatBody } from "@/api/types";
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { client } from "@/api";
|
||||
import { AssetCodeEnum, PaymentChannelEnum } from "@/api/enum";
|
||||
|
||||
const form = ref<DepositFiatBody>({
|
||||
@@ -19,11 +21,24 @@ function validate(value: string) {
|
||||
if (value === "") {
|
||||
return false;
|
||||
}
|
||||
const isEmailValid = emailPattern.test(form.value.amount);
|
||||
const isNumber = numberPattern.test(value);
|
||||
|
||||
isEmailValid ? inputInstance.value?.$el.classList.add("ion-valid") : inputInstance.value?.$el.classList.add("ion-invalid");
|
||||
isNumber ? inputInstance.value?.$el.classList.add("ion-valid") : inputInstance.value?.$el.classList.add("ion-invalid");
|
||||
|
||||
return isEmailValid;
|
||||
return isNumber;
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
const { data, status } = await client.api.deposit.fiat.post(form.value);
|
||||
if (status === 200) {
|
||||
const toast = await toastController.create({
|
||||
message: "Submission successful!",
|
||||
duration: 1500,
|
||||
position: "bottom",
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -38,7 +53,7 @@ function validate(value: string) {
|
||||
</ion-toolbar>
|
||||
</IonHeader>
|
||||
<IonContent :fullscreen="true" class="ion-padding">
|
||||
<div class="flex flex-col gap-10px">
|
||||
<div class="flex flex-col gap-20px">
|
||||
<ui-input-label
|
||||
label="Recharge bank card account"
|
||||
model-value="74321329321312"
|
||||
@@ -47,11 +62,22 @@ function validate(value: string) {
|
||||
disabled
|
||||
/>
|
||||
|
||||
<ion-select v-model="form.assetCode" label="Select Currency" placeholder="Select One" label-placement="floating">
|
||||
<ion-radio-group v-model="form.assetCode">
|
||||
<ion-label class="text-sm">
|
||||
Choose Currency
|
||||
</ion-label>
|
||||
<ion-item v-for="item in AssetCodeEnum" :key="item">
|
||||
<ion-radio :value="item" justify="space-between">
|
||||
{{ item }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
|
||||
<!-- <ion-select v-model="form.assetCode" label="Select Currency" placeholder="Select One" label-placement="floating">
|
||||
<ion-select-option v-for="item in AssetCodeEnum" :key="item" :value="item">
|
||||
{{ item }}
|
||||
</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-select> -->
|
||||
|
||||
<ui-input-label
|
||||
ref="inputInstance"
|
||||
@@ -59,10 +85,17 @@ function validate(value: string) {
|
||||
label="Amount"
|
||||
placeholder="Enter the amount"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
error-text="Please enter a valid amount."
|
||||
@ion-input="validate($event.target.value as string)"
|
||||
@ion-blur="markTouched"
|
||||
/>
|
||||
|
||||
<ion-note>Please make sure to enter the correct amount. After submission, the funds will be credited to your account after review in the background.</ion-note>
|
||||
|
||||
<ion-button expand="block" @click="onSubmit">
|
||||
Submit
|
||||
</ion-button>
|
||||
</div>
|
||||
</IonContent>
|
||||
</ion-page>
|
||||
|
||||
@@ -1,20 +1,41 @@
|
||||
<script lang='ts' setup>
|
||||
import { onIonViewDidEnter, onIonViewWillEnter } from "@ionic/vue";
|
||||
import { client } from "@/api";
|
||||
import RechargeChannel from "./recharge-channel.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { data } = await client.api.asset.balances.get();
|
||||
const router = useRouter();
|
||||
const { state } = useWalletStore();
|
||||
const rechargeInstance = ref<ModalInstance>();
|
||||
|
||||
async function init() {
|
||||
const { data } = await client.api.asset.balances.get();
|
||||
state.balances = data;
|
||||
}
|
||||
function onCloseModal() {
|
||||
rechargeInstance.value?.$el.dismiss(null, "confirm");
|
||||
}
|
||||
function handleWithdraw() {
|
||||
router.push("/withdraw/index");
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
onIonViewWillEnter(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
onIonViewDidEnter(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-20px">
|
||||
<div class="grid grid-cols-2 gap-20px p-20px bg-[var(--ion-card-background)] rounded-t-6px">
|
||||
<div v-for="item in data" :key="item.assetCode" class="flex flex-col gap-4px">
|
||||
<div class="mt-20px shadow-md rounded-6px">
|
||||
<div class="grid grid-cols-2 gap-20px p-20px bg-[var(--ion-card-background)]">
|
||||
<div v-for="item in state.balances" :key="item.assetCode" class="flex flex-col gap-4px">
|
||||
<div class="ion-text-uppercase text-xs color-text-400 font-500 tracking-0.4px">
|
||||
{{ item.assetCode }}
|
||||
</div>
|
||||
@@ -24,26 +45,27 @@ function onCloseModal() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-10px pb-20px bg-[var(--ion-card-background)] rounded-b-6px">
|
||||
<div class="px-10px pb-20px bg-[var(--ion-card-background)]">
|
||||
<ion-buttons class="gap-10px" expand="block">
|
||||
<ion-button id="open-modal" expand="block" fill="clear">
|
||||
<ion-button id="open-recharge-modal" expand="block" fill="clear">
|
||||
{{ t("wallet.recharge") }}
|
||||
</ion-button>
|
||||
|
||||
<ion-button expand="block" fill="clear">
|
||||
<ion-button expand="block" fill="clear" @click="handleWithdraw">
|
||||
{{ t("wallet.withdraw") }}
|
||||
</ion-button>
|
||||
</ion-buttons>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ion-modal ref="rechargeInstance" class="recharge-channel-modal" trigger="open-modal" :initial-breakpoint="1" :breakpoints="[0, 1]">
|
||||
<ion-modal ref="rechargeInstance" class="recharge-channel-modal" trigger="open-recharge-modal" :initial-breakpoint="1" :breakpoints="[0, 1]">
|
||||
<RechargeChannel @close="onCloseModal" />
|
||||
</ion-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.recharge-channel-modal {
|
||||
.recharge-channel-modal,
|
||||
.withdraw-channel-modal {
|
||||
--height: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
143
src/views/withdraw/index.vue
Normal file
143
src/views/withdraw/index.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<script lang='ts' setup>
|
||||
import type { WithdrawBody } from "@/api/types";
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { client } from "@/api";
|
||||
import { AssetCodeEnum, ChainEnum, WithdrawMethodEnum } from "@/api/enum";
|
||||
|
||||
const amountInputInst = useTemplateRef<InputInstance>("amountInputInst");
|
||||
const [form, resetForm] = useResetRef<WithdrawBody>({
|
||||
assetCode: AssetCodeEnum.USDT,
|
||||
amount: "",
|
||||
withdrawMethod: WithdrawMethodEnum.BANK,
|
||||
toAddress: "",
|
||||
bankAccountId: "",
|
||||
chain: "BEP20",
|
||||
});
|
||||
const { state } = useWalletStore();
|
||||
const maxAmount = computed(() => {
|
||||
const balance = state.balances?.find(item => item.assetCode === form.value.assetCode);
|
||||
return balance ? balance.available : "0";
|
||||
});
|
||||
|
||||
function markTouched() {
|
||||
amountInputInst.value?.$el.classList.add("ion-touched");
|
||||
}
|
||||
function validate(value: string) {
|
||||
amountInputInst.value?.$el.classList.remove("ion-valid");
|
||||
amountInputInst.value?.$el.classList.remove("ion-invalid");
|
||||
|
||||
if (value === "") {
|
||||
return false;
|
||||
}
|
||||
const isNumber = numberPattern.test(value);
|
||||
|
||||
isNumber ? amountInputInst.value?.$el.classList.add("ion-valid") : amountInputInst.value?.$el.classList.add("ion-invalid");
|
||||
|
||||
return isNumber;
|
||||
}
|
||||
function handleCurrentChange() {
|
||||
form.value.amount = "";
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
const { data, status } = await client.api.asset.withdraw.post(form.value);
|
||||
if (status === 200) {
|
||||
const toast = await toastController.create({
|
||||
message: "Submission successful!",
|
||||
duration: 1500,
|
||||
position: "bottom",
|
||||
});
|
||||
|
||||
await toast.present();
|
||||
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ion-page>
|
||||
<IonHeader>
|
||||
<ion-toolbar class="ui-toolbar">
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button />
|
||||
</ion-buttons>
|
||||
<ion-title>Withdraw</ion-title>
|
||||
</ion-toolbar>
|
||||
</IonHeader>
|
||||
<IonContent :fullscreen="true" class="ion-padding">
|
||||
<div class="flex flex-col gap-20px">
|
||||
<ion-radio-group v-model="form.assetCode" @ion-change="handleCurrentChange">
|
||||
<ion-label class="text-sm">
|
||||
Choose Currency
|
||||
</ion-label>
|
||||
<ion-item v-for="item in AssetCodeEnum" :key="item">
|
||||
<ion-radio :value="item" justify="space-between">
|
||||
{{ item }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
|
||||
<ion-radio-group v-model="form.withdrawMethod">
|
||||
<ion-label class="text-sm">
|
||||
Choose Withdraw Method
|
||||
</ion-label>
|
||||
<ion-item v-for="item in WithdrawMethodEnum" :key="item">
|
||||
<ion-radio :value="item" justify="space-between">
|
||||
{{ item }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
|
||||
<ui-input-label
|
||||
ref="amountInputInst"
|
||||
v-model="form.amount"
|
||||
label="Amount"
|
||||
:placeholder="`Enter the amount (Max: ${maxAmount})`"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
error-text="Please enter a valid amount."
|
||||
:max="maxAmount"
|
||||
@ion-input="validate($event.target.value as string)"
|
||||
@ion-blur="markTouched"
|
||||
/>
|
||||
|
||||
<ui-input-label
|
||||
v-if="form.withdrawMethod === WithdrawMethodEnum.BANK"
|
||||
v-model="form.bankAccountId"
|
||||
label="Bank Account ID"
|
||||
placeholder="Enter the bank account ID"
|
||||
type="text"
|
||||
inputmode="text"
|
||||
error-text="Please enter a valid bank account ID."
|
||||
/>
|
||||
<template v-else-if="form.withdrawMethod === WithdrawMethodEnum.CRYPTO">
|
||||
<ion-radio-group v-model="form.chain">
|
||||
<ion-label class="text-sm">
|
||||
Choose Chain
|
||||
</ion-label>
|
||||
<ion-item v-for="item in ChainEnum" :key="item">
|
||||
<ion-radio :value="item" justify="space-between">
|
||||
{{ item }}
|
||||
</ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
<ui-input-label
|
||||
v-model="form.toAddress"
|
||||
label="Crypto Address"
|
||||
placeholder="Enter the crypto address"
|
||||
type="text"
|
||||
inputmode="text"
|
||||
error-text="Please enter a valid crypto address."
|
||||
/>
|
||||
</template>
|
||||
|
||||
<ion-button expand="block" @click="onSubmit">
|
||||
Submit
|
||||
</ion-button>
|
||||
</div>
|
||||
</IonContent>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped></style>
|
||||
0
src/views/withdraw/rules.ts
Normal file
0
src/views/withdraw/rules.ts
Normal file
Reference in New Issue
Block a user