@griddo/ax 1.75.258 → 10.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/package.json +2 -2
  2. package/src/GlobalStore.tsx +3 -0
  3. package/src/__tests__/components/ConfigPanel/GlobalPageForm/GlobalPageForm.test.tsx +10 -1
  4. package/src/__tests__/components/Fields/IntegrationsField/IntegrationsField.test.tsx +391 -0
  5. package/src/__tests__/modules/Settings/Integrations/Integrations.test.tsx +167 -0
  6. package/src/api/index.tsx +3 -1
  7. package/src/api/integrations.tsx +153 -0
  8. package/src/api/sites.tsx +4 -2
  9. package/src/components/ActionMenu/index.tsx +3 -1
  10. package/src/components/ActionMenu/style.tsx +1 -0
  11. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +4 -1
  12. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +3 -0
  13. package/src/components/ConfigPanel/GlobalPageForm/index.tsx +11 -2
  14. package/src/components/FieldContainer/index.tsx +2 -1
  15. package/src/components/Fields/IntegrationsField/IntegrationItem/CustomPanel/index.tsx +101 -0
  16. package/src/components/Fields/IntegrationsField/IntegrationItem/CustomPanel/style.tsx +23 -0
  17. package/src/components/Fields/IntegrationsField/IntegrationItem/VariablesPanel/helpers.ts +31 -0
  18. package/src/components/Fields/IntegrationsField/IntegrationItem/VariablesPanel/index.tsx +120 -0
  19. package/src/components/Fields/IntegrationsField/IntegrationItem/VariablesPanel/style.tsx +23 -0
  20. package/src/components/Fields/IntegrationsField/IntegrationItem/atoms.tsx +27 -0
  21. package/src/components/Fields/IntegrationsField/IntegrationItem/index.tsx +132 -0
  22. package/src/components/Fields/IntegrationsField/IntegrationItem/style.tsx +63 -0
  23. package/src/components/Fields/IntegrationsField/PasteIntegrationButton/index.tsx +30 -0
  24. package/src/components/Fields/IntegrationsField/SideModal/SideModalOption/index.tsx +52 -0
  25. package/src/components/Fields/IntegrationsField/SideModal/SideModalOption/style.tsx +54 -0
  26. package/src/components/Fields/IntegrationsField/SideModal/index.tsx +57 -0
  27. package/src/components/Fields/IntegrationsField/SideModal/style.tsx +50 -0
  28. package/src/components/Fields/IntegrationsField/index.tsx +145 -0
  29. package/src/components/Fields/IntegrationsField/style.tsx +29 -0
  30. package/src/components/Fields/TextArea/index.tsx +1 -1
  31. package/src/components/Fields/ToggleField/index.tsx +5 -3
  32. package/src/components/Fields/UrlField/index.tsx +8 -8
  33. package/src/components/Fields/index.tsx +2 -0
  34. package/src/components/Icon/components/Deactivate.js +14 -0
  35. package/src/components/Icon/components/Lock.js +15 -0
  36. package/src/components/Icon/svgs/Deactivate.svg +8 -0
  37. package/src/components/Icon/svgs/Lock.svg +6 -0
  38. package/src/components/PageFinder/SelectionListItem/index.tsx +46 -0
  39. package/src/components/PageFinder/SelectionListItem/style.tsx +46 -0
  40. package/src/components/{Fields/UrlField/PageFinder → PageFinder}/index.tsx +99 -21
  41. package/src/components/{Fields/UrlField/PageFinder → PageFinder}/style.tsx +14 -1
  42. package/src/components/TableFilters/CheckGroupFilter/index.tsx +83 -0
  43. package/src/components/TableFilters/CheckGroupFilter/style.tsx +40 -0
  44. package/src/components/TableFilters/StateFilter/index.tsx +66 -0
  45. package/src/components/TableFilters/StateFilter/style.tsx +30 -0
  46. package/src/components/TableFilters/index.tsx +4 -0
  47. package/src/components/index.tsx +9 -1
  48. package/src/containers/Integrations/actions.tsx +190 -0
  49. package/src/containers/Integrations/constants.tsx +14 -0
  50. package/src/containers/Integrations/index.tsx +4 -0
  51. package/src/containers/Integrations/interfaces.tsx +24 -0
  52. package/src/containers/Integrations/reducer.tsx +31 -0
  53. package/src/containers/PageEditor/actions.tsx +11 -1
  54. package/src/containers/PageEditor/utils.tsx +30 -2
  55. package/src/hooks/content.tsx +46 -2
  56. package/src/hooks/index.tsx +2 -1
  57. package/src/hooks/modals.tsx +4 -2
  58. package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +13 -12
  59. package/src/modules/Content/index.tsx +5 -0
  60. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/ConfigPanel/IntegrationsField/index.tsx +47 -0
  61. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/ConfigPanel/IntegrationsField/style.tsx +7 -0
  62. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/ConfigPanel/{Field → NavigationField}/index.tsx +2 -2
  63. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/ConfigPanel/index.tsx +7 -7
  64. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/index.tsx +1 -1
  65. package/src/modules/Settings/Integrations/BulkHeader/TableHeader/index.tsx +81 -0
  66. package/src/modules/Settings/Integrations/BulkHeader/TableHeader/style.tsx +35 -0
  67. package/src/modules/Settings/Integrations/BulkHeader/index.tsx +69 -0
  68. package/src/modules/Settings/Integrations/IntegrationForm/VariableItem/index.tsx +95 -0
  69. package/src/modules/Settings/Integrations/IntegrationForm/VariableItem/style.tsx +62 -0
  70. package/src/modules/Settings/Integrations/IntegrationForm/VariablePanel/index.tsx +138 -0
  71. package/src/modules/Settings/Integrations/IntegrationForm/VariablePanel/style.tsx +28 -0
  72. package/src/modules/Settings/Integrations/IntegrationForm/index.tsx +319 -0
  73. package/src/modules/Settings/Integrations/IntegrationForm/style.tsx +77 -0
  74. package/src/modules/Settings/Integrations/IntegrationItem/CopyModal/index.tsx +44 -0
  75. package/src/modules/Settings/Integrations/IntegrationItem/CopyModal/style.tsx +13 -0
  76. package/src/modules/Settings/Integrations/IntegrationItem/index.tsx +197 -0
  77. package/src/modules/Settings/Integrations/IntegrationItem/style.tsx +81 -0
  78. package/src/modules/Settings/Integrations/atoms.tsx +49 -0
  79. package/src/modules/Settings/Integrations/hooks.tsx +72 -0
  80. package/src/modules/Settings/Integrations/index.tsx +299 -0
  81. package/src/modules/Settings/Integrations/style.tsx +48 -0
  82. package/src/modules/Settings/Integrations/utils.tsx +39 -0
  83. package/src/routes/site.tsx +19 -0
  84. package/src/schemas/pages/Page.tsx +5 -0
  85. package/src/types/index.tsx +35 -0
  86. package/tsconfig.paths.json +2 -0
  87. package/src/components/Fields/UrlField/PageFinder/SelectionListItem/index.tsx +0 -34
  88. package/src/components/Fields/UrlField/PageFinder/SelectionListItem/style.tsx +0 -35
  89. /package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/ConfigPanel/{Field → NavigationField}/style.tsx +0 -0
