diff --git a/src/components/commons/Logo/EthLogo.tsx b/src/components/commons/Logo/EthLogo.tsx new file mode 100644 index 0000000..0ee2407 --- /dev/null +++ b/src/components/commons/Logo/EthLogo.tsx @@ -0,0 +1,18 @@ +import { Icon, IconProps } from '@chakra-ui/react'; +import React from 'react'; + +export const EthLogo: React.FC = (props) => ( + + + + + + + + + + + + + +); diff --git a/src/components/navigation/config.ts b/src/components/navigation/config.ts index 51f5370..18e9b7b 100644 --- a/src/components/navigation/config.ts +++ b/src/components/navigation/config.ts @@ -93,7 +93,7 @@ export const useNavigationConfig = (): NavigationConfig => { ], }, { - label: 'Staking', + label: 'Stake', path: '/staking', }, { diff --git a/src/pages/stakingPage/components/BoldGrix.tsx b/src/pages/stakingPage/components/BoldGrix.tsx new file mode 100644 index 0000000..a3e792d --- /dev/null +++ b/src/pages/stakingPage/components/BoldGrix.tsx @@ -0,0 +1,24 @@ +import { Text } from '@chakra-ui/react'; +import React from 'react'; + +type BoldGrixProps = { + text: string; +}; + +export const BoldGrix: React.FC = ({ text }) => { + const parts = text.split(/(GRIX|esGRIX)/g); + return ( + <> + {parts.map((part, index) => { + if (part === 'GRIX' || part === 'esGRIX') { + return ( + + {part} + + ); + } + return part; + })} + + ); +}; diff --git a/src/pages/stakingPage/components/RewardsCard.tsx b/src/pages/stakingPage/components/RewardsCard.tsx index a34f636..3012995 100644 --- a/src/pages/stakingPage/components/RewardsCard.tsx +++ b/src/pages/stakingPage/components/RewardsCard.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useAccount } from 'wagmi'; import { GrixLogo } from '@/components/commons/Logo'; +import { EthLogo } from '@/components/commons/Logo/EthLogo'; import { claim, compound } from '@/web3Config/staking/hooks'; type RewardsCardProps = { @@ -16,7 +17,9 @@ type RewardsCardProps = { }; type DexScreenerResponse = { - pairs?: { priceUsd?: string }[]; + grix: { + usd: number; + }; }; export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Element => { @@ -26,15 +29,13 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen const [grixPrice, setGrixPrice] = useState(null); const toast = useToast(); - // Fetch GRIX price from dexscreener + // Fetch GRIX price from CoinGecko useEffect(() => { const fetchPrice = async () => { try { - const res = await fetch( - 'https://api.dexscreener.com/latest/dex/pairs/arbitrum/0x25d3ce097e413eeab09bbda72cd87d8972e673d4' - ); + const res = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=grix&vs_currencies=usd'); const json = (await res.json()) as DexScreenerResponse; - const price = json?.pairs?.[0]?.priceUsd ? parseFloat(json.pairs[0].priceUsd) : null; + const price = json.grix.usd; setGrixPrice(price); } catch { setGrixPrice(null); @@ -100,49 +101,49 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen return ( - + - 🔷 - + + WETH - + ≤{(0.0001).toFixed(4)} WETH (≤${(0.01).toFixed(2)}) - - + + Staked Amount - + {data?.stakedAmount ? Number(data.stakedAmount).toFixed(4) : '0.0000'} GRIX - - + + Claimable Rewards - + {data?.claimable ? Number(data.claimable).toFixed(4) : '0.0000'} esGRIX {grixPrice && data?.claimable && ( - +  ($ {(Number(data.claimable) * grixPrice).toLocaleString(undefined, { minimumFractionDigits: 2, @@ -159,9 +160,10 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen loadingText="Claiming" bg="teal.400" color="white" - size="lg" + size="md" width="full" - height="48px" + height="40px" + fontSize="sm" isDisabled={!data?.claimable || Number(data?.claimable) <= 0} _hover={{ bg: 'teal.500' }} _active={{ bg: 'teal.600' }} @@ -175,9 +177,10 @@ export const RewardsCard = ({ data, refetchData }: RewardsCardProps): JSX.Elemen loadingText="Compounding" bg="teal.400" color="white" - size="lg" + size="md" width="full" - height="48px" + height="40px" + fontSize="sm" _hover={{ bg: 'teal.500' }} _active={{ bg: 'teal.600' }} > diff --git a/src/pages/stakingPage/components/StakingCard.tsx b/src/pages/stakingPage/components/StakingCard.tsx index be8b039..d075fa9 100644 --- a/src/pages/stakingPage/components/StakingCard.tsx +++ b/src/pages/stakingPage/components/StakingCard.tsx @@ -22,7 +22,7 @@ import { StakingCardContent } from './StakingCardContent.js'; type StakingCardProps = { title: string; description: string; - type: 'gs' | 'esgs'; + type: 'gx' | 'esgx'; refreshTrigger: number; onActionComplete: () => void; }; @@ -45,7 +45,7 @@ export const StakingCard: React.FC = ({ const [apr, setApr] = useState(0); const [_showError, setShowError] = useState(false); const toast = useToast(); - const tokenAddress = type === 'gs' ? stakingContracts.grixToken.address : stakingContracts.esGRIXToken.address; + const tokenAddress = type === 'gx' ? stakingContracts.grixToken.address : stakingContracts.esGRIXToken.address; const showToast = useCallback( (title: string, description: string, status: 'success' | 'error') => { @@ -64,7 +64,7 @@ export const StakingCard: React.FC = ({ if (!address) return; try { - if (type === 'esgs') { + if (type === 'esgx') { const staked = await getEsGrixStakedAmount(address); setStakedAmount(staked); } else { @@ -107,7 +107,7 @@ export const StakingCard: React.FC = ({ }, [fetchBalance, fetchStakedAmount, fetchAPR, refreshTrigger]); const handleMaxClick = () => { - if (type === 'esgs') { + if (type === 'esgx') { setAmount(stakedAmount); } else { setAmount(availableBalance); @@ -169,9 +169,7 @@ export const StakingCard: React.FC = ({ [fetchBalance, fetchStakedAmount, fetchAPR, onActionComplete] ); - // Add a useEffect to handle refreshTrigger changes without causing infinite loops useEffect(() => { - // Skip the initial render if (refreshTrigger > 0) { void refreshAllData(false); } @@ -209,7 +207,7 @@ export const StakingCard: React.FC = ({ setIsStaking(true); const amountBigInt = parseEther(amount); - if (type === 'gs') { + if (type === 'gx') { await stakeGs(amountBigInt); } else { await stakeEsGs(amountBigInt); @@ -231,7 +229,7 @@ export const StakingCard: React.FC = ({ if (!amount || !address) return; try { - const currentStaked = type === 'gs' ? await getStakedAmount(address) : await getEsGrixStakedAmount(address); + const currentStaked = type === 'gx' ? await getStakedAmount(address) : await getEsGrixStakedAmount(address); if (Number(amount) > Number(currentStaked)) { showToast('Invalid Amount', 'Amount exceeds staked balance', 'error'); @@ -242,7 +240,7 @@ export const StakingCard: React.FC = ({ setIsUnstaking(true); const amountBigInt = parseEther(amount); - if (type === 'gs') { + if (type === 'gx') { await unstakeGs(amountBigInt); } else { await unstakeEsGs(amountBigInt); diff --git a/src/pages/stakingPage/components/StakingCardContent.tsx b/src/pages/stakingPage/components/StakingCardContent.tsx index a2c7db9..90ca4e0 100644 --- a/src/pages/stakingPage/components/StakingCardContent.tsx +++ b/src/pages/stakingPage/components/StakingCardContent.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { GrixLogo } from '@/components/commons/Logo'; import { formatBalance } from '../utils/formatters'; +import { BoldGrix } from './BoldGrix'; type StakingCardContentProps = { title: string; @@ -69,10 +70,10 @@ export const StakingCardContent: React.FC = ({
- {title} + - {description} +
@@ -100,7 +101,10 @@ export const StakingCardContent: React.FC = ({ APR - + + + ↗ + {apr.toFixed(2)}%
diff --git a/src/pages/stakingPage/components/VestingCard.tsx b/src/pages/stakingPage/components/VestingCard.tsx index 0c78739..23d7922 100644 --- a/src/pages/stakingPage/components/VestingCard.tsx +++ b/src/pages/stakingPage/components/VestingCard.tsx @@ -208,7 +208,7 @@ export const VestingCard: React.FC = ({ onActionComplete }) => ( - - -
- - Vesting - - - Convert esGRIX tokens to GRIX Token - -
-
+ + + Vesting + + + + + ); diff --git a/src/pages/stakingPage/components/VestingComponents/VestingInfo.tsx b/src/pages/stakingPage/components/VestingComponents/VestingInfo.tsx index e60bad8..13d6479 100644 --- a/src/pages/stakingPage/components/VestingComponents/VestingInfo.tsx +++ b/src/pages/stakingPage/components/VestingComponents/VestingInfo.tsx @@ -1,21 +1,15 @@ import { Text, VStack } from '@chakra-ui/react'; +import React from 'react'; -type VestingInfoProps = { - _vestingData?: { - claimable: string; - totalVested: string; - maxVestableAmount: string; - } | null; -}; +import { BoldGrix } from '../BoldGrix'; -export const VestingInfo: React.FC = ({ _vestingData }) => ( +export const VestingInfo: React.FC = () => ( - + About Vesting - - Convert your esGRIX tokens to GRIX Token through the vesting process. Vested tokens are claimable on an ongoing - basis. + + ); diff --git a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx index 49191b3..d598ab2 100644 --- a/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx +++ b/src/pages/stakingPage/components/VestingComponents/VestingStats.tsx @@ -1,6 +1,9 @@ import { Grid, GridItem, Progress, Text, VStack } from '@chakra-ui/react'; import React from 'react'; +import { formatBalance } from '../../utils/formatters'; +import { BoldGrix } from '../BoldGrix'; + type VestingProgress = { remainingDays: number; progress: number; @@ -21,65 +24,69 @@ type VestingStatsProps = { export const VestingStats: React.FC = ({ vestingData }) => ( - - Claimable - - - {vestingData?.claimable || '0'} Grix - + + + + + + {vestingData ? formatBalance(vestingData.claimable) : '0.0000'} + + + - - Total Vested - - - {vestingData?.totalVested || '0'} esGrix - + + + + + + {vestingData ? formatBalance(vestingData.totalVested) : '0.0000'} + + + - - Available to vest{' '} - - - {vestingData?.esGrixBalance || '0'} esGrix - + + + + + + {vestingData?.esGrixBalance ? formatBalance(vestingData.esGrixBalance) : '0.0000'} + + + - - Last Vesting Time - - - {vestingData?.lastVestingTime - ? new Date(Number(vestingData.lastVestingTime) * 1000).toLocaleString('en-GB', { - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }) - : '----------'} - + + + + + + {vestingData ? formatBalance(vestingData.maxVestableAmount) : '0.0000'} + + + - - + + Vesting Progress {vestingData?.vestingProgress?.isVesting ? ( <> - + {vestingData.vestingProgress.remainingDays} days remaining ) : ( - + No active vesting )} diff --git a/src/pages/stakingPage/index.tsx b/src/pages/stakingPage/index.tsx index 83fa1df..58ca768 100644 --- a/src/pages/stakingPage/index.tsx +++ b/src/pages/stakingPage/index.tsx @@ -1,9 +1,10 @@ -import { Box, Container, Grid, GridItem, Heading, Text, VStack } from '@chakra-ui/react'; +import { Container, Grid, GridItem, Heading, Text, VStack } from '@chakra-ui/react'; import React, { useCallback, useEffect, useState } from 'react'; import { useAccount } from 'wagmi'; import { getUserRewardTrackerData } from '@/web3Config/staking/hooks'; +import { BoldGrix } from './components/BoldGrix'; import { RewardsCard } from './components/RewardsCard'; import { StakingCard } from './components/StakingCard'; import { VestingCard } from './components/VestingCard'; @@ -57,70 +58,79 @@ export const StakingPage: React.FC = () => { }, [fetchUserRewards, refreshTrigger]); return ( - - - - - Staking - - - Stake GRIX and esGRIX to start earning rewards - - + + + + Staking + + + + + - - - - + + + + - - - + + + - - - + + + - - - - Learn about Staking - - - Check out our staking walkthrough and guides - - - + + + Learn about Staking + + + Check out our staking walkthrough and guides + - - + + + - - - Vesting - - - - -
+ + + + + Vesting + + + + + + ); };