| 62 | }; |
| 63 | |
| 64 | export const numberRangeRule = (opts: { decimals?: number; min?: number; max?: number } = {}) => { |
| 65 | return (value: unknown): string | boolean => { |
| 66 | const hasValue = !!value; |
| 67 | if (!hasValue) { |
| 68 | // this rule only applies if there is a value |
| 69 | return true; |
| 70 | } |
| 71 | |
| 72 | const min = opts.min ?? Number.MIN_SAFE_INTEGER; |
| 73 | const max = opts.max ?? Number.MAX_SAFE_INTEGER; |
| 74 | const allowedDecimalPlaces = opts.decimals ?? 0; |
| 75 | |
| 76 | const parsedValue = opts.decimals ? parseFloat(`${value}`) : parseInt(`${value}`, 10); |
| 77 | const parsedDecimalsLength = `${parsedValue}`.split('.')[1]?.length ?? 0; |
| 78 | |
| 79 | if (isNaN(parsedValue)) { |
| 80 | return i18n.global.t('forms.rules.requiredNumber'); |
| 81 | } |
| 82 | |
| 83 | if (allowedDecimalPlaces <= 0 && Number(value) % 1 !== 0) { |
| 84 | return i18n.global.t('forms.rules.requiredIntNumber'); |
| 85 | } |
| 86 | |
| 87 | if (allowedDecimalPlaces > 0 && parsedDecimalsLength > allowedDecimalPlaces) { |
| 88 | return i18n.global.t('forms.rules.invalidDecimalPlaces', { decimals: allowedDecimalPlaces }); |
| 89 | } |
| 90 | |
| 91 | return parsedValue >= min && parsedValue <= max |
| 92 | ? true |
| 93 | : i18n.global.t('forms.rules.numberRange', { min, max }); |
| 94 | }; |
| 95 | }; |
| 96 | |
| 97 | export const maxLengthRule = (max: number, field: string) => { |
| 98 | return (value: unknown): string | boolean => { |