fix(dashboard): support more decimals for tx rates (#13407)

* Percentage formatter set maximum digits to 4

* Modify Tax Region Forms to allow 4 digits tax rates

* Add changeset

* Revert placeholder value to use only 2 decimals

* fix(dashboard): support more decimals for tx rates

* changeset

* bypass scale

* refactor

* fix removal of deprecated input

* cleanup deprecated percentage input

* small mistake in onchange

* add deprecated percentage input back

* import

* remove file extension

* frane comment

---------

Co-authored-by: AmbroziuBaban <ambroziubaban@gmail.com>
This commit is contained in:
William BouchardandAmbroziuBaban authored and GitHub committed 2025-09-05 13:02:44 +02:00
1 parent 0000ed6998
commit 9b3831d258
10 files changed
+75 -14

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/dashboard": patch
---
fix(dashboard): support more decimals for tx rates
@@ -1,10 +1,22 @@
import { Input, Text, clx } from "@medusajs/ui" import { clx, Input, Text } from "@medusajs/ui"
import { getNumberOfDecimalPlaces } from "../../../lib/number-helper"
import { ComponentProps, ElementRef, forwardRef } from "react" import { ComponentProps, ElementRef, forwardRef } from "react"
import Primitive from "react-currency-input-field" import Primitive from "react-currency-input-field"
/** const MIN_DECIMAL_SCALE = 2
* @deprecated Use `PercentageInput` instead
*/ function resolveDecimalScale(
value: string | readonly string[] | number | undefined | null
): number | undefined {
if (value == null || Array.isArray(value)) {
return MIN_DECIMAL_SCALE
}
return Math.max(
getNumberOfDecimalPlaces(parseFloat(value.toString())),
MIN_DECIMAL_SCALE
)
}
export const DeprecatedPercentageInput = forwardRef< export const DeprecatedPercentageInput = forwardRef<
ElementRef<typeof Input>, ElementRef<typeof Input>,
Omit<ComponentProps<typeof Input>, "type"> Omit<ComponentProps<typeof Input>, "type">
@@ -38,18 +50,35 @@ DeprecatedPercentageInput.displayName = "PercentageInput"
export const PercentageInput = forwardRef< export const PercentageInput = forwardRef<
ElementRef<"input">, ElementRef<"input">,
ComponentProps<typeof Primitive> ComponentProps<typeof Primitive>
>(({ min = 0, decimalScale = 2, className, ...props }, ref) => { >(
(
{
min = 0,
max = 100,
decimalScale,
decimalsLimit,
value,
className,
...props
},
ref
) => {
const resolvedDecimalScale = decimalScale ?? resolveDecimalScale(value)
const resolvedDecimalsLimit = decimalsLimit ?? resolvedDecimalScale
return ( return (
<div className="relative"> <div className="relative">
<Primitive <Primitive
ref={ref as any} // dependency is typed incorrectly ref={ref as any} // dependency is typed incorrectly
min={min} min={min}
max={max}
autoComplete="off" autoComplete="off"
decimalScale={decimalScale} decimalScale={resolvedDecimalScale}
decimalsLimit={decimalScale} decimalsLimit={resolvedDecimalsLimit}
value={value}
{...props} {...props}
className={clx( className={clx(
"caret-ui-fg-base bg-ui-bg-field shadow-buttons-neutral transition-fg txt-compact-small flex w-full select-none appearance-none items-center justify-between rounded-md px-2 py-1.5 pr-10 text-right outline-none", "caret-ui-fg-base bg-ui-bg-field shadow-buttons-neutral transition-fg txt-compact-small flex w-full select-none appearance-none items-center justify-between rounded-md px-2 py-1.5 pl-10 text-left outline-none",
"placeholder:text-ui-fg-muted text-ui-fg-base", "placeholder:text-ui-fg-muted text-ui-fg-base",
"hover:bg-ui-bg-field-hover", "hover:bg-ui-bg-field-hover",
"focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active", "focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active",
@@ -59,7 +88,7 @@ export const PercentageInput = forwardRef<
className className
)} )}
/> />
<div className="absolute inset-y-0 right-0 z-10 flex w-8 items-center justify-center border-l"> <div className="absolute inset-y-0 left-0 z-10 flex w-8 items-center justify-center border-r">
<Text <Text
className="text-ui-fg-muted" className="text-ui-fg-muted"
size="small" size="small"
@@ -71,5 +100,6 @@ export const PercentageInput = forwardRef<
</div> </div>
</div> </div>
) )
}) }
)
PercentageInput.displayName = "PercentageInput" PercentageInput.displayName = "PercentageInput"
@@ -0,0 +1,19 @@
/**
* Gets the number of decimal places in a number
* @param num - The number for which we are getting the number of decimal places
* @returns The number of decimal places
*
* @example
* getDecimalPlaces(123.456) // 3
* getDecimalPlaces(42) // 0
* getDecimalPlaces(10.0000) // 0
*/
export function getNumberOfDecimalPlaces(num: number): number {
// Convert to string and check if it contains a decimal point
const str = num.toString()
if (str.indexOf(".") === -1) {
return 0
}
// Return the length of the part after the decimal point
return str.split(".")[1].length
}
@@ -1,6 +1,7 @@
const formatter = new Intl.NumberFormat([], { const formatter = new Intl.NumberFormat([], {
style: "percent", style: "percent",
minimumFractionDigits: 2, minimumFractionDigits: 2,
maximumFractionDigits: 4,
}) })
/** /**
@@ -3,7 +3,7 @@ import { useForm } from "react-hook-form"
import { z } from "zod" import { z } from "zod"
import { InformationCircleSolid } from "@medusajs/icons" import { InformationCircleSolid } from "@medusajs/icons"
import { Button, Heading, Input, Text, Tooltip, toast } from "@medusajs/ui" import { Button, Heading, Input, Text, toast, Tooltip } from "@medusajs/ui"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { CountrySelect } from "../../../../../components/inputs/country-select" import { CountrySelect } from "../../../../../components/inputs/country-select"
@@ -221,6 +221,7 @@ export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => {
<PercentageInput <PercentageInput
{...field} {...field}
value={value?.value} value={value?.value}
decimalsLimit={4}
onValueChange={(value, _name, values) => onValueChange={(value, _name, values) =>
onChange({ onChange({
value: value, value: value,
@@ -1,7 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { InformationCircleSolid } from "@medusajs/icons" import { InformationCircleSolid } from "@medusajs/icons"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { Button, Heading, Input, Text, Tooltip, toast } from "@medusajs/ui" import { Button, Heading, Input, Text, toast, Tooltip } from "@medusajs/ui"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { z } from "zod" import { z } from "zod"
@@ -189,6 +189,7 @@ export const TaxRegionProvinceCreateForm = ({
<PercentageInput <PercentageInput
{...field} {...field}
value={value?.value} value={value?.value}
decimalsLimit={4}
onValueChange={(value, _name, values) => onValueChange={(value, _name, values) =>
onChange({ onChange({
value: value, value: value,
@@ -1,6 +1,7 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { import {
Button, Button,
clx,
Divider, Divider,
Heading, Heading,
Hint, Hint,
@@ -8,7 +9,6 @@ import {
Label, Label,
Select, Select,
Text, Text,
clx,
toast, toast,
} from "@medusajs/ui" } from "@medusajs/ui"
import { useFieldArray, useForm, useWatch } from "react-hook-form" import { useFieldArray, useForm, useWatch } from "react-hook-form"
@@ -414,6 +414,7 @@ export const TaxRegionCreateTaxOverrideForm = ({
<PercentageInput <PercentageInput
{...field} {...field}
placeholder="0.00" placeholder="0.00"
decimalsLimit={4}
value={value?.value} value={value?.value}
onValueChange={(value, _name, values) => onValueChange={(value, _name, values) =>
onChange({ onChange({
@@ -3,6 +3,7 @@ import { MagnifyingGlass } from "@medusajs/icons"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { import {
Button, Button,
clx,
Divider, Divider,
Heading, Heading,
Hint, Hint,
@@ -10,7 +11,6 @@ import {
Label, Label,
Select, Select,
Text, Text,
clx,
toast, toast,
} from "@medusajs/ui" } from "@medusajs/ui"
import { useFieldArray, useForm, useWatch } from "react-hook-form" import { useFieldArray, useForm, useWatch } from "react-hook-form"
@@ -414,6 +414,7 @@ export const TaxRegionTaxOverrideEditForm = ({
<PercentageInput <PercentageInput
{...field} {...field}
value={value?.value} value={value?.value}
decimalsLimit={4}
onValueChange={(value, _name, values) => onValueChange={(value, _name, values) =>
onChange({ onChange({
value: value, value: value,
@@ -119,6 +119,7 @@ export const TaxRegionTaxRateCreateForm = ({
<PercentageInput <PercentageInput
{...field} {...field}
value={value?.value} value={value?.value}
decimalsLimit={4}
onValueChange={(value, _name, values) => onValueChange={(value, _name, values) =>
onChange({ onChange({
value: value, value: value,
@@ -117,6 +117,7 @@ export const TaxRegionTaxRateEditForm = ({
<PercentageInput <PercentageInput
{...field} {...field}
value={value?.value} value={value?.value}
decimalsLimit={4}
onValueChange={(value, _name, values) => onValueChange={(value, _name, values) =>
onChange({ onChange({
value: value, value: value,