Compare commits
6
Commits
9e776df79f
...
564b016f47
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
564b016f47 | ||
|
|
82932ce7c8 | ||
|
|
33600d5c1c | ||
|
|
7640f5d74d | ||
|
|
35052661f8 | ||
|
|
5f1c8bee4b |
@@ -1,6 +1,6 @@
|
|||||||
import { ServiceUnavailableException } from '@nestjs/common';
|
import { ServiceUnavailableException } from '@nestjs/common';
|
||||||
import type { ConfigService } from '@nestjs/config';
|
import type { ConfigService } from '@nestjs/config';
|
||||||
import { BitcoinWalletSyncStatus } from '../types/BitcoinWalletSyncStatus';
|
import { WalletSyncStatus } from '../../../types/wallet/WalletSyncStatus';
|
||||||
import type { ElectrumWalletRpcClient } from './ElectrumWalletRpcClient';
|
import type { ElectrumWalletRpcClient } from './ElectrumWalletRpcClient';
|
||||||
import { BitcoinWalletAdminService } from './BitcoinWalletAdminService';
|
import { BitcoinWalletAdminService } from './BitcoinWalletAdminService';
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ describe('BitcoinWalletAdminService', () => {
|
|||||||
rpcVersion: '4.8.1',
|
rpcVersion: '4.8.1',
|
||||||
blockHeight: 900_000,
|
blockHeight: 900_000,
|
||||||
serverHeight: 900_000,
|
serverHeight: 900_000,
|
||||||
syncStatus: BitcoinWalletSyncStatus.Synced,
|
syncStatus: WalletSyncStatus.Synced,
|
||||||
balanceBtc: '0.00150000',
|
balanceBtc: '0.00150000',
|
||||||
confirmedBalanceBtc: '0.00150000'
|
confirmedBalanceBtc: '0.00150000'
|
||||||
})
|
})
|
||||||
@@ -63,7 +63,7 @@ describe('BitcoinWalletAdminService', () => {
|
|||||||
|
|
||||||
const status = await service.getStatus();
|
const status = await service.getStatus();
|
||||||
|
|
||||||
expect(status.syncStatus).toBe(BitcoinWalletSyncStatus.Syncing);
|
expect(status.syncStatus).toBe(WalletSyncStatus.Syncing);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when RPC calls fail', async () => {
|
it('throws when RPC calls fail', async () => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Injectable, ServiceUnavailableException } from '@nestjs/common';
|
|||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { convertBtcAtomicToBtc } from '../../../utils/bitcoin/convertBtcAtomicToBtc';
|
import { convertBtcAtomicToBtc } from '../../../utils/bitcoin/convertBtcAtomicToBtc';
|
||||||
import type { Config } from '../../../types/Config';
|
import type { Config } from '../../../types/Config';
|
||||||
import { BitcoinWalletSyncStatus } from '../types/BitcoinWalletSyncStatus';
|
import { WalletSyncStatus } from '../../../types/wallet/WalletSyncStatus';
|
||||||
import type { BitcoinWalletStatusView } from '../types/BitcoinWalletStatusView';
|
import type { BitcoinWalletStatusView } from '../types/BitcoinWalletStatusView';
|
||||||
import { ElectrumWalletRpcClient } from './ElectrumWalletRpcClient';
|
import { ElectrumWalletRpcClient } from './ElectrumWalletRpcClient';
|
||||||
|
|
||||||
@@ -47,15 +47,15 @@ export class BitcoinWalletAdminService {
|
|||||||
isSynchronized: boolean,
|
isSynchronized: boolean,
|
||||||
blockHeight: number | null,
|
blockHeight: number | null,
|
||||||
serverHeight: number | null
|
serverHeight: number | null
|
||||||
): BitcoinWalletSyncStatus {
|
): WalletSyncStatus {
|
||||||
if (isSynchronized) {
|
if (isSynchronized) {
|
||||||
return BitcoinWalletSyncStatus.Synced;
|
return WalletSyncStatus.Synced;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockHeight === null || serverHeight === null) {
|
if (blockHeight === null || serverHeight === null) {
|
||||||
return BitcoinWalletSyncStatus.Unknown;
|
return WalletSyncStatus.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
return BitcoinWalletSyncStatus.Syncing;
|
return WalletSyncStatus.Syncing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { ElectrumNetwork } from '../../../types/ElectrumNetwork';
|
import { ElectrumNetwork } from '../../../types/ElectrumNetwork';
|
||||||
import { BitcoinWalletSyncStatus } from './BitcoinWalletSyncStatus';
|
import { WalletSyncStatus } from '../../../types/wallet/WalletSyncStatus';
|
||||||
|
|
||||||
export interface BitcoinWalletStatusView {
|
export interface BitcoinWalletStatusView {
|
||||||
network: ElectrumNetwork;
|
network: ElectrumNetwork;
|
||||||
rpcVersion: string;
|
rpcVersion: string;
|
||||||
blockHeight: number | null;
|
blockHeight: number | null;
|
||||||
serverHeight: number | null;
|
serverHeight: number | null;
|
||||||
syncStatus: BitcoinWalletSyncStatus;
|
syncStatus: WalletSyncStatus;
|
||||||
balanceBtc: string;
|
balanceBtc: string;
|
||||||
confirmedBalanceBtc: string;
|
confirmedBalanceBtc: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
export enum BitcoinWalletSyncStatus {
|
|
||||||
Synced = 'synced',
|
|
||||||
Syncing = 'syncing',
|
|
||||||
Unknown = 'unknown'
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ import { BadRequestException, ServiceUnavailableException } from '@nestjs/common
|
|||||||
import type { ConfigService } from '@nestjs/config';
|
import type { ConfigService } from '@nestjs/config';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import type { AuthService } from '../../auth/services/AuthService';
|
import type { AuthService } from '../../auth/services/AuthService';
|
||||||
import { MoneroWalletSyncStatus } from '../types/MoneroWalletSyncStatus';
|
import { WalletSyncStatus } from '../../../types/wallet/WalletSyncStatus';
|
||||||
import type { MoneroWalletRpcClient } from './MoneroWalletRpcClient';
|
import type { MoneroWalletRpcClient } from './MoneroWalletRpcClient';
|
||||||
import { MoneroWalletAdminService } from './MoneroWalletAdminService';
|
import { MoneroWalletAdminService } from './MoneroWalletAdminService';
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ describe('MoneroWalletAdminService', () => {
|
|||||||
rpcVersion: '0.18.3.1',
|
rpcVersion: '0.18.3.1',
|
||||||
walletHeight: 3_000_000,
|
walletHeight: 3_000_000,
|
||||||
daemonHeight: 3_000_000,
|
daemonHeight: 3_000_000,
|
||||||
syncStatus: MoneroWalletSyncStatus.Synced,
|
syncStatus: WalletSyncStatus.Synced,
|
||||||
balanceXmr: '2.00000000',
|
balanceXmr: '2.00000000',
|
||||||
unlockedBalanceXmr: '1.00000000'
|
unlockedBalanceXmr: '1.00000000'
|
||||||
})
|
})
|
||||||
@@ -137,7 +137,7 @@ describe('MoneroWalletAdminService', () => {
|
|||||||
|
|
||||||
const status = await service.getStatus();
|
const status = await service.getStatus();
|
||||||
|
|
||||||
expect(status.syncStatus).toBe(MoneroWalletSyncStatus.Unknown);
|
expect(status.syncStatus).toBe(WalletSyncStatus.Unknown);
|
||||||
expect(status.daemonHeight).toBeNull();
|
expect(status.daemonHeight).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ describe('MoneroWalletAdminService', () => {
|
|||||||
|
|
||||||
const status = await service.getStatus();
|
const status = await service.getStatus();
|
||||||
|
|
||||||
expect(status.syncStatus).toBe(MoneroWalletSyncStatus.Synced);
|
expect(status.syncStatus).toBe(WalletSyncStatus.Synced);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws when reveal seed RPC fails', async () => {
|
it('throws when reveal seed RPC fails', async () => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { Config } from '../../../types/Config';
|
|||||||
import type { MoneroDaemonGetInfoResult } from '../types/MoneroDaemonGetInfoResult';
|
import type { MoneroDaemonGetInfoResult } from '../types/MoneroDaemonGetInfoResult';
|
||||||
import type { MoneroWalletRevealSeedResult } from '../types/MoneroWalletRevealSeedResult';
|
import type { MoneroWalletRevealSeedResult } from '../types/MoneroWalletRevealSeedResult';
|
||||||
import type { MoneroWalletStatusView } from '../types/MoneroWalletStatusView';
|
import type { MoneroWalletStatusView } from '../types/MoneroWalletStatusView';
|
||||||
import { MoneroWalletSyncStatus } from '../types/MoneroWalletSyncStatus';
|
import { WalletSyncStatus } from '../../../types/wallet/WalletSyncStatus';
|
||||||
import type { MoneroWalletWithdrawResult } from '../types/MoneroWalletWithdrawResult';
|
import type { MoneroWalletWithdrawResult } from '../types/MoneroWalletWithdrawResult';
|
||||||
import { MoneroWalletRpcClient } from './MoneroWalletRpcClient';
|
import { MoneroWalletRpcClient } from './MoneroWalletRpcClient';
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ export class MoneroWalletAdminService {
|
|||||||
throw new BadRequestException('No unlocked balance to withdraw.');
|
throw new BadRequestException('No unlocked balance to withdraw.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.resolveSyncStatus(walletHeight, daemonHeight) !== MoneroWalletSyncStatus.Synced) {
|
if (this.resolveSyncStatus(walletHeight, daemonHeight) !== WalletSyncStatus.Synced) {
|
||||||
throw new BadRequestException('Wallet is still syncing. Try again after sync completes.');
|
throw new BadRequestException('Wallet is still syncing. Try again after sync completes.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,11 +125,11 @@ export class MoneroWalletAdminService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveSyncStatus(walletHeight: number, daemonHeight: number | null): MoneroWalletSyncStatus {
|
private resolveSyncStatus(walletHeight: number, daemonHeight: number | null): WalletSyncStatus {
|
||||||
if (daemonHeight === null) {
|
if (daemonHeight === null) {
|
||||||
return MoneroWalletSyncStatus.Unknown;
|
return WalletSyncStatus.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
return walletHeight >= daemonHeight - 1 ? MoneroWalletSyncStatus.Synced : MoneroWalletSyncStatus.Syncing;
|
return walletHeight >= daemonHeight - 1 ? WalletSyncStatus.Synced : WalletSyncStatus.Syncing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { MoneroNetwork } from '../../../types/MoneroNetwork';
|
import { MoneroNetwork } from '../../../types/MoneroNetwork';
|
||||||
import { MoneroWalletSyncStatus } from './MoneroWalletSyncStatus';
|
import { WalletSyncStatus } from '../../../types/wallet/WalletSyncStatus';
|
||||||
|
|
||||||
export interface MoneroWalletStatusView {
|
export interface MoneroWalletStatusView {
|
||||||
network: MoneroNetwork;
|
network: MoneroNetwork;
|
||||||
rpcVersion: string;
|
rpcVersion: string;
|
||||||
walletHeight: number;
|
walletHeight: number;
|
||||||
daemonHeight: number | null;
|
daemonHeight: number | null;
|
||||||
syncStatus: MoneroWalletSyncStatus;
|
syncStatus: WalletSyncStatus;
|
||||||
balanceXmr: string;
|
balanceXmr: string;
|
||||||
unlockedBalanceXmr: string;
|
unlockedBalanceXmr: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
export enum MoneroWalletSyncStatus {
|
export enum WalletSyncStatus {
|
||||||
Synced = 'synced',
|
Synced = 'synced',
|
||||||
Syncing = 'syncing',
|
Syncing = 'syncing',
|
||||||
Unknown = 'unknown'
|
Unknown = 'unknown'
|
||||||
Vendored
+2
-1
@@ -11,6 +11,7 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
BitcoinWallet: typeof import('./components/wallet/BitcoinWallet.vue')['default']
|
||||||
CmsListPagination: typeof import('./components/CmsListPagination.vue')['default']
|
CmsListPagination: typeof import('./components/CmsListPagination.vue')['default']
|
||||||
CreateOrEditCategoryModal: typeof import('./components/CreateOrEditCategoryModal.vue')['default']
|
CreateOrEditCategoryModal: typeof import('./components/CreateOrEditCategoryModal.vue')['default']
|
||||||
CreateOrEditDiscountCodeModal: typeof import('./components/CreateOrEditDiscountCodeModal.vue')['default']
|
CreateOrEditDiscountCodeModal: typeof import('./components/CreateOrEditDiscountCodeModal.vue')['default']
|
||||||
@@ -56,9 +57,9 @@ declare module 'vue' {
|
|||||||
MoneroWallet: typeof import('./components/wallet/MoneroWallet.vue')['default']
|
MoneroWallet: typeof import('./components/wallet/MoneroWallet.vue')['default']
|
||||||
OrderCartPanel: typeof import('./components/OrderCartPanel.vue')['default']
|
OrderCartPanel: typeof import('./components/OrderCartPanel.vue')['default']
|
||||||
OrderChatPanel: typeof import('./components/OrderChatPanel.vue')['default']
|
OrderChatPanel: typeof import('./components/OrderChatPanel.vue')['default']
|
||||||
|
OrderInvoicePaymentPanel: typeof import('./components/OrderInvoicePaymentPanel.vue')['default']
|
||||||
OrderLineAutoFulfillmentModal: typeof import('./components/OrderLineAutoFulfillmentModal.vue')['default']
|
OrderLineAutoFulfillmentModal: typeof import('./components/OrderLineAutoFulfillmentModal.vue')['default']
|
||||||
OrderManualShippingQuotePanel: typeof import('./components/OrderManualShippingQuotePanel.vue')['default']
|
OrderManualShippingQuotePanel: typeof import('./components/OrderManualShippingQuotePanel.vue')['default']
|
||||||
OrderMoneroPaymentPanel: typeof import('./components/OrderMoneroPaymentPanel.vue')['default']
|
|
||||||
OrderPaymentPanel: typeof import('./components/OrderPaymentPanel.vue')['default']
|
OrderPaymentPanel: typeof import('./components/OrderPaymentPanel.vue')['default']
|
||||||
OrderSummaryPanel: typeof import('./components/OrderSummaryPanel.vue')['default']
|
OrderSummaryPanel: typeof import('./components/OrderSummaryPanel.vue')['default']
|
||||||
RichTextEditor: typeof import('./components/RichTextEditor.vue')['default']
|
RichTextEditor: typeof import('./components/RichTextEditor.vue')['default']
|
||||||
|
|||||||
+20
-12
@@ -8,14 +8,12 @@
|
|||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item label="Expected total">
|
<el-descriptions-item label="Expected total">
|
||||||
<span class="mono"
|
<span class="mono">{{ invoice.expectedTotalCrypto }} {{ invoice.paymentLabel }}</span>
|
||||||
>{{ invoice.expectedTotalCrypto }} {{ paymentMethodCryptoCurrency[invoice.paymentMethod] }}</span
|
|
||||||
>
|
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item v-if="fiatPerXmrAtCreation !== undefined" :label="`Rate at ${rateLabel}`">
|
<el-descriptions-item v-if="fiatPerCryptoAtCreation !== undefined" :label="`Rate at ${rateLabel}`">
|
||||||
{{ formatFiatPrice(fiatPerXmrAtCreation, config.shopFiatCurrency) }} /
|
{{ formatFiatPrice(fiatPerCryptoAtCreation, config.shopFiatCurrency) }} /
|
||||||
{{ paymentMethodCryptoCurrency[invoice.paymentMethod] }}
|
{{ invoice.paymentLabel }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
@@ -37,9 +35,7 @@
|
|||||||
|
|
||||||
<el-table-column label="Amount" width="140">
|
<el-table-column label="Amount" width="140">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span class="mono"
|
<span class="mono">{{ row.amountCrypto }} {{ invoice.paymentLabel }}</span>
|
||||||
>{{ row.amountCrypto }} {{ paymentMethodCryptoCurrency[invoice.paymentMethod] }}</span
|
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
@@ -69,7 +65,7 @@
|
|||||||
import { computed, type PropType } from 'vue';
|
import { computed, type PropType } from 'vue';
|
||||||
import { config } from '@/config';
|
import { config } from '@/config';
|
||||||
import type { InvoiceExtended } from '@/types/payment/InvoiceExtended';
|
import type { InvoiceExtended } from '@/types/payment/InvoiceExtended';
|
||||||
import { paymentMethodCryptoCurrency } from '@/types/payment/PaymentMethod';
|
import { PaymentMethod } from '@/types/payment/PaymentMethod';
|
||||||
import { formatDate } from '@/utils/formatDate';
|
import { formatDate } from '@/utils/formatDate';
|
||||||
import { formatFiatPrice } from '@/utils/formatFiatPrice';
|
import { formatFiatPrice } from '@/utils/formatFiatPrice';
|
||||||
import { resolveInvoiceStatusTagType } from '@/utils/order/resolveInvoiceStatusTagType';
|
import { resolveInvoiceStatusTagType } from '@/utils/order/resolveInvoiceStatusTagType';
|
||||||
@@ -85,13 +81,25 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
emptyText: {
|
emptyText: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'No Monero payment session.'
|
default: 'No payment session.'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const payments = computed(() => props.invoice?.payments ?? []);
|
const payments = computed(() => props.invoice?.payments ?? []);
|
||||||
|
|
||||||
const fiatPerXmrAtCreation = computed(() => props.invoice?.moneroDetails?.fiatPerXmrAtCreation);
|
const fiatPerCryptoAtCreation = computed(() => {
|
||||||
|
const invoice = props.invoice;
|
||||||
|
|
||||||
|
if (!invoice) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (invoice.paymentMethod === PaymentMethod.Btc) {
|
||||||
|
return invoice.btcDetails?.fiatPerBtcAtCreation;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoice.moneroDetails?.fiatPerXmrAtCreation;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -53,10 +53,9 @@
|
|||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<template v-if="order.shippingInvoice">
|
<template v-if="order.shippingInvoice">
|
||||||
<order-monero-payment-panel
|
<order-invoice-payment-panel
|
||||||
:invoice="order.shippingInvoice"
|
:invoice="order.shippingInvoice"
|
||||||
rate-label="quote"
|
rate-label="quote"
|
||||||
empty-text="Shipping payment session not created yet."
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<span>Order payment</span>
|
<span>Order payment</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<order-monero-payment-panel
|
<order-invoice-payment-panel
|
||||||
:invoice="order.checkoutInvoice"
|
:invoice="order.checkoutInvoice"
|
||||||
rate-label="checkout"
|
rate-label="checkout"
|
||||||
empty-text="No checkout payment session."
|
empty-text="No checkout payment session."
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div v-loading="loading" class="detail-loading-host" element-loading-text="Loading wallet…">
|
||||||
|
<el-empty v-if="!loading && loadError" description="Failed to load wallet status" />
|
||||||
|
|
||||||
|
<template v-if="!loading && !loadError && walletStatus">
|
||||||
|
<div v-if="walletStatus.syncStatus !== WalletSyncStatus.Synced" class="mb-16">
|
||||||
|
<el-alert type="warning" :closable="false" show-icon title="Wallet is syncing. Please wait." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-card shadow="never">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between gap-16">
|
||||||
|
<span>Status</span>
|
||||||
|
<el-button type="default" :loading="refreshing" @click="refreshStatus">Refresh</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item label="Network">{{ walletStatus.network }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="RPC version">{{ walletStatus.rpcVersion }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Block height">
|
||||||
|
{{ walletStatus.blockHeight ?? 'Unavailable' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Server height">
|
||||||
|
{{ walletStatus.serverHeight ?? 'Unavailable' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Sync">
|
||||||
|
<el-tag :type="resolveWalletSyncStatusTagType(walletStatus.syncStatus)" size="small">
|
||||||
|
{{ resolveWalletSyncStatusLabel(walletStatus.syncStatus) }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Total balance">{{ walletStatus.balanceBtc }} BTC</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Confirmed balance">
|
||||||
|
{{ walletStatus.confirmedBalanceBtc }} BTC
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-card>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { onBeforeMount, ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { WalletSyncStatus } from '@/types/wallet/WalletSyncStatus';
|
||||||
|
import { useBitcoinWalletStore } from '@/stores/bitcoinWallet';
|
||||||
|
import { resolveAxiosErrorMessage } from '@/utils/resolveAxiosErrorMessage';
|
||||||
|
import { resolveWalletSyncStatusLabel } from '@/utils/wallet/resolveWalletSyncStatusLabel';
|
||||||
|
import { resolveWalletSyncStatusTagType } from '@/utils/wallet/resolveWalletSyncStatusTagType';
|
||||||
|
|
||||||
|
const bitcoinWalletStore = useBitcoinWalletStore();
|
||||||
|
|
||||||
|
const { status: walletStatus } = storeToRefs(bitcoinWalletStore);
|
||||||
|
|
||||||
|
const { fetchStatus } = bitcoinWalletStore;
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
const loadError = ref(false);
|
||||||
|
const refreshing = ref(false);
|
||||||
|
|
||||||
|
onBeforeMount(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
await loadWalletStatus();
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadWalletStatus = async (): Promise<void> => {
|
||||||
|
loadError.value = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fetchStatus();
|
||||||
|
} catch (error) {
|
||||||
|
loadError.value = true;
|
||||||
|
|
||||||
|
ElMessage.error(resolveAxiosErrorMessage(error, 'Failed to load wallet status'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshStatus = async (): Promise<void> => {
|
||||||
|
refreshing.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await loadWalletStatus();
|
||||||
|
|
||||||
|
ElMessage.success('Wallet status refreshed');
|
||||||
|
} finally {
|
||||||
|
refreshing.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<el-empty v-if="!loading && loadError" description="Failed to load wallet status" />
|
<el-empty v-if="!loading && loadError" description="Failed to load wallet status" />
|
||||||
|
|
||||||
<template v-if="!loading && !loadError && walletStatus">
|
<template v-if="!loading && !loadError && walletStatus">
|
||||||
<div v-if="walletStatus.syncStatus !== MoneroWalletSyncStatus.Synced" class="mb-16">
|
<div v-if="walletStatus.syncStatus !== WalletSyncStatus.Synced" class="mb-16">
|
||||||
<el-alert type="warning" :closable="false" show-icon title="Wallet is syncing. Please wait." />
|
<el-alert type="warning" :closable="false" show-icon title="Wallet is syncing. Please wait." />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@
|
|||||||
{{ walletStatus.daemonHeight ?? 'Unavailable' }}
|
{{ walletStatus.daemonHeight ?? 'Unavailable' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="Sync">
|
<el-descriptions-item label="Sync">
|
||||||
<el-tag :type="resolveMoneroWalletSyncStatusTagType(walletStatus.syncStatus)" size="small">
|
<el-tag :type="resolveWalletSyncStatusTagType(walletStatus.syncStatus)" size="small">
|
||||||
{{ resolveMoneroWalletSyncStatusLabel(walletStatus.syncStatus) }}
|
{{ resolveWalletSyncStatusLabel(walletStatus.syncStatus) }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="Total balance">{{ walletStatus.balanceXmr }} XMR</el-descriptions-item>
|
<el-descriptions-item label="Total balance">{{ walletStatus.balanceXmr }} XMR</el-descriptions-item>
|
||||||
@@ -90,10 +90,12 @@
|
|||||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
||||||
import { computed, onBeforeMount, reactive, ref } from 'vue';
|
import { computed, onBeforeMount, reactive, ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { MoneroWalletSyncStatus } from '@/types/moneroWallet/MoneroWalletSyncStatus';
|
import { WalletSyncStatus } from '@/types/wallet/WalletSyncStatus';
|
||||||
import { useMoneroWalletStore } from '@/stores/moneroWallet';
|
import { useMoneroWalletStore } from '@/stores/moneroWallet';
|
||||||
import { isMoneroStandardAddress } from '@/utils/monero/isMoneroStandardAddress';
|
import { isMoneroStandardAddress } from '@/utils/monero/isMoneroStandardAddress';
|
||||||
import { resolveAxiosErrorMessage } from '@/utils/resolveAxiosErrorMessage';
|
import { resolveAxiosErrorMessage } from '@/utils/resolveAxiosErrorMessage';
|
||||||
|
import { resolveWalletSyncStatusLabel } from '@/utils/wallet/resolveWalletSyncStatusLabel';
|
||||||
|
import { resolveWalletSyncStatusTagType } from '@/utils/wallet/resolveWalletSyncStatusTagType';
|
||||||
|
|
||||||
const moneroWalletStore = useMoneroWalletStore();
|
const moneroWalletStore = useMoneroWalletStore();
|
||||||
|
|
||||||
@@ -125,7 +127,7 @@ const withdrawFormRules = computed<FormRules>(() => ({
|
|||||||
destinationAddress: [
|
destinationAddress: [
|
||||||
{
|
{
|
||||||
validator: (_rule, value, callback) => {
|
validator: (_rule, value, callback) => {
|
||||||
if (walletStatus.value?.syncStatus !== MoneroWalletSyncStatus.Synced) {
|
if (walletStatus.value?.syncStatus !== WalletSyncStatus.Synced) {
|
||||||
callback(new Error('Wait until the wallet finishes syncing before withdrawing'));
|
callback(new Error('Wait until the wallet finishes syncing before withdrawing'));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -287,26 +289,4 @@ const onRevealSeedClick = async (): Promise<void> => {
|
|||||||
const clearSeed = (): void => {
|
const clearSeed = (): void => {
|
||||||
revealedMnemonic.value = '';
|
revealedMnemonic.value = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveMoneroWalletSyncStatusLabel = (syncStatus: MoneroWalletSyncStatus): string => {
|
|
||||||
switch (syncStatus) {
|
|
||||||
case MoneroWalletSyncStatus.Synced:
|
|
||||||
return 'Synced';
|
|
||||||
case MoneroWalletSyncStatus.Syncing:
|
|
||||||
return 'Syncing';
|
|
||||||
default:
|
|
||||||
return 'Unknown';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveMoneroWalletSyncStatusTagType = (syncStatus: MoneroWalletSyncStatus): 'success' | 'warning' | 'info' => {
|
|
||||||
switch (syncStatus) {
|
|
||||||
case MoneroWalletSyncStatus.Synced:
|
|
||||||
return 'success';
|
|
||||||
case MoneroWalletSyncStatus.Syncing:
|
|
||||||
return 'warning';
|
|
||||||
default:
|
|
||||||
return 'info';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ export const ROUTE_NAMES = {
|
|||||||
DiscountCodes: 'DiscountCodes',
|
DiscountCodes: 'DiscountCodes',
|
||||||
ShopSettings: 'ShopSettings',
|
ShopSettings: 'ShopSettings',
|
||||||
Notifications: 'Notifications',
|
Notifications: 'Notifications',
|
||||||
Wallet: 'Wallet',
|
MoneroWallet: 'MoneroWallet',
|
||||||
|
BitcoinWallet: 'BitcoinWallet',
|
||||||
Orders: 'Orders',
|
Orders: 'Orders',
|
||||||
OrderDetail: 'OrderDetail'
|
OrderDetail: 'OrderDetail'
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
+18
-3
@@ -57,9 +57,10 @@ const router = createRouter({
|
|||||||
path: '/settings',
|
path: '/settings',
|
||||||
component: () => import('../views/CmsSettingsLayout.vue'),
|
component: () => import('../views/CmsSettingsLayout.vue'),
|
||||||
meta: { requiresAuth: true, title: 'CMS - Settings', activeMenu: '/settings' },
|
meta: { requiresAuth: true, title: 'CMS - Settings', activeMenu: '/settings' },
|
||||||
|
redirect: { name: ROUTE_NAMES.ShopSettings },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: 'shop',
|
||||||
name: ROUTE_NAMES.ShopSettings,
|
name: ROUTE_NAMES.ShopSettings,
|
||||||
component: () => import('../views/CmsShopSettingsView.vue'),
|
component: () => import('../views/CmsShopSettingsView.vue'),
|
||||||
meta: { requiresAuth: true, title: 'CMS - Shop settings', activeMenu: '/settings' }
|
meta: { requiresAuth: true, title: 'CMS - Shop settings', activeMenu: '/settings' }
|
||||||
@@ -74,9 +75,23 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/wallet',
|
path: '/wallet',
|
||||||
name: ROUTE_NAMES.Wallet,
|
|
||||||
component: () => import('../views/CmsWalletView.vue'),
|
component: () => import('../views/CmsWalletView.vue'),
|
||||||
meta: { requiresAuth: true, title: 'CMS - Wallet' }
|
meta: { requiresAuth: true, title: 'CMS - Wallet', activeMenu: '/wallet' },
|
||||||
|
redirect: { name: ROUTE_NAMES.MoneroWallet },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'monero',
|
||||||
|
name: ROUTE_NAMES.MoneroWallet,
|
||||||
|
component: () => import('../components/wallet/MoneroWallet.vue'),
|
||||||
|
meta: { requiresAuth: true, title: 'CMS - Monero wallet', activeMenu: '/wallet' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'bitcoin',
|
||||||
|
name: ROUTE_NAMES.BitcoinWallet,
|
||||||
|
component: () => import('../components/wallet/BitcoinWallet.vue'),
|
||||||
|
meta: { requiresAuth: true, title: 'CMS - Bitcoin wallet', activeMenu: '/wallet' }
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { api } from '@/plugins/axios';
|
||||||
|
import type { BitcoinWalletStatus } from '@/types/bitcoinWallet/BitcoinWalletStatus';
|
||||||
|
|
||||||
|
export const useBitcoinWalletStore = defineStore('bitcoinWallet', () => {
|
||||||
|
const status = ref<BitcoinWalletStatus | null>(null);
|
||||||
|
|
||||||
|
const fetchStatus = async (): Promise<BitcoinWalletStatus> => {
|
||||||
|
const { data } = await api.get<BitcoinWalletStatus>('/bitcoin-wallet');
|
||||||
|
|
||||||
|
status.value = data;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
fetchStatus
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export type BitcoinNetwork = 'mainnet' | 'testnet4';
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { BitcoinNetwork } from './BitcoinNetwork';
|
||||||
|
import type { WalletSyncStatus } from '../wallet/WalletSyncStatus';
|
||||||
|
|
||||||
|
export interface BitcoinWalletStatus {
|
||||||
|
network: BitcoinNetwork;
|
||||||
|
rpcVersion: string;
|
||||||
|
blockHeight: number | null;
|
||||||
|
serverHeight: number | null;
|
||||||
|
syncStatus: WalletSyncStatus;
|
||||||
|
balanceBtc: string;
|
||||||
|
confirmedBalanceBtc: string;
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { MoneroNetwork } from './MoneroNetwork';
|
import type { MoneroNetwork } from './MoneroNetwork';
|
||||||
import type { MoneroWalletSyncStatus } from './MoneroWalletSyncStatus';
|
import type { WalletSyncStatus } from '../wallet/WalletSyncStatus';
|
||||||
|
|
||||||
export interface MoneroWalletStatus {
|
export interface MoneroWalletStatus {
|
||||||
network: MoneroNetwork;
|
network: MoneroNetwork;
|
||||||
rpcVersion: string;
|
rpcVersion: string;
|
||||||
walletHeight: number;
|
walletHeight: number;
|
||||||
daemonHeight: number | null;
|
daemonHeight: number | null;
|
||||||
syncStatus: MoneroWalletSyncStatus;
|
syncStatus: WalletSyncStatus;
|
||||||
balanceXmr: string;
|
balanceXmr: string;
|
||||||
unlockedBalanceXmr: string;
|
unlockedBalanceXmr: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { InvoiceStatusLabel } from './InvoiceStatusLabel';
|
|||||||
|
|
||||||
export type InvoiceExtended = Omit<Invoice, 'payments'> & {
|
export type InvoiceExtended = Omit<Invoice, 'payments'> & {
|
||||||
statusLabel: InvoiceStatusLabel | null;
|
statusLabel: InvoiceStatusLabel | null;
|
||||||
|
paymentLabel: string;
|
||||||
expectedTotalCrypto: string;
|
expectedTotalCrypto: string;
|
||||||
payments: InvoicePaymentExtended[];
|
payments: InvoicePaymentExtended[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,3 @@ export enum PaymentMethod {
|
|||||||
Xmr = 'xmr',
|
Xmr = 'xmr',
|
||||||
Btc = 'btc'
|
Btc = 'btc'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const paymentMethodCryptoCurrency: Record<PaymentMethod, string> = {
|
|
||||||
[PaymentMethod.Xmr]: 'XMR',
|
|
||||||
[PaymentMethod.Btc]: 'BTC'
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
export interface BitcoinConfirmationTier {
|
|
||||||
upToTotalFiat?: string;
|
|
||||||
minConfirmations: number;
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
export interface MoneroConfirmationTier {
|
export interface ConfirmationTier {
|
||||||
upToTotalFiat?: string;
|
upToTotalFiat?: string;
|
||||||
minConfirmations: number;
|
minConfirmations: number;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { BitcoinConfirmationTier } from './BitcoinConfirmationTier';
|
import type { ConfirmationTier } from './ConfirmationTier';
|
||||||
|
|
||||||
export interface ShopSettingsBitcoin {
|
export interface ShopSettingsBitcoin {
|
||||||
confirmationTiers: BitcoinConfirmationTier[];
|
confirmationTiers: ConfirmationTier[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { MoneroConfirmationTier } from './MoneroConfirmationTier';
|
import type { ConfirmationTier } from './ConfirmationTier';
|
||||||
|
|
||||||
export interface ShopSettingsMonero {
|
export interface ShopSettingsMonero {
|
||||||
confirmationTiers: MoneroConfirmationTier[];
|
confirmationTiers: ConfirmationTier[];
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
export enum MoneroWalletSyncStatus {
|
export enum WalletSyncStatus {
|
||||||
Synced = 'synced',
|
Synced = 'synced',
|
||||||
Syncing = 'syncing',
|
Syncing = 'syncing',
|
||||||
Unknown = 'unknown'
|
Unknown = 'unknown'
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { WalletSyncStatus } from '@/types/wallet/WalletSyncStatus';
|
||||||
|
|
||||||
|
export const resolveWalletSyncStatusLabel = (syncStatus: WalletSyncStatus): string => {
|
||||||
|
switch (syncStatus) {
|
||||||
|
case WalletSyncStatus.Synced:
|
||||||
|
return 'Synced';
|
||||||
|
case WalletSyncStatus.Syncing:
|
||||||
|
return 'Syncing';
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { WalletSyncStatus } from '@/types/wallet/WalletSyncStatus';
|
||||||
|
|
||||||
|
export const resolveWalletSyncStatusTagType = (syncStatus: WalletSyncStatus): 'success' | 'warning' | 'info' => {
|
||||||
|
switch (syncStatus) {
|
||||||
|
case WalletSyncStatus.Synced:
|
||||||
|
return 'success';
|
||||||
|
case WalletSyncStatus.Syncing:
|
||||||
|
return 'warning';
|
||||||
|
default:
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -37,7 +37,14 @@
|
|||||||
<el-descriptions-item label="Monero confirmation tiers">
|
<el-descriptions-item label="Monero confirmation tiers">
|
||||||
<ul class="m-0 pl-20">
|
<ul class="m-0 pl-20">
|
||||||
<li v-for="(tier, index) in settings.monero.confirmationTiers" :key="index">
|
<li v-for="(tier, index) in settings.monero.confirmationTiers" :key="index">
|
||||||
{{ formatMoneroConfirmationTier(tier, settings.shopFiatCurrency) }}
|
{{ formatConfirmationTier(tier, settings.shopFiatCurrency) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="Bitcoin confirmation tiers">
|
||||||
|
<ul class="m-0 pl-20">
|
||||||
|
<li v-for="(tier, index) in settings.bitcoin.confirmationTiers" :key="index">
|
||||||
|
{{ formatConfirmationTier(tier, settings.shopFiatCurrency) }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
@@ -210,7 +217,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { config } from '@/config';
|
import { config } from '@/config';
|
||||||
import { useShopSettingsStore } from '@/stores/shopSettings';
|
import { useShopSettingsStore } from '@/stores/shopSettings';
|
||||||
import type { MoneroConfirmationTier } from '@/types/shopSettings/MoneroConfirmationTier';
|
import type { ConfirmationTier } from '@/types/shopSettings/ConfirmationTier';
|
||||||
import { buildUploadHint } from '@/utils/upload/buildUploadHint';
|
import { buildUploadHint } from '@/utils/upload/buildUploadHint';
|
||||||
import { resolveAxiosErrorMessage } from '@/utils/resolveAxiosErrorMessage';
|
import { resolveAxiosErrorMessage } from '@/utils/resolveAxiosErrorMessage';
|
||||||
import { resolveUploadPublicUrl } from '@/utils/upload/resolveUploadPublicUrl';
|
import { resolveUploadPublicUrl } from '@/utils/upload/resolveUploadPublicUrl';
|
||||||
@@ -460,7 +467,7 @@ const submitShippingNote = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatMoneroConfirmationTier = (tier: MoneroConfirmationTier, currency: string): string => {
|
const formatConfirmationTier = (tier: ConfirmationTier, currency: string): string => {
|
||||||
const requirement = tier.minConfirmations === 0 ? '0 (tx-detected)' : `${tier.minConfirmations} confirmations`;
|
const requirement = tier.minConfirmations === 0 ? '0 (tx-detected)' : `${tier.minConfirmations} confirmations`;
|
||||||
|
|
||||||
if (tier.upToTotalFiat === undefined) {
|
if (tier.upToTotalFiat === undefined) {
|
||||||
|
|||||||
@@ -2,6 +2,31 @@
|
|||||||
<div>
|
<div>
|
||||||
<h3 class="m-0 mb-16">Wallet</h3>
|
<h3 class="m-0 mb-16">Wallet</h3>
|
||||||
|
|
||||||
<monero-wallet />
|
<el-tabs :model-value="activeTab" class="mb-24" @tab-change="onTabChange">
|
||||||
|
<el-tab-pane label="Monero (XMR)" name="monero" />
|
||||||
|
<el-tab-pane label="Bitcoin (BTC)" name="bitcoin" />
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ROUTE_NAMES } from '@/consts/routeNames';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const activeTab = computed(() => (route.name === ROUTE_NAMES.BitcoinWallet ? 'bitcoin' : 'monero'));
|
||||||
|
|
||||||
|
const onTabChange = (tabName: string | number) => {
|
||||||
|
if (tabName === 'bitcoin') {
|
||||||
|
router.push({ name: ROUTE_NAMES.BitcoinWallet });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({ name: ROUTE_NAMES.MoneroWallet });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user