diff --git a/src/config/constants/boards.ts b/src/config/constants/boards.ts new file mode 100644 index 0000000..0b545c2 --- /dev/null +++ b/src/config/constants/boards.ts @@ -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 diff --git a/src/config/constants/types.ts b/src/config/constants/types.ts index 08c7998..1eba84d 100644 --- a/src/config/constants/types.ts +++ b/src/config/constants/types.ts @@ -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 +} diff --git a/src/state/actions.ts b/src/state/actions.ts index f9d27ae..7f79027 100644 --- a/src/state/actions.ts +++ b/src/state/actions.ts @@ -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' diff --git a/src/state/boards/fetchBoards.ts b/src/state/boards/fetchBoards.ts new file mode 100644 index 0000000..4430903 --- /dev/null +++ b/src/state/boards/fetchBoards.ts @@ -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 diff --git a/src/state/boards/fetchBoardsUser.ts b/src/state/boards/fetchBoardsUser.ts new file mode 100644 index 0000000..ab9f202 --- /dev/null +++ b/src/state/boards/fetchBoardsUser.ts @@ -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 +} diff --git a/src/state/boards/index.ts b/src/state/boards/index.ts new file mode 100644 index 0000000..e2404b9 --- /dev/null +++ b/src/state/boards/index.ts @@ -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 diff --git a/src/state/hooks.ts b/src/state/hooks.ts index c054ae4..761e54f 100644 --- a/src/state/hooks.ts +++ b/src/state/hooks.ts @@ -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 })) diff --git a/src/state/index.ts b/src/state/index.ts index 9954eef..fdbbd34 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -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, diff --git a/src/state/types.ts b/src/state/types.ts index a576ac2..0a870a6 100644 --- a/src/state/types.ts +++ b/src/state/types.ts @@ -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 = ThunkAction @@ -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 } diff --git a/src/utils/formatBalance.ts b/src/utils/formatBalance.ts index 860fba1..b716356 100644 --- a/src/utils/formatBalance.ts +++ b/src/utils/formatBalance.ts @@ -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) => { diff --git a/src/views/Board/components/BoardCard/BoardCard.tsx b/src/views/Board/components/BoardCard/BoardCard.tsx new file mode 100644 index 0000000..4f107f2 --- /dev/null +++ b/src/views/Board/components/BoardCard/BoardCard.tsx @@ -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 = ({ node, account }) => { + const TranslateString = useI18n() + + const [showExpandableSection, setShowExpandableSection] = useState(false) + + return ( + + {/* {true && } */} + + + + setShowExpandableSection(!showExpandableSection)} + expanded={showExpandableSection} + /> + + + + + ) +} + +export default NodeCard diff --git a/src/views/Board/components/BoardCard/CardActionsContainer.tsx b/src/views/Board/components/BoardCard/CardActionsContainer.tsx new file mode 100644 index 0000000..26bee58 --- /dev/null +++ b/src/views/Board/components/BoardCard/CardActionsContainer.tsx @@ -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 = ({ 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 ? ( + <> + + + + + {TOKEN_SYMBOL2} + + + {TranslateString(101018, 'Amount')} + + + + {getBalanceNumber(new BigNumber(xCandyBalance))} + + + + ) : ( + + ) + } + + return ( + + + + {node.tokenSymbol} + + + {TranslateString(101004, 'Staked')} + + + {!account ? : renderApprovalOrStakeButton()} + + ) +} + +export default CardActions diff --git a/src/views/Board/components/BoardCard/CardHeading.tsx b/src/views/Board/components/BoardCard/CardHeading.tsx new file mode 100644 index 0000000..452d8bd --- /dev/null +++ b/src/views/Board/components/BoardCard/CardHeading.tsx @@ -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 = ({ name, img, tokenSymbol }) => { + const TranslateString = useI18n() + return ( + + + + {name} + + + + ) +} + +export default CardHeading diff --git a/src/views/Board/components/BoardCard/DetailsSection.tsx b/src/views/Board/components/BoardCard/DetailsSection.tsx new file mode 100644 index 0000000..f59e684 --- /dev/null +++ b/src/views/Board/components/BoardCard/DetailsSection.tsx @@ -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 = ({ bscScanAddress, removed, totalValueFormated }) => { + const TranslateString = useI18n() + + return ( + + + {TranslateString(101010, 'Total Staked')}: + {totalValueFormated} + + {/* )} */} + + + {TranslateString(356, 'View on BscScan')} + + + + ) +} + +export default DetailsSection diff --git a/src/views/Board/components/BoardCard/StakeAction.tsx b/src/views/Board/components/BoardCard/StakeAction.tsx new file mode 100644 index 0000000..8cbabe5 --- /dev/null +++ b/src/views/Board/components/BoardCard/StakeAction.tsx @@ -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 = ({ 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( + , + ) + const [onPresentWithdraw] = useModal( + , + ) + 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 ? ( + + ) : ( + + + + + + + + + ) + } + + return ( + + {rawStakedBalance} + {renderStakingButtons()} + + ) +} + +export default StakeAction diff --git a/src/views/Board/components/DepositModal.tsx b/src/views/Board/components/DepositModal.tsx new file mode 100644 index 0000000..73a9e0d --- /dev/null +++ b/src/views/Board/components/DepositModal.tsx @@ -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 = ({ + 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) => { + setVal(e.currentTarget.value) + }, + [setVal], + ) + + const handleSelectMax = useCallback(() => { + setVal(fullBalance) + }, [fullBalance, setVal]) + + return ( + + + + + + + + {TranslateString(100102, 'Get')} {tokenName} + + + ) +} + +export default DepositModal diff --git a/src/views/Board/components/Divider.tsx b/src/views/Board/components/Divider.tsx new file mode 100644 index 0000000..b71d204 --- /dev/null +++ b/src/views/Board/components/Divider.tsx @@ -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%; +` diff --git a/src/views/Board/components/WithdrawModal.tsx b/src/views/Board/components/WithdrawModal.tsx new file mode 100644 index 0000000..48b2659 --- /dev/null +++ b/src/views/Board/components/WithdrawModal.tsx @@ -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 = ({ + 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) => { + setVal(e.currentTarget.value) + }, + [setVal], + ) + + const handleSelectMax = useCallback(() => { + setVal(fullBalance) + }, [fullBalance, setVal]) + + return ( + + + + + + + + ) +} + +export default WithdrawModal diff --git a/src/views/Board/components/types.ts b/src/views/Board/components/types.ts new file mode 100644 index 0000000..a013be4 --- /dev/null +++ b/src/views/Board/components/types.ts @@ -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', +} diff --git a/src/views/Board/hooks/useStakeBoard.ts b/src/views/Board/hooks/useStakeBoard.ts new file mode 100644 index 0000000..edfbe46 --- /dev/null +++ b/src/views/Board/hooks/useStakeBoard.ts @@ -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 diff --git a/src/views/Board/index.tsx b/src/views/Board/index.tsx new file mode 100644 index 0000000..ec5ce2a --- /dev/null +++ b/src/views/Board/index.tsx @@ -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 ( +
+ + {farmsList.map((board) => ( + + ))} + +
+ ) + } + + return ( + <> +
+ + {TranslateString(100004, 'Boards')} + + + {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', + )} + +
+ {renderContent()} + + ) +} + +export default Farms