diff --git a/packages/admin-next/dashboard/src/routes/product-variants/product-variant-detail/components/variant-prices-section/variant-prices-section.tsx b/packages/admin-next/dashboard/src/routes/product-variants/product-variant-detail/components/variant-prices-section/variant-prices-section.tsx index a27070411a..1dbcfdc329 100644 --- a/packages/admin-next/dashboard/src/routes/product-variants/product-variant-detail/components/variant-prices-section/variant-prices-section.tsx +++ b/packages/admin-next/dashboard/src/routes/product-variants/product-variant-detail/components/variant-prices-section/variant-prices-section.tsx @@ -17,8 +17,8 @@ export function VariantPricesSection({ variant }: VariantPricesSectionProps) { const { t } = useTranslation() const prices = variant.prices - .filter((p) => !p.rules?.length) - .sort((p1, p2) => p1.currency_code?.localeCompare(p2.currency_code)) // display just currency prices + .filter((p) => !Object.keys(p.rules || {}).length) // display just currency prices + .sort((p1, p2) => p1.currency_code?.localeCompare(p2.currency_code)) const [current, setCurrent] = useState(Math.min(prices.length, 3)) diff --git a/packages/admin-next/dashboard/src/routes/products/common/variant-pricing-form.tsx b/packages/admin-next/dashboard/src/routes/products/common/variant-pricing-form.tsx index 298fc5c733..f0c81af0f7 100644 --- a/packages/admin-next/dashboard/src/routes/products/common/variant-pricing-form.tsx +++ b/packages/admin-next/dashboard/src/routes/products/common/variant-pricing-form.tsx @@ -1,4 +1,4 @@ -import { CurrencyDTO, HttpTypes } from "@medusajs/types" +import { CurrencyDTO, HttpTypes, RegionDTO } from "@medusajs/types" import { ColumnDef, createColumnHelper } from "@tanstack/react-table" import { useMemo } from "react" import { UseFormReturn, useWatch } from "react-hook-form" @@ -10,6 +10,7 @@ import { DataGridMeta } from "../../../components/grid/types" import { useCurrencies } from "../../../hooks/api/currencies" import { useStore } from "../../../hooks/api/store" import { ProductCreateSchema } from "../product-create/constants" +import { useRegions } from "../../../hooks/api/regions.tsx" type VariantPricingFormProps = { form: UseFormReturn @@ -27,8 +28,11 @@ export const VariantPricingForm = ({ form }: VariantPricingFormProps) => { } ) + const { regions } = useRegions({ limit: 9999 }) + const columns = useVariantPriceGridColumns({ currencies, + regions, }) const variants = useWatch({ @@ -52,8 +56,10 @@ const columnHelper = createColumnHelper() export const useVariantPriceGridColumns = ({ currencies = [], + regions = [], }: { currencies?: CurrencyDTO[] + regions?: RegionDTO[] }) => { const { t } = useTranslation() @@ -87,8 +93,24 @@ export const useVariantPriceGridColumns = ({ }, }) }), + ...regions.map((region) => { + return columnHelper.display({ + header: `Price ${region.name}`, + cell: ({ row, table }) => { + return ( + c.code === region.currency_code + )} + meta={table.options.meta as DataGridMeta} + field={`variants.${row.index}.prices.${region.id}`} + /> + ) + }, + }) + }), ] - }, [t, currencies]) + }, [t, currencies, regions]) return colDefs } diff --git a/packages/admin-next/dashboard/src/routes/products/product-create/components/product-create-form/product-create-form.tsx b/packages/admin-next/dashboard/src/routes/products/product-create/components/product-create-form/product-create-form.tsx index 80edf335de..141f3ce660 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-create/components/product-create-form/product-create-form.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-create/components/product-create-form/product-create-form.tsx @@ -21,6 +21,7 @@ import { ProductCreateInventoryKitForm } from "../product-create-inventory-kit-f import { ProductCreateVariantsForm } from "../product-create-variants-form" import { isFetchError } from "../../../../../lib/is-fetch-error" import { sdk } from "../../../../../lib/client" +import { useRegions } from "../../../../../hooks/api/regions.tsx" enum Tab { DETAILS = "details", @@ -62,10 +63,22 @@ export const ProductCreateForm = ({ }) const { mutateAsync, isPending } = useCreateProduct() + const { regions } = useRegions({ limit: 9999 }) + + const regionsCurrencyMap = useMemo(() => { + if (!regions?.length) { + return {} + } + + return regions.reduce((acc, reg) => { + acc[reg.id] = reg.currency_code + return acc + }, {}) + }, regions) /** * TODO: Important to revisit this - use variants watch so high in the tree can cause needless rerenders of the entire page - * which is suboptimal when rereners are caused by bulk editor changes + * which is suboptimal when rerenders are caused by bulk editor changes */ const watchedVariants = useWatch({ @@ -123,10 +136,10 @@ export const ProductCreateForm = ({ const { product } = await mutateAsync( normalizeProductFormValues({ - // TODO: workflow should handle inventory creation ...payload, media: uploadedMedia, status: (isDraftSubmission ? "draft" : "published") as any, + regionsCurrencyMap, }) ) diff --git a/packages/admin-next/dashboard/src/routes/products/product-create/utils.ts b/packages/admin-next/dashboard/src/routes/products/product-create/utils.ts index 6eb320e877..f4eccadcc4 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-create/utils.ts +++ b/packages/admin-next/dashboard/src/routes/products/product-create/utils.ts @@ -3,7 +3,10 @@ import { ProductCreateSchemaType } from "./types" import { castNumber } from "../../../lib/cast-number" export const normalizeProductFormValues = ( - values: ProductCreateSchemaType & { status: HttpTypes.AdminProductStatus } + values: ProductCreateSchemaType & { + status: HttpTypes.AdminProductStatus + regionsCurrencyMap: Record + } ) => { const thumbnail = values.media?.find((media) => media.isThumbnail)?.url const images = values.media @@ -39,13 +42,15 @@ export const normalizeProductFormValues = ( weight: values.weight ? parseFloat(values.weight) : undefined, options: values.options.filter((o) => o.title), // clean temp. values variants: normalizeVariants( - values.variants.filter((variant) => variant.should_create) + values.variants.filter((variant) => variant.should_create), + values.regionsCurrencyMap ), } } export const normalizeVariants = ( - variants: ProductCreateSchemaType["variants"] + variants: ProductCreateSchemaType["variants"], + regionsCurrencyMap: Record ) => { return variants.map((variant) => ({ title: @@ -71,8 +76,11 @@ export const normalizeVariants = ( prices: Object.entries(variant.prices || {}) .map(([key, value]: any) => { if (key.startsWith("reg_")) { - // TODO: route needs to accept region prices as well - return undefined + return { + currency_code: regionsCurrencyMap[key], + amount: castNumber(value), + rules: { region_id: key }, + } } else { return { currency_code: key, diff --git a/packages/admin-next/dashboard/src/routes/products/product-prices/pricing-edit.tsx b/packages/admin-next/dashboard/src/routes/products/product-prices/pricing-edit.tsx index d8834fd9ac..87c53c0352 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-prices/pricing-edit.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-prices/pricing-edit.tsx @@ -9,6 +9,8 @@ import { RouteFocusModal, useRouteModal } from "../../../components/route-modal" import { useUpdateProductVariantsBatch } from "../../../hooks/api/products" import { VariantPricingForm } from "../common/variant-pricing-form" import { castNumber } from "../../../lib/cast-number" +import { useRegions } from "../../../hooks/api/regions.tsx" +import { useMemo } from "react" export const UpdateVariantPricesSchema = zod.object({ variants: zod.array( @@ -34,6 +36,18 @@ export const PricingEdit = ({ const { t } = useTranslation() const { handleSuccess } = useRouteModal() + const { regions } = useRegions({ limit: 9999 }) + const regionsCurrencyMap = useMemo(() => { + if (!regions?.length) { + return {} + } + + return regions.reduce((acc, reg) => { + acc[reg.id] = reg.currency_code + return acc + }, {}) + }, regions) + const variants = variantId ? product.variants.filter((v) => v.id === variantId) : product.variants @@ -43,7 +57,11 @@ export const PricingEdit = ({ variants: variants.map((variant: any) => ({ title: variant.title, prices: variant.prices.reduce((acc: any, price: any) => { - acc[price.currency_code] = price.amount + if (price.rules?.region_id) { + acc[price.rules.region_id] = price.amount + } else { + acc[price.currency_code] = price.amount + } return acc }, {}), })) as any, @@ -59,19 +77,45 @@ export const PricingEdit = ({ const reqData = values.variants.map((variant, ind) => ({ id: variants[ind].id, prices: Object.entries(variant.prices || {}).map( - ([currency_code, value]: any) => { - const id = variants[ind].prices.find( - (p) => p.currency_code === currency_code - )?.id + ([currencyCodeOrRegionId, value]: any) => { + const regionId = currencyCodeOrRegionId.startsWith("reg_") + ? currencyCodeOrRegionId + : undefined + const currencyCode = currencyCodeOrRegionId.startsWith("reg_") + ? regionsCurrencyMap[regionId] + : currencyCodeOrRegionId + + let existingId = undefined + + if (regionId) { + existingId = variants[ind].prices.find( + (p) => p.rules["region_id"] === regionId + )?.id + } else { + existingId = variants[ind].prices.find( + (p) => p.currency_code === currencyCode + )?.id + } const amount = castNumber(value) - return id - ? { id, amount, currency_code } - : { currency_code, amount } + const pricePayload = existingId + ? { + id: existingId, + amount, + currency_code: currencyCode, + } + : { currency_code: currencyCode, amount } + + if (regionId && !existingId) { + pricePayload.rules = { region_id: regionId } + } + + return pricePayload } ), })) + await mutateAsync(reqData, { onSuccess: () => { handleSuccess("..") diff --git a/packages/admin-next/dashboard/src/routes/products/product-prices/product-prices.tsx b/packages/admin-next/dashboard/src/routes/products/product-prices/product-prices.tsx index 1dd005d969..b103966229 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-prices/product-prices.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-prices/product-prices.tsx @@ -1,8 +1,8 @@ import { useParams } from "react-router-dom" import { useProduct } from "../../../hooks/api/products" -import { PricingEdit } from "./pricing-edit" import { RouteFocusModal } from "../../../components/route-modal" +import { PricingEdit } from "./pricing-edit" export const ProductPrices = () => { const { id, variant_id } = useParams() diff --git a/packages/medusa/src/api/admin/products/validators.ts b/packages/medusa/src/api/admin/products/validators.ts index 33c2f4d3ee..c96b36790b 100644 --- a/packages/medusa/src/api/admin/products/validators.ts +++ b/packages/medusa/src/api/admin/products/validators.ts @@ -97,7 +97,6 @@ export const AdminUpdateProductOption = z.object({ values: z.array(z.string()).optional(), }) -// TODO: Add support for rules export type AdminCreateVariantPriceType = z.infer< typeof AdminCreateVariantPrice > @@ -109,7 +108,6 @@ export const AdminCreateVariantPrice = z.object({ rules: z.record(z.string(), z.string()).optional(), }) -// TODO: Add support for rules export type AdminUpdateVariantPriceType = z.infer< typeof AdminUpdateVariantPrice >