From e8d33ee8a4f5266514a474653b738e767620c361 Mon Sep 17 00:00:00 2001
From: Anastasios <anastas3993@gmail.com>
Date: Tue, 8 Oct 2024 11:17:05 +0400
Subject: [PATCH 1/2] feat: add vebal manage stats and chart

---
 app/(app)/vebal/manage/page.tsx               |  12 +
 app/(app)/vebal/page.tsx                      |  11 +-
 lib/modules/vebal/VebalBreadcrumbs.tsx        |  31 ++
 lib/modules/vebal/VebalInfo.tsx               |  85 ------
 lib/modules/vebal/VebalManage.tsx             |  54 ++++
 .../vebal/VebalStats/AllVebalStatsValues.tsx  |  14 +
 .../vebal/VebalStats/UserVebalStatsValues.tsx | 115 +++++++
 lib/modules/vebal/VebalStats/VebalStats.tsx   |  82 +++++
 .../vebal/VebalStats/VebalStatsLayout.tsx     |  14 +
 lib/modules/vebal/useVebalUserData.ts         |   4 +-
 .../vebal/vebal-chart/VebalLocksChart.tsx     |  17 +-
 lib/modules/vebal/vebal-chart/test-locks.ts   | 282 ++++++++++++++----
 .../vebal/vebal-chart/useVebalLocksChart.tsx  | 183 ++++++++++--
 lib/shared/components/navs/useNav.tsx         |   4 +-
 14 files changed, 720 insertions(+), 188 deletions(-)
 create mode 100644 app/(app)/vebal/manage/page.tsx
 create mode 100644 lib/modules/vebal/VebalBreadcrumbs.tsx
 delete mode 100644 lib/modules/vebal/VebalInfo.tsx
 create mode 100644 lib/modules/vebal/VebalManage.tsx
 create mode 100644 lib/modules/vebal/VebalStats/AllVebalStatsValues.tsx
 create mode 100644 lib/modules/vebal/VebalStats/UserVebalStatsValues.tsx
 create mode 100644 lib/modules/vebal/VebalStats/VebalStats.tsx
 create mode 100644 lib/modules/vebal/VebalStats/VebalStatsLayout.tsx

diff --git a/app/(app)/vebal/manage/page.tsx b/app/(app)/vebal/manage/page.tsx
new file mode 100644
index 000000000..d43d569c7
--- /dev/null
+++ b/app/(app)/vebal/manage/page.tsx
@@ -0,0 +1,12 @@
+'use client'
+
+import { VebalManage } from '@/lib/modules/vebal/VebalManage'
+import { Stack } from '@chakra-ui/react'
+
+export default function VebalManagePage() {
+  return (
+    <Stack gap="lg">
+      <VebalManage />
+    </Stack>
+  )
+}
diff --git a/app/(app)/vebal/page.tsx b/app/(app)/vebal/page.tsx
index df584b3c2..01974c04d 100644
--- a/app/(app)/vebal/page.tsx
+++ b/app/(app)/vebal/page.tsx
@@ -1,12 +1,5 @@
-'use client'
-
-import { VebalInfo } from '@/lib/modules/vebal/VebalInfo'
 import { Stack } from '@chakra-ui/react'
 
