fix(dashboard): fix currency input locale formatting (#12812)

* fix: refund forms and format currency util

* fix: claim form

* fix: return form

* fix: exchange form
This commit is contained in:
Frane Polić
2025-06-25 09:23:49 +02:00
committed by GitHub
parent 6ca755ede7
commit 9d61bb7e71
6 changed files with 183 additions and 76 deletions

View File

@@ -1,5 +1,5 @@
export const formatCurrency = (amount: number, currency: string) => {
return new Intl.NumberFormat("en-US", {
return new Intl.NumberFormat(undefined, {
style: "currency",
currency,
signDisplay: "auto",

View File

@@ -5,7 +5,6 @@ import {
clx,
CurrencyInput,
Divider,
Input,
Label,
RadioGroup,
Select,
@@ -15,8 +14,10 @@ import {
import { useEffect, useMemo, useState } from "react"
import { formatValue } from "react-currency-input-field"
import { useForm } from "react-hook-form"
import { useSearchParams } from "react-router-dom"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
import { Form } from "../../../../../components/common/form"
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
import { KeyboundForm } from "../../../../../components/utilities/keybound-form"
@@ -33,13 +34,19 @@ const OrderBalanceSettlementSchema = zod.object({
settlement_type: zod.enum(["credit_line", "refund"]),
refund: zod
.object({
amount: zod.string().or(zod.number()).optional(),
amount: zod.object({
value: zod.string().or(zod.number()).optional(),
float: zod.number().or(zod.null()),
}),
note: zod.string().optional(),
})
.optional(),
credit_line: zod
.object({
amount: zod.string().or(zod.number()).optional(),
amount: zod.object({
value: zod.string().or(zod.number()).optional(),
float: zod.number().or(zod.null()),
}),
note: zod.string().optional(),
})
.optional(),
@@ -51,19 +58,30 @@ export const OrderBalanceSettlementForm = ({
order: AdminOrder
}) => {
const { t } = useTranslation()
const [searchParams] = useSearchParams()
const { handleSuccess } = useRouteModal()
const [activePayment, setActivePayment] = useState<AdminPayment | null>(null)
const paymentId = searchParams.get("paymentId")
const payments = getPaymentsFromOrder(order)
const pendingDifference = order.summary.pending_difference * -1
const [activePayment, setActivePayment] = useState<AdminPayment | null>(
paymentId ? payments.find((p) => p.id === paymentId) || null : null
)
const form = useForm<zod.infer<typeof OrderBalanceSettlementSchema>>({
defaultValues: {
settlement_type: "refund",
refund: {
amount: 0,
amount: {
value: "",
float: null,
},
},
credit_line: {
amount: 0,
amount: {
value: "",
float: null,
},
},
},
resolver: zodResolver(OrderBalanceSettlementSchema),
@@ -79,9 +97,14 @@ export const OrderBalanceSettlementForm = ({
const handleSubmit = form.handleSubmit(async (data) => {
if (data.settlement_type === "credit_line") {
if (data.credit_line?.amount.float === null) {
return
}
await createCreditLine(
{
amount: parseFloat(data.credit_line!.amount! as string) * -1,
amount: data.credit_line!.amount.float! * -1,
reference: "refund",
reference_id: order.id,
},
{
onSuccess: () => {
@@ -97,9 +120,12 @@ export const OrderBalanceSettlementForm = ({
}
if (data.settlement_type === "refund") {
if (data.refund?.amount.float === null) {
return
}
await createRefund(
{
amount: parseFloat(data.refund!.amount! as string),
amount: data.refund!.amount!.float!,
note: data.refund!.note,
},
{
@@ -107,7 +133,7 @@ export const OrderBalanceSettlementForm = ({
toast.success(
t("orders.payment.refundPaymentSuccess", {
amount: formatCurrency(
parseFloat(data.refund!.amount! as string),
data.refund!.amount!.float!,
order.currency_code!
),
})
@@ -131,18 +157,23 @@ export const OrderBalanceSettlementForm = ({
useEffect(() => {
form.clearErrors()
const minimum = activePayment?.amount
const _minimum = activePayment?.amount
? Math.min(pendingDifference, activePayment.amount)
: pendingDifference
const minimum = {
value: _minimum.toFixed(currency.decimal_digits),
float: _minimum,
}
if (settlementType === "refund") {
form.setValue("refund.amount", activePayment ? minimum : 0)
form.setValue("refund.amount", minimum)
}
if (settlementType === "credit_line") {
form.setValue("credit_line.amount", minimum)
}
}, [settlementType, activePayment, pendingDifference, form])
}, [settlementType, activePayment, pendingDifference, form, currency])
return (
<RouteDrawer.Form form={form}>
@@ -194,6 +225,7 @@ export const OrderBalanceSettlementForm = ({
<>
<div className="flex flex-col gap-y-4">
<Select
defaultValue={activePayment?.id}
onValueChange={(value) => {
setActivePayment(payments.find((p) => p.id === value)!)
}}
@@ -260,9 +292,12 @@ export const OrderBalanceSettlementForm = ({
decimalScale={currency.decimal_digits}
symbol={currency.symbol_native}
code={currency.code}
value={field.value}
value={field.value.value}
onValueChange={(_value, _name, values) =>
onChange(values?.value ? values?.value : "")
onChange({
value: values?.value,
float: values?.float || null,
})
}
autoFocus
/>
@@ -315,10 +350,13 @@ export const OrderBalanceSettlementForm = ({
decimalScale={currency.decimal_digits}
symbol={currency.symbol_native}
code={currency.code}
value={field.value}
onValueChange={(_value, _name, values) =>
onChange(values?.value ? values?.value : "")
}
value={field.value.value}
onValueChange={(_value, _name, values) => {
onChange({
value: values?.value,
float: values?.float || null,
})
}}
autoFocus
/>
</Form.Control>

View File

@@ -87,10 +87,16 @@ export const ClaimCreateForm = ({
useState(false)
const [customInboundShippingAmount, setCustomInboundShippingAmount] =
useState<number | string>(0)
useState<{ value: string; float: number | null }>({
value: "0",
float: 0,
})
const [customOutboundShippingAmount, setCustomOutboundShippingAmount] =
useState<number | string>(0)
useState<{ value: string; float: number | null }>({
value: "0",
float: 0,
})
const [inventoryMap, setInventoryMap] = useState<
Record<string, InventoryLevelDTO[]>
@@ -263,13 +269,23 @@ export const ClaimCreateForm = ({
useEffect(() => {
if (inboundShipping) {
setCustomInboundShippingAmount(inboundShipping.total)
setCustomInboundShippingAmount({
value: inboundShipping.total.toFixed(
currencies[order.currency_code.toUpperCase()].decimal_digits
),
float: inboundShipping.total,
})
}
}, [inboundShipping])
useEffect(() => {
if (outboundShipping) {
setCustomOutboundShippingAmount(outboundShipping.total)
setCustomOutboundShippingAmount({
value: outboundShipping.total.toFixed(
currencies[order.currency_code.toUpperCase()].decimal_digits
),
float: outboundShipping.total,
})
}
}, [outboundShipping])
@@ -519,6 +535,7 @@ export const ClaimCreateForm = ({
).variants
variants.forEach((variant) => {
// TODO: fix this for inventory kits
ret[variant.id] = variant.inventory?.[0]?.location_levels || []
})
@@ -560,6 +577,15 @@ export const ClaimCreateForm = ({
return (method?.total as number) || 0
}, [preview.shipping_methods])
const outboundShippingTotal = useMemo(() => {
const method = preview.shipping_methods.find(
(sm) =>
!!sm.actions?.find((a) => a.action === "SHIPPING_ADD" && !a.return_id)
)
return (method?.total as number) || 0
}, [preview.shipping_methods])
return (
<RouteFocusModal.Form form={form}>
<KeyboundForm onSubmit={handleSubmit} className="flex h-full flex-col">
@@ -866,10 +892,7 @@ export const ClaimCreateForm = ({
}
})
const customPrice =
customInboundShippingAmount === ""
? null
: parseFloat(customInboundShippingAmount)
const customPrice = customInboundShippingAmount.float
if (actionId) {
updateInboundShipping(
@@ -891,8 +914,13 @@ export const ClaimCreateForm = ({
.symbol_native
}
code={order.currency_code}
onValueChange={setCustomInboundShippingAmount}
value={customInboundShippingAmount}
onValueChange={(value, _name, values) => {
setCustomInboundShippingAmount({
value: values?.value || "",
float: values?.float || null,
})
}}
value={customInboundShippingAmount.value}
disabled={showInboundItemsPlaceholder}
/>
) : (
@@ -937,10 +965,7 @@ export const ClaimCreateForm = ({
}
})
const customPrice =
customOutboundShippingAmount === ""
? null
: parseFloat(customOutboundShippingAmount)
const customPrice = customOutboundShippingAmount.float
if (actionId) {
updateOutboundShipping(
@@ -962,13 +987,18 @@ export const ClaimCreateForm = ({
.symbol_native
}
code={order.currency_code}
onValueChange={setCustomOutboundShippingAmount}
value={customOutboundShippingAmount}
onValueChange={(value, _name, values) => {
setCustomOutboundShippingAmount({
value: values?.value || "",
float: values?.float || null,
})
}}
value={customOutboundShippingAmount.value}
disabled={showOutboundItemsPlaceholder}
/>
) : (
getStylizedAmount(
outboundShipping?.amount ?? 0,
outboundShippingTotal,
order.currency_code
)
)}

View File

@@ -60,10 +60,18 @@ export const ExchangeCreateForm = ({
useState(false)
const [isOutboundShippingPriceEdit, setIsOutboundShippingPriceEdit] =
useState(false)
const [customInboundShippingAmount, setCustomInboundShippingAmount] =
useState<number | string>(0)
useState<{ value: string; float: number | null }>({
value: "0",
float: 0,
})
const [customOutboundShippingAmount, setCustomOutboundShippingAmount] =
useState<number | string>(0)
useState<{ value: string; float: number | null }>({
value: "0",
float: 0,
})
/**
* MUTATIONS
@@ -252,6 +260,15 @@ export const ExchangeCreateForm = ({
return (method?.total as number) || 0
}, [preview.shipping_methods])
const outboundShippingTotal = useMemo(() => {
const method = preview.shipping_methods.find(
(sm) =>
!!sm.actions?.find((a) => a.action === "SHIPPING_ADD" && !a.return_id)
)
return (method?.total as number) || 0
}, [preview.shipping_methods])
return (
<RouteFocusModal.Form form={form}>
<KeyboundForm onSubmit={handleSubmit} className="flex h-full flex-col">
@@ -356,10 +373,7 @@ export const ExchangeCreateForm = ({
}
})
const customPrice =
customInboundShippingAmount === ""
? null
: parseFloat(customInboundShippingAmount)
const customPrice = customInboundShippingAmount.float
if (actionId) {
updateInboundShipping(
@@ -381,8 +395,13 @@ export const ExchangeCreateForm = ({
.symbol_native
}
code={order.currency_code}
onValueChange={setCustomInboundShippingAmount}
value={customInboundShippingAmount}
onValueChange={(value, name, values) =>
setCustomInboundShippingAmount({
value: values?.value || "",
float: values?.float || null,
})
}
value={customInboundShippingAmount.value}
disabled={!inboundPreviewItems?.length}
/>
) : (
@@ -427,10 +446,7 @@ export const ExchangeCreateForm = ({
}
})
const customPrice =
customOutboundShippingAmount === ""
? null
: parseFloat(customOutboundShippingAmount)
const customPrice = customOutboundShippingAmount.float
if (actionId) {
updateOutboundShipping(
@@ -452,13 +468,18 @@ export const ExchangeCreateForm = ({
.symbol_native
}
code={order.currency_code}
onValueChange={setCustomOutboundShippingAmount}
value={customOutboundShippingAmount}
onValueChange={(value, name, values) =>
setCustomOutboundShippingAmount({
value: values?.value || "",
float: values?.float || null,
})
}
value={customOutboundShippingAmount.value}
disabled={!outboundPreviewItems?.length}
/>
) : (
getStylizedAmount(
outboundShipping?.amount ?? 0,
outboundShippingTotal,
order.currency_code
)
)}

View File

@@ -8,11 +8,11 @@ import {
Textarea,
toast,
} from "@medusajs/ui"
import { useEffect, useMemo } from "react"
import { useEffect, useMemo, useState } from "react"
import { formatValue } from "react-currency-input-field"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { useNavigate, useSearchParams } from "react-router-dom"
import { useSearchParams } from "react-router-dom"
import * as zod from "zod"
import { Form } from "../../../../../components/common/form"
import { RouteDrawer, useRouteModal } from "../../../../../components/modals"
@@ -29,16 +29,21 @@ type CreateRefundFormProps = {
}
const CreateRefundSchema = zod.object({
amount: zod.string().or(zod.number()),
amount: zod.object({
value: zod.string().or(zod.number()),
float: zod.number().or(zod.null()),
}),
note: zod.string().optional(),
})
export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const paymentId = searchParams.get("paymentId")
const [paymentId, setPaymentId] = useState<string | undefined>(
searchParams.get("paymentId") || undefined
)
const payments = getPaymentsFromOrder(order)
const payment = payments.find((p) => p.id === paymentId)!
const paymentAmount = payment?.amount || 0
@@ -50,7 +55,10 @@ export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
const form = useForm<zod.infer<typeof CreateRefundSchema>>({
defaultValues: {
amount: paymentAmount,
amount: {
value: paymentAmount.toFixed(currency.decimal_digits),
float: paymentAmount,
},
note: "",
},
resolver: zodResolver(CreateRefundSchema),
@@ -61,21 +69,24 @@ export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
const paymentAmount = (payment?.amount || 0) as number
const pendingAmount =
pendingDifference < 0
? Math.min(pendingDifference, paymentAmount)
? Math.min(Math.abs(pendingDifference), paymentAmount)
: paymentAmount
const normalizedAmount =
pendingAmount < 0 ? pendingAmount * -1 : pendingAmount
form.setValue("amount", normalizedAmount as number)
}, [payment])
form.setValue("amount", {
value: normalizedAmount.toFixed(currency.decimal_digits),
float: normalizedAmount,
})
}, [payment?.id || ""])
const { mutateAsync, isPending } = useRefundPayment(order.id, payment?.id!)
const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync(
{
amount: parseFloat(data.amount as string),
amount: data.amount.float!,
note: data.note,
},
{
@@ -83,7 +94,7 @@ export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
toast.success(
t("orders.payment.refundPaymentSuccess", {
amount: formatCurrency(
data.amount as number,
data.amount.float!,
payment?.currency_code!
),
})
@@ -107,11 +118,9 @@ export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
<RouteDrawer.Body className="flex-1 overflow-auto">
<div className="flex flex-col gap-y-4">
<Select
value={payment?.id}
value={paymentId}
onValueChange={(value) => {
navigate(`/orders/${order.id}/refund?paymentId=${value}`, {
replace: true,
})
setPaymentId(value)
}}
>
<Label className="txt-compact-small mb-[-6px] font-sans font-medium">
@@ -179,9 +188,12 @@ export const CreateRefundForm = ({ order }: CreateRefundFormProps) => {
decimalScale={currency.decimal_digits}
symbol={currency.symbol_native}
code={currency.code}
value={field.value}
value={field.value.value}
onValueChange={(_value, _name, values) =>
onChange(values?.value ? values?.value : "")
onChange({
value: values?.value,
float: values?.float || null,
})
}
autoFocus
/>

View File

@@ -94,7 +94,13 @@ export const ReturnCreateForm = ({
*/
const { setIsOpen } = useStackedModal()
const [isShippingPriceEdit, setIsShippingPriceEdit] = useState(false)
const [customShippingAmount, setCustomShippingAmount] = useState(0)
const [customShippingAmount, setCustomShippingAmount] = useState<{
value: string
float: number | null
}>({
value: "0",
float: 0,
})
const [inventoryMap, setInventoryMap] = useState<
Record<string, InventoryLevelDTO[]>
>({})
@@ -671,10 +677,7 @@ export const ReturnCreateForm = ({
if (actionId) {
updateReturnShipping({
actionId,
custom_amount:
typeof customShippingAmount === "string"
? null
: customShippingAmount,
custom_amount: customShippingAmount.float,
})
}
setIsShippingPriceEdit(false)
@@ -684,10 +687,13 @@ export const ReturnCreateForm = ({
.symbol_native
}
code={order.currency_code}
onValueChange={(value) =>
setCustomShippingAmount(value ? parseFloat(value) : "")
onValueChange={(value, name, values) =>
setCustomShippingAmount({
value: values?.value || "",
float: values?.float || null,
})
}
value={customShippingAmount}
value={customShippingAmount.value}
disabled={showPlaceholder}
/>
) : (