@@ -0,0 +1,132 @@
1
+ import * as React from "react";
2
+
3
+ import { Icon, ActionMenu, Tooltip, Button, Toast } from "@ax/components";
4
+ import { useModal, useToast } from "@ax/hooks";
5
+ import { IIntegration, IIntegrationVariable, ILanguage } from "@ax/types";
6
+
7
+ import VariablesPanel from "./VariablesPanel";
8
+ import CustomPanel from "./CustomPanel";
9
+ import { DeleteModal } from "./atoms";
10
+ import * as S from "./style";
11
+
12
+ const IntegrationItem = (props: IProps): JSX.Element => {
13
+ const { integration, disabled, removeItem, editIntegration, languages, index, copyIntegration } = props;
14
+ const { isOpen: isOpenDelete, toggleModal: toggleModalDelete } = useModal();
15
+ const { isVisible: isVisibleCopy, toggleToast: toggleToastCopy, setIsVisible: setIsVisibleCopy } = useToast();
16
+
17
+ const presenceType = integration.contentPresence?.presenceType;
18
+ const isCustom = presenceType === "page-custom";
19
+ const isEditable = isCustom || !!integration.variables?.length;
20
+ const canDelete = presenceType !== "all" && presenceType !== "page-specific";
21
+
22
+ const openFirstTime = isCustom && !integration.name?.length;
23
+
24
+ const { isOpen, toggleModal } = useModal(openFirstTime);
25
+
26
+ const options = canDelete
27
+ ? [
28
+ {
29
+ label: "Remove",
30
+ icon: "delete",
31
+ action: toggleModalDelete,
32
+ },
33
+ ]
34
+ : [];
35
+
36
+ const handleCopyIntegration = () => {
37
+ copyIntegration(integration);
38
+ toggleToastCopy();
39
+ };
40
+
41
+ if (isCustom) {
42
+ const copyOption = {
43
+ label: "Copy",
44
+ icon: "copy",
45
+ action: handleCopyIntegration,
46
+ };
47
+ options.unshift(copyOption);
48
+ }
49
+
50
+ const handleSetVariables = (variables: IIntegrationVariable[]) => {
51
+ const newIntegration = { ...integration, variables };
52
+ editIntegration(newIntegration, index);
53
+ };
54
+
55
+ const handleSetCustom = (modifiedIntegration: Partial<IIntegration>) => {
56
+ const newIntegration = { ...integration, ...modifiedIntegration };
57
+ editIntegration(newIntegration, index);
58
+ };
59
+
60
+ const integrationName = isCustom ? "Custom Code" : integration.name;
61
+
62
+ const mainDeleteModalAction = {
63
+ title: "Remove integration",
64
+ onClick: removeItem,
65
+ };
66
+ const secondaryDeleteModalAction = { title: "Cancel", onClick: toggleModalDelete };
67
+
68
+ return (
69
+ <>
70
+ <S.Component disabled={disabled} data-testid="integration-item">
71
+ <S.Text>
72
+ <S.Label disabled={disabled}>{integrationName}</S.Label>
73
+ {isCustom && <S.Subtitle disabled={disabled}>{integration.name}</S.Subtitle>}
74
+ </S.Text>
75
+ <S.ComponentOptions>
76
+ {isEditable ? (
77
+ <Tooltip content="Define values">
78
+ <Button type="button" buttonStyle="text" icon="edit" onClick={toggleModal}>
79
+ Edit
80
+ </Button>
81
+ </Tooltip>
82
+ ) : (
83
+ <Tooltip content="Configure in site settings">
84
+ <S.IconWrapper>
85
+ <Icon name="lock" size="14px" />
86
+ </S.IconWrapper>
87
+ </Tooltip>
88
+ )}
89
+ <S.HiddenButtonsWrapper>
90
+ <ActionMenu options={options} icon="more" />
91
+ </S.HiddenButtonsWrapper>
92
+ </S.ComponentOptions>
93
+ </S.Component>
94
+ {isCustom ? (
95
+ <CustomPanel
96
+ isOpen={isOpen}
97
+ toggleModal={toggleModal}
98
+ setIntegration={handleSetCustom}
99
+ integration={integration}
100
+ />
101
+ ) : (
102
+ <VariablesPanel
103
+ isOpen={isOpen}
104
+ toggleModal={toggleModal}
105
+ setIntegrationVariables={handleSetVariables}
106
+ integration={integration}
107
+ languages={languages}
108
+ />
109
+ )}
110
+ <DeleteModal
111
+ isOpen={isOpenDelete}
112
+ toggleModal={toggleModalDelete}
113
+ secondaryModalAction={secondaryDeleteModalAction}
114
+ mainModalAction={mainDeleteModalAction}
115
+ integrationName={integration.name}
116
+ />
117
+ {isVisibleCopy && <Toast message="1 Custom Code copied to clipboard" setIsVisible={setIsVisibleCopy} />}
118
+ </>
119
+ );
120
+ };
121
+
122
+ interface IProps {
123
+ integration: Partial<IIntegration>;
124
+ disabled: boolean;
125
+ removeItem: () => void;
126
+ editIntegration: (integration: Partial<IIntegration>, index: number) => void;
127
+ languages: ILanguage[];
128
+ index: number;
129
+ copyIntegration: (integration: Partial<IIntegration>) => void;
130
+ }
131
+
132
+ export default IntegrationItem;
@@ -0,0 +1,63 @@
1
+ import styled from "styled-components";
2
+
3
+ const HiddenButtonsWrapper = styled.span`
4
+ margin-left: auto;
5
+ display: flex;
6
+ opacity: 0;
7
+ `;
8
+
9
+ const Component = styled.span<{ disabled?: boolean }>`
10
+ color: ${(p) => p.theme.color.textHighEmphasis};
11
+ position: relative;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: space-between;
15
+ width: 100%;
16
+ background: ${(p) => p.theme.color.uiBackground02};
17
+ border: 1px solid transparent;
18
+ margin-bottom: ${(p) => p.theme.spacing.xs};
19
+ padding: ${(p) => p.theme.spacing.xs};
20
+ border-radius: ${(p) => p.theme.radii.s};
21
+ box-shadow: ${(p) => p.theme.shadow.shadowS};
22
+ ${(p) => p.theme.textStyle.fieldLabel};
23
+ text-align: left;
24
+
25
+ &:hover ${HiddenButtonsWrapper} {
26
+ opacity: 1;
27
+ }
28
+ `;
29
+
30
+ const Text = styled.div`
31
+ display: flex;
32
+ flex-direction: column;
33
+ `;
34
+
35
+ const Label = styled.span<{ disabled?: boolean }>`
36
+ margin: ${(p) => p.theme.spacing.xxs} 0;
37
+ color: ${(p) => (p.disabled ? p.theme.color.interactiveDisabled : p.theme.color.textHighEmphasis)};
38
+ `;
39
+
40
+ const Subtitle = styled.span<{ disabled?: boolean }>`
41
+ ${(p) => p.theme.textStyle.uiXS};
42
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
43
+ color: ${(p) => (p.disabled ? p.theme.color.interactiveDisabled : p.theme.color.textMediumEmphasis)};
44
+ `;
45
+
46
+ const IconWrapper = styled.div`
47
+ height: calc(${(p) => p.theme.spacing.s} * 2);
48
+ width: calc(${(p) => p.theme.spacing.s} * 2);
49
+ display: flex;
50
+ justify-content: center;
51
+ align-items: center;
52
+ margin-right: ${(p) => p.theme.spacing.xs};
53
+ `;
54
+
55
+ const ComponentOptions = styled.div`
56
+ display: flex;
57
+ `;
58
+
59
+ const ModalContent = styled.div`
60
+ padding: ${(p) => p.theme.spacing.m};
61
+ `;
62
+
63
+ export { Component, Label, IconWrapper, HiddenButtonsWrapper, ComponentOptions, Subtitle, Text, ModalContent };
@@ -0,0 +1,30 @@
1
+ import React, { memo } from "react";
2
+
3
+ import { useToast } from "@ax/hooks";
4
+ import { Tooltip, IconAction, Toast } from "@ax/components";
5
+
6
+ const PasteModuleButton = (props: IProps): JSX.Element => {
7
+ const { pasteIntegration } = props;
8
+
9
+ const { isVisible, toggleToast, setIsVisible } = useToast();
10
+
11
+ const handlePasteModule = async () => {
12
+ pasteIntegration();
13
+ toggleToast();
14
+ };
15
+
16
+ return (
17
+ <>
18
+ <Tooltip content="Paste from clipboard">
19
+ <IconAction icon="paste" onClick={handlePasteModule} />
20
+ </Tooltip>
21
+ {isVisible && <Toast message="1 Custom Code pasted from clipboard" setIsVisible={setIsVisible} />}
22
+ </>
23
+ );
24
+ };
25
+
26
+ interface IProps {
27
+ pasteIntegration: () => void;
28
+ }
29
+
30
+ export default memo(PasteModuleButton);
@@ -0,0 +1,52 @@
1
+ import React, { memo } from "react";
2
+
3
+ import Icon from "@ax/components/Icon";
4
+ import { IIntegration } from "@ax/types";
5
+
6
+ import * as S from "./style";
7
+
8
+ const SideModalOption = (props: IProps) => {
9
+ const { option, handleClick, toggleModal, custom } = props;
10
+
11
+ const customOption = {
12
+ name: "",
13
+ contentHead: "",
14
+ contentBody: "",
15
+ contentBodyPosition: null,
16
+ contentPresence: { presenceType: "page-custom", relatedPages: null },
17
+ active: true,
18
+ };
19
+
20
+ const setOption = () => {
21
+ handleClick(option || customOption);
22
+ toggleModal();
23
+ };
24
+
25
+ const optionProps = {
26
+ name: custom ? "Custom code" : option?.name,
27
+ description: custom ? "Add custom code to the head or body on this page" : option?.description,
28
+ };
29
+
30
+ return (
31
+ <S.Item onClick={setOption} data-testid="integration-side-modal-option">
32
+ {custom && (
33
+ <S.CustomSticker>
34
+ <S.Icon>
35
+ <Icon name="add" size="18px" />
36
+ </S.Icon>
37
+ </S.CustomSticker>
38
+ )}
39
+ <S.Title>{optionProps.name}</S.Title>
40
+ <S.Description>{optionProps.description}</S.Description>
41
+ </S.Item>
42
+ );
43
+ };
44
+
45
+ interface IProps {
46
+ option?: Partial<IIntegration>;
47
+ handleClick: (option: Partial<IIntegration>) => void;
48
+ toggleModal: () => void;
49
+ custom?: boolean;
50
+ }
51
+
52
+ export default memo(SideModalOption);
@@ -0,0 +1,54 @@
1
+ import styled from "styled-components";
2
+
3
+ const Item = styled.li`
4
+ position: relative;
5
+ overflow: hidden;
6
+ display: flex;
7
+ flex-direction: column;
8
+ cursor: pointer;
9
+ padding: ${(p) => p.theme.spacing.s};
10
+ padding-bottom: ${(p) => p.theme.spacing.s};
11
+ margin-bottom: ${(p) => p.theme.spacing.s};
12
+ box-shadow: ${(p) => p.theme.shadow.shadowS};
13
+ border-radius: ${(p) => p.theme.radii.s};
14
+ background-color: ${(p) => p.theme.color.interactiveBackground};
15
+ &:hover {
16
+ background: ${(p) => p.theme.color.overlayHoverPrimary};
17
+ }
18
+ &:focus {
19
+ background-color: ${(p) => p.theme.color.overlayFocusPrimary};
20
+ }
21
+ `;
22
+
23
+ const Title = styled.span`
24
+ ${(p) => p.theme.textStyle.uiS};
25
+ color: ${(p) => p.theme.color.textHighEmphasis};
26
+ `;
27
+
28
+ const Description = styled.span`
29
+ ${(p) => p.theme.textStyle.uiXS};
30
+ color: ${(p) => p.theme.color.textMediumEmphasis};
31
+ `;
32
+
33
+ const CustomSticker = styled.div`
34
+ position: absolute;
35
+ width: 80px;
36
+ height: 40px;
37
+ left: -40px;
38
+ top: -18px;
39
+ background: ${(p) => p.theme.color.interactive01};
40
+ transform: rotate(-45deg);
41
+ `;
42
+
43
+ const Icon = styled.div`
44
+ position: absolute;
45
+ right: 50%;
46
+ bottom: 2px;
47
+ margin-right: -2px;
48
+ transform: rotate(45deg) translate(50%);
49
+ & svg path {
50
+ fill: ${(p) => p.theme.color.iconHighEmphasisInverse};
51
+ }
52
+ `;
53
+
54
+ export { Item, Title, Description, CustomSticker, Icon };
@@ -0,0 +1,57 @@
1
+ import React, { useRef } from "react";
2
+ import { createPortal } from "react-dom";
3
+
4
+ import { useHandleClickOutside } from "@ax/hooks";
5
+ import { IconAction } from "@ax/components";
6
+ import { IIntegration } from "@ax/types";
7
+
8
+ import SideModalOption from "./SideModalOption";
9
+ import * as S from "./style";
10
+
11
+ const SideModal = (props: ISideModalProps): JSX.Element | null => {
12
+ const { integrations, isOpen, toggleModal, handleClick } = props;
13
+
14
+ const node = useRef<HTMLDivElement>(null);
15
+ const handleClickOutside = (e: React.MouseEvent) => {
16
+ if (node.current?.contains(e.target as Node)) {
17
+ return;
18
+ }
19
+ toggleModal();
20
+ };
21
+
22
+ useHandleClickOutside(isOpen, handleClickOutside);
23
+
24
+ return isOpen
25
+ ? createPortal(
26
+ <S.Wrapper ref={node}>
27
+ <S.Header>
28
+ <S.Title>Integrations</S.Title>
29
+ <S.ButtonWrapper>
30
+ <IconAction icon="close" onClick={toggleModal} />
31
+ </S.ButtonWrapper>
32
+ </S.Header>
33
+ <S.Content>
34
+ {integrations?.map((option, i) => (
35
+ <SideModalOption
36
+ key={`${option.id}${i}`}
37
+ option={option}
38
+ handleClick={handleClick}
39
+ toggleModal={toggleModal}
40
+ />
41
+ ))}
42
+ <SideModalOption handleClick={handleClick} toggleModal={toggleModal} custom />
43
+ </S.Content>
44
+ </S.Wrapper>,
45
+ document.body
46
+ )
47
+ : null;
48
+ };
49
+
50
+ interface ISideModalProps {
51
+ isOpen: boolean;
52
+ integrations: IIntegration[] | undefined;
53
+ toggleModal: () => void;
54
+ handleClick: (integrationId: Partial<IIntegration>) => void;
55
+ }
56
+
57
+ export default SideModal;
@@ -0,0 +1,50 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div`
4
+ position: fixed;
5
+ right: 0;
6
+ top: 0;
7
+ z-index: 1000;
8
+
9
+ height: 100vh;
10
+ background: ${(p) => p.theme.colors.uiBackground01};
11
+ box-shadow: ${(p) => p.theme.shadow.rightPanel};
12
+ padding-bottom: calc(${(p) => p.theme.spacing.m} * 2);
13
+ `;
14
+
15
+ const Header = styled.div`
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: space-between;
19
+ height: ${(p) => p.theme.spacing.xl};
20
+ width: 100%;
21
+ padding: 0 ${(p) => p.theme.spacing.m};
22
+ background-color: ${(p) => p.theme.colors.uiBackground02};
23
+ border-bottom: 1px solid ${(p) => p.theme.colors.uiLine};
24
+ h6 {
25
+ ${(p) => p.theme.textStyle.headingM}
26
+ color: ${(p) => p.theme.colors.textHighEmphasis};
27
+ text-transform: capitalize;
28
+ }
29
+ `;
30
+
31
+ const Title = styled.h6``;
32
+
33
+ const Content = styled.div`
34
+ list-style: none;
35
+ padding: ${(p) => p.theme.spacing.m};
36
+ height: ${(p) => `calc(100vh - (${p.theme.spacing.xl} * 2))`};
37
+ width: ${(p) => `calc(${p.theme.spacing.xl} * 3)`};
38
+ overflow: auto;
39
+ border-right: 1px solid ${(p) => p.theme.colors.uiLine};
40
+ &:last-child {
41
+ border-right: 0;
42
+ width: ${(p) => `calc(${p.theme.spacing.xl} * 4)`};
43
+ }
44
+ `;
45
+
46
+ const ButtonWrapper = styled.div`
47
+ margin: 0 0 0 auto;
48
+ `;
49
+
50
+ export { Wrapper, Content, Header, Title, ButtonWrapper };
@@ -0,0 +1,145 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { connect } from "react-redux";
3
+ import { differenceInSeconds } from "date-fns";
4
+
5
+ import { IIntegration, IRootState, ISite, ILanguage } from "@ax/types";
6
+ import { useModal } from "@ax/hooks";
7
+ import { Tooltip, IconAction } from "@ax/components";
8
+ import { integrationsActions } from "@ax/containers/Integrations";
9
+
10
+ import PasteIntegrationButton from "./PasteIntegrationButton";
11
+ import IntegrationItem from "./IntegrationItem";
12
+ import SideModal from "./SideModal";
13
+ import * as S from "./style";
14
+
15
+ const IntegrationsField = (props: IIntegrationsFieldProps): JSX.Element => {
16
+ const {
17
+ value,
18
+ onChange,
19
+ disabled,
20
+ getIntegrations,
21
+ integrations,
22
+ site,
23
+ languages,
24
+ copyIntegration,
25
+ integrationCopy,
26
+ } = props;
27
+
28
+ const [state, setState] = useState<Partial<IIntegration>[]>(value || null);
29
+ const { isOpen, toggleModal } = useModal();
30
+
31
+ useEffect(() => {
32
+ const params = {
33
+ pagination: false,
34
+ filterState: "enable",
35
+ };
36
+ getIntegrations(site.id, params, true);
37
+ }, [getIntegrations, site.id]);
38
+
39
+ useEffect(() => {
40
+ onChange && state && onChange(state);
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ }, [state]);
43
+
44
+ const handleAdd = (integration: Partial<IIntegration>) => {
45
+ setState((state) => (state ? [...state, integration] : [integration]));
46
+ };
47
+
48
+ const handleEditIntegration = (modifiedIntegration: Partial<IIntegration>, index: number) => {
49
+ const newIntegrations = [...state];
50
+ newIntegrations.splice(index, 1, modifiedIntegration);
51
+ setState(newIntegrations);
52
+ };
53
+
54
+ const removeItem = (index: number) => {
55
+ const newValue = [...state];
56
+ newValue.splice(index, 1);
57
+ setState(newValue);
58
+ };
59
+
60
+ const availableIntegrations = integrations.filter(
61
+ (integration) =>
62
+ (!integration.contentPresence.presenceType || integration.contentPresence.presenceType === "page-manual") &&
63
+ !state?.some((_integration: Partial<IIntegration>) => Number(_integration.id) === Number(integration.id))
64
+ );
65
+
66
+ const timeSinceIntegrationCopy = !!integrationCopy && differenceInSeconds(new Date(), new Date(integrationCopy.date));
67
+ const eightHoursInSeconds = 8 * 60 * 60;
68
+ const showPasteIntegrationButton = !disabled && !!integrationCopy && timeSinceIntegrationCopy < eightHoursInSeconds;
69
+
70
+ const pasteIntegration = () => {
71
+ integrationCopy && handleAdd(integrationCopy.integration);
72
+ };
73
+
74
+ return (
75
+ <S.Wrapper>
76
+ <S.Title>Third-Party Integration</S.Title>
77
+ <S.ItemRow>
78
+ <S.Subtitle>{state?.length || 0} items</S.Subtitle>
79
+ <S.ActionsWrapper>
80
+ {showPasteIntegrationButton && <PasteIntegrationButton pasteIntegration={pasteIntegration} />}
81
+ {!disabled && (
82
+ <>
83
+ <Tooltip content="Add integration" hideOnClick>
84
+ <IconAction icon="add" onClick={toggleModal} />
85
+ </Tooltip>
86
+ <SideModal
87
+ isOpen={isOpen}
88
+ toggleModal={toggleModal}
89
+ handleClick={handleAdd}
90
+ integrations={availableIntegrations}
91
+ />
92
+ </>
93
+ )}
94
+ </S.ActionsWrapper>
95
+ </S.ItemRow>
96
+ {state?.map((integration: Partial<IIntegration>, index: number) => (
97
+ <IntegrationItem
98
+ key={`${integration.id}${integration.name}${index}`}
99
+ integration={integration}
100
+ disabled={disabled}
101
+ removeItem={() => removeItem(index)}
102
+ editIntegration={handleEditIntegration}
103
+ languages={languages}
104
+ index={index}
105
+ copyIntegration={copyIntegration}
106
+ />
107
+ ))}
108
+ </S.Wrapper>
109
+ );
110
+ };
111
+
112
+ const mapStateToProps = (state: IRootState) => ({
113
+ site: state.sites.currentSiteInfo,
114
+ integrations: state.integrations.integrations,
115
+ languages: state.app.globalLangs,
116
+ integrationCopy: state.integrations.integrationCopy,
117
+ });
118
+
119
+ const mapDispatchToProps = {
120
+ getIntegrations: integrationsActions.getIntegrations,
121
+ copyIntegration: integrationsActions.copyIntegration,
122
+ };
123
+
124
+ interface IStateProps {
125
+ integrations: IIntegration[];
126
+ value: Partial<IIntegration>[];
127
+ onChange: (value: Partial<IIntegration>[]) => void;
128
+ disabled: boolean;
129
+ site: ISite;
130
+ languages: ILanguage[];
131
+ integrationCopy: { date: Date; integration: IIntegration } | null;
132
+ }
133
+
134
+ interface IDispatchProps {
135
+ getIntegrations: (
136
+ site: number,
137
+ params: { pagination: boolean; filterState: string },
138
+ isPage?: boolean
139
+ ) => Promise<void>;
140
+ copyIntegration: (integration: Partial<IIntegration>) => void;
141
+ }
142
+
143
+ export type IIntegrationsFieldProps = IStateProps & IDispatchProps;
144
+
145
+ export default connect(mapStateToProps, mapDispatchToProps)(IntegrationsField);
@@ -0,0 +1,29 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div``;
4
+
5
+ const Title = styled.p`
6
+ ${(p) => p.theme.textStyle.headingXXS};
7
+ color: ${(p) => p.theme.color.textMediumEmphasis};
8
+ padding-bottom: ${(p) => p.theme.spacing.xs};
9
+ border-bottom: 1px solid ${(p) => p.theme.color.uiLine};
10
+ `;
11
+
12
+ const ItemRow = styled.div`
13
+ display: flex;
14
+ align-items: center;
15
+ margin-bottom: ${(p) => p.theme.spacing.xs};
16
+ `;
17
+
18
+ const Subtitle = styled.span`
19
+ ${(p) => p.theme.textStyle.uiXS};
20
+ color: ${(p) => p.theme.color.textLowEmphasis};
21
+ `;
22
+
23
+ const ActionsWrapper = styled.div`
24
+ display: flex;
25
+ margin-left: auto;
26
+ align-items: center;
27
+ `;
28
+
29
+ export { Wrapper, Title, ItemRow, Subtitle, ActionsWrapper };
@@ -38,7 +38,7 @@ const TextArea = (props: ITextAreaProps): JSX.Element => {
38
38
  data-testid="text-area-component"
39
39
  name={name}
40
40
  minRows={rows}
41
- maxRows={4}
41
+ maxRows={10}
42
42
  value={state}
43
43
  onChange={handleOnChange}
44
44
  onBlur={handleOnBlur}
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useMemo } from "react";
2
2
 
