feat: Board
This commit is contained in:
parent
bb561b4453
commit
977b6fd61b
|
|
@ -0,0 +1,17 @@
|
|||
import contracts from './contracts'
|
||||
import tokens from './tokens'
|
||||
import { BoardConfig } from './types'
|
||||
|
||||
const BoardList: BoardConfig[] = [
|
||||
{
|
||||
pid: 1,
|
||||
name: 'Nodes',
|
||||
img: 'nodes',
|
||||
stringId: 100004,
|
||||
tokenSymbol: tokens.hcc.symbol,
|
||||
tokenAddresses: tokens.hcc.address,
|
||||
tokenDecimals: 18,
|
||||
},
|
||||
]
|
||||
|
||||
export default BoardList
|
||||
|
|
@ -177,3 +177,12 @@ export interface LotteryTicketClaimData {
|
|||
cakeTotal: BigNumber
|
||||
roundId: string
|
||||
}
|
||||
export interface BoardConfig {
|
||||
pid: number
|
||||
name: string
|
||||
img: string
|
||||
stringId?: number
|
||||
tokenSymbol: string
|
||||
tokenAddresses: Address
|
||||
tokenDecimals: number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export {
|
|||
} from './pools'
|
||||
export { setUserInfo, clearUserInfo } from './userInfo'
|
||||
export { fetchReferralInfoAsync } from './referral'
|
||||
export { fetchBoardsPublicDataAsync, fetchBoardUserDataAsync } from './boards'
|
||||
export { profileFetchStart, profileFetchSucceeded, profileFetchFailed } from './profile'
|
||||
export { fetchStart, teamFetchSucceeded, fetchFailed, teamsFetchSucceeded } from './teams'
|
||||
export { setBlock } from './block'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import erc20 from 'config/abi/erc20.json'
|
||||
import boardchefABI from 'config/abi/board.json'
|
||||
import multicall from 'utils/multicall'
|
||||
import { getAddress, getBoardAddress } from 'utils/addressHelpers'
|
||||
import boardsConfig from 'config/constants/boards'
|
||||
|
||||
const fetchBoards = async () => {
|
||||
const data = await Promise.all(
|
||||
boardsConfig.map(async (boardConfig) => {
|
||||
const tokenAddress = getAddress(boardConfig.tokenAddresses)
|
||||
const BoardChefAddress = getBoardAddress()
|
||||
const calls = [
|
||||
// 查节点的代币数量
|
||||
{
|
||||
address: tokenAddress,
|
||||
name: 'balanceOf',
|
||||
params: [BoardChefAddress],
|
||||
},
|
||||
// Token decimals
|
||||
{
|
||||
address: tokenAddress,
|
||||
name: 'decimals',
|
||||
},
|
||||
]
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [tokenBalance, tokenDecimals] = await multicall(erc20, calls)
|
||||
tokenBalance = new BigNumber(tokenBalance).div(new BigNumber(10).pow(tokenDecimals))
|
||||
let [minStakeAmount] = await multicall(boardchefABI, [
|
||||
// 最低质押额度
|
||||
{
|
||||
address: BoardChefAddress,
|
||||
name: 'firstStakeThreshold',
|
||||
},
|
||||
])
|
||||
minStakeAmount = new BigNumber(minStakeAmount).div(new BigNumber(10).pow(tokenDecimals)).toJSON()
|
||||
return {
|
||||
...boardConfig,
|
||||
tokenBalance: tokenBalance.toJSON(),
|
||||
minStakeAmount,
|
||||
}
|
||||
}),
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export default fetchBoards
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import erc20ABI from 'config/abi/erc20.json'
|
||||
import boardABI from 'config/abi/board.json'
|
||||
import multicall from 'utils/multicall'
|
||||
import boardsConfig from 'config/constants/boards'
|
||||
import { getAddress, getBoardAddress } from 'utils/addressHelpers'
|
||||
import contracts from 'config/constants/contracts'
|
||||
import tokens from 'config/constants/tokens'
|
||||
|
||||
export const fetchBoardUserAllowances = async (account: string) => {
|
||||
const calls = boardsConfig.map((board) => {
|
||||
const tokenAddresses = getAddress(board.tokenAddresses)
|
||||
const boardChefAdress = getBoardAddress()
|
||||
return { address: tokenAddresses, name: 'allowance', params: [account, boardChefAdress] }
|
||||
})
|
||||
|
||||
const rawLpAllowances = await multicall(erc20ABI, calls)
|
||||
const parsedLpAllowances = rawLpAllowances.map((balance) => {
|
||||
return new BigNumber(balance).toJSON()
|
||||
})
|
||||
return parsedLpAllowances
|
||||
}
|
||||
|
||||
export const fetchBoardUserTokenBalances = async (account: string) => {
|
||||
const calls = boardsConfig.map((board) => {
|
||||
const tokenAddresses = getAddress(board.tokenAddresses)
|
||||
return {
|
||||
address: tokenAddresses,
|
||||
name: 'balanceOf',
|
||||
params: [account],
|
||||
}
|
||||
})
|
||||
|
||||
const rawTokenBalances = await multicall(erc20ABI, calls)
|
||||
const parsedTokenBalances = rawTokenBalances.map((tokenBalance) => {
|
||||
return new BigNumber(tokenBalance).toJSON()
|
||||
})
|
||||
return parsedTokenBalances
|
||||
}
|
||||
|
||||
export const fetchBoardUserStakedBalances = async (account: string) => {
|
||||
const calls = boardsConfig.map((board) => {
|
||||
const boardChefAdress = getBoardAddress()
|
||||
return {
|
||||
address: boardChefAdress,
|
||||
name: 'userInfo',
|
||||
params: [account],
|
||||
}
|
||||
})
|
||||
|
||||
const rawStakedBalances = await multicall(boardABI, calls)
|
||||
const parsedStakedBalances = rawStakedBalances.map((stakedBalance) => {
|
||||
return {
|
||||
stakedBalance: new BigNumber(stakedBalance[0]._hex).toJSON(),
|
||||
stakedTime: new BigNumber(stakedBalance[1]._hex).toNumber(),
|
||||
}
|
||||
})
|
||||
return parsedStakedBalances
|
||||
}
|
||||
|
||||
export const fetchBoardUserUnlockTime = async (account: string) => {
|
||||
const calls = boardsConfig.map((board) => {
|
||||
const boardChefAdress = getBoardAddress()
|
||||
return {
|
||||
address: boardChefAdress,
|
||||
name: 'boardToPeriod',
|
||||
}
|
||||
})
|
||||
|
||||
const rawUnlockTime = await multicall(boardABI, calls)
|
||||
const parsedUnlockTime = rawUnlockTime.map((time) => {
|
||||
return new BigNumber(time[0]._hex).toNumber()
|
||||
})
|
||||
return parsedUnlockTime
|
||||
}
|
||||
|
||||
export const fetchUserXCandyBalance = async (account: string) => {
|
||||
const calls = boardsConfig.map((board) => {
|
||||
return {
|
||||
address: getAddress(tokens.hcc.address),
|
||||
name: 'balanceOf',
|
||||
params: [account],
|
||||
}
|
||||
})
|
||||
|
||||
const rawXTokenBalance = await multicall(erc20ABI, calls)
|
||||
const parsedXTokenBalance = rawXTokenBalance.map((item) => {
|
||||
return new BigNumber(item[0]._hex).toJSON()
|
||||
})
|
||||
return parsedXTokenBalance
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import boardsConfig from 'config/constants/boards'
|
||||
import fetchBoards from './fetchBoards'
|
||||
import {
|
||||
fetchBoardUserAllowances,
|
||||
fetchBoardUserTokenBalances,
|
||||
fetchBoardUserStakedBalances,
|
||||
fetchBoardUserUnlockTime,
|
||||
fetchUserXCandyBalance,
|
||||
} from './fetchBoardsUser'
|
||||
import { BoardsState, Boards } from '../types'
|
||||
|
||||
const initialState: BoardsState = { data: [...boardsConfig] }
|
||||
|
||||
export const boardsSlice = createSlice({
|
||||
name: 'Boards',
|
||||
initialState,
|
||||
reducers: {
|
||||
setBoardsPublicData: (state, action) => {
|
||||
const liveBoardsData: Boards[] = action.payload
|
||||
state.data = state.data.map((board) => {
|
||||
const liveBoardData = liveBoardsData.find((f) => f.pid === board.pid)
|
||||
return { ...board, ...liveBoardData }
|
||||
})
|
||||
},
|
||||
setBoardsUserData: (state, action) => {
|
||||
const { arrayOfUserDataObjects } = action.payload
|
||||
arrayOfUserDataObjects.forEach((userDataEl) => {
|
||||
const { index } = userDataEl
|
||||
const dataItem = { ...state.data[index] }
|
||||
state.data[index] = { ...dataItem, userData: { ...userDataEl } }
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Actions
|
||||
export const { setBoardsPublicData, setBoardsUserData } = boardsSlice.actions
|
||||
// Thunks
|
||||
export const fetchBoardsPublicDataAsync = () => async (dispatch) => {
|
||||
const nodes = await fetchBoards()
|
||||
dispatch(setBoardsPublicData(nodes))
|
||||
}
|
||||
export const fetchBoardUserDataAsync = (account) => async (dispatch) => {
|
||||
const userBoardAllowances = await fetchBoardUserAllowances(account)
|
||||
const userBoardTokenBalances = await fetchBoardUserTokenBalances(account)
|
||||
const userStakedBalances = await fetchBoardUserStakedBalances(account)
|
||||
const userUnlockTimes = await fetchBoardUserUnlockTime(account)
|
||||
const userXCandyBalances = await fetchUserXCandyBalance(account)
|
||||
const arrayOfUserDataObjects = userBoardAllowances.map((nodeAllowance, index) => {
|
||||
return {
|
||||
index,
|
||||
allowance: userBoardAllowances[index],
|
||||
tokenBalance: userBoardTokenBalances[index],
|
||||
stakedBalance: userStakedBalances[index].stakedBalance,
|
||||
xCandyBalance: userXCandyBalances[index],
|
||||
unlockTime: userUnlockTimes[index] + userStakedBalances[index].stakedTime,
|
||||
}
|
||||
})
|
||||
|
||||
dispatch(setBoardsUserData({ arrayOfUserDataObjects }))
|
||||
}
|
||||
|
||||
export default boardsSlice.reducer
|
||||
|
|
@ -34,6 +34,7 @@ import {
|
|||
ReduxNodeLedger,
|
||||
NodeLedger,
|
||||
ReduxNodeRound,
|
||||
Boards,
|
||||
} from './types'
|
||||
import { fetchProfile } from './profile'
|
||||
import { fetchTeam, fetchTeams } from './teams'
|
||||
|
|
@ -197,7 +198,29 @@ export const usePoolFromPid = (sousId: number): Pool => {
|
|||
const pool = useSelector((state: State) => state.pools.data.find((p) => p.sousId === sousId))
|
||||
return transformPool(pool)
|
||||
}
|
||||
// board
|
||||
// boards
|
||||
export const useBoards = (): Boards[] => {
|
||||
const boards = useSelector((state: State) => state.boards.data)
|
||||
return boards
|
||||
}
|
||||
|
||||
export const useBoardsFromPid = (pid): Boards => {
|
||||
const node = useSelector((state: State) => state.boards.data.find((p) => p.pid === pid))
|
||||
return node
|
||||
}
|
||||
|
||||
export const useNodeUser = (pid) => {
|
||||
const node = useBoardsFromPid(pid)
|
||||
return {
|
||||
allowance: node.userData ? new BigNumber(node.userData.allowance) : new BigNumber(0),
|
||||
tokenBalance: node.userData ? new BigNumber(node.userData.tokenBalance) : new BigNumber(0),
|
||||
stakedBalance: node.userData ? new BigNumber(node.userData.stakedBalance) : new BigNumber(0),
|
||||
unlockTime: node.userData ? node.userData.unlockTime : 0,
|
||||
xCandyBalance: node.userData ? node.userData.xCandyBalance : 0,
|
||||
}
|
||||
}
|
||||
// cake
|
||||
export const useFetchCakeVault = () => {
|
||||
const { account } = useWeb3React()
|
||||
const { fastRefresh } = useRefresh()
|
||||
|
|
@ -582,7 +605,7 @@ export const useFetchLottery = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (currentLotteryId) {
|
||||
// Get historical lottery data from nodes + subgraph
|
||||
// Get historical lottery data from boards + subgraph
|
||||
dispatch(fetchPublicLotteries({ currentLotteryId }))
|
||||
// get public data for current lottery
|
||||
dispatch(fetchCurrentLottery({ currentLotteryId }))
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import votingReducer from './voting'
|
|||
import lotteryReducer from './lottery'
|
||||
import userInfo from './userInfo'
|
||||
import referral from './referral'
|
||||
import boards from './boards'
|
||||
|
||||
import application from './application/reducer'
|
||||
import { updateVersion } from './global/actions'
|
||||
|
|
@ -38,6 +39,7 @@ const store = configureStore({
|
|||
lottery: lotteryReducer,
|
||||
userInfo,
|
||||
referral,
|
||||
boards,
|
||||
// Exchange
|
||||
application,
|
||||
user,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,16 @@ import { ThunkAction } from 'redux-thunk'
|
|||
import { AnyAction } from '@reduxjs/toolkit'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { ethers } from 'ethers'
|
||||
import { CampaignType, FarmConfig, LotteryStatus, LotteryTicket, Nft, PoolConfig, Team } from 'config/constants/types'
|
||||
import {
|
||||
CampaignType,
|
||||
FarmConfig,
|
||||
LotteryStatus,
|
||||
LotteryTicket,
|
||||
Nft,
|
||||
PoolConfig,
|
||||
Team,
|
||||
BoardConfig,
|
||||
} from 'config/constants/types'
|
||||
|
||||
export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, State, unknown, AnyAction>
|
||||
|
||||
|
|
@ -499,6 +508,20 @@ export interface Referral {
|
|||
export interface ReferralState {
|
||||
data: Referral
|
||||
}
|
||||
export interface Boards extends BoardConfig {
|
||||
tokenAmount?: BigNumber
|
||||
minStakeAmount?: number
|
||||
userData?: {
|
||||
allowance: BigNumber
|
||||
tokenBalance: BigNumber
|
||||
stakedBalance: BigNumber
|
||||
unlockTime: number
|
||||
xCandyBalance: number
|
||||
}
|
||||
}
|
||||
export interface BoardsState {
|
||||
data: Boards[]
|
||||
}
|
||||
// Global state
|
||||
|
||||
export interface State {
|
||||
|
|
@ -513,5 +536,6 @@ export interface State {
|
|||
voting: VotingState
|
||||
userInfo: UserInfoState
|
||||
referral: ReferralState
|
||||
boards: BoardsState
|
||||
lottery: LotteryState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const getBalanceAmount = (amount: BigNumber, decimals = 18) => {
|
|||
*/
|
||||
export const getBalanceNumber = (balance: BigNumber, decimals = 18, decimalPlaces?: number) => {
|
||||
const displayBalance = getBalanceAmount(balance, decimals)
|
||||
return decimalPlaces ?displayBalance.decimalPlaces(decimalPlaces).toNumber():displayBalance.toNumber()
|
||||
return decimalPlaces ? displayBalance.decimalPlaces(decimalPlaces).toNumber() : displayBalance.toNumber()
|
||||
}
|
||||
|
||||
export const getFullDisplayBalance = (balance: BigNumber, decimals = 18, displayDecimals?: number) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
import React, { useMemo, useState } from 'react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
import { Flex, Text, Skeleton } from '@pancakeswap/uikit'
|
||||
import { provider as ProviderType } from 'web3-core'
|
||||
import { getBoardAddress } from 'utils/addressHelpers'
|
||||
import useI18n from 'hooks/useI18n'
|
||||
import ExpandableSectionButton from 'components/ExpandableSectionButton'
|
||||
import DetailsSection from './DetailsSection'
|
||||
import CardHeading from './CardHeading'
|
||||
import CardActionsContainer from './CardActionsContainer'
|
||||
|
||||
const RainbowLight = keyframes`
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledCardAccent = styled.div`
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
rgba(255, 0, 0, 1) 0%,
|
||||
rgba(255, 154, 0, 1) 10%,
|
||||
rgba(208, 222, 33, 1) 20%,
|
||||
rgba(79, 220, 74, 1) 30%,
|
||||
rgba(63, 218, 216, 1) 40%,
|
||||
rgba(47, 201, 226, 1) 50%,
|
||||
rgba(28, 127, 238, 1) 60%,
|
||||
rgba(95, 21, 242, 1) 70%,
|
||||
rgba(186, 12, 248, 1) 80%,
|
||||
rgba(251, 7, 217, 1) 90%,
|
||||
rgba(255, 0, 0, 1) 100%
|
||||
);
|
||||
background-size: 300% 300%;
|
||||
animation: ${RainbowLight} 2s linear infinite;
|
||||
border-radius: 32px;
|
||||
filter: blur(6px);
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
left: -2px;
|
||||
z-index: -1;
|
||||
`
|
||||
|
||||
const FCard = styled.div`
|
||||
align-self: baseline;
|
||||
background: ${(props) => props.theme.card.background};
|
||||
border-radius: 32px;
|
||||
box-shadow: 0px 2px 30px 0px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding: 24px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
`
|
||||
|
||||
const Divider = styled.div`
|
||||
background-color: ${({ theme }) => theme.colors.borderColor};
|
||||
height: 1px;
|
||||
margin: 28px auto;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const ExpandingWrapper = styled.div<{ expanded: boolean }>`
|
||||
height: ${(props) => (props.expanded ? '100%' : '0px')};
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
interface NodeCardProps {
|
||||
node: any
|
||||
removed: boolean
|
||||
provider?: ProviderType
|
||||
account?: string
|
||||
}
|
||||
|
||||
const NodeCard: React.FC<NodeCardProps> = ({ node, account }) => {
|
||||
const TranslateString = useI18n()
|
||||
|
||||
const [showExpandableSection, setShowExpandableSection] = useState(false)
|
||||
|
||||
return (
|
||||
<FCard>
|
||||
{/* {true && <StyledCardAccent />} */}
|
||||
<CardHeading name={TranslateString(node.stringId, node.name)} img={node.img} tokenSymbol={node.tokenSymbol} />
|
||||
<CardActionsContainer node={node} account={account} />
|
||||
<Divider />
|
||||
<ExpandableSectionButton
|
||||
onClick={() => setShowExpandableSection(!showExpandableSection)}
|
||||
expanded={showExpandableSection}
|
||||
/>
|
||||
<ExpandingWrapper expanded={showExpandableSection}>
|
||||
<DetailsSection
|
||||
bscScanAddress={`${process.env.REACT_APP_NETWORK_URL}/address/${getBoardAddress()}`}
|
||||
totalValueFormated={node.tokenBalance || '-'}
|
||||
/>
|
||||
</ExpandingWrapper>
|
||||
</FCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default NodeCard
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import React, { useState, useCallback } from 'react'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import styled from 'styled-components'
|
||||
import { provider as ProviderType } from 'web3-core'
|
||||
import { getAddress } from 'utils/addressHelpers'
|
||||
import { getBep20Contract } from 'utils/contractHelpers'
|
||||
import { Button, Flex, Text } from '@pancakeswap/uikit'
|
||||
import { Boards } from 'state/types'
|
||||
import { useBoardUser } from 'state/hooks'
|
||||
import { TOKEN_SYMBOL2 } from 'config/index'
|
||||
import { getBalanceNumber } from 'utils/formatBalance'
|
||||
import useI18n from 'hooks/useI18n'
|
||||
import useWeb3 from 'hooks/useWeb3'
|
||||
import { useNodeApprove } from 'hooks/useApprove'
|
||||
import UnlockButton from 'components/UnlockButton'
|
||||
import StakeAction from './StakeAction'
|
||||
|
||||
const Action = styled.div`
|
||||
padding-top: 16px;
|
||||
`
|
||||
|
||||
interface NodeCardActionsProps {
|
||||
node: Boards
|
||||
provider?: ProviderType
|
||||
account?: string
|
||||
}
|
||||
|
||||
const CardActions: React.FC<NodeCardActionsProps> = ({ node, account }) => {
|
||||
const TranslateString = useI18n()
|
||||
const [requestedApproval, setRequestedApproval] = useState(false)
|
||||
const pid = node.pid
|
||||
const { allowance, tokenBalance, stakedBalance, xCandyBalance } = useBoardUser(pid)
|
||||
const isApproved = account && allowance && allowance.isGreaterThan(0)
|
||||
const web3 = useWeb3()
|
||||
const tokenAddress = getAddress(node.tokenAddresses)
|
||||
const tokenContract = getBep20Contract(tokenAddress, web3)
|
||||
|
||||
const { onApprove } = useNodeApprove(tokenContract)
|
||||
|
||||
const handleApprove = useCallback(async () => {
|
||||
try {
|
||||
setRequestedApproval(true)
|
||||
await onApprove()
|
||||
setRequestedApproval(false)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}, [onApprove])
|
||||
|
||||
const renderApprovalOrStakeButton = () => {
|
||||
return isApproved ? (
|
||||
<>
|
||||
<StakeAction stakedBalance={stakedBalance} tokenBalance={tokenBalance} pid={pid} />
|
||||
<Flex flexDirection="column" alignItems="flex-start" mt="10">
|
||||
<Flex>
|
||||
<Text bold textTransform="uppercase" color="secondary" fontSize="12px" pr="3px">
|
||||
{TOKEN_SYMBOL2}
|
||||
</Text>
|
||||
<Text color="textSubtle" fontSize="12px">
|
||||
{TranslateString(101018, 'Amount')}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text bold textTransform="uppercase" fontSize="20px">
|
||||
{getBalanceNumber(new BigNumber(xCandyBalance))}
|
||||
</Text>
|
||||
</Flex>
|
||||
</>
|
||||
) : (
|
||||
<Button mt="8px" width="100%" disabled={requestedApproval} onClick={handleApprove}>
|
||||
{TranslateString(758, 'Approve Contract')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Action>
|
||||
<Flex>
|
||||
<Text bold textTransform="uppercase" color="secondary" fontSize="12px" pr="3px">
|
||||
{node.tokenSymbol}
|
||||
</Text>
|
||||
<Text bold textTransform="uppercase" color="textSubtle" fontSize="12px">
|
||||
{TranslateString(101004, 'Staked')}
|
||||
</Text>
|
||||
</Flex>
|
||||
{!account ? <UnlockButton mt="8px" width="100%" /> : renderApprovalOrStakeButton()}
|
||||
</Action>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardActions
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Tag, Flex, Heading, Image } from '@pancakeswap/uikit'
|
||||
import Question from 'components/QuestionHelper'
|
||||
import useI18n from 'hooks/useI18n'
|
||||
|
||||
export interface ExpandableSectionProps {
|
||||
name?: string
|
||||
isCommunityFarm?: boolean
|
||||
img?: string
|
||||
tokenSymbol?: string
|
||||
}
|
||||
|
||||
const Wrapper = styled(Flex)`
|
||||
svg {
|
||||
margin-right: 4px;
|
||||
}
|
||||
`
|
||||
|
||||
const CardHeading: React.FC<ExpandableSectionProps> = ({ name, img, tokenSymbol }) => {
|
||||
const TranslateString = useI18n()
|
||||
return (
|
||||
<Wrapper justifyContent="space-between" alignItems="center" mb="12px">
|
||||
<Image src={`/images/nodes/${img}.png`} width={64} height={64} />
|
||||
<Flex>
|
||||
<Heading mb="4px">{name}</Heading>
|
||||
<Question
|
||||
text={TranslateString(101017, 'To join board, you need to stake at least 0.1% total supply of Token')}
|
||||
/>
|
||||
</Flex>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardHeading
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import React from 'react'
|
||||
import useI18n from 'hooks/useI18n'
|
||||
import styled from 'styled-components'
|
||||
import { Text, Flex, Link, LinkExternal } from '@pancakeswap/uikit'
|
||||
|
||||
export interface ExpandableSectionProps {
|
||||
bscScanAddress?: string
|
||||
removed?: boolean
|
||||
totalValueFormated?: string
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin-top: 24px;
|
||||
`
|
||||
|
||||
const StyledLinkExternal = styled(LinkExternal)`
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
padding-left: 4px;
|
||||
height: 18px;
|
||||
width: auto;
|
||||
fill: ${({ theme }) => theme.colors.primary};
|
||||
}
|
||||
`
|
||||
|
||||
const DetailsSection: React.FC<ExpandableSectionProps> = ({ bscScanAddress, removed, totalValueFormated }) => {
|
||||
const TranslateString = useI18n()
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Flex justifyContent="space-between">
|
||||
<Text>{TranslateString(101010, 'Total Staked')}:</Text>
|
||||
<Text>{totalValueFormated}</Text>
|
||||
</Flex>
|
||||
{/* )} */}
|
||||
<Flex justifyContent="flex-start">
|
||||
<Link external href={bscScanAddress} bold={false}>
|
||||
{TranslateString(356, 'View on BscScan')}
|
||||
</Link>
|
||||
</Flex>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default DetailsSection
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
import React, { useMemo } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import styled from 'styled-components'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { Button, Flex, Heading, IconButton, AddIcon, MinusIcon, useModal } from '@pancakeswap/uikit'
|
||||
import useI18n from 'hooks/useI18n'
|
||||
import { useNodeStake } from 'hooks/useStake'
|
||||
import { useNodeUnstake } from 'hooks/useUnstake'
|
||||
import { NODE_UNLOCK_TIME } from 'config/index'
|
||||
import { getBalanceNumber, getFullDisplayBalance } from 'utils/formatBalance'
|
||||
import { useBoardsFromPid } from 'state/hooks'
|
||||
import useToast from 'hooks/useToast'
|
||||
import DepositModal from '../DepositModal'
|
||||
import WithdrawModal from '../WithdrawModal'
|
||||
|
||||
interface NodeCardActionsProps {
|
||||
stakedBalance?: BigNumber
|
||||
tokenBalance?: BigNumber
|
||||
tokenName?: string
|
||||
pid?: number
|
||||
}
|
||||
|
||||
const IconButtonWrapper = styled.div`
|
||||
display: flex;
|
||||
svg {
|
||||
width: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const StakeAction: React.FC<NodeCardActionsProps> = ({ stakedBalance, tokenBalance, tokenName, pid }) => {
|
||||
const TranslateString = useI18n()
|
||||
const { toastWarning } = useToast()
|
||||
const { onStake } = useNodeStake(pid)
|
||||
const { onUnstake } = useNodeUnstake(pid)
|
||||
const { tokenDecimals = 18, minStakeAmount, userData } = useBoardsFromPid(pid)
|
||||
const rawStakedBalance = getBalanceNumber(stakedBalance, tokenDecimals, 8)
|
||||
const minStakedAmount = useMemo(() => {
|
||||
const minAmount = minStakeAmount - getBalanceNumber(userData.stakedBalance, tokenDecimals)
|
||||
return minAmount < 0 ? new BigNumber(0) : new BigNumber(minAmount)
|
||||
}, [userData, minStakeAmount, tokenDecimals])
|
||||
const [onPresentDeposit] = useModal(
|
||||
<DepositModal
|
||||
max={tokenBalance}
|
||||
tokenDecimals={tokenDecimals}
|
||||
min={minStakedAmount}
|
||||
onConfirm={onStake}
|
||||
tokenName={tokenName}
|
||||
/>,
|
||||
)
|
||||
const [onPresentWithdraw] = useModal(
|
||||
<WithdrawModal max={stakedBalance} tokenDecimals={tokenDecimals} onConfirm={onUnstake} tokenName={tokenName} />,
|
||||
)
|
||||
const handleUnstake = () => {
|
||||
const unstakeDay = new Date().getDay()
|
||||
const unlockTime = userData.unlockTime * 1000
|
||||
// 只能周五解锁 NEED CHANGE
|
||||
if (unstakeDay !== NODE_UNLOCK_TIME || unlockTime > new Date().getTime()) {
|
||||
toastWarning(
|
||||
TranslateString(101012, 'Unlock time'),
|
||||
`${dayjs(unlockTime).format('YYYY-MM-DD HH:mm')} Next Friday`, // NEED CHANGE
|
||||
)
|
||||
return
|
||||
}
|
||||
if (new BigNumber(userData.stakedBalance).toJSON() !== new BigNumber(userData.xCandyBalance).toJSON()) {
|
||||
toastWarning(TranslateString(100103, 'Hint'), TranslateString(101019, 'Unlocking conditions are not met'))
|
||||
return
|
||||
}
|
||||
onUnstake()
|
||||
}
|
||||
const renderStakingButtons = () => {
|
||||
return rawStakedBalance === 0 ? (
|
||||
<Button onClick={onPresentDeposit}>{TranslateString(999, `Stake Token`)}</Button>
|
||||
) : (
|
||||
<IconButtonWrapper>
|
||||
<IconButton variant="tertiary" onClick={handleUnstake} mr="6px">
|
||||
<MinusIcon color="primary" />
|
||||
</IconButton>
|
||||
<IconButton variant="tertiary" onClick={onPresentDeposit}>
|
||||
<AddIcon color="primary" />
|
||||
</IconButton>
|
||||
</IconButtonWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex justifyContent="space-between" alignItems="center">
|
||||
<Heading color={rawStakedBalance === 0 ? 'textDisabled' : 'text'}>{rawStakedBalance}</Heading>
|
||||
{renderStakingButtons()}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default StakeAction
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Button, Modal, LinkExternal } from '@pancakeswap/uikit'
|
||||
import ModalActions from 'components/ModalActions'
|
||||
import ModalInput from 'components/ModalInput'
|
||||
import useI18n from 'hooks/useI18n'
|
||||
import { useToast } from 'state/hooks'
|
||||
import { getFullDisplayBalance } from 'utils/formatBalance'
|
||||
|
||||
interface DepositModalProps {
|
||||
min: BigNumber
|
||||
max: BigNumber
|
||||
onConfirm: (amount: string) => void
|
||||
onDismiss?: () => void
|
||||
tokenName?: string
|
||||
tokenDecimals?: number
|
||||
addLiquidityUrl?: string
|
||||
}
|
||||
|
||||
const DepositModal: React.FC<DepositModalProps> = ({
|
||||
min,
|
||||
max,
|
||||
onConfirm,
|
||||
onDismiss,
|
||||
tokenDecimals = 18,
|
||||
tokenName = '',
|
||||
addLiquidityUrl,
|
||||
}) => {
|
||||
const { toastWarning } = useToast()
|
||||
const [val, setVal] = useState('')
|
||||
const [pendingTx, setPendingTx] = useState(false)
|
||||
const TranslateString = useI18n()
|
||||
const fullBalance = useMemo(() => {
|
||||
return getFullDisplayBalance(max, tokenDecimals)
|
||||
}, [max, tokenDecimals])
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: React.FormEvent<HTMLInputElement>) => {
|
||||
setVal(e.currentTarget.value)
|
||||
},
|
||||
[setVal],
|
||||
)
|
||||
|
||||
const handleSelectMax = useCallback(() => {
|
||||
setVal(fullBalance)
|
||||
}, [fullBalance, setVal])
|
||||
|
||||
return (
|
||||
<Modal title={TranslateString(101004, 'Stake tokens')} onDismiss={onDismiss}>
|
||||
<ModalInput
|
||||
value={val}
|
||||
onSelectMax={handleSelectMax}
|
||||
onChange={handleChange}
|
||||
max={fullBalance}
|
||||
symbol={tokenName}
|
||||
addLiquidityUrl={addLiquidityUrl}
|
||||
inputTitle={TranslateString(101004, 'Stake')}
|
||||
/>
|
||||
<ModalActions>
|
||||
<Button variant="secondary" onClick={onDismiss} width="100%">
|
||||
{TranslateString(462, 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
width="100%"
|
||||
disabled={pendingTx || fullBalance === '0' || val === '0'}
|
||||
onClick={async () => {
|
||||
if (Number(val) < min.toNumber()) {
|
||||
toastWarning(
|
||||
TranslateString(100103, 'Hint'),
|
||||
`${TranslateString(101011, 'At least stake ')}${min.toNumber()}`,
|
||||
)
|
||||
return
|
||||
}
|
||||
setPendingTx(true)
|
||||
await onConfirm(val)
|
||||
setPendingTx(false)
|
||||
onDismiss()
|
||||
}}
|
||||
>
|
||||
{pendingTx ? TranslateString(488, 'Pending Confirmation') : TranslateString(464, 'Confirm')}
|
||||
</Button>
|
||||
</ModalActions>
|
||||
<LinkExternal href={addLiquidityUrl} style={{ alignSelf: 'center' }}>
|
||||
{TranslateString(100102, 'Get')} {tokenName}
|
||||
</LinkExternal>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default DepositModal
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import styled from 'styled-components'
|
||||
|
||||
export default styled.div`
|
||||
background-color: ${({ theme }) => theme.colors.textSubtle};
|
||||
height: 1px;
|
||||
margin: 0 auto 32px;
|
||||
width: 100%;
|
||||
`
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import BigNumber from 'bignumber.js'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Button, Modal } from '@pancakeswap/uikit'
|
||||
import ModalActions from 'components/ModalActions'
|
||||
import ModalInput from 'components/ModalInput'
|
||||
import useI18n from 'hooks/useI18n'
|
||||
import { getFullDisplayBalance } from 'utils/formatBalance'
|
||||
|
||||
interface WithdrawModalProps {
|
||||
max: BigNumber
|
||||
onConfirm: (amount: string) => void
|
||||
onDismiss?: () => void
|
||||
tokenDecimals?: number
|
||||
tokenName?: string
|
||||
}
|
||||
|
||||
const WithdrawModal: React.FC<WithdrawModalProps> = ({
|
||||
onConfirm,
|
||||
tokenDecimals = 18,
|
||||
onDismiss,
|
||||
max,
|
||||
tokenName = '',
|
||||
}) => {
|
||||
const [val, setVal] = useState('')
|
||||
const [pendingTx, setPendingTx] = useState(false)
|
||||
const TranslateString = useI18n()
|
||||
const fullBalance = useMemo(() => {
|
||||
return getFullDisplayBalance(max, tokenDecimals)
|
||||
}, [max, tokenDecimals])
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: React.FormEvent<HTMLInputElement>) => {
|
||||
setVal(e.currentTarget.value)
|
||||
},
|
||||
[setVal],
|
||||
)
|
||||
|
||||
const handleSelectMax = useCallback(() => {
|
||||
setVal(fullBalance)
|
||||
}, [fullBalance, setVal])
|
||||
|
||||
return (
|
||||
<Modal title={TranslateString(101008, 'Unstake tokens')} onDismiss={onDismiss}>
|
||||
<ModalInput
|
||||
onSelectMax={handleSelectMax}
|
||||
onChange={handleChange}
|
||||
value={val}
|
||||
max={fullBalance}
|
||||
symbol={tokenName}
|
||||
inputTitle={TranslateString(588, 'Unstake')}
|
||||
/>
|
||||
<ModalActions>
|
||||
<Button variant="secondary" onClick={onDismiss} width="100%">
|
||||
{TranslateString(462, 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={pendingTx}
|
||||
onClick={async () => {
|
||||
setPendingTx(true)
|
||||
await onConfirm(val)
|
||||
setPendingTx(false)
|
||||
onDismiss()
|
||||
}}
|
||||
width="100%"
|
||||
>
|
||||
{pendingTx ? TranslateString(488, 'Pending Confirmation') : TranslateString(464, 'Confirm')}
|
||||
</Button>
|
||||
</ModalActions>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default WithdrawModal
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
export type TableProps = {
|
||||
data?: TableDataTypes[]
|
||||
selectedFilters?: string
|
||||
sortBy?: string
|
||||
sortDir?: string
|
||||
onSort?: (value: string) => void
|
||||
}
|
||||
|
||||
export type ColumnsDefTypes = {
|
||||
id: number
|
||||
bold: string
|
||||
normal: string
|
||||
name: string
|
||||
translationId: number
|
||||
sortable: boolean
|
||||
}
|
||||
|
||||
export type ScrollBarProps = {
|
||||
ref: string
|
||||
width: number
|
||||
}
|
||||
|
||||
export type TableDataTypes = {
|
||||
POOL: string
|
||||
APY: string
|
||||
EARNED: string
|
||||
STAKED: string
|
||||
DETAILS: string
|
||||
LINKS: string
|
||||
}
|
||||
|
||||
export const MobileColumnSchema = [
|
||||
{
|
||||
id: 1,
|
||||
bold: '',
|
||||
normal: 'Farm',
|
||||
name: 'farm',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
bold: 'CAKE',
|
||||
normal: 'EARNED',
|
||||
name: 'earned',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
bold: '',
|
||||
normal: 'APR',
|
||||
name: 'apr',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
bold: '',
|
||||
normal: 'DETAILS',
|
||||
name: 'details',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const DesktopColumnSchema: ColumnsDefTypes[] = [
|
||||
{
|
||||
id: 1,
|
||||
bold: '',
|
||||
normal: 'Farm',
|
||||
name: 'farm',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
bold: 'CAKE',
|
||||
normal: 'EARNED',
|
||||
name: 'earned',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
bold: '',
|
||||
normal: 'APR',
|
||||
name: 'apr',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
bold: '',
|
||||
normal: 'STAKED',
|
||||
name: 'liquidity',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
bold: '',
|
||||
normal: 'MULTIPLIER',
|
||||
name: 'multiplier',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
bold: '',
|
||||
normal: 'DETAILS',
|
||||
name: 'details',
|
||||
translationId: 999,
|
||||
sortable: true,
|
||||
},
|
||||
]
|
||||
|
||||
export enum ViewMode {
|
||||
'TABLE' = 'TABLE',
|
||||
'CARD' = 'CARD',
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
export const useNodeStake = (pid: number) => {
|
||||
const dispatch = useDispatch()
|
||||
const { account } = useWeb3React()
|
||||
const nodeChefContract = useNodechef()
|
||||
const { tokenDecimals = 18 } = useNodesFromPid(pid)
|
||||
const handleStake = useCallback(
|
||||
async (amount: string) => {
|
||||
const txHash = await nodeStake(nodeChefContract, amount, account, tokenDecimals)
|
||||
// dispatch(fetchFarmsPublicDataAsync())
|
||||
// dispatch(fetchFarmUserDataAsync(account))
|
||||
console.info(txHash)
|
||||
},
|
||||
[account, dispatch, nodeChefContract, tokenDecimals],
|
||||
)
|
||||
|
||||
return { onStake: handleStake }
|
||||
}
|
||||
|
||||
export default useNodeStake
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react'
|
||||
import { Route, useRouteMatch, useLocation } from 'react-router-dom'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useWeb3React } from '@web3-react/core'
|
||||
import { Image, Heading, RowType, Toggle, Text } from '@pancakeswap/uikit'
|
||||
import styled from 'styled-components'
|
||||
import FlexLayout from 'components/Layout/Flex'
|
||||
import Page from 'components/Layout/Page'
|
||||
import { useBoards } from 'state/hooks'
|
||||
import useRefresh from 'hooks/useRefresh'
|
||||
import { fetchBoardUserDataAsync, fetchBoardsPublicDataAsync } from 'state/actions'
|
||||
import useI18n from 'hooks/useI18n'
|
||||
import BoardCard from './components/BoardCard/BoardCard'
|
||||
|
||||
const Header = styled.div`
|
||||
padding: 32px 0px;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
text-align: center;
|
||||
|
||||
${({ theme }) => theme.mediaQueries.sm} {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
`
|
||||
const SecondText = styled(Text)`
|
||||
white-space: break-spaces;
|
||||
`
|
||||
const Farms: React.FC = () => {
|
||||
const TranslateString = useI18n()
|
||||
const boardsList = useBoards()
|
||||
const [query, setQuery] = useState('')
|
||||
const { account } = useWeb3React()
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const { fastRefresh } = useRefresh()
|
||||
useEffect(() => {
|
||||
dispatch(fetchBoardsPublicDataAsync())
|
||||
if (account) {
|
||||
dispatch(fetchBoardUserDataAsync(account))
|
||||
}
|
||||
}, [account, dispatch, fastRefresh])
|
||||
|
||||
const farmsList = useMemo(() => {
|
||||
return boardsList.map((board) => {
|
||||
return board
|
||||
})
|
||||
}, [boardsList])
|
||||
const renderContent = (): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
<FlexLayout>
|
||||
{farmsList.map((board) => (
|
||||
<BoardCard key={board.pid} board={board} account={account} removed />
|
||||
))}
|
||||
</FlexLayout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
<Heading as="h1" size="xl" color="text" mb="10px" mt="10px">
|
||||
{TranslateString(100004, 'Boards')}
|
||||
</Heading>
|
||||
<SecondText fontSize="28px" color="text">
|
||||
{TranslateString(
|
||||
101013,
|
||||
'Joining the board of directors will obtain the governance token xcandy \n participate in the governance of the project, vote, obtain additional pledge income, \n and have a higher invitation airdrop reward',
|
||||
)}
|
||||
</SecondText>
|
||||
</Header>
|
||||
<Page>{renderContent()}</Page>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Farms
|
||||
Loading…
Reference in New Issue