fix(dashboard): Add FE validation for conditional prices (#10523)

* fix(dashboard): Add FE validation for conditional prices

* fix(dashboard): Add FE validation for conditional prices

* lint

* only set error on one field

* fix which field shows error
This commit is contained in:
Kasper Fabricius Kristensen authored and GitHub committed 2024-12-10 11:57:10 +01:00
1 parent 69f4c4f4e0
commit de81187929
5 files changed
+165 -70

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/dashboard": patch
---
fix(dashboard): Add FE validation for conditional prices
@@ -2978,15 +2978,11 @@
"type": "string"
}
},
"required": [
"stockedQuantity"
],
"required": ["stockedQuantity"],
"additionalProperties": false
}
},
"required": [
"errors"
],
"required": ["errors"],
"additionalProperties": false
},
"toast": {
@@ -3792,10 +3788,7 @@
"type": "string"
}
},
"required": [
"title",
"requestSuccess"
],
"required": ["title", "requestSuccess"],
"additionalProperties": false
},
"shippingAddress": {
@@ -3808,10 +3801,7 @@
"type": "string"
}
},
"required": [
"title",
"requestSuccess"
],
"required": ["title", "requestSuccess"],
"additionalProperties": false
},
"billingAddress": {
@@ -3824,18 +3814,11 @@
"type": "string"
}
},
"required": [
"title",
"requestSuccess"
],
"required": ["title", "requestSuccess"],
"additionalProperties": false
}
},
"required": [
"email",
"shippingAddress",
"billingAddress"
],
"required": ["email", "shippingAddress", "billingAddress"],
"additionalProperties": false
},
"returns": {
@@ -4987,11 +4970,7 @@
"type": "string"
}
},
"required": [
"shipping_address",
"billing_address",
"email"
],
"required": ["shipping_address", "billing_address", "email"],
"additionalProperties": false
}
},
@@ -5560,9 +5539,7 @@
"type": "string"
}
},
"required": [
"cartItemTotal"
],
"required": ["cartItemTotal"],
"additionalProperties": false
},
"summaries": {
@@ -5578,11 +5555,7 @@
"type": "string"
}
},
"required": [
"range",
"greaterThan",
"lessThan"
],
"required": ["range", "greaterThan", "lessThan"],
"additionalProperties": false
},
"actions": {
@@ -5595,10 +5568,7 @@
"type": "string"
}
},
"required": [
"addPrice",
"manageConditionalPrices"
],
"required": ["addPrice", "manageConditionalPrices"],
"additionalProperties": false
},
"rules": {
@@ -5614,11 +5584,7 @@
"type": "string"
}
},
"required": [
"amount",
"gte",
"lte"
],
"required": ["amount", "gte", "lte"],
"additionalProperties": false
},
"customRules": {
@@ -5640,13 +5606,7 @@
"type": "string"
}
},
"required": [
"label",
"tooltip",
"eq",
"gt",
"lt"
],
"required": ["label", "tooltip", "eq", "gt", "lt"],
"additionalProperties": false
},
"errors": {
@@ -5660,12 +5620,20 @@
},
"minGreaterThanMax": {
"type": "string"
},
"duplicateAmount": {
"type": "string"
},
"overlappingConditions": {
"type": "string"
}
},
"required": [
"amountRequired",
"minOrMaxRequired",
"minGreaterThanMax"
"minGreaterThanMax",
"duplicateAmount",
"overlappingConditions"
],
"additionalProperties": false
}
@@ -8842,16 +8810,6 @@
"deleteSalesChannelWarning": {
"type": "string"
},
"tooltip": {
"type": "object",
"properties": {
"cannotDeleteDefault": {
"type": "string"
}
},
"required": ["cannotDeleteDefault"],
"additionalProperties": false
},
"toast": {
"type": "object",
"properties": {
@@ -8868,6 +8826,16 @@
"required": ["create", "update", "delete"],
"additionalProperties": false
},
"tooltip": {
"type": "object",
"properties": {
"cannotDeleteDefault": {
"type": "string"
}
},
"required": ["cannotDeleteDefault"],
"additionalProperties": false
},
"products": {
"type": "object",
"properties": {
@@ -8916,6 +8884,7 @@
"productAlreadyAdded",
"deleteSalesChannelWarning",
"toast",
"tooltip",
"products"
],
"additionalProperties": false
@@ -1493,7 +1493,9 @@
"errors": {
"amountRequired": "Shipping option price is required",
"minOrMaxRequired": "At least one of minimum or maximum cart item total must be provided",
"minGreaterThanMax": "Minimum cart item total must be less than or equal to maximum cart item total"
"minGreaterThanMax": "Minimum cart item total must be less than or equal to maximum cart item total",
"duplicateAmount": "Shipping option price must be unique for each condition",
"overlappingConditions": "Conditions must be unique across all price rules"
}
},
"fields": {
@@ -9,6 +9,7 @@ import {
import {
Badge,
Button,
clx,
CurrencyInput,
Heading,
IconButton,
@@ -110,15 +111,29 @@ export const ConditionalPriceForm = ({
remove(index)
}
const handleOnSubmit = conditionalPriceForm.handleSubmit((values) => {
const handleOnSubmit = conditionalPriceForm.handleSubmit(
(values) => {
setFormValue(name, values.prices, {
shouldDirty: true,
shouldValidate: true,
shouldTouch: true,
})
onCloseConditionalPricesModal()
},
(e) => {
const indexesWithErrors = Object.keys(e.prices || {})
setValue((prev) => {
const values = new Set(prev)
indexesWithErrors.forEach((index) => {
values.add(getRuleValue(Number(index)))
})
return Array.from(values)
})
}
)
// Intercept the Cmd + Enter key to only save the inner form.
const handleOnKeyDown = (event: React.KeyboardEvent<HTMLFormElement>) => {
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
@@ -255,7 +270,9 @@ const ConditionalPriceItem = ({
return (
<Accordion.Item
value={getRuleValue(index)}
className="bg-ui-bg-component shadow-elevation-card-rest rounded-lg"
className={clx(
"bg-ui-bg-component shadow-elevation-card-rest rounded-lg"
)}
>
<Accordion.Trigger asChild>
<div className="group/trigger flex w-full cursor-pointer items-start justify-between gap-x-2 p-3">
@@ -73,18 +73,120 @@ export type UpdateConditionalPrice = z.infer<
typeof UpdateConditionalPriceSchema
>
export const CondtionalPriceRuleSchema = z.object({
function refineDuplicates(
data: {
prices: {
amount: string | number
gte?: string | number | null | undefined
lte?: string | number | null | undefined
lt?: number | null | undefined
gt?: number | null | undefined
eq?: number | null | undefined
}[]
},
ctx: z.RefinementCtx
) {
const prices = data.prices
for (let i = 0; i < prices.length; i++) {
for (let j = i + 1; j < prices.length; j++) {
const price1 = prices[i]
const price2 = prices[j]
if (price1.amount === "" || price2.amount === "") {
continue
}
const price1Amount = castNumber(price1.amount)
const price2Amount = castNumber(price2.amount)
if (price1Amount === price2Amount) {
addDuplicateAmountError(ctx, j)
}
// Then check conditions separately
const conditions = [
{ value: price1.gte, type: "gte" },
{ value: price1.lte, type: "lte" },
{ value: price1.eq, type: "eq" },
{ value: price1.lt, type: "lt" },
{ value: price1.gt, type: "gt" },
] as const
conditions.forEach((condition1) => {
if (!condition1.value && condition1.value !== 0) {
return
}
const conditions2 = [
{ value: price2.gte, type: "gte" },
{ value: price2.lte, type: "lte" },
{ value: price2.eq, type: "eq" },
{ value: price2.lt, type: "lt" },
{ value: price2.gt, type: "gt" },
] as const
conditions2.forEach((condition2) => {
if (!condition2.value && condition2.value !== 0) {
return
}
const condition1Value = castNumber(
condition1.value as string | number
)
const condition2Value = castNumber(
condition2.value as string | number
)
if (condition1Value === condition2Value) {
addOverlappingConditionError(ctx, j, condition2.type)
}
})
})
}
}
}
export const CondtionalPriceRuleSchema = z
.object({
prices: z.array(ConditionalPriceSchema),
})
})
.superRefine(refineDuplicates)
export type CondtionalPriceRuleSchemaType = z.infer<
typeof CondtionalPriceRuleSchema
>
export const UpdateConditionalPriceRuleSchema = z.object({
export const UpdateConditionalPriceRuleSchema = z
.object({
prices: z.array(UpdateConditionalPriceSchema),
})
})
.superRefine(refineDuplicates)
export type UpdateConditionalPriceRuleSchemaType = z.infer<
typeof UpdateConditionalPriceRuleSchema
>
const addDuplicateAmountError = (ctx: z.RefinementCtx, index: number) => {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t(
"stockLocations.shippingOptions.conditionalPrices.errors.duplicateAmount"
),
path: ["prices", index, "amount"],
})
}
const addOverlappingConditionError = (
ctx: z.RefinementCtx,
index: number,
type: "gte" | "lte" | "eq" | "lt" | "gt"
) => {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t(
"stockLocations.shippingOptions.conditionalPrices.errors.overlappingConditions"
),
path: ["prices", index, type],
})
}