Skip to content

Commit

Permalink
feat(swap-warning-modals): Add warning modals for specific tokens whe…
Browse files Browse the repository at this point in the history
…n swapping (pancakeswap#1743)

* feat(swap-warning-modals): Add config and components to pop warning modal for tokens

* feat(swap-warning-modals): Use getAddress helpers

* chore(swap-warnings): Modify variable names

* feat(swap-warning-modals): Improve styles and reorg component to match design

* chore(swap-warnings): Tidy up components and props

* chore(swap-warnings): Use uikit message component

* chore(warning-modal): Remove unused imports

* chore(warning-modal): Type config and use imported tokens where possible

* chore(warning-modal): Add Safemoon to tokens config and import to swap warning tokens
  • Loading branch information
ChefHutch authored Jul 19, 2021
1 parent 8acc4fb commit 38a9aff
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/components/SearchModal/CurrencySearchModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export default function CurrencySearchModal({

const handleCurrencySelect = useCallback(
(currency: Currency) => {
onCurrencySelect(currency)
onDismiss()
onCurrencySelect(currency)
},
[onDismiss, onCurrencySelect],
)
Expand Down
20 changes: 20 additions & 0 deletions src/config/constants/swapWarningTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import tokens from 'config/constants/tokens'
import { Address } from './types'

const { bondly, safemoon } = tokens

interface WarningToken {
symbol: string
address: Address
}

interface WarningTokenList {
[key: string]: WarningToken
}

const SwapWarningTokens = <WarningTokenList>{
safemoon,
bondly,
}

export default SwapWarningTokens
9 changes: 9 additions & 0 deletions src/config/constants/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1756,6 +1756,15 @@ const tokens = {
decimals: 18,
projectLink: 'https://decentral.games/',
},
safemoon: {
symbol: 'SAFEMOON',
address: {
56: '0x8076C74C5e3F5852037F31Ff0093Eeb8c8ADd8D3',
97: '',
},
decimals: 9,
projectLink: 'https://safemoon.net/',
},
}

export default tokens
10 changes: 9 additions & 1 deletion src/config/localization/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1182,5 +1182,13 @@
"Waiting For Confirmation": "Waiting For Confirmation",
"Confirm this transaction in your wallet": "Confirm this transaction in your wallet",
"Dismiss": "Dismiss",
"Latest": "Latest"
"Latest": "Latest",
"Notice for trading %symbol%": "Notice for trading %symbol%",
"To trade SAFEMOON, you must:": "To trade SAFEMOON, you must:",
"Click on the settings icon": "Click on the settings icon",
"Set your slippage tolerance to 12%+": "Set your slippage tolerance to 12%+",
"This is because SafeMoon taxes a 10% fee on each transaction:": "This is because SafeMoon taxes a 10% fee on each transaction:",
"5% fee = redistributed to all existing holders": "5% fee = redistributed to all existing holders",
"5% fee = used to add liquidity": "5% fee = used to add liquidity",
"Warning: BONDLY has been compromised. Please remove liqudity until further notice.": "Warning: BONDLY has been compromised. Please remove liqudity until further notice."
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface Props extends InjectedModalProps {
onCancel: () => void
}

const TokenWarningModal: React.FC<Props> = ({ tokens, onDismiss, onCancel }) => {
const ImportTokenWarningModal: React.FC<Props> = ({ tokens, onDismiss, onCancel }) => {
return (
<Modal
title="Import Token"
Expand All @@ -25,4 +25,4 @@ const TokenWarningModal: React.FC<Props> = ({ tokens, onDismiss, onCancel }) =>
)
}

export default TokenWarningModal
export default ImportTokenWarningModal
37 changes: 37 additions & 0 deletions src/views/Swap/components/SwapWarningModal/Acknowledgement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useState } from 'react'
import { useTranslation } from 'contexts/Localization'
import { Text, Flex, Checkbox, Button } from '@pancakeswap/uikit'

interface AcknowledgementProps {
handleContinueClick: () => void
}

const Acknowledgement: React.FC<AcknowledgementProps> = ({ handleContinueClick }) => {
const { t } = useTranslation()
const [isConfirmed, setIsConfirmed] = useState(false)

return (
<>
<Flex justifyContent="space-between">
<Flex alignItems="center">
<Checkbox
name="confirmed"
type="checkbox"
checked={isConfirmed}
onChange={() => setIsConfirmed(!isConfirmed)}
scale="sm"
/>
<Text ml="10px" style={{ userSelect: 'none' }}>
{t('I understand')}
</Text>
</Flex>

<Button disabled={!isConfirmed} onClick={handleContinueClick}>
{t('Continue')}
</Button>
</Flex>
</>
)
}

export default Acknowledgement
11 changes: 11 additions & 0 deletions src/views/Swap/components/SwapWarningModal/BondlyWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react'
import { Text } from '@pancakeswap/uikit'
import { useTranslation } from 'contexts/Localization'

const BondlyWarning = () => {
const { t } = useTranslation()

return <Text>{t('Warning: BONDLY has been compromised. Please remove liqudity until further notice.')}</Text>
}

export default BondlyWarning
20 changes: 20 additions & 0 deletions src/views/Swap/components/SwapWarningModal/SafemoonWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { useTranslation } from 'contexts/Localization'
import { Text } from '@pancakeswap/uikit'

const SafemoonWarning = () => {
const { t } = useTranslation()

return (
<>
<Text>{t('To trade SAFEMOON, you must:')} </Text>
<Text>{t('Click on the settings icon')}</Text>
<Text mb="24px">{t('Set your slippage tolerance to 12%+')}</Text>
<Text>{t('This is because SafeMoon taxes a 10% fee on each transaction:')}</Text>
<Text>{t('5% fee = redistributed to all existing holders')}</Text>
<Text>{t('5% fee = used to add liquidity')}</Text>
</>
)
}

export default SafemoonWarning
81 changes: 81 additions & 0 deletions src/views/Swap/components/SwapWarningModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useEffect } from 'react'
import styled from 'styled-components'
import { ModalBody, ModalContainer, Message, ModalHeader, Box, Heading } from '@pancakeswap/uikit'
import useTheme from 'hooks/useTheme'
import { getAddress } from 'utils/addressHelpers'
import { useTranslation } from 'contexts/Localization'
import { WrappedTokenInfo } from 'state/lists/hooks'
import SwapWarningTokensConfig from 'config/constants/swapWarningTokens'
import SafemoonWarning from './SafemoonWarning'
import BondlyWarning from './BondlyWarning'
import Acknowledgement from './Acknowledgement'

const StyledModalContainer = styled(ModalContainer)`
max-width: 440px;
`

const MessageContainer = styled(Message)`
align-items: flex-start;
justify-content: flex-start;
`

interface SwapWarningModalProps {
swapCurrency: WrappedTokenInfo
onDismiss?: () => void
}

// Modal is fired by a useEffect and doesn't respond to closeOnOverlayClick prop being set to false
const usePreventModalOverlayClick = () => {
useEffect(() => {
const preventClickHandler = (e) => {
e.stopPropagation()
e.preventDefault()
return false
}

document.querySelectorAll('[role="presentation"]').forEach((el) => {
el.addEventListener('click', preventClickHandler, true)
})

return () => {
document.querySelectorAll('[role="presentation"]').forEach((el) => {
el.removeEventListener('click', preventClickHandler, true)
})
}
}, [])
}

const SwapWarningModal: React.FC<SwapWarningModalProps> = ({ swapCurrency, onDismiss }) => {
const { t } = useTranslation()
const { theme } = useTheme()
usePreventModalOverlayClick()

const TOKEN_WARNINGS = {
[getAddress(SwapWarningTokensConfig.safemoon.address)]: {
symbol: SwapWarningTokensConfig.safemoon.symbol,
component: <SafemoonWarning />,
},
[getAddress(SwapWarningTokensConfig.bondly.address)]: {
symbol: SwapWarningTokensConfig.bondly.symbol,
component: <BondlyWarning />,
},
}

const SWAP_WARNING = TOKEN_WARNINGS[swapCurrency.address]

return (
<StyledModalContainer minWidth="280px">
<ModalHeader background={theme.colors.gradients.cardHeader}>
<Heading p="12px 24px">{t('Notice for trading %symbol%', { symbol: SWAP_WARNING.symbol })}</Heading>
</ModalHeader>
<ModalBody p="24px">
<MessageContainer variant="warning" mb="24px">
<Box>{SWAP_WARNING.component}</Box>
</MessageContainer>
<Acknowledgement handleContinueClick={onDismiss} />
</ModalBody>
</StyledModalContainer>
)
}

export default SwapWarningModal
48 changes: 43 additions & 5 deletions src/views/Swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { useIsTransactionUnsupported } from 'hooks/Trades'
import UnsupportedCurrencyFooter from 'components/UnsupportedCurrencyFooter'
import { RouteComponentProps } from 'react-router-dom'
import { useTranslation } from 'contexts/Localization'
import SwapWarningTokens from 'config/constants/swapWarningTokens'
import { getAddress } from 'utils/addressHelpers'
import AddressInputPanel from './components/AddressInputPanel'
import { GreyCard } from '../../components/Card'
import Column, { AutoColumn } from '../../components/Layout/Column'
Expand All @@ -16,7 +18,7 @@ import AdvancedSwapDetailsDropdown from './components/AdvancedSwapDetailsDropdow
import confirmPriceImpactWithoutFee from './components/confirmPriceImpactWithoutFee'
import { ArrowWrapper, SwapCallbackError, Wrapper } from './components/styleds'
import TradePrice from './components/TradePrice'
import TokenWarningModal from './components/TokenWarningModal'
import ImportTokenWarningModal from './components/ImportTokenWarningModal'
import ProgressSteps from './components/ProgressSteps'
import { AppHeader, AppBody } from '../../components/App'
import UnlockButton from '../../components/UnlockButton'
Expand All @@ -39,6 +41,7 @@ import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
import CircleLoader from '../../components/Loader/CircleLoader'
import Page from '../Page'
import SwapWarningModal from './components/SwapWarningModal'

const Label = styled(Text)`
font-size: 12px;
Expand Down Expand Up @@ -214,10 +217,36 @@ export default function Swap({ history }: RouteComponentProps) {
setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn })
}, [attemptingTxn, swapErrorMessage, trade, txHash])

// swap warning state
const [swapWarningCurrency, setSwapWarningCurrency] = useState(null)
const [onPresentSwapWarningModal] = useModal(<SwapWarningModal swapCurrency={swapWarningCurrency} />)

const shouldShowSwapWarning = (swapCurrency) => {
const isWarningToken = Object.entries(SwapWarningTokens).find((warningTokenConfig) => {
const warningTokenData = warningTokenConfig[1]
const warningTokenAddress = getAddress(warningTokenData.address)
return swapCurrency.address === warningTokenAddress
})
return Boolean(isWarningToken)
}

useEffect(() => {
if (swapWarningCurrency) {
onPresentSwapWarningModal()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [swapWarningCurrency])

const handleInputSelect = useCallback(
(inputCurrency) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
const showSwapWarning = shouldShowSwapWarning(inputCurrency)
if (showSwapWarning) {
setSwapWarningCurrency(inputCurrency)
} else {
setSwapWarningCurrency(null)
}
},
[onCurrencySelection],
)
Expand All @@ -229,19 +258,28 @@ export default function Swap({ history }: RouteComponentProps) {
}, [maxAmountInput, onUserInput])

const handleOutputSelect = useCallback(
(outputCurrency) => onCurrencySelection(Field.OUTPUT, outputCurrency),
(outputCurrency) => {
onCurrencySelection(Field.OUTPUT, outputCurrency)
const showSwapWarning = shouldShowSwapWarning(outputCurrency)
if (showSwapWarning) {
setSwapWarningCurrency(outputCurrency)
} else {
setSwapWarningCurrency(null)
}
},

[onCurrencySelection],
)

const swapIsUnsupported = useIsTransactionUnsupported(currencies?.INPUT, currencies?.OUTPUT)

const [onPresentTokenWarningModal] = useModal(
<TokenWarningModal tokens={importTokensNotInDefault} onCancel={() => history.push('/swap/')} />,
const [onPresentImportTokenWarningModal] = useModal(
<ImportTokenWarningModal tokens={importTokensNotInDefault} onCancel={() => history.push('/swap/')} />,
)

useEffect(() => {
if (importTokensNotInDefault.length > 0) {
onPresentTokenWarningModal()
onPresentImportTokenWarningModal()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importTokensNotInDefault.length])
Expand Down

0 comments on commit 38a9aff

Please sign in to comment.