feat(dashboard): handle region pricing on create/edit (#7811)

**What**
- allow for region prices creation on product create flow
- editing of region prices in prices edit form
This commit is contained in:
Frane Polić
2024-06-25 15:15:08 +00:00
committed by GitHub
parent bea3e669e1
commit 4a631e789d
7 changed files with 107 additions and 22 deletions
@@ -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))
@@ -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<ProductCreateSchema>
@@ -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<HttpTypes.AdminProductVariant>()
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 (
<CurrencyCell
currency={currencies.find(
(c) => 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
}
@@ -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,
})
)
@@ -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<string, string>
}
) => {
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<string, string>
) => {
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,
@@ -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("..")
@@ -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()