diff --git a/apps/web/src/components/conditions/Conditions.tsx b/apps/web/src/components/conditions/Conditions.tsx index fb9e52c7a7c..f3635c2baec 100644 --- a/apps/web/src/components/conditions/Conditions.tsx +++ b/apps/web/src/components/conditions/Conditions.tsx @@ -1,56 +1,78 @@ -import { Grid, Group, ActionIcon, Center } from '@mantine/core'; +import { Grid, Group, ActionIcon, Center, useMantineTheme } from '@mantine/core'; import styled from '@emotion/styled'; -import { Controller, useFieldArray, useForm } from 'react-hook-form'; +import { useMemo } from 'react'; +import { Control, Controller, useFieldArray, useForm, useWatch } from 'react-hook-form'; import { FILTER_TO_LABEL, FilterPartTypeEnum } from '@novu/shared'; import { Button, colors, Dropdown, Input, Select, Sidebar, Text, Title, Tooltip } from '../../design-system'; import { ConditionPlus, DotsHorizontal, Duplicate, Trash, Condition, ErrorIcon } from '../../design-system/icons'; import { When } from '../utils/When'; -import { IConditions } from '../../pages/integrations/types'; +import { ConditionsContextEnum, ConditionsContextFields, IConditions } from './types'; +interface IConditionsForm { + conditions: IConditions[]; +} export function Conditions({ isOpened, conditions, onClose, setConditions, name, + context = ConditionsContextEnum.INTEGRATIONS, }: { isOpened: boolean; onClose: () => void; setConditions: (data: IConditions[]) => void; conditions?: IConditions[]; name: string; + context?: ConditionsContextEnum; }) { + const { colorScheme } = useMantineTheme(); + const { control, - setValue, getValues, trigger, - formState: { errors, isValid }, - } = useForm({ + formState: { errors, isValid, isDirty }, + } = useForm({ defaultValues: { conditions }, - shouldUseNativeValidation: false, mode: 'onChange', - reValidateMode: 'onChange', }); - const { fields, append, update, remove, insert } = useFieldArray({ + const { fields, append, remove, insert } = useFieldArray({ control, name: `conditions.0.children`, }); - const FilterPartTypeList = [{ value: FilterPartTypeEnum.TENANT, label: FILTER_TO_LABEL[FilterPartTypeEnum.TENANT] }]; + const { label, filterPartsList } = ConditionsContextFields[context]; - function handleOnChildOnChange(index: number) { - return (data) => { - const newField = Object.assign({}, fields[index], { on: data }); - update(index, newField); - }; + const FilterPartTypeList = useMemo(() => { + return filterPartsList.map((filterType) => { + return { + value: filterType, + label: FILTER_TO_LABEL[filterType], + }; + }); + }, [context]); + + function handleDuplicate(index: number) { + insert(index + 1, getValues(`conditions.0.children.${index}`)); } + function handleDelete(index: number) { + remove(index); + } + + const onApplyConditions = async () => { + await trigger('conditions'); + if (!errors.conditions) { + updateConditions(getValues('conditions')); + } + }; + function updateConditions(data) { - setConditions(data.conditions); + setConditions(data); onClose(); } @@ -61,38 +83,28 @@ export function Conditions({ isExpanded customHeader={
- - - Condition for {name} provider instance + <Condition color={colorScheme === 'dark' ? colors.white : colors.B30} /> + <Title ml={8} size={2} data-test-id="conditions-form-title"> + Conditions for {name} {label}
} customFooter={ - - - 0} - label={!isValid ? 'Some conditions are missing values' : 'Add at least one condition'} - > -
- -
-
-
+ +
+ +
+
} > @@ -110,13 +122,12 @@ export function Conditions({ render={({ field }) => { return ( ); }} /> - - { - return ( - - - - - - - - - - } - required - disabled={getValues(`conditions.0.children.${index}.operator`) === 'IS_DEFINED'} - error={!!fieldState.error} - placeholder="Value" - data-test-id="filter-value-input" - /> - ); - }} - /> - )} - + - { - insert(index + 1, getValues(`conditions.0.children.${index}`)); - }} - icon={} - > + handleDuplicate(index)} icon={}> Duplicate - { - remove(index); - }} - icon={} - > + handleDelete(index)} icon={}> Delete @@ -284,6 +202,92 @@ export function Conditions({ ); } +function EqualityForm({ control, index }: { control: Control; index: number }) { + const operator = useWatch({ + control, + name: `conditions.0.children.${index}.operator`, + }); + + return ( + <> + + { + return ( + + ); + }} + /> + + + + {operator !== 'IS_DEFINED' && ( + { + return ( + + + + + + + + } + error={!!fieldState.error} + placeholder="Value" + data-test-id="conditions-form-value-input" + /> + ); + }} + /> + )} + + + ); +} + const Wrapper = styled.div` .mantine-Select-wrapper:not(:hover) { .mantine-Select-input { @@ -297,24 +301,3 @@ const Wrapper = styled.div` } } `; - -const TooltipContainer = styled.div` - & .mantine-Tooltip-tooltip { - color: ${colors.error}; - padding: 16px; - font-size: 14px; - font-weight: 400; - border-radius: 8px; - background: ${({ theme }) => - `linear-gradient(0deg, rgba(229, 69, 69, 0.2) 0%, rgba(229, 69, 69, 0.2) 100%), ${ - theme.colorScheme === 'dark' ? '#23232b' : colors.white - } !important`}; - } - - & .mantine-Tooltip-arrow { - background: ${({ theme }) => - `linear-gradient(0deg, rgba(229, 69, 69, 0.2) 0%, rgba(229, 69, 69, 0.2) 100%), ${ - theme.colorScheme === 'dark' ? '#23232b' : colors.white - } !important`}; - } -`; diff --git a/apps/web/src/components/conditions/index.ts b/apps/web/src/components/conditions/index.ts new file mode 100644 index 00000000000..a4dfe570c58 --- /dev/null +++ b/apps/web/src/components/conditions/index.ts @@ -0,0 +1,2 @@ +export * from './Conditions'; +export * from './types'; diff --git a/apps/web/src/components/conditions/types.ts b/apps/web/src/components/conditions/types.ts new file mode 100644 index 00000000000..fc8556e2ed5 --- /dev/null +++ b/apps/web/src/components/conditions/types.ts @@ -0,0 +1,19 @@ +import { BuilderFieldType, BuilderGroupValues, FilterParts, FilterPartTypeEnum } from '@novu/shared'; + +export interface IConditions { + isNegated?: boolean; + type?: BuilderFieldType; + value?: BuilderGroupValues; + children?: FilterParts[]; +} + +export enum ConditionsContextEnum { + INTEGRATIONS = 'INTEGRATIONS', +} + +export const ConditionsContextFields = { + [ConditionsContextEnum.INTEGRATIONS]: { + label: 'provider instance', + filterPartsList: [FilterPartTypeEnum.TENANT], + }, +}; diff --git a/apps/web/src/design-system/tooltip/Tooltip.styles.ts b/apps/web/src/design-system/tooltip/Tooltip.styles.ts index aab5ac085da..ed0bd07b040 100644 --- a/apps/web/src/design-system/tooltip/Tooltip.styles.ts +++ b/apps/web/src/design-system/tooltip/Tooltip.styles.ts @@ -1,20 +1,27 @@ import { createStyles, MantineTheme } from '@mantine/core'; import { colors, shadows } from '../config'; +import { getGradient } from '../config/helper'; -export default createStyles((theme: MantineTheme) => { +export default createStyles((theme: MantineTheme, { error }: { error: boolean }) => { const dark = theme.colorScheme === 'dark'; + const opacityErrorColor = theme.fn.rgba(colors.error, 0.2); + const errorGradient = getGradient(opacityErrorColor); + const backgroundErrorColor = dark ? colors.B17 : colors.white; + const backgroundColor = dark ? colors.B20 : colors.white; + const background = error ? `${errorGradient}, ${backgroundErrorColor}` : backgroundColor; + const color = error ? colors.error : colors.B60; return { tooltip: { - backgroundColor: dark ? colors.B20 : theme.white, - color: colors.B60, + background, + color, boxShadow: dark ? shadows.dark : shadows.medium, padding: '12px 15px', fontSize: '14px', fontWeight: 400, }, arrow: { - backgroundColor: dark ? colors.B20 : theme.white, + background, }, }; }); diff --git a/apps/web/src/design-system/tooltip/Tooltip.tsx b/apps/web/src/design-system/tooltip/Tooltip.tsx index bd268add1d6..53d854a1c26 100644 --- a/apps/web/src/design-system/tooltip/Tooltip.tsx +++ b/apps/web/src/design-system/tooltip/Tooltip.tsx @@ -2,30 +2,29 @@ import { Tooltip as MantineTooltip, TooltipProps } from '@mantine/core'; import useStyles from './Tooltip.styles'; +interface ITooltipProps + extends Pick< + TooltipProps, + | 'multiline' + | 'width' + | 'label' + | 'opened' + | 'position' + | 'disabled' + | 'children' + | 'sx' + | 'withinPortal' + | 'offset' + | 'classNames' + > { + error?: boolean; +} /** * Tooltip component * */ -export function Tooltip({ - children, - label, - opened = undefined, - ...props -}: Pick< - TooltipProps, - | 'multiline' - | 'width' - | 'label' - | 'opened' - | 'position' - | 'disabled' - | 'children' - | 'sx' - | 'withinPortal' - | 'offset' - | 'classNames' ->) { - const { classes } = useStyles(); +export function Tooltip({ children, label, opened = undefined, error = false, ...props }: ITooltipProps) { + const { classes } = useStyles({ error }); return ( ({ + sx={{ fontWeight: size === 1 ? 800 : 700, - color: theme.colorScheme === 'dark' ? colors.white : colors.B40, - })} + }} order={size} - {...rest} + color={textColor} + {...props} > {children} diff --git a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx index a11e175190e..830791b39c2 100644 --- a/apps/web/src/pages/integrations/components/ConditionIconButton.tsx +++ b/apps/web/src/pages/integrations/components/ConditionIconButton.tsx @@ -1,10 +1,9 @@ -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import styled from '@emotion/styled'; -import { Group, ActionIcon, Title } from '@mantine/core'; +import { Group, ActionIcon, Center } from '@mantine/core'; import { When } from '../../../components/utils/When'; -import { colors, Tooltip, Text, Modal, Button } from '../../../design-system'; +import { colors, Tooltip, Text, Modal, Button, Title } from '../../../design-system'; import { Condition, ConditionPlus, Warning } from '../../../design-system/icons'; -import { IConditions } from '../types'; const IconButton = styled(Group)` text-align: center; @@ -29,29 +28,22 @@ const RemovesPrimary = () => { }; export const ConditionIconButton = ({ - conditions, + conditions = 0, primary = false, onClick, }: { - conditions?: IConditions[]; + conditions?: number; primary?: boolean; onClick: () => void; }) => { const [modalOpen, setModalOpen] = useState(false); - const numOfConditions: number = useMemo(() => { - if (conditions && conditions[0] && conditions[0].children) { - return conditions[0].children.length; - } - - return 0; - }, [conditions]); return ( <> - {numOfConditions > 0 ? 'Edit' : 'Add'} Conditions + {conditions > 0 ? 'Edit' : 'Add'} Conditions @@ -71,14 +63,14 @@ export const ConditionIconButton = ({ variant="transparent" > - + - 0}> - + 0}> +
-
{numOfConditions}
- +
{conditions}
+
@@ -88,7 +80,9 @@ export const ConditionIconButton = ({ title={ - Primary will be removed + + Primary will be removed + } size="lg" diff --git a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx index 0f0d2daa6a0..e81a4ab0640 100644 --- a/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx +++ b/apps/web/src/pages/integrations/components/PrimaryIconButton.tsx @@ -1,8 +1,8 @@ import styled from '@emotion/styled'; -import { Group, ActionIcon, Text, Title } from '@mantine/core'; +import { Group, ActionIcon, Text } from '@mantine/core'; import { useState } from 'react'; import { When } from '../../../components/utils/When'; -import { Tooltip, Button, colors, Modal } from '../../../design-system'; +import { Tooltip, Button, colors, Modal, Title } from '../../../design-system'; import { RemoveCondition, StarEmpty, Warning } from '../../../design-system/icons'; const IconButton = styled(Group)` @@ -28,11 +28,11 @@ const RemovesCondition = () => { }; export const PrimaryIconButton = ({ - conditions, + conditions = 0, primary = false, onClick, }: { - conditions?: any[]; + conditions?: number; primary?: boolean; onClick: () => void; }) => { @@ -48,7 +48,7 @@ export const PrimaryIconButton = ({ label={ <> Mark as Primary - 0}> + 0}> @@ -57,7 +57,7 @@ export const PrimaryIconButton = ({ > { - if (conditions && conditions.length > 0) { + if (conditions > 0) { setModalOpen(true); return; diff --git a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx index 1b223899d60..bf50ae76243 100644 --- a/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx +++ b/apps/web/src/pages/integrations/components/UpdateIntegrationSidebarHeader.tsx @@ -1,6 +1,6 @@ import { ReactNode, useMemo, useState } from 'react'; import { Group, useMantineTheme } from '@mantine/core'; -import { Controller, useFormContext } from 'react-hook-form'; +import { Controller, useFormContext, useWatch } from 'react-hook-form'; import { CHANNELS_WITH_PRIMARY } from '@novu/shared'; import { Button, colors, Dropdown, Modal, NameInput, Text, Title } from '../../../design-system'; @@ -36,6 +36,15 @@ export const UpdateIntegrationSidebarHeader = ({ const canMarkAsPrimary = provider && !provider.primary && CHANNELS_WITH_PRIMARY.includes(provider.channel); const { openModal, SelectPrimaryIntegrationModal } = useSelectPrimaryIntegrationModal(); + const watchedConditions = useWatch({ control, name: 'conditions' }); + const numOfConditions: number = useMemo(() => { + if (watchedConditions && watchedConditions[0] && watchedConditions[0].children) { + return watchedConditions[0].children.length; + } + + return 0; + }, [watchedConditions]); + const shouldSetNewPrimary = useMemo(() => { if (!provider) return false; @@ -117,9 +126,9 @@ export const UpdateIntegrationSidebarHeader = ({ onClick={() => { makePrimaryIntegration({ id: provider.integrationId }); }} - conditions={provider.conditions} + conditions={numOfConditions} /> - +
providers.find((el) => el.channel === channel && el.id === providerId), @@ -145,17 +146,20 @@ export function CreateProviderInstanceSidebar({ if (!provider) { return null; } + const updateConditions = (conditions: IConditions[]) => { + setValue('conditions', conditions, { shouldDirty: true }); + }; + + if (conditionsFormOpened) { + const [conditions, name] = getValues(['conditions', 'name']); - if (openConditions) { return ( { - setValue('conditions', data, { shouldDirty: true }); - }} - onClose={() => setOpenConditions(false)} + conditions={conditions} + name={name} + isOpened={conditionsFormOpened} + setConditions={updateConditions} + onClose={closeConditionsForm} /> ); } @@ -192,7 +196,7 @@ export function CreateProviderInstanceSidebar({ }} /> - setOpenConditions(true)} /> + } @@ -281,7 +285,7 @@ export function CreateProviderInstanceSidebar({