Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/app-elements/src/providers/I18NProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const initI18n = async (
load: "languageOnly",
lng: localeCode,
fallbackLng: i18nLocales[0],
showSupportNotice: false,
react: {
useSuspense: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ export const CodeEditor = forwardRef<
const schema = await fetchJsonSchema(jsonSchema, domain).then(
(json) => {
if (json != null) {
console.log(json)
return clearExamples(json)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function DropdownIndicator(
{/** biome-ignore lint/a11y/noSvgWithoutTitle: Don't need to add a title */}
<svg
width="12"
height="auto"
height="100%"
viewBox="0 0 16 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
Expand Down
247 changes: 76 additions & 171 deletions packages/app-elements/src/ui/forms/RuleEngine/Action/ActionListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { useEffect } from "react"
import { useTranslation } from "#providers/I18NProvider"
import { Icon } from "#ui/atoms/Icon"
import { Text } from "#ui/atoms/Text"
import { Dropdown, DropdownItem } from "#ui/composite/Dropdown"
import {
InputSelect,
isMultiValueSelected,
isSingleValueSelected,
} from "#ui/forms/InputSelect"
import { useAvailableGroups } from "../Condition/hooks"
import { InputSelect, isSingleValueSelected } from "#ui/forms/InputSelect"
import { ListItemContainer } from "../layout/ListItemContainer"
import { OptionRow } from "../layout/OptionRow"
import { Options } from "../Options"
import { ActionOptions } from "../Options"
import { useAvailableOptions } from "../optionsConfig"
import type { RuleEngineProps } from "../RuleEngineComponent"
import { useRuleEngine } from "../RuleEngineContext"
import type { ActionType, SchemaActionItem } from "../utils"
import { ActionValue } from "./ActionValue"
Expand All @@ -32,26 +24,28 @@ export function ActionListItem({
setRenderOption,
state: { selectedRuleIndex },
optionsConfig,
schemaType,
} = useRuleEngine()

const availableActionTypes = Object.keys(
optionsConfig.actions,
) as ActionType[]

const selector = item != null && "selector" in item ? item.selector : ""

const actionOptionsConfig =
item?.type != null && item?.selector != null
item?.type != null
? (optionsConfig.actions?.[item.type]?.[
item.selector as keyof (typeof optionsConfig.actions)[typeof item.type]
selector as keyof (typeof optionsConfig.actions)[typeof item.type]
] ?? [])
: []

const { available: availableOptions } = useAvailableOptions(
item,
actionOptionsConfig,
)
const { available: availableOptions, required: requiredOptions } =
useAvailableOptions(item, actionOptionsConfig)

type Item = NonNullable<typeof item>
const typeDictionary: Record<Item["type"], string> = {
free_gift: "Free gift",
percentage: "Percentage discount",
fixed_amount: "Fixed amount",
fixed_price: "Fixed price",
Expand All @@ -61,6 +55,67 @@ export function ActionListItem({

const pathPrefix = `rules.${selectedRuleIndex}.actions.${index}`

const setDefaultOptionFor = (optionName: string) => {
switch (optionName) {
case "selector":
setPath(
`${pathPrefix}.selector`,
schemaType === "order-rules"
? "order.line_items"
: schemaType === "price-rules"
? "price"
: null,
true,
)
break
case "groups":
setPath(`${pathPrefix}.groups`, [])
break
case "round":
setPath(`${pathPrefix}.round`, true)
break
case "apply_on":
setPath(`${pathPrefix}.apply_on`, null, true)
break
case "discount_mode":
setPath(`${pathPrefix}.discount_mode`, "default")
break
case "limit":
setPath(`${pathPrefix}.limit`, {})
break
case "aggregation":
setPath(`${pathPrefix}.aggregation`, {})
break
case "bundle":
setPath(`${pathPrefix}.bundle`, {
type: "balanced",
})
break
case "quantity":
setPath(`${pathPrefix}.quantity`, null, true)
break
case "identifiers": {
setPath(`${pathPrefix}.identifiers`, {})
break
}
}
}

useEffect(
function ensureRequiredOptions() {
if (item == null) {
return
}

requiredOptions.forEach((option) => {
if (!(option.name in item)) {
setDefaultOptionFor(option.name)
}
})
},
[item, requiredOptions, pathPrefix],
)

return (
<div className="mb-4 last:mb-0">
<ListItemContainer
Expand All @@ -79,9 +134,7 @@ export function ActionListItem({
}
options={
<>
<ActionSelector item={item} pathPrefix={pathPrefix} />
<ActionGroups item={item} pathPrefix={pathPrefix} />
<Options item={item} pathPrefix={pathPrefix} />
<ActionOptions item={item} pathPrefix={pathPrefix} />

{availableOptions.length > 0 && (
<Dropdown
Expand All @@ -91,28 +144,7 @@ export function ActionListItem({
<DropdownItem
onClick={() => {
// Set default values based on option type
switch (option.name) {
case "round":
setPath(`${pathPrefix}.round`, true)
break
case "apply_on":
setPath(`${pathPrefix}.apply_on`, null, true)
break
case "discount_mode":
setPath(`${pathPrefix}.discount_mode`, "default")
break
case "limit":
setPath(`${pathPrefix}.limit`, {})
break
case "aggregation":
setPath(`${pathPrefix}.aggregation`, {})
break
case "bundle":
setPath(`${pathPrefix}.bundle`, {
type: "balanced",
})
break
}
setDefaultOptionFor(option.name)
}}
label={option.label}
key={`option-${option.name}`}
Expand Down Expand Up @@ -151,7 +183,9 @@ export function ActionListItem({
}))}
onSelect={(selected) => {
if (isSingleValueSelected(selected)) {
setPath(`${pathPrefix}.type`, selected.value)
setPath(`${pathPrefix}`, {
type: selected.value,
})
}
}}
/>
Expand All @@ -163,132 +197,3 @@ export function ActionListItem({
</div>
)
}

function ActionSelector({
item,
pathPrefix,
}: {
item: SchemaActionItem | null
pathPrefix: string
}) {
const { setPath, schemaType } = useRuleEngine()
const { t } = useTranslation()

const initialValues = actionPaths[schemaType].map((field) => ({
value: field,
label: (t(`resource_paths.${field}`) as string).replace(
"resource_paths.",
"",
),
}))

const name = `${pathPrefix}.selector`

return (
<OptionRow
label={
<Text variant="info" size="small">
Apply to
</Text>
}
>
<InputSelect
name={name}
isSearchable={false}
initialValues={initialValues}
value={
item?.selector == null
? undefined
: (initialValues.find((c) => c.value === item.selector) ?? {
value: item.selector,
label: item.selector,
})
}
onSelect={async (selection) => {
if (isSingleValueSelected(selection)) {
setPath(name, selection.value)
setPath(`${pathPrefix}.apply_on`, null)
}
}}
/>
</OptionRow>
)
}

function ActionGroups({
item,
pathPrefix,
}: {
item: SchemaActionItem | null
pathPrefix: string
}) {
const { setPath, schemaType } = useRuleEngine()
const availableGroups = useAvailableGroups()

useEffect(() => {
if (availableGroups.length === 0 && (item?.groups ?? []).length > 0) {
setPath(`${pathPrefix}.groups`, [])
}
}, [availableGroups])

if (availableGroups.length <= 0 && (item?.groups ?? []).length <= 0) {
return null
}

return (
<OptionRow
label={
<Text variant="info" size="small">
Groups
</Text>
}
>
<InputSelect
name={`${pathPrefix}.groups`}
isMulti
isClearable={false}
value={
item != null
? item.groups?.map((groups) => ({
label: availableGroups.includes(groups)
? groups
: `⚠️   ${groups}`,
value: groups,
}))
: undefined
}
initialValues={availableGroups.map((group) => ({
value: group,
label: group,
}))}
onSelect={(selected) => {
if (isMultiValueSelected(selected)) {
setPath(
`${pathPrefix}.groups`,
selected.map((s) => s.value),
)

if (schemaType === "order-rules" && selected.length > 0) {
setPath(`${pathPrefix}.selector`, "order.line_items")
}
}
}}
/>
</OptionRow>
)
}

const actionPaths = {
"order-rules": [
"order",
"order.line_items",
"order.line_items.line_item_options",
"order.line_items.sku",
"order.line_items.bundle",
"order.line_items.shipment",
"order.line_items.payment_method",
"order.line_items.adjustment",
"order.line_items.gift_card",
] as const,
"price-rules": ["price"] as const,
} satisfies Record<RuleEngineProps["schemaType"], string[]>
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export function ActionValue({
return (
<div className="w-36 flex items-center gap-0.5">
<Input
key={`${item.type}-${pathPrefix}.value.x`}
name={`${pathPrefix}.value.x`}
type="number"
suffix="X"
Expand All @@ -31,6 +32,7 @@ export function ActionValue({
}}
/>
<Input
key={`${item.type}-${pathPrefix}.value.y`}
name={`${pathPrefix}.value.y`}
type="number"
suffix="Y"
Expand All @@ -50,6 +52,7 @@ export function ActionValue({
return (
<div className="w-36 flex items-center gap-0.5">
<Input
key={`${item.type}-${pathPrefix}.value.x`}
name={`${pathPrefix}.value.x`}
type="number"
suffix="X"
Expand All @@ -62,6 +65,7 @@ export function ActionValue({
}}
/>
<Input
key={`${item.type}-${pathPrefix}.value.y`}
name={`${pathPrefix}.value.y`}
type="number"
suffix="Y"
Expand All @@ -82,6 +86,7 @@ export function ActionValue({
return (
<div className="w-36">
<Input
key={`${item.type}-${pathPrefix}.value`}
name={`${pathPrefix}.value`}
type="number"
defaultValue={item.value}
Expand All @@ -103,6 +108,7 @@ export function ActionValue({
return (
<div className="w-24">
<Input
key={`${item.type}-${pathPrefix}.value`}
name={`${pathPrefix}.value`}
type="number"
defaultValue={
Expand All @@ -124,6 +130,10 @@ export function ActionValue({
)
}

case "free_gift": {
return null
}

default: {
return expectNever(item)
}
Expand Down
Loading
Loading