feat: Board

This commit is contained in:
gary 2022-04-14 23:14:02 +08:00
parent bb561b4453
commit 977b6fd61b
21 changed files with 1051 additions and 3 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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'

View File

@ -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

View File

@ -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
}

66
src/state/boards/index.ts Normal file
View File

@ -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

View File

@ -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 }))

View File

@ -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,

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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%;
`

View File

@ -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

View File

@ -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',
}

View File

@ -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

80
src/views/Board/index.tsx Normal file
View File

@ -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