fix(admin-ui): Price list domain fixes (#5339)
**Fixes** - Fixes an issue where click the Delete button on a table item in the Price List overview would also navigate to the Price Lists page - Fixes an issue where Region prices were not showing which region they belong to in the Bulk Editor - Fixes an issue where the wrong widgets were being injected into the Price List domain **Improvements** - Align label for Expiry date field - Add form validation to require setting the prices of at least one product variant when creating or adding prices to a Price List. - Show a prompt warning if prices have not been set for all selected products in the create and add product flows.
This commit is contained in:
committed by
GitHub
parent
a91fca69ac
commit
e04927a3cb
6
.changeset/many-bees-own.md
Normal file
6
.changeset/many-bees-own.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/admin-ui": patch
|
||||
"@medusajs/admin": patch
|
||||
---
|
||||
|
||||
fix(admin-ui): minor bugs throughout Price List domain plus improved form validation
|
||||
@@ -70,7 +70,7 @@
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"i18next-http-backend": "^2.2.1",
|
||||
"md5": "^2.3.0",
|
||||
"medusa-react": "*",
|
||||
"medusa-react": "^9.0.7",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"moment": "^2.29.4",
|
||||
"path-browserify": "^1.0.1",
|
||||
@@ -118,8 +118,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "7.22.5",
|
||||
"@medusajs/medusa": "*",
|
||||
"@medusajs/types": "*",
|
||||
"@medusajs/medusa": "^1.17.1",
|
||||
"@medusajs/types": "^1.11.3",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-datepicker": "^4.10.0",
|
||||
|
||||
@@ -1423,6 +1423,9 @@
|
||||
"price-list-new-form-prompt-title": "Are you sure?",
|
||||
"price-list-new-form-prompt-exit-description": "You have unsaved changes, are you sure you want to exit?",
|
||||
"price-list-new-form-prompt-back-description": "You have unsaved changes, are you sure you want to go back?",
|
||||
"price-list-add-products-modal-no-prices-error": "Please assign prices for at least one product.",
|
||||
"price-list-add-products-modal-missing-prices-title": "Incomplete price list",
|
||||
"price-list-add-products-modal-missing-prices-description": "Prices have not been assigned to all of your chosen products. Would you like to continue?",
|
||||
"price-list-add-products-modal-success-title": "New prices added",
|
||||
"price-list-add-products-modal-success-message": "The new prices have been added to the price list.",
|
||||
"price-list-add-products-modal-error-title": "An error occurred",
|
||||
@@ -1488,7 +1491,7 @@
|
||||
"price-list-details-form-dates-starts-at-label": "Start date",
|
||||
"price-list-details-form-ends-at-heading": "Price list has an expiry date?",
|
||||
"price-list-details-form-ends-at-description": "Schedule the price overrides to deactivate in the future.",
|
||||
"price-list-details-form-ends-at-label": "End date",
|
||||
"price-list-details-form-ends-at-label": "Expiry date",
|
||||
"price-list-details-form-customer-groups-name": "Name",
|
||||
"price-list-details-form-customer-groups-members": "Members",
|
||||
"price-list-details-form-customer-groups-error": "An error occurred while loading customer groups. Reload the page and try again. If the issue persists, try again later.",
|
||||
@@ -1514,7 +1517,7 @@
|
||||
"price-list-product-prices-form-column-visibility-regions-label": "Regions",
|
||||
"price-list-product-prices-form-column-product-label": "Product",
|
||||
"price-list-product-prices-form-column-currencies-price-label": "Price {{code}}",
|
||||
"price-list-product-prices-form-column-regions-price-label": "Price {{code}}",
|
||||
"price-list-product-prices-form-column-regions-price-label": "Price {{name}} ({{code}})",
|
||||
"price-list-products-form-select-all": "Select all products on the current page",
|
||||
"price-list-products-form-select-row": "Select row",
|
||||
"price-list-products-form-product-label": "Product",
|
||||
@@ -1530,6 +1533,9 @@
|
||||
"price-list-products-form-no-products": "No products found.",
|
||||
"price-list-products-form-heading": "Choose products",
|
||||
"price-list-products-form-search-placeholder": "Search",
|
||||
"price-list-new-form-no-prices-error": "Please set prices for at least one product.",
|
||||
"price-list-new-form-missing-prices-title": "Incomplete price list",
|
||||
"price-list-new-products-modal-missing-prices-description": "Prices have not been assigned to all of your chosen products. Would you like to proceed?",
|
||||
"price-list-new-form-notification-success-title": "Price list created",
|
||||
"price-list-new-form-notification-success-message": "Successfully created price list",
|
||||
"price-list-new-form-notification-error-title": "An error occurred",
|
||||
|
||||
@@ -69,6 +69,7 @@ const PriceListDetailsSection = ({
|
||||
name,
|
||||
}
|
||||
),
|
||||
verificationText: name,
|
||||
confirmText,
|
||||
cancelText,
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@ const PriceListEdit = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-y-2">
|
||||
{getWidgets("product.details.before").map((w, i) => {
|
||||
{getWidgets("price_list.details.before").map((w, i) => {
|
||||
return (
|
||||
<WidgetContainer
|
||||
key={i}
|
||||
@@ -60,7 +60,7 @@ const PriceListEdit = () => {
|
||||
})}
|
||||
<PriceListGeneralSection priceList={price_list} />
|
||||
<PriceListPricesSection priceList={price_list} />
|
||||
{getWidgets("product.details.after").map((w, i) => {
|
||||
{getWidgets("price_list.details.after").map((w, i) => {
|
||||
return (
|
||||
<WidgetContainer
|
||||
key={i}
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
import * as React from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import * as z from "zod"
|
||||
|
||||
import { useAdminCreatePriceListPrices } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { Form } from "../../../../components/helpers/form"
|
||||
import useNotification from "../../../../hooks/use-notification"
|
||||
import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
|
||||
@@ -110,6 +110,7 @@ const AddProductsModal = ({
|
||||
trigger,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
setError,
|
||||
getValues,
|
||||
reset,
|
||||
formState: { isDirty },
|
||||
@@ -138,6 +139,7 @@ const AddProductsModal = ({
|
||||
const onCloseModal = React.useCallback(() => {
|
||||
onOpenChange(false)
|
||||
setTab(Tab.PRODUCTS)
|
||||
setSelectedIds([])
|
||||
setStatus({
|
||||
[Tab.PRODUCTS]: "not-started",
|
||||
[Tab.PRICES]: "not-started",
|
||||
@@ -205,6 +207,42 @@ const AddProductsModal = ({
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
const prices: PricePayload[] = []
|
||||
|
||||
const productPriceKeys = Object.keys(data.prices.products)
|
||||
const productIds = data.products.ids
|
||||
|
||||
if (!productPriceKeys.length || !data.prices.products) {
|
||||
setError("prices.products", {
|
||||
type: "manual",
|
||||
message: t(
|
||||
"price-list-add-products-modal-no-prices-error",
|
||||
"Please assign prices for at least one product."
|
||||
) as string,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const missingProducts = productIds.filter(
|
||||
(id) => !productPriceKeys.includes(id)
|
||||
)
|
||||
|
||||
if (missingProducts.length > 0) {
|
||||
const res = await prompt({
|
||||
title: t(
|
||||
"price-list-add-products-modal-missing-prices-title",
|
||||
"Incomplete price list"
|
||||
),
|
||||
description: t(
|
||||
"price-list-add-products-modal-missing-prices-description",
|
||||
"Prices have not been assigned to all of your chosen products. Would you like to continue?"
|
||||
),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for (const productId of Object.keys(data.prices.products)) {
|
||||
const product = data.prices.products[productId]
|
||||
|
||||
@@ -533,6 +571,7 @@ const AddProductsModal = ({
|
||||
</span>
|
||||
</ProgressTabs.Trigger>
|
||||
<ProgressTabs.Trigger
|
||||
disabled={selectedIds.length === 0}
|
||||
value={Tab.PRICES}
|
||||
className="w-full max-w-[200px]"
|
||||
status={status[Tab.PRICES]}
|
||||
|
||||
@@ -376,7 +376,10 @@ const PriceListDates = ({ form, layout }: PriceListDetailsFormProps) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>
|
||||
{t("price-list-details-form-ends-at-label", "End date")}
|
||||
{t(
|
||||
"price-list-details-form-ends-at-label",
|
||||
"Expiry date"
|
||||
)}
|
||||
</Form.Label>
|
||||
<Form.Control>
|
||||
<DatePicker
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useDebounce } from "../../../../hooks/use-debounce"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
import { ProductFilter, ProductFilterMenu } from "../../components"
|
||||
import { PriceListPricesSchema } from "./types"
|
||||
import { Form } from "../../../../components/helpers/form"
|
||||
|
||||
type PriceListPricesFormProps = {
|
||||
form: NestedForm<PriceListPricesSchema>
|
||||
@@ -23,7 +24,12 @@ const PriceListPricesForm = ({
|
||||
productIds,
|
||||
priceListId,
|
||||
}: PriceListPricesFormProps) => {
|
||||
const { path, getValues } = form
|
||||
const {
|
||||
control,
|
||||
path,
|
||||
getValues,
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -87,9 +93,24 @@ const PriceListPricesForm = ({
|
||||
<div className="relative flex h-full w-full flex-col">
|
||||
<div>
|
||||
<div className="border-ui-border-base bg-ui-bg-base z-10 flex items-center justify-between border-b px-4 py-3">
|
||||
<Heading>
|
||||
{t("price-list-prices-form-heading", "Edit prices")}
|
||||
</Heading>
|
||||
<div className="flex items-center gap-x-3">
|
||||
<Heading>
|
||||
{t("price-list-prices-form-heading", "Edit prices")}
|
||||
</Heading>
|
||||
{isDirty && (
|
||||
<Form.Field
|
||||
control={control}
|
||||
name={path("products")}
|
||||
render={() => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<ProductFilterMenu
|
||||
onClearFilters={() => setFilters({})}
|
||||
|
||||
@@ -1565,8 +1565,9 @@ const PriceListProductPricesForm = ({
|
||||
<span>
|
||||
{t(
|
||||
"price-list-product-prices-form-column-regions-price-label",
|
||||
"Price {{code}}",
|
||||
"Price {{name}} ({{code}})",
|
||||
{
|
||||
name: region.name,
|
||||
code: region.currency_code.toUpperCase(),
|
||||
}
|
||||
)}
|
||||
|
||||
@@ -132,6 +132,7 @@ const PriceListNew = () => {
|
||||
reset,
|
||||
getValues,
|
||||
setValue,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
@@ -163,6 +164,7 @@ const PriceListNew = () => {
|
||||
const onCloseModal = React.useCallback(() => {
|
||||
setOpen(false)
|
||||
setTab(Tab.DETAILS)
|
||||
setSelectedIds([])
|
||||
setStatus({
|
||||
[Tab.DETAILS]: "not-started",
|
||||
[Tab.PRODUCTS]: "not-started",
|
||||
@@ -228,6 +230,42 @@ const PriceListNew = () => {
|
||||
await handleSubmit(async (data) => {
|
||||
const prices: PricePayload[] = []
|
||||
|
||||
const productPriceKeys = Object.keys(data.prices.products)
|
||||
const productIds = data.products.ids
|
||||
|
||||
if (!productPriceKeys.length || !data.prices.products) {
|
||||
setError("prices.products", {
|
||||
type: "manual",
|
||||
message: t(
|
||||
"price-list-new-form-no-prices-error",
|
||||
"Please set prices for at least one product."
|
||||
) as string,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const missingProducts = productIds.filter(
|
||||
(id) => !productPriceKeys.includes(id)
|
||||
)
|
||||
|
||||
if (missingProducts.length > 0) {
|
||||
const res = await prompt({
|
||||
title: t(
|
||||
"price-list-new-form-missing-prices-title",
|
||||
"Incomplete price list"
|
||||
),
|
||||
description: t(
|
||||
"price-list-new-products-modal-missing-prices-description",
|
||||
"Prices have not been assigned to all of your chosen products. Would you like to proceed?"
|
||||
),
|
||||
})
|
||||
|
||||
if (!res) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop through all the products and variants
|
||||
* and create a payload for each price.
|
||||
@@ -348,6 +386,8 @@ const PriceListNew = () => {
|
||||
mutateAsync,
|
||||
notification,
|
||||
onCloseModal,
|
||||
setError,
|
||||
prompt,
|
||||
t,
|
||||
isTaxInclPricesEnabled,
|
||||
regions,
|
||||
|
||||
@@ -384,7 +384,9 @@ const PriceListTableRowActions = ({ row }: PriceListTableRowActionsProps) => {
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleDelete = async () => {
|
||||
const handleDelete = async (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
|
||||
const response = await prompt({
|
||||
title: "Are you sure?",
|
||||
description: "This will permanently delete the price list",
|
||||
|
||||
12
yarn.lock
12
yarn.lock
@@ -6268,8 +6268,8 @@ __metadata:
|
||||
"@hookform/error-message": ^2.0.1
|
||||
"@hookform/resolvers": ^3.3.1
|
||||
"@medusajs/icons": 1.1.0
|
||||
"@medusajs/medusa": "*"
|
||||
"@medusajs/types": "*"
|
||||
"@medusajs/medusa": ^1.17.1
|
||||
"@medusajs/types": ^1.11.3
|
||||
"@medusajs/ui": ^2.2.0
|
||||
"@medusajs/ui-preset": 1.0.2
|
||||
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.10
|
||||
@@ -6311,7 +6311,7 @@ __metadata:
|
||||
i18next-parser: ^8.0.0
|
||||
jest: 25.5.4
|
||||
md5: ^2.3.0
|
||||
medusa-react: "*"
|
||||
medusa-react: ^9.0.7
|
||||
mini-css-extract-plugin: ^2.7.6
|
||||
moment: ^2.29.4
|
||||
path-browserify: ^1.0.1
|
||||
@@ -6628,7 +6628,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/medusa@*, @medusajs/medusa@^1.12.1, @medusajs/medusa@^1.12.2, @medusajs/medusa@^1.12.3, @medusajs/medusa@^1.15.0, @medusajs/medusa@^1.15.1, @medusajs/medusa@^1.17.0, @medusajs/medusa@^1.17.1, @medusajs/medusa@workspace:*, @medusajs/medusa@workspace:packages/medusa":
|
||||
"@medusajs/medusa@^1.12.1, @medusajs/medusa@^1.12.2, @medusajs/medusa@^1.12.3, @medusajs/medusa@^1.15.0, @medusajs/medusa@^1.15.1, @medusajs/medusa@^1.17.0, @medusajs/medusa@^1.17.1, @medusajs/medusa@workspace:*, @medusajs/medusa@workspace:packages/medusa":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/medusa@workspace:packages/medusa"
|
||||
dependencies:
|
||||
@@ -6867,7 +6867,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@medusajs/types@*, @medusajs/types@^1.10.0, @medusajs/types@^1.11.2, @medusajs/types@^1.11.3, @medusajs/types@^1.8.10, @medusajs/types@^1.8.8, @medusajs/types@workspace:^, @medusajs/types@workspace:packages/types":
|
||||
"@medusajs/types@^1.10.0, @medusajs/types@^1.11.2, @medusajs/types@^1.11.3, @medusajs/types@^1.8.10, @medusajs/types@^1.8.8, @medusajs/types@workspace:^, @medusajs/types@workspace:packages/types":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@medusajs/types@workspace:packages/types"
|
||||
dependencies:
|
||||
@@ -32499,7 +32499,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"medusa-react@*, medusa-react@^9.0.6, medusa-react@workspace:packages/medusa-react":
|
||||
"medusa-react@^9.0.6, medusa-react@^9.0.7, medusa-react@workspace:packages/medusa-react":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "medusa-react@workspace:packages/medusa-react"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user