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

📟 Fix the InputNumber behavior #4825

Merged
merged 2 commits into from
Apr 8, 2024
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
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
Loading