Skip to content

Commit

Permalink
Add FeeControlGroup
Browse files Browse the repository at this point in the history
  • Loading branch information
neikop committed Dec 8, 2023
1 parent 9ebb87e commit 6b165ef
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 35 deletions.
156 changes: 156 additions & 0 deletions src/components/FeeControlGroup/CustomFeeInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { t } from '@lingui/macro'
import React, { useEffect, useRef, useState } from 'react'
import { Text } from 'rebass'
import styled, { css } from 'styled-components'

import { DEFAULT_TIPS } from 'constants/index'
import { formatSlippage } from 'utils/slippage'

const parseTipInput = (str: string): number => Math.round(Number.parseFloat(str) * 100)

const getFeeText = (fee: number) => {
const isCustom = !DEFAULT_TIPS.includes(fee)
if (!isCustom) {
return ''
}
return formatSlippage(fee, false)
}

const feeOptionCSS = css`
height: 100%;
padding: 0;
border-radius: 20px;
border: 1px solid transparent;
background-color: ${({ theme }) => theme.tabBackground};
color: ${({ theme }) => theme.subText};
text-align: center;
font-size: 12px;
font-weight: 400;
line-height: 16px;
outline: none;
cursor: pointer;
:hover {
border-color: ${({ theme }) => theme.bg4};
}
:focus {
border-color: ${({ theme }) => theme.bg4};
}
&[data-active='true'] {
background-color: ${({ theme }) => theme.tabActive};
color: ${({ theme }) => theme.text};
border-color: ${({ theme }) => theme.primary};
font-weight: 500;
}
`

const CustomFeeOption = styled.div`
${feeOptionCSS};
flex: 0 0 24%;
display: inline-flex;
align-items: center;
padding: 0 4px;
column-gap: 2px;
flex: 1;
transition: all 150ms linear;
&[data-active='true'] {
color: ${({ theme }) => theme.text};
font-weight: 500;
}
`

const CustomInput = styled.input`
width: 100%;
height: 100%;
border: 0px;
border-radius: inherit;
color: inherit;
background: transparent;
outline: none;
text-align: right;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
&::placeholder {
font-size: 12px;
}
@media only screen and (max-width: 375px) {
font-size: 10px;
}
`

export type Props = {
fee: number
onFeeChange: (value: number) => void
}

const CustomFeeInput = ({ fee, onFeeChange }: Props) => {
const [text, setText] = useState(getFeeText(fee))

const inputRef = useRef<HTMLInputElement>(null)
const isCustomOptionActive = !DEFAULT_TIPS.includes(fee)

const handleChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value

if (value === '') {
setText(value)
onFeeChange(50)
return
}

const numberRegex = /^(\d+)\.?(\d{1,2})?$/
if (!value.match(numberRegex)) {
e.preventDefault()
return
}

const parsedValue = parseTipInput(value)
if (Number.isNaN(parsedValue)) {
e.preventDefault()
return
}

setText(value)
onFeeChange(parsedValue)
}

const handleCommitChange = () => {
setText(getFeeText(fee))
}

useEffect(() => {
if (inputRef.current !== document.activeElement) {
setText(getFeeText(fee))
}
}, [fee])

return (
<CustomFeeOption data-active={isCustomOptionActive}>
<CustomInput
ref={inputRef}
placeholder={t`Custom`}
value={text}
onChange={handleChangeInput}
onBlur={handleCommitChange}
/>
<Text as="span" sx={{ flex: '0 0 12px' }}>
%
</Text>
</CustomFeeOption>
)
}

export default CustomFeeInput
118 changes: 118 additions & 0 deletions src/components/FeeControlGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Flex, Text } from 'rebass'
import styled, { css } from 'styled-components'

import useGetFeeConfig from 'components/SwapForm/hooks/useGetFeeConfig'
import { DEFAULT_TIPS } from 'constants/index'
import useTheme from 'hooks/useTheme'

import CustomFeeInput from './CustomFeeInput'

