Skip to content

Commit

Permalink
Merge pull request #1253 from satoshipay/bugfix/fix-trading-form-inpu…
Browse files Browse the repository at this point in the history
…t-field-issues

Fix trading form input field issues
  • Loading branch information
andywer authored Jun 3, 2021
2 parents cd4b436 + d432bf8 commit 27fe090
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 30 deletions.
5 changes: 3 additions & 2 deletions i18n/locales/en/trading.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@
"validation": {
"primary-amount-missing": "No amount specified.",
"primary-asset-missing": "No asset selected.",
"secondary-asset-missing": "No asset selected.",
"invalid-amount": "Invalid amount specified.",
"invalid-price": "Invalid price"
"invalid-price": "Invalid price",
"not-enough-balance": "Exceeds your spendable balance",
"secondary-asset-missing": "No asset selected."
},
"warning": {
"title": "Warning",
Expand Down
2 changes: 1 addition & 1 deletion src/Assets/components/CustomTrustline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function CustomTrustlineDialog(props: Props) {
/>
<TextField
inputProps={{
pattern: "[0-9]*",
pattern: "^[0-9]*(.[0-9]+)?$",
inputMode: "decimal"
}}
fullWidth
Expand Down
4 changes: 0 additions & 4 deletions src/Generic/components/FormFields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ export const PriceInput = React.memo(function PriceInput(props: PriceInputProps)
),
...textfieldProps.InputProps
}}
type={
// Prevent `The specified value (...) is not a valid number` warning
readOnly ? undefined : "number"
}
style={{
pointerEvents: props.readOnly ? "none" : undefined,
...textfieldProps.style
Expand Down
18 changes: 18 additions & 0 deletions src/Generic/lib/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import BigNumber, { BigSource } from "big.js"

export const isValidAmount = (amount: string) => /^[0-9]*([\.,][0-9]+)?$/.test(amount)

// replaces ',' with '.' in a string
export function replaceCommaWithDot(input: string) {
return input.replace(/,/g, ".")
}

// should be used when creating a Big from user input because there are issues with
// parsing a Big from number-strings with a comma on iOS
export function FormBigNumber(value: BigSource) {
if (typeof value === "string") {
return BigNumber(replaceCommaWithDot(value))
} else {
return BigNumber(value)
}
}
14 changes: 11 additions & 3 deletions src/Payment/components/PaymentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { PriceInput, QRReader } from "~Generic/components/FormFields"
import { formatBalance } from "~Generic/lib/balances"
import { HorizontalLayout } from "~Layout/components/Box"
import Portal from "~Generic/components/Portal"
import { FormBigNumber, isValidAmount, replaceCommaWithDot } from "~Generic/lib/form"

export interface PaymentFormValues {
amount: string
Expand Down Expand Up @@ -211,8 +212,15 @@ const PaymentForm = React.memo(function PaymentForm(props: PaymentFormProps) {
error={Boolean(form.errors.amount)}
inputRef={form.register({
required: t<string>("payment.validation.no-price"),
pattern: { value: /^[0-9]+(\.[0-9]+)?$/, message: t<string>("payment.validation.invalid-price") },
validate: value => BigNumber(value).lte(spendableBalance) || t<string>("payment.validation.not-enough-funds")
validate: value => {
if (!isValidAmount(value) || FormBigNumber(value).eq(0)) {
return t<string>("payment.validation.invalid-price")
} else if (FormBigNumber(value).gt(spendableBalance)) {
return t<string>("payment.validation.not-enough-funds")
} else {
return undefined
}
}
})}
label={form.errors.amount ? form.errors.amount.message : t("payment.inputs.price.label")}
margin="normal"
Expand Down Expand Up @@ -355,7 +363,7 @@ function PaymentFormContainer(props: Props) {

const payment = await createPaymentOperation({
asset: asset || Asset.native(),
amount: formValues.amount,
amount: replaceCommaWithDot(formValues.amount),
destination,
horizon
})
Expand Down
31 changes: 21 additions & 10 deletions src/Trading/components/TradingForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import BigNumber from "big.js"
import React from "react"
import { Controller, useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
Expand Down Expand Up @@ -32,9 +31,10 @@ import {
getAccountMinimumBalance,
getSpendableBalance
} from "~Generic/lib/stellar"
import { FormBigNumber, isValidAmount } from "~Generic/lib/form"
import { createTransaction } from "~Generic/lib/transaction"
import { Box, HorizontalLayout, VerticalLayout } from "~Layout/components/Box"
import { bigNumberToInputValue, isValidAmount, TradingFormValues, useCalculation } from "../hooks/form"
import { bigNumberToInputValue, TradingFormValues, useCalculation } from "../hooks/form"
import TradingPrice from "./TradingPrice"

const useStyles = makeStyles({
Expand Down Expand Up @@ -141,8 +141,8 @@ function TradingForm(props: Props) {
}

const validateManualPrice = React.useCallback(() => {
const value = BigNumber(manualPrice).gt(0) ? manualPrice : defaultPrice
const valid = isValidAmount(value) && BigNumber(value).gt(0)
const value = FormBigNumber(manualPrice).gt(0) ? manualPrice : defaultPrice
const valid = isValidAmount(value) && FormBigNumber(value).gt(0)
if (!valid) {
if (!expanded) {
setExpanded(true)
Expand Down Expand Up @@ -272,17 +272,23 @@ function TradingForm(props: Props) {
inputRef={form.register({
required: t<string>("trading.validation.primary-amount-missing"),
validate: value => {
const amountInvalid =
primaryAmount.lt(0) ||
(value.length > 0 && primaryAmount.eq(0)) ||
const amountInvalid = primaryAmount.lt(0) || (value.length > 0 && primaryAmount.eq(0))
const exceedsBalance =
(props.primaryAction === "sell" && primaryBalance && primaryAmount.gt(spendablePrimaryBalance)) ||
(props.primaryAction === "buy" && secondaryBalance && secondaryAmount.gt(spendableSecondaryBalance))
return !amountInvalid || t<string>("trading.validation.invalid-amount")

if (amountInvalid) {
return t<string>("trading.validation.invalid-amount")
} else if (exceedsBalance) {
return t<string>("trading.validation.not-enough-balance")
} else {
return true
}
}
})}
error={Boolean(form.errors.primaryAmountString)}
inputProps={{
pattern: "[0-9]*",
pattern: "^[0-9]*(.[0-9]+)?$",
inputMode: "decimal",
min: "0.0000001",
max: maxPrimaryAmount.toFixed(7),
Expand Down Expand Up @@ -320,7 +326,6 @@ function TradingForm(props: Props) {
)}
required
style={{ flexGrow: 1, flexShrink: 1, width: "55%" }}
type="number"
/>
</HorizontalLayout>
<HorizontalLayout margin="8px 0 32px">
Expand Down Expand Up @@ -394,6 +399,12 @@ function TradingForm(props: Props) {
}
control={form.control}
name="manualPrice"
rules={{
validate: value => {
const valid = isValidAmount(value)
return valid || t<string>("trading.validation.invalid-price")
}
}}
valueName="manualPrice"
/>
</ExpansionPanelDetails>
Expand Down
3 changes: 1 addition & 2 deletions src/Trading/components/TradingPrice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const TradingPrice = React.forwardRef(function TradingPrice(props: TradingPriceP
return (
<TextField
inputProps={{
pattern: "[0-9]*",
pattern: "^[0-9]*(.[0-9]+)?$",
inputMode: "decimal",
min: "0.0000001"
}}
Expand All @@ -64,7 +64,6 @@ const TradingPrice = React.forwardRef(function TradingPrice(props: TradingPriceP
onChange={props.onChange}
onFocus={props.selectOnFocus ? event => event.target.select() : undefined}
style={props.style}
type="number"
value={props.defaultPrice ? props.defaultPrice : props.manualPrice}
/>
)
Expand Down
13 changes: 6 additions & 7 deletions src/Trading/hooks/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import BigNumber from "big.js"
import { Asset, Horizon } from "stellar-sdk"
import { AccountData } from "~Generic/lib/account"
import { formatBalance, BalanceFormattingOptions } from "~Generic/lib/balances"
import { FormBigNumber, isValidAmount } from "~Generic/lib/form"
import { calculateSpread, FixedOrderbookRecord } from "~Generic/lib/orderbook"
import { BASE_RESERVE, balancelineToAsset, getAccountMinimumBalance, getSpendableBalance } from "~Generic/lib/stellar"
import { useConversionOffers } from "./conversion"

export const bigNumberToInputValue = (bignum: BigNumber, overrides?: BalanceFormattingOptions) =>
formatBalance(bignum, { minimumSignificants: 3, maximumSignificants: 9, ...overrides })

export const isValidAmount = (amount: string) => /^[0-9]+([\.,][0-9]+)?$/.test(amount)
formatBalance(bignum, { minimumSignificants: 3, maximumSignificants: 9, groupThousands: false, ...overrides })

function findMatchingBalance(balances: AccountData["balances"], asset: Asset) {
return balances.find(balance => balancelineToAsset(balance).equals(asset))
Expand Down Expand Up @@ -61,14 +60,14 @@ export function useCalculation(parameters: CalculationParameters): CalculationRe
const price =
manualPrice && isValidAmount(manualPrice)
? priceMode === "secondary"
? BigNumber(manualPrice)
: BigNumber(manualPrice).eq(0) // prevent division by zero
? FormBigNumber(manualPrice)
: FormBigNumber(manualPrice).eq(0) // prevent division by zero
? BigNumber(0)
: BigNumber(1).div(manualPrice)
: BigNumber(1).div(FormBigNumber(manualPrice))
: BigNumber(0)

const primaryAmount =
primaryAmountString && isValidAmount(primaryAmountString) ? BigNumber(primaryAmountString) : BigNumber(0)
primaryAmountString && isValidAmount(primaryAmountString) ? FormBigNumber(primaryAmountString) : BigNumber(0)

const primaryBalance = primaryAsset ? findMatchingBalance(accountData.balances, primaryAsset) : undefined
const secondaryBalance = secondaryAsset ? findMatchingBalance(accountData.balances, secondaryAsset) : undefined
Expand Down
2 changes: 1 addition & 1 deletion src/Transaction/stories/SubmissionProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ storiesOf("SubmissionProgress", module)
<SubmissionProgress
type={SubmissionType.default}
promise={Promise.reject(new Error("Test error"))}
onRetry={() => undefined}
onRetry={() => Promise.resolve()}
/>
))

0 comments on commit 27fe090

Please sign in to comment.