3
3
  import * as S from "./style";
4
4
 
@@ -12,10 +12,12 @@ const ToggleField = (props: IToggleFieldProps): JSX.Element => {
12
12
  }
13
13
  };
14
14
 
15
+ const id = useMemo(() => Math.random().toString(), []);
16
+
15
17
  return (
16
18
  <S.Wrapper background={background} data-testid="toggle-field-wrapper">
17
19
  <S.Input
18
- id={`toggle-${name}`}
20
+ id={`toggle-${id}`}
19
21
  type="checkbox"
20
22
  name={name}
21
23
  value={value || false}
@@ -24,7 +26,7 @@ const ToggleField = (props: IToggleFieldProps): JSX.Element => {
24
26
  onChange={handleChange}
25
27
  data-testid="toggle-field-input"
26
28
  />
27
- <S.Label htmlFor={`toggle-${name}`} size={background ? "s" : size} />
29
+ <S.Label htmlFor={`toggle-${id}`} size={background ? "s" : size} />
28
30
  {auxText && <S.AuxText data-testid="toggle-auxtext">{auxText}</S.AuxText>}
29
31
  </S.Wrapper>
30
32
  );
@@ -1,11 +1,10 @@
1
1
  import React, { memo, useEffect, useState } from "react";
2
2
 
3
- import { IconAction, TextField, FloatingPanel, FieldsBehavior } from "@ax/components";
3
+ import { IconAction, TextField, FloatingPanel, FieldsBehavior, PageFinder } from "@ax/components";
4
4
  import { useModal } from "@ax/hooks";
5
5
  import { isReqOk } from "@ax/helpers";
6
6
  import { IPage, IUrlField, Field, ISelectOption } from "@ax/types";
7
7
  import { pages as pagesApi } from "@ax/api";
8
- import PageFinder from "./PageFinder";
9
8
  import { findAnchorsFromPage, findAnchorsFromTab, findTabsFromPage } from "./utils";
10
9
 
11
10
  import * as S from "./style";
@@ -77,17 +76,18 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
77
76
  resetValidation && resetValidation();
78
77
  };
79
78
 
80
- const handleSetPage = (page: IPage) => {
79
+ const handleSetPage = (page: IPage | IPage[]) => {
81
80
  toggleModal();
81
+ const selectedPage = Array.isArray(page) ? page[0] : page;
82
82
  onChange({
83
83
  ...value,
84
84
  href: null,
85
- linkTo: page.id,
86
- linkToURL: page.fullUrl,
87
- title: page.title,
88
- noFollow: !page.follow ? true : false,
85
+ linkTo: selectedPage.id,
86
+ linkToURL: selectedPage.fullUrl,
87
+ title: selectedPage.title,
88
+ noFollow: !selectedPage.follow ? true : false,
89
89
  });
90
- handleValidation && handleValidation(page.id.toString(), validators);
90
+ handleValidation && handleValidation(selectedPage.id.toString(), validators);
91
91
  handlePanel && handlePanel(isOpen);
92
92
  };
93
93