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:
Kasper Fabricius Kristensen authored and GitHub committed 2023-10-12 08:34:46 +00:00
1 parent a91fca69ac
commit e04927a3cb
12 files changed
+137 -18

No files matched your search

+6
View 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
+3 -3
View File
@@ -70,7 +70,7 @@
"i18next-browser-languagedetector": "^7.0.1", "i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.2.1", "i18next-http-backend": "^2.2.1",
"md5": "^2.3.0", "md5": "^2.3.0",
"medusa-react": "*", "medusa-react": "^9.0.7",
"mini-css-extract-plugin": "^2.7.6", "mini-css-extract-plugin": "^2.7.6",
"moment": "^2.29.4", "moment": "^2.29.4",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
@@ -118,8 +118,8 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/types": "7.22.5", "@babel/types": "7.22.5",
"@medusajs/medusa": "*", "@medusajs/medusa": "^1.17.1",
"@medusajs/types": "*", "@medusajs/types": "^1.11.3",
"@types/pluralize": "^0.0.29", "@types/pluralize": "^0.0.29",
"@types/react": "^18.0.27", "@types/react": "^18.0.27",
"@types/react-datepicker": "^4.10.0", "@types/react-datepicker": "^4.10.0",
@@ -1423,6 +1423,9 @@
"price-list-new-form-prompt-title": "Are you sure?", "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-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-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-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-success-message": "The new prices have been added to the price list.",
"price-list-add-products-modal-error-title": "An error occurred", "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-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-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-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-name": "Name",
"price-list-details-form-customer-groups-members": "Members", "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.", "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-visibility-regions-label": "Regions",
"price-list-product-prices-form-column-product-label": "Product", "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-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-all": "Select all products on the current page",
"price-list-products-form-select-row": "Select row", "price-list-products-form-select-row": "Select row",
"price-list-products-form-product-label": "Product", "price-list-products-form-product-label": "Product",
@@ -1530,6 +1533,9 @@
"price-list-products-form-no-products": "No products found.", "price-list-products-form-no-products": "No products found.",
"price-list-products-form-heading": "Choose products", "price-list-products-form-heading": "Choose products",
"price-list-products-form-search-placeholder": "Search", "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-title": "Price list created",
"price-list-new-form-notification-success-message": "Successfully created price list", "price-list-new-form-notification-success-message": "Successfully created price list",
"price-list-new-form-notification-error-title": "An error occurred", "price-list-new-form-notification-error-title": "An error occurred",
@@ -69,6 +69,7 @@ const PriceListDetailsSection = ({
name, name,
} }
), ),
verificationText: name,
confirmText, confirmText,
cancelText, cancelText,
}) })
@@ -48,7 +48,7 @@ const PriceListEdit = () => {
return ( return (
<> <>
<div className="flex flex-col gap-y-2"> <div className="flex flex-col gap-y-2">
{getWidgets("product.details.before").map((w, i) => { {getWidgets("price_list.details.before").map((w, i) => {
return ( return (
<WidgetContainer <WidgetContainer
key={i} key={i}
@@ -60,7 +60,7 @@ const PriceListEdit = () => {
})} })}
<PriceListGeneralSection priceList={price_list} /> <PriceListGeneralSection priceList={price_list} />
<PriceListPricesSection priceList={price_list} /> <PriceListPricesSection priceList={price_list} />
{getWidgets("product.details.after").map((w, i) => { {getWidgets("price_list.details.after").map((w, i) => {
return ( return (
<WidgetContainer <WidgetContainer
key={i} key={i}
@@ -12,9 +12,9 @@ import {
import * as React from "react" import * as React from "react"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import * as z from "zod" import * as z from "zod"
import { useAdminCreatePriceListPrices } from "medusa-react" import { useAdminCreatePriceListPrices } from "medusa-react"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Form } from "../../../../components/helpers/form" import { Form } from "../../../../components/helpers/form"
import useNotification from "../../../../hooks/use-notification" import useNotification from "../../../../hooks/use-notification"
import { useFeatureFlag } from "../../../../providers/feature-flag-provider" import { useFeatureFlag } from "../../../../providers/feature-flag-provider"
@@ -110,6 +110,7 @@ const AddProductsModal = ({
trigger, trigger,
handleSubmit, handleSubmit,
setValue, setValue,
setError,
getValues, getValues,
reset, reset,
formState: { isDirty }, formState: { isDirty },
@@ -138,6 +139,7 @@ const AddProductsModal = ({
const onCloseModal = React.useCallback(() => { const onCloseModal = React.useCallback(() => {
onOpenChange(false) onOpenChange(false)
setTab(Tab.PRODUCTS) setTab(Tab.PRODUCTS)
setSelectedIds([])
setStatus({ setStatus({
[Tab.PRODUCTS]: "not-started", [Tab.PRODUCTS]: "not-started",
[Tab.PRICES]: "not-started", [Tab.PRICES]: "not-started",
@@ -205,6 +207,42 @@ const AddProductsModal = ({
const onSubmit = handleSubmit(async (data) => { const onSubmit = handleSubmit(async (data) => {
const prices: PricePayload[] = [] 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)) { for (const productId of Object.keys(data.prices.products)) {
const product = data.prices.products[productId] const product = data.prices.products[productId]
@@ -533,6 +571,7 @@ const AddProductsModal = ({
</span> </span>
</ProgressTabs.Trigger> </ProgressTabs.Trigger>
<ProgressTabs.Trigger <ProgressTabs.Trigger
disabled={selectedIds.length === 0}
value={Tab.PRICES} value={Tab.PRICES}
className="w-full max-w-[200px]" className="w-full max-w-[200px]"
status={status[Tab.PRICES]} status={status[Tab.PRICES]}
@@ -376,7 +376,10 @@ const PriceListDates = ({ form, layout }: PriceListDetailsFormProps) => {
return ( return (
<Form.Item> <Form.Item>
<Form.Label> <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.Label>
<Form.Control> <Form.Control>
<DatePicker <DatePicker
@@ -9,6 +9,7 @@ import { useDebounce } from "../../../../hooks/use-debounce"
import { NestedForm } from "../../../../utils/nested-form" import { NestedForm } from "../../../../utils/nested-form"
import { ProductFilter, ProductFilterMenu } from "../../components" import { ProductFilter, ProductFilterMenu } from "../../components"
import { PriceListPricesSchema } from "./types" import { PriceListPricesSchema } from "./types"
import { Form } from "../../../../components/helpers/form"
type PriceListPricesFormProps = { type PriceListPricesFormProps = {
form: NestedForm<PriceListPricesSchema> form: NestedForm<PriceListPricesSchema>
@@ -23,7 +24,12 @@ const PriceListPricesForm = ({
productIds, productIds,
priceListId, priceListId,
}: PriceListPricesFormProps) => { }: PriceListPricesFormProps) => {
const { path, getValues } = form const {
control,
path,
getValues,
formState: { isDirty },
} = form
const { t } = useTranslation() const { t } = useTranslation()
@@ -87,9 +93,24 @@ const PriceListPricesForm = ({
<div className="relative flex h-full w-full flex-col"> <div className="relative flex h-full w-full flex-col">
<div> <div>
<div className="border-ui-border-base bg-ui-bg-base z-10 flex items-center justify-between border-b px-4 py-3"> <div className="border-ui-border-base bg-ui-bg-base z-10 flex items-center justify-between border-b px-4 py-3">
<div className="flex items-center gap-x-3">
<Heading> <Heading>
{t("price-list-prices-form-heading", "Edit prices")} {t("price-list-prices-form-heading", "Edit prices")}
</Heading> </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"> <div className="flex items-center gap-x-2">
<ProductFilterMenu <ProductFilterMenu
onClearFilters={() => setFilters({})} onClearFilters={() => setFilters({})}
@@ -1565,8 +1565,9 @@ const PriceListProductPricesForm = ({
<span> <span>
{t( {t(
"price-list-product-prices-form-column-regions-price-label", "price-list-product-prices-form-column-regions-price-label",
"Price {{code}}", "Price {{name}} ({{code}})",
{ {
name: region.name,
code: region.currency_code.toUpperCase(), code: region.currency_code.toUpperCase(),
} }
)} )}
@@ -132,6 +132,7 @@ const PriceListNew = () => {
reset, reset,
getValues, getValues,
setValue, setValue,
setError,
handleSubmit, handleSubmit,
formState: { isDirty }, formState: { isDirty },
} = form } = form
@@ -163,6 +164,7 @@ const PriceListNew = () => {
const onCloseModal = React.useCallback(() => { const onCloseModal = React.useCallback(() => {
setOpen(false) setOpen(false)
setTab(Tab.DETAILS) setTab(Tab.DETAILS)
setSelectedIds([])
setStatus({ setStatus({
[Tab.DETAILS]: "not-started", [Tab.DETAILS]: "not-started",
[Tab.PRODUCTS]: "not-started", [Tab.PRODUCTS]: "not-started",
@@ -228,6 +230,42 @@ const PriceListNew = () => {
await handleSubmit(async (data) => { await handleSubmit(async (data) => {
const prices: PricePayload[] = [] 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 * Loop through all the products and variants
* and create a payload for each price. * and create a payload for each price.
@@ -348,6 +386,8 @@ const PriceListNew = () => {
mutateAsync, mutateAsync,
notification, notification,
onCloseModal, onCloseModal,
setError,
prompt,
t, t,
isTaxInclPricesEnabled, isTaxInclPricesEnabled,
regions, regions,
@@ -384,7 +384,9 @@ const PriceListTableRowActions = ({ row }: PriceListTableRowActionsProps) => {
const navigate = useNavigate() const navigate = useNavigate()
const handleDelete = async () => { const handleDelete = async (e: React.MouseEvent) => {
e.stopPropagation()
const response = await prompt({ const response = await prompt({
title: "Are you sure?", title: "Are you sure?",
description: "This will permanently delete the price list", description: "This will permanently delete the price list",
+6 -6
View File
@@ -6268,8 +6268,8 @@ __metadata:
"@hookform/error-message": ^2.0.1 "@hookform/error-message": ^2.0.1
"@hookform/resolvers": ^3.3.1 "@hookform/resolvers": ^3.3.1
"@medusajs/icons": 1.1.0 "@medusajs/icons": 1.1.0
"@medusajs/medusa": "*" "@medusajs/medusa": ^1.17.1
"@medusajs/types": "*" "@medusajs/types": ^1.11.3
"@medusajs/ui": ^2.2.0 "@medusajs/ui": ^2.2.0
"@medusajs/ui-preset": 1.0.2 "@medusajs/ui-preset": 1.0.2
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.10 "@pmmmwh/react-refresh-webpack-plugin": ^0.5.10
@@ -6311,7 +6311,7 @@ __metadata:
i18next-parser: ^8.0.0 i18next-parser: ^8.0.0
jest: 25.5.4 jest: 25.5.4
md5: ^2.3.0 md5: ^2.3.0
medusa-react: "*" medusa-react: ^9.0.7
mini-css-extract-plugin: ^2.7.6 mini-css-extract-plugin: ^2.7.6
moment: ^2.29.4 moment: ^2.29.4
path-browserify: ^1.0.1 path-browserify: ^1.0.1
@@ -6628,7 +6628,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft 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 version: 0.0.0-use.local
resolution: "@medusajs/medusa@workspace:packages/medusa" resolution: "@medusajs/medusa@workspace:packages/medusa"
dependencies: dependencies:
@@ -6867,7 +6867,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft 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 version: 0.0.0-use.local
resolution: "@medusajs/types@workspace:packages/types" resolution: "@medusajs/types@workspace:packages/types"
dependencies: dependencies:
@@ -32499,7 +32499,7 @@ __metadata:
languageName: unknown languageName: unknown
linkType: soft 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 version: 0.0.0-use.local
resolution: "medusa-react@workspace:packages/medusa-react" resolution: "medusa-react@workspace:packages/medusa-react"
dependencies: dependencies: