diff --git a/services/backend-api/client/src/features/feedConnections/components/FiltersForm/LogicalExpressionForm.tsx b/services/backend-api/client/src/features/feedConnections/components/FiltersForm/LogicalExpressionForm.tsx index c61c14b46..2db1dccee 100644 --- a/services/backend-api/client/src/features/feedConnections/components/FiltersForm/LogicalExpressionForm.tsx +++ b/services/backend-api/client/src/features/feedConnections/components/FiltersForm/LogicalExpressionForm.tsx @@ -5,6 +5,8 @@ import { Button, CloseButton, Flex, + FormControl, + FormErrorMessage, Menu, MenuButton, MenuItem, @@ -12,8 +14,9 @@ import { Stack, Text, } from "@chakra-ui/react"; -import { useFieldArray, useFormContext, useWatch } from "react-hook-form"; +import { FieldError, useFieldArray, useFormContext, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import { useEffect } from "react"; import { FilterExpression, FilterExpressionType, @@ -26,6 +29,7 @@ import { } from "../../types"; import { AnyAllSelector } from "./AnyAllSelector"; import { Condition } from "./Condition"; +import { getNestedField } from "../../../../utils/getNestedField"; interface Props { onDeleted: () => void; @@ -34,15 +38,34 @@ interface Props { } export const LogicalExpressionForm = ({ onDeleted, prefix = "", containerProps }: Props) => { - const { control, setValue } = useFormContext(); + const { + control, + setValue, + setError, + clearErrors, + formState: { errors }, + } = useFormContext(); + const childrenName = `${prefix}children`; + const childrenError = getNestedField(errors, childrenName); const { fields, append, remove, insert } = useFieldArray({ control, - name: `${prefix}children`, + name: childrenName, }); const operator: LogicalExpressionOperator = useWatch({ control, name: `${prefix}op`, }); + // console.log("🚀 ~ LogicalExpressionForm ~ errors:", errors); + // console.log("🚀 ~ useEffect ~ childrenName:", childrenName); + useEffect(() => { + if (fields.length === 0) { + setError(childrenName, { + type: "required", + }); + } else if (childrenError?.type === "required") { + clearErrors(childrenName); + } + }, [fields.length, childrenError?.type]); const { t } = useTranslation(); @@ -111,15 +134,6 @@ export const LogicalExpressionForm = ({ onDeleted, prefix = "", containerProps } append(toInsert); }; - const numberOfRelational = - ( - fields as Array< - FilterExpression & { - id: string; - } - > - )?.filter((child) => child?.type === FilterExpressionType.Relational).length || 0; - return ( - - {(fields as Array)?.map((child, childIndex) => { - if (child?.type === FilterExpressionType.Logical) { - return ( - onChildDeleted(childIndex)} - prefix={`${prefix}children.${childIndex}.`} - /> - ); - } - - if (child?.type === FilterExpressionType.Relational) { - return ( - onChildDeleted(childIndex)} - prefix={`${prefix}children.${childIndex}.`} - deletable={numberOfRelational > 1} - /> - ); - } - - return null; - })} - + {!!fields.length && ( + + {(fields as Array)?.map((child, childIndex) => { + if (child?.type === FilterExpressionType.Logical) { + return ( + onChildDeleted(childIndex)} + prefix={`${prefix}children.${childIndex}.`} + /> + ); + } + + if (child?.type === FilterExpressionType.Relational) { + return ( + onChildDeleted(childIndex)} + prefix={`${prefix}children.${childIndex}.`} + deletable + /> + ); + } + + return null; + })} + + )} + + At least one condition is required. + - } variant="ghost"> + }> {t("features.feedConnections.components.filtersForm.addButtonText")} diff --git a/services/backend-api/client/src/features/feedConnections/components/FiltersForm/index.tsx b/services/backend-api/client/src/features/feedConnections/components/FiltersForm/index.tsx index 90cca3193..0697d3d89 100644 --- a/services/backend-api/client/src/features/feedConnections/components/FiltersForm/index.tsx +++ b/services/backend-api/client/src/features/feedConnections/components/FiltersForm/index.tsx @@ -49,6 +49,7 @@ export const FiltersForm = ({ resetField, reset, } = formMethods; + // @ts-ignore cyclical references in typescript types const watchedExpression = useWatch({ control, @@ -67,10 +68,12 @@ export const FiltersForm = ({ }; const onSaveExpression = async ({ expression: finalExpression }: FormData) => { - await onSave(finalExpression); - reset({ - expression: finalExpression, - }); + try { + await onSave(finalExpression); + reset({ + expression: finalExpression, + }); + } catch (err) {} }; const onSubmit = async (e: React.FormEvent) => { diff --git a/services/backend-api/client/src/mocks/handlers.ts b/services/backend-api/client/src/mocks/handlers.ts index 3b4f7f8f5..29064ca7b 100644 --- a/services/backend-api/client/src/mocks/handlers.ts +++ b/services/backend-api/client/src/mocks/handlers.ts @@ -873,7 +873,7 @@ const handlers = [ rest.patch("/api/v1/user-feeds/:feedId/connections/discord-channels/:id", (req, res, ctx) => { return res( ctx.delay(500), - ctx.status(400), + ctx.status(200), ctx.json({ result: mockFeedChannelConnections[0], })