mars-interface/src/components/common/InputSection/InputSection.tsx
2023-02-10 18:27:20 +01:00

237 lines
7.0 KiB
TypeScript

import { Button, DisplayCurrency, InputSlider } from 'components/common'
import { formatValue, lookup, lookupSymbol, parseNumberInput } from 'libs/parse'
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import CurrencyInput from 'react-currency-input-field'
import { useTranslation } from 'react-i18next'
import useStore from 'store'
import { ViewType } from 'types/enums'
import styles from './InputSection.module.scss'
interface Props {
asset: RedBankAsset
availableText: string
amount: number
maxUsableAmount: number
actionButton: ReactNode
checkForMaxValue?: boolean
disabled?: boolean
amountUntilDepositCap: number
activeView: ViewType
walletBalance: number
inputCallback: (value: number) => void
onEnterHandler: () => void
setAmountCallback: (value: number) => void
}
export const InputSection = ({
asset,
availableText,
amount,
maxUsableAmount,
actionButton,
checkForMaxValue = false,
disabled,
amountUntilDepositCap,
activeView,
walletBalance,
inputCallback,
onEnterHandler,
setAmountCallback,
}: Props) => {
// ------------------
// EXTERNAL HOOKS
// ------------------
const { t } = useTranslation()
const inputRef = useRef<HTMLInputElement>(null)
// ------------------
// STORE STATE
// ------------------
const baseCurrency = useStore((s) => s.baseCurrency)
const whitelistedAssets = useStore((s) => s.whitelistedAssets)
// ------------------
// LOCAL STATE
// ------------------
const [sliderValue, setSliderValue] = useState(0)
const [depositWarning, setDepositWarning] = useState(false)
const [fakeAmount, setFakeAmount] = useState<string | undefined>()
// ------------------
// VARIABLES
// ------------------
const sliderEnabled = useMemo(() => maxUsableAmount > 1, [maxUsableAmount])
// If within 50ms of our last update for the slider, return the cached value.
// This prevents a 'flicker' of the display label when we finish dragging while hovering
const sliderDisplayValue = useMemo(() => {
const amountToSet = (amount / maxUsableAmount) * 100
return amountToSet >= 100 ? amountToSet + Math.random() : amountToSet
}, [amount, maxUsableAmount])
// -----------------------
// Callbacks
// -----------------------
const onSliderDragged = useMemo(
() => (value: number) => {
const selectedValue = value / 100
setAmountCallback(maxUsableAmount * selectedValue)
if (checkForMaxValue) {
setSliderValue(value)
}
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[setAmountCallback, maxUsableAmount],
)
useEffect(() => {
if (inputRef?.current) inputRef.current.focus()
}, [])
useEffect(
() => {
if (asset.denom !== baseCurrency.denom || !checkForMaxValue) return
if (
(activeView === ViewType.Repay && walletBalance <= amount) ||
(activeView === ViewType.Deposit && amount >= maxUsableAmount)
) {
if (!depositWarning) setDepositWarning(true)
} else {
if (depositWarning) setDepositWarning(false)
}
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[sliderValue, amount, maxUsableAmount],
)
// -------------
// Presentation
// -------------
const produceUpperInputInfo = () => {
return (
<Button
color='quaternary'
className={`overline ${styles.available}`}
onClick={() => setAmountCallback(maxUsableAmount)}
text={availableText}
variant='transparent'
/>
)
}
const suffix = lookupSymbol(asset.denom, whitelistedAssets || []) || asset.denom
const produceWarningComponent = useCallback(() => {
let text = ''
switch (activeView) {
case ViewType.Deposit:
text =
amountUntilDepositCap <= 0
? t('redbank.warning.depositCapReached', {
symbol: asset.symbol,
})
: amount > amountUntilDepositCap
? t('redbank.warning.depositCap', {
symbol: asset.symbol,
amount: amountUntilDepositCap / 10 ** asset.decimals,
})
: ''
break
case ViewType.Withdraw:
text = maxUsableAmount < 1 ? t('redbank.warning.withdraw') : ''
break
case ViewType.Borrow:
text = maxUsableAmount < 1 ? t('redbank.warning.borrow') : ''
break
}
return <span className={`${styles.warning}`}>{text}</span>
}, [activeView, amountUntilDepositCap, amount, asset, t, maxUsableAmount])
return (
<div>
{/* INPUT SECTION */}
<div className={styles.container}>
{produceUpperInputInfo()}
<div className={styles.inputWrapper}>
<CurrencyInput
allowNegativeValue={false}
autoFocus={true}
className={`h4 number ${styles.inputPercentage}`}
decimalSeparator='.'
decimalsLimit={asset.decimals}
disableAbbreviations={true}
disabled={disabled}
groupSeparator=','
name='currencyInput'
onKeyPress={(event) => {
if (event.key === 'Enter') {
onEnterHandler()
}
}}
onValueChange={(value) => {
if (value?.charAt(value.length - 1) !== '.') {
inputCallback(parseNumberInput(value))
setFakeAmount(undefined)
} else {
setFakeAmount(value)
}
}}
placeholder='0'
suffix={` ${suffix}`}
value={
fakeAmount
? fakeAmount
: amount
? lookup(
Number(formatValue(amount, 0, 0, false, false, false, false, false)),
asset.denom,
asset.decimals,
)
: ''
}
/>
</div>
<DisplayCurrency
className={`overline ${styles.inputRaw}`}
coin={{ amount: amount.toString(), denom: asset.denom }}
/>
<div className={styles.inputContainer}>
<button
className={`${styles.sliderButton} ${styles.zero}`}
disabled={disabled}
onClick={() => setAmountCallback(0)}
>
0
</button>
<div className={styles.input}>
<InputSlider
disabled={disabled}
enabled={sliderEnabled}
onChange={onSliderDragged}
value={sliderDisplayValue}
/>
{depositWarning && (
<div className={styles.inputWarning}>
<p className='tippyContainer'>{t('common.lowUstAmountAfterTransaction')}</p>
</div>
)}
</div>
<button
className={`caption ${styles.sliderButton}`}
disabled={disabled}
onClick={() => setAmountCallback(maxUsableAmount)}
>
{t('global.max')}
</button>
</div>
{produceWarningComponent()}
<div className={styles.actionButton}>{actionButton}</div>
</div>
</div>
)
}