Skip to content

Commit

Permalink
📟 Fix the InputNumber behavior (#4825)
Browse files Browse the repository at this point in the history
* Fix the InputNumber behavior

* Fix the validation by replacing `null` by `undefined`
  • Loading branch information
thesan authored Apr 8, 2024
1 parent 18ea9c5 commit 7d409d8
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 43 deletions.
19 changes: 13 additions & 6 deletions packages/ui/src/common/components/forms/InputNumber.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useFormContext, Controller } from 'react-hook-form'
import NumberFormat, { NumberFormatValues, SourceInfo } from 'react-number-format'
import styled from 'styled-components'

import { asBN, powerOf10, whenDefined } from '@/common/utils'
import { asBN, divToNum, whenDefined } from '@/common/utils'

import { Input, InputProps } from './InputComponent'

Expand Down Expand Up @@ -55,7 +55,7 @@ export const InputNumber = React.memo(({ name, isInBN = false, ...props }: Numbe
return <BasedInputNumber {...props} />
}

const exp = props.decimalScale ?? 0
const exp = 10 ** (props.decimalScale ?? 0)

return (
<Controller
Expand All @@ -65,11 +65,18 @@ export const InputNumber = React.memo(({ name, isInBN = false, ...props }: Numbe
<BasedInputNumber
{...props}
value={whenDefined(field.value, (value) => {
const num = isInBN ? asBN(value).toNumber() : value // TODO convert to number safely
return String(num / 10 ** exp)
if (value === undefined || value === null) {
return ''
}
const numValue = isInBN ? divToNum(asBN(value), exp) : value / exp
return numValue.toFixed(props.decimalScale ?? 0)
})}
onChange={(_, numValue) => {
const value = isInBN ? new BN(numValue).mul(powerOf10(exp)) : numValue * 10 ** exp
onChange={(event, numValue) => {
if (!event) return
if (event.target.value === '') {
return field.onChange(null)
}
const value = isInBN ? new BN(numValue).muln(exp) : numValue * exp
return field.onChange(value)
}}
onBlur={field.onBlur}
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/src/common/utils/bn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ export const sumBN = (a: BN | undefined, b: BN | undefined): BN => new BN(a ?? 0
export const powerOf10 = (value: any) => BN_TEN.pow(asBN(value))

export const powerOf2 = (value: any) => BN_TWO.pow(asBN(value))

export const divToNum = (dividend: BN, divisor: number): number => {
const div = dividend.divmod(new BN(divisor))
return div.div.toNumber() + div.mod.toNumber() / divisor
}
2 changes: 2 additions & 0 deletions packages/ui/src/common/utils/validation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { formatJoyValue } from '@/common/model/formatters'

export const BNSchema = Yup.mixed()

export const NumberSchema = Yup.number().transform((_, value) => value ?? undefined)

export const whenDefined = (key: string, schema: Yup.AnySchema) =>
Yup.mixed().when(key, {
is: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ButtonPrimary } from '@/common/components/buttons'
import { InputComponent, InputNumber } from '@/common/components/forms'
import { Modal, ModalBody, ModalFooter, ModalHeader, Row, TransactionAmount } from '@/common/components/Modal'
import { TextMedium } from '@/common/components/typography'
import { maxContext, useYupValidationResolver } from '@/common/utils/validation'
import { maxContext, NumberSchema, useYupValidationResolver } from '@/common/utils/validation'
import { InviteMembershipFormFields } from '@/memberships/modals/BuyMembershipModal/BuyMembershipFormModal'
import { AccountSchema } from '@/memberships/model/validation'

Expand All @@ -29,8 +29,7 @@ const formDefaultValues = {

const TransferInviteSchema = Yup.object().shape({
from: AccountSchema.required('This field is required'),
amount: Yup.number()
.min(1, 'Number of invitation has to be greater than 0')
amount: NumberSchema.min(1, 'Number of invitation has to be greater than 0')
.test(maxContext('You only have ${max} invites left.', 'inviteCount', false))
.required('This field is required'),
to: AccountSchema.required('This field is required'),
Expand Down
48 changes: 19 additions & 29 deletions packages/ui/src/proposals/modals/AddNewProposal/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { QuestionValueProps } from '@/common/components/EditableInputList/Editab
import { isDefined } from '@/common/utils'
import {
BNSchema,
NumberSchema,
lessThanMixed,
maxContext,
maxMixed,
Expand Down Expand Up @@ -224,8 +225,7 @@ export const schemaFactory = (api?: Api) => {
trigger: Yup.boolean(),
triggerBlock: Yup.number().when('trigger', {
is: true,
then: Yup.number()
.test(minContext('The minimum block number is ${min}', 'minTriggerBlock', false))
then: NumberSchema.test(minContext('The minimum block number is ${min}', 'minTriggerBlock', false))
.test(maxContext('The maximum block number is ${max}', 'maxTriggerBlock', false))
.required('Field is required'),
}),
Expand Down Expand Up @@ -300,7 +300,7 @@ export const schemaFactory = (api?: Api) => {
isLimited: Yup.boolean(),
duration: Yup.number().when('isLimited', {
is: true,
then: Yup.number().required('Field is required'),
then: NumberSchema.required('Field is required'),
}),
}),
applicationForm: Yup.object().shape({
Expand Down Expand Up @@ -369,20 +369,19 @@ export const schemaFactory = (api?: Api) => {
),
}),
setReferralCut: Yup.object().shape({
referralCut: Yup.number()
.test(
maxContext(
'Input must be equal or less than ${max}% for proposal to execute',
'maximumReferralCut',
false,
'execution'
)
referralCut: NumberSchema.test(
maxContext(
'Input must be equal or less than ${max}% for proposal to execute',
'maximumReferralCut',
false,
'execution'
)
)
.max(100, 'Value exceed maximal percentage')
.required('Field is required'),
}),
setMembershipLeadInvitationQuota: Yup.object().shape({
count: Yup.number().min(1, 'Quota must be greater than zero').required('Field is required'),
count: NumberSchema.min(1, 'Quota must be greater than zero').required('Field is required'),
leadId: Yup.string().test('execution', (value) => !!value),
}),
setInitialInvitationBalance: Yup.object().shape({
Expand Down Expand Up @@ -443,8 +442,7 @@ export const schemaFactory = (api?: Api) => {
.required('Field is required'),
}),
setEraPayoutDampingFactor: Yup.object().shape({
dampingFactor: Yup.number()
.min(0, 'The value must be between 0 and 100%.')
dampingFactor: NumberSchema.min(0, 'The value must be between 0 and 100%.')
.max(100, 'The value must be between 0 and 100%.')
.required('Field is required'),
}),
Expand All @@ -462,22 +460,14 @@ export const schemaFactory = (api?: Api) => {
}),
updateTokenPalletTokenConstraints: Yup.object()
.shape({
maxYearlyRate: Yup.number()
.min(0, 'Rate must be 0 or greater')
.max(10 ** 6, 'Rate must be 100 or less'),
maxYearlyRate: NumberSchema.min(0, 'Rate must be 0 or greater').max(10 ** 6, 'Rate must be 100 or less'),
minAmmSlope: BNSchema.test(moreThanMixed(0, 'Amount must be greater than zero')),
minSaleDuration: Yup.number().min(0, 'Duration must be 0 or greater'),
minRevenueSplitDuration: Yup.number().min(0, 'Duration must be 0 or greater'),
minRevenueSplitTimeToStart: Yup.number().min(0, 'Duration must be 0 or greater'),
salePlatformFee: Yup.number()
.min(0, 'Rate must be 0 or greater')
.max(10 ** 6, 'Rate must be 100 or less'),
ammBuyTxFees: Yup.number()
.min(0, 'Rate must be 0 or greater')
.max(10 ** 6, 'Rate must be 100 or less'),
ammSellTxFees: Yup.number()
.min(0, 'Rate must be 0 or greater')
.max(10 ** 6, 'Rate must be 100 or less'),
minSaleDuration: NumberSchema.min(0, 'Duration must be 0 or greater'),
minRevenueSplitDuration: NumberSchema.min(0, 'Duration must be 0 or greater'),
minRevenueSplitTimeToStart: NumberSchema.min(0, 'Duration must be 0 or greater'),
salePlatformFee: NumberSchema.min(0, 'Rate must be 0 or greater').max(10 ** 6, 'Rate must be 100 or less'),
ammBuyTxFees: NumberSchema.min(0, 'Rate must be 0 or greater').max(10 ** 6, 'Rate must be 100 or less'),
ammSellTxFees: NumberSchema.min(0, 'Rate must be 0 or greater').max(10 ** 6, 'Rate must be 100 or less'),
bloatBond: BNSchema.test(moreThanMixed(0, 'Amount must be greater than zero')),
})
.test((fields) => {
Expand Down
10 changes: 5 additions & 5 deletions packages/ui/src/working-groups/modals/CreateOpening/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as Yup from 'yup'

import { QuestionValueProps } from '@/common/components/EditableInputList/EditableInputList'
import { ModalWithDataCall } from '@/common/providers/modal/types'
import { BNSchema, minContext, minMixed } from '@/common/utils/validation'
import { BNSchema, NumberSchema, minContext, minMixed } from '@/common/utils/validation'
import { GroupIdName, WorkingGroup } from '@/working-groups/types'

export interface OpeningModalData {
Expand Down Expand Up @@ -44,11 +44,11 @@ export const OpeningSchema = Yup.object().shape({
isLimited: Yup.boolean(),
duration: Yup.number().when('isLimited', {
is: true,
then: Yup.number().required('Duration is required'),
then: NumberSchema.required('Duration is required'),
}),
target: Yup.number()
.min(1, 'Minimum hiring target must be greater than zero')
.required('Hiring target is required'),
target: NumberSchema.min(1, 'Minimum hiring target must be greater than zero').required(
'Hiring target is required'
),
}),
stakingPolicyAndReward: Yup.object().shape({
stakingAmount: BNSchema.test(
Expand Down

0 comments on commit 7d409d8

Please sign in to comment.