harb/landing/src/components/WalletCard.vue

144 lines
3.8 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import { RouterLink } from 'vue-router';
import { useAccount } from '@harb/web3';
const { address, isConnected } = useAccount();
const holder = ref<{
balance: string;
totalEthSpent: string;
totalTokensAcquired: string;
} | null>(null);
const stats = ref<{ currentPriceWei: string } | null>(null);
const loading = ref(false);
const endpoint = `${window.location.origin}/api/graphql`;
async function fetchWalletData(addr: string) {
loading.value = true;
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `query WalletData($address: String!) {
holders(address: $address) {
balance
totalEthSpent
totalTokensAcquired
}
protocolStatss(where: { id: "0x01" }) {
items { currentPriceWei }
}
}`,
variables: { address: addr.toLowerCase() },
}),
});
const json = await res.json();
holder.value = json?.data?.holders ?? null;
stats.value = json?.data?.protocolStatss?.items?.[0] ?? null;
} catch {
holder.value = null;
} finally {
loading.value = false;
}
}
watch([address, isConnected], () => {
if (isConnected.value && address.value) {
void fetchWalletData(address.value);
} else {
holder.value = null;
}
}, { immediate: true });
function fmt(wei: string, decimals = 4) {
const n = Number(wei) / 1e18;
return n.toLocaleString('en', { maximumFractionDigits: decimals });
}
const balanceKrk = computed(() => fmt(holder.value?.balance ?? '0', 2));
const hasPosition = computed(() => holder.value && BigInt(holder.value.balance) > 0n);
const avgCost = computed(() => {
if (!holder.value) return 0;
const spent = Number(holder.value.totalEthSpent) / 1e18;
const acquired = Number(holder.value.totalTokensAcquired) / 1e18;
return acquired > 0 ? spent / acquired : 0;
});
const currentPrice = computed(() =>
stats.value ? Number(stats.value.currentPriceWei) / 1e18 : 0,
);
const pnlPercent = computed(() => {
if (avgCost.value === 0) return 0;
return (currentPrice.value / avgCost.value - 1) * 100;
});
const pnlClass = computed(() => (pnlPercent.value >= 0 ? 'positive' : 'negative'));
const walletRoute = computed(() => ({ name: 'wallet', params: { address: address.value } }));
</script>
<template>
<div v-if="isConnected && hasPosition" class="wallet-card" :class="pnlClass">
<div class="wallet-card__balance">{{ balanceKrk }} KRK</div>
<div v-if="avgCost > 0" class="wallet-card__pnl">
{{ pnlPercent >= 0 ? '+' : '' }}{{ pnlPercent.toFixed(1) }}%
</div>
<RouterLink :to="walletRoute" class="wallet-card__link">View Dashboard </RouterLink>
</div>
<div v-else-if="isConnected && !loading && !hasPosition" class="wallet-card wallet-card--empty">
<span>No KRK yet</span>
<a href="#get-krk" class="wallet-card__link">Get KRK </a>
</div>
</template>
<style lang="sass" scoped>
.wallet-card
display: flex
align-items: center
gap: 1rem
padding: 0.75rem 1.25rem
border-radius: 12px
background: rgba(255, 255, 255, 0.06)
border: 1px solid rgba(255, 255, 255, 0.15)
margin-top: 1rem
&.positive
border-color: rgba(16, 185, 129, 0.4)
background: rgba(16, 185, 129, 0.06)
&.negative
border-color: rgba(239, 68, 68, 0.4)
background: rgba(239, 68, 68, 0.06)
&--empty
color: #9a9898
font-size: 0.85rem
&__balance
font-weight: 700
font-size: 1.1rem
&__pnl
font-weight: 600
.positive &
color: #10b981
.negative &
color: #ef4444
&__link
margin-left: auto
color: #3b82f6
text-decoration: none
font-size: 0.85rem
&:hover
text-decoration: underline
</style>