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
2023-10-12 10:34:46 +02:00
committed by GitHub
parent a91fca69ac
commit e04927a3cb
12 changed files with 140 additions and 21 deletions

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

View File

@@ -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",

View File

@@ -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",

View File

@@ -69,6 +69,7 @@ const PriceListDetailsSection = ({
name,
}
),
verificationText: name,
confirmText,
cancelText,
})

View File

@@ -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}

View File

@@ -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]}

View File

@@ -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

View File

@@ -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({})}

View File

@@ -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(),
}
)}

View File

@@ -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,

View File

@@ -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",

View File

@@ -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: