Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
class="grid grid-rows-[max-content_1fr_max-content] grid-cols-[1fr] w-full justify-items-center"
>
<div class="max-w-[1300px] w-full lg:px-md">
<SvgIcon name="logo" class="w-[170px] mt-md" />
<SvgIcon
name="logo"
class="w-[370px] h-auto mt-md sm:w-[340px] xs:w-[300px]"
/>
</div>
<div class="max-w-[1100px] w-full grid gap-2xl">
<div
Expand All @@ -22,6 +25,7 @@
<StakeVolume
:visible-stakers="visibleStakers"
:total-staked="totalStaked"
:circulating-supply="circulatingSupply"
:loading="loading"
class="h-max md:m-md"
/>
Expand All @@ -41,9 +45,12 @@ import dayjs from "dayjs"

import { footerSections } from "./footerSections"

const { data, refresh } = await useFetch("/api/staking")
const { data: staking, refresh: refreshStaking } =
await useFetch("/api/staking")
const { data: supply, refresh: refreshSupply } = await useFetch("/api/supply")

useIntervalFn(refresh, 10000)
useIntervalFn(refreshStaking, 10000)
useIntervalFn(refreshSupply, 10000)

function epochToTimestamp(genesisDate, epoch) {
const secondsSinceGenesis = epoch * 45
Expand All @@ -53,7 +60,7 @@ function epochToTimestamp(genesisDate, epoch) {
}

const visibleStakers = computed(() => {
return data.value.map((stake) => {
return staking.value.map((stake) => {
const validator = stake.key.validator
const withdrawer = stake.key.withdrawer
const amount = stake.value.coins
Expand All @@ -78,6 +85,10 @@ const totalStaked = computed(() => {
: 0
})

const circulatingSupply = computed(() => {
return parseInt(supply.value)
})

// const visibleStakers = ref(mockStakers);
const loading = ref(false)

Expand Down
3 changes: 3 additions & 0 deletions assets/svg/arrow-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/svg/arrow-up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 28 additions & 4 deletions assets/svg/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
116 changes: 88 additions & 28 deletions components/LeaderBoard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,55 @@
>
<thead class="text-xs text-white-200 sm:hidden">
<tr>
<th scope="col" class="px-md py-md text-start">Rank</th>
<th scope="col" class="px-md py-md text-start">Address</th>
<th scope="col" class="px-md py-md text-start">Staked Amount</th>
<th scope="col" class="px-md text-end">Timestamp</th>
<th
scope="col"
class="px-md py-md text-start cursor-pointer"
@click="setSortLabel(Label.rank)"
>
Rank
<SortArrow
:currentLabel="currentLabel"
:label="Label.rank"
:order="order"
/>
</th>
<th
scope="col"
class="px-md py-md text-start cursor-pointer"
@click="setSortLabel(Label.address)"
>
Withdrawer
<SortArrow
:currentLabel="currentLabel"
:label="Label.address"
:order="order"
/>
</th>
<th
scope="col"
class="px-md text-end cursor-pointer"
@click="setSortLabel(Label.amount)"
>
Staked Amount
<SortArrow
:currentLabel="currentLabel"
:label="Label.amount"
:order="order"
/>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(staker, index) in visibleStakers"
v-for="staker in sortedStakers"
:key="staker.withdrawer"
class="transition-all duration-200 even:bg-black-600 sm:grid sm:grid-col-1 sm:py-md"
>
<th class="label">Rank</th>
<td class="px-md py-md [&&]:sm:pt-sm whitespace-nowrap">
{{ index + 1 }}
{{ staker.rank }}
</td>
<th class="label">Address</th>
<th class="label">Withdrawer</th>
<td
class="px-md py-md [&&]:sm:pt-sm whitespace-nowrap text-sm text-wit-blue-500 font-mono truncate hover:cursor-pointer [&&]sm:py-xs"
>
Expand All @@ -37,16 +69,10 @@
</td>
<th class="label">Amount</th>
<td
class="px-md py-md [&&]:sm:pt-sm whitespace-nowrap text-sm font-bold text-black"
class="px-md py-md [&&]:sm:pt-sm whitespace-nowrap text-sm font-bold text-black text-end"
>
{{ formatNumber(nanoWitToWit(staker.amount).toFixed()) }} $WIT
</td>
<th class="label">Timestamp</th>
<td
class="px-md py-md [&&]:sm:pt-sm whitespace-nowrap text-sm text-end sm:text-start sm:py-sm font-mono"
>
{{ formatDate(staker.timestamp) }}
</td>
</tr>
</tbody>
</table>
Expand All @@ -55,32 +81,66 @@
class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-wit-blue-500"
/>
</div>
<!-- <div class="border-t border-white-500 flex justify-end pb-md" v-if="total > pageSize">
<WPagination :total="total" :pageSize="pageSize" :page="currentPage" v-model:page="currentPage" class="text-white-50 mt-md" />
</div> -->
<div class="flex justify-end pb-md">
<WPagination
v-if="total > pageSize"
:total="total"
:pageSize="pageSize"
v-model:page="currentPage"
class="text-white-50 mt-md"
/>
</div>
</BaseCard>
</template>

<script setup>
import dayjs from "dayjs"
// import { WPagination } from "wit-vue-ui"
<script setup lang="ts">
import { WPagination } from "wit-vue-ui"
import { ref, watch } from "vue"
import { Label, type AggregatedStaker, type Staker } from "@/types"

const props = defineProps({
loading: Boolean,
visibleStakers: Array,
visibleStakers: {
type: Array<Staker>,
required: true,
},
})

function formatDate(timestamp) {
const targetDate = dayjs(timestamp)

return targetDate.format("MMM D, YYYY [@] hh:mm A")
}
const pageSize = ref(15)
const currentPage = ref(1)
const total = computed(() => props.visibleStakers.length)
const total = computed(() => withdrawers.value.length)
watch(currentPage, (valX, _valY) => {
console.log("Page updated:", valX)
})

const currentLabel: Ref<Label> = ref(Label.rank)
const order: Ref<boolean> = ref(true)
const withdrawers = computed(() => getWithdrawers(props.visibleStakers))

const sortedStakers = computed(() => {
const formattedWithdrawers = formatWithdrawers(withdrawers.value)
const sortFunctions = {
[Label.rank]: (a: AggregatedStaker, b: AggregatedStaker) =>
order.value ? a.rank - b.rank : b.rank - a.rank,
[Label.address]: (a: AggregatedStaker, b: AggregatedStaker) =>
order.value
? a.withdrawer.localeCompare(b.withdrawer)
: b.withdrawer.localeCompare(a.withdrawer),
[Label.amount]: (a: AggregatedStaker, b: AggregatedStaker) =>
order.value ? b.amount - a.amount : a.amount - b.amount,
}
return formattedWithdrawers.sort(sortFunctions[currentLabel.value])
})

function setSortLabel(label: Label) {
if (currentLabel.value === label) {
order.value = !order.value
} else {
currentLabel.value = label
order.value = true
}
}

const pageSize = ref(withdrawers.value.length)
</script>
<style lang="scss" scoped>
.pointer-events-none {
Expand Down
33 changes: 33 additions & 0 deletions components/SortArrow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<SvgIcon
v-if="order && isCurrentLabel"
:name="'arrow-down'"
class="w-[10px] h-auto pl-xs inline"
/>
<SvgIcon
v-else-if="!order && isCurrentLabel"
:name="'arrow-up'"
class="w-[10px] h-auto pl-xs inline"
/>
<SvgIcon v-else :name="'arrow-down'" class="w-[10px] h-auto pl-xs inline" />
</template>

<script setup lang="ts">
import type { Label } from "~/types"

const props = defineProps({
label: {
required: true,
type: Number,
},
order: {
required: true,
type: Boolean,
},
currentLabel: {
required: true,
type: Number,
},
})
const isCurrentLabel = computed(() => props.currentLabel === props.label)
</script>
30 changes: 14 additions & 16 deletions components/StakeVolume.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,12 @@
<p class="data">{{ formatNumber(totalStakedFormatted) }} $WIT</p>
</div>
<div class="text-center mb-md">
<p class="title">Progress to Goal</p>
<p class="data">
{{ ((totalStakedFormatted / stakingGoal) * 100).toFixed(2) }}%
</p>
<p class="title">Staked supply</p>
<p class="data">{{ stakedSupplyPercentage }}%</p>
</div>
<div class="text-center mb-md">
<p class="title">Target</p>
<p class="data">{{ formatNumber(stakingGoal) }} $WIT</p>
</div>
</div>

<div class="relative">
<div class="overflow-hidden h-md bg-black-800 rounded-full">
<div
class="h-full bg-wit-blue-500 rounded-full transition-all duration-300"
:style="{ width: `${(totalStaked / stakingGoal) * 100}%` }"
/>
<p class="title">Stakers</p>
<p class="data">{{ numberOfStakers }}</p>
</div>
</div>
</div>
Expand All @@ -33,11 +22,20 @@ import { formatNumber } from "@/utils/formatNumber.js"
const props = defineProps({
visibleStakers: Array,
totalStaked: Number,
circulatingSupply: Number,
})
const stakingGoal = 300_000_000
const totalStakedFormatted = computed(() => {
return props.totalStaked ? nanoWitToWit(props.totalStaked).toFixed() : 0
})

const stakedSupplyPercentage = computed(() => {
return (
(props.totalStaked / (props.circulatingSupply * 1000000000)).toFixed(4) *
100
)
})

const numberOfStakers = ref(getWithdrawers(props.visibleStakers).length)
</script>

<style lang="scss" scoped>
Expand Down
2 changes: 2 additions & 0 deletions constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const EXPLORER_API = "https://witnet.network/api"
export const CACHE_TTL = 1 * 60 * 1000 // 1 minute
3 changes: 1 addition & 2 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,13 @@ export default defineNuxtConfig({
script: [
{
textContent: `
<!-- Twitter conversion tracking base code -->
<script>
!function(e,t,n,s,u,a){e.twq||(s=e.twq=function(){s.exe?s.exe.apply(s,arguments):s.queue.push(arguments);
},s.version='1.1',s.queue=[],u=t.createElement(n),u.async=!0,u.src='https://static.ads-twitter.com/uwt.js',
a=t.getElementsByTagName(n)[0],a.parentNode.insertBefore(u,a))}(window,document,'script');
twq('config','ovifm');
</script>
<!-- End Twitter conversion tracking base code -->`,
`,
type: "text/javascript",
},
],
Expand Down
38 changes: 38 additions & 0 deletions server/api/cacheData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { defineEventHandler } from "h3"
import { CACHE_TTL } from "~/constants"
import { RouteName } from "~/types"

const supplyCache: { data: any | null; timestamp: number } = {
data: null,
timestamp: 0,
}
const stakingCache: { data: any | null; timestamp: number } = {
data: null,
timestamp: 0,
}

const cacheByType = {
[RouteName.staking]: stakingCache,
[RouteName.supply]: supplyCache,
}

export default (call: Function, name: RouteName) =>
defineEventHandler(async () => {
const cache = cacheByType[name]
const now = Date.now()

// If cache is valid, return cached data
if (cache.data && now - cache.timestamp < CACHE_TTL) {
return cache.data
}

try {
const result = await call()
// Store in cache
cache.data = result
cache.timestamp = now
return cache.data
} catch {
return { error: "Failed to fetch data" }
}
})
Loading
Loading