manifold/web/components/amount-input.tsx

185 lines
4.9 KiB
TypeScript
Raw Normal View History

2022-01-11 03:41:42 +00:00
import clsx from 'clsx'
import React, { useState } from 'react'
import { useUser } from 'web/hooks/use-user'
import { formatMoney } from 'common/util/format'
2022-01-11 03:41:42 +00:00
import { Col } from './layout/col'
2022-05-17 12:17:22 +00:00
import { ENV_CONFIG } from 'common/envs/constants'
import { Row } from './layout/row'
import { AddFundsModal } from './add-funds-modal'
import { Input } from './input'
import Slider from 'rc-slider'
import 'rc-slider/assets/index.css'
2022-01-11 03:41:42 +00:00
export function AmountInput(props: {
amount: number | undefined
onChange: (newAmount: number | undefined) => void
error: string | undefined
label: string
2022-01-11 03:41:42 +00:00
disabled?: boolean
className?: string
inputClassName?: string
// Needed to focus the amount input
inputRef?: React.MutableRefObject<any>
2022-01-11 03:41:42 +00:00
}) {
const {
amount,
onChange,
error,
label,
2022-01-11 03:41:42 +00:00
disabled,
className,
inputClassName,
inputRef,
2022-01-11 03:41:42 +00:00
} = props
const onAmountChange = (str: string) => {
const amount = parseInt(str.replace(/\D/g, ''))
const isInvalid = !str || isNaN(amount)
onChange(isInvalid ? undefined : amount)
2022-01-11 03:41:42 +00:00
}
const [addFundsModalOpen, setAddFundsModalOpen] = useState(false)
2022-01-11 03:41:42 +00:00
return (
<>
<Col className={clsx('relative', className)}>
<label className="font-sm md:font-lg relative">
<span className="text-greyscale-4 absolute top-1/2 my-auto ml-2 -translate-y-1/2">
{label}
</span>
<Input
className={clsx('w-24 pl-9 !text-base md:w-auto', inputClassName)}
ref={inputRef}
type="text"
pattern="[0-9]*"
inputMode="numeric"
placeholder="0"
maxLength={6}
value={amount ?? ''}
error={!!error}
disabled={disabled}
onChange={(e) => onAmountChange(e.target.value)}
/>
</label>
{error && (
<div className="absolute -bottom-5 whitespace-nowrap text-xs font-medium tracking-wide text-red-500">
{error === 'Insufficient balance' ? (
<>
Not enough funds.
<button
className="ml-1 text-indigo-500 hover:underline hover:decoration-indigo-400"
onClick={() => setAddFundsModalOpen(true)}
>
Buy more?
</button>
<AddFundsModal
open={addFundsModalOpen}
setOpen={setAddFundsModalOpen}
/>
</>
) : (
error
)}
</div>
)}
</Col>
</>
)
}
export function BuyAmountInput(props: {
amount: number | undefined
onChange: (newAmount: number | undefined) => void
error: string | undefined
setError: (error: string | undefined) => void
minimumAmount?: number
disabled?: boolean
2022-09-05 22:11:32 +00:00
showSliderOnMobile?: boolean
className?: string
inputClassName?: string
// Needed to focus the amount input
inputRef?: React.MutableRefObject<any>
}) {
const {
amount,
onChange,
error,
setError,
2022-09-05 22:11:32 +00:00
showSliderOnMobile: showSlider,
disabled,
className,
inputClassName,
minimumAmount,
inputRef,
} = props
const user = useUser()
const onAmountChange = (amount: number | undefined) => {
onChange(amount)
// Check for errors.
if (amount !== undefined) {
2022-04-13 17:52:12 +00:00
if (user && user.balance < amount) {
setError('Insufficient balance')
} else if (minimumAmount && amount < minimumAmount) {
setError('Minimum amount: ' + formatMoney(minimumAmount))
} else {
setError(undefined)
}
} else {
setError(undefined)
}
}
const parseRaw = (x: number) => {
if (x <= 100) return x
if (x <= 130) return 100 + (x - 100) * 5
return 250 + (x - 130) * 10
}
const getRaw = (x: number) => {
if (x <= 100) return x
if (x <= 250) return 100 + (x - 100) / 5
return 130 + (x - 250) / 10
}
return (
2022-09-05 22:11:32 +00:00
<>
<Row className="items-center gap-4">
<AmountInput
amount={amount}
onChange={onAmountChange}
label={ENV_CONFIG.moneyMoniker}
error={error}
disabled={disabled}
className={className}
inputClassName={inputClassName}
inputRef={inputRef}
2022-09-05 22:11:32 +00:00
/>
{showSlider && (
<Slider
min={0}
max={205}
value={getRaw(amount ?? 0)}
onChange={(value) => onAmountChange(parseRaw(value as number))}
className="mx-4 !h-4 xl:hidden [&>.rc-slider-rail]:bg-gray-200 [&>.rc-slider-track]:bg-indigo-400 [&>.rc-slider-handle]:bg-indigo-400"
railStyle={{ height: 16, top: 0, left: 0 }}
trackStyle={{ height: 16, top: 0 }}
handleStyle={{
height: 32,
width: 32,
opacity: 1,
border: 'none',
boxShadow: 'none',
top: -2,
}}
step={5}
/>
)}
</Row>
2022-09-05 22:11:32 +00:00
</>
)
}