const feeOptionCSS = css`
height: 100%;
padding: 0;
border-radius: 20px;
border: 1px solid transparent;
background-color: ${({ theme }) => theme.tabBackground};
color: ${({ theme }) => theme.subText};
text-align: center;
font-size: 12px;
font-weight: 400;
line-height: 16px;
outline: none;
cursor: pointer;
:hover {
border-color: ${({ theme }) => theme.bg4};
}
:focus {
border-color: ${({ theme }) => theme.bg4};
}
&[data-active='true'] {
background-color: ${({ theme }) => theme.tabActive};
color: ${({ theme }) => theme.text};
border-color: ${({ theme }) => theme.primary};
font-weight: 500;
}
`

const DefaultFeeOption = styled.button`
${feeOptionCSS};
flex: 0 0 18%;
@media only screen and (max-width: 375px) {
font-size: 10px;
flex: 0 0 15%;
}
`

const FeeControlGroup = () => {
const theme = useTheme()
const { feeAmount, enableTip } = useGetFeeConfig() ?? {}
const [searchParams, setSearchParams] = useSearchParams()

const [feeValue, setFee] = useState(Math.round(Number.parseFloat(feeAmount ?? '0')) || 0)

useEffect(() => {
if (enableTip) {
searchParams.set('feeAmount', feeValue.toString())
setSearchParams(searchParams)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enableTip, feeValue])

if (!enableTip) {
return null
}

return (
<Flex
sx={{
flexDirection: 'column',
width: '100%',
padding: '0 8px',
}}
>
<Text color={theme.subText} fontSize={12} fontWeight={500}>
Tip :
</Text>
<Text color={theme.text3} fontSize={12} fontWeight={500}>
No hidden fees - Your optional tips support DEX Screener!
</Text>

<Flex
sx={{
justifyContent: 'space-between',
width: '100%',
maxWidth: '100%',
height: '28px',
borderRadius: '20px',
background: theme.tabBackground,
padding: '2px',
marginTop: '8px',
}}
>
{DEFAULT_TIPS.map(tip => (
<DefaultFeeOption
key={tip}
onClick={() => {
setFee(tip)
}}
data-active={tip === feeValue}
>
{tip ? `${tip / 100}%` : 'No tip'}
</DefaultFeeOption>
))}
<CustomFeeInput fee={feeValue} onFeeChange={setFee} />
</Flex>
</Flex>
)
}

export default FeeControlGroup
15 changes: 0 additions & 15 deletions src/components/SlippageControl/CustomSlippageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,6 @@ const CustomSlippageInput: React.FC<Props> = ({ rawSlippage, setRawSlippage, isW
mixpanelHandler(MIXPANEL_TYPE.SLIPPAGE_CHANGED, { new_slippage: Number(formatSlippage(rawSlippage, false)) })
}

const handleKeyPressInput = (e: React.KeyboardEvent<HTMLInputElement>) => {
const key = e.key
if (key === '.' || ('0' <= key && key <= '9')) {
return
}

if (key === 'Enter') {
inputRef.current?.blur()
return
}

e.preventDefault()
}

useEffect(() => {
if (inputRef.current !== document.activeElement) {
setRawText(getSlippageText(rawSlippage))
Expand All @@ -207,7 +193,6 @@ const CustomSlippageInput: React.FC<Props> = ({ rawSlippage, setRawSlippage, isW
placeholder={t`Custom`}
value={rawText}
onChange={handleChangeInput}
onKeyPress={handleKeyPressInput}
onBlur={handleCommitChange}
/>
<Text
Expand Down
30 changes: 30 additions & 0 deletions src/components/SwapForm/hooks/useGetFeeConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'

import { ChargeFeeBy } from 'types/route'

const useGetFeeConfig = () => {
const [searchParams] = useSearchParams()

const feeAmount = searchParams.get('feeAmount') || ''
const chargeFeeBy = (searchParams.get('chargeFeeBy') as ChargeFeeBy) || ChargeFeeBy.NONE
const enableTip = searchParams.get('enableTip') || ''
const isInBps = searchParams.get('isInBps') || ''
const feeReceiver = searchParams.get('feeReceiver') || ''

const feeConfigFromUrl = useMemo(() => {
if (feeAmount && chargeFeeBy && (enableTip || isInBps) && feeReceiver)
return {
feeAmount,
chargeFeeBy,
enableTip,
isInBps: enableTip ? '1' : isInBps,
feeReceiver,
}
return null
}, [feeAmount, chargeFeeBy, enableTip, isInBps, feeReceiver])

return feeConfigFromUrl
}

export default useGetFeeConfig
22 changes: 2 additions & 20 deletions src/components/SwapForm/hooks/useGetRoute.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ChainId, Currency, CurrencyAmount } from '@kyberswap/ks-sdk-core'
import debounce from 'lodash/debounce'
import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useSearchParams } from 'react-router-dom'
import routeApi from 'services/route'
import { GetRouteParams } from 'services/route/types/getRoute'

import useGetFeeConfig from 'components/SwapForm/hooks/useGetFeeConfig'
import useGetSwapFeeConfig, { SwapFeeConfig } from 'components/SwapForm/hooks/useGetSwapFeeConfig'
import useSelectedDexes from 'components/SwapForm/hooks/useSelectedDexes'
import { AGGREGATOR_API } from 'constants/env'
Expand Down Expand Up @@ -80,25 +80,7 @@ const useGetRoute = (args: ArgsGetRoute) => {
const { chainId: currentChain } = useActiveWeb3React()
const chainId = customChain || currentChain

const [searchParams] = useSearchParams()

const feeAmount = searchParams.get('feeAmount') || ''
const chargeFeeBy = (searchParams.get('chargeFeeBy') as ChargeFeeBy) || ChargeFeeBy.NONE
const enableTip = searchParams.get('enableTip') || ''
const isInBps = searchParams.get('isInBps') || ''
const feeReceiver = searchParams.get('feeReceiver') || ''

const feeConfigFromUrl = useMemo(() => {
if (feeAmount && chargeFeeBy && (enableTip || isInBps) && feeReceiver)
return {
feeAmount,
chargeFeeBy,
enableTip,
isInBps: enableTip ? '1' : isInBps,
feeReceiver,
}
return null
}, [feeAmount, chargeFeeBy, enableTip, isInBps, feeReceiver])
const feeConfigFromUrl = useGetFeeConfig()

const [trigger, _result] = routeApi.useLazyGetRouteQuery()
const aggregatorDomain = useRouteApiDomain()
Expand Down
2 changes: 2 additions & 0 deletions src/components/SwapForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { parseGetRouteResponse } from 'services/route/utils'
import styled from 'styled-components'

import AddressInputPanel from 'components/AddressInputPanel'
import FeeControlGroup from 'components/FeeControlGroup'
import { Clock } from 'components/Icons'
import { NetworkSelector } from 'components/NetworkSelector'
import { AutoRow } from 'components/Row'
Expand Down Expand Up @@ -261,6 +262,7 @@ const SwapForm: React.FC<SwapFormProps> = props => {
<AddressInputPanel id="recipient" value={recipient} onChange={setRecipient} />
)}
<SlippageSettingGroup isWrapOrUnwrap={isWrapOrUnwrap} isStablePairSwap={isStablePairSwap} />
<FeeControlGroup />
</Flex>
</Wrapper>
<Flex flexDirection="column" style={{ gap: '1.25rem' }}>
Expand Down
1 change: 1 addition & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export const SWR_KEYS = {
export const MAX_NORMAL_SLIPPAGE_IN_BIPS = 1999
export const MAX_DEGEN_SLIPPAGE_IN_BIPS = 5000
export const DEFAULT_SLIPPAGES = [5, 10, 50, 100]
export const DEFAULT_TIPS = [0, 10, 50, 100]

export const DEFAULT_SLIPPAGE = 50
export const DEFAULT_SLIPPAGE_STABLE_PAIR_SWAP = 5
Expand Down

0 comments on commit 6b165ef

Please sign in to comment.