diff --git a/packages/ui/src/common/components/forms/InputNumber.tsx b/packages/ui/src/common/components/forms/InputNumber.tsx
index d809874eb0..7bd5fd48dd 100644
--- a/packages/ui/src/common/components/forms/InputNumber.tsx
+++ b/packages/ui/src/common/components/forms/InputNumber.tsx
@@ -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'
@@ -55,7 +55,7 @@ export const InputNumber = React.memo(({ name, isInBN = false, ...props }: Numbe
return
}
- const exp = props.decimalScale ?? 0
+ const exp = 10 ** (props.decimalScale ?? 0)
return (
{
- 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}
diff --git a/packages/ui/src/common/utils/bn.ts b/packages/ui/src/common/utils/bn.ts
index 0a11b6c24e..ed268099cd 100644
--- a/packages/ui/src/common/utils/bn.ts
+++ b/packages/ui/src/common/utils/bn.ts
@@ -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
+}
diff --git a/packages/ui/src/common/utils/validation.tsx b/packages/ui/src/common/utils/validation.tsx
index 21abd6b935..160d33928b 100644
--- a/packages/ui/src/common/utils/validation.tsx
+++ b/packages/ui/src/common/utils/validation.tsx
@@ -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,
diff --git a/packages/ui/src/memberships/modals/TransferInviteModal/TransferInviteFormModal.tsx b/packages/ui/src/memberships/modals/TransferInviteModal/TransferInviteFormModal.tsx
index 7607efd1a5..cbcdbcde18 100644
--- a/packages/ui/src/memberships/modals/TransferInviteModal/TransferInviteFormModal.tsx
+++ b/packages/ui/src/memberships/modals/TransferInviteModal/TransferInviteFormModal.tsx
@@ -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'
@@ -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'),
diff --git a/packages/ui/src/proposals/modals/AddNewProposal/helpers.ts b/packages/ui/src/proposals/modals/AddNewProposal/helpers.ts
index 38f7010777..fe9a691578 100644
--- a/packages/ui/src/proposals/modals/AddNewProposal/helpers.ts
+++ b/packages/ui/src/proposals/modals/AddNewProposal/helpers.ts
@@ -8,6 +8,7 @@ import { QuestionValueProps } from '@/common/components/EditableInputList/Editab
import { isDefined } from '@/common/utils'
import {
BNSchema,
+ NumberSchema,
lessThanMixed,
maxContext,
maxMixed,
@@ -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'),
}),
@@ -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({
@@ -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({
@@ -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'),
}),
@@ -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) => {
diff --git a/packages/ui/src/working-groups/modals/CreateOpening/types.tsx b/packages/ui/src/working-groups/modals/CreateOpening/types.tsx
index 4cfc63a95d..82c21abedb 100644
--- a/packages/ui/src/working-groups/modals/CreateOpening/types.tsx
+++ b/packages/ui/src/working-groups/modals/CreateOpening/types.tsx
@@ -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 {
@@ -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(