feat(admin-ui): Delete pricing forms from edit + create variant modals (#5676)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/admin-ui": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(admin-ui): delete edit variant prices in favor of bulk editor
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
import { useAdminRegions, useAdminStore } from "medusa-react"
|
|
||||||
import { useEffect, useMemo } from "react"
|
|
||||||
import { FieldArrayWithId, useFieldArray } from "react-hook-form"
|
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
|
||||||
import NestedPrice from "./nested-price"
|
|
||||||
|
|
||||||
type PricePayload = {
|
|
||||||
id: string | null
|
|
||||||
amount: number | null
|
|
||||||
currency_code: string
|
|
||||||
region_id: string | null
|
|
||||||
includes_tax?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type PriceObject = FieldArrayWithId<
|
|
||||||
{
|
|
||||||
__nested__: PricesFormType
|
|
||||||
},
|
|
||||||
"__nested__.prices",
|
|
||||||
"id"
|
|
||||||
> & { index: number }
|
|
||||||
|
|
||||||
export type PricesFormType = {
|
|
||||||
prices: PricePayload[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type NestedPriceObject = {
|
|
||||||
currencyPrice: PriceObject
|
|
||||||
regionPrices: (PriceObject & { regionName: string })[]
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
form: NestedForm<PricesFormType>
|
|
||||||
required?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-usable nested form used to submit pricing information for products and their variants.
|
|
||||||
* Fetches store currencies and regions from the backend, and allows the user to specify both
|
|
||||||
* currency and region specific prices.
|
|
||||||
* @example
|
|
||||||
* <Pricing form={nestedForm(form, "prices")} />
|
|
||||||
*/
|
|
||||||
const PricesForm = ({ form }: Props) => {
|
|
||||||
const { store } = useAdminStore()
|
|
||||||
const { regions } = useAdminRegions()
|
|
||||||
|
|
||||||
const { control, path } = form
|
|
||||||
|
|
||||||
const { append, update, fields } = useFieldArray({
|
|
||||||
control,
|
|
||||||
name: path("prices"),
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!regions || !store || !fields) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
regions.forEach((reg) => {
|
|
||||||
if (!fields.some((field) => field.region_id === reg.id)) {
|
|
||||||
append({
|
|
||||||
id: null,
|
|
||||||
region_id: reg.id,
|
|
||||||
amount: null,
|
|
||||||
currency_code: reg.currency_code,
|
|
||||||
includes_tax: reg.includes_tax,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
store.currencies.forEach((cur) => {
|
|
||||||
if (!fields.some((field) => field.currency_code === cur.code)) {
|
|
||||||
append({
|
|
||||||
id: null,
|
|
||||||
currency_code: cur.code,
|
|
||||||
amount: null,
|
|
||||||
region_id: null,
|
|
||||||
includes_tax: cur.includes_tax,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [regions, store, fields])
|
|
||||||
|
|
||||||
// Ensure that prices are up to date with their respective tax inclusion setting
|
|
||||||
useEffect(() => {
|
|
||||||
if (!regions || !fields || !store) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
regions.forEach((reg) => {
|
|
||||||
const regionPrice = fields.findIndex(
|
|
||||||
(field) => !!field && field.region_id === reg.id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
regionPrice !== -1 &&
|
|
||||||
fields[regionPrice].includes_tax !== reg.includes_tax
|
|
||||||
) {
|
|
||||||
update(regionPrice, {
|
|
||||||
...fields[regionPrice],
|
|
||||||
includes_tax: reg.includes_tax,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
store.currencies.forEach((cur) => {
|
|
||||||
const currencyPrice = fields.findIndex(
|
|
||||||
(field) =>
|
|
||||||
!!field && !field.region_id && field.currency_code === cur.code
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
currencyPrice !== -1 &&
|
|
||||||
fields[currencyPrice].includes_tax !== cur.includes_tax
|
|
||||||
) {
|
|
||||||
update(currencyPrice, {
|
|
||||||
...fields[currencyPrice],
|
|
||||||
includes_tax: cur.includes_tax,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [regions, store])
|
|
||||||
|
|
||||||
const priceObj = useMemo(() => {
|
|
||||||
const obj: Record<string, NestedPriceObject> = {}
|
|
||||||
|
|
||||||
const currencyPrices = fields.filter((field) => field.region_id === null)
|
|
||||||
const regionPrices = fields.filter((field) => field.region_id !== null)
|
|
||||||
|
|
||||||
currencyPrices.forEach((price) => {
|
|
||||||
obj[price.currency_code!] = {
|
|
||||||
currencyPrice: {
|
|
||||||
...price,
|
|
||||||
index: fields.indexOf(price),
|
|
||||||
},
|
|
||||||
regionPrices: regionPrices
|
|
||||||
.filter(
|
|
||||||
(regionPrice) => regionPrice.currency_code === price.currency_code
|
|
||||||
)
|
|
||||||
.map((rp) => ({
|
|
||||||
...rp,
|
|
||||||
regionName: regions?.find((r) => r.id === rp.region_id)?.name || "",
|
|
||||||
index: fields.indexOf(rp),
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}, [fields, regions])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
{Object.values(priceObj).map((po) => {
|
|
||||||
return (
|
|
||||||
<NestedPrice
|
|
||||||
form={form}
|
|
||||||
nestedPrice={po}
|
|
||||||
key={po.currencyPrice.id}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PricesForm
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import { NestedPriceObject, PricesFormType } from "."
|
|
||||||
|
|
||||||
import CoinsIcon from "../../../fundamentals/icons/coins-icon"
|
|
||||||
import { Controller } from "react-hook-form"
|
|
||||||
import IncludesTaxTooltip from "../../../atoms/includes-tax-tooltip"
|
|
||||||
import MapPinIcon from "../../../fundamentals/icons/map-pin-icon"
|
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
|
||||||
import PriceFormInput from "./price-form-input"
|
|
||||||
import TriangleRightIcon from "../../../fundamentals/icons/triangle-right-icon"
|
|
||||||
import clsx from "clsx"
|
|
||||||
import { currencies } from "../../../../utils/currencies"
|
|
||||||
import useToggleState from "../../../../hooks/use-toggle-state"
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
form: NestedForm<PricesFormType>
|
|
||||||
nestedPrice: NestedPriceObject
|
|
||||||
}
|
|
||||||
|
|
||||||
const NestedPrice = ({ form, nestedPrice }: Props) => {
|
|
||||||
const { state, toggle } = useToggleState()
|
|
||||||
|
|
||||||
const { control, path } = form
|
|
||||||
const { currencyPrice, regionPrices } = nestedPrice
|
|
||||||
return (
|
|
||||||
<div className="gap-y-2xsmall flex flex-col">
|
|
||||||
<div className="gap-x-base p-2xsmall hover:bg-grey-5 focus-within:bg-grey-5 rounded-rounded relative grid grid-cols-[1fr_223px] justify-between pl-10 transition-colors">
|
|
||||||
<button
|
|
||||||
className={clsx(
|
|
||||||
"left-xsmall text-grey-40 absolute top-1/2 -translate-y-1/2 transition-all",
|
|
||||||
{
|
|
||||||
"rotate-90": state,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hidden: regionPrices.length === 0,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
type="button"
|
|
||||||
onClick={toggle}
|
|
||||||
disabled={regionPrices.length === 0}
|
|
||||||
>
|
|
||||||
<TriangleRightIcon />
|
|
||||||
</button>
|
|
||||||
<div className="gap-x-small flex items-center">
|
|
||||||
<div className="bg-grey-10 rounded-rounded text-grey-50 flex h-10 w-10 items-center justify-center">
|
|
||||||
<CoinsIcon size={20} />
|
|
||||||
</div>
|
|
||||||
<div className="gap-x-xsmall flex items-center">
|
|
||||||
<span className="inter-base-semibold">
|
|
||||||
{currencyPrice.currency_code.toUpperCase()}
|
|
||||||
</span>
|
|
||||||
<span className="inter-base-regular text-grey-50">
|
|
||||||
{currencies[currencyPrice.currency_code.toUpperCase()].name}
|
|
||||||
</span>
|
|
||||||
<IncludesTaxTooltip includesTax={currencyPrice?.includes_tax} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Controller
|
|
||||||
name={path(`prices.${currencyPrice.index}.amount`)}
|
|
||||||
control={control}
|
|
||||||
render={({ field: { value, onChange }, formState: { errors } }) => {
|
|
||||||
return (
|
|
||||||
<PriceFormInput
|
|
||||||
onChange={onChange}
|
|
||||||
amount={value !== null ? value : undefined}
|
|
||||||
currencyCode={currencyPrice.currency_code}
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
className={clsx(
|
|
||||||
"gap-y-2xsmall my-2xsmall flex flex-col overflow-hidden",
|
|
||||||
{
|
|
||||||
"max-h-0": !state,
|
|
||||||
"max-h-[9999px]": state,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{regionPrices.map((rp) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="p-2xsmall hover:bg-grey-5 focus-within:bg-grey-5 rounded-rounded grid grid-cols-[1fr_223px] justify-between pl-10 transition-colors"
|
|
||||||
key={rp.id}
|
|
||||||
>
|
|
||||||
<div className="gap-x-small flex items-center">
|
|
||||||
<div className="bg-grey-10 rounded-rounded text-grey-50 flex h-10 w-10 items-center justify-center">
|
|
||||||
<MapPinIcon size={20} />
|
|
||||||
</div>
|
|
||||||
<div className="gap-x-xsmall flex items-center">
|
|
||||||
<span className="inter-base-regular text-grey-50">
|
|
||||||
{rp.regionName}
|
|
||||||
</span>
|
|
||||||
<IncludesTaxTooltip includesTax={rp.includes_tax} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Controller
|
|
||||||
name={path(`prices.${rp.index}.amount`)}
|
|
||||||
control={control}
|
|
||||||
render={({
|
|
||||||
field: { value, onChange },
|
|
||||||
formState: { errors },
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<PriceFormInput
|
|
||||||
onChange={onChange}
|
|
||||||
amount={value !== null ? value : undefined}
|
|
||||||
currencyCode={currencyPrice.currency_code}
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NestedPrice
|
|
||||||
+6
-11
@@ -1,28 +1,26 @@
|
|||||||
import { UseFormReturn } from "react-hook-form"
|
|
||||||
import { nestedForm } from "../../../../../utils/nested-form"
|
|
||||||
import InputError from "../../../../atoms/input-error"
|
|
||||||
import IconTooltip from "../../../../molecules/icon-tooltip"
|
|
||||||
import Accordion from "../../../../organisms/accordion"
|
|
||||||
import { PricesFormType } from "../../../general/prices-form"
|
|
||||||
import CustomsForm, { CustomsFormType } from "../../customs-form"
|
import CustomsForm, { CustomsFormType } from "../../customs-form"
|
||||||
import DimensionsForm, { DimensionsFormType } from "../../dimensions-form"
|
import DimensionsForm, { DimensionsFormType } from "../../dimensions-form"
|
||||||
import VariantGeneralForm, {
|
import VariantGeneralForm, {
|
||||||
VariantGeneralFormType,
|
VariantGeneralFormType,
|
||||||
} from "../variant-general-form"
|
} from "../variant-general-form"
|
||||||
import VariantPricesForm from "../variant-prices-form"
|
|
||||||
import VariantSelectOptionsForm, {
|
import VariantSelectOptionsForm, {
|
||||||
VariantOptionValueType,
|
VariantOptionValueType,
|
||||||
VariantSelectOptionsFormType,
|
VariantSelectOptionsFormType,
|
||||||
} from "../variant-select-options-form"
|
} from "../variant-select-options-form"
|
||||||
import VariantStockForm, { VariantStockFormType } from "../variant-stock-form"
|
import VariantStockForm, { VariantStockFormType } from "../variant-stock-form"
|
||||||
|
|
||||||
|
import Accordion from "../../../../organisms/accordion"
|
||||||
|
import IconTooltip from "../../../../molecules/icon-tooltip"
|
||||||
|
import InputError from "../../../../atoms/input-error"
|
||||||
|
import { UseFormReturn } from "react-hook-form"
|
||||||
|
import { nestedForm } from "../../../../../utils/nested-form"
|
||||||
|
|
||||||
export type CreateFlowVariantFormType = {
|
export type CreateFlowVariantFormType = {
|
||||||
/**
|
/**
|
||||||
* Used to identify the variant during product create flow. Will not be submitted to the backend.
|
* Used to identify the variant during product create flow. Will not be submitted to the backend.
|
||||||
*/
|
*/
|
||||||
_internal_id?: string
|
_internal_id?: string
|
||||||
general: VariantGeneralFormType
|
general: VariantGeneralFormType
|
||||||
prices: PricesFormType
|
|
||||||
stock: VariantStockFormType
|
stock: VariantStockFormType
|
||||||
options: VariantSelectOptionsFormType
|
options: VariantSelectOptionsFormType
|
||||||
customs: CustomsFormType
|
customs: CustomsFormType
|
||||||
@@ -77,9 +75,6 @@ const CreateFlowVariantForm = ({ form, options, onCreateOption }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
<Accordion.Item title="Pricing" value="pricing">
|
|
||||||
<VariantPricesForm form={nestedForm(form, "prices")} />
|
|
||||||
</Accordion.Item>
|
|
||||||
<Accordion.Item title="Stock & Inventory" value="stock">
|
<Accordion.Item title="Stock & Inventory" value="stock">
|
||||||
<VariantStockForm form={nestedForm(form, "stock")} />
|
<VariantStockForm form={nestedForm(form, "stock")} />
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
|||||||
+5
-9
@@ -1,19 +1,18 @@
|
|||||||
import { useFieldArray, UseFormReturn } from "react-hook-form"
|
|
||||||
import CustomsForm, { CustomsFormType } from "../../customs-form"
|
import CustomsForm, { CustomsFormType } from "../../customs-form"
|
||||||
import DimensionsForm, { DimensionsFormType } from "../../dimensions-form"
|
import DimensionsForm, { DimensionsFormType } from "../../dimensions-form"
|
||||||
|
import MetadataForm, { MetadataFormType } from "../../../general/metadata-form"
|
||||||
|
import { UseFormReturn, useFieldArray } from "react-hook-form"
|
||||||
import VariantGeneralForm, {
|
import VariantGeneralForm, {
|
||||||
VariantGeneralFormType,
|
VariantGeneralFormType,
|
||||||
} from "../variant-general-form"
|
} from "../variant-general-form"
|
||||||
import VariantStockForm, { VariantStockFormType } from "../variant-stock-form"
|
import VariantStockForm, { VariantStockFormType } from "../variant-stock-form"
|
||||||
|
|
||||||
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
import Accordion from "../../../../organisms/accordion"
|
||||||
import { nestedForm } from "../../../../../utils/nested-form"
|
|
||||||
import IconTooltip from "../../../../molecules/icon-tooltip"
|
import IconTooltip from "../../../../molecules/icon-tooltip"
|
||||||
import InputField from "../../../../molecules/input"
|
import InputField from "../../../../molecules/input"
|
||||||
import Accordion from "../../../../organisms/accordion"
|
|
||||||
import MetadataForm, { MetadataFormType } from "../../../general/metadata-form"
|
|
||||||
import { PricesFormType } from "../../../general/prices-form"
|
import { PricesFormType } from "../../../general/prices-form"
|
||||||
import VariantPricesForm from "../variant-prices-form"
|
import { nestedForm } from "../../../../../utils/nested-form"
|
||||||
|
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
||||||
|
|
||||||
export type EditFlowVariantFormType = {
|
export type EditFlowVariantFormType = {
|
||||||
/**
|
/**
|
||||||
@@ -97,9 +96,6 @@ const EditFlowVariantForm = ({ form, isEdit }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
<Accordion.Item title="Pricing" value="pricing">
|
|
||||||
<VariantPricesForm form={nestedForm(form, "prices")} />
|
|
||||||
</Accordion.Item>
|
|
||||||
{showStockAndInventory && (
|
{showStockAndInventory && (
|
||||||
<Accordion.Item title="Stock & Inventory" value="stock">
|
<Accordion.Item title="Stock & Inventory" value="stock">
|
||||||
<VariantStockForm form={nestedForm(form, "stock")} />
|
<VariantStockForm form={nestedForm(form, "stock")} />
|
||||||
|
|||||||
-21
@@ -1,21 +0,0 @@
|
|||||||
import { NestedForm } from "../../../../../utils/nested-form"
|
|
||||||
import PricesForm, { PricesFormType } from "../../../general/prices-form"
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
form: NestedForm<PricesFormType>
|
|
||||||
}
|
|
||||||
|
|
||||||
const VariantPricesForm = ({ form }: Props) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p className="inter-base-regular text-grey-50">
|
|
||||||
Configure the pricing for this variant.
|
|
||||||
</p>
|
|
||||||
<div className="pt-large">
|
|
||||||
<PricesForm form={form} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default VariantPricesForm
|
|
||||||
+16
-17
@@ -1,27 +1,26 @@
|
|||||||
import { MoneyAmount, Product } from "@medusajs/client-types"
|
import { MoneyAmount, Product } from "@medusajs/client-types"
|
||||||
import mapKeys from "lodash/mapKeys"
|
|
||||||
import pick from "lodash/pick"
|
|
||||||
import pickBy from "lodash/pickBy"
|
|
||||||
import { useAdminRegions, useAdminUpdateVariant } from "medusa-react"
|
|
||||||
import { useEffect, useMemo, useRef, useState } from "react"
|
|
||||||
|
|
||||||
import { currencies as CURRENCY_MAP } from "../../../../utils/currencies"
|
|
||||||
|
|
||||||
import useNotification from "../../../../hooks/use-notification"
|
|
||||||
import Fade from "../../../atoms/fade-wrapper"
|
|
||||||
import Button from "../../../fundamentals/button"
|
|
||||||
import CrossIcon from "../../../fundamentals/icons/cross-icon"
|
|
||||||
import Modal from "../../../molecules/modal"
|
|
||||||
import DeletePrompt from "../../delete-prompt"
|
|
||||||
import EditPricesActions from "./edit-prices-actions"
|
|
||||||
import EditPricesTable from "./edit-prices-table"
|
|
||||||
import SavePrompt from "./save-prompt"
|
|
||||||
import {
|
import {
|
||||||
getAllProductPricesCurrencies,
|
getAllProductPricesCurrencies,
|
||||||
getAllProductPricesRegions,
|
getAllProductPricesRegions,
|
||||||
getCurrencyPricesOnly,
|
getCurrencyPricesOnly,
|
||||||
getRegionPricesOnly,
|
getRegionPricesOnly,
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
|
import { useAdminRegions, useAdminUpdateVariant } from "medusa-react"
|
||||||
|
import { useEffect, useMemo, useRef, useState } from "react"
|
||||||
|
|
||||||
|
import Button from "../../../fundamentals/button"
|
||||||
|
import { currencies as CURRENCY_MAP } from "../../../../utils/currencies"
|
||||||
|
import CrossIcon from "../../../fundamentals/icons/cross-icon"
|
||||||
|
import DeletePrompt from "../../delete-prompt"
|
||||||
|
import EditPricesActions from "./edit-prices-actions"
|
||||||
|
import EditPricesTable from "./edit-prices-table"
|
||||||
|
import Fade from "../../../atoms/fade-wrapper"
|
||||||
|
import Modal from "../../../molecules/modal"
|
||||||
|
import SavePrompt from "./save-prompt"
|
||||||
|
import mapKeys from "lodash/mapKeys"
|
||||||
|
import pick from "lodash/pick"
|
||||||
|
import pickBy from "lodash/pickBy"
|
||||||
|
import useNotification from "../../../../hooks/use-notification"
|
||||||
|
|
||||||
type EditPricesModalProps = {
|
type EditPricesModalProps = {
|
||||||
close: () => void
|
close: () => void
|
||||||
|
|||||||
Reference in New Issue
Block a user