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:
2
.env
2
.env
@@ -1 +1 @@
|
|||||||
VITE_API_URL=http://192.168.1.36:9528
|
VITE_API_URL=http://192.168.1.36:9527
|
||||||
6
auto-imports.d.ts
vendored
6
auto-imports.d.ts
vendored
@@ -52,6 +52,7 @@ declare global {
|
|||||||
const makeDestructurable: typeof import('@vueuse/core').makeDestructurable
|
const makeDestructurable: typeof import('@vueuse/core').makeDestructurable
|
||||||
const markRaw: typeof import('vue').markRaw
|
const markRaw: typeof import('vue').markRaw
|
||||||
const nextTick: typeof import('vue').nextTick
|
const nextTick: typeof import('vue').nextTick
|
||||||
|
const numberPattern: typeof import('./src/utils/pattern').numberPattern
|
||||||
const onActivated: typeof import('vue').onActivated
|
const onActivated: typeof import('vue').onActivated
|
||||||
const onBeforeMount: typeof import('vue').onBeforeMount
|
const onBeforeMount: typeof import('vue').onBeforeMount
|
||||||
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
|
const onBeforeRouteLeave: typeof import('vue-router').onBeforeRouteLeave
|
||||||
@@ -230,6 +231,7 @@ declare global {
|
|||||||
const usePrevious: typeof import('@vueuse/core').usePrevious
|
const usePrevious: typeof import('@vueuse/core').usePrevious
|
||||||
const useRafFn: typeof import('@vueuse/core').useRafFn
|
const useRafFn: typeof import('@vueuse/core').useRafFn
|
||||||
const useRefHistory: typeof import('@vueuse/core').useRefHistory
|
const useRefHistory: typeof import('@vueuse/core').useRefHistory
|
||||||
|
const useResetRef: typeof import('./src/composables/useResetRef').useResetRef
|
||||||
const useResizeObserver: typeof import('@vueuse/core').useResizeObserver
|
const useResizeObserver: typeof import('@vueuse/core').useResizeObserver
|
||||||
const useRoute: typeof import('vue-router').useRoute
|
const useRoute: typeof import('vue-router').useRoute
|
||||||
const useRouter: typeof import('vue-router').useRouter
|
const useRouter: typeof import('vue-router').useRouter
|
||||||
@@ -277,6 +279,7 @@ declare global {
|
|||||||
const useVibrate: typeof import('@vueuse/core').useVibrate
|
const useVibrate: typeof import('@vueuse/core').useVibrate
|
||||||
const useVirtualList: typeof import('@vueuse/core').useVirtualList
|
const useVirtualList: typeof import('@vueuse/core').useVirtualList
|
||||||
const useWakeLock: typeof import('@vueuse/core').useWakeLock
|
const useWakeLock: typeof import('@vueuse/core').useWakeLock
|
||||||
|
const useWalletStore: typeof import('./src/store/wallet').useWalletStore
|
||||||
const useWebNotification: typeof import('@vueuse/core').useWebNotification
|
const useWebNotification: typeof import('@vueuse/core').useWebNotification
|
||||||
const useWebSocket: typeof import('@vueuse/core').useWebSocket
|
const useWebSocket: typeof import('@vueuse/core').useWebSocket
|
||||||
const useWebWorker: typeof import('@vueuse/core').useWebWorker
|
const useWebWorker: typeof import('@vueuse/core').useWebWorker
|
||||||
@@ -362,6 +365,7 @@ declare module 'vue' {
|
|||||||
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
||||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||||
|
readonly numberPattern: UnwrapRef<typeof import('./src/utils/pattern')['numberPattern']>
|
||||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||||
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
||||||
@@ -540,6 +544,7 @@ declare module 'vue' {
|
|||||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||||
|
readonly useResetRef: UnwrapRef<typeof import('./src/composables/useResetRef')['useResetRef']>
|
||||||
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
||||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
||||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
||||||
@@ -587,6 +592,7 @@ declare module 'vue' {
|
|||||||
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
||||||
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
||||||
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
||||||
|
readonly useWalletStore: UnwrapRef<typeof import('./src/store/wallet')['useWalletStore']>
|
||||||
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
||||||
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
||||||
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
|
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
|
||||||
|
|||||||
8
components.d.ts
vendored
8
components.d.ts
vendored
@@ -35,7 +35,11 @@ declare module 'vue' {
|
|||||||
IonLabel: typeof import('@ionic/vue')['IonLabel']
|
IonLabel: typeof import('@ionic/vue')['IonLabel']
|
||||||
IonList: typeof import('@ionic/vue')['IonList']
|
IonList: typeof import('@ionic/vue')['IonList']
|
||||||
IonModal: typeof import('@ionic/vue')['IonModal']
|
IonModal: typeof import('@ionic/vue')['IonModal']
|
||||||
|
IonNavLink: typeof import('@ionic/vue')['IonNavLink']
|
||||||
|
IonNote: typeof import('@ionic/vue')['IonNote']
|
||||||
IonPage: typeof import('@ionic/vue')['IonPage']
|
IonPage: typeof import('@ionic/vue')['IonPage']
|
||||||
|
IonRadio: typeof import('@ionic/vue')['IonRadio']
|
||||||
|
IonRadioGroup: typeof import('@ionic/vue')['IonRadioGroup']
|
||||||
IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
|
IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
|
||||||
IonSearchbar: typeof import('@ionic/vue')['IonSearchbar']
|
IonSearchbar: typeof import('@ionic/vue')['IonSearchbar']
|
||||||
IonSelect: typeof import('@ionic/vue')['IonSelect']
|
IonSelect: typeof import('@ionic/vue')['IonSelect']
|
||||||
@@ -81,7 +85,11 @@ declare global {
|
|||||||
const IonLabel: typeof import('@ionic/vue')['IonLabel']
|
const IonLabel: typeof import('@ionic/vue')['IonLabel']
|
||||||
const IonList: typeof import('@ionic/vue')['IonList']
|
const IonList: typeof import('@ionic/vue')['IonList']
|
||||||
const IonModal: typeof import('@ionic/vue')['IonModal']
|
const IonModal: typeof import('@ionic/vue')['IonModal']
|
||||||
|
const IonNavLink: typeof import('@ionic/vue')['IonNavLink']
|
||||||
|
const IonNote: typeof import('@ionic/vue')['IonNote']
|
||||||
const IonPage: typeof import('@ionic/vue')['IonPage']
|
const IonPage: typeof import('@ionic/vue')['IonPage']
|
||||||
|
const IonRadio: typeof import('@ionic/vue')['IonRadio']
|
||||||
|
const IonRadioGroup: typeof import('@ionic/vue')['IonRadioGroup']
|
||||||
const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
|
const IonRouterOutlet: typeof import('@ionic/vue')['IonRouterOutlet']
|
||||||
const IonSearchbar: typeof import('@ionic/vue')['IonSearchbar']
|
const IonSearchbar: typeof import('@ionic/vue')['IonSearchbar']
|
||||||
const IonSelect: typeof import('@ionic/vue')['IonSelect']
|
const IonSelect: typeof import('@ionic/vue')['IonSelect']
|
||||||
|
|||||||
@@ -31,5 +31,6 @@ export default antfu({
|
|||||||
"unused-imports/no-unused-imports": "off",
|
"unused-imports/no-unused-imports": "off",
|
||||||
"vue/no-deprecated-slot-attribute": "off",
|
"vue/no-deprecated-slot-attribute": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"prefer-promise-reject-errors": "off",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,11 +23,13 @@
|
|||||||
"@elysiajs/eden": "^1.4.5",
|
"@elysiajs/eden": "^1.4.5",
|
||||||
"@ionic/vue": "^8.7.11",
|
"@ionic/vue": "^8.7.11",
|
||||||
"@ionic/vue-router": "^8.7.11",
|
"@ionic/vue-router": "^8.7.11",
|
||||||
"@riwa/api-types": "http://192.168.1.36:9527/api/riwa-api-types-0.0.1.tgz",
|
"@nuxt/ui": "^4.2.1",
|
||||||
|
"@riwa/api-types": "http://192.168.1.36:9527/api/riwa-api-types-0.0.6.tgz",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"better-auth": "^1.4.6",
|
"better-auth": "^1.4.6",
|
||||||
"ionicons": "^8.0.13",
|
"ionicons": "^8.0.13",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.25",
|
"vue": "^3.5.25",
|
||||||
"vue-i18n": "^11.2.2",
|
"vue-i18n": "^11.2.2",
|
||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
|
|||||||
1603
pnpm-lock.yaml
generated
1603
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -7,3 +7,15 @@ export enum AssetCodeEnum {
|
|||||||
USDT = "USDT",
|
USDT = "USDT",
|
||||||
OPTS = "OPTS",
|
OPTS = "OPTS",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum WithdrawMethodEnum {
|
||||||
|
BANK = "bank",
|
||||||
|
CRYPTO = "crypto",
|
||||||
|
CASH = "cash",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ChainEnum {
|
||||||
|
BEP20 = "BEP20",
|
||||||
|
ERC20 = "ERC20",
|
||||||
|
TRC20 = "TRC20",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import type { Treaty } from "@elysiajs/eden";
|
import type { Treaty } from "@elysiajs/eden";
|
||||||
import type { client } from ".";
|
import type { client } from ".";
|
||||||
import type { AssetCodeEnum, PaymentChannelEnum } from "./enum";
|
import type { AssetCodeEnum, PaymentChannelEnum, WithdrawMethodEnum } from "./enum";
|
||||||
|
|
||||||
export type DepositFiatBody = Parameters<typeof client.api.asset.deposit.fiat.post>[0] & {
|
export type DepositFiatBody = Parameters<typeof client.api.deposit.fiat.post>[0] & {
|
||||||
paymentChannel: PaymentChannelEnum;
|
paymentChannel: PaymentChannelEnum;
|
||||||
assetCode: AssetCodeEnum;
|
assetCode: AssetCodeEnum;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DepositFiatData = Treaty.Data<typeof client.api.asset.deposit.fiat.post>;
|
export type DepositFiatData = Treaty.Data<typeof client.api.deposit.fiat.post>;
|
||||||
|
|
||||||
|
export type BalancesData = Treaty.Data<typeof client.api.asset.balances.get>;
|
||||||
|
|
||||||
|
export type WithdrawBody = Omit<Parameters<typeof client.api.asset.withdraw.post>[0], "assetCode" | "withdrawMethod"> & {
|
||||||
|
assetCode: AssetCodeEnum;
|
||||||
|
withdrawMethod: WithdrawMethodEnum;
|
||||||
|
};
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ defineExpose({} as ComponentInstance<typeof UiInput>);
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
margin-left: 8px;
|
|
||||||
color: var(--ion-text-color-secondary);
|
color: var(--ion-text-color-secondary);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
14
src/composables/useResetRef.ts
Normal file
14
src/composables/useResetRef.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { MaybeRef } from "vue";
|
||||||
|
import cloneDeepWith from "lodash-es/cloneDeepWith";
|
||||||
|
import { isRef, ref } from "vue";
|
||||||
|
|
||||||
|
export function useResetRef<T>(value: MaybeRef<T>) {
|
||||||
|
const _valueDefine = cloneDeepWith(value as any);
|
||||||
|
const _value = isRef(value) ? value : ref(value);
|
||||||
|
|
||||||
|
function reset(value?: T) {
|
||||||
|
_value.value = value ? cloneDeepWith(value) : cloneDeepWith(_valueDefine);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [_value, reset] as const;
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { IonicVue } from "@ionic/vue";
|
import { IonicVue } from "@ionic/vue";
|
||||||
|
import ui from "@nuxt/ui/vue-plugin";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
|
|
||||||
@@ -39,10 +41,14 @@ import "@ionic/vue/css/palettes/dark.system.css";
|
|||||||
import "./theme/variables.css";
|
import "./theme/variables.css";
|
||||||
import "./theme/ionic.css";
|
import "./theme/ionic.css";
|
||||||
|
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
.use(IonicVue)
|
.use(IonicVue)
|
||||||
.use(router)
|
.use(router)
|
||||||
.use(i18n);
|
.use(pinia)
|
||||||
|
.use(i18n)
|
||||||
|
.use(ui);
|
||||||
|
|
||||||
router.isReady().then(() => {
|
router.isReady().then(() => {
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: "/deposit/fiat",
|
path: "/deposit/fiat",
|
||||||
component: () => import("@/views/deposit/fiat.vue"),
|
component: () => import("@/views/deposit/fiat.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/withdraw/index",
|
||||||
|
component: () => import("@/views/withdraw/index.vue"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
1
src/store/index.ts
Normal file
1
src/store/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./wallet";
|
||||||
16
src/store/wallet.ts
Normal file
16
src/store/wallet.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { BalancesData } from "@/api/types";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
balances: BalancesData | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useWalletStore = defineStore("wallet", () => {
|
||||||
|
const state = reactive<State>({
|
||||||
|
balances: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -3,7 +3,7 @@ export function formatBalance(amount: MaybeRefOrGetter<number | string>, locale:
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
value = 0;
|
value = 0;
|
||||||
}
|
}
|
||||||
if (typeof value === "string" && !Number.isNaN(Number(value))) {
|
if (typeof value === "string" && Number.isNaN(Number(value))) {
|
||||||
value = 0;
|
value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export const emailPattern = /^(?=.{1,254}$)(?=.{1,64}@)[\w!#$%&'*+/=?^`{|}~-]+(?:\.[\w!#$%&'*+/=?^`{|}~-]+)*@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i;
|
export const emailPattern = /^(?=.{1,254}$)(?=.{1,64}@)[\w!#$%&'*+/=?^`{|}~-]+(?:\.[\w!#$%&'*+/=?^`{|}~-]+)*@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i;
|
||||||
|
export const numberPattern = /^\d+(?:\.\d+)?$/;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
import type { DepositFiatBody } from "@/api/types";
|
import type { DepositFiatBody } from "@/api/types";
|
||||||
|
import { toastController } from "@ionic/vue";
|
||||||
|
import { client } from "@/api";
|
||||||
import { AssetCodeEnum, PaymentChannelEnum } from "@/api/enum";
|
import { AssetCodeEnum, PaymentChannelEnum } from "@/api/enum";
|
||||||
|
|
||||||
const form = ref<DepositFiatBody>({
|
const form = ref<DepositFiatBody>({
|
||||||
@@ -19,11 +21,24 @@ function validate(value: string) {
|
|||||||
if (value === "") {
|
if (value === "") {
|
||||||
return false;
|
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>
|
</script>
|
||||||
|
|
||||||
@@ -38,7 +53,7 @@ function validate(value: string) {
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</IonHeader>
|
</IonHeader>
|
||||||
<IonContent :fullscreen="true" class="ion-padding">
|
<IonContent :fullscreen="true" class="ion-padding">
|
||||||
<div class="flex flex-col gap-10px">
|
<div class="flex flex-col gap-20px">
|
||||||
<ui-input-label
|
<ui-input-label
|
||||||
label="Recharge bank card account"
|
label="Recharge bank card account"
|
||||||
model-value="74321329321312"
|
model-value="74321329321312"
|
||||||
@@ -47,11 +62,22 @@ function validate(value: string) {
|
|||||||
disabled
|
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">
|
<ion-select-option v-for="item in AssetCodeEnum" :key="item" :value="item">
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</ion-select-option>
|
</ion-select-option>
|
||||||
</ion-select>
|
</ion-select> -->
|
||||||
|
|
||||||
<ui-input-label
|
<ui-input-label
|
||||||
ref="inputInstance"
|
ref="inputInstance"
|
||||||
@@ -59,10 +85,17 @@ function validate(value: string) {
|
|||||||
label="Amount"
|
label="Amount"
|
||||||
placeholder="Enter the amount"
|
placeholder="Enter the amount"
|
||||||
type="number"
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
error-text="Please enter a valid amount."
|
error-text="Please enter a valid amount."
|
||||||
@ion-input="validate($event.target.value as string)"
|
@ion-input="validate($event.target.value as string)"
|
||||||
@ion-blur="markTouched"
|
@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>
|
</div>
|
||||||
</IonContent>
|
</IonContent>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
|
|||||||
@@ -1,20 +1,41 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
|
import { onIonViewDidEnter, onIonViewWillEnter } from "@ionic/vue";
|
||||||
import { client } from "@/api";
|
import { client } from "@/api";
|
||||||
import RechargeChannel from "./recharge-channel.vue";
|
import RechargeChannel from "./recharge-channel.vue";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { data } = await client.api.asset.balances.get();
|
const router = useRouter();
|
||||||
|
const { state } = useWalletStore();
|
||||||
const rechargeInstance = ref<ModalInstance>();
|
const rechargeInstance = ref<ModalInstance>();
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
const { data } = await client.api.asset.balances.get();
|
||||||
|
state.balances = data;
|
||||||
|
}
|
||||||
function onCloseModal() {
|
function onCloseModal() {
|
||||||
rechargeInstance.value?.$el.dismiss(null, "confirm");
|
rechargeInstance.value?.$el.dismiss(null, "confirm");
|
||||||
}
|
}
|
||||||
|
function handleWithdraw() {
|
||||||
|
router.push("/withdraw/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
onIonViewWillEnter(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
onIonViewDidEnter(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-20px">
|
<div class="mt-20px shadow-md rounded-6px">
|
||||||
<div class="grid grid-cols-2 gap-20px p-20px bg-[var(--ion-card-background)] rounded-t-6px">
|
<div class="grid grid-cols-2 gap-20px p-20px bg-[var(--ion-card-background)]">
|
||||||
<div v-for="item in data" :key="item.assetCode" class="flex flex-col gap-4px">
|
<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">
|
<div class="ion-text-uppercase text-xs color-text-400 font-500 tracking-0.4px">
|
||||||
{{ item.assetCode }}
|
{{ item.assetCode }}
|
||||||
</div>
|
</div>
|
||||||
@@ -24,26 +45,27 @@ function onCloseModal() {
|
|||||||
</div>
|
</div>
|
||||||
</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-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") }}
|
{{ t("wallet.recharge") }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ion-button expand="block" fill="clear">
|
<ion-button expand="block" fill="clear" @click="handleWithdraw">
|
||||||
{{ t("wallet.withdraw") }}
|
{{ t("wallet.withdraw") }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</div>
|
</div>
|
||||||
</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" />
|
<RechargeChannel @close="onCloseModal" />
|
||||||
</ion-modal>
|
</ion-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.recharge-channel-modal {
|
.recharge-channel-modal,
|
||||||
|
.withdraw-channel-modal {
|
||||||
--height: auto;
|
--height: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</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
@@ -3,6 +3,11 @@
|
|||||||
"composite": true,
|
"composite": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"paths": {
|
||||||
|
"#build/ui": [
|
||||||
|
"./node_modules/.nuxt-ui/ui"
|
||||||
|
]
|
||||||
|
},
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import ui from "@nuxt/ui/vite";
|
||||||
import legacy from "@vitejs/plugin-legacy";
|
import legacy from "@vitejs/plugin-legacy";
|
||||||
import vue from "@vitejs/plugin-vue";
|
import vue from "@vitejs/plugin-vue";
|
||||||
import jsx from "@vitejs/plugin-vue-jsx";
|
import jsx from "@vitejs/plugin-vue-jsx";
|
||||||
@@ -22,7 +23,7 @@ export default defineConfig({
|
|||||||
jsx(),
|
jsx(),
|
||||||
legacy(),
|
legacy(),
|
||||||
autoImport({
|
autoImport({
|
||||||
dirs: ["src/composables", "src/utils"],
|
dirs: ["src/composables", "src/utils", "src/store"],
|
||||||
imports: ["vue", "vue-router", "@vueuse/core", "vue-i18n"],
|
imports: ["vue", "vue-router", "@vueuse/core", "vue-i18n"],
|
||||||
resolvers: [IonicResolver()],
|
resolvers: [IonicResolver()],
|
||||||
vueTemplate: true,
|
vueTemplate: true,
|
||||||
@@ -35,6 +36,7 @@ export default defineConfig({
|
|||||||
autoInstall: true,
|
autoInstall: true,
|
||||||
}),
|
}),
|
||||||
UnoCSS(),
|
UnoCSS(),
|
||||||
|
ui(),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Reference in New Issue
Block a user