-export default function VebalPage() {
-  return (
-    <Stack gap="lg">
-      <VebalInfo />
-    </Stack>
-  )
+export default function VeBALPage() {
+  return <Stack></Stack>
 }
diff --git a/lib/modules/vebal/VebalBreadcrumbs.tsx b/lib/modules/vebal/VebalBreadcrumbs.tsx
new file mode 100644
index 000000000..0e19c66be
--- /dev/null
+++ b/lib/modules/vebal/VebalBreadcrumbs.tsx
@@ -0,0 +1,31 @@
+import { Box, Breadcrumb, BreadcrumbItem, BreadcrumbLink, Button } from '@chakra-ui/react'
+import { ChevronRight, Home } from 'react-feather'
+
+export function VebalBreadcrumbs() {
+  return (
+    <Breadcrumb
+      color="grayText"
+      spacing="sm"
+      fontSize="sm"
+      separator={
+        <Box color="border.base">
+          <ChevronRight size={16} />
+        </Box>
+      }
+    >
+      <BreadcrumbItem>
+        <BreadcrumbLink href="/">
+          <Button variant="link" size="xs" color="grayText">
+            <Home size={16} />
+          </Button>
+        </BreadcrumbLink>
+      </BreadcrumbItem>
+      <BreadcrumbItem>
+        <BreadcrumbLink href={'/vebal/manage'}>veBAL</BreadcrumbLink>
+      </BreadcrumbItem>
+      <BreadcrumbItem isCurrentPage>
+        <BreadcrumbLink href="#">Manage</BreadcrumbLink>
+      </BreadcrumbItem>
+    </Breadcrumb>
+  )
+}
diff --git a/lib/modules/vebal/VebalInfo.tsx b/lib/modules/vebal/VebalInfo.tsx
deleted file mode 100644
index a76ac2d14..000000000
--- a/lib/modules/vebal/VebalInfo.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { useVebalLockInfo } from './useVebalLockInfo'
-import { bn, fNum } from '@/lib/shared/utils/numbers'
-import { differenceInDays, format } from 'date-fns'
-import { useUserAccount } from '../web3/UserAccountProvider'
-import { Stack, Text } from '@chakra-ui/react'
-import { VeBALLocksChart } from './vebal-chart/VebalLocksChart'
-import { useVebalUserData } from './useVebalUserData'
-import { useTokenBalances } from '../tokens/TokenBalancesProvider'
-import mainnetNetworkConfig from '@/lib/config/networks/mainnet'
-
-export function VebalInfo() {
-  const lockInfo = useVebalLockInfo()
-  const { isConnected } = useUserAccount()
-
-  const { data } = useVebalUserData()
-
-  const lockedUntil = !lockInfo.mainnetLockedInfo.lockedEndDate
-    ? '-'
-    : format(lockInfo.mainnetLockedInfo.lockedEndDate, 'yyyy-MM-dd')
-
-  const percentOfAllSupply = bn(data?.veBalGetUser.balance || 0).div(
-    lockInfo.mainnetLockedInfo.totalSupply || 0
-  )
-
-  const { balanceFor } = useTokenBalances()
-  const unlockedBalance = balanceFor(mainnetNetworkConfig.tokens.addresses.veBalBpt)
-
-  const lockData = [
-    {
-      title: 'Locked veBAL',
-      value: lockInfo.mainnetLockedInfo.lockedAmount,
-    },
-    {
-      title: 'Unlocked veBAL',
-      value: unlockedBalance?.formatted,
-    },
-  ]
-
-  const vebalData = [
-    {
-      title: 'My veBAL',
-      value: data?.veBalGetUser.balance,
-    },
-    {
-      title: '',
-      value: lockedUntil
-        ? `Expires ${lockedUntil} (${differenceInDays(new Date(lockedUntil), new Date())} days)`
-        : '',
-    },
-    {
-      title: '% of all supply',
-      value: fNum('feePercent', percentOfAllSupply),
-    },
-    {
-      title: 'Rank',
-      value: data?.veBalGetUser.rank,
-    },
-  ]
-
-  if (!isConnected) {
-    return <Text>Not connected</Text>
-  }
-
-  return (
-    <Stack spacing="lg">
-      {vebalData.map(({ title, value }) => (
-        <Stack key={title} spacing="2">
-          <Text fontWeight={700} fontSize="lg">
-            {title}
-          </Text>
-          <Text>{value}</Text>
-        </Stack>
-      ))}
-      <Stack spacing="lg">
-        {lockData.map(({ title, value }) => (
-          <Stack key={title} spacing="2">
-            <Text>{title}</Text>
-            <Text>{value}</Text>
-          </Stack>
-        ))}
-      </Stack>
-      <VeBALLocksChart />
-    </Stack>
-  )
-}
diff --git a/lib/modules/vebal/VebalManage.tsx b/lib/modules/vebal/VebalManage.tsx
new file mode 100644
index 000000000..7284976bb
--- /dev/null
+++ b/lib/modules/vebal/VebalManage.tsx
@@ -0,0 +1,54 @@
+import { useUserAccount } from '../web3/UserAccountProvider'
+import { Button, Heading, Stack, Text, VStack } from '@chakra-ui/react'
+import { VebalStatsLayout } from './VebalStats/VebalStatsLayout'
+import { VebalBreadcrumbs } from '@/lib/modules/vebal/VebalBreadcrumbs'
+
+export function VebalManage() {
+  const { isConnected } = useUserAccount()
+
+  if (!isConnected) {
+    return <Text>Not connected</Text>
+  }
+
+  return (
+    <Stack spacing="lg">
+      <VebalBreadcrumbs />
+      <VStack align="start" w="full">
+        <Stack
+          w="full"
+          justify="space-between"
+          alignItems="center"
+          spacing="md"
+          direction={{ base: 'column', md: 'row' }}
+        >
+          <Heading as="h2" size="lg" variant="special">
+            Manage veBAL
+          </Heading>
+
+          <Stack spacing="md" direction={{ base: 'column', md: 'row' }}>
+            <Button
+              onClick={() => {
+                //
+              }}
+              size="lg"
+              isDisabled={false}
+            >
+              Extend lock
+            </Button>
+            <Button
+              onClick={() => {
+                //
+              }}
+              variant="primary"
+              size="lg"
+              isDisabled={false}
+            >
+              Get veBAL
+            </Button>
+          </Stack>
+        </Stack>
+      </VStack>
+      <VebalStatsLayout />
+    </Stack>
+  )
+}
diff --git a/lib/modules/vebal/VebalStats/AllVebalStatsValues.tsx b/lib/modules/vebal/VebalStats/AllVebalStatsValues.tsx
new file mode 100644
index 000000000..3e6e4776c
--- /dev/null
+++ b/lib/modules/vebal/VebalStats/AllVebalStatsValues.tsx
@@ -0,0 +1,14 @@
+'use client'
+import { Stack } from '@chakra-ui/react'
+import BigNumber from 'bignumber.js'
+
+export type VebalAllStatsValues = {
+  balance: string | undefined
+  rank: number | undefined
+  percentOfAllSupply: BigNumber | undefined
+  lockedUntil: string | undefined
+}
+
+export function AllVebalStatsValues() {
+  return <Stack></Stack>
+}
diff --git a/lib/modules/vebal/VebalStats/UserVebalStatsValues.tsx b/lib/modules/vebal/VebalStats/UserVebalStatsValues.tsx
new file mode 100644
index 000000000..4a1892344
--- /dev/null
+++ b/lib/modules/vebal/VebalStats/UserVebalStatsValues.tsx
@@ -0,0 +1,115 @@
+'use client'
+
+import React, { useMemo } from 'react'
+import { Heading, Skeleton, Text, Tooltip, VStack } from '@chakra-ui/react'
+import { bn, fNum } from '@/lib/shared/utils/numbers'
+import { useVebalUserData } from '@/lib/modules/vebal/useVebalUserData'
+import { useVebalLockInfo } from '@/lib/modules/vebal/useVebalLockInfo'
+import { differenceInDays, format } from 'date-fns'
+import BigNumber from 'bignumber.js'
+
+export type VebalUserStatsValues = {
+  balance: string | undefined
+  rank: number | undefined
+  percentOfAllSupply: BigNumber | undefined
+  lockedUntil: string | undefined
+}
+
+export function UserVebalStatsValues() {
+  const lockInfo = useVebalLockInfo()
+  const vebalUserData = useVebalUserData()
+
+  const vebalUserStatsValues: VebalUserStatsValues | undefined = useMemo(() => {
+    if (vebalUserData.isConnected) {
+      const balance = vebalUserData.data?.veBalGetUser.balance
+      const rank = vebalUserData.data?.veBalGetUser.rank ?? undefined
+      const percentOfAllSupply = vebalUserData.data
+        ? bn(vebalUserData.data.veBalGetUser.balance || 0).div(
+            lockInfo.mainnetLockedInfo.totalSupply || 0
+          )
+        : undefined
+      const lockedUntil =
+        !lockInfo.mainnetLockedInfo.lockedEndDate || lockInfo.mainnetLockedInfo.isExpired
+          ? undefined
+          : format(lockInfo.mainnetLockedInfo.lockedEndDate, 'yyyy-MM-dd')
+
+      return {
+        balance,
+        rank,
+        percentOfAllSupply,
+        lockedUntil,
+      }
+    }
+  }, [lockInfo.mainnetLockedInfo, vebalUserData.isConnected, vebalUserData.data])
+
+  return (
+    <>
+      <VStack spacing="0" align="flex-start" w="full">
+        <Text variant="secondary" fontWeight="semibold" fontSize="sm" mt="xxs">
+          My veBAL
+        </Text>
+        {vebalUserData.loading ? (
+          <Skeleton height="28px" w="100px" />
+        ) : (
+          <Heading size="h4">
+            {typeof vebalUserStatsValues?.balance === 'string' ? (
+              fNum('token', vebalUserStatsValues.balance)
+            ) : (
+              <>&mdash;</>
+            )}
+          </Heading>
+        )}
+      </VStack>
+      <VStack spacing="0" align="flex-start" w="full">
+        <Text variant="secondary" fontWeight="semibold" fontSize="sm" mt="xxs">
+          My rank
+        </Text>
+        {vebalUserData.loading ? (
+          <Skeleton height="28px" w="100px" />
+        ) : (
+          <Heading size="h4">{vebalUserStatsValues?.rank ?? <>&mdash;</>}</Heading>
+        )}
+      </VStack>
+      <VStack spacing="0" align="flex-start" w="full">
+        <Text variant="secondary" fontWeight="semibold" fontSize="sm" mt="xxs">
+          My share of veBAL
+        </Text>
+        {vebalUserData.loading ? (
+          <Skeleton height="28px" w="100px" />
+        ) : (
+          <Heading size="h4">
+            {vebalUserStatsValues?.percentOfAllSupply ? (
+              fNum('feePercent', vebalUserStatsValues.percentOfAllSupply)
+            ) : (
+              <>&mdash;</>
+            )}
+          </Heading>
+        )}
+      </VStack>
+      <VStack spacing="0" align="flex-start" w="full">
+        <Text variant="secondary" fontWeight="semibold" fontSize="sm" mt="xxs">
+          Expiry date
+        </Text>
+        {lockInfo.isLoading ? (
+          <Skeleton height="28px" w="100px" />
+        ) : (
+          <Tooltip
+            label={
+              vebalUserStatsValues?.lockedUntil
+                ? `Expires ${format(new Date(vebalUserStatsValues.lockedUntil), 'dd MMM yyyy')}`
+                : undefined
+            }
+          >
+            <Heading size="h4">
+              {vebalUserStatsValues?.lockedUntil ? (
+                `${differenceInDays(new Date(vebalUserStatsValues.lockedUntil), new Date())} days`
+              ) : (
+                <>&mdash;</>
+              )}
+            </Heading>
+          </Tooltip>
+        )}
+      </VStack>
+    </>
+  )
+}
diff --git a/lib/modules/vebal/VebalStats/VebalStats.tsx b/lib/modules/vebal/VebalStats/VebalStats.tsx
new file mode 100644
index 000000000..1a1216153
--- /dev/null
+++ b/lib/modules/vebal/VebalStats/VebalStats.tsx
@@ -0,0 +1,82 @@
+'use client'
+
+import React, { useState } from 'react'
+import { Box, BoxProps, Card, CardProps, VStack } from '@chakra-ui/react'
+
+import { NoisyCard } from '@/lib/shared/components/containers/NoisyCard'
+import { ZenGarden } from '@/lib/shared/components/zen/ZenGarden'
+import ButtonGroup, {
+  ButtonGroupOption,
+} from '@/lib/shared/components/btns/button-group/ButtonGroup'
+
+import { UserVebalStatsValues } from '@/lib/modules/vebal/VebalStats/UserVebalStatsValues'
+import { AllVebalStatsValues } from '@/lib/modules/vebal/VebalStats/AllVebalStatsValues'
+
+const COMMON_NOISY_CARD_PROPS: { contentProps: BoxProps; cardProps: BoxProps } = {
+  contentProps: {
+    display: 'flex',
+    alignItems: 'center',
+    justifyContent: 'center',
+    borderBottomLeftRadius: 'none',
+    borderTopLeftRadius: 'none',
+    borderBottomRightRadius: 'none',
+  },
+  cardProps: {
+    position: 'relative',
+    height: 'full',
+  },
+}
+
+const TABS = [
+  {
+    value: 'allStats',
+    label: 'All stats',
+  },
+  {
+    value: 'myStats',
+    label: 'My stats',
+  },
+] as const
+
+export function VebalStats({ ...props }: CardProps) {
+  const [activeTab, setActiveTab] = useState<ButtonGroupOption>(TABS[1])
+
+  function handleTabChanged(option: ButtonGroupOption) {
+    setActiveTab(option)
+  }
+
+  return (
+    <Card position="relative" {...props}>
+      <NoisyCard
+        cardProps={COMMON_NOISY_CARD_PROPS.cardProps}
+        contentProps={COMMON_NOISY_CARD_PROPS.contentProps}
+      >
+        <Box top={0} bottom={0} left={0} right={0} position="absolute" overflow="hidden">
+          <ZenGarden variant="circle" sizePx="280px" subdued />
+        </Box>
+        <VStack
+          spacing="xl"
+          m="auto"
+          align="flex-start"
+          w="full"
+          justify="flex-start"
+          mb="8"
+          p={{ base: 'sm', md: 'md' }}
+          zIndex={1}
+          h="full"
+          role="group"
+        >
+          <ButtonGroup
+            size="xxs"
+            currentOption={activeTab}
+            options={TABS}
+            onChange={handleTabChanged}
+            groupId="pool-stats"
+          />
+          {activeTab.value === 'allStats' && <AllVebalStatsValues />}
+          {activeTab.value === 'myStats' && <UserVebalStatsValues />}
+        </VStack>
+      </NoisyCard>
+    </Card>
+  )
+}
diff --git a/lib/modules/vebal/VebalStats/VebalStatsLayout.tsx b/lib/modules/vebal/VebalStats/VebalStatsLayout.tsx
new file mode 100644
index 000000000..122b08908
--- /dev/null
+++ b/lib/modules/vebal/VebalStats/VebalStatsLayout.tsx
@@ -0,0 +1,14 @@
+'use client'
+
+import { Stack } from '@chakra-ui/react'
+import { VebalStats } from '@/lib/modules/vebal/VebalStats/VebalStats'
+import { VeBALLocksChart } from '@/lib/modules/vebal/vebal-chart/VebalLocksChart'
+
+export function VebalStatsLayout() {
+  return (
+    <Stack w="full" spacing="md" direction={{ base: 'column', md: 'row' }} justifyContent="stretch">
+      <VebalStats w={{ base: 'full', md: 'md' }} />
+      <VeBALLocksChart w="full" h={{ base: 'md', md: 'auto' }} />
+    </Stack>
+  )
+}
diff --git a/lib/modules/vebal/useVebalUserData.ts b/lib/modules/vebal/useVebalUserData.ts
index 8d77927af..ee5ed2c85 100644
--- a/lib/modules/vebal/useVebalUserData.ts
+++ b/lib/modules/vebal/useVebalUserData.ts
@@ -5,7 +5,7 @@ import { useQuery } from '@apollo/experimental-nextjs-app-support/ssr'
 export function useVebalUserData() {
   const { userAddress, isConnected } = useUserAccount()
 
-  const { data, refetch } = useQuery(GetVeBalUserDocument, {
+  const { data, refetch, loading, error } = useQuery(GetVeBalUserDocument, {
     variables: {
       address: userAddress.toLowerCase(),
       chain: GqlChain.Mainnet,
@@ -16,5 +16,7 @@ export function useVebalUserData() {
     data,
     refetch,
     isConnected,
+    loading,
+    error,
   }
 }
diff --git a/lib/modules/vebal/vebal-chart/VebalLocksChart.tsx b/lib/modules/vebal/vebal-chart/VebalLocksChart.tsx
index e5fde03d8..81d07319a 100644
--- a/lib/modules/vebal/vebal-chart/VebalLocksChart.tsx
+++ b/lib/modules/vebal/vebal-chart/VebalLocksChart.tsx
@@ -1,14 +1,19 @@
-import { Stack } from '@chakra-ui/react'
+import { Card, CardProps } from '@chakra-ui/react'
 
 import ReactECharts from 'echarts-for-react'
 import { useVebalLocksChart } from './useVebalLocksChart'
 
-export function VeBALLocksChart() {
-  const { options } = useVebalLocksChart()
+export function VeBALLocksChart(props: CardProps) {
+  const { options, onChartReady, onEvents } = useVebalLocksChart()
 
   return (
-    <Stack w="full" h="full" height="300px">
-      <ReactECharts style={{ height: '100%', width: '100%' }} option={options} onEvents={{}} />
-    </Stack>
+    <Card position="relative" {...props}>
+      <ReactECharts
+        onChartReady={onChartReady}
+        style={{ height: '100%', width: '100%' }}
+        option={options}
+        onEvents={onEvents}
+      />
+    </Card>
   )
 }
diff --git a/lib/modules/vebal/vebal-chart/test-locks.ts b/lib/modules/vebal/vebal-chart/test-locks.ts
index 8f957c07d..453180d32 100644
--- a/lib/modules/vebal/vebal-chart/test-locks.ts
+++ b/lib/modules/vebal/vebal-chart/test-locks.ts
@@ -1,92 +1,254 @@
 export const lockSnapshots = [
   {
-    bias: '301.388808164359183128',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1648477572',
-    slope: '0.000009705929936826',
-    timestamp: 1648477572,
+    bias: '411.968613215338375488',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1648494832',
+    slope: '0.000013274422196916',
+    timestamp: 1648494832,
   },
   {
-    bias: '618.950318966359818012',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1651885359',
-    slope: '0.000022389846730332',
-    timestamp: 1651885359,
+    bias: '426.768704596875629824',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1648914664',
+    slope: '0.000013939885570784',
+    timestamp: 1648914664,
   },
   {
-    bias: '956.863613477739943196',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1654088236',
-    slope: '0.000037610546882539',
-    timestamp: 1654088236,
+    bias: '437.379758392016650735',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1649386739',
+    slope: '0.000014510227094635',
+    timestamp: 1649386739,
   },
   {
-    bias: '1002.355111964593047795',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1654088295',
-    slope: '0.000037610546882539',
-    timestamp: 1654088295,
+    bias: '498.274642722178822178',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1650253669',
+    slope: '0.000017019941832838',
+    timestamp: 1650253669,
   },
   {
-    bias: '1475.194475239244712888',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1656527262',
-    slope: '0.000060928393061276',
-    timestamp: 1656527262,
+    bias: '525.4719047945281344',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1651287630',
+    slope: '0.00001860606412352',
+    timestamp: 1651287630,
   },
   {
-    bias: '1504.241981926209602436',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1658797166',
-    slope: '0.000068555266203954',
-    timestamp: 1658797166,
+    bias: '526.22180254040241455',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1652399425',
+    slope: '0.000019396181651626',
+    timestamp: 1652399425,
   },
   {
-    bias: '428.293668367008540547',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1682703443',
-    slope: '0.000013683610015471',
-    timestamp: 1682703443,
+    bias: '537.78149185152830984',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1652671730',
+    slope: '0.000020023236833432',
+    timestamp: 1652671730,
   },
   {
-    bias: '771.437205712039985286',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1685451467',
-    slope: '0.000027018927562542',
-    timestamp: 1685451467,
+    bias: '546.23608139662436016',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1652935896',
+    slope: '0.00002054005269054',
+    timestamp: 1652935896,
   },
   {
-    bias: '836.800746817080090678',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1685451491',
-    slope: '0.000027018927562542',
-    timestamp: 1685451491,
+    bias: '645.68362210525044576',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1655956656',
+    slope: '0.00002054005269054',
+    timestamp: 1655956656,
   },
   {
-    bias: '1145.006774769866674166',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1687828823',
-    slope: '0.000040044195057158',
-    timestamp: 1687828823,
+    bias: '597.063090614348179615',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1659069463',
+    slope: '0.000021080847757895',
+    timestamp: 1659069463,
   },
   {
-    bias: '1241.880730391461935974',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1687828847',
-    slope: '0.000040044195057158',
-    timestamp: 1687828847,
+    bias: '570.212661942865193703',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1660773221',
+    slope: '0.000021421443182757',
+    timestamp: 1660773221,
   },
   {
-    bias: '1894.53106627656545857',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1689381779',
-    slope: '0.00006430898090917',
-    timestamp: 1689381779,
+    bias: '578.856298996191034753',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1660960409',
+    slope: '0.000021900168589783',
+    timestamp: 1660960409,
   },
   {
-    bias: '2011.21173782262168649',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1689381803',
-    slope: '0.00006430898090917',
-    timestamp: 1689381803,
+    bias: '873.31490731813230788',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1661367935',
+    slope: '0.000033557974410152',
+    timestamp: 1661367935,
   },
   {
-    bias: '2486.14202915624302687',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1692387515',
-    slope: '0.000087947480353342',
-    timestamp: 1692387515,
+    bias: '784.567787366075541538',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1664163563',
+    slope: '0.000033776176475674',
+    timestamp: 1664163563,
   },
   {
-    bias: '2752.093099005220754662',
-    id: '0x25b70c8050b7e327ce62cfd80a0c60cccf057fa6-1692387539',
-    slope: '0.000087947480353342',
-    timestamp: 1692387539,
+    bias: '1050.128381346061674874',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1664163599',
+    slope: '0.000033776176475674',
+    timestamp: 1664163599,
+  },
+  {
+    bias: '931.328332517805007732',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1668044771',
+    slope: '0.000034227895298308',
+    timestamp: 1668044771,
+  },
+  {
+    bias: '1076.234317848491017444',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1668044807',
+    slope: '0.000034227895298308',
+    timestamp: 1668044807,
+  },
+  {
+    bias: '994.550288880754896919',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1671037031',
+    slope: '0.000034956640277551',
+    timestamp: 1671037031,
+  },
+  {
+    bias: '1100.258330120702459695',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1671037055',
+    slope: '0.000034956640277551',
+    timestamp: 1671037055,
+  },
+  {
+    bias: '986.354377972902335952',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1674874919',
+    slope: '0.000035689528064592',
+    timestamp: 1674874919,
+  },
+  {
+    bias: '1122.554182554120910032',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1675897079',
+    slope: '0.000035689528064592',
+    timestamp: 1675897079,
+  },
+  {
+    bias: '1103.901122090218030416',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1685491727',
+    slope: '0.000035689528064592',
+    timestamp: 1685491727,
+  },
+  {
+    bias: '1163.452523590283058086',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1686367439',
+    slope: '0.000038710831253126',
+    timestamp: 1686367439,
+  },
+  {
+    bias: '1210.27575148413915515',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1686367475',
+    slope: '0.000038710831253126',
+    timestamp: 1686367475,
+  },
+  {
+    bias: '1185.891454125148391631',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1687746371',
+    slope: '0.000039680993634939',
+    timestamp: 1687746371,
+  },
+  {
+    bias: '1233.888155510199748227',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1687746407',
+    slope: '0.000039680993634939',
+    timestamp: 1687746407,
+  },
+  {
+    bias: '1211.693697205053065936',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1688699951',
+    slope: '0.000040199980339664',
+    timestamp: 1688699951,
+  },
+  {
+    bias: '1260.3186286243824884',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1688699975',
+    slope: '0.000040199980339664',
+    timestamp: 1688699975,
+  },
+  {
+    bias: '1167.088644074268523808',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1691292359',
+    slope: '0.000040581908153888',
+    timestamp: 1691292359,
+  },
+  {
+    bias: '1265.26293533146083344',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1691292395',
+    slope: '0.000040581908153888',
+    timestamp: 1691292395,
+  },
+  {
+    bias: '1282.359069389423530864',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1691721443',
+    slope: '0.000041704148514352',
+    timestamp: 1691721443,
+  },
+  {
+    bias: '1226.553715113925634922',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1693644311',
+    slope: '0.000042550125863898',
+    timestamp: 1693644311,
+  },
+  {
+    bias: '1329.48995840084694297',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1693644335',
+    slope: '0.000042550125863898',
+    timestamp: 1693644335,
+  },
+  {
+    bias: '1311.815238816018343179',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1694491019',
+    slope: '0.000043153831384959',
+    timestamp: 1694491019,
+  },
+  {
+    bias: '1051.61523535449844771',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1700936615',
+    slope: '0.000043903306220686',
+    timestamp: 1700936615,
+  },
+  {
+    bias: '1370.244182704026623686',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1700936699',
+    slope: '0.000043903306220686',
+    timestamp: 1700936699,
+  },
+  {
+    bias: '1207.52747382758520986',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1706411831',
+    slope: '0.00004692093102794',
+    timestamp: 1706411831,
+  },
+  {
+    bias: '1462.92523339417887674',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1706411879',
+    slope: '0.00004692093102794',
+    timestamp: 1706411879,
+  },
+  {
+    bias: '1389.499237660943196577',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1708406507',
+    slope: '0.000047611853485789',
+    timestamp: 1708406507,
+  },
+  {
+    bias: '1130.568460169324406853',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1714278167',
+    slope: '0.000048496789654141',
+    timestamp: 1714278167,
+  },
+  {
+    bias: '989.152639305655695838',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1717392263',
+    slope: '0.000048972469060174',
+    timestamp: 1717392263,
+  },
+  {
+    bias: '1522.28006211641798647',
+    id: '0x5e4568c4d8343052a06ec8aab1e124af08b73248-1717392395',
+    slope: '0.000048972469060174',
+    timestamp: 1717392395,
   },
 ]
diff --git a/lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx b/lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx
index b1ff9c68d..b80f1d9e0 100644
--- a/lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx
+++ b/lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx
@@ -1,14 +1,17 @@
-import { useMemo } from 'react'
+import { useCallback, useEffect, useMemo, useRef } from 'react'
+
 import { useTheme as useChakraTheme } from '@chakra-ui/react'
 
 import * as echarts from 'echarts/core'
+import { EChartsOption, ECharts } from 'echarts'
 import { format, differenceInDays } from 'date-fns'
 import BigNumber from 'bignumber.js'
 import { lockSnapshots } from './test-locks'
 import { useVebalLockInfo } from '../../vebal/useVebalLockInfo'
 import { bn } from '@/lib/shared/utils/numbers'
+import { useTheme as useNextTheme } from 'next-themes'
 
-type ChartValueAcc = (readonly [string, number])[]
+type ChartValueAcc = [string, number][]
 
 interface LockSnapshot {
   bias: string
@@ -72,8 +75,14 @@ function filterAndFlattenValues(valuesByDates: Record<string, number[]>) {
   }, [])
 }
 
+const MAIN_SERIES_ID = 'main-series'
+const FUTURE_SERIES_ID = 'future-series'
+
 export function useVebalLocksChart() {
   const theme = useChakraTheme()
+  const { theme: nextTheme } = useNextTheme()
+
+  const instanceRef = useRef<ECharts>()
 
   const userHistoricalLocks = lockSnapshots
 
@@ -95,29 +104,97 @@ export function useVebalLocksChart() {
   const futureLockChartData = useMemo(() => {
     if (hasExistingLock && !isExpired) {
       return {
+        id: FUTURE_SERIES_ID,
         name: '',
-        type: 'line',
+        type: 'line' as const,
         data: [
           chartValues[chartValues.length - 1],
           [format(new Date(mainnetLockedInfo.lockedEndDate).getTime(), 'yyyy/MM/dd'), 0],
         ],
         lineStyle: {
-          type: 'dashed',
+          type: [3, 15],
+          color: '#EAA879',
+          width: 5,
+          cap: 'round' as const,
         },
+        showSymbol: false,
       }
     }
     return {
       name: '',
-      type: 'line',
+      type: 'line' as const,
       data: [],
     }
   }, [chartValues, mainnetLockedInfo.lockedEndDate, hasExistingLock, isExpired])
 
-  const options = useMemo(() => {
+  const showStaticTooltip = useCallback(() => {
+    if (!mouseoverRef.current) {
+      if (instanceRef.current) {
+        // Show tooltip on a specific data point when chart is loaded
+        instanceRef.current.dispatchAction({
+          type: 'showTip',
+          seriesIndex: 0, // Index of the series
+          dataIndex: chartValues.length - 1, // Index of the data point
+        })
+      }
+    }
+  }, [chartValues])
+
+  const onChartReady = useCallback((instance: ECharts) => {
+    instanceRef.current = instance
+  }, [])
+
+  // detect if "static" tooltip is showing
+  const mouseoverRef = useRef<boolean | undefined>()
+
+  useEffect(() => {
+    if (instanceRef.current) {
+      const handler = () => {
+        mouseoverRef.current = true
+      }
+      const element = instanceRef.current.getDom()
+
+      // using "addEventListener" instead of "onEvents.mouseover" since "onEvents.mouseover" emits only when cursor crosses the line, not the entire chart...
+      element.addEventListener('mouseover', handler)
+
+      return () => {
+        element.removeEventListener('mouseover', handler)
+      }
+    }
+  }, [])
+
+  const onEvents = useMemo((): Partial<
+    Record<
+      echarts.ElementEvent['type'] | 'highlight' | 'finished',
+      (event: echarts.ElementEvent | any, instance: ECharts) => boolean | void
+    >
+  > => {
+    return {
+      click: () => {
+        mouseoverRef.current = true
+      },
+      globalout: () => {
+        mouseoverRef.current = false
+        showStaticTooltip()
+      },
+      finished: () => {
+        showStaticTooltip()
+      },
+    }
+  }, [showStaticTooltip])
+
+  const options = useMemo((): EChartsOption => {
     const toolTipTheme = {
       heading: 'font-weight: bold; color: #E5D3BE',
-      container: `background: ${theme.colors.gray[800]};`,
-      text: theme.colors.gray[400],
+      container: `background: ${
+        nextTheme === 'dark'
+          ? theme.semanticTokens.colors.background.level3._dark
+          : theme.semanticTokens.colors.background.default
+      };`,
+      text:
+        nextTheme === 'dark'
+          ? theme.semanticTokens.colors.font.primary._dark
+          : theme.semanticTokens.colors.font.primary.default,
     }
     return {
       grid: {
@@ -139,26 +216,67 @@ export function useVebalLocksChart() {
             show: false,
           },
         },
-        extraCssText: `padding-right:2rem;border: none;${toolTipTheme.container}`,
-        formatter: (params: any) => {
+        extraCssText: `border: none;${toolTipTheme.container};max-width: 215px`,
+        position: (point, params, dom, rect, size) => {
+          if (!mouseoverRef.current) {
+            return [point[0] - size.contentSize[0] / 2, 0]
+          }
+          return [point[0] + 15, point[1] + 15]
+        },
+        formatter: params => {
           const firstPoint = Array.isArray(params) ? params[0] : params
           const secondPoint = Array.isArray(params) ? params[1] : null
 
+          const firstPointValue = firstPoint.value as number[]
+          const secondPointValue = secondPoint ? (secondPoint.value as number[]) : null
+
+          if (!mouseoverRef.current) {
+            if (firstPoint.seriesId === MAIN_SERIES_ID) {
+              if (firstPoint.dataIndex === chartValues.length - 1) {
+                return `
+                <div style="padding: unset; display: flex; flex-direction: column; justify-content: center;
+                  ${toolTipTheme.container}">
+                  <div style="font-size: 0.85rem; font-weight: 500; white-space: normal; line-height: 20px;
+                    color: ${toolTipTheme.text};">
+                    Increase your lock to 1 year to maximize your veBAL to 30.346 (mocked text)
+                  </div>
+                </div>
+              `
+              }
+            }
+          }
+
           return `
-            <div style="padding: none; display: flex; flex-direction: column; justify-content: center;${
-              toolTipTheme.container
-            }">
-              <div style="font-size: 0.85rem; font-weight: 500; color: ${toolTipTheme.text};">
-                ${format(new Date(firstPoint.value[0]), 'yyyy/MM/dd')}
-              </div>
-              <div style="display: flex; flex-direction: column; font-size: 14px; font-weight: 500; color: ${
-                toolTipTheme.text
-              };">
-                <span>${secondPoint ? `${secondPoint.value[1]} veBAL` : ''}</span>
-                <span>${firstPoint.value[1]} veBAL</span>
-              </div>
+          <div style="padding: unset; display: flex; flex-direction: column;
+            justify-content: center; ${toolTipTheme.container}">
+            <div style="font-size: 14px; font-weight: 700; display: flex; flex-wrap: wrap;
+              justify-content: start; gap: 0px; letter-spacing: -0.25px; padding-bottom: 2px;
+              ${toolTipTheme.heading}; color: ${toolTipTheme.text};">
+              ${format(new Date(firstPointValue[0]), 'yyyy/MM/dd')}
+            </div>
+            <div style="display: flex; flex-direction: column; font-size: 14px;
+              line-height: 20px; font-weight: 500; color: ${toolTipTheme.text};">
+              ${
+                secondPointValue
+                  ? `
+                    <span>
+                      <span style="display: inline-block; margin-right: 4px; border-radius: 10px;
+                        width: 10px; height: 10px; background-color: #BCA25D;">
+                      </span>
+                      <span>${secondPointValue[1]} veBAL</span>
+                    </span>
+                  `
+                  : ''
+              }
+              <span>
+                <span style="display: inline-block; margin-right: 4px; border-radius: 10px;
+                  width: 10px; height: 10px; background-color: #BCA25D;">
+                </span>
+                <span>${firstPointValue[1]} veBAL</span>
+              </span>
             </div>
-          `
+          </div>
+        `
         },
       },
       xAxis: {
@@ -202,8 +320,9 @@ export function useVebalLocksChart() {
       },
       series: [
         {
+          id: MAIN_SERIES_ID,
           name: '',
-          type: 'line',
+          type: 'line' as const,
           data: chartValues,
           areaStyle: {
             color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
@@ -211,15 +330,29 @@ export function useVebalLocksChart() {
               { offset: 1, color: 'rgba(68, 9, 236, 0)' },
             ]),
           },
+          lineStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 1, 1, [
+              { offset: 0, color: '#B3AEF5' },
+              { offset: 0.33, color: '#D7CBE7' },
+              { offset: 0.66, color: '#E5C8C8' },
+              { offset: 1, color: '#EAA879' },
+            ]),
+            width: 5,
+            join: 'round' as const,
+            cap: 'round' as const,
+          },
+          showSymbol: false,
         },
         futureLockChartData,
       ],
     }
-  }, [chartValues, futureLockChartData, theme])
+  }, [chartValues, futureLockChartData, theme, nextTheme])
 
   return {
     lockedUntil,
     chartData: options,
     options,
+    onChartReady,
+    onEvents,
   }
 }
diff --git a/lib/shared/components/navs/useNav.tsx b/lib/shared/components/navs/useNav.tsx
index a4e87aaa5..8ede56793 100644
--- a/lib/shared/components/navs/useNav.tsx
+++ b/lib/shared/components/navs/useNav.tsx
@@ -31,8 +31,8 @@ export function useNav() {
   // To-do: Remove this when veBAL is live
   if (isDev || isStaging) {
     appLinks.push({
-      href: '/vebal',
-      label: 'veBAL (wip)',
+      href: '/vebal/manage',
+      label: 'veBAL (manage)',
     })
   }
 

From 5c9cd6b081b01ea76c9b506ef74de5709995510e Mon Sep 17 00:00:00 2001
From: Anastasios <anastas3993@gmail.com>
Date: Tue, 8 Oct 2024 12:13:28 +0400
Subject: [PATCH 2/2] refactor: num format

---
 lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx b/lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx
index b80f1d9e0..c33159e0d 100644
--- a/lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx
+++ b/lib/modules/vebal/vebal-chart/useVebalLocksChart.tsx
@@ -8,7 +8,7 @@ import { format, differenceInDays } from 'date-fns'
 import BigNumber from 'bignumber.js'
 import { lockSnapshots } from './test-locks'
 import { useVebalLockInfo } from '../../vebal/useVebalLockInfo'
-import { bn } from '@/lib/shared/utils/numbers'
+import { bn, fNum } from '@/lib/shared/utils/numbers'
 import { useTheme as useNextTheme } from 'next-themes'
 
 type ChartValueAcc = [string, number][]
@@ -216,7 +216,7 @@ export function useVebalLocksChart() {
             show: false,
           },
         },
-        extraCssText: `border: none;${toolTipTheme.container};max-width: 215px`,
+        extraCssText: `border: none;${toolTipTheme.container};max-width: 215px; z-index: 5`,
         position: (point, params, dom, rect, size) => {
           if (!mouseoverRef.current) {
             return [point[0] - size.contentSize[0] / 2, 0]
@@ -263,7 +263,7 @@ export function useVebalLocksChart() {
                       <span style="display: inline-block; margin-right: 4px; border-radius: 10px;
                         width: 10px; height: 10px; background-color: #BCA25D;">
                       </span>
-                      <span>${secondPointValue[1]} veBAL</span>
+                      <span>${fNum('token', secondPointValue[1])} veBAL</span>
                     </span>
                   `
                   : ''
@@ -272,8 +272,8 @@ export function useVebalLocksChart() {
                 <span style="display: inline-block; margin-right: 4px; border-radius: 10px;
                   width: 10px; height: 10px; background-color: #BCA25D;">
                 </span>
-                <span>${firstPointValue[1]} veBAL</span>
-              </span>
+                <span>${fNum('token', firstPointValue[1])} veBAL</span>
+                </span>
             </div>
           </div>
         `