Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom tip for dex screener #2440

Merged
merged 8 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 169 additions & 0 deletions src/components/FeeControlGroup/CustomFeeInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { t } from '@lingui/macro'
import React, { useEffect, useRef, useState } from 'react'
import { Text } from 'rebass'
import styled, { css } from 'styled-components'

import Tooltip from 'components/Tooltip'
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 [tooltip, setTooltip] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const isCustomOptionActive = !DEFAULT_TIPS.includes(fee)

const handleChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
setTooltip('')
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
}

const maxCustomFee = 9999
if (parsedValue > maxCustomFee) {
const format = formatSlippage(maxCustomFee)
setTooltip(t`Max is ${format}`)
e.preventDefault()
return
}

setText(value)
onFeeChange(parsedValue)
}

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

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

return (
<Tooltip text={tooltip} show={!!tooltip} placement="bottom" width="fit-content">
<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>
</Tooltip>
)
}

export default CustomFeeInput
116 changes: 116 additions & 0 deletions src/components/FeeControlGroup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Trans } from '@lingui/macro'
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 = Number.parseFloat(feeAmount ?? '0')

const handleFeeChange = (feeValue: number) => {
if (enableTip) {
searchParams.set('feeAmount', feeValue.toString())
setSearchParams(searchParams)
}
}

if (!enableTip) {
return null
}

return (
<Flex
sx={{
flexDirection: 'column',
width: '100%',
padding: '0 8px',
}}
>
<Text color={theme.subText} fontSize={12} fontWeight={500}>
<Trans>Tip</Trans>:
</Text>
<Text color={theme.text3} fontSize={12} fontWeight={500}>
<Trans>No hidden fees - Your optional tips support DEX Screener!</Trans>
</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={() => {
handleFeeChange(tip)
}}
data-active={tip === feeValue}
>
{tip ? `${tip / 100}%` : <Trans>No tip</Trans>}
</DefaultFeeOption>
))}
<CustomFeeInput fee={feeValue} onFeeChange={handleFeeChange} />
</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
3 changes: 1 addition & 2 deletions src/components/SwapForm/SlippageSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,9 @@ const SlippageSetting = ({ isStablePairSwap, rightComponent, tooltip }: Props) =
)
}
>
<Trans>Max Slippage</Trans>
<Trans>Max Slippage</Trans>:
</MouseoverTooltip>
</TextDashed>
:
<Flex
sx={{
alignItems: 'center',
Expand Down
31 changes: 31 additions & 0 deletions src/components/SwapForm/hooks/useGetFeeConfig.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useMemo } from 'react'
import { useSearchParams } from 'react-router-dom'

import { ChargeFeeBy } from 'types/route'
import { convertStringToBoolean } from 'utils/string'

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

const feeAmount = searchParams.get('feeAmount') || ''
const chargeFeeBy = (searchParams.get('chargeFeeBy') as ChargeFeeBy) || ChargeFeeBy.NONE
const enableTip = convertStringToBoolean(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
20 changes: 2 additions & 18 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,23 +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 isInBps = searchParams.get('isInBps') || ''
const feeReceiver = searchParams.get('feeReceiver') || ''

const feeConfigFromUrl = useMemo(() => {
if (feeAmount && chargeFeeBy && isInBps && feeReceiver)
return {
feeAmount,
chargeFeeBy,
isInBps,
feeReceiver,
}
return null
}, [feeAmount, chargeFeeBy, isInBps, feeReceiver])
const feeConfigFromUrl = useGetFeeConfig()

const [trigger, _result] = routeApi.useLazyGetRouteQuery()
const aggregatorDomain = useRouteApiDomain()
Expand Down
Loading
Loading