Merge pull request 'fix: Snatch notifications and position history (#151)' (#206) from fix/issue-151 into master
This commit is contained in:
commit
058451792f
7 changed files with 497 additions and 18 deletions
191
web-app/src/components/NotificationBell.vue
Normal file
191
web-app/src/components/NotificationBell.vue
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
<template>
|
||||
<div class="notification-bell" ref="bellRef">
|
||||
<button class="notification-bell__btn" @click="toggle" :aria-label="`Notifications${unseenCount > 0 ? `, ${unseenCount} unseen` : ''}`">
|
||||
<Icon icon="mdi:bell-outline" class="notification-bell__icon" />
|
||||
<span v-if="unseenCount > 0" class="notification-bell__badge">{{ unseenCount }}</span>
|
||||
</button>
|
||||
|
||||
<Transition name="bell-panel">
|
||||
<div v-if="isOpen" class="notification-bell__panel" role="dialog" aria-label="Snatch notifications">
|
||||
<div class="notification-bell__panel-header">
|
||||
<span class="notification-bell__panel-title">Snatch Notifications</span>
|
||||
<button class="notification-bell__close" @click="close" aria-label="Close notifications">✕</button>
|
||||
</div>
|
||||
<div class="notification-bell__panel-body">
|
||||
<div v-if="allRecentSnatches.length === 0" class="notification-bell__empty">No snatch events found.</div>
|
||||
<div v-for="event in allRecentSnatches" :key="event.id" class="notification-bell__event">
|
||||
<div class="notification-bell__event-icon">⚡</div>
|
||||
<div class="notification-bell__event-details">
|
||||
<div class="notification-bell__event-title">Position snatched</div>
|
||||
<div class="notification-bell__event-meta">
|
||||
<span>{{ formatTokenAmount(event.tokenAmount, 18, 2) }} $KRK</span>
|
||||
<span class="notification-bell__event-time">{{ relativeTime(event.timestamp) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { Icon } from '@iconify/vue';
|
||||
import { useSnatchNotifications } from '@/composables/useSnatchNotifications';
|
||||
import { useWallet } from '@/composables/useWallet';
|
||||
import { DEFAULT_CHAIN_ID } from '@/config';
|
||||
import { relativeTime, formatTokenAmount } from '@/utils/helper';
|
||||
|
||||
const wallet = useWallet();
|
||||
const chainId = wallet.account.chainId ?? DEFAULT_CHAIN_ID;
|
||||
|
||||
const { unseenCount, allRecentSnatches, isOpen, toggle, close } = useSnatchNotifications(chainId);
|
||||
|
||||
const bellRef = ref<HTMLElement | null>(null);
|
||||
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (bellRef.value && !bellRef.value.contains(event.target as Node)) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
.notification-bell
|
||||
position: relative
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
|
||||
&__btn
|
||||
background: none
|
||||
border: none
|
||||
cursor: pointer
|
||||
color: var(--color-navbar-font, #fff)
|
||||
padding: 8px
|
||||
border-radius: 8px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
position: relative
|
||||
transition: background 0.15s
|
||||
&:hover
|
||||
background: rgba(255, 255, 255, 0.08)
|
||||
|
||||
&__icon
|
||||
font-size: 22px
|
||||
|
||||
&__badge
|
||||
position: absolute
|
||||
top: 2px
|
||||
right: 2px
|
||||
background: #f59e0b
|
||||
color: #000
|
||||
border-radius: 50%
|
||||
font-size: 10px
|
||||
font-weight: 700
|
||||
min-width: 16px
|
||||
height: 16px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
padding: 0 3px
|
||||
line-height: 1
|
||||
|
||||
&__panel
|
||||
position: absolute
|
||||
top: calc(100% + 8px)
|
||||
right: 0
|
||||
width: 300px
|
||||
background: #0F0F0F
|
||||
border: 1px solid var(--color-collapse-border, #2D2D2D)
|
||||
border-radius: 12px
|
||||
z-index: 100
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5)
|
||||
overflow: hidden
|
||||
|
||||
&__panel-header
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
padding: 12px 16px
|
||||
border-bottom: 1px solid var(--color-collapse-border, #2D2D2D)
|
||||
|
||||
&__panel-title
|
||||
font-size: 14px
|
||||
font-weight: 600
|
||||
color: #fff
|
||||
|
||||
&__close
|
||||
background: none
|
||||
border: none
|
||||
color: #9A9898
|
||||
cursor: pointer
|
||||
font-size: 14px
|
||||
padding: 2px 6px
|
||||
border-radius: 4px
|
||||
&:hover
|
||||
color: #fff
|
||||
|
||||
&__panel-body
|
||||
max-height: 320px
|
||||
overflow-y: auto
|
||||
padding: 8px 0
|
||||
|
||||
&__empty
|
||||
padding: 16px
|
||||
color: #9A9898
|
||||
font-size: 13px
|
||||
text-align: center
|
||||
|
||||
&__event
|
||||
display: flex
|
||||
gap: 10px
|
||||
padding: 10px 16px
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04)
|
||||
&:last-child
|
||||
border-bottom: none
|
||||
&:hover
|
||||
background: rgba(255, 255, 255, 0.03)
|
||||
|
||||
&__event-icon
|
||||
font-size: 18px
|
||||
flex-shrink: 0
|
||||
margin-top: 1px
|
||||
|
||||
&__event-details
|
||||
flex: 1
|
||||
min-width: 0
|
||||
|
||||
&__event-title
|
||||
font-size: 13px
|
||||
font-weight: 500
|
||||
color: #f59e0b
|
||||
|
||||
&__event-meta
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
font-size: 12px
|
||||
color: #9A9898
|
||||
margin-top: 2px
|
||||
|
||||
&__event-time
|
||||
flex-shrink: 0
|
||||
|
||||
.bell-panel-enter-active,
|
||||
.bell-panel-leave-active
|
||||
transition: opacity 0.15s, transform 0.15s
|
||||
|
||||
.bell-panel-enter-from,
|
||||
.bell-panel-leave-to
|
||||
opacity: 0
|
||||
transform: translateY(-6px)
|
||||
</style>
|
||||
|
|
@ -2,17 +2,23 @@
|
|||
<FCollapse class="f-collapse-history">
|
||||
<template v-slot:header>
|
||||
<div class="collapse-header">
|
||||
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div>
|
||||
<div>
|
||||
<span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span>
|
||||
<div class="collapse-header-row1">
|
||||
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div>
|
||||
<div class="position-id">
|
||||
<span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span>
|
||||
</div>
|
||||
<FTag v-if="isSnatched" class="snatched-tag">⚡ Snatched</FTag>
|
||||
</div>
|
||||
<div class="collapse-amount">
|
||||
<span class="number-small">{{ compactNumber(props.amount) }}</span>
|
||||
<span class="caption"> $KRK</span>
|
||||
<div class="collapse-header-row2">
|
||||
<div class="collapse-amount">
|
||||
<span class="number-small">{{ compactNumber(props.amount) }}</span>
|
||||
<span class="caption"> $KRK</span>
|
||||
</div>
|
||||
<div v-if="isSnatched && relativeClosedAt" class="snatched-time">{{ relativeClosedAt }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="collapsed-body history">
|
||||
<div class="history-body">
|
||||
<div>
|
||||
<span class="subheader2">Tax paid</span><span class="number-small">{{ props.taxPaid }}</span
|
||||
><span class="caption"> $KRK</span>
|
||||
|
|
@ -21,6 +27,15 @@
|
|||
<span class="subheader2">Profit</span><span class="number-small">{{ profit }}</span
|
||||
><span class="caption"> $KRK</span>
|
||||
</div>
|
||||
<template v-if="isSnatched">
|
||||
<div v-if="payoutKrk !== null">
|
||||
<span class="subheader2">Payout</span><span class="number-small">{{ payoutKrk }}</span
|
||||
><span class="caption"> $KRK</span>
|
||||
</div>
|
||||
<div class="history-position-link">
|
||||
<RouterLink :to="{ name: 'position', params: { id: props.id.toString() } }">View position details →</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</FCollapse>
|
||||
</template>
|
||||
|
|
@ -28,9 +43,12 @@
|
|||
<script setup lang="ts">
|
||||
import type { Position } from '@/composables/usePositions';
|
||||
import FCollapse from '@/components/fcomponents/FCollapse.vue';
|
||||
import { compactNumber } from '@/utils/helper';
|
||||
import FTag from '@/components/fcomponents/FTag.vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { compactNumber, relativeTime, formatTokenAmount } from '@/utils/helper';
|
||||
import { calculateClosedPositionProfit } from 'kraiken-lib/position';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
taxRate: number;
|
||||
taxPaid: string;
|
||||
|
|
@ -40,23 +58,65 @@ const props = defineProps<{
|
|||
position: Position;
|
||||
}>();
|
||||
|
||||
const isSnatched = computed(() => props.position.snatched > 0);
|
||||
|
||||
const profit = computed(() => {
|
||||
// Calculate issuance earned using kraiken-lib profit calculation
|
||||
if (!props.position.totalSupplyEnd) {
|
||||
return 0;
|
||||
}
|
||||
return calculateClosedPositionProfit(
|
||||
props.position.totalSupplyInit,
|
||||
props.position.totalSupplyEnd,
|
||||
props.position.share
|
||||
);
|
||||
return calculateClosedPositionProfit(props.position.totalSupplyInit, props.position.totalSupplyEnd, props.position.share);
|
||||
});
|
||||
|
||||
const payoutKrk = computed(() => {
|
||||
if (!props.position.payout) return null;
|
||||
return formatTokenAmount(props.position.payout);
|
||||
});
|
||||
|
||||
const relativeClosedAt = computed(() => {
|
||||
if (!props.position.closedAt) return null;
|
||||
return relativeTime(props.position.closedAt) || null;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
@use 'collapse'
|
||||
.f-collapse
|
||||
&.f-collapse-history
|
||||
.collapse-header, .collapsableDiv
|
||||
margin-right: 32px
|
||||
&.f-collapse-history
|
||||
.collapse-header, .collapsableDiv
|
||||
margin-right: 32px
|
||||
.collapse-header
|
||||
width: 100%
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 8px
|
||||
.collapse-header-row1
|
||||
display: flex
|
||||
flex-direction: row
|
||||
gap: 16px
|
||||
align-items: center
|
||||
.position-id
|
||||
margin-left: auto
|
||||
.snatched-tag
|
||||
border-color: #f59e0b
|
||||
color: #f59e0b
|
||||
.collapse-header-row2
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
.snatched-time
|
||||
font-size: 12px
|
||||
color: #9A9898
|
||||
.history-body
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 8px
|
||||
padding: 8px 0
|
||||
.history-position-link
|
||||
margin-top: 4px
|
||||
a
|
||||
color: #7550AE
|
||||
text-decoration: none
|
||||
font-size: 13px
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<NetworkChanger></NetworkChanger>
|
||||
<div class="vertical-line"></div>
|
||||
</template>
|
||||
<NotificationBell v-if="address"></NotificationBell>
|
||||
<ConnectButton @click="showPanel = true" v-if="!isMobile"></ConnectButton>
|
||||
<IconLogin @click="showPanel = true" v-else-if="isMobile && !address"></IconLogin>
|
||||
<img @click="showPanel = true" v-else-if="isMobile && address" :src="getBlocky(address)" alt="avatar" />
|
||||
|
|
@ -40,6 +41,7 @@ import IconLogin from '@/components/icons/IconLogin.vue';
|
|||
// import ThemeToggle from "@/components/layouts/ThemeToggle.vue";
|
||||
import NetworkChanger from '@/components/layouts/NetworkChanger.vue';
|
||||
import ConnectButton from '@/components/layouts/ConnectButton.vue';
|
||||
import NotificationBell from '@/components/NotificationBell.vue';
|
||||
import { inject, ref } from 'vue';
|
||||
import { useAccount } from '@wagmi/vue';
|
||||
import { useDark } from '@/composables/useDark';
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ export interface Position {
|
|||
harbDeposit: bigint;
|
||||
iAmOwner: boolean;
|
||||
snatched: number;
|
||||
closedAt?: string;
|
||||
payout?: string;
|
||||
}
|
||||
|
||||
const myClosedPositions: ComputedRef<Position[]> = computed(() => {
|
||||
|
|
@ -208,6 +210,7 @@ export async function loadMyClosedPositions(chainId: number, endpointOverride: s
|
|||
totalSupplyEnd
|
||||
totalSupplyInit
|
||||
creationTime
|
||||
closedAt
|
||||
}
|
||||
}
|
||||
}`,
|
||||
|
|
|
|||
167
web-app/src/composables/useSnatchNotifications.ts
Normal file
167
web-app/src/composables/useSnatchNotifications.ts
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { getAccount, watchAccount } from '@wagmi/core';
|
||||
import type { WatchAccountReturnType } from '@wagmi/core';
|
||||
import { config } from '@/wagmi';
|
||||
import type { Config } from '@wagmi/core';
|
||||
import { DEFAULT_CHAIN_ID } from '@/config';
|
||||
import { resolveGraphqlEndpoint } from '@/utils/graphqlRetry';
|
||||
import logger from '@/utils/logger';
|
||||
|
||||
export interface SnatchEvent {
|
||||
id: string;
|
||||
timestamp: string;
|
||||
tokenAmount: string;
|
||||
ethAmount: string;
|
||||
txHash: string;
|
||||
}
|
||||
|
||||
const GRAPHQL_TIMEOUT_MS = 15_000;
|
||||
const STORAGE_KEY_PREFIX = 'snatch-seen-';
|
||||
|
||||
const unseenSnatches = ref<SnatchEvent[]>([]);
|
||||
const allRecentSnatches = ref<SnatchEvent[]>([]);
|
||||
const isOpen = ref(false);
|
||||
|
||||
let unwatchAccount: WatchAccountReturnType | null = null;
|
||||
let consumerCount = 0;
|
||||
|
||||
function storageKey(address: string): string {
|
||||
return `${STORAGE_KEY_PREFIX}${address.toLowerCase()}`;
|
||||
}
|
||||
|
||||
function getLastSeen(address: string): string {
|
||||
return localStorage.getItem(storageKey(address)) ?? '0';
|
||||
}
|
||||
|
||||
function setLastSeen(address: string, timestamp: string): void {
|
||||
localStorage.setItem(storageKey(address), timestamp);
|
||||
}
|
||||
|
||||
async function fetchSnatchEvents(endpoint: string, address: string, since: string | null): Promise<SnatchEvent[]> {
|
||||
const holder = address.toLowerCase();
|
||||
const whereClause =
|
||||
since !== null
|
||||
? `{ holder: "${holder}", type: "snatch_out", timestamp_gt: "${since}" }`
|
||||
: `{ holder: "${holder}", type: "snatch_out" }`;
|
||||
const res = await axios.post(
|
||||
endpoint,
|
||||
{
|
||||
query: `query SnatchEvents {
|
||||
transactionss(
|
||||
where: ${whereClause}
|
||||
orderBy: "timestamp"
|
||||
orderDirection: "desc"
|
||||
limit: 20
|
||||
) {
|
||||
items { id timestamp tokenAmount ethAmount txHash }
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{ timeout: GRAPHQL_TIMEOUT_MS }
|
||||
);
|
||||
|
||||
const errors = res.data?.errors;
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
logger.info('useSnatchNotifications GraphQL errors', errors);
|
||||
return [];
|
||||
}
|
||||
return (res.data?.data?.transactionss?.items ?? []) as SnatchEvent[];
|
||||
}
|
||||
|
||||
async function refresh(chainId: number): Promise<void> {
|
||||
const account = getAccount(config as Config);
|
||||
if (!account.address) {
|
||||
unseenSnatches.value = [];
|
||||
allRecentSnatches.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
let endpoint: string;
|
||||
try {
|
||||
endpoint = resolveGraphqlEndpoint(chainId);
|
||||
} catch {
|
||||
unseenSnatches.value = [];
|
||||
allRecentSnatches.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const since = getLastSeen(account.address);
|
||||
const [unseen, all] = await Promise.all([
|
||||
fetchSnatchEvents(endpoint, account.address, since),
|
||||
fetchSnatchEvents(endpoint, account.address, null),
|
||||
]);
|
||||
unseenSnatches.value = unseen;
|
||||
allRecentSnatches.value = all;
|
||||
} catch (err) {
|
||||
logger.info('useSnatchNotifications fetch failed', err);
|
||||
}
|
||||
}
|
||||
|
||||
function markSeen(): void {
|
||||
const account = getAccount(config as Config);
|
||||
if (!account.address) return;
|
||||
|
||||
// Advance last-seen to the most recent event timestamp
|
||||
const latestTs = allRecentSnatches.value[0]?.timestamp ?? unseenSnatches.value[0]?.timestamp;
|
||||
if (latestTs) {
|
||||
setLastSeen(account.address, latestTs);
|
||||
}
|
||||
// Clear badge; allRecentSnatches stays populated so the panel still shows history
|
||||
unseenSnatches.value = [];
|
||||
}
|
||||
|
||||
export function useSnatchNotifications(chainId: number = DEFAULT_CHAIN_ID) {
|
||||
const unseenCount = computed(() => unseenSnatches.value.length);
|
||||
|
||||
function open(): void {
|
||||
isOpen.value = true;
|
||||
markSeen();
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
isOpen.value = false;
|
||||
}
|
||||
|
||||
function toggle(): void {
|
||||
if (isOpen.value) {
|
||||
close();
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
consumerCount += 1;
|
||||
|
||||
if (consumerCount === 1) {
|
||||
unwatchAccount = watchAccount(config as Config, {
|
||||
async onChange() {
|
||||
await refresh(chainId);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await refresh(chainId);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
consumerCount = Math.max(0, consumerCount - 1);
|
||||
if (consumerCount === 0 && unwatchAccount) {
|
||||
unwatchAccount();
|
||||
unwatchAccount = null;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
unseenCount,
|
||||
unseenSnatches,
|
||||
allRecentSnatches,
|
||||
isOpen,
|
||||
open,
|
||||
close,
|
||||
toggle,
|
||||
refresh: () => refresh(chainId),
|
||||
};
|
||||
}
|
||||
|
|
@ -46,6 +46,28 @@ export function InsertCommaNumber(number: number) {
|
|||
return formattedWithOptions;
|
||||
}
|
||||
|
||||
export function relativeTime(timestamp: string | number): string {
|
||||
try {
|
||||
const sec = typeof timestamp === 'string' ? Number(timestamp) : timestamp;
|
||||
const nowSec = Math.floor(Date.now() / 1000);
|
||||
const diff = nowSec - sec;
|
||||
if (diff < 60) return 'just now';
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
||||
return `${Math.floor(diff / 86400)}d ago`;
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function formatTokenAmount(raw: string, decimals: number = 18, digits: number = 4): string {
|
||||
try {
|
||||
return Number(formatUnits(BigInt(raw), decimals)).toFixed(digits);
|
||||
} catch {
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
export function formatBigIntDivision(nominator: bigint, denominator: bigint, _digits: number = 2) {
|
||||
if (!nominator) {
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@
|
|||
</div>
|
||||
<div class="active-positions-wrapper">
|
||||
<h3>Active Positions</h3>
|
||||
<div v-if="myActivePositions.length === 0 && myClosedPositions.length > 0" class="no-active-positions">
|
||||
No active positions — your position history is below.
|
||||
</div>
|
||||
<div class="active-positions-list">
|
||||
<CollapseActive
|
||||
v-for="position in myActivePositions"
|
||||
|
|
@ -83,6 +86,21 @@
|
|||
></CollapseActive>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="myClosedPositions.length > 0" class="position-history-wrapper">
|
||||
<h3>Position History</h3>
|
||||
<div class="position-history-list">
|
||||
<CollapseHistory
|
||||
v-for="position in myClosedPositions"
|
||||
:key="position.id"
|
||||
:taxRate="position.taxRatePercentage"
|
||||
:taxPaid="position.taxPaid.toString()"
|
||||
:treshold="tresholdValue"
|
||||
:id="position.positionId"
|
||||
:amount="position.amount"
|
||||
:position="position"
|
||||
></CollapseHistory>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <f-button @click="getGraphData">graphql test</f-button>
|
||||
<div v-for="position in positions" :key="position.id">
|
||||
{{ position }}
|
||||
|
|
@ -95,6 +113,7 @@ import StakeHolder from '@/components/StakeHolder.vue';
|
|||
import ChartComplete from '@/components/chart/ChartComplete.vue';
|
||||
import StatsOutput from '@/components/StatsOutput.vue';
|
||||
import CollapseActive from '@/components/collapse/CollapseActive.vue';
|
||||
import CollapseHistory from '@/components/collapse/CollapseHistory.vue';
|
||||
import ProtocolStatsCard from '@/components/ProtocolStatsCard.vue';
|
||||
import { onMounted, computed, inject } from 'vue';
|
||||
import { useStatCollection } from '@/composables/useStatCollection';
|
||||
|
|
@ -113,7 +132,7 @@ const showPanel = inject('showPanel');
|
|||
import { InsertCommaNumber } from '@/utils/helper';
|
||||
const wallet = useWallet();
|
||||
const initialChainId = wallet.account.chainId ?? DEFAULT_CHAIN_ID;
|
||||
const { myActivePositions, tresholdValue, activePositions } = usePositions(initialChainId);
|
||||
const { myActivePositions, myClosedPositions, tresholdValue, activePositions } = usePositions(initialChainId);
|
||||
|
||||
const stats = useStatCollection(initialChainId);
|
||||
const chains = useChains();
|
||||
|
|
@ -249,8 +268,23 @@ onMounted(async () => {
|
|||
margin-right: auto
|
||||
@media (min-width: 768px)
|
||||
width: 580px
|
||||
.no-active-positions
|
||||
color: #9A9898
|
||||
font-size: 14px
|
||||
padding: 16px 0
|
||||
.active-positions-list
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
.position-history-wrapper
|
||||
width: 100%
|
||||
margin-left: auto
|
||||
margin-right: auto
|
||||
@media (min-width: 768px)
|
||||
width: 580px
|
||||
.position-history-list
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue