resolve conflicts

This commit is contained in:
ian-jh 2019-07-29 16:46:36 -04:00
commit 7a58ca810f
20 changed files with 27574 additions and 830 deletions

26650
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

@ -27,7 +27,7 @@
"noLiquidity": "No liquidity.",
"insufficientLiquidity": "Insufficient liquidity.",
"unlockTokenCont": "Please unlock token to continue.",
"transactionDetails": "Transaction Details",
"transactionDetails": "Advanced Details",
"hideDetails": "Hide Details",
"slippageWarning": "Slippage Warning",
"highSlippageWarning": "High Slippage Warning",

@ -21,7 +21,7 @@ const ContainerRow = styled.div`
justify-content: center;
align-items: center;
border-radius: 1.25rem;
box-shadow: 0 0 0 0.5px ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
box-shadow: 0 0 0 1px ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
background-color: ${({ theme }) => theme.white};
transition: box-shadow 200ms ease-in-out;
`

@ -51,8 +51,8 @@ class ContextualInfo extends Component {
}
static defaultProps = {
openDetailsText: 'Transaction Details',
closeDetailsText: 'Hide Details',
openDetailsText: 'Advanced Details',
closeDetailsText: 'Hide Advanced',
renderTransactionDetails() {},
contextualInfo: '',
isError: false

@ -32,11 +32,10 @@ const SummaryWrapperContainer = styled.div`
const Details = styled.div`
background-color: ${({ theme }) => theme.concreteGray};
padding: 1.5rem;
padding-bottom: 1rem;
/* padding: 1.25rem 1.25rem 1rem 1.25rem; */
border-radius: 1rem;
font-size: 0.75rem;
margin-top: 1rem;
margin: 1rem 0.5rem 0 0.5rem;
`
const ErrorSpan = styled.span`
@ -86,11 +85,10 @@ const ColoredDropdown = styled(WrappedDropdown)`
`
export default function ContextualInfo({
openDetailsText = 'Transaction Details',
closeDetailsText = 'Hide Details',
openDetailsText = 'Advanced Details',
closeDetailsText = 'Hide Advanced',
contextualInfo = '',
allowExpand = false,
renderTransactionDetails = () => {},
isError = false,
slippageWarning,
highSlippageWarning,

@ -34,6 +34,7 @@ const SubCurrencySelect = styled.button`
outline: none;
cursor: pointer;
user-select: none;
`
const InputRow = styled.div`
@ -70,7 +71,7 @@ const CurrencySelect = styled.button`
}
:focus {
box-shadow: 0 0 0.5px 0.5px ${({ theme }) => theme.malibuBlue};
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
}
:active {
@ -104,12 +105,12 @@ const InputPanel = styled.div`
const Container = styled.div`
border-radius: 1.25rem;
box-shadow: 0 0 0 0.5px ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
box-shadow: 0 0 0 1px ${({ error, theme }) => (error ? theme.salmonRed : theme.mercuryGray)};
background-color: ${({ theme }) => theme.white};
transition: box-shadow 200ms ease-in-out;
:focus-within {
box-shadow: 0 0 0.5px 0.5px ${({ theme }) => theme.malibuBlue};
box-shadow: 0 0 1px 1px ${({ theme }) => theme.malibuBlue};
}
`

@ -5,8 +5,9 @@ import { DialogOverlay, DialogContent } from '@reach/dialog'
import '@reach/dialog/styles.css'
const AnimatedDialogOverlay = animated(DialogOverlay)
const StyledDialogOverlay = styled(AnimatedDialogOverlay).attrs({
suppressclassnamewarning: 'true'
const WrappedDialogOverlay = ({ suppressClassNameWarning, ...rest }) => <AnimatedDialogOverlay {...rest} />
const StyledDialogOverlay = styled(WrappedDialogOverlay).attrs({
suppressClassNameWarning: true
})`
&[data-reach-dialog-overlay] {
z-index: 2;

@ -5,7 +5,7 @@ import styled from 'styled-components'
import { transparentize, darken } from 'polished'
import { useBodyKeyDown } from '../../hooks'
import { useBetaMessageManager } from '../../contexts/Application'
import { useBetaMessageManager } from '../../contexts/LocalStorage'
const tabOrder = [
{
@ -84,11 +84,11 @@ const StyledNavLink = styled(NavLink).attrs({
&.${activeClassName} {
background-color: ${({ theme }) => theme.white};
border-radius: 3rem;
box-shadow: 0 0 0.5px 1px ${({ theme }) => theme.mercuryGray};
box-shadow: 0 0 1px 1px ${({ theme }) => theme.mercuryGray};
font-weight: 500;
color: ${({ theme }) => theme.royalBlue};
:hover {
box-shadow: 0 0 0.5px 1px ${({ theme }) => darken(0.1, theme.mercuryGray)};
box-shadow: 0 0 1px 1px ${({ theme }) => darken(0.1, theme.mercuryGray)};
}
}

@ -1,38 +1,81 @@
import React, { useState } from 'react'
import React, { useState, useEffect, useRef } from 'react'
import ReactGA from 'react-ga'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import styled, { css, keyframes } from 'styled-components'
import { darken, lighten } from 'polished'
import { isAddress, amountFormatter } from '../../utils'
import questionMark from '../../assets/images/question-mark.svg'
import { useDebounce } from '../../hooks'
import question from '../../assets/images/question.svg'
import NewContextualInfo from '../../components/ContextualInfoNew'
const WARNING_TYPE = Object.freeze({
none: 'none',
emptyInput: 'emptyInput',
invalidEntryBound: 'invalidEntryBound',
riskyEntryHigh: 'riskyEntryHigh',
riskyEntryLow: 'riskyEntryLow'
})
const b = text => <Bold>{text}</Bold>
const Flex = styled.div`
display: flex;
justify-content: center;
button {
max-width: 20rem;
}
`
const SlippageRow = styled(Flex)`
const FlexBetween = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
`
const WrappedSlippageRow = ({ wrap, ...rest }) => <Flex {...rest} />
const SlippageRow = styled(WrappedSlippageRow)`
position: relative;
width: 100%;
flex-wrap: ${({ wrap }) => wrap && 'wrap'};
flex-direction: row;
justify-content: flex-start;
align-items: center;
font-size: 0.8rem;
width: 100%;
padding: 0;
height: 24px;
margin-bottom: 14px;
padding-top: ${({ wrap }) => wrap && '0.25rem'};
`
const QuestionWrapper = styled.div`
const QuestionWrapper = styled.button`
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
margin-left: 0.4rem;
margin-top: 0.2rem;
padding: 0.2rem;
border: none;
background: none;
outline: none;
cursor: default;
border-radius: 36px;
&:hover {
cursor: pointer;
:hover,
:focus {
opacity: 0.7;
}
`
const HelpCircleStyled = styled.img`
height: 18px;
width: 18px;
`
const fadeIn = keyframes`
from {
opacity : 0;
}
to {
opacity : 1;
}
`
@ -41,100 +84,166 @@ const Popup = styled(Flex)`
width: 228px;
left: -78px;
top: -124px;
flex-direction: column;
aligm-items: center;
padding: 1rem;
line-height: 183.52%;
background: #404040;
align-items: center;
padding: 0.6rem 1rem;
line-height: 150%;
background: ${({ theme }) => theme.charcoalBlack};
border-radius: 8px;
animation: ${fadeIn} 0.15s linear;
color: white;
font-style: italic;
${({ theme }) => theme.mediaWidth.upToSmall`
left: -20px;
`}
`
const Option = styled(Flex)`
const FancyButton = styled.button`
align-items: center;
min-width: 55px;
height: 24px;
margin-right: 4px;
height: 2rem;
border-radius: 36px;
border: 1px solid #f2f2f2;
font-size: 12px;
border: 1px solid ${({ theme }) => theme.mercuryGray};
outline: none;
background: ${({ theme }) => theme.white};
${({ active }) =>
active &&
:hover {
cursor: inherit;
border: 1px solid ${({ theme }) => theme.chaliceGray};
}
:focus {
border: 1px solid ${({ theme }) => theme.royalBlue};
/* box-shadow: 0 0 1px 1px #5CA2FF; */
}
`
background-color: #2f80ed;
color: white;
border: 1px solid #2f80ed;
`}
&:hover {
const Option = styled(FancyButton)`
margin-right: 8px;
margin-top: 6px;
:hover {
cursor: pointer;
}
${({ active, theme }) =>
active &&
css`
background-color: ${({ theme }) => theme.royalBlue};
color: ${({ theme }) => theme.white};
border: none;
:hover {
border: none;
box-shadow: none;
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
}
:focus {
border: none;
box-shadow: none;
background-color: ${({ theme }) => lighten(0.05, theme.royalBlue)};
}
:active {
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
}
:hover:focus {
background-color: ${({ theme }) => theme.royalBlue};
}
:hover:focus:active {
background-color: ${({ theme }) => darken(0.05, theme.royalBlue)};
}
`}
`
const OptionLarge = styled(Option)`
width: 120px;
`
const Input = styled.input`
width: 123.27px;
background: #ffffff;
height: 2rem;
background: ${({ theme }) => theme.white};
flex-grow: 1;
outline: none;
margin-left: 20px;
border: 1px solid #f2f2f2;
box-sizing: border-box;
border-radius: 36px;
color: #aeaeae;
&:focus {
}
text-align: left;
padding-left: 0.9rem;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
cursor: inherit;
color: ${({ theme }) => theme.doveGray};
text-align: left;
${({ active }) =>
active &&
`
border: 1px solid #2f80ed;
css`
color: initial;
cursor: initial;
text-align: right;
padding-right 1.5rem;
padding-left 0rem;
color : inherit;
`}
${({ warning }) =>
warning === 'red' &&
`
color : #FF6871;
border: 1px solid #FF6871;
${({ placeholder }) =>
placeholder !== 'Custom' &&
css`
text-align: right;
`}
${({ color }) =>
color === 'red' &&
css`
color: ${({ theme }) => theme.salmonRed};
`}
`
const BottomError = styled.div`
margin-top: 1rem;
color: #aeaeae;
${({ show }) =>
show &&
css`
padding-top: 12px;
`}
color: ${({ theme }) => theme.doveGray};
${({ color }) =>
color === 'red' &&
`
color : #FF6871;
css`
color: ${({ theme }) => theme.salmonRed};
`}
`
const Break = styled.div`
border: 1px solid #f2f2f2;
width: 100%;
margin-top: 1rem;
`
const OptionLarge = styled(Option)`
const OptionCustom = styled(FancyButton)`
height: 2rem;
position: relative;
width: 120px;
margin-top: 6px;
padding: 0 0.75rem;
${({ active }) =>
active &&
css`
border: 1px solid ${({ theme }) => theme.royalBlue};
:hover {
border: 1px solid ${({ theme }) => darken(0.1, theme.royalBlue)};
}
`}
${({ color }) =>
color === 'red' &&
css`
border: 1px solid ${({ theme }) => theme.salmonRed};
`}
input {
width: 100%;
height: 100%;
border: 0px;
border-radius: 2rem;
}
`
const Bold = styled.span`
@ -142,48 +251,67 @@ const Bold = styled.span`
`
const LastSummaryText = styled.div`
margin-top: 0.6rem;
padding-top: 0.5rem;
`
const SlippageSelector = styled.div`
margin-top: 1rem;
`
const InputGroup = styled.div`
position: relative;
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
padding: 1rem 1.25rem 1rem 1.25rem;
border-radius: 12px;
`
const Percent = styled.div`
right: 14px;
top: 9px;
position: absolute;
color: inherit;
font-size: 0, 8rem;
flex-grow: 0;
${({ color }) =>
${({ color, theme }) =>
(color === 'faded' &&
`
color : #AEAEAE
css`
color: ${theme.doveGray};
`) ||
(color === 'red' &&
`
color : #FF6871
`)}
css`
color: ${theme.salmonRed};
`)};
`
const Faded = styled.span`
opacity: 0.7;
`
const ErrorEmoji = styled.span`
left: 30px;
top: 4px;
position: absolute;
const TransactionInfo = styled.div`
padding: 1.25rem 1.25rem 1rem 1.25rem;
`
const ValueWrapper = styled.span`
padding: 0.125rem 0.3rem 0.1rem 0.3rem;
background-color: ${({ theme }) => darken(0.04, theme.concreteGray)};
border-radius: 12px;
font-variant: tabular-nums;
vertical
`
export default function TransactionDetails(props) {
const { t } = useTranslation()
const [activeIndex, setActiveIndex] = useState(3)
const [warningType, setWarningType] = useState(WARNING_TYPE.none)
const inputRef = useRef()
const [showPopup, setPopup] = useState(false)
const [userInput, setUserInput] = useState('')
const debouncedInput = useDebounce(userInput, 150)
useEffect(() => {
if (activeIndex === 4) {
checkBounds(debouncedInput)
}
})
function renderSummary() {
let contextualInfo = ''
let isError = false
@ -233,23 +361,17 @@ export default function TransactionDetails(props) {
)
}
const [activeIndex, setActiveIndex] = useState(3)
const [placeHolder, setplaceHolder] = useState('Custom')
const [warningType, setWarningType] = useState('none')
const [showPopup, setPopup] = useState(false)
const dropDownContent = () => {
return (
<>
{renderTransactionDetails()}
<Break />
<SlippageSelector>
<SlippageRow>
Limit addtional price slippage
Limit additional price slippage
<QuestionWrapper
onClick={() => {
setPopup(!showPopup)
}}
onMouseEnter={() => {
setPopup(true)
}}
@ -257,105 +379,114 @@ export default function TransactionDetails(props) {
setPopup(false)
}}
>
<img src={questionMark} alt="question mark" />
<HelpCircleStyled src={question} alt="popup" />
</QuestionWrapper>
{showPopup ? (
<Popup>
Lowering this limit decreases your risk of frontrunning. This makes it more likely that your transaction
will fail due to normal price movements.
Lowering this limit decreases your risk of frontrunning. However, this makes it more likely that your
transaction will fail due to normal price movements.
</Popup>
) : (
''
)}
</SlippageRow>
<SlippageRow>
<SlippageRow wrap>
<Option
onClick={() => {
updateSlippage(0.1)
setWarningType('none')
setActiveIndex(1)
props.setcustomSlippageError('valid')
setplaceHolder('Custom')
setFromFixed(1, 0.1)
}}
active={activeIndex === 1 ? true : false}
active={activeIndex === 1}
>
0.1%
</Option>
<Option
onClick={() => {
updateSlippage(1)
setWarningType('none')
setActiveIndex(2)
props.setcustomSlippageError('valid')
setplaceHolder('Custom')
setFromFixed(2, 0.5)
}}
active={activeIndex === 2 ? true : false}
active={activeIndex === 2}
>
1%
0.5%
</Option>
<OptionLarge
onClick={() => {
updateSlippage(2)
setWarningType('none')
setActiveIndex(3)
props.setcustomSlippageError('valid')
setplaceHolder('Custom')
setFromFixed(3, 1)
}}
active={activeIndex === 3 ? true : false}
active={activeIndex === 3}
>
2%
<Faded>(suggested)</Faded>
1% <Faded>(suggested)</Faded>
</OptionLarge>
<InputGroup>
{warningType !== 'none' ? <ErrorEmoji></ErrorEmoji> : ''}
<Input
placeholder={placeHolder}
value={userInput || ''}
onChange={parseInput}
onClick={e => {
setActiveIndex(4)
setplaceHolder('')
parseInput(e)
}}
active={activeIndex === 4 ? true : false}
warning={
warningType === 'emptyInput'
<OptionCustom
active={activeIndex === 4}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== 'none' && warningType !== 'riskyEntryLow'
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
onClick={() => {
setFromCustom()
}}
>
<FlexBetween>
{!(warningType === WARNING_TYPE.none || warningType === WARNING_TYPE.emptyInput) && (
<span role="img" aria-label="warning">
</span>
)}
<Input
tabIndex={-1}
ref={inputRef}
active={activeIndex === 4}
placeholder={
activeIndex === 4
? !!userInput
? ''
: '0'
: activeIndex !== 4 && userInput !== ''
? userInput
: 'Custom'
}
value={activeIndex === 4 ? userInput : ''}
onChange={parseInput}
color={
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
/>
<Percent
color={
warningType === 'emptyInput'
activeIndex !== 4
? 'faded'
: warningType !== 'none' && warningType !== 'riskyEntryLow'
: warningType === WARNING_TYPE.riskyEntryHigh || warningType === WARNING_TYPE.invalidEntryBound
? 'red'
: activeIndex !== 4
? 'faded'
: ''
}
>
%
</Percent>
</InputGroup>
</FlexBetween>
</OptionCustom>
</SlippageRow>
<SlippageRow>
<BottomError
show={activeIndex === 4}
color={
warningType === 'emptyInput'
warningType === WARNING_TYPE.emptyInput
? ''
: warningType !== 'none' && warningType !== 'riskyEntryLow'
: warningType !== WARNING_TYPE.none && warningType !== WARNING_TYPE.riskyEntryLow
? 'red'
: ''
}
>
{warningType === 'emptyInput' ? 'Enter a slippage percentage.' : ''}
{warningType === 'invalidEntry' ? 'Please input a valid percentage.' : ''}
{warningType === 'invalidEntryBound' ? 'Pleae select value less than 50%' : ''}
{warningType === 'riskyEntryHigh' ? 'Your transaction may be frontrun.' : ''}
{warningType === 'riskyEntryLow' ? 'Your transaction may fail.' : ''}
{activeIndex === 4 && warningType.toString() === 'none' && 'Custom slippage value entered'}
{warningType === WARNING_TYPE.emptyInput && 'Enter a slippage percentage.'}
{warningType === WARNING_TYPE.invalidEntryBound && 'Please select value less than 50%'}
{warningType === WARNING_TYPE.riskyEntryHigh && 'Your transaction may be frontrun.'}
{warningType === WARNING_TYPE.riskyEntryLow && 'Your transaction may fail.'}
</BottomError>
</SlippageRow>
</SlippageSelector>
@ -363,52 +494,65 @@ export default function TransactionDetails(props) {
)
}
const [userInput, setUserInput] = useState()
const setFromCustom = () => {
setActiveIndex(4)
inputRef.current.focus()
// if there's a value, evaluate the bounds
checkBounds(userInput)
}
// used for slippage presets
const setFromFixed = (index, slippage) => {
// update slippage in parent, reset errors and input state
updateSlippage(slippage)
setWarningType(WARNING_TYPE.none)
setActiveIndex(index)
props.setcustomSlippageError('valid`')
}
const checkBounds = slippageValue => {
setWarningType(WARNING_TYPE.none)
props.setcustomSlippageError('valid')
if (slippageValue === '') {
props.setcustomSlippageError('invalid')
return setWarningType(WARNING_TYPE.emptyInput)
}
// check bounds and set errors
if (slippageValue < 0 || slippageValue > 50) {
props.setcustomSlippageError('invalid')
return setWarningType(WARNING_TYPE.invalidEntryBound)
}
if (slippageValue >= 0 && slippageValue < 0.1) {
props.setcustomSlippageError('valid')
setWarningType(WARNING_TYPE.riskyEntryLow)
}
if (slippageValue > 5) {
props.setcustomSlippageError('warning')
setWarningType(WARNING_TYPE.riskyEntryHigh)
}
//update the actual slippage value in parent
updateSlippage(slippageValue)
}
// check that the theyve entered number and correct decimal
const parseInput = e => {
let input = e.target.value
if (input === '') {
setUserInput(input)
props.setcustomSlippageError('invalid')
return setWarningType('emptyInput')
}
//check for decimal
let isValid = /^[+]?\d*\.?\d{1,2}$/.test(input) || /^[+]?\d*\.$/.test(input)
let decimalLimit = /^\d+\.?\d{0,2}$/.test(input) || input === ''
if (decimalLimit) {
setUserInput(input)
} else {
return
}
if (isValid) {
checkAcceptablePercentValue(input)
} else {
setWarningType('invalidEntry')
}
}
const checkAcceptablePercentValue = input => {
setTimeout(function() {
setWarningType('none')
props.setcustomSlippageError('valid')
if (input < 0 || input > 50) {
props.setcustomSlippageError('invalid')
return setWarningType('invalidEntryBound')
// restrict to 2 decimal places
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
// if its within accepted decimal limit, update the input state
if (acceptableValues.some(a => a.test(input))) {
setUserInput(input)
}
if (input >= 0 && input < 0.1) {
props.setcustomSlippageError('valid')
setWarningType('riskyEntryLow')
}
if (input >= 5) {
props.setcustomSlippageError('warning')
setWarningType('riskyEntryHigh')
}
updateSlippage(input)
}, 300)
}
const updateSlippage = newSlippage => {
// round to 2 decimals to prevent ethers error
let numParsed = parseFloat((newSlippage * 100).toFixed(2))
// set both slippage values in parents
props.setRawSlippage(numParsed)
props.setRawTokenSlippage(numParsed)
}
@ -423,9 +567,10 @@ export default function TransactionDetails(props) {
if (props.independentField === props.INPUT) {
return props.sending ? (
<div>
<TransactionInfo>
<div>
{t('youAreSelling')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.independentValueParsed,
@ -433,34 +578,12 @@ export default function TransactionDetails(props) {
Math.min(4, props.independentDecimals)
)} ${props.inputSymbol}`
)}
</ValueWrapper>
.
</div>
<LastSummaryText>
{b(props.recipientAddress)} {t('willReceive')}{' '}
{b(
`${amountFormatter(
props.dependentValueMinumum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.outputSymbol}`
)}{' '}
</LastSummaryText>
<LastSummaryText>
{t('priceChange')} {b(`${props.percentSlippageFormatted}%`)}.
</LastSummaryText>
</div>
) : (
<div>
<div>
{t('youAreSelling')}{' '}
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.inputSymbol}`
)}{' '}
{t('forAtLeast')}
<ValueWrapper>
{b(
`${amountFormatter(
props.dependentValueMinumum,
@ -468,45 +591,79 @@ export default function TransactionDetails(props) {
Math.min(4, props.dependentDecimals)
)} ${props.outputSymbol}`
)}
</ValueWrapper>{' '}
</LastSummaryText>
<LastSummaryText>
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>.
</LastSummaryText>
</TransactionInfo>
) : (
<TransactionInfo>
<div>
{t('youAreSelling')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.inputSymbol}`
)}
</ValueWrapper>{' '}
{t('forAtLeast')}
<ValueWrapper>
{b(
`${amountFormatter(
props.dependentValueMinumum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.outputSymbol}`
)}
</ValueWrapper>
.
</div>
<LastSummaryText>
{t('priceChange')} {b(`${props.percentSlippageFormatted}%`)}.
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>.
</LastSummaryText>
</div>
</TransactionInfo>
)
} else {
return props.sending ? (
<div>
<TransactionInfo>
<div>
{t('youAreSending')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.independentValueParsed,
props.independentDecimals,
Math.min(4, props.independentDecimals)
)} ${props.outputSymbol}`
)}{' '}
)}
</ValueWrapper>{' '}
{t('to')} {b(props.recipientAddress)}.
</div>
<LastSummaryText>
{t('itWillCost')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.dependentValueMaximum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.inputSymbol}`
)}{' '}
)}
</ValueWrapper>{' '}
</LastSummaryText>
<LastSummaryText>
{t('priceChange')} {b(`${props.percentSlippageFormatted}%`)}.
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>.
</LastSummaryText>
</div>
</TransactionInfo>
) : (
<div>
<TransactionInfo>
<div>
{t('youAreBuying')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.independentValueParsed,
@ -514,22 +671,25 @@ export default function TransactionDetails(props) {
Math.min(4, props.independentDecimals)
)} ${props.outputSymbol}`
)}
</ValueWrapper>
.
</div>
<LastSummaryText>
{t('itWillCost')}{' '}
<ValueWrapper>
{b(
`${amountFormatter(
props.dependentValueMaximum,
props.dependentDecimals,
Math.min(4, props.dependentDecimals)
)} ${props.inputSymbol}`
)}{' '}
)}
</ValueWrapper>{' '}
</LastSummaryText>
<LastSummaryText>
{t('priceChange')} {b(`${props.percentSlippageFormatted}%`)}.
{t('priceChange')} <ValueWrapper>{b(`${props.percentSlippageFormatted}%`)}</ValueWrapper>.
</LastSummaryText>
</div>
</TransactionInfo>
)
}
}

@ -5,7 +5,7 @@ import { useWeb3Context, Connectors } from 'web3-react'
import { darken, transparentize } from 'polished'
import Jazzicon from 'jazzicon'
import { ethers } from 'ethers'
import { Activity, ArrowRight } from 'react-feather'
import { Activity } from 'react-feather'
import { shortenAddress } from '../../utils'
import { useENSName } from '../../hooks'
@ -91,13 +91,6 @@ const NetworkIcon = styled(Activity)`
height: 16px;
`
const ArrowIcon = styled(ArrowRight)`
margin-left: 0.25rem;
margin-right: 0.5rem;
width: 16px;
height: 16px;
`
const SpinnerWrapper = styled(Spinner)`
margin: 0 0.25rem 0 0.25rem;
`
@ -251,7 +244,6 @@ export default function Web3Status() {
return (
<Web3StatusConnect onClick={onClick}>
<Text>{t('Connect')}</Text>
<ArrowIcon />
</Web3StatusConnect>
)
} else {

@ -2,10 +2,8 @@ import React, { createContext, useContext, useReducer, useMemo, useCallback, use
import { useWeb3Context } from 'web3-react'
import { safeAccess } from '../utils'
const SHOW_BETA_MESSAGE = 'SHOW_BETA_MESSAGE'
const BLOCK_NUMBERS = 'BLOCK_NUMBERS'
const DISMISS_BETA_MESSAGE = 'DISMISS_BETA_MESSAGE'
const UPDATE_BLOCK_NUMBER = 'UPDATE_BLOCK_NUMBER'
const ApplicationContext = createContext()
@ -16,12 +14,6 @@ function useApplicationContext() {
function reducer(state, { type, payload }) {
switch (type) {
case DISMISS_BETA_MESSAGE: {
return {
...state,
[SHOW_BETA_MESSAGE]: false
}
}
case UPDATE_BLOCK_NUMBER: {
const { networkId, blockNumber } = payload
return {
@ -40,25 +32,15 @@ function reducer(state, { type, payload }) {
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, {
[SHOW_BETA_MESSAGE]: true,
[BLOCK_NUMBERS]: {}
})
const dismissBetaMessage = useCallback(() => {
dispatch({ type: DISMISS_BETA_MESSAGE })
}, [])
const updateBlockNumber = useCallback((networkId, blockNumber) => {
dispatch({ type: UPDATE_BLOCK_NUMBER, payload: { networkId, blockNumber } })
}, [])
return (
<ApplicationContext.Provider
value={useMemo(() => [state, { dismissBetaMessage, updateBlockNumber }], [
state,
dismissBetaMessage,
updateBlockNumber
])}
>
<ApplicationContext.Provider value={useMemo(() => [state, { updateBlockNumber }], [state, updateBlockNumber])}>
{children}
</ApplicationContext.Provider>
)
@ -101,12 +83,6 @@ export function Updater() {
return null
}
export function useBetaMessageManager() {
const [state, { dismissBetaMessage }] = useApplicationContext()
return [safeAccess(state, [SHOW_BETA_MESSAGE]), dismissBetaMessage]
}
export function useBlockNumber() {
const { networkId } = useWeb3Context()

@ -0,0 +1,90 @@
import React, { createContext, useContext, useReducer, useMemo, useCallback, useEffect } from 'react'
const UNISWAP = 'UNISWAP'
const VERSION = 'VERSION'
const CURRENT_VERSION = 0
const LAST_SAVED = 'LAST_SAVED'
const BETA_MESSAGE_DISMISSED = 'BETA_MESSAGE_DISMISSED'
const UPDATABLE_KEYS = [BETA_MESSAGE_DISMISSED]
const UPDATE_KEY = 'UPDATE_KEY'
const LocalStorageContext = createContext()
function useLocalStorageContext() {
return useContext(LocalStorageContext)
}
function reducer(state, { type, payload }) {
switch (type) {
case UPDATE_KEY: {
const { key, value } = payload
if (!UPDATABLE_KEYS.some(k => k === key)) {
throw Error(`Unexpected key in LocalStorageContext reducer: '${key}'.`)
} else {
return {
...state,
[key]: value
}
}
}
default: {
throw Error(`Unexpected action type in LocalStorageContext reducer: '${type}'.`)
}
}
}
function init() {
const defaultLocalStorage = {
[VERSION]: CURRENT_VERSION,
[BETA_MESSAGE_DISMISSED]: false
}
try {
const parsed = JSON.parse(window.localStorage.getItem(UNISWAP))
if (parsed[VERSION] !== CURRENT_VERSION) {
// this is where we could run migration logic
return defaultLocalStorage
} else {
return parsed
}
} catch {
return defaultLocalStorage
}
}
export default function Provider({ children }) {
const [state, dispatch] = useReducer(reducer, undefined, init)
const updateKey = useCallback((key, value) => {
dispatch({ type: UPDATE_KEY, payload: { key, value } })
}, [])
return (
<LocalStorageContext.Provider value={useMemo(() => [state, { updateKey }], [state, updateKey])}>
{children}
</LocalStorageContext.Provider>
)
}
export function Updater() {
const [state] = useLocalStorageContext()
useEffect(() => {
window.localStorage.setItem(UNISWAP, JSON.stringify({ ...state, [LAST_SAVED]: Math.floor(Date.now() / 1000) }))
})
return null
}
export function useBetaMessageManager() {
const [state, { updateKey }] = useLocalStorageContext()
const dismissBetaMessage = useCallback(() => {
updateKey(BETA_MESSAGE_DISMISSED, true)
}, [updateKey])
return [!state[BETA_MESSAGE_DISMISSED], dismissBetaMessage]
}

@ -89,6 +89,14 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 9,
[EXCHANGE_ADDRESS]: '0xb92dE8B30584392Af27726D5ce04Ef3c4e5c9924'
},
'0xc719d010B63E5bbF2C0551872CD5316ED26AcD83': {
[NAME]: 'Decentralized Insurance Protocol',
[SYMBOL]: 'DIP',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x61792F290e5100FBBcBb2309F03A1Bab869fb850'
},
'0x4946Fcea7C692606e8908002e55A582af44AC121': {
[NAME]: 'FOAM Token',
[SYMBOL]: 'FOAM',
@ -191,6 +199,12 @@ const INITIAL_TOKENS_CONTEXT = {
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0x2C4Bd064b998838076fa341A83d007FC2FA50957'
},
'0xec67005c4E498Ec7f55E092bd1d35cbC47C91892': {
[NAME]: 'Melon Token',
[SYMBOL]: 'MLN',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xA931F4eB165AC307fD7431b5EC6eADde53E14b0C'
},
'0x957c30aB0426e0C93CD8241E2c60392d08c6aC8e': {
[NAME]: 'Modum Token',
[SYMBOL]: 'MOD',

@ -4,6 +4,7 @@ import ReactGA from 'react-ga'
import Web3Provider, { Connectors } from 'web3-react'
import ThemeProvider, { GlobalStyle } from './theme'
import LocalStorageContextProvider, { Updater as LocalStorageContextUpdater } from './contexts/LocalStorage'
import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application'
import TransactionContextProvider, { Updater as TransactionContextUpdater } from './contexts/Transactions'
import TokensContextProvider from './contexts/Tokens'
@ -29,6 +30,7 @@ const connectors = { Injected, Network }
function ContextProviders({ children }) {
return (
<LocalStorageContextProvider>
<ApplicationContextProvider>
<TransactionContextProvider>
<TokensContextProvider>
@ -38,12 +40,14 @@ function ContextProviders({ children }) {
</TokensContextProvider>
</TransactionContextProvider>
</ApplicationContextProvider>
</LocalStorageContextProvider>
)
}
function Updaters() {
return (
<>
<LocalStorageContextUpdater />
<ApplicationContextUpdater />
<TransactionContextUpdater />
</>

@ -362,6 +362,7 @@ export default function AddLiquidity() {
})
const deadline = Math.ceil(Date.now() / 1000) + DEADLINE_FROM_NOW
const estimatedGasLimit = await exchangeContract.estimate.addLiquidity(
isNewExchange ? ethers.constants.Zero : liquidityTokensMin,
isNewExchange ? outputValueParsed : outputValueMax,
@ -371,6 +372,8 @@ export default function AddLiquidity() {
}
)
const gasLimit = calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
exchangeContract
.addLiquidity(
isNewExchange ? ethers.constants.Zero : liquidityTokensMin,
@ -378,7 +381,7 @@ export default function AddLiquidity() {
deadline,
{
value: inputValueParsed,
gasLimit: calculateGasMargin(estimatedGasLimit, GAS_MARGIN)
gasLimit
}
)
.then(response => {

@ -64,7 +64,7 @@ const StyledNavLink = styled(NavLink).attrs({
&.${activeClassName} {
background-color: ${({ theme }) => theme.white};
border-radius: 3rem;
box-shadow: 0 0 0.5px 0.5px ${({ theme }) => theme.mercuryGray};
box-shadow: 0 0 1px 1px ${({ theme }) => theme.mercuryGray};
font-weight: 500;
color: ${({ theme }) => theme.royalBlue};
}

@ -1,5 +1,5 @@
import styled, { keyframes } from 'styled-components'
import { lighten, darken } from 'polished'
import { darken } from 'polished'
export const Button = styled.button.attrs(({ warning, theme }) => ({
backgroundColor: warning ? theme.salmonRed : theme.royalBlue
@ -18,11 +18,11 @@ export const Button = styled.button.attrs(({ warning, theme }) => ({
:hover,
:focus {
background-color: ${({ backgroundColor }) => lighten(0.05, backgroundColor)};
background-color: ${({ backgroundColor }) => darken(0.05, backgroundColor)};
}
:active {
background-color: ${({ backgroundColor }) => darken(0.05, backgroundColor)};
background-color: ${({ backgroundColor }) => darken(0.1, backgroundColor)};
}
:disabled {

@ -38,6 +38,9 @@ const theme = {
chaliceGray: '#AEAEAE',
doveGray: '#737373',
mineshaftGray: '#2B2B2B',
buttonOutlineGrey: '#f2f2f2',
//blacks
charcoalBlack: '#404040',
// blues
zumthorBlue: '#EBF4FF',
malibuBlue: '#5CA2FF',
@ -53,6 +56,7 @@ const theme = {
// pink
uniswapPink: '#DC6BE5',
connectedGreen: '#27AE60',
// media queries
mediaWidth: mediaWidthTemplates,
// css snippets

825
yarn.lock

File diff suppressed because it is too large Load Diff