fix(dashboard,ui): ConditionBlock styling (#10481)
**What** - Resolves CMRC-58 - Also fixes some other issues, TS errors, Eslint warnings etc. in the Promotion domain. There are still several TS errors as the return types from `@medusajs/types` don't seem to match how they are used here, but I have left that as is, as I am not super familiar with the Promotion module.
This commit is contained in:
committed by
GitHub
parent
3409953c4f
commit
c9a66b19af
6
.changeset/quick-items-march.md
Normal file
6
.changeset/quick-items-march.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/ui": patch
|
||||
"@medusajs/dashboard": patch
|
||||
---
|
||||
|
||||
fix(dashboard,ui): Bring ConditionBlock in line with design
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Separator as PrimitiveSeparator,
|
||||
} from "@ariakit/react"
|
||||
import {
|
||||
CheckMini,
|
||||
EllipseMiniSolid,
|
||||
PlusMini,
|
||||
TrianglesMini,
|
||||
@@ -290,7 +291,7 @@ const ComboboxImpl = <T extends Value = string>(
|
||||
ref={comboboxRef}
|
||||
onFocus={() => setOpen(true)}
|
||||
className={clx(
|
||||
"txt-compact-small text-ui-fg-base placeholder:text-ui-fg-subtle transition-fg size-full cursor-pointer bg-transparent pl-2 pr-8 outline-none focus:cursor-text",
|
||||
"txt-compact-small text-ui-fg-base !placeholder:text-ui-fg-muted transition-fg size-full cursor-pointer bg-transparent pl-2 pr-8 outline-none focus:cursor-text",
|
||||
"hover:bg-ui-bg-field-hover",
|
||||
{
|
||||
"opacity-0": hideInput,
|
||||
@@ -349,7 +350,7 @@ const ComboboxImpl = <T extends Value = string>(
|
||||
)}
|
||||
>
|
||||
<PrimitiveComboboxItemCheck className="flex !size-5 items-center justify-center">
|
||||
<EllipseMiniSolid />
|
||||
{isArrayValue ? <CheckMini /> : <EllipseMiniSolid />}
|
||||
</PrimitiveComboboxItemCheck>
|
||||
<PrimitiveComboboxItemValue className="txt-compact-small">
|
||||
{label}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RuleAttributeOptionsResponse, StoreDTO } from "@medusajs/types"
|
||||
import { Input, Select } from "@medusajs/ui"
|
||||
import { RefCallBack, useWatch } from "react-hook-form"
|
||||
import { useWatch } from "react-hook-form"
|
||||
import { Form } from "../../../../../../components/common/form"
|
||||
import { Combobox } from "../../../../../../components/inputs/combobox"
|
||||
import { usePromotionRuleValues } from "../../../../../../hooks/api/promotions"
|
||||
@@ -13,9 +13,8 @@ type RuleValueFormFieldType = {
|
||||
| "application_method.buy_rules"
|
||||
| "rules"
|
||||
| "application_method.target_rules"
|
||||
valuesField: any
|
||||
operatorsField: any
|
||||
valuesRef: RefCallBack
|
||||
name: string
|
||||
operator: string
|
||||
fieldRule: any
|
||||
attributes: RuleAttributeOptionsResponse[]
|
||||
ruleType: "rules" | "target-rules" | "buy-rules"
|
||||
@@ -39,9 +38,8 @@ export const RuleValueFormField = ({
|
||||
form,
|
||||
identifier,
|
||||
scope,
|
||||
valuesField,
|
||||
operatorsField,
|
||||
valuesRef,
|
||||
name,
|
||||
operator,
|
||||
fieldRule,
|
||||
attributes,
|
||||
ruleType,
|
||||
@@ -65,13 +63,13 @@ export const RuleValueFormField = ({
|
||||
|
||||
const watchOperator = useWatch({
|
||||
control: form.control,
|
||||
name: operatorsField.name,
|
||||
name: operator,
|
||||
})
|
||||
|
||||
return (
|
||||
<Form.Field
|
||||
key={`${identifier}.${scope}.${valuesField.name}-${fieldRule.attribute}`}
|
||||
{...valuesField}
|
||||
key={`${identifier}.${scope}.${name}-${fieldRule.attribute}`}
|
||||
name={name}
|
||||
render={({ field: { onChange, ref, ...field } }) => {
|
||||
if (attribute?.field_type === "number") {
|
||||
return (
|
||||
@@ -82,7 +80,7 @@ export const RuleValueFormField = ({
|
||||
type="number"
|
||||
onChange={onChange}
|
||||
className="bg-ui-bg-base"
|
||||
ref={valuesRef}
|
||||
ref={ref}
|
||||
min={1}
|
||||
disabled={!fieldRule.attribute}
|
||||
/>
|
||||
@@ -96,6 +94,7 @@ export const RuleValueFormField = ({
|
||||
<Form.Control>
|
||||
<Input
|
||||
{...field}
|
||||
ref={ref}
|
||||
onChange={onChange}
|
||||
className="bg-ui-bg-base"
|
||||
disabled={!fieldRule.attribute}
|
||||
@@ -143,6 +142,7 @@ export const RuleValueFormField = ({
|
||||
<Form.Control>
|
||||
<Combobox
|
||||
{...field}
|
||||
ref={ref}
|
||||
placeholder="Select Values"
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { XMarkMini } from "@medusajs/icons"
|
||||
import { PromotionDTO } from "@medusajs/types"
|
||||
import { Badge, Button, Heading, Select, Text } from "@medusajs/ui"
|
||||
import { Fragment, useEffect } from "react"
|
||||
import { useFieldArray, UseFormReturn, useWatch } from "react-hook-form"
|
||||
import { Badge, Button, Heading, IconButton, Select, Text } from "@medusajs/ui"
|
||||
import { forwardRef, Fragment, useEffect } from "react"
|
||||
import {
|
||||
ControllerRenderProps,
|
||||
useFieldArray,
|
||||
UseFormReturn,
|
||||
useWatch,
|
||||
} from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Form } from "../../../../../../components/common/form"
|
||||
import {
|
||||
@@ -102,7 +107,16 @@ export const RulesFormField = ({
|
||||
|
||||
replace(generateRuleAttributes(rulesToAppend) as any)
|
||||
}
|
||||
}, [promotionType, isLoading])
|
||||
}, [
|
||||
promotionType,
|
||||
isLoading,
|
||||
ruleType,
|
||||
fields.length,
|
||||
form,
|
||||
replace,
|
||||
rules,
|
||||
promotion?.id,
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
@@ -116,24 +130,16 @@ export const RulesFormField = ({
|
||||
|
||||
{fields.map((fieldRule: any, index) => {
|
||||
const identifier = fieldRule.id
|
||||
const { ref: attributeRef, ...attributeField } = form.register(
|
||||
`${scope}.${index}.attribute`
|
||||
)
|
||||
const { ref: operatorRef, ...operatorsField } = form.register(
|
||||
`${scope}.${index}.operator`
|
||||
)
|
||||
const { ref: valuesRef, ...valuesField } = form.register(
|
||||
`${scope}.${index}.values`
|
||||
)
|
||||
|
||||
return (
|
||||
<Fragment key={`${fieldRule.id}.${index}.${fieldRule.attribute}`}>
|
||||
<div className="bg-ui-bg-subtle border-ui-border-base flex flex-row gap-2 rounded-xl border px-2 py-2">
|
||||
<div className="grow">
|
||||
<Form.Field
|
||||
key={`${identifier}.${scope}.${attributeField.name}`}
|
||||
{...attributeField}
|
||||
render={({ field: { onChange, ref, ...field } }) => {
|
||||
name={`${scope}.${index}.attribute`}
|
||||
render={({ field }) => {
|
||||
const { onChange, ref, ...fieldProps } = field
|
||||
|
||||
const existingAttributes =
|
||||
fields?.map((field: any) => field.attribute) || []
|
||||
const attributeOptions =
|
||||
@@ -145,55 +151,71 @@ export const RulesFormField = ({
|
||||
return !existingAttributes.includes(attr.value)
|
||||
}) || []
|
||||
|
||||
const disabled = !!fieldRule.required
|
||||
const onValueChange = (e: string) => {
|
||||
const currentAttributeOption = attributeOptions.find(
|
||||
(ao) => ao.id === e
|
||||
)
|
||||
|
||||
update(index, {
|
||||
...fieldRule,
|
||||
values: [],
|
||||
disguised: currentAttributeOption?.disguised || false,
|
||||
})
|
||||
onChange(e)
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Item className="mb-2">
|
||||
{fieldRule.required && (
|
||||
<p className="text text-ui-fg-muted txt-small">
|
||||
{t("promotions.form.required")}
|
||||
</p>
|
||||
<div className="flex items-center px-2">
|
||||
<p className="text text-ui-fg-muted txt-small">
|
||||
{t("promotions.form.required")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form.Control>
|
||||
<Select
|
||||
{...field}
|
||||
onValueChange={(e) => {
|
||||
const currentAttributeOption =
|
||||
attributeOptions.find((ao) => ao.id === e)
|
||||
|
||||
update(index, {
|
||||
...fieldRule,
|
||||
values: [],
|
||||
disguised:
|
||||
currentAttributeOption?.disguised || false,
|
||||
})
|
||||
onChange(e)
|
||||
}}
|
||||
disabled={fieldRule.required}
|
||||
>
|
||||
<Select.Trigger
|
||||
ref={attributeRef}
|
||||
className="bg-ui-bg-base"
|
||||
{!disabled ? (
|
||||
<Select
|
||||
{...fieldProps}
|
||||
onValueChange={onValueChange}
|
||||
disabled={fieldRule.required}
|
||||
>
|
||||
<Select.Value
|
||||
placeholder={t(
|
||||
"promotions.form.selectAttribute"
|
||||
)}
|
||||
/>
|
||||
</Select.Trigger>
|
||||
<Select.Trigger
|
||||
ref={ref}
|
||||
className="bg-ui-bg-base"
|
||||
>
|
||||
<Select.Value
|
||||
placeholder={t(
|
||||
"promotions.form.selectAttribute"
|
||||
)}
|
||||
/>
|
||||
</Select.Trigger>
|
||||
|
||||
<Select.Content>
|
||||
{attributeOptions?.map((c, i) => (
|
||||
<Select.Item
|
||||
key={`${identifier}-attribute-option-${i}`}
|
||||
value={c.value}
|
||||
>
|
||||
<span className="text-ui-fg-subtle">
|
||||
{c.label}
|
||||
</span>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
<Select.Content>
|
||||
{attributeOptions?.map((c, i) => (
|
||||
<Select.Item
|
||||
key={`${identifier}-attribute-option-${i}`}
|
||||
value={c.value}
|
||||
>
|
||||
<span className="text-ui-fg-subtle">
|
||||
{c.label}
|
||||
</span>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
) : (
|
||||
<DisabledField
|
||||
label={
|
||||
attributeOptions?.find(
|
||||
(ao) => ao.value === fieldRule.attribute
|
||||
)?.label || ""
|
||||
}
|
||||
field={field}
|
||||
/>
|
||||
)}
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
@@ -203,43 +225,60 @@ export const RulesFormField = ({
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Form.Field
|
||||
key={`${identifier}.${scope}.${operatorsField.name}`}
|
||||
{...operatorsField}
|
||||
render={({ field: { onChange, ref, ...field } }) => {
|
||||
const currentAttributeOption = attributes.find(
|
||||
name={`${scope}.${index}.operator`}
|
||||
render={({ field }) => {
|
||||
const { onChange, ref, ...fieldProps } = field
|
||||
|
||||
const currentAttributeOption = attributes?.find(
|
||||
(attr) => attr.value === fieldRule.attribute
|
||||
)
|
||||
|
||||
const options =
|
||||
currentAttributeOption?.operators?.map((o, idx) => ({
|
||||
label: o.label,
|
||||
value: o.value,
|
||||
key: `${identifier}-operator-option-${idx}`,
|
||||
})) || []
|
||||
|
||||
const disabled =
|
||||
!!fieldRule.attribute && options?.length <= 1
|
||||
|
||||
return (
|
||||
<Form.Item className="basis-1/2">
|
||||
<Form.Control>
|
||||
<Select
|
||||
{...field}
|
||||
disabled={!fieldRule.attribute}
|
||||
onValueChange={onChange}
|
||||
>
|
||||
<Select.Trigger
|
||||
ref={operatorRef}
|
||||
className="bg-ui-bg-base"
|
||||
{!disabled ? (
|
||||
<Select
|
||||
{...fieldProps}
|
||||
disabled={!fieldRule.attribute}
|
||||
onValueChange={onChange}
|
||||
>
|
||||
<Select.Value placeholder="Select Operator" />
|
||||
</Select.Trigger>
|
||||
<Select.Trigger
|
||||
ref={ref}
|
||||
className="bg-ui-bg-base"
|
||||
>
|
||||
<Select.Value placeholder="Select Operator" />
|
||||
</Select.Trigger>
|
||||
|
||||
<Select.Content>
|
||||
{currentAttributeOption?.operators?.map(
|
||||
(c, i) => (
|
||||
<Select.Item
|
||||
key={`${identifier}-operator-option-${i}`}
|
||||
value={c.value}
|
||||
>
|
||||
<Select.Content>
|
||||
{options?.map((c) => (
|
||||
<Select.Item key={c.key} value={c.value}>
|
||||
<span className="text-ui-fg-subtle">
|
||||
{c.label}
|
||||
</span>
|
||||
</Select.Item>
|
||||
)
|
||||
)}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
) : (
|
||||
<DisabledField
|
||||
label={
|
||||
options.find(
|
||||
(o) => o.value === fieldProps.value
|
||||
)?.label || ""
|
||||
}
|
||||
field={field}
|
||||
/>
|
||||
)}
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
@@ -251,9 +290,8 @@ export const RulesFormField = ({
|
||||
form={form}
|
||||
identifier={identifier}
|
||||
scope={scope}
|
||||
valuesField={valuesField}
|
||||
operatorsField={operatorsField}
|
||||
valuesRef={valuesRef}
|
||||
name={`${scope}.${index}.values`}
|
||||
operator={`${scope}.${index}.operator`}
|
||||
fieldRule={fieldRule}
|
||||
attributes={attributes}
|
||||
ruleType={ruleType}
|
||||
@@ -261,20 +299,25 @@ export const RulesFormField = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-none self-center px-1">
|
||||
<XMarkMini
|
||||
className={`text-ui-fg-muted cursor-pointer ${
|
||||
fieldRule.required ? "invisible" : "visible"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (!fieldRule.required) {
|
||||
setRulesToRemove &&
|
||||
setRulesToRemove([...rulesToRemove, fieldRule])
|
||||
<div className="size-7 flex-none self-center">
|
||||
{!fieldRule.required && (
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="transparent"
|
||||
className="text-ui-fg-muted"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!fieldRule.required) {
|
||||
setRulesToRemove &&
|
||||
setRulesToRemove([...rulesToRemove, fieldRule])
|
||||
|
||||
remove(index)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
remove(index)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<XMarkMini />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -291,7 +334,7 @@ export const RulesFormField = ({
|
||||
)
|
||||
})}
|
||||
|
||||
<div className={!!fields.length ? "mt-6" : ""}>
|
||||
<div className={fields.length ? "mt-6" : ""}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
@@ -330,3 +373,27 @@ export const RulesFormField = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type DisabledAttributeProps = {
|
||||
label: string
|
||||
field: ControllerRenderProps
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this if an attribute is disabled, or
|
||||
* if there is only one option available.
|
||||
*/
|
||||
const DisabledField = forwardRef<HTMLInputElement, DisabledAttributeProps>(
|
||||
({ label, field }, ref) => {
|
||||
return (
|
||||
<div>
|
||||
<div className="txt-compact-small bg-ui-bg-component shadow-borders-base text-ui-fg-base h-8 rounded-md px-2 py-1.5">
|
||||
{label}
|
||||
</div>
|
||||
<input {...field} ref={ref} disabled hidden />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
DisabledField.displayName = "DisabledField"
|
||||
|
||||
@@ -79,6 +79,7 @@ export const CreatePromotionForm = () => {
|
||||
defaultValues,
|
||||
resolver: zodResolver(CreatePromotionSchema),
|
||||
})
|
||||
const { setValue, reset, getValues } = form
|
||||
|
||||
const { mutateAsync: createPromotion } = useCreatePromotion()
|
||||
|
||||
@@ -150,7 +151,7 @@ export const CreatePromotionForm = () => {
|
||||
})
|
||||
)
|
||||
|
||||
handleSuccess()
|
||||
handleSuccess(`/promotions/${promotion.id}`)
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(e.message)
|
||||
@@ -244,20 +245,20 @@ export const CreatePromotionForm = () => {
|
||||
return
|
||||
}
|
||||
|
||||
form.reset({ ...defaultValues, template_id: watchTemplateId })
|
||||
reset({ ...defaultValues, template_id: watchTemplateId })
|
||||
|
||||
for (const [key, value] of Object.entries(currentTemplate.defaults)) {
|
||||
if (typeof value === "object") {
|
||||
for (const [subKey, subValue] of Object.entries(value)) {
|
||||
form.setValue(`application_method.${subKey}`, subValue)
|
||||
setValue(`application_method.${subKey}`, subValue)
|
||||
}
|
||||
} else {
|
||||
form.setValue(key, value)
|
||||
setValue(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
return currentTemplate
|
||||
}, [watchTemplateId])
|
||||
}, [watchTemplateId, setValue, reset])
|
||||
|
||||
const watchValueType = useWatch({
|
||||
control: form.control,
|
||||
@@ -272,9 +273,9 @@ export const CreatePromotionForm = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (watchAllocation === "across") {
|
||||
form.setValue("application_method.max_quantity", null)
|
||||
setValue("application_method.max_quantity", null)
|
||||
}
|
||||
}, [watchAllocation])
|
||||
}, [watchAllocation, setValue])
|
||||
|
||||
const watchType = useWatch({
|
||||
control: form.control,
|
||||
@@ -307,19 +308,19 @@ export const CreatePromotionForm = () => {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const formData = form.getValues()
|
||||
const formData = getValues()
|
||||
|
||||
if (watchCampaignChoice !== "existing") {
|
||||
form.setValue("campaign_id", undefined)
|
||||
setValue("campaign_id", undefined)
|
||||
}
|
||||
|
||||
if (watchCampaignChoice !== "new") {
|
||||
form.setValue("campaign", undefined)
|
||||
setValue("campaign", undefined)
|
||||
}
|
||||
|
||||
if (watchCampaignChoice === "new") {
|
||||
if (!formData.campaign || !formData.campaign?.budget?.type) {
|
||||
form.setValue("campaign", {
|
||||
setValue("campaign", {
|
||||
...DEFAULT_CAMPAIGN_VALUES,
|
||||
budget: {
|
||||
...DEFAULT_CAMPAIGN_VALUES.budget,
|
||||
@@ -328,7 +329,7 @@ export const CreatePromotionForm = () => {
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [watchCampaignChoice])
|
||||
}, [watchCampaignChoice, getValues, setValue])
|
||||
|
||||
const watchRules = useWatch({
|
||||
control: form.control,
|
||||
|
||||
@@ -85,7 +85,7 @@ export const PromotionGeneralSection = ({
|
||||
[PromotionStatus.EXPIRED]: ["red", t("statuses.expired")],
|
||||
}[getPromotionStatus(promotion)] as [
|
||||
"grey" | "orange" | "green" | "red",
|
||||
string,
|
||||
string
|
||||
]
|
||||
|
||||
const displayValue = getDisplayValue(promotion)
|
||||
@@ -141,7 +141,11 @@ export const PromotionGeneralSection = ({
|
||||
{t("fields.code")}
|
||||
</Text>
|
||||
|
||||
<Copy content={promotion.code!} asChild>
|
||||
<Copy
|
||||
content={promotion.code!}
|
||||
className="text-ui-tag-neutral-text"
|
||||
asChild
|
||||
>
|
||||
<Badge
|
||||
size="2xsmall"
|
||||
rounded="full"
|
||||
|
||||
@@ -90,7 +90,7 @@ const Copy = React.forwardRef<HTMLButtonElement, CopyProps>(
|
||||
aria-label="Copy code snippet"
|
||||
type="button"
|
||||
className={clx(
|
||||
"text-ui-contrast-fg-secondary h-fit w-fit",
|
||||
"h-fit w-fit",
|
||||
className
|
||||
)}
|
||||
onClick={copyToClipboard}
|
||||
@@ -100,14 +100,14 @@ const Copy = React.forwardRef<HTMLButtonElement, CopyProps>(
|
||||
children
|
||||
) : done ? (
|
||||
isDefault ? (
|
||||
<CheckCircleSolid />
|
||||
<CheckCircleSolid className="text-ui-fg-subtle" />
|
||||
) : (
|
||||
<CheckCircleMiniSolid />
|
||||
<CheckCircleMiniSolid className="text-ui-fg-subtle" />
|
||||
)
|
||||
) : isDefault ? (
|
||||
<SquareTwoStack />
|
||||
<SquareTwoStack className="text-ui-fg-subtle" />
|
||||
) : (
|
||||
<SquareTwoStackMini />
|
||||
<SquareTwoStackMini className="text-ui-fg-subtle" />
|
||||
)}
|
||||
</Component>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { EllipseMiniSolid, TrianglesMini } from "@medusajs/icons"
|
||||
import { Check, TrianglesMini } from "@medusajs/icons"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { cva } from "cva"
|
||||
import * as React from "react"
|
||||
@@ -194,7 +194,7 @@ const Item = React.forwardRef<
|
||||
>
|
||||
<span className="flex h-[15px] w-[15px] items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator className="flex items-center justify-center">
|
||||
<EllipseMiniSolid />
|
||||
<Check />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText className="flex-1 truncate">
|
||||
|
||||
Reference in New Issue
Block a user