import { zodResolver } from "@hookform/resolvers/zod" import { InformationCircleSolid, Plus, TriangleDownMini, XMark, XMarkMini, } from "@medusajs/icons" import { Badge, Button, clx, CurrencyInput, Divider, Heading, IconButton, Label, Text, Tooltip, } from "@medusajs/ui" import { Accordion as RadixAccordion } from "radix-ui" import React, { Fragment, ReactNode, useRef, useState } from "react" import { Control, ControllerRenderProps, useFieldArray, useForm, useFormContext, useWatch, } from "react-hook-form" import { Trans, useTranslation } from "react-i18next" import { formatValue } from "react-currency-input-field" import { Form } from "../../../../../components/common/form" import { StackedFocusModal } from "../../../../../components/modals" import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { useCombinedRefs } from "../../../../../hooks/use-combined-refs" import { castNumber } from "../../../../../lib/cast-number" import { CurrencyInfo } from "../../../../../lib/data/currencies" import { getLocaleAmount } from "../../../../../lib/money-amount-helpers" import { CreateShippingOptionSchemaType } from "../../../location-service-zone-shipping-option-create/components/create-shipping-options-form/schema" import { CondtionalPriceRuleSchema, CondtionalPriceRuleSchemaType, UpdateConditionalPriceRuleSchema, UpdateConditionalPriceRuleSchemaType, } from "../../schema" import { ConditionalPriceInfo } from "../../types" import { getCustomShippingOptionPriceFieldName } from "../../utils/get-custom-shipping-option-price-field-info" import { useShippingOptionPrice } from "../shipping-option-price-provider" const RULE_ITEM_PREFIX = "rule-item" const getRuleValue = (index: number) => `${RULE_ITEM_PREFIX}-${index}` interface ConditionalPriceFormProps { info: ConditionalPriceInfo variant: "create" | "update" } export const ConditionalPriceForm = ({ info, variant, }: ConditionalPriceFormProps) => { const { t } = useTranslation() const { getValues, setValue: setFormValue } = useFormContext() const { onCloseConditionalPricesModal } = useShippingOptionPrice() const [value, setValue] = useState([getRuleValue(0)]) const { field, type, currency, name: header } = info const name = getCustomShippingOptionPriceFieldName(field, type) const conditionalPriceForm = useForm< CondtionalPriceRuleSchemaType | UpdateConditionalPriceRuleSchemaType >({ defaultValues: { prices: getValues(name) || [ { amount: "", gte: "", lte: null, }, ], }, resolver: zodResolver( variant === "create" ? CondtionalPriceRuleSchema : UpdateConditionalPriceRuleSchema ), }) const { fields, append, remove } = useFieldArray({ control: conditionalPriceForm.control, name: "prices", }) const handleAdd = () => { append({ amount: "", gte: "", lte: null, }) setValue([...value, getRuleValue(fields.length)]) } const handleRemove = (index: number) => { remove(index) } const handleOnSubmit = conditionalPriceForm.handleSubmit( (values) => { setFormValue(name, values.prices, { shouldDirty: true, shouldValidate: true, shouldTouch: true, }) onCloseConditionalPricesModal() }, (e) => { const indexesWithErrors = Object.keys(e.prices || {}) setValue((prev) => { const values = new Set(prev) indexesWithErrors.forEach((index) => { values.add(getRuleValue(Number(index))) }) return Array.from(values) }) } ) // Intercept the Cmd + Enter key to only save the inner form. const handleOnKeyDown = (event: React.KeyboardEvent) => { if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) { console.log("Fired") event.preventDefault() event.stopPropagation() handleOnSubmit() } } return (
{t( "stockLocations.shippingOptions.conditionalPrices.header", { name: header, } )} {t( "stockLocations.shippingOptions.conditionalPrices.description" )}
{fields.map((field, index) => ( ))}
) } interface ConditionalPriceListProps { children?: ReactNode value: string[] onValueChange: (value: string[]) => void } const ConditionalPriceList = ({ children, value, onValueChange, }: ConditionalPriceListProps) => { return ( {children} ) } interface ConditionalPriceItemProps { index: number currency: CurrencyInfo onRemove: (index: number) => void control: Control } const ConditionalPriceItem = ({ index, currency, onRemove, control, }: ConditionalPriceItemProps) => { const { t } = useTranslation() const handleRemove = (e: React.MouseEvent) => { e.stopPropagation() onRemove(index) } return (
{ return (
{t( "stockLocations.shippingOptions.conditionalPrices.rules.amount" )}
onChange(values?.value ? values?.value : "") } autoFocus={false} {...props} />
) }} /> { return ( ) }} /> { return ( ) }} />
) } interface OperatorInputProps { currency: CurrencyInfo placeholder: string label: string field: ControllerRenderProps< CondtionalPriceRuleSchemaType, `prices.${number}.lte` | `prices.${number}.gte` > } const OperatorInput = ({ field, label, currency, placeholder, }: OperatorInputProps) => { const innerRef = useRef(null) const { value, onChange, ref, ...props } = field const refs = useCombinedRefs(innerRef, ref) const action = () => { if (value === null) { onChange("") requestAnimationFrame(() => { innerRef.current?.focus() }) return } onChange(null) } const isNull = value === null return (
{isNull ? : } {label}
{!isNull && (
onChange(values?.value ? values?.value : "") } {...props} />
)}
) } const ReadOnlyConditions = ({ index, control, currency, }: { index: number control: Control currency: CurrencyInfo }) => { const { t } = useTranslation() const item = useWatch({ control, name: `prices.${index}`, }) if (item.eq == null && item.gt == null && item.lt == null) { return null } return (
{t( "stockLocations.shippingOptions.conditionalPrices.customRules.label" )}
{item.eq != null && (
)} {item.gt != null && (
)} {item.lt != null && (
)}
) } const AmountDisplay = ({ index, currency, control, }: { index: number currency: CurrencyInfo control: Control }) => { const amount = useWatch({ control, name: `prices.${index}.amount`, }) if (amount === "" || amount === undefined) { return ( - ) } const castAmount = castNumber(amount) return ( {getLocaleAmount(castAmount, currency.code)} ) } const ConditionContainer = ({ children }: { children: ReactNode }) => (
{children}
) const ConditionDisplay = ({ index, currency, control, }: { index: number currency: CurrencyInfo control: Control }) => { const { t, i18n } = useTranslation() const gte = useWatch({ control, name: `prices.${index}.gte`, }) const lte = useWatch({ control, name: `prices.${index}.lte`, }) const renderCondition = () => { const castGte = gte ? castNumber(gte) : undefined const castLte = lte ? castNumber(lte) : undefined if (!castGte && !castLte) { return null } if (castGte && !castLte) { return ( , , ]} values={{ attribute: t( "stockLocations.shippingOptions.conditionalPrices.attributes.cartItemTotal" ), gte: getLocaleAmount(castGte, currency.code), }} /> ) } if (!castGte && castLte) { return ( , , ]} values={{ attribute: t( "stockLocations.shippingOptions.conditionalPrices.attributes.cartItemTotal" ), lte: getLocaleAmount(castLte, currency.code), }} /> ) } if (castGte && castLte) { return ( , , , ]} values={{ attribute: t( "stockLocations.shippingOptions.conditionalPrices.attributes.cartItemTotal" ), gte: getLocaleAmount(castGte, currency.code), lte: getLocaleAmount(castLte, currency.code), }} /> ) } return null } return renderCondition() }