From 6e5bc69da88550dfef1adbc5f4b428b72a040ca3 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 13 Apr 2021 12:18:10 +0100 Subject: [PATCH 01/40] feat(syrup-pool-v2): Update PageHeader and TabButtons --- src/components/PageHeader/PageHeader.tsx | 20 +++ src/components/PageHeader/index.ts | 1 + src/views/Farms/Farms.tsx | 18 +-- src/views/Pools/Syrup.tsx | 123 +++++++++--------- .../Pools/components/PoolTabButtons/index.tsx | 35 ++--- 5 files changed, 91 insertions(+), 106 deletions(-) create mode 100644 src/components/PageHeader/PageHeader.tsx create mode 100644 src/components/PageHeader/index.ts diff --git a/src/components/PageHeader/PageHeader.tsx b/src/components/PageHeader/PageHeader.tsx new file mode 100644 index 0000000000000..fe8d023e12ae7 --- /dev/null +++ b/src/components/PageHeader/PageHeader.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import styled from 'styled-components' +import Container from '../layout/Container' + +const Outer = styled.div` + background: ${({ theme }) => theme.colors.gradients.bubblegum}; +` + +const Inner = styled(Container)` + padding-top: 32px; + padding-bottom: 32px; +` + +const PageHeader = ({ children }) => ( + + {children} + +) + +export default PageHeader diff --git a/src/components/PageHeader/index.ts b/src/components/PageHeader/index.ts new file mode 100644 index 0000000000000..cecab8ce59da8 --- /dev/null +++ b/src/components/PageHeader/index.ts @@ -0,0 +1 @@ +export { default } from './PageHeader' diff --git a/src/views/Farms/Farms.tsx b/src/views/Farms/Farms.tsx index 981211c8327f9..847d71032ef06 100644 --- a/src/views/Farms/Farms.tsx +++ b/src/views/Farms/Farms.tsx @@ -7,6 +7,7 @@ import { Image, Heading, RowType, Toggle, Text } from '@pancakeswap-libs/uikit' import styled from 'styled-components' import FlexLayout from 'components/layout/Flex' import Page from 'components/layout/Page' +import PageHeader from 'components/PageHeader' import { useFarms, usePriceCakeBusd, useGetApiPrices } from 'state/hooks' import useRefresh from 'hooks/useRefresh' import { fetchFarmUserDataAsync } from 'state/actions' @@ -97,19 +98,6 @@ const StyledImage = styled(Image)` margin-top: 58px; ` -const Header = styled.div` - padding: 32px 0px; - background: ${({ theme }) => theme.colors.gradients.bubblegum}; - - padding-left: 16px; - padding-right: 16px; - - ${({ theme }) => theme.mediaQueries.sm} { - padding-left: 24px; - padding-right: 24px; - } -` - const Farms: React.FC = () => { const { path } = useRouteMatch() const { pathname } = useLocation() @@ -292,14 +280,14 @@ const Farms: React.FC = () => { return ( <> -
+ {TranslateString(674, 'Farms')} {TranslateString(999, 'Stake Liquidity Pool (LP) tokens to earn.')} -
+ diff --git a/src/views/Pools/Syrup.tsx b/src/views/Pools/Syrup.tsx index 8a94e362fce87..3ebd7f6c4c08a 100644 --- a/src/views/Pools/Syrup.tsx +++ b/src/views/Pools/Syrup.tsx @@ -3,19 +3,27 @@ import { Route, useRouteMatch } from 'react-router-dom' import BigNumber from 'bignumber.js' import styled from 'styled-components' import { useWeb3React } from '@web3-react/core' -import { Heading } from '@pancakeswap-libs/uikit' +import { Heading, Flex, Button, HelpIcon, Text } from '@pancakeswap-libs/uikit' import orderBy from 'lodash/orderBy' import partition from 'lodash/partition' import useI18n from 'hooks/useI18n' import { usePools, useBlock } from 'state/hooks' import FlexLayout from 'components/layout/Flex' import Page from 'components/layout/Page' +import PageHeader from 'components/PageHeader' import Coming from './components/Coming' import PoolCard from './components/PoolCard' import PoolTabButtons from './components/PoolTabButtons' import Divider from './components/Divider' -const Farm: React.FC = () => { +const ButtonText = styled(Text)` + display: none; + ${({ theme }) => theme.mediaQueries.lg} { + display: block; + } +` + +const Syrup: React.FC = () => { const { path } = useRouteMatch() const TranslateString = useI18n() const { account } = useWeb3React() @@ -33,69 +41,54 @@ const Farm: React.FC = () => { ) return ( - - -
- - {TranslateString(738, 'Syrup Pool')} - -
    -
  • {TranslateString(580, 'Stake CAKE to earn new tokens.')}
  • -
  • {TranslateString(486, 'You can unstake at any time.')}
  • -
  • {TranslateString(406, 'Rewards are calculated per block.')}
  • -
-
- SYRUP POOL icon -
- - - - - <> - {stakedOnly - ? orderBy(stakedOnlyPools, ['sortOrder']).map((pool) => ) - : orderBy(openPools, ['sortOrder']).map((pool) => )} - - - - - {orderBy(finishedPools, ['sortOrder']).map((pool) => ( - - ))} - - -
+ <> + + + + + {TranslateString(999, 'Syrup Pools')} + + + {TranslateString(999, 'Simply stake tokens to earn.')} + + + {TranslateString(999, 'High APR, low risk.')} + + + + + + + + + + + <> + {stakedOnly + ? orderBy(stakedOnlyPools, ['sortOrder']).map((pool) => ) + : orderBy(openPools, ['sortOrder']).map((pool) => )} + + + + + {orderBy(finishedPools, ['sortOrder']).map((pool) => ( + + ))} + + + + ) } -const Hero = styled.div` - align-items: center; - color: ${({ theme }) => theme.colors.primary}; - display: grid; - grid-gap: 32px; - grid-template-columns: 1fr; - margin-left: auto; - margin-right: auto; - max-width: 250px; - padding: 48px 0; - ul { - margin: 0; - padding: 0; - list-style-type: none; - font-size: 16px; - li { - margin-bottom: 4px; - } - } - img { - height: auto; - max-width: 100%; - } - @media (min-width: 576px) { - grid-template-columns: 1fr 1fr; - margin: 0; - max-width: none; - } -` - -export default Farm +export default Syrup diff --git a/src/views/Pools/components/PoolTabButtons/index.tsx b/src/views/Pools/components/PoolTabButtons/index.tsx index b1e0d51b64cff..b53f2c6b9e08b 100644 --- a/src/views/Pools/components/PoolTabButtons/index.tsx +++ b/src/views/Pools/components/PoolTabButtons/index.tsx @@ -1,7 +1,6 @@ import React from 'react' -import styled from 'styled-components' import { useRouteMatch, Link } from 'react-router-dom' -import { ButtonMenu, ButtonMenuItem, Toggle, Text } from '@pancakeswap-libs/uikit' +import { ButtonMenu, ButtonMenuItem, Toggle, Text, Flex } from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' const PoolTabButtons = ({ stakedOnly, setStakedOnly }) => { @@ -9,11 +8,7 @@ const PoolTabButtons = ({ stakedOnly, setStakedOnly }) => { const TranslateString = useI18n() return ( - - - setStakedOnly(!stakedOnly)} /> - {TranslateString(999, 'Staked only')} - + {TranslateString(1198, 'Live')} @@ -22,26 +17,14 @@ const PoolTabButtons = ({ stakedOnly, setStakedOnly }) => { {TranslateString(388, 'Finished')} - + + setStakedOnly(!stakedOnly)} /> + + {TranslateString(999, 'Staked only')} + + + ) } export default PoolTabButtons - -const Wrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; - margin-bottom: 32px; -` - -const ToggleWrapper = styled.div` - display: flex; - justify-content: center; - align-items: center; - margin-right: 32px; - - ${Text} { - margin-left: 8px; - } -` From a72465a3f0542fa3401b0f34a0850d7ef4c9de17 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 13 Apr 2021 15:03:42 +0100 Subject: [PATCH 02/40] feat(syrup-pool-v2): Reorg component structure and add new styledCardHeader --- src/views/Pools/components/CardTitle.tsx | 15 ----------- src/views/Pools/components/Coming.tsx | 4 +-- src/views/Pools/components/Divider.tsx | 8 ------ .../components/{ => PoolCard}/CardFooter.tsx | 0 .../{ => PoolCard}/CompoundModal.tsx | 0 .../{ => PoolCard}/DepositModal.tsx | 0 .../{ => PoolCard}/HarvestButton.tsx | 0 .../{ => PoolCard}/OldSyrupTitle.tsx | 0 .../{Card.tsx => PoolCard/StyledCard.tsx} | 5 ++-- .../components/PoolCard/StyledCardHeader.tsx | 22 +++++++++++++++ .../{ => PoolCard}/WithdrawModal.tsx | 0 .../{PoolCard.tsx => PoolCard/index.tsx} | 27 ++++++++----------- .../index.tsx => PoolTabButtons.tsx} | 0 src/views/Pools/index.ts | 1 - src/views/Pools/{Syrup.tsx => index.tsx} | 2 -- 15 files changed, 38 insertions(+), 46 deletions(-) delete mode 100644 src/views/Pools/components/CardTitle.tsx delete mode 100644 src/views/Pools/components/Divider.tsx rename src/views/Pools/components/{ => PoolCard}/CardFooter.tsx (100%) rename src/views/Pools/components/{ => PoolCard}/CompoundModal.tsx (100%) rename src/views/Pools/components/{ => PoolCard}/DepositModal.tsx (100%) rename src/views/Pools/components/{ => PoolCard}/HarvestButton.tsx (100%) rename src/views/Pools/components/{ => PoolCard}/OldSyrupTitle.tsx (100%) rename src/views/Pools/components/{Card.tsx => PoolCard/StyledCard.tsx} (79%) create mode 100644 src/views/Pools/components/PoolCard/StyledCardHeader.tsx rename src/views/Pools/components/{ => PoolCard}/WithdrawModal.tsx (100%) rename src/views/Pools/components/{PoolCard.tsx => PoolCard/index.tsx} (92%) rename src/views/Pools/components/{PoolTabButtons/index.tsx => PoolTabButtons.tsx} (100%) delete mode 100644 src/views/Pools/index.ts rename src/views/Pools/{Syrup.tsx => index.tsx} (98%) diff --git a/src/views/Pools/components/CardTitle.tsx b/src/views/Pools/components/CardTitle.tsx deleted file mode 100644 index 817c0bf3254b7..0000000000000 --- a/src/views/Pools/components/CardTitle.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import styled from 'styled-components' - -interface StyledTitleProps { - isFinished?: boolean -} - -const CardTitle = styled.div` - color: ${({ isFinished, theme }) => theme.colors[isFinished ? 'textDisabled' : 'text']}; - font-weight: 600; - font-size: 24px; - line-height: 1.1; - margin-bottom: 14px; -` - -export default CardTitle diff --git a/src/views/Pools/components/Coming.tsx b/src/views/Pools/components/Coming.tsx index 8b4d291ff2df8..aeeb5523e8721 100644 --- a/src/views/Pools/components/Coming.tsx +++ b/src/views/Pools/components/Coming.tsx @@ -3,8 +3,8 @@ import styled from 'styled-components' import { Image, Button } from '@pancakeswap-libs/uikit' import { CommunityTag } from 'components/Tags' import useI18n from 'hooks/useI18n' -import Card from './Card' -import CardTitle from './CardTitle' +import Card from './PoolCard/StyledCard' +import CardTitle from './PoolCard/CardTitle' const Balance = styled.div` color: ${({ theme }) => theme.colors.text}; diff --git a/src/views/Pools/components/Divider.tsx b/src/views/Pools/components/Divider.tsx deleted file mode 100644 index b71d204c32ca0..0000000000000 --- a/src/views/Pools/components/Divider.tsx +++ /dev/null @@ -1,8 +0,0 @@ -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/Pools/components/CardFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter.tsx similarity index 100% rename from src/views/Pools/components/CardFooter.tsx rename to src/views/Pools/components/PoolCard/CardFooter.tsx diff --git a/src/views/Pools/components/CompoundModal.tsx b/src/views/Pools/components/PoolCard/CompoundModal.tsx similarity index 100% rename from src/views/Pools/components/CompoundModal.tsx rename to src/views/Pools/components/PoolCard/CompoundModal.tsx diff --git a/src/views/Pools/components/DepositModal.tsx b/src/views/Pools/components/PoolCard/DepositModal.tsx similarity index 100% rename from src/views/Pools/components/DepositModal.tsx rename to src/views/Pools/components/PoolCard/DepositModal.tsx diff --git a/src/views/Pools/components/HarvestButton.tsx b/src/views/Pools/components/PoolCard/HarvestButton.tsx similarity index 100% rename from src/views/Pools/components/HarvestButton.tsx rename to src/views/Pools/components/PoolCard/HarvestButton.tsx diff --git a/src/views/Pools/components/OldSyrupTitle.tsx b/src/views/Pools/components/PoolCard/OldSyrupTitle.tsx similarity index 100% rename from src/views/Pools/components/OldSyrupTitle.tsx rename to src/views/Pools/components/PoolCard/OldSyrupTitle.tsx diff --git a/src/views/Pools/components/Card.tsx b/src/views/Pools/components/PoolCard/StyledCard.tsx similarity index 79% rename from src/views/Pools/components/Card.tsx rename to src/views/Pools/components/PoolCard/StyledCard.tsx index bbdc60f6e9a8f..e4387bb5e6d4d 100644 --- a/src/views/Pools/components/Card.tsx +++ b/src/views/Pools/components/PoolCard/StyledCard.tsx @@ -1,6 +1,7 @@ import styled from 'styled-components' +import { Card } from '@pancakeswap-libs/uikit' -const Card = styled.div<{ isActive?: boolean; isFinished?: boolean }>` +const StyledCard = styled(Card)<{ isActive?: boolean; isFinished?: boolean }>` background: ${(props) => props.theme.card.background}; box-shadow: 0px 2px 12px -8px rgba(25, 19, 38, 0.1), 0px 1px 1px rgba(25, 19, 38, 0.05); border-radius: 32px; @@ -14,4 +15,4 @@ const Card = styled.div<{ isActive?: boolean; isFinished?: boolean }>` position: relative; ` -export default Card +export default StyledCard diff --git a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx new file mode 100644 index 0000000000000..286d5f5fd886a --- /dev/null +++ b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import { CardHeader, Heading, Text, Flex, Image } from '@pancakeswap-libs/uikit' + +const StyledCardHeader: React.FC<{ + poolImage: string + earningTokenSymbol: string + stakingTokenSymbol: string +}> = ({ poolImage, earningTokenSymbol, stakingTokenSymbol }) => { + return ( + + + + Earn {earningTokenSymbol} + Stake {stakingTokenSymbol} + + {earningTokenSymbol} + + + ) +} + +export default StyledCardHeader diff --git a/src/views/Pools/components/WithdrawModal.tsx b/src/views/Pools/components/PoolCard/WithdrawModal.tsx similarity index 100% rename from src/views/Pools/components/WithdrawModal.tsx rename to src/views/Pools/components/PoolCard/WithdrawModal.tsx diff --git a/src/views/Pools/components/PoolCard.tsx b/src/views/Pools/components/PoolCard/index.tsx similarity index 92% rename from src/views/Pools/components/PoolCard.tsx rename to src/views/Pools/components/PoolCard/index.tsx index 7727026ac9192..37278ec6a2458 100644 --- a/src/views/Pools/components/PoolCard.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -1,7 +1,7 @@ import BigNumber from 'bignumber.js' import React, { useCallback, useState } from 'react' import styled from 'styled-components' -import { Button, IconButton, useModal, AddIcon, Image } from '@pancakeswap-libs/uikit' +import { Button, IconButton, useModal, AddIcon } from '@pancakeswap-libs/uikit' import { useWeb3React } from '@web3-react/core' import UnlockButton from 'components/UnlockButton' import Label from 'components/Label' @@ -22,17 +22,13 @@ import { useGetApiPrice } from 'state/hooks' import DepositModal from './DepositModal' import WithdrawModal from './WithdrawModal' import CompoundModal from './CompoundModal' -import CardTitle from './CardTitle' -import Card from './Card' +import StyledCard from './StyledCard' import OldSyrupTitle from './OldSyrupTitle' import HarvestButton from './HarvestButton' import CardFooter from './CardFooter' +import StyledCardHeader from './StyledCardHeader' -interface HarvestProps { - pool: Pool -} - -const PoolCard: React.FC = ({ pool }) => { +const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { const { sousId, stakingToken, @@ -117,16 +113,15 @@ const PoolCard: React.FC = ({ pool }) => { }, [onApprove, setRequestedApproval]) return ( - + + {isFinished && sousId !== 0 && }
- - {isOldSyrup && '[OLD]'} {earningToken.symbol} {TranslateString(348, 'Pool')} -
-
- {earningToken.symbol} -
{account && harvest && !isOldSyrup && ( = ({ pool }) => { tokenAddress={earningToken.address ? getAddress(earningToken.address) : ''} tokenDecimals={earningToken.decimals} /> - + ) } diff --git a/src/views/Pools/components/PoolTabButtons/index.tsx b/src/views/Pools/components/PoolTabButtons.tsx similarity index 100% rename from src/views/Pools/components/PoolTabButtons/index.tsx rename to src/views/Pools/components/PoolTabButtons.tsx diff --git a/src/views/Pools/index.ts b/src/views/Pools/index.ts deleted file mode 100644 index d03b7bc86c530..0000000000000 --- a/src/views/Pools/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Syrup' diff --git a/src/views/Pools/Syrup.tsx b/src/views/Pools/index.tsx similarity index 98% rename from src/views/Pools/Syrup.tsx rename to src/views/Pools/index.tsx index 3ebd7f6c4c08a..7b839d0809060 100644 --- a/src/views/Pools/Syrup.tsx +++ b/src/views/Pools/index.tsx @@ -14,7 +14,6 @@ import PageHeader from 'components/PageHeader' import Coming from './components/Coming' import PoolCard from './components/PoolCard' import PoolTabButtons from './components/PoolTabButtons' -import Divider from './components/Divider' const ButtonText = styled(Text)` display: none; @@ -70,7 +69,6 @@ const Syrup: React.FC = () => { - <> From 3efb72d4dac42975f873a1a56e11ae85e877e06e Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 13 Apr 2021 15:11:50 +0100 Subject: [PATCH 03/40] feat(syrup-pool-v2): Yeet some sheet --- src/views/Pools/components/Coming.tsx | 80 ------------------- .../components/PoolCard/OldSyrupTitle.tsx | 43 ---------- src/views/Pools/components/PoolCard/index.tsx | 4 +- src/views/Pools/index.tsx | 2 - 4 files changed, 2 insertions(+), 127 deletions(-) delete mode 100644 src/views/Pools/components/Coming.tsx delete mode 100644 src/views/Pools/components/PoolCard/OldSyrupTitle.tsx diff --git a/src/views/Pools/components/Coming.tsx b/src/views/Pools/components/Coming.tsx deleted file mode 100644 index aeeb5523e8721..0000000000000 --- a/src/views/Pools/components/Coming.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { Image, Button } from '@pancakeswap-libs/uikit' -import { CommunityTag } from 'components/Tags' -import useI18n from 'hooks/useI18n' -import Card from './PoolCard/StyledCard' -import CardTitle from './PoolCard/CardTitle' - -const Balance = styled.div` - color: ${({ theme }) => theme.colors.text}; - font-size: 40px; - font-weight: 600; -` - -const Label = styled.div` - color: ${({ theme }) => theme.colors.textSubtle}; - font-size: 14px; - margin-bottom: 16px; -` - -const DetailPlaceholder = styled.div` - display: flex; - font-size: 14px; -` -const Value = styled.div` - color: ${({ theme }) => theme.colors.text}; - font-size: 14px; -` - -const Footer = styled.div` - border-top: 1px solid ${({ theme }) => (theme.isDark ? '#524B63' : '#E9EAEB')}; - padding: 24px; -` -const Coming: React.FC = () => { - const TranslateString = useI18n() - - return ( - -
- - {TranslateString(414, 'Your Project?')}{' '} - - 👀 - - - Your project here - ??? - - - -
{TranslateString(736, 'APR')}:
- ?? -
- -
- - 🥞{' '} - - {TranslateString(384, 'Your Stake')}: -
- ??? CAKE -
-
-
- -
-
- ) -} - -export default Coming diff --git a/src/views/Pools/components/PoolCard/OldSyrupTitle.tsx b/src/views/Pools/components/PoolCard/OldSyrupTitle.tsx deleted file mode 100644 index 61ad11eb1ecb9..0000000000000 --- a/src/views/Pools/components/PoolCard/OldSyrupTitle.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import CardTitle from './CardTitle' - -interface Props { - hasBalance?: boolean -} - -const Title = styled.div` - color: #ed4b9e; - font-size: 24px; - font-weight: 600; - text-transform: uppercase; -` - -const ActionLink = styled.a` - color: #ed4b9e; - font-size: 14px; -` - -const OldSyrupTitle: React.FC = ({ hasBalance = false }) => { - if (hasBalance) { - return ( -
- Action Required - - What do I need to do? - -
- ) - } - - return ( -
- FINISHED -
- ) -} - -export default OldSyrupTitle diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index 37278ec6a2458..26f8d51d77884 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -23,7 +23,6 @@ import DepositModal from './DepositModal' import WithdrawModal from './WithdrawModal' import CompoundModal from './CompoundModal' import StyledCard from './StyledCard' -import OldSyrupTitle from './OldSyrupTitle' import HarvestButton from './HarvestButton' import CardFooter from './CardFooter' import StyledCardHeader from './StyledCardHeader' @@ -146,7 +145,8 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { )} ) : ( - + Is old syrup + // )}
From b99f8faa957c6808f66be32c531028bbdc026ffa Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 13 Apr 2021 16:15:20 +0100 Subject: [PATCH 04/40] feat(syrup-pool-v2): Add finished sash and handle finished state of card header --- .../components/PoolCard/PoolFinishedSash.tsx | 27 +++++++++++++++++++ .../components/PoolCard/StyledCardHeader.tsx | 19 +++++++++---- src/views/Pools/components/PoolCard/index.tsx | 21 ++++++--------- src/views/Pools/svgs/SashVector.tsx | 12 +++++++++ 4 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 src/views/Pools/components/PoolCard/PoolFinishedSash.tsx create mode 100644 src/views/Pools/svgs/SashVector.tsx diff --git a/src/views/Pools/components/PoolCard/PoolFinishedSash.tsx b/src/views/Pools/components/PoolCard/PoolFinishedSash.tsx new file mode 100644 index 0000000000000..08501b48f41c3 --- /dev/null +++ b/src/views/Pools/components/PoolCard/PoolFinishedSash.tsx @@ -0,0 +1,27 @@ +import React from 'react' +import { Text, Box } from '@pancakeswap-libs/uikit' +import styled from 'styled-components' +import useI18n from 'hooks/useI18n' +import SashVector from '../../svgs/SashVector' + +const StyledText = styled(Text)` + position: absolute; + top: 50%; + left: 50%; + transform: rotate(45deg) translate(-50%, -40%); +` + +const PoolFinishedSash = () => { + const TranslateString = useI18n() + + return ( + + + {TranslateString(388, 'Finished')} + + + + ) +} + +export default PoolFinishedSash diff --git a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx index 286d5f5fd886a..75770062cde6a 100644 --- a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx +++ b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx @@ -1,21 +1,30 @@ import React from 'react' import { CardHeader, Heading, Text, Flex, Image } from '@pancakeswap-libs/uikit' +import styled from 'styled-components' + +const Wrapper = styled(CardHeader)<{ isFinished?: boolean }>` + background: ${({ isFinished, theme }) => + isFinished ? theme.colors.backgroundDisabled : theme.card.cardHeaderBackground.default}; +` const StyledCardHeader: React.FC<{ poolImage: string earningTokenSymbol: string stakingTokenSymbol: string -}> = ({ poolImage, earningTokenSymbol, stakingTokenSymbol }) => { + isFinished?: boolean +}> = ({ poolImage, earningTokenSymbol, stakingTokenSymbol, isFinished = false }) => { return ( - + - Earn {earningTokenSymbol} - Stake {stakingTokenSymbol} + + Earn {earningTokenSymbol} + + Stake {stakingTokenSymbol} {earningTokenSymbol} - + ) } diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index 26f8d51d77884..44374d0d04cdd 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -1,7 +1,7 @@ import BigNumber from 'bignumber.js' import React, { useCallback, useState } from 'react' import styled from 'styled-components' -import { Button, IconButton, useModal, AddIcon } from '@pancakeswap-libs/uikit' +import { Button, IconButton, useModal, AddIcon, Box } from '@pancakeswap-libs/uikit' import { useWeb3React } from '@web3-react/core' import UnlockButton from 'components/UnlockButton' import Label from 'components/Label' @@ -19,6 +19,7 @@ import { PoolCategory } from 'config/constants/types' import tokens from 'config/constants/tokens' import { Pool } from 'state/types' import { useGetApiPrice } from 'state/hooks' +import PoolFinishedSash from './PoolFinishedSash' import DepositModal from './DepositModal' import WithdrawModal from './WithdrawModal' import CompoundModal from './CompoundModal' @@ -117,8 +118,13 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { poolImage={poolImage} earningTokenSymbol={earningToken.symbol} stakingTokenSymbol={stakingToken.symbol} + isFinished={isFinished && sousId !== 0} /> - {isFinished && sousId !== 0 && } + {isFinished && sousId !== 0 && ( + + + + )}
{account && harvest && !isOldSyrup && ( @@ -216,17 +222,6 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { ) } -const PoolFinishedSash = styled.div` - background-image: url('/images/pool-finished-sash.svg'); - background-position: top right; - background-repeat: not-repeat; - height: 135px; - position: absolute; - right: -24px; - top: -24px; - width: 135px; -` - const StyledCardActions = styled.div` display: flex; justify-content: center; diff --git a/src/views/Pools/svgs/SashVector.tsx b/src/views/Pools/svgs/SashVector.tsx new file mode 100644 index 0000000000000..77e44d46f8c29 --- /dev/null +++ b/src/views/Pools/svgs/SashVector.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { Svg, SvgProps } from '@pancakeswap-libs/uikit' + +const SashVector: React.FC = (props) => { + return ( + + + + ) +} + +export default SashVector From 70ca8916892429fd4d5867b37dc267ec5468426c Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 13 Apr 2021 16:45:21 +0100 Subject: [PATCH 05/40] feat(syrup-pool-v2): Create APR row comp --- src/components/Balance.tsx | 5 ++-- .../Pools/components/PoolCard/AprRow.tsx | 29 ++++++++++++++++++ .../components/PoolCard/StyledCardHeader.tsx | 30 ++++++++++++------- src/views/Pools/components/PoolCard/index.tsx | 24 +++++---------- 4 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 src/views/Pools/components/PoolCard/AprRow.tsx diff --git a/src/components/Balance.tsx b/src/components/Balance.tsx index 3ca503f261619..02cde9922cab7 100644 --- a/src/components/Balance.tsx +++ b/src/components/Balance.tsx @@ -6,6 +6,7 @@ interface TextProps { isDisabled?: boolean fontSize?: string color?: string + bold?: boolean } interface BalanceProps extends TextProps { @@ -14,7 +15,7 @@ interface BalanceProps extends TextProps { unit?: string } -const Balance: React.FC = ({ value, fontSize, color, decimals, isDisabled, unit }) => { +const Balance: React.FC = ({ value, fontSize, color, decimals, isDisabled, unit, bold }) => { const previousValue = useRef(0) useEffect(() => { @@ -22,7 +23,7 @@ const Balance: React.FC = ({ value, fontSize, color, decimals, isD }, [value]) return ( - + {value && unit && {unit}} diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx new file mode 100644 index 0000000000000..ba82e7b23935f --- /dev/null +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { Flex, Text } from '@pancakeswap-libs/uikit' +import useI18n from 'hooks/useI18n' +import Balance from 'components/Balance' + +interface AprProps { + tokenName?: string + isFinished: boolean + isOldSyrup: boolean + apy: number +} + +const AprRow: React.FC = ({ isFinished, isOldSyrup, apy }) => { + const TranslateString = useI18n() + return ( + + {TranslateString(736, 'APR')}: + {isFinished || isOldSyrup || !apy ? ( + '0%' + ) : ( + + + + )} + + ) +} + +export default AprRow diff --git a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx index 75770062cde6a..ab74cb379a8f6 100644 --- a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx +++ b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx @@ -1,6 +1,7 @@ import React from 'react' -import { CardHeader, Heading, Text, Flex, Image } from '@pancakeswap-libs/uikit' +import { CardHeader, Heading, Text, Flex, Image, Box } from '@pancakeswap-libs/uikit' import styled from 'styled-components' +import PoolFinishedSash from './PoolFinishedSash' const Wrapper = styled(CardHeader)<{ isFinished?: boolean }>` background: ${({ isFinished, theme }) => @@ -14,17 +15,24 @@ const StyledCardHeader: React.FC<{ isFinished?: boolean }> = ({ poolImage, earningTokenSymbol, stakingTokenSymbol, isFinished = false }) => { return ( - - - - - Earn {earningTokenSymbol} - - Stake {stakingTokenSymbol} + <> + + + + + Earn {earningTokenSymbol} + + Stake {stakingTokenSymbol} + + {earningTokenSymbol} - {earningTokenSymbol} - - + + {isFinished && ( + + + + )} + ) } diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index 44374d0d04cdd..6d37e74f5138d 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -1,7 +1,7 @@ import BigNumber from 'bignumber.js' import React, { useCallback, useState } from 'react' import styled from 'styled-components' -import { Button, IconButton, useModal, AddIcon, Box } from '@pancakeswap-libs/uikit' +import { Button, IconButton, useModal, AddIcon, CardBody } from '@pancakeswap-libs/uikit' import { useWeb3React } from '@web3-react/core' import UnlockButton from 'components/UnlockButton' import Label from 'components/Label' @@ -19,7 +19,7 @@ import { PoolCategory } from 'config/constants/types' import tokens from 'config/constants/tokens' import { Pool } from 'state/types' import { useGetApiPrice } from 'state/hooks' -import PoolFinishedSash from './PoolFinishedSash' +import AprRow from './AprRow' import DepositModal from './DepositModal' import WithdrawModal from './WithdrawModal' import CompoundModal from './CompoundModal' @@ -120,12 +120,9 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { stakingTokenSymbol={stakingToken.symbol} isFinished={isFinished && sousId !== 0} /> - {isFinished && sousId !== 0 && ( - - - - )} -
+ + +
{account && harvest && !isOldSyrup && ( = ({ pool }) => { ))} - -
{TranslateString(736, 'APR')}:
- {isFinished || isOldSyrup || !apy ? ( - '-' - ) : ( - - )} -
+
{TranslateString(384, 'Your Stake')}:
= ({ pool }) => { value={getBalanceNumber(stakedBalance, stakingToken.decimals)} />
-
+
Date: Wed, 14 Apr 2021 17:02:31 +0100 Subject: [PATCH 06/40] feat(syrup-pool-v2): Tidy up & restyling of footer --- src/components/ApyCalculatorModal/index.tsx | 8 +- .../Pools/components/PoolCard/AprRow.tsx | 29 ++- .../Pools/components/PoolCard/CardFooter.tsx | 178 +++++++----------- src/views/Pools/components/PoolCard/index.tsx | 7 +- src/views/Pools/index.tsx | 4 +- 5 files changed, 93 insertions(+), 133 deletions(-) diff --git a/src/components/ApyCalculatorModal/index.tsx b/src/components/ApyCalculatorModal/index.tsx index ba913fe5c351a..56f61bfdadb98 100644 --- a/src/components/ApyCalculatorModal/index.tsx +++ b/src/components/ApyCalculatorModal/index.tsx @@ -7,9 +7,9 @@ import { cakeEarnedPerThousandDollarsCompounding, getRoi } from 'utils/compoundA interface ApyCalculatorModalProps { onDismiss?: () => void + cakePrice: BigNumber + apr: number lpLabel?: string - cakePrice?: BigNumber - apr?: number addLiquidityUrl?: string } @@ -31,10 +31,10 @@ const Description = styled(Text)` const ApyCalculatorModal: React.FC = ({ onDismiss, - lpLabel, cakePrice, apr, - addLiquidityUrl, + lpLabel = '', + addLiquidityUrl = '', }) => { const TranslateString = useI18n() const oneThousandDollarsWorthOfCake = 1000 / cakePrice.toNumber() diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index ba82e7b23935f..0cd745e3f4b21 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -1,26 +1,35 @@ import React from 'react' -import { Flex, Text } from '@pancakeswap-libs/uikit' +import BigNumber from 'bignumber.js' +import { Flex, Text, IconButton, useModal, CalculateIcon } from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' import Balance from 'components/Balance' +import ApyCalculatorModal from 'components/ApyCalculatorModal' -interface AprProps { - tokenName?: string +interface AprRowProps { + isOldSyrup?: boolean isFinished: boolean - isOldSyrup: boolean - apy: number + apr: number + cakePrice: BigNumber } -const AprRow: React.FC = ({ isFinished, isOldSyrup, apy }) => { +const AprRow: React.FC = ({ isOldSyrup = false, cakePrice, isFinished, apr }) => { const TranslateString = useI18n() + const [onPresentApyModal] = useModal() + return ( {TranslateString(736, 'APR')}: - {isFinished || isOldSyrup || !apy ? ( + {isFinished || isOldSyrup || !apr ? ( '0%' ) : ( - - - + <> + + + + + + + )} ) diff --git a/src/views/Pools/components/PoolCard/CardFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter.tsx index dc66f6ef33e3b..a75f753ee4f1e 100644 --- a/src/views/Pools/components/PoolCard/CardFooter.tsx +++ b/src/views/Pools/components/PoolCard/CardFooter.tsx @@ -3,22 +3,13 @@ import BigNumber from 'bignumber.js' import styled from 'styled-components' import { getBalanceNumber } from 'utils/formatBalance' import useI18n from 'hooks/useI18n' -import { ChevronDown, ChevronUp } from 'react-feather' -import { Flex, MetamaskIcon } from '@pancakeswap-libs/uikit' +import { Flex, MetamaskIcon, CardFooter, ExpandableLabel, Text, LinkExternal, TimerIcon } from '@pancakeswap-libs/uikit' import Balance from 'components/Balance' -import { CommunityTag, CoreTag, BinanceTag } from 'components/Tags' import { useBlock } from 'state/hooks' -import { PoolCategory } from 'config/constants/types' import { registerToken } from 'utils/wallet' import { BASE_URL } from 'config' -const tags = { - [PoolCategory.BINANCE]: BinanceTag, - [PoolCategory.CORE]: CoreTag, - [PoolCategory.COMMUNITY]: CommunityTag, -} - -interface Props { +interface FooterProps { projectLink: string decimals: number totalStaked: BigNumber @@ -28,59 +19,17 @@ interface Props { startBlock: number endBlock: number isFinished: boolean - poolCategory: PoolCategory + stakingTokenSymbol: string } -const StyledFooter = styled.div<{ isFinished: boolean }>` - border-top: 1px solid ${({ theme }) => (theme.isDark ? '#524B63' : '#E9EAEB')}; - color: ${({ isFinished, theme }) => theme.colors[isFinished ? 'textDisabled2' : 'primary2']}; - padding: 24px; -` - -const StyledDetailsButton = styled.button` - align-items: center; - background-color: transparent; - border: 0; - color: ${(props) => props.theme.colors.primary}; - cursor: pointer; - display: inline-flex; - font-size: 16px; - font-weight: 600; - height: 32px; - justify-content: center; - outline: 0; - padding: 0; - &:hover { - opacity: 0.9; - } - - & > svg { - margin-left: 4px; +const ExpandedWrapper = styled(Flex)` + svg { + height: 14px; + width: 14px; } ` -const Details = styled.div` - margin-top: 24px; -` - -const Row = styled(Flex)` - align-items: center; -` - -const FlexFull = styled.div` - flex: 1; -` -const Label = styled.div` - font-size: 14px; -` -const TokenLink = styled.a` - font-size: 14px; - text-decoration: none; - color: ${(props) => props.theme.colors.primary}; - cursor: pointer; -` - -const CardFooter: React.FC = ({ +const Footer: React.FC = ({ projectLink, decimals, tokenAddress, @@ -90,75 +39,76 @@ const CardFooter: React.FC = ({ isFinished, startBlock, endBlock, - poolCategory, + stakingTokenSymbol, }) => { const { currentBlock } = useBlock() - const [isOpen, setIsOpen] = useState(false) + const [isExpanded, setIsExpanded] = useState(false) const TranslateString = useI18n() - const Icon = isOpen ? ChevronUp : ChevronDown - - const handleClick = () => setIsOpen(!isOpen) - const Tag = tags[poolCategory] const blocksUntilStart = Math.max(startBlock - currentBlock, 0) const blocksRemaining = Math.max(endBlock - currentBlock, 0) const imageSrc = `${BASE_URL}/images/tokens/${tokenName.toLowerCase()}.png` + const hasPoolStarted = blocksUntilStart === 0 && blocksRemaining > 0 + return ( - - - - - - - {isOpen ? TranslateString(1066, 'Hide') : TranslateString(658, 'Details')} - - - {isOpen && ( -
- - - - - - - {blocksUntilStart > 0 && ( - - - - - - - )} - {blocksUntilStart === 0 && blocksRemaining > 0 && ( - - - - - - - )} + + + setIsExpanded(!isExpanded)}> + {isExpanded ? TranslateString(1066, 'Hide') : TranslateString(658, 'Details')} + + + {isExpanded && ( + + + {TranslateString(999, 'Total staked:')} + + + + {stakingTokenSymbol} + + + + + + {hasPoolStarted ? TranslateString(410, 'End') : TranslateString(1212, 'Start')}: + + + + + {TranslateString(999, 'blocks')} + + + + + + + {TranslateString(412, 'View project site')} + + {tokenAddress && ( - - registerToken(tokenAddress, tokenName, tokenDecimals, imageSrc)}> - Add {tokenName} to Metamask - - + + registerToken(tokenAddress, tokenName, tokenDecimals, imageSrc)} + > + Add to Metamask + + )} - - {TranslateString(412, 'View project site')} - -
+ )} -
+
) } -export default React.memo(CardFooter) +export default React.memo(Footer) diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index 6d37e74f5138d..cf9256e592a4f 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -18,7 +18,7 @@ import Balance from 'components/Balance' import { PoolCategory } from 'config/constants/types' import tokens from 'config/constants/tokens' import { Pool } from 'state/types' -import { useGetApiPrice } from 'state/hooks' +import { useGetApiPrice, usePriceCakeBusd } from 'state/hooks' import AprRow from './AprRow' import DepositModal from './DepositModal' import WithdrawModal from './WithdrawModal' @@ -52,6 +52,7 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { const { onStake } = useSousStake(sousId, isBnbPool) const { onUnstake } = useSousUnstake(sousId) const { onReward } = useSousHarvest(sousId, isBnbPool) + const cakePrice = usePriceCakeBusd() // APY const rewardTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') @@ -121,7 +122,7 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { isFinished={isFinished && sousId !== 0} /> - +
{account && harvest && !isOldSyrup && ( @@ -203,10 +204,10 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { startBlock={startBlock} endBlock={endBlock} isFinished={isFinished} - poolCategory={poolCategory} tokenName={earningToken.symbol} tokenAddress={earningToken.address ? getAddress(earningToken.address) : ''} tokenDecimals={earningToken.decimals} + stakingTokenSymbol={stakingToken.symbol} /> ) diff --git a/src/views/Pools/index.tsx b/src/views/Pools/index.tsx index 2ba2c6f6a49cf..020e6801d65b1 100644 --- a/src/views/Pools/index.tsx +++ b/src/views/Pools/index.tsx @@ -21,7 +21,7 @@ const ButtonText = styled(Text)` } ` -const Syrup: React.FC = () => { +const Pools: React.FC = () => { const { path } = useRouteMatch() const TranslateString = useI18n() const { account } = useWeb3React() @@ -87,4 +87,4 @@ const Syrup: React.FC = () => { ) } -export default Syrup +export default Pools From e5f7df2583785c1efc4d8a8ef83d95d4b914ea72 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Mon, 19 Apr 2021 15:14:28 +0100 Subject: [PATCH 07/40] feat(syrup-pool-v2): Finish up various footer comps --- src/views/Pools/Syrup.tsx | 102 ----------- src/views/Pools/components/CardFooter.tsx | 166 ------------------ .../Pools/components/PoolCard/CardFooter.tsx | 86 ++++++--- .../components/PoolCard/PoolFinishedSash.tsx | 2 +- src/views/Pools/components/PoolCard/index.tsx | 10 +- 5 files changed, 69 insertions(+), 297 deletions(-) delete mode 100644 src/views/Pools/Syrup.tsx delete mode 100644 src/views/Pools/components/CardFooter.tsx diff --git a/src/views/Pools/Syrup.tsx b/src/views/Pools/Syrup.tsx deleted file mode 100644 index a4cb331dc8c48..0000000000000 --- a/src/views/Pools/Syrup.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useMemo } from 'react' -import { Route, useRouteMatch } from 'react-router-dom' -import BigNumber from 'bignumber.js' -import styled from 'styled-components' -import { useWeb3React } from '@web3-react/core' -import { Heading } from '@pancakeswap-libs/uikit' -import orderBy from 'lodash/orderBy' -import partition from 'lodash/partition' -import useI18n from 'hooks/useI18n' -import usePersistState from 'hooks/usePersistState' -import { usePools, useBlock } from 'state/hooks' -import FlexLayout from 'components/layout/Flex' -import Page from 'components/layout/Page' -import Coming from './components/Coming' -import PoolCard from './components/PoolCard' -import PoolTabButtons from './components/PoolTabButtons' -import Divider from './components/Divider' - -const Syrup: React.FC = () => { - const { path } = useRouteMatch() - const TranslateString = useI18n() - const { account } = useWeb3React() - const pools = usePools(account) - const { currentBlock } = useBlock() - const [stakedOnly, setStakedOnly] = usePersistState(false, 'pancake_pool_staked') - - const [finishedPools, openPools] = useMemo( - () => partition(pools, (pool) => pool.isFinished || currentBlock > pool.endBlock), - [currentBlock, pools], - ) - const stakedOnlyPools = useMemo( - () => openPools.filter((pool) => pool.userData && new BigNumber(pool.userData.stakedBalance).isGreaterThan(0)), - [openPools], - ) - - return ( - - -
- - {TranslateString(738, 'Syrup Pool')} - -
    -
  • {TranslateString(580, 'Stake CAKE to earn new tokens.')}
  • -
  • {TranslateString(486, 'You can unstake at any time.')}
  • -
  • {TranslateString(406, 'Rewards are calculated per block.')}
  • -
-
- SYRUP POOL icon -
- - - - - <> - {stakedOnly - ? orderBy(stakedOnlyPools, ['sortOrder']).map((pool) => ) - : orderBy(openPools, ['sortOrder']).map((pool) => )} - - - - - {orderBy(finishedPools, ['sortOrder']).map((pool) => ( - - ))} - - -
- ) -} - -const Hero = styled.div` - align-items: center; - color: ${({ theme }) => theme.colors.primary}; - display: grid; - grid-gap: 32px; - grid-template-columns: 1fr; - margin-left: auto; - margin-right: auto; - max-width: 250px; - padding: 48px 0; - ul { - margin: 0; - padding: 0; - list-style-type: none; - font-size: 16px; - li { - margin-bottom: 4px; - } - } - img { - height: auto; - max-width: 100%; - } - @media (min-width: 576px) { - grid-template-columns: 1fr 1fr; - margin: 0; - max-width: none; - } -` - -export default Syrup diff --git a/src/views/Pools/components/CardFooter.tsx b/src/views/Pools/components/CardFooter.tsx deleted file mode 100644 index 7e48df9274ede..0000000000000 --- a/src/views/Pools/components/CardFooter.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import React, { useState } from 'react' -import BigNumber from 'bignumber.js' -import styled from 'styled-components' -import { getBalanceNumber } from 'utils/formatBalance' -import useI18n from 'hooks/useI18n' -import { ChevronDown, ChevronUp } from 'react-feather' -import { Flex, MetamaskIcon } from '@pancakeswap-libs/uikit' -import Balance from 'components/Balance' -import { CommunityTag, CoreTag, BinanceTag } from 'components/Tags' -import { useBlock } from 'state/hooks' -import { PoolCategory } from 'config/constants/types' -import { registerToken } from 'utils/wallet' -import { BASE_URL } from 'config' - -const tags = { - [PoolCategory.BINANCE]: BinanceTag, - [PoolCategory.CORE]: CoreTag, - [PoolCategory.COMMUNITY]: CommunityTag, -} - -interface Props { - projectLink: string - decimals: number - totalStaked: BigNumber - tokenName: string - tokenAddress: string - tokenDecimals: number - startBlock: number - endBlock: number - isFinished: boolean - poolCategory: PoolCategory -} - -const StyledFooter = styled.div<{ isFinished: boolean }>` - border-top: 1px solid ${({ theme }) => (theme.isDark ? '#524B63' : '#E9EAEB')}; - color: ${({ isFinished, theme }) => theme.colors[isFinished ? 'textDisabled2' : 'primary2']}; - padding: 24px; -` - -const StyledDetailsButton = styled.button` - align-items: center; - background-color: transparent; - border: 0; - color: ${(props) => props.theme.colors.primary}; - cursor: pointer; - display: inline-flex; - font-size: 16px; - font-weight: 600; - height: 32px; - justify-content: center; - outline: 0; - padding: 0; - &:hover { - opacity: 0.9; - } - - & > svg { - margin-left: 4px; - } -` - -const Details = styled.div` - margin-top: 24px; -` - -const Row = styled(Flex)` - align-items: center; -` - -const FlexFull = styled.div` - flex: 1; -` -const Label = styled.div` - font-size: 14px; -` -const TokenLink = styled.a` - font-size: 14px; - text-decoration: none; - color: ${(props) => props.theme.colors.primary}; - cursor: pointer; -` - -const CardFooter: React.FC = ({ - projectLink, - decimals, - tokenAddress, - totalStaked, - tokenName, - tokenDecimals, - isFinished, - startBlock, - endBlock, - poolCategory, -}) => { - const { currentBlock } = useBlock() - const [isOpen, setIsOpen] = useState(false) - const TranslateString = useI18n() - const Icon = isOpen ? ChevronUp : ChevronDown - - const handleClick = () => setIsOpen(!isOpen) - const Tag = tags[poolCategory] - - const blocksUntilStart = Math.max(startBlock - currentBlock, 0) - const blocksRemaining = Math.max(endBlock - currentBlock, 0) - - const imageSrc = `${BASE_URL}/images/tokens/${tokenName.toLowerCase()}.png` - - const isMetaMaskInScope = !!(window as WindowChain).ethereum?.isMetaMask - - return ( - - - - - - - {isOpen ? TranslateString(1066, 'Hide') : TranslateString(658, 'Details')} - - - {isOpen && ( -
- - - - - - - {blocksUntilStart > 0 && ( - - - - - - - )} - {blocksUntilStart === 0 && blocksRemaining > 0 && ( - - - - - - - )} - {isMetaMaskInScope && tokenAddress && ( - - registerToken(tokenAddress, tokenName, tokenDecimals, imageSrc)}> - Add {tokenName} to MetaMask - - - - )} - - {TranslateString(412, 'View project site')} - -
- )} -
- ) -} - -export default React.memo(CardFooter) diff --git a/src/views/Pools/components/PoolCard/CardFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter.tsx index a75f753ee4f1e..e9fa5777fdde7 100644 --- a/src/views/Pools/components/PoolCard/CardFooter.tsx +++ b/src/views/Pools/components/PoolCard/CardFooter.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react' import BigNumber from 'bignumber.js' import styled from 'styled-components' +import { useWeb3React } from '@web3-react/core' import { getBalanceNumber } from 'utils/formatBalance' import useI18n from 'hooks/useI18n' import { Flex, MetamaskIcon, CardFooter, ExpandableLabel, Text, LinkExternal, TimerIcon } from '@pancakeswap-libs/uikit' @@ -20,6 +21,7 @@ interface FooterProps { endBlock: number isFinished: boolean stakingTokenSymbol: string + poolContractAddress: string } const ExpandedWrapper = styled(Flex)` @@ -29,6 +31,12 @@ const ExpandedWrapper = styled(Flex)` } ` +const ExpandableButtonWrapper = styled(Flex)` + button { + padding: 0; + } +` + const Footer: React.FC = ({ projectLink, decimals, @@ -40,28 +48,30 @@ const Footer: React.FC = ({ startBlock, endBlock, stakingTokenSymbol, + poolContractAddress, }) => { - const { currentBlock } = useBlock() - const [isExpanded, setIsExpanded] = useState(false) const TranslateString = useI18n() + const [isExpanded, setIsExpanded] = useState(false) + const { account } = useWeb3React() + const { currentBlock } = useBlock() + const imageSrc = `${BASE_URL}/images/tokens/${tokenName.toLowerCase()}.png` + const isMetaMaskInScope = !!(window as WindowChain).ethereum?.isMetaMask + const shouldShowBlockCountdown = Boolean(startBlock && endBlock) const blocksUntilStart = Math.max(startBlock - currentBlock, 0) const blocksRemaining = Math.max(endBlock - currentBlock, 0) - - const imageSrc = `${BASE_URL}/images/tokens/${tokenName.toLowerCase()}.png` - const hasPoolStarted = blocksUntilStart === 0 && blocksRemaining > 0 return ( - + setIsExpanded(!isExpanded)}> {isExpanded ? TranslateString(1066, 'Hide') : TranslateString(658, 'Details')} - + {isExpanded && ( - + {TranslateString(999, 'Total staked:')} @@ -70,30 +80,56 @@ const Footer: React.FC = ({ - - - {hasPoolStarted ? TranslateString(410, 'End') : TranslateString(1212, 'Start')}: - - - - - {TranslateString(999, 'blocks')} + {shouldShowBlockCountdown && ( + + + {hasPoolStarted ? TranslateString(410, 'End') : TranslateString(1212, 'Start')}: - + + + + {TranslateString(999, 'blocks')} + + + - - + )} + {TranslateString(412, 'View project site')} + {poolContractAddress && ( + + + {TranslateString(412, 'View contract')} + + + )} {tokenAddress && ( + + + {TranslateString(412, 'Info site')} + + + )} + {account && isMetaMaskInScope && tokenAddress && ( { return ( - + {TranslateString(388, 'Finished')} diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index 137dda9400c48..cee776ab5e673 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -41,11 +41,15 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { isFinished, userData, stakingLimit, + contractAddress, } = pool // Pools using native BNB behave differently than pools using a token const isBnbPool = poolCategory === PoolCategory.BINANCE + const TranslateString = useI18n() + const [requestedApproval, setRequestedApproval] = useState(false) + const [pendingTx, setPendingTx] = useState(false) const stakingTokenContract = useERC20(stakingToken.address ? getAddress(stakingToken.address) : '') const { account } = useWeb3React() const { onApprove } = useSousApprove(stakingTokenContract, sousId) @@ -64,9 +68,6 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { parseFloat(pool.tokenPerBlock), ) - const [requestedApproval, setRequestedApproval] = useState(false) - const [pendingTx, setPendingTx] = useState(false) - const allowance = new BigNumber(userData?.allowance || 0) const stakingTokenBalance = new BigNumber(userData?.stakingTokenBalance || 0) const stakedBalance = new BigNumber(userData?.stakedBalance || 0) @@ -77,6 +78,8 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { const needsApproval = !accountHasStakedBalance && !allowance.toNumber() && !isBnbPool const isCardActive = isFinished && accountHasStakedBalance + const poolContractAddress = contractAddress[process.env.REACT_APP_CHAIN_ID] + const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) const [onPresentDeposit] = useModal( = ({ pool }) => { tokenAddress={earningToken.address ? getAddress(earningToken.address) : ''} tokenDecimals={earningToken.decimals} stakingTokenSymbol={stakingToken.symbol} + poolContractAddress={poolContractAddress} /> ) From a82d259972dcc68286f2731e430f834e6a82b27a Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Mon, 19 Apr 2021 16:16:41 +0100 Subject: [PATCH 08/40] feat(syrup-pool-v2): Upgrade ApyCalculatorModal to handle different earning tokens and links --- .../utils/compoundApyHelpers.test.ts | 18 +++--- src/components/ApyCalculatorModal/index.tsx | 61 +++++++++++-------- src/utils/compoundApyHelpers.ts | 12 ++-- .../Farms/components/FarmCard/ApyButton.tsx | 9 ++- .../Pools/components/PoolCard/AprRow.tsx | 41 +++++++++---- src/views/Pools/components/PoolCard/index.tsx | 12 +++- 6 files changed, 94 insertions(+), 59 deletions(-) diff --git a/src/__tests__/utils/compoundApyHelpers.test.ts b/src/__tests__/utils/compoundApyHelpers.test.ts index d778fccb3e2a8..c9b559ac64919 100644 --- a/src/__tests__/utils/compoundApyHelpers.test.ts +++ b/src/__tests__/utils/compoundApyHelpers.test.ts @@ -1,14 +1,14 @@ -import { cakeEarnedPerThousandDollarsCompounding, getRoi } from 'utils/compoundApyHelpers' +import { tokenEarnedPerThousandDollarsCompounding, getRoi } from 'utils/compoundApyHelpers' it.each([ - [{ numberOfDays: 1, farmApr: 365, cakePrice: 1 }, 10], - [{ numberOfDays: 7, farmApr: 20, cakePrice: 0.8 }, 4.8], - [{ numberOfDays: 40, farmApr: 212.21, cakePrice: 1.2 }, 217.48], - [{ numberOfDays: 330, farmApr: 45.12, cakePrice: 5 }, 100.67], - [{ numberOfDays: 365, farmApr: 100, cakePrice: 0.2 }, 8572.84], - [{ numberOfDays: 365, farmApr: 20, cakePrice: 1 }, 221.34], -])('calculate cake earned with values %o', ({ numberOfDays, farmApr, cakePrice }, expected) => { - expect(cakeEarnedPerThousandDollarsCompounding({ numberOfDays, farmApr, cakePrice })).toEqual(expected) + [{ numberOfDays: 1, farmApr: 365, tokenPrice: 1 }, 10], + [{ numberOfDays: 7, farmApr: 20, tokenPrice: 0.8 }, 4.8], + [{ numberOfDays: 40, farmApr: 212.21, tokenPrice: 1.2 }, 217.48], + [{ numberOfDays: 330, farmApr: 45.12, tokenPrice: 5 }, 100.67], + [{ numberOfDays: 365, farmApr: 100, tokenPrice: 0.2 }, 8572.84], + [{ numberOfDays: 365, farmApr: 20, tokenPrice: 1 }, 221.34], +])('calculate cake earned with values %o', ({ numberOfDays, farmApr, tokenPrice }, expected) => { + expect(tokenEarnedPerThousandDollarsCompounding({ numberOfDays, farmApr, tokenPrice })).toEqual(expected) }) it.each([ diff --git a/src/components/ApyCalculatorModal/index.tsx b/src/components/ApyCalculatorModal/index.tsx index 56f61bfdadb98..da92cb4bf5a09 100644 --- a/src/components/ApyCalculatorModal/index.tsx +++ b/src/components/ApyCalculatorModal/index.tsx @@ -1,16 +1,16 @@ import React from 'react' -import BigNumber from 'bignumber.js' import styled from 'styled-components' import { Modal, Text, LinkExternal, Flex } from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' -import { cakeEarnedPerThousandDollarsCompounding, getRoi } from 'utils/compoundApyHelpers' +import { tokenEarnedPerThousandDollarsCompounding, getRoi } from 'utils/compoundApyHelpers' interface ApyCalculatorModalProps { onDismiss?: () => void - cakePrice: BigNumber + tokenPrice: number apr: number - lpLabel?: string - addLiquidityUrl?: string + linkLabel: string + linkHref: string + earningTokenSymbol?: string } const Grid = styled.div` @@ -31,25 +31,34 @@ const Description = styled(Text)` const ApyCalculatorModal: React.FC = ({ onDismiss, - cakePrice, + tokenPrice, apr, - lpLabel = '', - addLiquidityUrl = '', + linkLabel, + linkHref, + earningTokenSymbol = 'CAKE', }) => { const TranslateString = useI18n() - const oneThousandDollarsWorthOfCake = 1000 / cakePrice.toNumber() + const oneThousandDollarsWorthOfToken = 1000 / tokenPrice - const cakeEarnedPerThousand1D = cakeEarnedPerThousandDollarsCompounding({ numberOfDays: 1, farmApr: apr, cakePrice }) - const cakeEarnedPerThousand7D = cakeEarnedPerThousandDollarsCompounding({ numberOfDays: 7, farmApr: apr, cakePrice }) - const cakeEarnedPerThousand30D = cakeEarnedPerThousandDollarsCompounding({ + const tokenEarnedPerThousand1D = tokenEarnedPerThousandDollarsCompounding({ + numberOfDays: 1, + farmApr: apr, + tokenPrice, + }) + const tokenEarnedPerThousand7D = tokenEarnedPerThousandDollarsCompounding({ + numberOfDays: 7, + farmApr: apr, + tokenPrice, + }) + const tokenEarnedPerThousand30D = tokenEarnedPerThousandDollarsCompounding({ numberOfDays: 30, farmApr: apr, - cakePrice, + tokenPrice, }) - const cakeEarnedPerThousand365D = cakeEarnedPerThousandDollarsCompounding({ + const tokenEarnedPerThousand365D = tokenEarnedPerThousandDollarsCompounding({ numberOfDays: 365, farmApr: apr, - cakePrice, + tokenPrice, }) return ( @@ -67,7 +76,7 @@ const ApyCalculatorModal: React.FC = ({ - {TranslateString(864, 'CAKE per $1000')} + {earningTokenSymbol} {TranslateString(999, 'per')} $1000 {/* 1 day row */} @@ -76,11 +85,11 @@ const ApyCalculatorModal: React.FC = ({ - {getRoi({ amountEarned: cakeEarnedPerThousand1D, amountInvested: oneThousandDollarsWorthOfCake })}% + {getRoi({ amountEarned: tokenEarnedPerThousand1D, amountInvested: oneThousandDollarsWorthOfToken })}% - {cakeEarnedPerThousand1D} + {tokenEarnedPerThousand1D} {/* 7 day row */} @@ -88,11 +97,11 @@ const ApyCalculatorModal: React.FC = ({ - {getRoi({ amountEarned: cakeEarnedPerThousand7D, amountInvested: oneThousandDollarsWorthOfCake })}% + {getRoi({ amountEarned: tokenEarnedPerThousand7D, amountInvested: oneThousandDollarsWorthOfToken })}% - {cakeEarnedPerThousand7D} + {tokenEarnedPerThousand7D} {/* 30 day row */} @@ -100,11 +109,11 @@ const ApyCalculatorModal: React.FC = ({ - {getRoi({ amountEarned: cakeEarnedPerThousand30D, amountInvested: oneThousandDollarsWorthOfCake })}% + {getRoi({ amountEarned: tokenEarnedPerThousand30D, amountInvested: oneThousandDollarsWorthOfToken })}% - {cakeEarnedPerThousand30D} + {tokenEarnedPerThousand30D} {/* 365 day / APY row */} @@ -112,11 +121,11 @@ const ApyCalculatorModal: React.FC = ({ - {getRoi({ amountEarned: cakeEarnedPerThousand365D, amountInvested: oneThousandDollarsWorthOfCake })}% + {getRoi({ amountEarned: tokenEarnedPerThousand365D, amountInvested: oneThousandDollarsWorthOfToken })}% - {cakeEarnedPerThousand365D} + {tokenEarnedPerThousand365D} @@ -126,9 +135,7 @@ const ApyCalculatorModal: React.FC = ({ )} - - {TranslateString(999, 'Get')} {lpLabel} - + {linkLabel} ) diff --git a/src/utils/compoundApyHelpers.ts b/src/utils/compoundApyHelpers.ts index 0338f6c6ae0b3..6bf6b5890e4b8 100644 --- a/src/utils/compoundApyHelpers.ts +++ b/src/utils/compoundApyHelpers.ts @@ -1,18 +1,16 @@ const roundToTwoDp = (number) => Math.round(number * 100) / 100 -export const cakeEarnedPerThousandDollarsCompounding = ({ numberOfDays, farmApr, cakePrice }) => { +export const tokenEarnedPerThousandDollarsCompounding = ({ numberOfDays, farmApr, tokenPrice }) => { // Everything here is worked out relative to a year, with the asset compounding daily const timesCompounded = 365 - // We use decimal values rather than % in the math for both APY and the number of days being calculates as a proportion of the year + // We use decimal values rather than % in the math for both APY and the number of days being calculates as a proportion of the year const aprAsDecimal = farmApr / 100 const daysAsDecimalOfYear = numberOfDays / timesCompounded - // Calculate the starting CAKE balance with a dollar balance of $1000. - const principal = 1000 / cakePrice - + // Calculate the starting TOKEN balance with a dollar balance of $1000. + const principal = 1000 / tokenPrice // This is a translation of the typical mathematical compounding APY formula. Details here: https://www.calculatorsoup.com/calculators/financial/compound-interest-calculator.php const finalAmount = principal * (1 + aprAsDecimal / timesCompounded) ** (timesCompounded * daysAsDecimalOfYear) - - // To get the cake earned, deduct the amount after compounding (finalAmount) from the starting CAKE balance (principal) + // To get the TOKEN amount earned, deduct the amount after compounding (finalAmount) from the starting TOKEN balance (principal) const interestEarned = finalAmount - principal return roundToTwoDp(interestEarned) } diff --git a/src/views/Farms/components/FarmCard/ApyButton.tsx b/src/views/Farms/components/FarmCard/ApyButton.tsx index 6f409980acb1c..004f7f6320572 100644 --- a/src/views/Farms/components/FarmCard/ApyButton.tsx +++ b/src/views/Farms/components/FarmCard/ApyButton.tsx @@ -2,6 +2,7 @@ import React from 'react' import BigNumber from 'bignumber.js' import { IconButton, useModal, CalculateIcon } from '@pancakeswap-libs/uikit' import ApyCalculatorModal from 'components/ApyCalculatorModal' +import useI18n from 'hooks/useI18n' export interface ApyButtonProps { lpLabel?: string @@ -11,8 +12,14 @@ export interface ApyButtonProps { } const ApyButton: React.FC = ({ lpLabel, cakePrice, apr, addLiquidityUrl }) => { + const TranslateString = useI18n() const [onPresentApyModal] = useModal( - , + , ) const handleClickButton = (event): void => { diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index 0cd745e3f4b21..e23a863caa744 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -1,20 +1,39 @@ import React from 'react' -import BigNumber from 'bignumber.js' import { Flex, Text, IconButton, useModal, CalculateIcon } from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' import Balance from 'components/Balance' import ApyCalculatorModal from 'components/ApyCalculatorModal' +import { Token } from 'config/constants/types' interface AprRowProps { isOldSyrup?: boolean isFinished: boolean apr: number - cakePrice: BigNumber + rewardTokenPrice: number + stakingToken: Token + earningTokenSymbol: string } -const AprRow: React.FC = ({ isOldSyrup = false, cakePrice, isFinished, apr }) => { +const AprRow: React.FC = ({ + isOldSyrup = false, + rewardTokenPrice, + isFinished, + apr, + stakingToken, + earningTokenSymbol, +}) => { const TranslateString = useI18n() - const [onPresentApyModal] = useModal() + const [onPresentApyModal] = useModal( + , + ) return ( @@ -22,14 +41,12 @@ const AprRow: React.FC = ({ isOldSyrup = false, cakePrice, isFinish {isFinished || isOldSyrup || !apr ? ( '0%' ) : ( - <> - - - - - - - + + + + + + )} ) diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index cee776ab5e673..62fd8650060fe 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -18,7 +18,7 @@ import Balance from 'components/Balance' import { PoolCategory } from 'config/constants/types' import tokens from 'config/constants/tokens' import { Pool } from 'state/types' -import { useGetApiPrice, usePriceCakeBusd } from 'state/hooks' +import { useGetApiPrice } from 'state/hooks' import AprRow from './AprRow' import DepositModal from './DepositModal' import WithdrawModal from './WithdrawModal' @@ -56,7 +56,6 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { const { onStake } = useSousStake(sousId, isBnbPool) const { onUnstake } = useSousUnstake(sousId) const { onReward } = useSousHarvest(sousId, isBnbPool) - const cakePrice = usePriceCakeBusd() // APR const rewardTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') @@ -125,7 +124,14 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { isFinished={isFinished && sousId !== 0} /> - +
{account && harvest && !isOldSyrup && ( From affa8289d453ae998483ee10b60ded514d99e18d Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Mon, 19 Apr 2021 17:05:58 +0100 Subject: [PATCH 09/40] feat(syrup-pool-v2): Loading states for footer and APR --- .../Pools/components/PoolCard/AprRow.tsx | 15 +++++++---- .../Pools/components/PoolCard/CardFooter.tsx | 25 +++++++++++++++---- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index e23a863caa744..6f601174927c9 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Flex, Text, IconButton, useModal, CalculateIcon } from '@pancakeswap-libs/uikit' +import { Flex, Text, IconButton, useModal, CalculateIcon, Skeleton } from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' import Balance from 'components/Balance' import ApyCalculatorModal from 'components/ApyCalculatorModal' @@ -23,14 +23,19 @@ const AprRow: React.FC = ({ earningTokenSymbol, }) => { const TranslateString = useI18n() + + const apyModalLink = + stakingToken.address && + `https://exchange.pancakeswap.finance/#/swap?outputCurrency= + ${stakingToken.address[process.env.REACT_APP_CHAIN_ID]} + ` + const [onPresentApyModal] = useModal( , ) @@ -39,7 +44,7 @@ const AprRow: React.FC = ({ {TranslateString(736, 'APR')}: {isFinished || isOldSyrup || !apr ? ( - '0%' + ) : ( diff --git a/src/views/Pools/components/PoolCard/CardFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter.tsx index e9fa5777fdde7..415a4af334b71 100644 --- a/src/views/Pools/components/PoolCard/CardFooter.tsx +++ b/src/views/Pools/components/PoolCard/CardFooter.tsx @@ -4,7 +4,16 @@ import styled from 'styled-components' import { useWeb3React } from '@web3-react/core' import { getBalanceNumber } from 'utils/formatBalance' import useI18n from 'hooks/useI18n' -import { Flex, MetamaskIcon, CardFooter, ExpandableLabel, Text, LinkExternal, TimerIcon } from '@pancakeswap-libs/uikit' +import { + Flex, + MetamaskIcon, + CardFooter, + ExpandableLabel, + Text, + LinkExternal, + TimerIcon, + Skeleton, +} from '@pancakeswap-libs/uikit' import Balance from 'components/Balance' import { useBlock } from 'state/hooks' import { registerToken } from 'utils/wallet' @@ -74,10 +83,16 @@ const Footer: React.FC = ({ {TranslateString(999, 'Total staked:')} - - - {stakingTokenSymbol} - + {totalStaked ? ( + <> + + + {stakingTokenSymbol} + + + ) : ( + + )} {shouldShowBlockCountdown && ( From 3cdb0b98fe55cc7fd230d839d86b36dcd624a9b2 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 20 Apr 2021 08:57:38 +0100 Subject: [PATCH 10/40] fix(syrup-pool-v2): Fix merge conflicts --- src/views/Pools/Syrup.tsx | 99 --------------------------- src/views/Pools/components/Coming.tsx | 80 ---------------------- 2 files changed, 179 deletions(-) delete mode 100644 src/views/Pools/Syrup.tsx delete mode 100644 src/views/Pools/components/Coming.tsx diff --git a/src/views/Pools/Syrup.tsx b/src/views/Pools/Syrup.tsx deleted file mode 100644 index 68aa7994aebea..0000000000000 --- a/src/views/Pools/Syrup.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useMemo } from 'react' -import { Route, useRouteMatch } from 'react-router-dom' -import BigNumber from 'bignumber.js' -import styled from 'styled-components' -import { useWeb3React } from '@web3-react/core' -import { Heading, Flex, Button, Text, HelpIcon, Link } from '@pancakeswap-libs/uikit' -import orderBy from 'lodash/orderBy' -import partition from 'lodash/partition' -import useI18n from 'hooks/useI18n' -import usePersistState from 'hooks/usePersistState' -import { usePools, useBlock } from 'state/hooks' -import PageHeader from 'components/PageHeader' -import FlexLayout from 'components/layout/Flex' -import Page from 'components/layout/Page' -import Coming from './components/Coming' -import PoolCard from './components/PoolCard' -import PoolTabButtons from './components/PoolTabButtons' -import Divider from './components/Divider' - -const ButtonText = styled(Text)` - display: none; - ${({ theme }) => theme.mediaQueries.lg} { - display: block; - } -` - -const StyledLink = styled(Link)` - display: inline; - height: fit-content; -` - -const Syrup: React.FC = () => { - const { path } = useRouteMatch() - const TranslateString = useI18n() - const { account } = useWeb3React() - const pools = usePools(account) - const { currentBlock } = useBlock() - const [stakedOnly, setStakedOnly] = usePersistState(false, 'pancake_pool_staked') - - const [finishedPools, openPools] = useMemo( - () => partition(pools, (pool) => pool.isFinished || currentBlock > pool.endBlock), - [currentBlock, pools], - ) - const stakedOnlyPools = useMemo( - () => openPools.filter((pool) => pool.userData && new BigNumber(pool.userData.stakedBalance).isGreaterThan(0)), - [openPools], - ) - - return ( - <> - - - - - {TranslateString(999, 'Syrup Pools')} - - - {TranslateString(999, 'Simply stake tokens to earn.')} - - - {TranslateString(999, 'High APR, low risk.')} - - - - - - - - - - - - - - - <> - {stakedOnly - ? orderBy(stakedOnlyPools, ['sortOrder']).map((pool) => ) - : orderBy(openPools, ['sortOrder']).map((pool) => )} - - - - - {orderBy(finishedPools, ['sortOrder']).map((pool) => ( - - ))} - - - - - ) -} - -export default Syrup diff --git a/src/views/Pools/components/Coming.tsx b/src/views/Pools/components/Coming.tsx deleted file mode 100644 index f27e727cad0b9..0000000000000 --- a/src/views/Pools/components/Coming.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react' -import styled from 'styled-components' -import { Image, Button } from '@pancakeswap-libs/uikit' -import { CommunityTag } from 'components/Tags' -import useI18n from 'hooks/useI18n' -import Card from './Card' -import CardTitle from './CardTitle' - -const Balance = styled.div` - color: ${({ theme }) => theme.colors.text}; - font-size: 40px; - font-weight: 600; -` - -const Label = styled.div` - color: ${({ theme }) => theme.colors.textSubtle}; - font-size: 14px; - margin-bottom: 16px; -` - -const DetailPlaceholder = styled.div` - display: flex; - font-size: 14px; -` -const Value = styled.div` - color: ${({ theme }) => theme.colors.text}; - font-size: 14px; -` - -const Footer = styled.div` - border-top: 1px solid ${({ theme }) => (theme.isDark ? '#524B63' : '#E9EAEB')}; - padding: 24px; -` -const Coming: React.FC = () => { - const TranslateString = useI18n() - - return ( - -
- - {TranslateString(414, 'Your Project?')}{' '} - - 👀 - - - Your project here - ??? - - - -
{TranslateString(736, 'APR')}:
- ?? -
- -
- - 🥞{' '} - - {TranslateString(384, 'Your Stake')}: -
- ??? CAKE -
-
-
- -
-
- ) -} - -export default Coming From 8b69e2f6520c46b9301c494aa3c7ca4a0d15628d Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 20 Apr 2021 09:01:47 +0100 Subject: [PATCH 11/40] fix(syrup-pool-v2): Fix lint error --- src/views/Farms/Farms.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views/Farms/Farms.tsx b/src/views/Farms/Farms.tsx index 4f50135c1cdf8..5da93dc43f6d5 100644 --- a/src/views/Farms/Farms.tsx +++ b/src/views/Farms/Farms.tsx @@ -7,7 +7,6 @@ import { Image, Heading, RowType, Toggle, Text } from '@pancakeswap-libs/uikit' import styled from 'styled-components' import FlexLayout from 'components/layout/Flex' import Page from 'components/layout/Page' -import PageHeader from 'components/PageHeader' import { useFarms, usePriceCakeBusd, useGetApiPrices } from 'state/hooks' import useRefresh from 'hooks/useRefresh' import { fetchFarmUserDataAsync } from 'state/actions' From 1ce5132a236a8cbd006da68973988d3e93db44ae Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 20 Apr 2021 12:00:06 +0100 Subject: [PATCH 12/40] chore(syrup-pool-v2): Break CardActions into its own component and reorg props --- .../Pools/components/PoolCard/AprRow.tsx | 30 ++- .../Pools/components/PoolCard/CardActions.tsx | 181 +++++++++++++++ .../Pools/components/PoolCard/CardFooter.tsx | 16 +- .../components/PoolCard/StyledCardHeader.tsx | 6 +- src/views/Pools/components/PoolCard/index.tsx | 213 ++---------------- 5 files changed, 237 insertions(+), 209 deletions(-) create mode 100644 src/views/Pools/components/PoolCard/CardActions.tsx diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index 6f601174927c9..587c6d196bd4b 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -1,27 +1,41 @@ import React from 'react' +import BigNumber from 'bignumber.js' import { Flex, Text, IconButton, useModal, CalculateIcon, Skeleton } from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' +import { Token } from 'config/constants/types' +import { getBalanceNumber } from 'utils/formatBalance' +import { getPoolApr } from 'utils/apr' +import { getAddress } from 'utils/addressHelpers' +import { useGetApiPrice } from 'state/hooks' import Balance from 'components/Balance' import ApyCalculatorModal from 'components/ApyCalculatorModal' -import { Token } from 'config/constants/types' interface AprRowProps { isOldSyrup?: boolean isFinished: boolean - apr: number - rewardTokenPrice: number stakingToken: Token - earningTokenSymbol: string + earningToken: Token + totalStaked: BigNumber + tokenPerBlock: string } const AprRow: React.FC = ({ isOldSyrup = false, - rewardTokenPrice, isFinished, - apr, stakingToken, - earningTokenSymbol, + earningToken, + totalStaked, + tokenPerBlock, }) => { + const rewardTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') + const stakingTokenPrice = useGetApiPrice(stakingToken.address ? getAddress(stakingToken.address) : '') + const apr = getPoolApr( + stakingTokenPrice, + rewardTokenPrice, + getBalanceNumber(totalStaked, stakingToken.decimals), + parseFloat(tokenPerBlock), + ) + const TranslateString = useI18n() const apyModalLink = @@ -36,7 +50,7 @@ const AprRow: React.FC = ({ apr={apr} linkLabel={`${TranslateString(999, 'Get')} ${stakingToken.symbol}`} linkHref={apyModalLink || 'https://exchange.pancakeswap.finance'} - earningTokenSymbol={earningTokenSymbol} + earningTokenSymbol={earningToken.symbol} />, ) diff --git a/src/views/Pools/components/PoolCard/CardActions.tsx b/src/views/Pools/components/PoolCard/CardActions.tsx new file mode 100644 index 0000000000000..02f5202f07f32 --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardActions.tsx @@ -0,0 +1,181 @@ +import BigNumber from 'bignumber.js' +import React, { useCallback, useState } from 'react' +import styled from 'styled-components' +import { Button, IconButton, useModal, AddIcon } from '@pancakeswap-libs/uikit' +import Label from 'components/Label' +import { useERC20 } from 'hooks/useContract' +import { useSousApprove } from 'hooks/useApprove' +import useI18n from 'hooks/useI18n' +import { useSousStake } from 'hooks/useStake' +import { useSousUnstake } from 'hooks/useUnstake' +import { getBalanceNumber } from 'utils/formatBalance' +import { getAddress } from 'utils/addressHelpers' +import { useSousHarvest } from 'hooks/useHarvest' +import Balance from 'components/Balance' +import { PoolCategory } from 'config/constants/types' +import { Pool } from 'state/types' +import DepositModal from './DepositModal' +import WithdrawModal from './WithdrawModal' +import CompoundModal from './CompoundModal' +import HarvestButton from './HarvestButton' + +const StyledCardActions = styled.div` + display: flex; + justify-content: center; + margin: 16px 0; + width: 100%; + box-sizing: border-box; +` + +const BalanceAndCompound = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + flex-direction: row; +` + +const StyledActionSpacer = styled.div` + height: ${(props) => props.theme.spacing[4]}px; + width: ${(props) => props.theme.spacing[4]}px; +` + +const StyledDetails = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + font-size: 14px; +` + +const CardActions: React.FC<{ pool: Pool; isOldSyrup: boolean }> = ({ pool, isOldSyrup }) => { + const { sousId, stakingToken, earningToken, harvest, poolCategory, isFinished, userData, stakingLimit } = pool + + // Pools using native BNB behave differently than pools using a token + const isBnbPool = poolCategory === PoolCategory.BINANCE + + const TranslateString = useI18n() + const [requestedApproval, setRequestedApproval] = useState(false) + const [pendingTx, setPendingTx] = useState(false) + const stakingTokenContract = useERC20(stakingToken.address ? getAddress(stakingToken.address) : '') + const { onApprove } = useSousApprove(stakingTokenContract, sousId) + const { onStake } = useSousStake(sousId, isBnbPool) + const { onUnstake } = useSousUnstake(sousId) + const { onReward } = useSousHarvest(sousId, isBnbPool) + + const allowance = new BigNumber(userData?.allowance || 0) + const stakingTokenBalance = new BigNumber(userData?.stakingTokenBalance || 0) + const stakedBalance = new BigNumber(userData?.stakedBalance || 0) + const earnings = new BigNumber(userData?.pendingReward || 0) + + const accountHasStakedBalance = stakedBalance?.toNumber() > 0 + const needsApproval = !accountHasStakedBalance && !allowance.toNumber() && !isBnbPool + + const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) + const [onPresentDeposit] = useModal( + , + ) + + const [onPresentCompound] = useModal( + , + ) + const [onPresentWithdraw] = useModal( + , + ) + + const handleApprove = useCallback(async () => { + try { + setRequestedApproval(true) + const txHash = await onApprove() + // user rejected tx or didn't go thru + if (!txHash) { + setRequestedApproval(false) + } + } catch (e) { + console.error(e) + } + }, [onApprove, setRequestedApproval]) + return ( +
+
+ {harvest && !isOldSyrup && ( + { + setPendingTx(true) + await onReward() + setPendingTx(false) + }} + /> + )} +
+ {!isOldSyrup ? ( + + + {sousId === 0 && harvest && ( + + )} + + ) : ( + Is old syrup + // + )} +
+ ) +} + +export default CardActions diff --git a/src/views/Pools/components/PoolCard/CardFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter.tsx index 415a4af334b71..ad06ed74acc19 100644 --- a/src/views/Pools/components/PoolCard/CardFooter.tsx +++ b/src/views/Pools/components/PoolCard/CardFooter.tsx @@ -14,10 +14,11 @@ import { TimerIcon, Skeleton, } from '@pancakeswap-libs/uikit' -import Balance from 'components/Balance' +import { BASE_URL } from 'config' +import { Address } from 'config/constants/types' import { useBlock } from 'state/hooks' import { registerToken } from 'utils/wallet' -import { BASE_URL } from 'config' +import Balance from 'components/Balance' interface FooterProps { projectLink: string @@ -30,7 +31,7 @@ interface FooterProps { endBlock: number isFinished: boolean stakingTokenSymbol: string - poolContractAddress: string + contractAddress: Address } const ExpandedWrapper = styled(Flex)` @@ -57,7 +58,7 @@ const Footer: React.FC = ({ startBlock, endBlock, stakingTokenSymbol, - poolContractAddress, + contractAddress, }) => { const TranslateString = useI18n() const [isExpanded, setIsExpanded] = useState(false) @@ -70,6 +71,7 @@ const Footer: React.FC = ({ const blocksUntilStart = Math.max(startBlock - currentBlock, 0) const blocksRemaining = Math.max(endBlock - currentBlock, 0) const hasPoolStarted = blocksUntilStart === 0 && blocksRemaining > 0 + const poolContractAddress = contractAddress[process.env.REACT_APP_CHAIN_ID] return ( @@ -117,7 +119,7 @@ const Footer: React.FC = ({ )} - {TranslateString(412, 'View project site')} + {TranslateString(412, 'View Project Site')} {poolContractAddress && ( @@ -128,7 +130,7 @@ const Footer: React.FC = ({ href={`https://bscscan.com/address/${poolContractAddress}`} target="_blank" > - {TranslateString(412, 'View contract')} + {TranslateString(412, 'View Contract')}
)} @@ -140,7 +142,7 @@ const Footer: React.FC = ({ href={`https://pancakeswap.info/token/${tokenAddress}`} target="_blank" > - {TranslateString(412, 'Info site')} + {TranslateString(412, 'View Pool Info')}
)} diff --git a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx index ab74cb379a8f6..09b3b6a448858 100644 --- a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx +++ b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx @@ -9,11 +9,11 @@ const Wrapper = styled(CardHeader)<{ isFinished?: boolean }>` ` const StyledCardHeader: React.FC<{ - poolImage: string + poolImageSrc: string earningTokenSymbol: string stakingTokenSymbol: string isFinished?: boolean -}> = ({ poolImage, earningTokenSymbol, stakingTokenSymbol, isFinished = false }) => { +}> = ({ poolImageSrc, earningTokenSymbol, stakingTokenSymbol, isFinished = false }) => { return ( <> @@ -24,7 +24,7 @@ const StyledCardHeader: React.FC<{ Stake {stakingTokenSymbol} - {earningTokenSymbol} + {earningTokenSymbol} {isFinished && ( diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index 62fd8650060fe..4cfd6a95aeddb 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -1,124 +1,42 @@ import BigNumber from 'bignumber.js' -import React, { useCallback, useState } from 'react' -import styled from 'styled-components' -import { Button, IconButton, useModal, AddIcon, CardBody } from '@pancakeswap-libs/uikit' +import React from 'react' +import { CardBody, Flex, Text } from '@pancakeswap-libs/uikit' import { useWeb3React } from '@web3-react/core' import UnlockButton from 'components/UnlockButton' -import Label from 'components/Label' -import { useERC20 } from 'hooks/useContract' -import { useSousApprove } from 'hooks/useApprove' import useI18n from 'hooks/useI18n' -import { useSousStake } from 'hooks/useStake' -import { useSousUnstake } from 'hooks/useUnstake' -import { getBalanceNumber } from 'utils/formatBalance' -import { getPoolApr } from 'utils/apr' import { getAddress } from 'utils/addressHelpers' -import { useSousHarvest } from 'hooks/useHarvest' -import Balance from 'components/Balance' -import { PoolCategory } from 'config/constants/types' import tokens from 'config/constants/tokens' import { Pool } from 'state/types' -import { useGetApiPrice } from 'state/hooks' import AprRow from './AprRow' -import DepositModal from './DepositModal' -import WithdrawModal from './WithdrawModal' -import CompoundModal from './CompoundModal' import StyledCard from './StyledCard' -import HarvestButton from './HarvestButton' import CardFooter from './CardFooter' import StyledCardHeader from './StyledCardHeader' +import CardActions from './CardActions' const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { const { sousId, stakingToken, earningToken, - harvest, - poolCategory, totalStaked, startBlock, endBlock, isFinished, userData, - stakingLimit, contractAddress, } = pool - - // Pools using native BNB behave differently than pools using a token - const isBnbPool = poolCategory === PoolCategory.BINANCE - - const TranslateString = useI18n() - const [requestedApproval, setRequestedApproval] = useState(false) - const [pendingTx, setPendingTx] = useState(false) - const stakingTokenContract = useERC20(stakingToken.address ? getAddress(stakingToken.address) : '') const { account } = useWeb3React() - const { onApprove } = useSousApprove(stakingTokenContract, sousId) - const { onStake } = useSousStake(sousId, isBnbPool) - const { onUnstake } = useSousUnstake(sousId) - const { onReward } = useSousHarvest(sousId, isBnbPool) - - // APR - const rewardTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') - const stakingTokenPrice = useGetApiPrice(stakingToken.address ? getAddress(stakingToken.address) : '') - const apr = getPoolApr( - stakingTokenPrice, - rewardTokenPrice, - getBalanceNumber(pool.totalStaked, stakingToken.decimals), - parseFloat(pool.tokenPerBlock), - ) - - const allowance = new BigNumber(userData?.allowance || 0) - const stakingTokenBalance = new BigNumber(userData?.stakingTokenBalance || 0) + const TranslateString = useI18n() const stakedBalance = new BigNumber(userData?.stakedBalance || 0) - const earnings = new BigNumber(userData?.pendingReward || 0) - - const isOldSyrup = stakingToken.symbol === tokens.syrup.symbol const accountHasStakedBalance = stakedBalance?.toNumber() > 0 - const needsApproval = !accountHasStakedBalance && !allowance.toNumber() && !isBnbPool const isCardActive = isFinished && accountHasStakedBalance - - const poolContractAddress = contractAddress[process.env.REACT_APP_CHAIN_ID] - - const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) - const [onPresentDeposit] = useModal( - , - ) - - const [onPresentCompound] = useModal( - , - ) - const poolImage = `${pool.earningToken.symbol}-${pool.stakingToken.symbol}.svg`.toLocaleLowerCase() - const [onPresentWithdraw] = useModal( - , - ) - - const handleApprove = useCallback(async () => { - try { - setRequestedApproval(true) - const txHash = await onApprove() - // user rejected tx or didn't go thru - if (!txHash) { - setRequestedApproval(false) - } - } catch (e) { - console.error(e) - } - }, [onApprove, setRequestedApproval]) + const isOldSyrup = stakingToken.symbol === tokens.syrup.symbol + const poolImageSrc = `${pool.earningToken.symbol}-${pool.stakingToken.symbol}.svg`.toLocaleLowerCase() return ( = ({ pool }) => { - -
- {account && harvest && !isOldSyrup && ( - { - setPendingTx(true) - await onReward() - setPendingTx(false) - }} - /> + + {account ? ( + + ) : ( + <> + + {TranslateString(999, 'Start earning')} + + + )} -
- {!isOldSyrup ? ( - - - {sousId === 0 && account && harvest && ( - - )} - - ) : ( - Is old syrup - // - )} -
) } -const StyledCardActions = styled.div` - display: flex; - justify-content: center; - margin: 16px 0; - width: 100%; - box-sizing: border-box; -` - -const BalanceAndCompound = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - flex-direction: row; -` - -const StyledActionSpacer = styled.div` - height: ${(props) => props.theme.spacing[4]}px; - width: ${(props) => props.theme.spacing[4]}px; -` - -const StyledDetails = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; -` - export default PoolCard From c188ea31172859589bddfcb8bba6785cd6f5e36b Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 20 Apr 2021 15:37:26 +0100 Subject: [PATCH 13/40] feat(syrup-pool-v2): Create separate Staking / HarvestActions comps --- .../Pools/components/PoolCard/AprRow.tsx | 1 + .../PoolCard/CardActions/HarvestActions.tsx | 7 + .../PoolCard/CardActions/StakingActions.tsx | 73 +++++++++++ .../index.tsx} | 120 +++++++++--------- .../Pools/components/PoolCard/StyledCard.tsx | 6 +- .../components/PoolCard/StyledCardHeader.tsx | 5 +- src/views/Pools/components/PoolCard/index.tsx | 13 +- 7 files changed, 155 insertions(+), 70 deletions(-) create mode 100644 src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx create mode 100644 src/views/Pools/components/PoolCard/CardActions/StakingActions.tsx rename src/views/Pools/components/PoolCard/{CardActions.tsx => CardActions/index.tsx} (64%) diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index 587c6d196bd4b..9226bcb413ed9 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -58,6 +58,7 @@ const AprRow: React.FC = ({ {TranslateString(736, 'APR')}: {isFinished || isOldSyrup || !apr ? ( + // IS OLD SYRUP CONDITIONAL ) : ( diff --git a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx new file mode 100644 index 0000000000000..4a654695d76ca --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +const HarvestActions = () => { + return
ss
+} + +export default HarvestActions diff --git a/src/views/Pools/components/PoolCard/CardActions/StakingActions.tsx b/src/views/Pools/components/PoolCard/CardActions/StakingActions.tsx new file mode 100644 index 0000000000000..3dc5123814a63 --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardActions/StakingActions.tsx @@ -0,0 +1,73 @@ +import React, { useState, useCallback } from 'react' +import { Flex, Box, Text, Button } from '@pancakeswap-libs/uikit' +import styled from 'styled-components' +import { useSousApprove } from 'hooks/useApprove' +import useI18n from 'hooks/useI18n' +import { useERC20 } from 'hooks/useContract' +import { useToast } from 'state/hooks' +import { getAddress } from 'utils/addressHelpers' +import { Token } from 'config/constants/types' + +interface StakingActionsProps { + stakingToken: Token + needsApproval: boolean + isOldSyrup: boolean + isFinished: boolean + sousId: number +} + +const InlineText = styled(Text)` + display: inline; +` + +const StakingActions: React.FC = ({ + stakingToken, + needsApproval, + isOldSyrup, + isFinished, + sousId, +}) => { + const TranslateString = useI18n() + const stakingTokenContract = useERC20(stakingToken.address ? getAddress(stakingToken.address) : '') + const [requestedApproval, setRequestedApproval] = useState(false) + const { onApprove } = useSousApprove(stakingTokenContract, sousId) + const { toastSuccess } = useToast() + + const handleApprove = useCallback(async () => { + try { + setRequestedApproval(true) + const txHash = await onApprove() + debugger // eslint-disable-line + if (txHash) { + toastSuccess('You have claimed your rewards!') + } + // user rejected tx or didn't go thru + setRequestedApproval(false) + } catch (e) { + console.error(e) + } + }, [onApprove, setRequestedApproval, toastSuccess]) + + return ( + + + + {TranslateString(1070, `stake`)} + + + {` ${stakingToken.symbol}`} + + + {needsApproval && !isOldSyrup ? ( + // IS OLD SYRUP CONDITIONAL + + ) : ( + Thing + )} + + ) +} + +export default StakingActions diff --git a/src/views/Pools/components/PoolCard/CardActions.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx similarity index 64% rename from src/views/Pools/components/PoolCard/CardActions.tsx rename to src/views/Pools/components/PoolCard/CardActions/index.tsx index 02f5202f07f32..cdc5f816729ea 100644 --- a/src/views/Pools/components/PoolCard/CardActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -1,31 +1,22 @@ import BigNumber from 'bignumber.js' import React, { useCallback, useState } from 'react' import styled from 'styled-components' -import { Button, IconButton, useModal, AddIcon } from '@pancakeswap-libs/uikit' +import { Button, IconButton, useModal, AddIcon, Flex, Text, Box } from '@pancakeswap-libs/uikit' import Label from 'components/Label' -import { useERC20 } from 'hooks/useContract' -import { useSousApprove } from 'hooks/useApprove' import useI18n from 'hooks/useI18n' import { useSousStake } from 'hooks/useStake' import { useSousUnstake } from 'hooks/useUnstake' import { getBalanceNumber } from 'utils/formatBalance' -import { getAddress } from 'utils/addressHelpers' + import { useSousHarvest } from 'hooks/useHarvest' import Balance from 'components/Balance' import { PoolCategory } from 'config/constants/types' import { Pool } from 'state/types' -import DepositModal from './DepositModal' -import WithdrawModal from './WithdrawModal' -import CompoundModal from './CompoundModal' -import HarvestButton from './HarvestButton' - -const StyledCardActions = styled.div` - display: flex; - justify-content: center; - margin: 16px 0; - width: 100%; - box-sizing: border-box; -` +import DepositModal from '../DepositModal' +import WithdrawModal from '../WithdrawModal' +import CompoundModal from '../CompoundModal' +import HarvestButton from '../HarvestButton' +import StakingActions from './StakingActions' const BalanceAndCompound = styled.div` display: flex; @@ -46,27 +37,31 @@ const StyledDetails = styled.div` font-size: 14px; ` -const CardActions: React.FC<{ pool: Pool; isOldSyrup: boolean }> = ({ pool, isOldSyrup }) => { +const InlineText = styled(Text)` + display: inline; +` + +const CardActions: React.FC<{ + pool: Pool + isOldSyrup: boolean + stakedBalance: BigNumber + accountHasStakedBalance: boolean +}> = ({ pool, isOldSyrup, stakedBalance, accountHasStakedBalance }) => { const { sousId, stakingToken, earningToken, harvest, poolCategory, isFinished, userData, stakingLimit } = pool // Pools using native BNB behave differently than pools using a token const isBnbPool = poolCategory === PoolCategory.BINANCE const TranslateString = useI18n() - const [requestedApproval, setRequestedApproval] = useState(false) + const [pendingTx, setPendingTx] = useState(false) - const stakingTokenContract = useERC20(stakingToken.address ? getAddress(stakingToken.address) : '') - const { onApprove } = useSousApprove(stakingTokenContract, sousId) const { onStake } = useSousStake(sousId, isBnbPool) const { onUnstake } = useSousUnstake(sousId) const { onReward } = useSousHarvest(sousId, isBnbPool) const allowance = new BigNumber(userData?.allowance || 0) const stakingTokenBalance = new BigNumber(userData?.stakingTokenBalance || 0) - const stakedBalance = new BigNumber(userData?.stakedBalance || 0) const earnings = new BigNumber(userData?.pendingReward || 0) - - const accountHasStakedBalance = stakedBalance?.toNumber() > 0 const needsApproval = !accountHasStakedBalance && !allowance.toNumber() && !isBnbPool const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) @@ -91,34 +86,18 @@ const CardActions: React.FC<{ pool: Pool; isOldSyrup: boolean }> = ({ pool, isOl />, ) - const handleApprove = useCallback(async () => { - try { - setRequestedApproval(true) - const txHash = await onApprove() - // user rejected tx or didn't go thru - if (!txHash) { - setRequestedApproval(false) - } - } catch (e) { - console.error(e) - } - }, [onApprove, setRequestedApproval]) return ( -
-
- {harvest && !isOldSyrup && ( - { - setPendingTx(true) - await onReward() - setPendingTx(false) - }} - /> - )} -
+ + + + {`${earningToken.symbol} `} + + + {TranslateString(1072, `earned`)} + + {!isOldSyrup ? ( + // IS OLD SYRUP CONDITIONAL {sousId === 0 && harvest && ( @@ -133,21 +112,41 @@ const CardActions: React.FC<{ pool: Pool; isOldSyrup: boolean }> = ({ pool, isOl Is old syrup // )} -
{TranslateString(384, 'Your Stake')}:
= ({ pool, isOl isDisabled={isFinished} value={getBalanceNumber(stakedBalance, stakingToken.decimals)} /> -
-
+ */} +
) } diff --git a/src/views/Pools/components/PoolCard/StyledCard.tsx b/src/views/Pools/components/PoolCard/StyledCard.tsx index 213eb769d38df..b6dc1a0c60fbf 100644 --- a/src/views/Pools/components/PoolCard/StyledCard.tsx +++ b/src/views/Pools/components/PoolCard/StyledCard.tsx @@ -1,14 +1,14 @@ import styled from 'styled-components' import { Card } from '@pancakeswap-libs/uikit' -const StyledCard = styled(Card)<{ isActive?: boolean; isFinished?: boolean }>` +const StyledCard = styled(Card)<{ isStaking?: boolean; isFinished?: boolean }>` background: ${(props) => props.theme.card.background}; box-shadow: 0px 2px 12px -8px rgba(25, 19, 38, 0.1), 0px 1px 1px rgba(25, 19, 38, 0.05); border-radius: 32px; display: flex; color: ${({ isFinished, theme }) => theme.colors[isFinished ? 'textDisabled' : 'secondary']}; - box-shadow: ${({ isActive }) => - isActive + box-shadow: ${({ isStaking }) => + isStaking ? '0px 0px 0px 1px #0098A1, 0px 0px 4px 8px rgba(31, 199, 212, 0.4);' : '0px 2px 12px -8px rgba(25, 19, 38, 0.1), 0px 1px 1px rgba(25, 19, 38, 0.05)'}; flex-direction: column; diff --git a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx index 09b3b6a448858..97192583c0ffc 100644 --- a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx +++ b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx @@ -9,11 +9,12 @@ const Wrapper = styled(CardHeader)<{ isFinished?: boolean }>` ` const StyledCardHeader: React.FC<{ - poolImageSrc: string earningTokenSymbol: string stakingTokenSymbol: string isFinished?: boolean -}> = ({ poolImageSrc, earningTokenSymbol, stakingTokenSymbol, isFinished = false }) => { +}> = ({ earningTokenSymbol, stakingTokenSymbol, isFinished = false }) => { + const poolImageSrc = `${earningTokenSymbol}-${stakingTokenSymbol}.svg`.toLocaleLowerCase() + return ( <> diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index 4cfd6a95aeddb..deb8fbace920a 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -29,14 +29,12 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { const TranslateString = useI18n() const stakedBalance = new BigNumber(userData?.stakedBalance || 0) const accountHasStakedBalance = stakedBalance?.toNumber() > 0 - const isCardActive = isFinished && accountHasStakedBalance + // IS OLD SYRUP CONDITION const isOldSyrup = stakingToken.symbol === tokens.syrup.symbol - const poolImageSrc = `${pool.earningToken.symbol}-${pool.stakingToken.symbol}.svg`.toLocaleLowerCase() return ( - + = ({ pool }) => { /> {account ? ( - + ) : ( <> From 32adaa8170752111ccdc3585b09b6ebc1213bf42 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Tue, 20 Apr 2021 17:27:37 +0100 Subject: [PATCH 14/40] feat(syrup-pool-v2): Approval and Staking actions --- .../PoolCard/CardActions/StakingActions.tsx | 73 ------------------- .../StakingActions/ApprovalAction.tsx | 57 +++++++++++++++ .../StakingActions/StakeAction.tsx | 35 +++++++++ .../CardActions/StakingActions/index.tsx | 59 +++++++++++++++ .../components/PoolCard/CardActions/index.tsx | 2 + 5 files changed, 153 insertions(+), 73 deletions(-) delete mode 100644 src/views/Pools/components/PoolCard/CardActions/StakingActions.tsx create mode 100644 src/views/Pools/components/PoolCard/CardActions/StakingActions/ApprovalAction.tsx create mode 100644 src/views/Pools/components/PoolCard/CardActions/StakingActions/StakeAction.tsx create mode 100644 src/views/Pools/components/PoolCard/CardActions/StakingActions/index.tsx diff --git a/src/views/Pools/components/PoolCard/CardActions/StakingActions.tsx b/src/views/Pools/components/PoolCard/CardActions/StakingActions.tsx deleted file mode 100644 index 3dc5123814a63..0000000000000 --- a/src/views/Pools/components/PoolCard/CardActions/StakingActions.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useState, useCallback } from 'react' -import { Flex, Box, Text, Button } from '@pancakeswap-libs/uikit' -import styled from 'styled-components' -import { useSousApprove } from 'hooks/useApprove' -import useI18n from 'hooks/useI18n' -import { useERC20 } from 'hooks/useContract' -import { useToast } from 'state/hooks' -import { getAddress } from 'utils/addressHelpers' -import { Token } from 'config/constants/types' - -interface StakingActionsProps { - stakingToken: Token - needsApproval: boolean - isOldSyrup: boolean - isFinished: boolean - sousId: number -} - -const InlineText = styled(Text)` - display: inline; -` - -const StakingActions: React.FC = ({ - stakingToken, - needsApproval, - isOldSyrup, - isFinished, - sousId, -}) => { - const TranslateString = useI18n() - const stakingTokenContract = useERC20(stakingToken.address ? getAddress(stakingToken.address) : '') - const [requestedApproval, setRequestedApproval] = useState(false) - const { onApprove } = useSousApprove(stakingTokenContract, sousId) - const { toastSuccess } = useToast() - - const handleApprove = useCallback(async () => { - try { - setRequestedApproval(true) - const txHash = await onApprove() - debugger // eslint-disable-line - if (txHash) { - toastSuccess('You have claimed your rewards!') - } - // user rejected tx or didn't go thru - setRequestedApproval(false) - } catch (e) { - console.error(e) - } - }, [onApprove, setRequestedApproval, toastSuccess]) - - return ( - - - - {TranslateString(1070, `stake`)} - - - {` ${stakingToken.symbol}`} - - - {needsApproval && !isOldSyrup ? ( - // IS OLD SYRUP CONDITIONAL - - ) : ( - Thing - )} - - ) -} - -export default StakingActions diff --git a/src/views/Pools/components/PoolCard/CardActions/StakingActions/ApprovalAction.tsx b/src/views/Pools/components/PoolCard/CardActions/StakingActions/ApprovalAction.tsx new file mode 100644 index 0000000000000..d4a67f767ce87 --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardActions/StakingActions/ApprovalAction.tsx @@ -0,0 +1,57 @@ +import React, { useState, useCallback } from 'react' +import { Button, AutoRenewIcon } from '@pancakeswap-libs/uikit' +import { useSousApprove } from 'hooks/useApprove' +import useI18n from 'hooks/useI18n' +import { useERC20 } from 'hooks/useContract' +import { useToast } from 'state/hooks' +import { getAddress } from 'utils/addressHelpers' +import { Token } from 'config/constants/types' + +interface ApprovalActionProps { + stakingToken: Token + earningTokenSymbol: string + isFinished: boolean + sousId: number +} + +const ApprovalAction: React.FC = ({ stakingToken, earningTokenSymbol, isFinished, sousId }) => { + const TranslateString = useI18n() + const stakingTokenContract = useERC20(stakingToken.address ? getAddress(stakingToken.address) : '') + const [requestedApproval, setRequestedApproval] = useState(false) + const { onApprove } = useSousApprove(stakingTokenContract, sousId) + const { toastSuccess, toastError } = useToast() + + const handleApprove = useCallback(async () => { + try { + setRequestedApproval(true) + const txHash = await onApprove() + if (txHash) { + toastSuccess( + `${TranslateString(999, 'Contract Enabled')}`, + `${TranslateString(999, `You can now stake in the ${earningTokenSymbol} pool!`)}`, + ) + setRequestedApproval(false) + } else { + // user rejected tx or didn't go thru + setRequestedApproval(false) + } + } catch (e) { + console.error(e) + toastError('Error', e?.message) + } + }, [onApprove, setRequestedApproval, toastSuccess, toastError, TranslateString, earningTokenSymbol]) + + return ( + + ) +} + +export default ApprovalAction diff --git a/src/views/Pools/components/PoolCard/CardActions/StakingActions/StakeAction.tsx b/src/views/Pools/components/PoolCard/CardActions/StakingActions/StakeAction.tsx new file mode 100644 index 0000000000000..e3dbd9fd61d83 --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardActions/StakingActions/StakeAction.tsx @@ -0,0 +1,35 @@ +import React, { useState, useCallback } from 'react' +import { Flex, Box, Text, Button, AutoRenewIcon } from '@pancakeswap-libs/uikit' +import BigNumber from 'bignumber.js' +import styled from 'styled-components' +import { useSousApprove } from 'hooks/useApprove' +import useI18n from 'hooks/useI18n' +import { useERC20 } from 'hooks/useContract' +import { useToast } from 'state/hooks' +import { getAddress } from 'utils/addressHelpers' +import { Token } from 'config/constants/types' +import ApprovalAction from './ApprovalAction' + +interface StakeActionsProps { + stakedBalance: BigNumber +} + +const InlineText = styled(Text)` + display: inline; +` + +const StakeAction: React.FC = ({ stakedBalance }) => { + const TranslateString = useI18n() + + return ( + + {stakedBalance.toNumber() === 0 ? ( + + ) : ( + + )} + + ) +} + +export default StakeAction diff --git a/src/views/Pools/components/PoolCard/CardActions/StakingActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/StakingActions/index.tsx new file mode 100644 index 0000000000000..c97cb8da96687 --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardActions/StakingActions/index.tsx @@ -0,0 +1,59 @@ +import React from 'react' +import { Flex, Box, Text } from '@pancakeswap-libs/uikit' +import BigNumber from 'bignumber.js' +import styled from 'styled-components' +import useI18n from 'hooks/useI18n' +import { Token } from 'config/constants/types' +import ApprovalAction from './ApprovalAction' +import StakeAction from './StakeAction' + +interface StakingActionsProps { + stakingToken: Token + stakedBalance: BigNumber + earningTokenSymbol: string + needsApproval: boolean + isOldSyrup: boolean + isFinished: boolean + sousId: number +} + +const InlineText = styled(Text)` + display: inline; +` + +const StakingActions: React.FC = ({ + stakingToken, + stakedBalance, + earningTokenSymbol, + needsApproval, + isOldSyrup, + isFinished, + sousId, +}) => { + const TranslateString = useI18n() + + return ( + + + + {TranslateString(1070, `stake`)} + + + {` ${stakingToken.symbol}`} + + + {needsApproval && !isOldSyrup ? ( + + ) : ( + + )} + + ) +} + +export default StakingActions diff --git a/src/views/Pools/components/PoolCard/CardActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx index cdc5f816729ea..03433e27c3cd7 100644 --- a/src/views/Pools/components/PoolCard/CardActions/index.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -127,6 +127,8 @@ const CardActions: React.FC<{ Date: Wed, 21 Apr 2021 14:54:46 +0100 Subject: [PATCH 15/40] feat(pool-stake-modal): Basics of stake modal laid out --- src/utils/formatBalance.ts | 2 +- .../Pools/components/PoolCard/AprRow.tsx | 13 +- .../{StakingActions => }/ApprovalAction.tsx | 0 .../PoolCard/CardActions/StakeAction.tsx | 75 ++++++++ .../StakingActions/StakeAction.tsx | 35 ---- .../CardActions/StakingActions/index.tsx | 59 ------- .../components/PoolCard/CardActions/index.tsx | 92 ++++------ .../components/PoolCard/ConfirmButton.tsx | 39 +++++ .../Pools/components/PoolCard/StakeModal.tsx | 163 ++++++++++++++++++ src/views/Pools/components/PoolCard/index.tsx | 10 +- src/views/Pools/index.tsx | 4 +- 11 files changed, 318 insertions(+), 174 deletions(-) rename src/views/Pools/components/PoolCard/CardActions/{StakingActions => }/ApprovalAction.tsx (100%) create mode 100644 src/views/Pools/components/PoolCard/CardActions/StakeAction.tsx delete mode 100644 src/views/Pools/components/PoolCard/CardActions/StakingActions/StakeAction.tsx delete mode 100644 src/views/Pools/components/PoolCard/CardActions/StakingActions/index.tsx create mode 100644 src/views/Pools/components/PoolCard/ConfirmButton.tsx create mode 100644 src/views/Pools/components/PoolCard/StakeModal.tsx diff --git a/src/utils/formatBalance.ts b/src/utils/formatBalance.ts index 0325f536dd4c8..ea8834fc646f0 100644 --- a/src/utils/formatBalance.ts +++ b/src/utils/formatBalance.ts @@ -6,7 +6,7 @@ export const getBalanceNumber = (balance: BigNumber, decimals = 18) => { } export const getFullDisplayBalance = (balance: BigNumber, decimals = 18) => { - return balance.dividedBy(new BigNumber(10).pow(decimals)).toFixed() + return balance.dividedBy(new BigNumber(10).pow(decimals)).toFixed(decimals) } export const formatNumber = (number: number, minPrecision = 2, maxPrecision = 2) => { diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index 9226bcb413ed9..92e685cdd6820 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -11,7 +11,6 @@ import Balance from 'components/Balance' import ApyCalculatorModal from 'components/ApyCalculatorModal' interface AprRowProps { - isOldSyrup?: boolean isFinished: boolean stakingToken: Token earningToken: Token @@ -19,14 +18,7 @@ interface AprRowProps { tokenPerBlock: string } -const AprRow: React.FC = ({ - isOldSyrup = false, - isFinished, - stakingToken, - earningToken, - totalStaked, - tokenPerBlock, -}) => { +const AprRow: React.FC = ({ isFinished, stakingToken, earningToken, totalStaked, tokenPerBlock }) => { const rewardTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') const stakingTokenPrice = useGetApiPrice(stakingToken.address ? getAddress(stakingToken.address) : '') const apr = getPoolApr( @@ -57,8 +49,7 @@ const AprRow: React.FC = ({ return ( {TranslateString(736, 'APR')}: - {isFinished || isOldSyrup || !apr ? ( - // IS OLD SYRUP CONDITIONAL + {isFinished || !apr ? ( ) : ( diff --git a/src/views/Pools/components/PoolCard/CardActions/StakingActions/ApprovalAction.tsx b/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx similarity index 100% rename from src/views/Pools/components/PoolCard/CardActions/StakingActions/ApprovalAction.tsx rename to src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx diff --git a/src/views/Pools/components/PoolCard/CardActions/StakeAction.tsx b/src/views/Pools/components/PoolCard/CardActions/StakeAction.tsx new file mode 100644 index 0000000000000..3bfc02658e428 --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardActions/StakeAction.tsx @@ -0,0 +1,75 @@ +import React, { useState, useCallback } from 'react' +import { Flex, Box, Text, Button, AutoRenewIcon, useModal } from '@pancakeswap-libs/uikit' +import BigNumber from 'bignumber.js' +import styled from 'styled-components' +import { Token } from 'config/constants/types' +import { useSousStake } from 'hooks/useStake' +import { useSousApprove } from 'hooks/useApprove' +import useI18n from 'hooks/useI18n' +import { useERC20 } from 'hooks/useContract' +import { useToast } from 'state/hooks' +import { getAddress } from 'utils/addressHelpers' +import DepositModal from '../DepositModal' +import StakeModal from '../StakeModal' + +import ApprovalAction from './ApprovalAction' + +interface StakeActionsProps { + stakingTokenBalance: BigNumber + stakingToken: Token + earningToken: Token + stakedBalance: BigNumber + stakingLimit?: number + sousId: number + isBnbPool: boolean +} + +const InlineText = styled(Text)` + display: inline; +` + +const StakeAction: React.FC = ({ + stakingTokenBalance, + stakingToken, + earningToken, + stakedBalance, + stakingLimit, + sousId, + isBnbPool, +}) => { + const TranslateString = useI18n() + const { onStake } = useSousStake(sousId, isBnbPool) + const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) + + const [onPresentDeposit] = useModal( + , + ) + + const [onPresentStake] = useModal( + , + ) + + return ( + + {stakedBalance.toNumber() === 0 ? ( + + ) : ( + + )} + + ) +} + +export default StakeAction diff --git a/src/views/Pools/components/PoolCard/CardActions/StakingActions/StakeAction.tsx b/src/views/Pools/components/PoolCard/CardActions/StakingActions/StakeAction.tsx deleted file mode 100644 index e3dbd9fd61d83..0000000000000 --- a/src/views/Pools/components/PoolCard/CardActions/StakingActions/StakeAction.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useState, useCallback } from 'react' -import { Flex, Box, Text, Button, AutoRenewIcon } from '@pancakeswap-libs/uikit' -import BigNumber from 'bignumber.js' -import styled from 'styled-components' -import { useSousApprove } from 'hooks/useApprove' -import useI18n from 'hooks/useI18n' -import { useERC20 } from 'hooks/useContract' -import { useToast } from 'state/hooks' -import { getAddress } from 'utils/addressHelpers' -import { Token } from 'config/constants/types' -import ApprovalAction from './ApprovalAction' - -interface StakeActionsProps { - stakedBalance: BigNumber -} - -const InlineText = styled(Text)` - display: inline; -` - -const StakeAction: React.FC = ({ stakedBalance }) => { - const TranslateString = useI18n() - - return ( - - {stakedBalance.toNumber() === 0 ? ( - - ) : ( - - )} - - ) -} - -export default StakeAction diff --git a/src/views/Pools/components/PoolCard/CardActions/StakingActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/StakingActions/index.tsx deleted file mode 100644 index c97cb8da96687..0000000000000 --- a/src/views/Pools/components/PoolCard/CardActions/StakingActions/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react' -import { Flex, Box, Text } from '@pancakeswap-libs/uikit' -import BigNumber from 'bignumber.js' -import styled from 'styled-components' -import useI18n from 'hooks/useI18n' -import { Token } from 'config/constants/types' -import ApprovalAction from './ApprovalAction' -import StakeAction from './StakeAction' - -interface StakingActionsProps { - stakingToken: Token - stakedBalance: BigNumber - earningTokenSymbol: string - needsApproval: boolean - isOldSyrup: boolean - isFinished: boolean - sousId: number -} - -const InlineText = styled(Text)` - display: inline; -` - -const StakingActions: React.FC = ({ - stakingToken, - stakedBalance, - earningTokenSymbol, - needsApproval, - isOldSyrup, - isFinished, - sousId, -}) => { - const TranslateString = useI18n() - - return ( - - - - {TranslateString(1070, `stake`)} - - - {` ${stakingToken.symbol}`} - - - {needsApproval && !isOldSyrup ? ( - - ) : ( - - )} - - ) -} - -export default StakingActions diff --git a/src/views/Pools/components/PoolCard/CardActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx index 03433e27c3cd7..0017e91003d4e 100644 --- a/src/views/Pools/components/PoolCard/CardActions/index.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -4,19 +4,18 @@ import styled from 'styled-components' import { Button, IconButton, useModal, AddIcon, Flex, Text, Box } from '@pancakeswap-libs/uikit' import Label from 'components/Label' import useI18n from 'hooks/useI18n' -import { useSousStake } from 'hooks/useStake' import { useSousUnstake } from 'hooks/useUnstake' import { getBalanceNumber } from 'utils/formatBalance' - import { useSousHarvest } from 'hooks/useHarvest' +import { useSousStake } from 'hooks/useStake' import Balance from 'components/Balance' import { PoolCategory } from 'config/constants/types' import { Pool } from 'state/types' -import DepositModal from '../DepositModal' import WithdrawModal from '../WithdrawModal' import CompoundModal from '../CompoundModal' import HarvestButton from '../HarvestButton' -import StakingActions from './StakingActions' +import ApprovalAction from './ApprovalAction' +import StakeAction from './StakeAction' const BalanceAndCompound = styled.div` display: flex; @@ -25,28 +24,15 @@ const BalanceAndCompound = styled.div` flex-direction: row; ` -const StyledActionSpacer = styled.div` - height: ${(props) => props.theme.spacing[4]}px; - width: ${(props) => props.theme.spacing[4]}px; -` - -const StyledDetails = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; -` - const InlineText = styled(Text)` display: inline; ` const CardActions: React.FC<{ pool: Pool - isOldSyrup: boolean stakedBalance: BigNumber accountHasStakedBalance: boolean -}> = ({ pool, isOldSyrup, stakedBalance, accountHasStakedBalance }) => { +}> = ({ pool, stakedBalance, accountHasStakedBalance }) => { const { sousId, stakingToken, earningToken, harvest, poolCategory, isFinished, userData, stakingLimit } = pool // Pools using native BNB behave differently than pools using a token @@ -55,25 +41,15 @@ const CardActions: React.FC<{ const TranslateString = useI18n() const [pendingTx, setPendingTx] = useState(false) - const { onStake } = useSousStake(sousId, isBnbPool) const { onUnstake } = useSousUnstake(sousId) const { onReward } = useSousHarvest(sousId, isBnbPool) + const { onStake } = useSousStake(sousId, isBnbPool) const allowance = new BigNumber(userData?.allowance || 0) const stakingTokenBalance = new BigNumber(userData?.stakingTokenBalance || 0) const earnings = new BigNumber(userData?.pendingReward || 0) const needsApproval = !accountHasStakedBalance && !allowance.toNumber() && !isBnbPool - const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) - const [onPresentDeposit] = useModal( - , - ) - const [onPresentCompound] = useModal( , ) @@ -96,24 +72,8 @@ const CardActions: React.FC<{ {TranslateString(1072, `earned`)} - {!isOldSyrup ? ( - // IS OLD SYRUP CONDITIONAL - - - {sousId === 0 && harvest && ( - - )} - - ) : ( - Is old syrup - // - )} - {harvest && !isOldSyrup && ( - // IS OLD SYRUP CONDITIONAL + + {harvest && ( )} - - + + + + {TranslateString(1070, `stake`)} + + + {` ${stakingToken.symbol}`} + + + {needsApproval ? ( + + ) : ( + + )} + {/* {needsApproval && !isOldSyrup ? ( // IS OLD SYRUP CONDITIONAL diff --git a/src/views/Pools/components/PoolCard/ConfirmButton.tsx b/src/views/Pools/components/PoolCard/ConfirmButton.tsx new file mode 100644 index 0000000000000..e53414311c1c3 --- /dev/null +++ b/src/views/Pools/components/PoolCard/ConfirmButton.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import { Button, ButtonProps, RefreshIcon, CheckmarkIcon } from '@pancakeswap-libs/uikit' + +interface ConfirmButtonProps extends ButtonProps { + isConfirmed?: boolean +} + +const ConfirmButton: React.FC = ({ + isLoading, + isConfirmed, + children, + endIcon, + disabled, + ...props +}) => { + const handleRenderIcon = () => { + if (endIcon) { + return endIcon + } + + if (isLoading) { + return + } + + if (isConfirmed) { + return + } + + return null + } + + return ( + + ) +} + +export default ConfirmButton diff --git a/src/views/Pools/components/PoolCard/StakeModal.tsx b/src/views/Pools/components/PoolCard/StakeModal.tsx new file mode 100644 index 0000000000000..aaf482a252c84 --- /dev/null +++ b/src/views/Pools/components/PoolCard/StakeModal.tsx @@ -0,0 +1,163 @@ +import React, { useState } from 'react' +import styled from 'styled-components' +import { Modal, Text, Flex, Image, Button, Slider, BalanceInput, AutoRenewIcon } from '@pancakeswap-libs/uikit' +import useI18n from 'hooks/useI18n' +import { BASE_EXCHANGE_URL } from 'config' +import { useSousStake } from 'hooks/useStake' +import { useSousUnstake } from 'hooks/useUnstake' +import useTheme from 'hooks/useTheme' +import BigNumber from 'bignumber.js' +import { getFullDisplayBalance } from 'utils/formatBalance' +import { Token } from 'config/constants/types' +import { useGetApiPrice, useToast } from 'state/hooks' +import ConfirmButton from './ConfirmButton' + +interface StakeModalProps { + sousId: number + isBnbPool: boolean + stakingToken: Token + stakingMax: BigNumber + isStaking?: boolean + onDismiss?: () => void +} + +const StyledButton = styled(Button)` + flex-grow: 1; +` + +const StakeModal: React.FC = ({ + sousId, + isBnbPool, + stakingToken, + isStaking = true, + stakingMax, + onDismiss, +}) => { + const TranslateString = useI18n() + const { onStake } = useSousStake(sousId, isBnbPool) + const { onUnstake } = useSousUnstake(sousId) + const { theme } = useTheme() + const { toastSuccess, toastError } = useToast() + + const [pendingTx, setPendingTx] = useState(false) + const [stakeAmount, setStakeAmount] = useState('') + const [percent, setPercent] = useState(0) + + const tokenPrice = useGetApiPrice(stakingToken.symbol) + + const handleStakeInputChange = (event: React.ChangeEvent) => { + const inputValue = event.target.value ? event.target.value : '0' + const convertedInput = new BigNumber(inputValue).multipliedBy(new BigNumber(10).pow(stakingToken.decimals)) + const percentage = Math.floor(convertedInput.dividedBy(stakingMax).multipliedBy(100).toNumber()) + setStakeAmount(inputValue) + setPercent(percentage > 100 ? 100 : percentage) + } + + const handleChangePercent = (sliderPercent: number) => { + const percentageOfStakingMax = stakingMax.dividedBy(100).multipliedBy(sliderPercent) + const amountToStake = getFullDisplayBalance(percentageOfStakingMax, stakingToken.decimals) + setStakeAmount(amountToStake) + setPercent(sliderPercent) + } + + const handleConfirmClick = async () => { + setPendingTx(true) + + if (isStaking) { + try { + await onStake(stakeAmount, stakingToken.decimals) + toastSuccess( + `${TranslateString(1074, 'Staked')}!`, + TranslateString(999, 'Your funds have been staked in the pool!'), + ) + setPendingTx(false) + onDismiss() + } catch (e) { + toastError( + TranslateString(999, 'Canceled'), + TranslateString(999, 'Please try again and confirm the transaction.'), + ) + setPendingTx(false) + } + } else { + try { + await onUnstake(stakeAmount, stakingToken.decimals) + toastSuccess( + `${TranslateString(999, 'Unstaked')}!`, + TranslateString(999, 'Your earnings have also been harvested to your wallet!'), + ) + setPendingTx(false) + onDismiss() + } catch (e) { + toastError( + TranslateString(999, 'Canceled'), + TranslateString(999, 'Please try again and confirm the transaction.'), + ) + setPendingTx(false) + } + } + } + + return ( + + + {isStaking ? TranslateString(316, 'Stake') : TranslateString(588, 'Unstake')}: + + {stakingToken.symbol} + + {stakingToken.symbol} + + + + + + Balance: {getFullDisplayBalance(stakingMax, stakingToken.decimals)} + + + + handleChangePercent(25)}> + 25% + + handleChangePercent(50)}> + 50% + + handleChangePercent(75)}> + 75% + + handleChangePercent(100)}> + MAX + + + : null} + onClick={handleConfirmClick} + mt="24px" + > + {TranslateString(464, 'Confirm')} + + {isStaking && ( + + )} + + ) +} + +export default StakeModal diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index deb8fbace920a..c6cdc2aea72db 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -29,8 +29,6 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { const TranslateString = useI18n() const stakedBalance = new BigNumber(userData?.stakedBalance || 0) const accountHasStakedBalance = stakedBalance?.toNumber() > 0 - // IS OLD SYRUP CONDITION - const isOldSyrup = stakingToken.symbol === tokens.syrup.symbol return ( @@ -42,7 +40,6 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { = ({ pool }) => { /> {account ? ( - + ) : ( <> diff --git a/src/views/Pools/index.tsx b/src/views/Pools/index.tsx index 020e6801d65b1..a30ea7ca292db 100644 --- a/src/views/Pools/index.tsx +++ b/src/views/Pools/index.tsx @@ -59,10 +59,10 @@ const Pools: React.FC = () => { as="a" href="https://docs.pancakeswap.finance/syrup-pools/syrup-pool" > - + {TranslateString(999, 'Help')} - + From 41a6b0deac3f7f2af74df64c4b3256c4cd28fe01 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Wed, 21 Apr 2021 17:21:19 +0100 Subject: [PATCH 16/40] feat(syrup-pool-v2): Styling of cards and improvements to error reporting for users --- src/utils/formatBalance.ts | 4 ++-- .../components/PoolCard/CardActions/ApprovalAction.tsx | 7 +++++++ src/views/Pools/components/PoolCard/StakeModal.tsx | 2 +- src/views/Pools/components/PoolCard/StyledCard.tsx | 8 +++++++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/utils/formatBalance.ts b/src/utils/formatBalance.ts index ea8834fc646f0..d59f8d18b8380 100644 --- a/src/utils/formatBalance.ts +++ b/src/utils/formatBalance.ts @@ -5,8 +5,8 @@ export const getBalanceNumber = (balance: BigNumber, decimals = 18) => { return displayBalance.toNumber() } -export const getFullDisplayBalance = (balance: BigNumber, decimals = 18) => { - return balance.dividedBy(new BigNumber(10).pow(decimals)).toFixed(decimals) +export const getFullDisplayBalance = (balance: BigNumber, decimals = 18, decimalsToAppear = null) => { + return balance.dividedBy(new BigNumber(10).pow(decimals)).toFixed(decimalsToAppear) } export const formatNumber = (number: number, minPrecision = 2, maxPrecision = 2) => { diff --git a/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx b/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx index d4a67f767ce87..d3a72b7e96629 100644 --- a/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx @@ -33,6 +33,13 @@ const ApprovalAction: React.FC = ({ stakingToken, earningTo setRequestedApproval(false) } else { // user rejected tx or didn't go thru + toastError( + `${TranslateString(999, 'Error')}`, + `${TranslateString( + 999, + `Please try again. Confirm the transaction and make sure you are paying enough gas!`, + )}`, + ) setRequestedApproval(false) } } catch (e) { diff --git a/src/views/Pools/components/PoolCard/StakeModal.tsx b/src/views/Pools/components/PoolCard/StakeModal.tsx index aaf482a252c84..1590be049853b 100644 --- a/src/views/Pools/components/PoolCard/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/StakeModal.tsx @@ -149,7 +149,7 @@ const StakeModal: React.FC = ({ onClick={handleConfirmClick} mt="24px" > - {TranslateString(464, 'Confirm')} + {pendingTx ? TranslateString(464, 'Confirming') : TranslateString(464, 'Confirm')} {isStaking && ( + {isStaked ? ( + + + {formattedBalance} + {`~${stakingMaxDollarValue || 0} USD`} + + + + + + + + + + ) : ( - + )} ) diff --git a/src/views/Pools/components/PoolCard/CardActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx index bf6dc46c35223..af2cf1e13e3a2 100644 --- a/src/views/Pools/components/PoolCard/CardActions/index.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -50,6 +50,7 @@ const CardActions: React.FC<{ const stakingTokenBalance = new BigNumber(userData?.stakingTokenBalance || 0) const earnings = new BigNumber(userData?.pendingReward || 0) const needsApproval = !accountHasStakedBalance && !allowance.toNumber() && !isBnbPool + const isStaked = stakedBalance.toNumber() > 0 const [onPresentCompound] = useModal( , @@ -88,11 +89,12 @@ const CardActions: React.FC<{ - - {TranslateString(1070, `stake`)} + + {isStaked ? stakingToken.symbol : TranslateString(1070, `stake`)} - - {` ${stakingToken.symbol}`} + + {' '} + {isStaked ? TranslateString(999, `staked`) : stakingToken.symbol} {needsApproval ? ( @@ -112,6 +114,7 @@ const CardActions: React.FC<{ stakingLimit={stakingLimit} sousId={sousId} isBnbPool={isBnbPool} + isStaked={isStaked} /> )} diff --git a/src/views/Pools/components/PoolCard/StakeModal.tsx b/src/views/Pools/components/PoolCard/StakeModal.tsx index 5edea3269fa32..5ce7061171d3b 100644 --- a/src/views/Pools/components/PoolCard/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/StakeModal.tsx @@ -18,7 +18,7 @@ interface StakeModalProps { stakingToken: Token stakingMax: BigNumber stakingTokenPrice: number - isStaking?: boolean + isRemovingStake?: boolean onDismiss?: () => void } @@ -30,7 +30,7 @@ const StakeModal: React.FC = ({ sousId, isBnbPool, stakingToken, - isStaking = true, + isRemovingStake = false, stakingMax, stakingTokenPrice, onDismiss, @@ -65,12 +65,12 @@ const StakeModal: React.FC = ({ const handleConfirmClick = async () => { setPendingTx(true) - if (isStaking) { + if (isRemovingStake) { try { - await onStake(stakeAmount, stakingToken.decimals) + await onUnstake(stakeAmount, stakingToken.decimals) toastSuccess( - `${TranslateString(1074, 'Staked')}!`, - TranslateString(999, 'Your funds have been staked in the pool!'), + `${TranslateString(999, 'Unstaked')}!`, + TranslateString(999, 'Your earnings have also been harvested to your wallet!'), ) setPendingTx(false) onDismiss() @@ -83,10 +83,10 @@ const StakeModal: React.FC = ({ } } else { try { - await onUnstake(stakeAmount, stakingToken.decimals) + await onStake(stakeAmount, stakingToken.decimals) toastSuccess( - `${TranslateString(999, 'Unstaked')}!`, - TranslateString(999, 'Your earnings have also been harvested to your wallet!'), + `${TranslateString(1074, 'Staked')}!`, + TranslateString(999, 'Your funds have been staked in the pool!'), ) setPendingTx(false) onDismiss() @@ -102,11 +102,11 @@ const StakeModal: React.FC = ({ return ( - {isStaking ? TranslateString(316, 'Stake') : TranslateString(588, 'Unstake')}: + {isRemovingStake ? TranslateString(588, 'Unstake') : TranslateString(316, 'Stake')}: {stakingToken.symbol} @@ -117,7 +117,7 @@ const StakeModal: React.FC = ({ Balance: {getFullDisplayBalance(stakingMax, stakingToken.decimals)} @@ -153,7 +153,7 @@ const StakeModal: React.FC = ({ > {pendingTx ? TranslateString(464, 'Confirming') : TranslateString(464, 'Confirm')} - {isStaking && ( + {!isRemovingStake && ( From fcbb1e4f0b543ce3986990eb9328b3b1530e4850 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Thu, 22 Apr 2021 15:05:48 +0100 Subject: [PATCH 19/40] feat(syrup-pool-v2): Refactor footer for performance and add HarvestActions --- .../PoolCard/CardActions/HarvestActions.tsx | 82 ++++++++- .../{StakeAction.tsx => StakeActions.tsx} | 19 +- .../components/PoolCard/CardActions/index.tsx | 16 +- .../Pools/components/PoolCard/CardFooter.tsx | 167 ------------------ .../PoolCard/CardFooter/ExpandedFooter.tsx | 139 +++++++++++++++ .../components/PoolCard/CardFooter/index.tsx | 71 ++++++++ .../PoolCard/NotEnoughTokensModal.tsx | 43 +++++ .../Pools/components/PoolCard/StakeModal.tsx | 10 +- src/views/Pools/components/PoolCard/index.tsx | 2 +- 9 files changed, 366 insertions(+), 183 deletions(-) rename src/views/Pools/components/PoolCard/CardActions/{StakeAction.tsx => StakeActions.tsx} (74%) delete mode 100644 src/views/Pools/components/PoolCard/CardFooter.tsx create mode 100644 src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx create mode 100644 src/views/Pools/components/PoolCard/CardFooter/index.tsx create mode 100644 src/views/Pools/components/PoolCard/NotEnoughTokensModal.tsx diff --git a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx index 4a654695d76ca..641631468f905 100644 --- a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx @@ -1,7 +1,85 @@ import React from 'react' +import { Flex, Text, Button, IconButton, AddIcon, MinusIcon, Heading, useModal } from '@pancakeswap-libs/uikit' +import BigNumber from 'bignumber.js' +import { Token } from 'config/constants/types' +import useI18n from 'hooks/useI18n' +import { getBalanceNumber, formatNumber } from 'utils/formatBalance' +import NotEnoughTokensModal from '../NotEnoughTokensModal' +import StakeModal from '../StakeModal' -const HarvestActions = () => { - return
ss
+interface HarvestActionsProps { + stakingTokenBalance: BigNumber + stakingTokenPrice: number + stakingToken: Token + earningToken: Token + stakedBalance: BigNumber + stakingLimit?: number + sousId: number + isBnbPool: boolean + isStaked: ConstrainBoolean +} + +const HarvestActions: React.FC = ({ + stakingTokenBalance, + stakingTokenPrice, + stakingToken, + earningToken, + stakedBalance, + stakingLimit, + sousId, + isBnbPool, + isStaked, +}) => { + const TranslateString = useI18n() + const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) + const stakingMax = + stakingLimit && stakingTokenBalance.isGreaterThan(convertedLimit) ? convertedLimit : stakingTokenBalance + const formattedBalance = formatNumber(getBalanceNumber(stakedBalance, stakingToken.decimals), 3, 3) + const stakingMaxDollarValue = formatNumber( + getBalanceNumber(stakedBalance.multipliedBy(stakingTokenPrice), stakingToken.decimals), + ) + + const [onPresentTokenRequired] = useModal() + + const [onPresentStake] = useModal( + , + ) + + const [onPresentUnstake] = useModal( + , + ) + + return ( + + + + {formattedBalance} + {`~${stakingMaxDollarValue || 0} USD`} + + + + + + + + + + + + ) } export default HarvestActions diff --git a/src/views/Pools/components/PoolCard/CardActions/StakeAction.tsx b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx similarity index 74% rename from src/views/Pools/components/PoolCard/CardActions/StakeAction.tsx rename to src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx index 2b3e3c5d218ce..018986e1a81bc 100644 --- a/src/views/Pools/components/PoolCard/CardActions/StakeAction.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx @@ -1,9 +1,10 @@ import React from 'react' -import { Flex, Box, Text, Button, IconButton, AddIcon, MinusIcon, Heading, useModal } from '@pancakeswap-libs/uikit' +import { Flex, Text, Button, IconButton, AddIcon, MinusIcon, Heading, useModal } from '@pancakeswap-libs/uikit' import BigNumber from 'bignumber.js' import { Token } from 'config/constants/types' import useI18n from 'hooks/useI18n' import { getBalanceNumber, formatNumber } from 'utils/formatBalance' +import NotEnoughTokensModal from '../NotEnoughTokensModal' import StakeModal from '../StakeModal' interface StakeActionsProps { @@ -18,7 +19,7 @@ interface StakeActionsProps { isStaked: ConstrainBoolean } -const StakeAction: React.FC = ({ +const StakeActions: React.FC = ({ stakingTokenBalance, stakingTokenPrice, stakingToken, @@ -33,11 +34,13 @@ const StakeAction: React.FC = ({ const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) const stakingMax = stakingLimit && stakingTokenBalance.isGreaterThan(convertedLimit) ? convertedLimit : stakingTokenBalance - const formattedBalance = formatNumber(getBalanceNumber(stakingMax, stakingToken.decimals), 3, 3) + const formattedBalance = formatNumber(getBalanceNumber(stakedBalance, stakingToken.decimals), 3, 3) const stakingMaxDollarValue = formatNumber( - getBalanceNumber(stakingMax.multipliedBy(stakingTokenPrice), stakingToken.decimals), + getBalanceNumber(stakedBalance.multipliedBy(stakingTokenPrice), stakingToken.decimals), ) + const [onPresentTokenRequired] = useModal() + const [onPresentStake] = useModal( = ({ const [onPresentUnstake] = useModal( = ({
) : ( - + )}
) } -export default StakeAction +export default StakeActions diff --git a/src/views/Pools/components/PoolCard/CardActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx index af2cf1e13e3a2..dbbdecd778f83 100644 --- a/src/views/Pools/components/PoolCard/CardActions/index.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -15,7 +15,8 @@ import WithdrawModal from '../WithdrawModal' import CompoundModal from '../CompoundModal' import HarvestButton from '../HarvestButton' import ApprovalAction from './ApprovalAction' -import StakeAction from './StakeAction' +import StakeActions from './StakeActions' +import HarvestActions from './HarvestActions' const BalanceAndCompound = styled.div` display: flex; @@ -97,6 +98,17 @@ const CardActions: React.FC<{ {isStaked ? TranslateString(999, `staked`) : stakingToken.symbol} + {needsApproval ? ( ) : ( - = ({ - projectLink, - decimals, - tokenAddress, - totalStaked, - tokenName, - tokenDecimals, - isFinished, - startBlock, - endBlock, - stakingTokenSymbol, - contractAddress, -}) => { - const TranslateString = useI18n() - const [isExpanded, setIsExpanded] = useState(false) - const { account } = useWeb3React() - const { currentBlock } = useBlock() - const imageSrc = `${BASE_URL}/images/tokens/${tokenName.toLowerCase()}.png` - const isMetaMaskInScope = !!(window as WindowChain).ethereum?.isMetaMask - - const shouldShowBlockCountdown = Boolean(startBlock && endBlock) - const blocksUntilStart = Math.max(startBlock - currentBlock, 0) - const blocksRemaining = Math.max(endBlock - currentBlock, 0) - const hasPoolStarted = blocksUntilStart === 0 && blocksRemaining > 0 - const poolContractAddress = contractAddress[process.env.REACT_APP_CHAIN_ID] - - return ( - - - setIsExpanded(!isExpanded)}> - {isExpanded ? TranslateString(1066, 'Hide') : TranslateString(658, 'Details')} - - - {isExpanded && ( - - - {TranslateString(999, 'Total staked:')} - - {totalStaked ? ( - <> - - - {stakingTokenSymbol} - - - ) : ( - - )} - - - {shouldShowBlockCountdown && ( - - - {hasPoolStarted ? TranslateString(410, 'End') : TranslateString(1212, 'Start')}: - - - - - {TranslateString(999, 'blocks')} - - - - - )} - - - {TranslateString(412, 'View Project Site')} - - - {poolContractAddress && ( - - - {TranslateString(412, 'View Contract')} - - - )} - {tokenAddress && ( - - - {TranslateString(412, 'View Pool Info')} - - - )} - {account && isMetaMaskInScope && tokenAddress && ( - - registerToken(tokenAddress, tokenName, tokenDecimals, imageSrc)} - > - Add to Metamask - - - - )} - - )} - - ) -} - -export default React.memo(Footer) diff --git a/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx new file mode 100644 index 0000000000000..3feaa1c8d36a3 --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx @@ -0,0 +1,139 @@ +import React from 'react' +import BigNumber from 'bignumber.js' +import styled from 'styled-components' +import { useWeb3React } from '@web3-react/core' +import { getBalanceNumber } from 'utils/formatBalance' +import useI18n from 'hooks/useI18n' +import { Flex, MetamaskIcon, Text, LinkExternal, TimerIcon, Skeleton } from '@pancakeswap-libs/uikit' +import { BASE_URL } from 'config' +import { Address } from 'config/constants/types' +import { useBlock } from 'state/hooks' +import { registerToken } from 'utils/wallet' +import Balance from 'components/Balance' + +interface FooterProps { + projectLink: string + decimals: number + totalStaked: BigNumber + tokenSymbol: string + tokenAddress: string + tokenDecimals: number + startBlock: number + endBlock: number + isFinished: boolean + stakingTokenSymbol: string + contractAddress: Address +} + +const ExpandedWrapper = styled(Flex)` + svg { + height: 14px; + width: 14px; + } +` + +const ExpandedFooter: React.FC = ({ + projectLink, + decimals, + tokenAddress, + totalStaked, + tokenSymbol, + tokenDecimals, + isFinished, + startBlock, + endBlock, + stakingTokenSymbol, + contractAddress, +}) => { + const TranslateString = useI18n() + const { account } = useWeb3React() + const imageSrc = `${BASE_URL}/images/tokens/${tokenSymbol.toLowerCase()}.png` + const isMetaMaskInScope = !!(window as WindowChain).ethereum?.isMetaMask + const shouldShowBlockCountdown = Boolean(startBlock && endBlock) + const { currentBlock } = useBlock() + const blocksUntilStart = Math.max(startBlock - currentBlock, 0) + const blocksRemaining = Math.max(endBlock - currentBlock, 0) + const hasPoolStarted = blocksUntilStart === 0 && blocksRemaining > 0 + const poolContractAddress = contractAddress[process.env.REACT_APP_CHAIN_ID] + + return ( + + + {TranslateString(999, 'Total staked:')} + + {totalStaked ? ( + <> + + + {stakingTokenSymbol} + + + ) : ( + + )} + + + {shouldShowBlockCountdown && ( + + {hasPoolStarted ? TranslateString(410, 'End') : TranslateString(1212, 'Start')}: + + + + {TranslateString(999, 'blocks')} + + + + + )} + + + {TranslateString(412, 'View Project Site')} + + + {poolContractAddress && ( + + + {TranslateString(412, 'View Contract')} + + + )} + {tokenAddress && ( + + + {TranslateString(412, 'View Pool Info')} + + + )} + {account && isMetaMaskInScope && tokenAddress && ( + + registerToken(tokenAddress, tokenSymbol, tokenDecimals, imageSrc)} + > + Add to Metamask + + + + )} + + ) +} + +export default React.memo(ExpandedFooter) diff --git a/src/views/Pools/components/PoolCard/CardFooter/index.tsx b/src/views/Pools/components/PoolCard/CardFooter/index.tsx new file mode 100644 index 0000000000000..b01860e1d711a --- /dev/null +++ b/src/views/Pools/components/PoolCard/CardFooter/index.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react' +import BigNumber from 'bignumber.js' +import styled from 'styled-components' +import useI18n from 'hooks/useI18n' +import { Flex, CardFooter, ExpandableLabel } from '@pancakeswap-libs/uikit' +import { Address } from 'config/constants/types' +import ExpandedFooter from './ExpandedFooter' + +interface FooterProps { + projectLink: string + decimals: number + totalStaked: BigNumber + tokenSymbol: string + tokenAddress: string + tokenDecimals: number + startBlock: number + endBlock: number + isFinished: boolean + stakingTokenSymbol: string + contractAddress: Address +} + +const ExpandableButtonWrapper = styled(Flex)` + button { + padding: 0; + } +` + +const Footer: React.FC = ({ + projectLink, + decimals, + tokenAddress, + totalStaked, + tokenSymbol, + tokenDecimals, + isFinished, + startBlock, + endBlock, + stakingTokenSymbol, + contractAddress, +}) => { + const TranslateString = useI18n() + const [isExpanded, setIsExpanded] = useState(false) + + return ( + + + setIsExpanded(!isExpanded)}> + {isExpanded ? TranslateString(1066, 'Hide') : TranslateString(658, 'Details')} + + + {isExpanded && ( + + )} + + ) +} + +export default Footer diff --git a/src/views/Pools/components/PoolCard/NotEnoughTokensModal.tsx b/src/views/Pools/components/PoolCard/NotEnoughTokensModal.tsx new file mode 100644 index 0000000000000..59a0d462c6331 --- /dev/null +++ b/src/views/Pools/components/PoolCard/NotEnoughTokensModal.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import useI18n from 'hooks/useI18n' +import { Modal, Text, Button, OpenNewIcon } from '@pancakeswap-libs/uikit' +import { BASE_EXCHANGE_URL } from 'config' +import useTheme from 'hooks/useTheme' + +interface NotEnoughTokensModalProps { + tokenSymbol: string + onDismiss?: () => void +} + +const NotEnoughTokensModal: React.FC = ({ tokenSymbol, onDismiss }) => { + const TranslateString = useI18n() + const { theme } = useTheme() + + return ( + + + {TranslateString(999, 'Insufficient')} {tokenSymbol} {TranslateString(1120, 'balance')} + + {TranslateString(999, `You’ll need ${tokenSymbol} to stake in this pool!`)} + + {TranslateString(999, `Buy some ${tokenSymbol}, or make sure your ${tokenSymbol} isn’t in another pool or LP.`)} + + + + + + ) +} + +export default NotEnoughTokensModal diff --git a/src/views/Pools/components/PoolCard/StakeModal.tsx b/src/views/Pools/components/PoolCard/StakeModal.tsx index 5ce7061171d3b..4f9a6f7adc8bc 100644 --- a/src/views/Pools/components/PoolCard/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/StakeModal.tsx @@ -30,22 +30,23 @@ const StakeModal: React.FC = ({ sousId, isBnbPool, stakingToken, - isRemovingStake = false, stakingMax, stakingTokenPrice, + isRemovingStake = false, onDismiss, }) => { const TranslateString = useI18n() + const { theme } = useTheme() + const { onStake } = useSousStake(sousId, isBnbPool) const { onUnstake } = useSousUnstake(sousId) - const { theme } = useTheme() const { toastSuccess, toastError } = useToast() const [pendingTx, setPendingTx] = useState(false) const [stakeAmount, setStakeAmount] = useState('') const [percent, setPercent] = useState(0) - const usdValueStaked = formatNumber(parseFloat(stakeAmount) * stakingTokenPrice) + const usdValueStaked = stakeAmount && formatNumber(parseFloat(stakeAmount) * stakingTokenPrice) const handleStakeInputChange = (event: React.ChangeEvent) => { const inputValue = event.target.value ? event.target.value : '0' @@ -104,6 +105,7 @@ const StakeModal: React.FC = ({ {isRemovingStake ? TranslateString(588, 'Unstake') : TranslateString(316, 'Stake')}: @@ -117,7 +119,7 @@ const StakeModal: React.FC = ({ Balance: {getFullDisplayBalance(stakingMax, stakingToken.decimals)} diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index e3159e2c17567..b65d76eb1c58e 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -73,7 +73,7 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { startBlock={startBlock} endBlock={endBlock} isFinished={isFinished} - tokenName={earningToken.symbol} + tokenSymbol={earningToken.symbol} tokenAddress={earningToken.address ? getAddress(earningToken.address) : ''} tokenDecimals={earningToken.decimals} stakingTokenSymbol={stakingToken.symbol} From 43044fa32f2eae25d5a1b6df634076edea6fb704 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Thu, 22 Apr 2021 16:52:32 +0100 Subject: [PATCH 20/40] feat(syrup-pool-v2): Basic harvest modal and functionality added --- .../components/PoolCard/CBCollectModal.tsx | 171 ++++++++++++++++++ .../PoolCard/CardActions/HarvestActions.tsx | 78 +++----- .../PoolCard/CardActions/StakeActions.tsx | 4 +- .../components/PoolCard/CardActions/index.tsx | 90 ++------- .../components/PoolCard/CollectModal.tsx | 83 +++++++++ .../Pools/components/PoolCard/StakeModal.tsx | 2 +- 6 files changed, 300 insertions(+), 128 deletions(-) create mode 100644 src/views/Pools/components/PoolCard/CBCollectModal.tsx create mode 100644 src/views/Pools/components/PoolCard/CollectModal.tsx diff --git a/src/views/Pools/components/PoolCard/CBCollectModal.tsx b/src/views/Pools/components/PoolCard/CBCollectModal.tsx new file mode 100644 index 0000000000000..20d0026ee7c2b --- /dev/null +++ b/src/views/Pools/components/PoolCard/CBCollectModal.tsx @@ -0,0 +1,171 @@ +import React, { useState, useMemo } from 'react' +import useI18n from 'hooks/useI18n' +import BigNumber from 'bignumber.js' +import { Button, Modal, ButtonMenu, ButtonMenuItem, Flex, HelpIcon, Text, RefreshIcon } from '@pancakeswap-libs/uikit' +import { getFullDisplayBalance, getBalanceNumber } from 'utils/formatBalance' +import Balance from 'components/Balance' +import { useSousStake } from 'hooks/useStake' +import { useSousHarvest } from 'hooks/useHarvest' +import useTheme from 'hooks/useTheme' +import { useToast } from 'state/hooks' + +import ConfirmButton from './ConfirmButton' + +interface CollectModalProps { + sousId: number + isBnbPool: boolean + earnings: BigNumber + earningsBusd: number + earningTokenDecimals: number + earningTokenName: string + harvest?: boolean + onDismiss?: () => void +} + +const CollectModal: React.FC = ({ + earnings, + earningsBusd, + earningTokenName, + earningTokenDecimals, + sousId, + isBnbPool, + harvest = false, + onDismiss, +}) => { + const TranslateString = useI18n() + const [pendingTx, setPendingTx] = useState(false) + const [confirmedTx, setConfirmedTx] = useState(false) + + const { onStake } = useSousStake(sousId, isBnbPool) + const { onReward } = useSousHarvest(sousId, isBnbPool) + const { theme } = useTheme() + const { toastSuccess, toastError } = useToast() + + const [showCompound, setShowCompound] = useState(!harvest) + + const fullBalance = useMemo(() => { + return getFullDisplayBalance(earnings, earningTokenDecimals) + }, [earnings, earningTokenDecimals]) + + const handleRenderLabel = () => { + if (showCompound) { + return TranslateString(999, 'Compounding') + } + + return TranslateString(999, 'Harvesting') + } + + const handleRenderActionButtonLabel = () => { + if (pendingTx) { + return TranslateString(999, 'Confirming') + } + + if (confirmedTx) { + return TranslateString(999, 'Confirmed') + } + + return TranslateString(999, 'Confirm') + } + + const handleAction = async () => { + setPendingTx(true) + if (showCompound) { + try { + await onStake(fullBalance, earningTokenDecimals) + toastSuccess( + `${TranslateString(1074, 'Staked')}!`, + TranslateString(999, 'Your funds have been staked in the pool!'), + ) + setPendingTx(false) + setConfirmedTx(true) + onDismiss() + } catch (e) { + toastError( + TranslateString(999, 'Canceled'), + TranslateString(999, 'Please try again and confirm the transaction.'), + ) + setPendingTx(false) + } + } else { + try { + await onReward() + toastSuccess( + `${TranslateString(999, 'Harvested')}!`, + TranslateString(999, 'Your earnings have been sent to your wallet!'), + ) + setPendingTx(false) + setConfirmedTx(true) + onDismiss() + } catch (e) { + toastError( + TranslateString(999, 'Canceled'), + TranslateString(999, 'Please try again and confirm the transaction.'), + ) + setPendingTx(false) + } + } + } + + const handleRenderIcon = () => { + if (pendingTx) { + return + } + + return null + } + + return ( + + {!harvest && ( + + setShowCompound(!index)} + > + {TranslateString(704, 'Compound')} + {TranslateString(562, 'Harvest')} + + + + + + )} + + + {handleRenderLabel()}: + + + + + + {handleRenderActionButtonLabel()} + + + + ) +} + +export default CollectModal diff --git a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx index 641631468f905..7b0b10dcddf77 100644 --- a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx @@ -1,81 +1,55 @@ import React from 'react' -import { Flex, Text, Button, IconButton, AddIcon, MinusIcon, Heading, useModal } from '@pancakeswap-libs/uikit' +import styled from 'styled-components' +import { Flex, Text, Button, Box, MinusIcon, Heading, useModal } from '@pancakeswap-libs/uikit' import BigNumber from 'bignumber.js' import { Token } from 'config/constants/types' +import { getAddress } from 'utils/addressHelpers' import useI18n from 'hooks/useI18n' import { getBalanceNumber, formatNumber } from 'utils/formatBalance' + +import { useGetApiPrice } from 'state/hooks' import NotEnoughTokensModal from '../NotEnoughTokensModal' -import StakeModal from '../StakeModal' +import CollectModal from '../CollectModal' interface HarvestActionsProps { - stakingTokenBalance: BigNumber - stakingTokenPrice: number - stakingToken: Token + earnings: BigNumber earningToken: Token - stakedBalance: BigNumber - stakingLimit?: number sousId: number isBnbPool: boolean - isStaked: ConstrainBoolean } -const HarvestActions: React.FC = ({ - stakingTokenBalance, - stakingTokenPrice, - stakingToken, - earningToken, - stakedBalance, - stakingLimit, - sousId, - isBnbPool, - isStaked, -}) => { +const HarvestActions: React.FC = ({ earnings, earningToken, sousId, isBnbPool }) => { const TranslateString = useI18n() - const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) - const stakingMax = - stakingLimit && stakingTokenBalance.isGreaterThan(convertedLimit) ? convertedLimit : stakingTokenBalance - const formattedBalance = formatNumber(getBalanceNumber(stakedBalance, stakingToken.decimals), 3, 3) - const stakingMaxDollarValue = formatNumber( - getBalanceNumber(stakedBalance.multipliedBy(stakingTokenPrice), stakingToken.decimals), + const earningTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') + const formattedBalance = formatNumber(getBalanceNumber(earnings, earningToken.decimals), 3, 3) + const earningsDollarValue = formatNumber( + getBalanceNumber(earnings.multipliedBy(earningTokenPrice), earningToken.decimals), ) + const hasEarnings = earnings.toNumber() > 0 - const [onPresentTokenRequired] = useModal() - - const [onPresentStake] = useModal( - , - ) - - const [onPresentUnstake] = useModal( - , ) return ( - + - {formattedBalance} - {`~${stakingMaxDollarValue || 0} USD`} + {hasEarnings ? formattedBalance : 0} + {`~${ + hasEarnings ? earningsDollarValue : 0 + } USD`} - - - - - - + diff --git a/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx index 018986e1a81bc..eac543de84c5f 100644 --- a/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx @@ -19,7 +19,7 @@ interface StakeActionsProps { isStaked: ConstrainBoolean } -const StakeActions: React.FC = ({ +const StakeAction: React.FC = ({ stakingTokenBalance, stakingTokenPrice, stakingToken, @@ -88,4 +88,4 @@ const StakeActions: React.FC = ({ ) } -export default StakeActions +export default StakeAction diff --git a/src/views/Pools/components/PoolCard/CardActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx index dbbdecd778f83..97e195c942ce6 100644 --- a/src/views/Pools/components/PoolCard/CardActions/index.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -18,13 +18,6 @@ import ApprovalAction from './ApprovalAction' import StakeActions from './StakeActions' import HarvestActions from './HarvestActions' -const BalanceAndCompound = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - flex-direction: row; -` - const InlineText = styled(Text)` display: inline; ` @@ -67,16 +60,7 @@ const CardActions: React.FC<{ return ( - - - {`${earningToken.symbol} `} - - - {TranslateString(1072, `earned`)} - - - - {harvest && ( + {/* {harvest && ( - )} + )} */} + {harvest && ( + <> + + + {`${earningToken.symbol} `} + + + {TranslateString(1072, `earned`)} + + + + + )} - {isStaked ? stakingToken.symbol : TranslateString(1070, `stake`)} + {isStaked ? stakingToken.symbol : TranslateString(1070, `stake`)}{' '} - {' '} - {isStaked ? TranslateString(999, `staked`) : stakingToken.symbol} + {isStaked ? TranslateString(1074, `staked`) : `${stakingToken.symbol}`} - {needsApproval ? ( )} - {/* - {needsApproval && !isOldSyrup ? ( - // IS OLD SYRUP CONDITIONAL - - ) : ( - <> - - - {!isOldSyrup && ( - // IS OLD SYRUP CONDITIONAL - - - - )} - - )} - - -
{TranslateString(384, 'Your Stake')}:
- -
*/}
) } diff --git a/src/views/Pools/components/PoolCard/CollectModal.tsx b/src/views/Pools/components/PoolCard/CollectModal.tsx new file mode 100644 index 0000000000000..466eaab224978 --- /dev/null +++ b/src/views/Pools/components/PoolCard/CollectModal.tsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react' +import { Modal, Text, Button, OpenNewIcon, Heading, Flex, AutoRenewIcon } from '@pancakeswap-libs/uikit' +import useI18n from 'hooks/useI18n' +import useTheme from 'hooks/useTheme' +import { useSousHarvest } from 'hooks/useHarvest' +import { useToast } from 'state/hooks' + +interface CollectModalProps { + tokenSymbol: string + formattedBalance: string + earningsDollarValue: string + sousId: number + isBnbPool: boolean + onDismiss?: () => void +} + +const CollectModal: React.FC = ({ + tokenSymbol, + formattedBalance, + earningsDollarValue, + sousId, + isBnbPool, + onDismiss, +}) => { + const TranslateString = useI18n() + const { theme } = useTheme() + const { toastSuccess, toastError } = useToast() + const [pendingTx, setPendingTx] = useState(false) + const { onReward } = useSousHarvest(sousId, isBnbPool) + + const handleHarvestConfirm = async () => { + setPendingTx(true) + + try { + await onReward() + toastSuccess( + `${TranslateString(999, 'Harvested')}!`, + TranslateString(999, 'Your earnings have been sent to your wallet!'), + ) + setPendingTx(false) + onDismiss() + } catch (e) { + toastError( + TranslateString(999, 'Canceled'), + TranslateString(999, 'Please try again and confirm the transaction.'), + ) + setPendingTx(false) + } + } + + return ( + + + {TranslateString(999, 'Harvesting')}: + + + {formattedBalance} {tokenSymbol} + + {`~${earningsDollarValue || 0} USD`} + + + + + + + ) +} + +export default CollectModal diff --git a/src/views/Pools/components/PoolCard/StakeModal.tsx b/src/views/Pools/components/PoolCard/StakeModal.tsx index 4f9a6f7adc8bc..2ee64cb4fe83c 100644 --- a/src/views/Pools/components/PoolCard/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/StakeModal.tsx @@ -153,7 +153,7 @@ const StakeModal: React.FC = ({ onClick={handleConfirmClick} mt="24px" > - {pendingTx ? TranslateString(464, 'Confirming') : TranslateString(464, 'Confirm')} + {pendingTx ? TranslateString(802, 'Confirming') : TranslateString(464, 'Confirm')} {!isRemovingStake && ( -
- ) -} - -export default CollectModal diff --git a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx index 7b0b10dcddf77..632b1a727ae2c 100644 --- a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx @@ -26,14 +26,16 @@ const HarvestActions: React.FC = ({ earnings, earningToken, getBalanceNumber(earnings.multipliedBy(earningTokenPrice), earningToken.decimals), ) const hasEarnings = earnings.toNumber() > 0 + const isCompoundPool = sousId === 0 - const [onPresentHarvest] = useModal( + const [onPresentCollect] = useModal( , ) @@ -41,14 +43,14 @@ const HarvestActions: React.FC = ({ earnings, earningToken, - {hasEarnings ? formattedBalance : 0} + {hasEarnings ? formattedBalance : 0} {`~${ hasEarnings ? earningsDollarValue : 0 } USD`} - diff --git a/src/views/Pools/components/PoolCard/CardActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx index 97e195c942ce6..c2fdcc7d5b119 100644 --- a/src/views/Pools/components/PoolCard/CardActions/index.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -80,7 +80,7 @@ const CardActions: React.FC<{ {`${earningToken.symbol} `} - {TranslateString(1072, `earned`)} + {TranslateString(330, `earned`)} diff --git a/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx index 3feaa1c8d36a3..e4117311c5ff4 100644 --- a/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx +++ b/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx @@ -11,7 +11,7 @@ import { useBlock } from 'state/hooks' import { registerToken } from 'utils/wallet' import Balance from 'components/Balance' -interface FooterProps { +interface ExpandedFooterProps { projectLink: string decimals: number totalStaked: BigNumber @@ -23,6 +23,7 @@ interface FooterProps { isFinished: boolean stakingTokenSymbol: string contractAddress: Address + account: string } const ExpandedWrapper = styled(Flex)` @@ -32,7 +33,7 @@ const ExpandedWrapper = styled(Flex)` } ` -const ExpandedFooter: React.FC = ({ +const ExpandedFooter: React.FC = ({ projectLink, decimals, tokenAddress, @@ -44,9 +45,9 @@ const ExpandedFooter: React.FC = ({ endBlock, stakingTokenSymbol, contractAddress, + account, }) => { const TranslateString = useI18n() - const { account } = useWeb3React() const imageSrc = `${BASE_URL}/images/tokens/${tokenSymbol.toLowerCase()}.png` const isMetaMaskInScope = !!(window as WindowChain).ethereum?.isMetaMask const shouldShowBlockCountdown = Boolean(startBlock && endBlock) diff --git a/src/views/Pools/components/PoolCard/CardFooter/index.tsx b/src/views/Pools/components/PoolCard/CardFooter/index.tsx index b01860e1d711a..93cb6ca403a43 100644 --- a/src/views/Pools/components/PoolCard/CardFooter/index.tsx +++ b/src/views/Pools/components/PoolCard/CardFooter/index.tsx @@ -18,6 +18,7 @@ interface FooterProps { isFinished: boolean stakingTokenSymbol: string contractAddress: Address + account: string } const ExpandableButtonWrapper = styled(Flex)` @@ -38,6 +39,7 @@ const Footer: React.FC = ({ endBlock, stakingTokenSymbol, contractAddress, + account, }) => { const TranslateString = useI18n() const [isExpanded, setIsExpanded] = useState(false) @@ -62,6 +64,7 @@ const Footer: React.FC = ({ tokenDecimals={tokenDecimals} stakingTokenSymbol={stakingTokenSymbol} contractAddress={contractAddress} + account={account} /> )} diff --git a/src/views/Pools/components/PoolCard/CollectModal.tsx b/src/views/Pools/components/PoolCard/CollectModal.tsx index 466eaab224978..2e428e70cf71d 100644 --- a/src/views/Pools/components/PoolCard/CollectModal.tsx +++ b/src/views/Pools/components/PoolCard/CollectModal.tsx @@ -11,6 +11,7 @@ interface CollectModalProps { earningsDollarValue: string sousId: number isBnbPool: boolean + isCompoundPool: boolean onDismiss?: () => void } @@ -20,6 +21,7 @@ const CollectModal: React.FC = ({ earningsDollarValue, sousId, isBnbPool, + isCompoundPool, onDismiss, }) => { const TranslateString = useI18n() diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index b65d76eb1c58e..60fca19175d7b 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -1,7 +1,6 @@ import BigNumber from 'bignumber.js' import React from 'react' import { CardBody, Flex, Text } from '@pancakeswap-libs/uikit' -import { useWeb3React } from '@web3-react/core' import UnlockButton from 'components/UnlockButton' import useI18n from 'hooks/useI18n' import { getAddress } from 'utils/addressHelpers' @@ -14,7 +13,7 @@ import CardFooter from './CardFooter' import StyledCardHeader from './StyledCardHeader' import CardActions from './CardActions' -const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { +const PoolCard: React.FC<{ pool: Pool; account: string }> = ({ pool, account }) => { const { sousId, stakingToken, @@ -26,7 +25,6 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { userData, contractAddress, } = pool - const { account } = useWeb3React() const TranslateString = useI18n() const stakedBalance = new BigNumber(userData?.stakedBalance || 0) const accountHasStakedBalance = stakedBalance?.toNumber() > 0 @@ -78,6 +76,7 @@ const PoolCard: React.FC<{ pool: Pool }> = ({ pool }) => { tokenDecimals={earningToken.decimals} stakingTokenSymbol={stakingToken.symbol} contractAddress={contractAddress} + account={account} />
) diff --git a/src/views/Pools/index.tsx b/src/views/Pools/index.tsx index a30ea7ca292db..0bdcbfd38a5c7 100644 --- a/src/views/Pools/index.tsx +++ b/src/views/Pools/index.tsx @@ -72,13 +72,17 @@ const Pools: React.FC = () => { <> {stakedOnly - ? orderBy(stakedOnlyPools, ['sortOrder']).map((pool) => ) - : orderBy(openPools, ['sortOrder']).map((pool) => )} + ? orderBy(stakedOnlyPools, ['sortOrder']).map((pool) => ( + + )) + : orderBy(openPools, ['sortOrder']).map((pool) => ( + + ))} {orderBy(finishedPools, ['sortOrder']).map((pool) => ( - + ))} From 40922fd0ce47419b75d9b572b5404ff81d1b8d18 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Thu, 22 Apr 2021 19:54:51 +0100 Subject: [PATCH 22/40] feat(syrup-pool-v2): Styling and tooltip for compounding modal --- .../components/PoolCard/CollectModal.tsx | 52 +++++++++++++++++-- src/views/Pools/components/PoolCard/index.tsx | 1 - 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/views/Pools/components/PoolCard/CollectModal.tsx b/src/views/Pools/components/PoolCard/CollectModal.tsx index 2e428e70cf71d..719688802f086 100644 --- a/src/views/Pools/components/PoolCard/CollectModal.tsx +++ b/src/views/Pools/components/PoolCard/CollectModal.tsx @@ -1,5 +1,18 @@ import React, { useState } from 'react' -import { Modal, Text, Button, OpenNewIcon, Heading, Flex, AutoRenewIcon } from '@pancakeswap-libs/uikit' +import { + Modal, + Text, + Button, + OpenNewIcon, + Heading, + Flex, + AutoRenewIcon, + ButtonMenu, + ButtonMenuItem, + HelpIcon, + useTooltip, + Box, +} from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' import useTheme from 'hooks/useTheme' import { useSousHarvest } from 'hooks/useHarvest' @@ -11,7 +24,7 @@ interface CollectModalProps { earningsDollarValue: string sousId: number isBnbPool: boolean - isCompoundPool: boolean + isCompoundPool?: boolean onDismiss?: () => void } @@ -21,14 +34,26 @@ const CollectModal: React.FC = ({ earningsDollarValue, sousId, isBnbPool, - isCompoundPool, + isCompoundPool = false, onDismiss, }) => { const TranslateString = useI18n() const { theme } = useTheme() const { toastSuccess, toastError } = useToast() const [pendingTx, setPendingTx] = useState(false) + const [shouldCompound, setShouldCompound] = useState(isCompoundPool) const { onReward } = useSousHarvest(sousId, isBnbPool) + const { targetRef, tooltip, tooltipVisible } = useTooltip( + <> + {TranslateString(999, 'Compound: collect and restake CAKE into pool.')} + {TranslateString(999, 'Harvest: collect CAKE and send to wallet')} + , + 'bottom-end', + 'hover', + undefined, + undefined, + [20, 10], + ) const handleHarvestConfirm = async () => { setPendingTx(true) @@ -56,8 +81,26 @@ const CollectModal: React.FC = ({ onDismiss={onDismiss} headerBackground={theme.colors.gradients.cardHeader} > + {isCompoundPool && ( + + setShouldCompound(!index)} + > + {TranslateString(704, 'Compound')} + {TranslateString(562, 'Harvest')} + + + + + {tooltipVisible && tooltip} + + )} + - {TranslateString(999, 'Harvesting')}: + {shouldCompound ? TranslateString(999, 'Compounding') : TranslateString(999, 'Harvesting')}: {formattedBalance} {tokenSymbol} @@ -73,7 +116,6 @@ const CollectModal: React.FC = ({ endIcon={pendingTx ? : null} > {pendingTx ? TranslateString(802, 'Confirming') : TranslateString(464, 'Confirm')} - - - - - ) -} - -export default CompoundModal - -const BalanceRow = styled.div` - display: flex; - align-items: center; - justify-content: center; - flex-direction: row; -` diff --git a/src/views/Pools/components/PoolCard/StakeModal.tsx b/src/views/Pools/components/PoolCard/StakeModal.tsx index 2ee64cb4fe83c..7f0bb83048835 100644 --- a/src/views/Pools/components/PoolCard/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/StakeModal.tsx @@ -19,6 +19,7 @@ interface StakeModalProps { stakingMax: BigNumber stakingTokenPrice: number isRemovingStake?: boolean + earningTokenSymbol: string onDismiss?: () => void } @@ -33,6 +34,7 @@ const StakeModal: React.FC = ({ stakingMax, stakingTokenPrice, isRemovingStake = false, + earningTokenSymbol, onDismiss, }) => { const TranslateString = useI18n() @@ -71,7 +73,7 @@ const StakeModal: React.FC = ({ await onUnstake(stakeAmount, stakingToken.decimals) toastSuccess( `${TranslateString(999, 'Unstaked')}!`, - TranslateString(999, 'Your earnings have also been harvested to your wallet!'), + TranslateString(999, `Your ${earningTokenSymbol} earnings have also been harvested to your wallet!`), ) setPendingTx(false) onDismiss() @@ -87,7 +89,7 @@ const StakeModal: React.FC = ({ await onStake(stakeAmount, stakingToken.decimals) toastSuccess( `${TranslateString(1074, 'Staked')}!`, - TranslateString(999, 'Your funds have been staked in the pool!'), + TranslateString(999, `Your ${stakingToken.symbol} funds have been staked in the pool!`), ) setPendingTx(false) onDismiss() From db54948314339c9bf4cecd8f8151ff7de98d3a73 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Thu, 22 Apr 2021 21:19:54 +0100 Subject: [PATCH 24/40] feat(syrup-pool-v2): Style for promoted pool (& cake) and organising files --- src/views/Pools/components/CardContent.tsx | 13 ---- .../PoolCard/CardActions/HarvestActions.tsx | 2 +- .../PoolCard/CardActions/StakeActions.tsx | 4 +- .../PoolCard/CardFooter/ExpandedFooter.tsx | 5 +- .../components/PoolCard/DepositModal.tsx | 72 ------------------- .../PoolCard/{ => Modals}/CollectModal.tsx | 0 .../{ => Modals}/NotEnoughTokensModal.tsx | 0 .../PoolCard/{ => Modals}/StakeModal.tsx | 2 +- .../components/PoolCard/StyledCardHeader.tsx | 10 +-- .../components/PoolCard/WithdrawModal.tsx | 71 ------------------ 10 files changed, 12 insertions(+), 167 deletions(-) delete mode 100644 src/views/Pools/components/CardContent.tsx delete mode 100644 src/views/Pools/components/PoolCard/DepositModal.tsx rename src/views/Pools/components/PoolCard/{ => Modals}/CollectModal.tsx (100%) rename src/views/Pools/components/PoolCard/{ => Modals}/NotEnoughTokensModal.tsx (100%) rename src/views/Pools/components/PoolCard/{ => Modals}/StakeModal.tsx (99%) delete mode 100644 src/views/Pools/components/PoolCard/WithdrawModal.tsx diff --git a/src/views/Pools/components/CardContent.tsx b/src/views/Pools/components/CardContent.tsx deleted file mode 100644 index 92bbf6ea5ccd1..0000000000000 --- a/src/views/Pools/components/CardContent.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import styled from 'styled-components' - -const CardContent: React.FC = ({ children }) => {children} - -const StyledCardContent = styled.div` - display: flex; - flex: 1; - flex-direction: column; - padding: 0; -` - -export default CardContent diff --git a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx index 2557593e09c25..43dd087b2cc41 100644 --- a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx @@ -6,7 +6,7 @@ import { getAddress } from 'utils/addressHelpers' import useI18n from 'hooks/useI18n' import { getFullDisplayBalance, getBalanceNumber, formatNumber } from 'utils/formatBalance' import { useGetApiPrice } from 'state/hooks' -import CollectModal from '../CollectModal' +import CollectModal from '../Modals/CollectModal' interface HarvestActionsProps { earnings: BigNumber diff --git a/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx index 5a54c215da39a..978a074228836 100644 --- a/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx @@ -4,8 +4,8 @@ import BigNumber from 'bignumber.js' import { Token } from 'config/constants/types' import useI18n from 'hooks/useI18n' import { getBalanceNumber, formatNumber } from 'utils/formatBalance' -import NotEnoughTokensModal from '../NotEnoughTokensModal' -import StakeModal from '../StakeModal' +import NotEnoughTokensModal from '../Modals/NotEnoughTokensModal' +import StakeModal from '../Modals/StakeModal' interface StakeActionsProps { stakingTokenBalance: BigNumber diff --git a/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx index 56f6c1bbdb5c9..8435f16bd7a7c 100644 --- a/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx +++ b/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx @@ -49,7 +49,7 @@ const ExpandedFooter: React.FC = ({ const TranslateString = useI18n() const imageSrc = `${BASE_URL}/images/tokens/${tokenSymbol.toLowerCase()}.png` const isMetaMaskInScope = !!(window as WindowChain).ethereum?.isMetaMask - const shouldShowBlockCountdown = Boolean(startBlock && endBlock) + const shouldShowBlockCountdown = Boolean(!isFinished && startBlock && endBlock) const { currentBlock } = useBlock() const blocksUntilStart = Math.max(startBlock - currentBlock, 0) const blocksRemaining = Math.max(endBlock - currentBlock, 0) @@ -63,7 +63,7 @@ const ExpandedFooter: React.FC = ({ {totalStaked ? ( <> - + {stakingTokenSymbol} @@ -80,7 +80,6 @@ const ExpandedFooter: React.FC = ({ diff --git a/src/views/Pools/components/PoolCard/DepositModal.tsx b/src/views/Pools/components/PoolCard/DepositModal.tsx deleted file mode 100644 index 78c37ea7ad5f2..0000000000000 --- a/src/views/Pools/components/PoolCard/DepositModal.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import BigNumber from 'bignumber.js' -import React, { useCallback, useMemo, useState } from 'react' -import { Button, Modal } from '@pancakeswap-libs/uikit' -import ModalActions from 'components/ModalActions' -import TokenInput from 'components/TokenInput' -import useI18n from 'hooks/useI18n' -import { getFullDisplayBalance } from 'utils/formatBalance' - -interface DepositModalProps { - max: BigNumber - onConfirm: (amount: string, decimals: number) => void - onDismiss?: () => void - tokenName?: string - stakingTokenDecimals?: number -} - -const DepositModal: React.FC = ({ - max, - onConfirm, - onDismiss, - tokenName = '', - stakingTokenDecimals = 18, -}) => { - const [val, setVal] = useState('') - const [pendingTx, setPendingTx] = useState(false) - const TranslateString = useI18n() - const fullBalance = useMemo(() => { - return getFullDisplayBalance(max, stakingTokenDecimals) - }, [max, stakingTokenDecimals]) - - const handleChange = useCallback( - (e: React.FormEvent) => { - setVal(e.currentTarget.value) - }, - [setVal], - ) - - const handleSelectMax = useCallback(() => { - setVal(fullBalance) - }, [fullBalance, setVal]) - - return ( - - - - - - - - ) -} - -export default DepositModal diff --git a/src/views/Pools/components/PoolCard/CollectModal.tsx b/src/views/Pools/components/PoolCard/Modals/CollectModal.tsx similarity index 100% rename from src/views/Pools/components/PoolCard/CollectModal.tsx rename to src/views/Pools/components/PoolCard/Modals/CollectModal.tsx diff --git a/src/views/Pools/components/PoolCard/NotEnoughTokensModal.tsx b/src/views/Pools/components/PoolCard/Modals/NotEnoughTokensModal.tsx similarity index 100% rename from src/views/Pools/components/PoolCard/NotEnoughTokensModal.tsx rename to src/views/Pools/components/PoolCard/Modals/NotEnoughTokensModal.tsx diff --git a/src/views/Pools/components/PoolCard/StakeModal.tsx b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx similarity index 99% rename from src/views/Pools/components/PoolCard/StakeModal.tsx rename to src/views/Pools/components/PoolCard/Modals/StakeModal.tsx index 7f0bb83048835..de8d9490c090e 100644 --- a/src/views/Pools/components/PoolCard/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx @@ -10,7 +10,7 @@ import BigNumber from 'bignumber.js' import { getFullDisplayBalance, formatNumber } from 'utils/formatBalance' import { Token } from 'config/constants/types' import { useToast } from 'state/hooks' -import ConfirmButton from './ConfirmButton' +import ConfirmButton from '../ConfirmButton' interface StakeModalProps { sousId: number diff --git a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx index 97192583c0ffc..eb0e712efc512 100644 --- a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx +++ b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx @@ -3,9 +3,9 @@ import { CardHeader, Heading, Text, Flex, Image, Box } from '@pancakeswap-libs/u import styled from 'styled-components' import PoolFinishedSash from './PoolFinishedSash' -const Wrapper = styled(CardHeader)<{ isFinished?: boolean }>` - background: ${({ isFinished, theme }) => - isFinished ? theme.colors.backgroundDisabled : theme.card.cardHeaderBackground.default}; +const Wrapper = styled(CardHeader)<{ isFinished?: boolean; activeBackground?: string }>` + background: ${({ isFinished, activeBackground, theme }) => + isFinished ? theme.colors.backgroundDisabled : theme.colors.gradients[activeBackground]}; ` const StyledCardHeader: React.FC<{ @@ -14,10 +14,12 @@ const StyledCardHeader: React.FC<{ isFinished?: boolean }> = ({ earningTokenSymbol, stakingTokenSymbol, isFinished = false }) => { const poolImageSrc = `${earningTokenSymbol}-${stakingTokenSymbol}.svg`.toLocaleLowerCase() + const isPromoted = earningTokenSymbol === 'CAKE' + const activeBackground = isPromoted ? 'bubblegum' : 'cardHeader' return ( <> - + diff --git a/src/views/Pools/components/PoolCard/WithdrawModal.tsx b/src/views/Pools/components/PoolCard/WithdrawModal.tsx deleted file mode 100644 index 85ab026373963..0000000000000 --- a/src/views/Pools/components/PoolCard/WithdrawModal.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import BigNumber from 'bignumber.js' -import React, { useCallback, useMemo, useState } from 'react' -import { Button, Modal } from '@pancakeswap-libs/uikit' -import ModalActions from 'components/ModalActions' -import TokenInput from 'components/TokenInput' -import useI18n from 'hooks/useI18n' -import { getFullDisplayBalance } from 'utils/formatBalance' - -interface WithdrawModalProps { - max: BigNumber - onConfirm: (amount: string, decimals: number) => void - onDismiss?: () => void - tokenName?: string - stakingTokenDecimals?: number -} - -const WithdrawModal: React.FC = ({ - onConfirm, - onDismiss, - max, - tokenName = '', - stakingTokenDecimals = 18, -}) => { - const [val, setVal] = useState('') - const [pendingTx, setPendingTx] = useState(false) - const TranslateString = useI18n() - const fullBalance = useMemo(() => { - return getFullDisplayBalance(max, stakingTokenDecimals) - }, [max, stakingTokenDecimals]) - - const handleChange = useCallback( - (e: React.FormEvent) => { - setVal(e.currentTarget.value) - }, - [setVal], - ) - - const handleSelectMax = useCallback(() => { - setVal(fullBalance) - }, [fullBalance, setVal]) - - return ( - - - - - - - - ) -} - -export default WithdrawModal From 1d918dcb926b6b81f097cbfd4eb054073cd298b9 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Fri, 23 Apr 2021 14:40:55 +0100 Subject: [PATCH 25/40] feat(syrup-pool-v2): Massive proptypes tidy up --- src/utils/formatBalance.ts | 2 +- .../Pools/components/PoolCard/AprRow.tsx | 20 ++--- .../PoolCard/CardActions/ApprovalAction.tsx | 14 ++-- .../PoolCard/CardActions/StakeActions.tsx | 26 ++----- .../components/PoolCard/CardActions/index.tsx | 14 +--- .../PoolCard/CardFooter/ExpandedFooter.tsx | 77 ++++++------------- .../components/PoolCard/CardFooter/index.tsx | 49 ++---------- .../components/PoolCard/Modals/StakeModal.tsx | 15 ++-- src/views/Pools/components/PoolCard/index.tsx | 36 +-------- 9 files changed, 61 insertions(+), 192 deletions(-) diff --git a/src/utils/formatBalance.ts b/src/utils/formatBalance.ts index ef819b115d10c..e918e84830f75 100644 --- a/src/utils/formatBalance.ts +++ b/src/utils/formatBalance.ts @@ -5,7 +5,7 @@ export const getBalanceNumber = (balance: BigNumber, decimals = 18) => { return displayBalance.toNumber() } -export const getFullDisplayBalance = (balance: BigNumber, decimals = 18, decimalsToAppear = undefined) => { +export const getFullDisplayBalance = (balance: BigNumber, decimals = 18, decimalsToAppear?: number) => { return balance.dividedBy(new BigNumber(10).pow(decimals)).toFixed(decimalsToAppear) } diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index f1d6121bcb1e8..d3e490fc740be 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -1,32 +1,22 @@ import React from 'react' -import BigNumber from 'bignumber.js' import { Flex, Text, IconButton, useModal, CalculateIcon, Skeleton } from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' -import { Token } from 'config/constants/types' import { getBalanceNumber } from 'utils/formatBalance' import { getPoolApr } from 'utils/apr' import { getAddress } from 'utils/addressHelpers' import { useGetApiPrice } from 'state/hooks' import Balance from 'components/Balance' import ApyCalculatorModal from 'components/ApyCalculatorModal' +import { Pool } from 'state/types' interface AprRowProps { - isFinished: boolean - stakingToken: Token - earningToken: Token - totalStaked: BigNumber - tokenPerBlock: string + pool: Pool stakingTokenPrice: number } -const AprRow: React.FC = ({ - isFinished, - stakingToken, - earningToken, - totalStaked, - tokenPerBlock, - stakingTokenPrice, -}) => { +const AprRow: React.FC = ({ pool, stakingTokenPrice }) => { + const { stakingToken, earningToken, totalStaked, isFinished, tokenPerBlock } = pool + const rewardTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') const apr = getPoolApr( stakingTokenPrice, diff --git a/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx b/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx index d3a72b7e96629..8d82479ae7db8 100644 --- a/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/ApprovalAction.tsx @@ -5,16 +5,14 @@ import useI18n from 'hooks/useI18n' import { useERC20 } from 'hooks/useContract' import { useToast } from 'state/hooks' import { getAddress } from 'utils/addressHelpers' -import { Token } from 'config/constants/types' +import { Pool } from 'state/types' interface ApprovalActionProps { - stakingToken: Token - earningTokenSymbol: string - isFinished: boolean - sousId: number + pool: Pool } -const ApprovalAction: React.FC = ({ stakingToken, earningTokenSymbol, isFinished, sousId }) => { +const ApprovalAction: React.FC = ({ pool }) => { + const { sousId, stakingToken, earningToken, isFinished } = pool const TranslateString = useI18n() const stakingTokenContract = useERC20(stakingToken.address ? getAddress(stakingToken.address) : '') const [requestedApproval, setRequestedApproval] = useState(false) @@ -28,7 +26,7 @@ const ApprovalAction: React.FC = ({ stakingToken, earningTo if (txHash) { toastSuccess( `${TranslateString(999, 'Contract Enabled')}`, - `${TranslateString(999, `You can now stake in the ${earningTokenSymbol} pool!`)}`, + `${TranslateString(999, `You can now stake in the ${earningToken.symbol} pool!`)}`, ) setRequestedApproval(false) } else { @@ -46,7 +44,7 @@ const ApprovalAction: React.FC = ({ stakingToken, earningTo console.error(e) toastError('Error', e?.message) } - }, [onApprove, setRequestedApproval, toastSuccess, toastError, TranslateString, earningTokenSymbol]) + }, [onApprove, setRequestedApproval, toastSuccess, toastError, TranslateString, earningToken]) return ( + <> + {isLoading ? ( + + ) : ( + + )} + ) } diff --git a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx index 43dd087b2cc41..b75f2f06319d9 100644 --- a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Flex, Text, Button, Heading, useModal } from '@pancakeswap-libs/uikit' +import { Flex, Text, Button, Heading, useModal, Skeleton } from '@pancakeswap-libs/uikit' import BigNumber from 'bignumber.js' import { Token } from 'config/constants/types' import { getAddress } from 'utils/addressHelpers' @@ -13,9 +13,16 @@ interface HarvestActionsProps { earningToken: Token sousId: number isBnbPool: boolean + isLoading?: boolean } -const HarvestActions: React.FC = ({ earnings, earningToken, sousId, isBnbPool }) => { +const HarvestActions: React.FC = ({ + earnings, + earningToken, + sousId, + isBnbPool, + isLoading = false, +}) => { const TranslateString = useI18n() const earningTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') const fullBalance = getFullDisplayBalance(earnings, earningToken.decimals) @@ -42,10 +49,16 @@ const HarvestActions: React.FC = ({ earnings, earningToken, - {hasEarnings ? formattedBalance : 0} - {`~${ - hasEarnings ? earningsDollarValue : 0 - } USD`} + {isLoading ? ( + + ) : ( + <> + {hasEarnings ? formattedBalance : 0} + {`~${ + hasEarnings ? earningsDollarValue : 0 + } USD`} + + )} - )} - - ) + + + + + + + + + + ) : ( + + ) + } + + return {isLoading ? : renderStakeAction()} } export default StakeAction diff --git a/src/views/Pools/components/PoolCard/CardActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx index 6abcc0ad8fe77..1cc357b2314b4 100644 --- a/src/views/Pools/components/PoolCard/CardActions/index.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -28,6 +28,7 @@ const CardActions: React.FC<{ const earnings = new BigNumber(userData?.pendingReward || 0) const needsApproval = !accountHasStakedBalance && !allowance.toNumber() && !isBnbPool const isStaked = stakedBalance.toNumber() > 0 + const isLoading = !userData return ( @@ -42,7 +43,13 @@ const CardActions: React.FC<{ {TranslateString(330, `earned`)} - + )} @@ -54,9 +61,10 @@ const CardActions: React.FC<{ {needsApproval ? ( - + ) : ( Date: Sat, 24 Apr 2021 11:12:01 +0100 Subject: [PATCH 28/40] feat(syrup-pool-v2): Add bunny img --- public/images/3d-syrup-bunnies.png | Bin 0 -> 63227 bytes src/views/Pools/index.tsx | 10 +++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 public/images/3d-syrup-bunnies.png diff --git a/public/images/3d-syrup-bunnies.png b/public/images/3d-syrup-bunnies.png new file mode 100644 index 0000000000000000000000000000000000000000..02a47cf1209158fefc58b7265a44a0391cbee687 GIT binary patch literal 63227 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 UyF^G4m;e9(07*qoM6N<$f`hBHvH$=8 literal 0 HcmV?d00001 diff --git a/src/views/Pools/index.tsx b/src/views/Pools/index.tsx index 35a555bcf230b..105bb65c7bdf4 100644 --- a/src/views/Pools/index.tsx +++ b/src/views/Pools/index.tsx @@ -3,7 +3,7 @@ import { Route, useRouteMatch } from 'react-router-dom' import BigNumber from 'bignumber.js' import styled from 'styled-components' import { useWeb3React } from '@web3-react/core' -import { Heading, Flex, Button, HelpIcon, Text } from '@pancakeswap-libs/uikit' +import { Heading, Flex, Button, HelpIcon, Text, Image } from '@pancakeswap-libs/uikit' import orderBy from 'lodash/orderBy' import partition from 'lodash/partition' import useI18n from 'hooks/useI18n' @@ -94,6 +94,14 @@ const Pools: React.FC = () => { ))} + Pancake illustration ) From df92291a7e5198bd28d0bd77353e4645f4b94f42 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Sat, 24 Apr 2021 11:55:44 +0100 Subject: [PATCH 29/40] feat(syrup-pool-v2): Fix high price token showing 0% APR --- src/components/ApyCalculatorModal/index.tsx | 6 ++++++ src/utils/compoundApyHelpers.ts | 15 +++++++++------ src/views/Pools/components/PoolCard/AprRow.tsx | 10 +++++++--- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/components/ApyCalculatorModal/index.tsx b/src/components/ApyCalculatorModal/index.tsx index da92cb4bf5a09..39fa9d3981b68 100644 --- a/src/components/ApyCalculatorModal/index.tsx +++ b/src/components/ApyCalculatorModal/index.tsx @@ -11,6 +11,7 @@ interface ApyCalculatorModalProps { linkLabel: string linkHref: string earningTokenSymbol?: string + roundingDecimals?: number } const Grid = styled.div` @@ -36,6 +37,7 @@ const ApyCalculatorModal: React.FC = ({ linkLabel, linkHref, earningTokenSymbol = 'CAKE', + roundingDecimals = 2, }) => { const TranslateString = useI18n() const oneThousandDollarsWorthOfToken = 1000 / tokenPrice @@ -44,21 +46,25 @@ const ApyCalculatorModal: React.FC = ({ numberOfDays: 1, farmApr: apr, tokenPrice, + roundingDecimals, }) const tokenEarnedPerThousand7D = tokenEarnedPerThousandDollarsCompounding({ numberOfDays: 7, farmApr: apr, tokenPrice, + roundingDecimals, }) const tokenEarnedPerThousand30D = tokenEarnedPerThousandDollarsCompounding({ numberOfDays: 30, farmApr: apr, tokenPrice, + roundingDecimals, }) const tokenEarnedPerThousand365D = tokenEarnedPerThousandDollarsCompounding({ numberOfDays: 365, farmApr: apr, tokenPrice, + roundingDecimals, }) return ( diff --git a/src/utils/compoundApyHelpers.ts b/src/utils/compoundApyHelpers.ts index 6bf6b5890e4b8..01a013b361db4 100644 --- a/src/utils/compoundApyHelpers.ts +++ b/src/utils/compoundApyHelpers.ts @@ -1,6 +1,9 @@ -const roundToTwoDp = (number) => Math.round(number * 100) / 100 - -export const tokenEarnedPerThousandDollarsCompounding = ({ numberOfDays, farmApr, tokenPrice }) => { +export const tokenEarnedPerThousandDollarsCompounding = ({ + numberOfDays, + farmApr, + tokenPrice, + roundingDecimals = 2, +}) => { // Everything here is worked out relative to a year, with the asset compounding daily const timesCompounded = 365 // We use decimal values rather than % in the math for both APY and the number of days being calculates as a proportion of the year @@ -12,10 +15,10 @@ export const tokenEarnedPerThousandDollarsCompounding = ({ numberOfDays, farmApr const finalAmount = principal * (1 + aprAsDecimal / timesCompounded) ** (timesCompounded * daysAsDecimalOfYear) // To get the TOKEN amount earned, deduct the amount after compounding (finalAmount) from the starting TOKEN balance (principal) const interestEarned = finalAmount - principal - return roundToTwoDp(interestEarned) + return parseFloat(interestEarned.toFixed(roundingDecimals)) } -export const getRoi = ({ amountEarned, amountInvested }) => { +export const getRoi = ({ amountEarned, amountInvested, roundingDecimals = 2 }) => { const percentage = (amountEarned / amountInvested) * 100 - return percentage.toFixed(2) + return percentage.toFixed(roundingDecimals) } diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index d3e490fc740be..66e404d4e007c 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -17,14 +17,17 @@ interface AprRowProps { const AprRow: React.FC = ({ pool, stakingTokenPrice }) => { const { stakingToken, earningToken, totalStaked, isFinished, tokenPerBlock } = pool - const rewardTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') + const earningTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') const apr = getPoolApr( stakingTokenPrice, - rewardTokenPrice, + earningTokenPrice, getBalanceNumber(totalStaked, stakingToken.decimals), parseFloat(tokenPerBlock), ) + // // special handling for tokens like tBTC or BIFI where the daily token rewards for $1000 dollars will be less than 0.001 of that token + const isHighValueToken = Math.round(earningTokenPrice / 1000) > 0 + const TranslateString = useI18n() const apyModalLink = @@ -35,11 +38,12 @@ const AprRow: React.FC = ({ pool, stakingTokenPrice }) => { const [onPresentApyModal] = useModal( , ) From b5e02c93a7ef0935700880a275172afd8e451f75 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Sat, 24 Apr 2021 12:02:21 +0100 Subject: [PATCH 30/40] chore(syrup-pool-v2): Cleanup white space --- src/views/Pools/components/PoolCard/AprRow.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index 66e404d4e007c..afdd3897ce353 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -15,6 +15,7 @@ interface AprRowProps { } const AprRow: React.FC = ({ pool, stakingTokenPrice }) => { + const TranslateString = useI18n() const { stakingToken, earningToken, totalStaked, isFinished, tokenPerBlock } = pool const earningTokenPrice = useGetApiPrice(earningToken.address ? getAddress(earningToken.address) : '') @@ -24,12 +25,9 @@ const AprRow: React.FC = ({ pool, stakingTokenPrice }) => { getBalanceNumber(totalStaked, stakingToken.decimals), parseFloat(tokenPerBlock), ) - // // special handling for tokens like tBTC or BIFI where the daily token rewards for $1000 dollars will be less than 0.001 of that token const isHighValueToken = Math.round(earningTokenPrice / 1000) > 0 - const TranslateString = useI18n() - const apyModalLink = stakingToken.address && `https://exchange.pancakeswap.finance/#/swap?outputCurrency= From 1d4806f8b2d4b09af3dd1d508081a5f8c91ff195 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Sat, 24 Apr 2021 12:09:19 +0100 Subject: [PATCH 31/40] chore(syrup-pool-v2): Re-add usePersistState --- src/views/Pools/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/views/Pools/index.tsx b/src/views/Pools/index.tsx index 105bb65c7bdf4..20a21cf65b399 100644 --- a/src/views/Pools/index.tsx +++ b/src/views/Pools/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useMemo } from 'react' +import React, { useMemo } from 'react' import { Route, useRouteMatch } from 'react-router-dom' import BigNumber from 'bignumber.js' import styled from 'styled-components' @@ -7,6 +7,7 @@ import { Heading, Flex, Button, HelpIcon, Text, Image } from '@pancakeswap-libs/ import orderBy from 'lodash/orderBy' import partition from 'lodash/partition' import useI18n from 'hooks/useI18n' +import usePersistState from 'hooks/usePersistState' import { usePools, useBlock } from 'state/hooks' import FlexLayout from 'components/layout/Flex' import Page from 'components/layout/Page' @@ -27,7 +28,7 @@ const Pools: React.FC = () => { const { account } = useWeb3React() const pools = usePools(account) const { currentBlock } = useBlock() - const [stakedOnly, setStakedOnly] = useState(false) + const [stakedOnly, setStakedOnly] = usePersistState(false, 'pancake_pool_staked') const [finishedPools, openPools] = useMemo( () => partition(pools, (pool) => pool.isFinished || currentBlock > pool.endBlock), From 8890e77e31cc34e1355c683f95685bd491d57647 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Sat, 24 Apr 2021 12:13:07 +0100 Subject: [PATCH 32/40] chore(syrup-pool-v2): Add comments to help --- src/views/Pools/components/PoolCard/Modals/CollectModal.tsx | 2 ++ src/views/Pools/components/PoolCard/Modals/StakeModal.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/views/Pools/components/PoolCard/Modals/CollectModal.tsx b/src/views/Pools/components/PoolCard/Modals/CollectModal.tsx index b7a60c8450452..74be441935324 100644 --- a/src/views/Pools/components/PoolCard/Modals/CollectModal.tsx +++ b/src/views/Pools/components/PoolCard/Modals/CollectModal.tsx @@ -61,6 +61,7 @@ const CollectModal: React.FC = ({ const handleHarvestConfirm = async () => { setPendingTx(true) + // compounding if (shouldCompound) { try { await onStake(fullBalance, earningToken.decimals) @@ -78,6 +79,7 @@ const CollectModal: React.FC = ({ setPendingTx(false) } } else { + // harvesting try { await onReward() toastSuccess( diff --git a/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx index 15727bad3c5cc..b24218fa8ff28 100644 --- a/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx @@ -66,6 +66,7 @@ const StakeModal: React.FC = ({ setPendingTx(true) if (isRemovingStake) { + // unstaking try { await onUnstake(stakeAmount, stakingToken.decimals) toastSuccess( @@ -83,6 +84,7 @@ const StakeModal: React.FC = ({ } } else { try { + // staking await onStake(stakeAmount, stakingToken.decimals) toastSuccess( `${TranslateString(1074, 'Staked')}!`, From 6130692405a755e7ebcc173cbb076a9c927a59bc Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Mon, 26 Apr 2021 20:48:46 +0100 Subject: [PATCH 33/40] chore(syrup-pool-v2): Remove unused components --- .../components/PoolCard/ConfirmButton.tsx | 39 ------ .../components/PoolCard/HarvestButton.tsx | 130 ------------------ .../components/PoolCard/Modals/StakeModal.tsx | 5 +- 3 files changed, 2 insertions(+), 172 deletions(-) delete mode 100644 src/views/Pools/components/PoolCard/ConfirmButton.tsx delete mode 100644 src/views/Pools/components/PoolCard/HarvestButton.tsx diff --git a/src/views/Pools/components/PoolCard/ConfirmButton.tsx b/src/views/Pools/components/PoolCard/ConfirmButton.tsx deleted file mode 100644 index e53414311c1c3..0000000000000 --- a/src/views/Pools/components/PoolCard/ConfirmButton.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react' -import { Button, ButtonProps, RefreshIcon, CheckmarkIcon } from '@pancakeswap-libs/uikit' - -interface ConfirmButtonProps extends ButtonProps { - isConfirmed?: boolean -} - -const ConfirmButton: React.FC = ({ - isLoading, - isConfirmed, - children, - endIcon, - disabled, - ...props -}) => { - const handleRenderIcon = () => { - if (endIcon) { - return endIcon - } - - if (isLoading) { - return - } - - if (isConfirmed) { - return - } - - return null - } - - return ( - - ) -} - -export default ConfirmButton diff --git a/src/views/Pools/components/PoolCard/HarvestButton.tsx b/src/views/Pools/components/PoolCard/HarvestButton.tsx deleted file mode 100644 index d623409af55c0..0000000000000 --- a/src/views/Pools/components/PoolCard/HarvestButton.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React, { useContext, useMemo } from 'react' -import styled, { ThemeContext } from 'styled-components' - -import { Link } from 'react-router-dom' - -interface ButtonProps { - children?: React.ReactNode - disabled?: boolean - href?: string - onClick?: () => void - size?: 'sm' | 'md' | 'lg' - text?: string - to?: string - variant?: 'default' | 'secondary' | 'tertiary' -} - -const Button: React.FC = ({ children, disabled, href, onClick, size, text, to }) => { - const { colors, spacing } = useContext(ThemeContext) - const buttonColor = colors.background - - let boxShadow: string - let buttonSize: number - let buttonPadding: number - let fontSize: number - switch (size) { - case 'sm': - buttonPadding = spacing[3] - buttonSize = 36 - fontSize = 14 - break - case 'lg': - buttonPadding = spacing[4] - buttonSize = 72 - fontSize = 16 - break - case 'md': - default: - buttonPadding = spacing[4] - buttonSize = 56 - fontSize = 16 - } - - const ButtonChild = useMemo(() => { - if (to) { - return {text} - } - if (href) { - return ( - - {text} - - ) - } - return text - }, [href, text, to]) - - return ( - - {children} - {ButtonChild} - - ) -} - -interface StyledButtonProps { - boxShadow: string - color: string - disabled?: boolean - fontSize: number - padding: number - size: number -} - -const StyledButton = styled.button` - align-items: center; - background: ${(props) => (!props.disabled ? props.theme.card.background : `#ddd`)}; - border: 0; - border-radius: 12px; - color: ${(props) => (!props.disabled ? `#32cad7` : `#acaaaf`)}; - cursor: pointer; - display: flex; - font-size: ${(props) => props.fontSize}px; - font-weight: 700; - height: ${(props) => props.size}px; - justify-content: center; - outline: none; - padding-left: ${(props) => props.padding}px; - padding-right: ${(props) => props.padding}px; - pointer-events: ${(props) => (!props.disabled ? undefined : 'none')}; - width: 100%; - border: 2px solid ${(props) => (!props.disabled ? `#33cbd7` : `#eee`)}; - width: 100px; - height: 30px; - font-size: 14px; - padding: 0px; -` - -const StyledLink = styled(Link)` - align-items: center; - color: inherit; - display: flex; - flex: 1; - height: 56px; - justify-content: center; - margin: 0 ${(props) => -props.theme.spacing[4]}px; - padding: 0 ${(props) => props.theme.spacing[4]}px; - text-decoration: none; -` - -const StyledExternalLink = styled.a` - align-items: center; - color: inherit; - display: flex; - flex: 1; - height: 56px; - justify-content: center; - margin: 0 ${(props) => -props.theme.spacing[4]}px; - padding: 0 ${(props) => props.theme.spacing[4]}px; - text-decoration: none; -` - -export default Button diff --git a/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx index b24218fa8ff28..19254f058fbf9 100644 --- a/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx @@ -10,7 +10,6 @@ import BigNumber from 'bignumber.js' import { getFullDisplayBalance, formatNumber } from 'utils/formatBalance' import { useToast } from 'state/hooks' import { Pool } from 'state/types' -import ConfirmButton from '../ConfirmButton' interface StakeModalProps { isBnbPool: boolean @@ -148,14 +147,14 @@ const StakeModal: React.FC = ({ MAX - : null} onClick={handleConfirmClick} mt="24px" > {pendingTx ? TranslateString(802, 'Confirming') : TranslateString(464, 'Confirm')} - + {!isRemovingStake && ( ) diff --git a/src/views/Pools/index.tsx b/src/views/Pools/index.tsx index 20a21cf65b399..ded9d5a782fb6 100644 --- a/src/views/Pools/index.tsx +++ b/src/views/Pools/index.tsx @@ -1,9 +1,8 @@ import React, { useMemo } from 'react' import { Route, useRouteMatch } from 'react-router-dom' import BigNumber from 'bignumber.js' -import styled from 'styled-components' import { useWeb3React } from '@web3-react/core' -import { Heading, Flex, Button, HelpIcon, Text, Image } from '@pancakeswap-libs/uikit' +import { Heading, Flex, Image } from '@pancakeswap-libs/uikit' import orderBy from 'lodash/orderBy' import partition from 'lodash/partition' import useI18n from 'hooks/useI18n' @@ -15,13 +14,6 @@ import PageHeader from 'components/PageHeader' import PoolCard from './components/PoolCard' import PoolTabButtons from './components/PoolTabButtons' -const ButtonText = styled(Text)` - display: none; - ${({ theme }) => theme.mediaQueries.lg} { - display: block; - } -` - const Pools: React.FC = () => { const { path } = useRouteMatch() const TranslateString = useI18n() @@ -58,17 +50,6 @@ const Pools: React.FC = () => { {TranslateString(999, 'High APR, low risk.')} - From 9fe4356f6d5bd444a245940d5644422a44038b46 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Wed, 28 Apr 2021 08:20:50 +0100 Subject: [PATCH 36/40] feat(syrup-pool-v2): Disabled button when stakeAmount is zero or unset --- src/views/Pools/components/PoolCard/Modals/StakeModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx index 3d6a6019aa174..e37c80a818925 100644 --- a/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx @@ -6,9 +6,9 @@ import { BASE_EXCHANGE_URL } from 'config' import { useSousStake } from 'hooks/useStake' import { useSousUnstake } from 'hooks/useUnstake' import useTheme from 'hooks/useTheme' +import useToast from 'hooks/useToast' import BigNumber from 'bignumber.js' import { getFullDisplayBalance, formatNumber } from 'utils/formatBalance' -import useToast from 'hooks/useToast' import { Pool } from 'state/types' interface StakeModalProps { @@ -151,6 +151,7 @@ const StakeModal: React.FC = ({ isLoading={pendingTx} endIcon={pendingTx ? : null} onClick={handleConfirmClick} + disabled={!stakeAmount || parseFloat(stakeAmount) === 0} mt="24px" > {pendingTx ? TranslateString(802, 'Confirming') : TranslateString(464, 'Confirm')} From dfa6bc5aa092e1b9cbd1775d013df125568f660a Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Wed, 28 Apr 2021 18:22:25 +0100 Subject: [PATCH 37/40] fix(pool-cards-v2): Amendments based on Doge's CR comments --- src/config/index.ts | 1 + .../Pools/components/PoolCard/AprRow.tsx | 5 ++- .../PoolCard/CardActions/HarvestActions.tsx | 6 +-- .../PoolCard/CardActions/StakeActions.tsx | 6 +-- .../components/PoolCard/CardActions/index.tsx | 15 +++++-- .../PoolCard/CardFooter/ExpandedFooter.tsx | 13 ++----- .../PoolCard/Modals/NotEnoughTokensModal.tsx | 17 +++++--- .../PoolCard/Modals/PercentageButton.tsx | 21 ++++++++++ .../components/PoolCard/Modals/StakeModal.tsx | 39 ++++++++----------- .../components/PoolCard/PoolFinishedSash.tsx | 27 ------------- .../components/PoolCard/StyledCardHeader.tsx | 30 ++++++-------- src/views/Pools/components/PoolCard/index.tsx | 8 +++- src/views/Pools/components/PoolTabButtons.tsx | 26 +++++++------ src/views/Pools/svgs/SashVector.tsx | 12 ------ 14 files changed, 106 insertions(+), 120 deletions(-) create mode 100644 src/views/Pools/components/PoolCard/Modals/PercentageButton.tsx delete mode 100644 src/views/Pools/components/PoolCard/PoolFinishedSash.tsx delete mode 100644 src/views/Pools/svgs/SashVector.tsx diff --git a/src/config/index.ts b/src/config/index.ts index 3b22ae895c4c2..abf088c4cca05 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -21,6 +21,7 @@ export const BASE_URL = 'https://pancakeswap.finance' export const BASE_EXCHANGE_URL = 'https://exchange.pancakeswap.finance' export const BASE_ADD_LIQUIDITY_URL = `${BASE_EXCHANGE_URL}/#/add` export const BASE_LIQUIDITY_POOL_URL = `${BASE_EXCHANGE_URL}/#/pool` +export const BASE_BSC_SCAN_URL = 'https://bscscan.com' export const LOTTERY_MAX_NUMBER_OF_TICKETS = 50 export const LOTTERY_TICKET_PRICE = 1 export const DEFAULT_TOKEN_DECIMAL = new BigNumber(10).pow(18) diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index afdd3897ce353..1fd9bf5f919c3 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -8,6 +8,7 @@ import { useGetApiPrice } from 'state/hooks' import Balance from 'components/Balance' import ApyCalculatorModal from 'components/ApyCalculatorModal' import { Pool } from 'state/types' +import { BASE_EXCHANGE_URL } from 'config' interface AprRowProps { pool: Pool @@ -30,7 +31,7 @@ const AprRow: React.FC = ({ pool, stakingTokenPrice }) => { const apyModalLink = stakingToken.address && - `https://exchange.pancakeswap.finance/#/swap?outputCurrency= + `${BASE_EXCHANGE_URL}/#/swap?outputCurrency= ${stakingToken.address[process.env.REACT_APP_CHAIN_ID]} ` @@ -39,7 +40,7 @@ const AprRow: React.FC = ({ pool, stakingTokenPrice }) => { tokenPrice={earningTokenPrice} apr={apr} linkLabel={`${TranslateString(999, 'Get')} ${stakingToken.symbol}`} - linkHref={apyModalLink || 'https://exchange.pancakeswap.finance'} + linkHref={apyModalLink || BASE_EXCHANGE_URL} earningTokenSymbol={earningToken.symbol} roundingDecimals={isHighValueToken ? 4 : 2} />, diff --git a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx index b75f2f06319d9..2779674f4e3f1 100644 --- a/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/HarvestActions.tsx @@ -54,9 +54,9 @@ const HarvestActions: React.FC = ({ ) : ( <> {hasEarnings ? formattedBalance : 0} - {`~${ - hasEarnings ? earningsDollarValue : 0 - } USD`} + + {`~${hasEarnings ? earningsDollarValue : 0} USD`} + )}
diff --git a/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx index c3b551f58378f..86e1b5afc94e6 100644 --- a/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx @@ -12,7 +12,7 @@ import { } from '@pancakeswap-libs/uikit' import BigNumber from 'bignumber.js' import useI18n from 'hooks/useI18n' -import { getBalanceNumber, formatNumber } from 'utils/formatBalance' +import { getBalanceNumber, formatNumber, getDecimalAmount } from 'utils/formatBalance' import { Pool } from 'state/types' import NotEnoughTokensModal from '../Modals/NotEnoughTokensModal' import StakeModal from '../Modals/StakeModal' @@ -38,7 +38,7 @@ const StakeAction: React.FC = ({ }) => { const { stakingToken, earningToken, stakingLimit } = pool const TranslateString = useI18n() - const convertedLimit = new BigNumber(stakingLimit).multipliedBy(new BigNumber(10).pow(earningToken.decimals)) + const convertedLimit = getDecimalAmount(new BigNumber(stakingLimit), earningToken.decimals) const stakingMax = stakingLimit && stakingTokenBalance.isGreaterThan(convertedLimit) ? convertedLimit : stakingTokenBalance const formattedBalance = formatNumber(getBalanceNumber(stakedBalance, stakingToken.decimals), 3, 3) @@ -79,7 +79,7 @@ const StakeAction: React.FC = ({ ) : ( - ) diff --git a/src/views/Pools/components/PoolCard/CardActions/index.tsx b/src/views/Pools/components/PoolCard/CardActions/index.tsx index 1cc357b2314b4..5e4c6cc1989ce 100644 --- a/src/views/Pools/components/PoolCard/CardActions/index.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/index.tsx @@ -13,12 +13,19 @@ const InlineText = styled(Text)` display: inline; ` -const CardActions: React.FC<{ +interface CardActionsProps { pool: Pool stakedBalance: BigNumber accountHasStakedBalance: boolean stakingTokenPrice: number -}> = ({ pool, stakedBalance, accountHasStakedBalance, stakingTokenPrice }) => { +} + +const CardActions: React.FC = ({ + pool, + stakedBalance, + accountHasStakedBalance, + stakingTokenPrice, +}) => { const { sousId, stakingToken, earningToken, harvest, poolCategory, userData } = pool // Pools using native BNB behave differently than pools using a token const isBnbPool = poolCategory === PoolCategory.BINANCE @@ -26,8 +33,8 @@ const CardActions: React.FC<{ const allowance = new BigNumber(userData?.allowance || 0) const stakingTokenBalance = new BigNumber(userData?.stakingTokenBalance || 0) const earnings = new BigNumber(userData?.pendingReward || 0) - const needsApproval = !accountHasStakedBalance && !allowance.toNumber() && !isBnbPool - const isStaked = stakedBalance.toNumber() > 0 + const needsApproval = !accountHasStakedBalance && !allowance.gt(0) && !isBnbPool + const isStaked = stakedBalance.gt(0) const isLoading = !userData return ( diff --git a/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx b/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx index 8c8f60e69ff56..3cfbb31ba681b 100644 --- a/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx +++ b/src/views/Pools/components/PoolCard/CardFooter/ExpandedFooter.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components' import { getBalanceNumber } from 'utils/formatBalance' import useI18n from 'hooks/useI18n' import { Flex, MetamaskIcon, Text, LinkExternal, TimerIcon, Skeleton } from '@pancakeswap-libs/uikit' -import { BASE_URL } from 'config' +import { BASE_BSC_SCAN_URL, BASE_URL } from 'config' import { useBlock } from 'state/hooks' import { Pool } from 'state/types' import { getAddress } from 'utils/addressHelpers' @@ -28,7 +28,7 @@ const ExpandedFooter: React.FC = ({ pool, account }) => { const { stakingToken, earningToken, totalStaked, startBlock, endBlock, isFinished, contractAddress } = pool const tokenAddress = earningToken.address ? getAddress(earningToken.address) : '' - const poolContractAddress = contractAddress[process.env.REACT_APP_CHAIN_ID] + const poolContractAddress = getAddress(contractAddress) const imageSrc = `${BASE_URL}/images/tokens/${earningToken.symbol.toLowerCase()}.png` const isMetaMaskInScope = !!(window as WindowChain).ethereum?.isMetaMask @@ -76,18 +76,13 @@ const ExpandedFooter: React.FC = ({ pool, account }) => { )} - + {TranslateString(412, 'View Project Site')} {poolContractAddress && ( - + {TranslateString(412, 'View Contract')} diff --git a/src/views/Pools/components/PoolCard/Modals/NotEnoughTokensModal.tsx b/src/views/Pools/components/PoolCard/Modals/NotEnoughTokensModal.tsx index 59a0d462c6331..8a3533696e690 100644 --- a/src/views/Pools/components/PoolCard/Modals/NotEnoughTokensModal.tsx +++ b/src/views/Pools/components/PoolCard/Modals/NotEnoughTokensModal.tsx @@ -1,6 +1,7 @@ import React from 'react' import useI18n from 'hooks/useI18n' -import { Modal, Text, Button, OpenNewIcon } from '@pancakeswap-libs/uikit' +import styled from 'styled-components' +import { Modal, Text, Button, OpenNewIcon, Link } from '@pancakeswap-libs/uikit' import { BASE_EXCHANGE_URL } from 'config' import useTheme from 'hooks/useTheme' @@ -9,6 +10,10 @@ interface NotEnoughTokensModalProps { onDismiss?: () => void } +const StyledLink = styled(Link)` + width: 100%; +` + const NotEnoughTokensModal: React.FC = ({ tokenSymbol, onDismiss }) => { const TranslateString = useI18n() const { theme } = useTheme() @@ -29,10 +34,12 @@ const NotEnoughTokensModal: React.FC = ({ tokenSymbol - + + + diff --git a/src/views/Pools/components/PoolCard/Modals/PercentageButton.tsx b/src/views/Pools/components/PoolCard/Modals/PercentageButton.tsx new file mode 100644 index 0000000000000..9cb57b2ef7cad --- /dev/null +++ b/src/views/Pools/components/PoolCard/Modals/PercentageButton.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import styled from 'styled-components' +import { Button } from '@pancakeswap-libs/uikit' + +interface PercentageButtonProps { + onClick?: () => void +} + +const StyledButton = styled(Button)` + flex-grow: 1; +` + +const PercentageButton: React.FC = ({ children, onClick }) => { + return ( + + {children} + + ) +} + +export default PercentageButton diff --git a/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx index e37c80a818925..2dfb3d3ecfac1 100644 --- a/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx +++ b/src/views/Pools/components/PoolCard/Modals/StakeModal.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import styled from 'styled-components' -import { Modal, Text, Flex, Image, Button, Slider, BalanceInput, AutoRenewIcon } from '@pancakeswap-libs/uikit' +import { Modal, Text, Flex, Image, Button, Slider, BalanceInput, AutoRenewIcon, Link } from '@pancakeswap-libs/uikit' import useI18n from 'hooks/useI18n' import { BASE_EXCHANGE_URL } from 'config' import { useSousStake } from 'hooks/useStake' @@ -8,8 +8,9 @@ import { useSousUnstake } from 'hooks/useUnstake' import useTheme from 'hooks/useTheme' import useToast from 'hooks/useToast' import BigNumber from 'bignumber.js' -import { getFullDisplayBalance, formatNumber } from 'utils/formatBalance' +import { getFullDisplayBalance, formatNumber, getDecimalAmount } from 'utils/formatBalance' import { Pool } from 'state/types' +import PercentageButton from './PercentageButton' interface StakeModalProps { isBnbPool: boolean @@ -20,8 +21,8 @@ interface StakeModalProps { onDismiss?: () => void } -const StyledButton = styled(Button)` - flex-grow: 1; +const StyledLink = styled(Link)` + width: 100%; ` const StakeModal: React.FC = ({ @@ -44,14 +45,14 @@ const StakeModal: React.FC = ({ const [stakeAmount, setStakeAmount] = useState('') const [percent, setPercent] = useState(0) - const usdValueStaked = stakeAmount && formatNumber(parseFloat(stakeAmount) * stakingTokenPrice) + const usdValueStaked = stakeAmount && formatNumber(new BigNumber(stakeAmount).times(stakingTokenPrice).toNumber()) const handleStakeInputChange = (event: React.ChangeEvent) => { const inputValue = event.target.value ? event.target.value : '0' - const convertedInput = new BigNumber(inputValue).multipliedBy(new BigNumber(10).pow(stakingToken.decimals)) + const convertedInput = getDecimalAmount(new BigNumber(inputValue), stakingToken.decimals) const percentage = Math.floor(convertedInput.dividedBy(stakingMax).multipliedBy(100).toNumber()) setStakeAmount(inputValue) - setPercent(percentage > 100 ? 100 : percentage) + setPercent(Math.min(percentage, 100)) } const handleChangePercent = (sliderPercent: number) => { @@ -134,18 +135,10 @@ const StakeModal: React.FC = ({ step={1} /> - handleChangePercent(25)}> - 25% - - handleChangePercent(50)}> - 50% - - handleChangePercent(75)}> - 75% - - handleChangePercent(100)}> - MAX - + handleChangePercent(25)}>25% + handleChangePercent(50)}>50% + handleChangePercent(75)}>75% + handleChangePercent(100)}>MAX {!isRemovingStake && ( - + + + )} ) diff --git a/src/views/Pools/components/PoolCard/PoolFinishedSash.tsx b/src/views/Pools/components/PoolCard/PoolFinishedSash.tsx deleted file mode 100644 index 90cfcda424bd1..0000000000000 --- a/src/views/Pools/components/PoolCard/PoolFinishedSash.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react' -import { Text, Box } from '@pancakeswap-libs/uikit' -import styled from 'styled-components' -import useI18n from 'hooks/useI18n' -import SashVector from '../../svgs/SashVector' - -const StyledText = styled(Text)` - position: absolute; - top: 50%; - left: 50%; - transform: rotate(45deg) translate(-50%, -40%); -` - -const PoolFinishedSash = () => { - const TranslateString = useI18n() - - return ( - - - {TranslateString(388, 'Finished')} - - - - ) -} - -export default PoolFinishedSash diff --git a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx index eb0e712efc512..bdc745086f4dd 100644 --- a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx +++ b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx @@ -1,7 +1,6 @@ import React from 'react' -import { CardHeader, Heading, Text, Flex, Image, Box } from '@pancakeswap-libs/uikit' +import { CardHeader, Heading, Text, Flex, Image } from '@pancakeswap-libs/uikit' import styled from 'styled-components' -import PoolFinishedSash from './PoolFinishedSash' const Wrapper = styled(CardHeader)<{ isFinished?: boolean; activeBackground?: string }>` background: ${({ isFinished, activeBackground, theme }) => @@ -18,24 +17,17 @@ const StyledCardHeader: React.FC<{ const activeBackground = isPromoted ? 'bubblegum' : 'cardHeader' return ( - <> - - - - - Earn {earningTokenSymbol} - - Stake {stakingTokenSymbol} - - {earningTokenSymbol} + + + + + Earn {earningTokenSymbol} + + Stake {stakingTokenSymbol} - - {isFinished && ( - - - - )} - + {earningTokenSymbol} + + ) } diff --git a/src/views/Pools/components/PoolCard/index.tsx b/src/views/Pools/components/PoolCard/index.tsx index 438e570e942e6..2a8d2a08fb336 100644 --- a/src/views/Pools/components/PoolCard/index.tsx +++ b/src/views/Pools/components/PoolCard/index.tsx @@ -1,6 +1,6 @@ import BigNumber from 'bignumber.js' import React from 'react' -import { CardBody, Flex, Text } from '@pancakeswap-libs/uikit' +import { CardBody, Flex, Text, CardRibbon } from '@pancakeswap-libs/uikit' import UnlockButton from 'components/UnlockButton' import useI18n from 'hooks/useI18n' import { getAddress } from 'utils/addressHelpers' @@ -20,7 +20,11 @@ const PoolCard: React.FC<{ pool: Pool; account: string }> = ({ pool, account }) const stakingTokenPrice = useGetApiPrice(stakingToken.address ? getAddress(stakingToken.address) : '') return ( - + } + > { const { url, isExact } = useRouteMatch() const TranslateString = useI18n() @@ -38,24 +43,21 @@ const PoolTabButtons = ({ stakedOnly, setStakedOnly, hasStakeInFinishedPools }) - setStakedOnly(!stakedOnly)} /> + setStakedOnly(!prev)} /> {TranslateString(999, 'Staked only')} - + + + ) diff --git a/src/views/Pools/svgs/SashVector.tsx b/src/views/Pools/svgs/SashVector.tsx deleted file mode 100644 index 77e44d46f8c29..0000000000000 --- a/src/views/Pools/svgs/SashVector.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react' -import { Svg, SvgProps } from '@pancakeswap-libs/uikit' - -const SashVector: React.FC = (props) => { - return ( - - - - ) -} - -export default SashVector From 0097dccee066429ce3439d61b3d196a0660b826b Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Wed, 28 Apr 2021 18:30:32 +0100 Subject: [PATCH 38/40] fix(pool-cards-v2): Proper finished handling on staking actions --- .../Pools/components/PoolCard/CardActions/StakeActions.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx index 86e1b5afc94e6..fd8ae8a694059 100644 --- a/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx +++ b/src/views/Pools/components/PoolCard/CardActions/StakeActions.tsx @@ -36,7 +36,7 @@ const StakeAction: React.FC = ({ isStaked, isLoading = false, }) => { - const { stakingToken, earningToken, stakingLimit } = pool + const { stakingToken, earningToken, stakingLimit, isFinished } = pool const TranslateString = useI18n() const convertedLimit = getDecimalAmount(new BigNumber(stakingLimit), earningToken.decimals) const stakingMax = @@ -73,13 +73,13 @@ const StakeAction: React.FC = ({ - + ) : ( - ) From 91f9de51948c00e60e72c7182336ab47fa3d96d0 Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Thu, 29 Apr 2021 08:22:24 +0100 Subject: [PATCH 39/40] fix(pool-cards-v2): Follow up CR comments --- src/views/Pools/components/PoolCard/AprRow.tsx | 4 +--- .../Pools/components/PoolCard/CardActions/StakeActions.tsx | 6 +++++- .../Pools/components/PoolCard/Modals/PercentageButton.tsx | 2 +- src/views/Pools/components/PoolCard/StyledCardHeader.tsx | 2 +- src/views/Pools/components/PoolTabButtons.tsx | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/views/Pools/components/PoolCard/AprRow.tsx b/src/views/Pools/components/PoolCard/AprRow.tsx index 1fd9bf5f919c3..dfa8b8c61f8f5 100644 --- a/src/views/Pools/components/PoolCard/AprRow.tsx +++ b/src/views/Pools/components/PoolCard/AprRow.tsx @@ -31,9 +31,7 @@ const AprRow: React.FC = ({ pool, stakingTokenPrice }) => { const apyModalLink = stakingToken.address && - `${BASE_EXCHANGE_URL}/#/swap?outputCurrency= - ${stakingToken.address[process.env.REACT_APP_CHAIN_ID]} - ` + `${BASE_EXCHANGE_URL}/#/swap?outputCurrency=${stakingToken.address[process.env.REACT_APP_CHAIN_ID]}` const [onPresentApyModal] = useModal( = ({ - + diff --git a/src/views/Pools/components/PoolCard/Modals/PercentageButton.tsx b/src/views/Pools/components/PoolCard/Modals/PercentageButton.tsx index 9cb57b2ef7cad..7178dbccbe40a 100644 --- a/src/views/Pools/components/PoolCard/Modals/PercentageButton.tsx +++ b/src/views/Pools/components/PoolCard/Modals/PercentageButton.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components' import { Button } from '@pancakeswap-libs/uikit' interface PercentageButtonProps { - onClick?: () => void + onClick: () => void } const StyledButton = styled(Button)` diff --git a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx index bdc745086f4dd..de44dac784449 100644 --- a/src/views/Pools/components/PoolCard/StyledCardHeader.tsx +++ b/src/views/Pools/components/PoolCard/StyledCardHeader.tsx @@ -13,7 +13,7 @@ const StyledCardHeader: React.FC<{ isFinished?: boolean }> = ({ earningTokenSymbol, stakingTokenSymbol, isFinished = false }) => { const poolImageSrc = `${earningTokenSymbol}-${stakingTokenSymbol}.svg`.toLocaleLowerCase() - const isPromoted = earningTokenSymbol === 'CAKE' + const isPromoted = earningTokenSymbol === 'CAKE' && stakingTokenSymbol === 'CAKE' const activeBackground = isPromoted ? 'bubblegum' : 'cardHeader' return ( diff --git a/src/views/Pools/components/PoolTabButtons.tsx b/src/views/Pools/components/PoolTabButtons.tsx index 773becf7a0c84..6e4d5e0a4edb5 100644 --- a/src/views/Pools/components/PoolTabButtons.tsx +++ b/src/views/Pools/components/PoolTabButtons.tsx @@ -43,7 +43,7 @@ const PoolTabButtons = ({ stakedOnly, setStakedOnly, hasStakeInFinishedPools }) - setStakedOnly(!prev)} /> + setStakedOnly((prev) => !prev)} /> {TranslateString(999, 'Staked only')} From 7cc8518401d4951021d65f8a00cf05c6a62f849b Mon Sep 17 00:00:00 2001 From: ChefHutch Date: Thu, 29 Apr 2021 09:58:17 +0100 Subject: [PATCH 40/40] feat(syrup-pool-v2): Add new uikit version --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 59c16e9cb8226..3e2acdafd46e7 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@binance-chain/bsc-connector": "^1.0.0", "@crowdin/crowdin-api-client": "^1.10.4", "@ethersproject/abi": "^5.0.7", - "@pancakeswap-libs/uikit": "^0.29.0", + "@pancakeswap-libs/uikit": "^0.29.1", "@reduxjs/toolkit": "^1.5.0", "@types/react": "^17.0.2", "@types/react-dom": "^17.0.1", diff --git a/yarn.lock b/yarn.lock index ee8eed322cf30..ee4bcbf095b4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2017,10 +2017,10 @@ eslint-plugin-react "^7.21.5" eslint-plugin-react-hooks "^4.0.0" -"@pancakeswap-libs/uikit@^0.29.0": - version "0.29.0" - resolved "https://registry.yarnpkg.com/@pancakeswap-libs/uikit/-/uikit-0.29.0.tgz#b9e533081b16d19ad958f9d52df6289de2fdc1bf" - integrity sha512-VWSnXO4UzY2IPYa9pZFJbjoP4TDQxIfHFqvRqhse5cd2neFODD4obNJGqu1gqR8Tnuu/AEjISSzEyW7GFoBHuQ== +"@pancakeswap-libs/uikit@^0.29.1": + version "0.29.1" + resolved "https://registry.yarnpkg.com/@pancakeswap-libs/uikit/-/uikit-0.29.1.tgz#0b96d3456df39d9727d4201547c8e1bae47c3cf3" + integrity sha512-pQvBK+UXfiAWmpLKPIPcu9ISF5PrYLwJZndNKbjyW9+HoO5e9szhjewzTKFlfmR1G5imhZvrd9ebwiJEgZyZ2w== dependencies: "@popperjs/core" "^2.9.2" "@types/lodash" "^4.14.168"