@griddo/ax 10.1.69 → 10.1.71

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 (50) hide show
  1. package/config/jest/setup.js +14 -4
  2. package/package.json +5 -2
  3. package/public/img/logos/logoSQY.svg +3 -0
  4. package/public/img/slider/analytics.png +0 -0
  5. package/public/img/slider/content.png +0 -0
  6. package/public/img/slider/editor.png +0 -0
  7. package/public/img/slider/gallery.png +0 -0
  8. package/public/img/slider/left-on.svg +4 -0
  9. package/public/img/slider/left.svg +4 -0
  10. package/public/img/slider/right-on.svg +4 -0
  11. package/public/img/slider/right.svg +4 -0
  12. package/src/__tests__/components/Login/Login.test.tsx +5 -7
  13. package/src/api/integrations.tsx +18 -1
  14. package/src/components/Fields/CheckField/index.tsx +6 -12
  15. package/src/components/Fields/CheckField/style.tsx +16 -6
  16. package/src/components/Fields/NoteField/style.tsx +8 -8
  17. package/src/components/Fields/TextField/index.tsx +3 -1
  18. package/src/components/Fields/TextField/style.tsx +6 -1
  19. package/src/components/Fields/UniqueCheck/index.tsx +3 -1
  20. package/src/components/FieldsBehavior/index.tsx +4 -3
  21. package/src/components/FieldsBehavior/style.tsx +35 -22
  22. package/src/components/Login/Circle/index.tsx +16 -0
  23. package/src/components/Login/Circle/style.tsx +30 -0
  24. package/src/components/Login/LoginSlider/index.tsx +63 -0
  25. package/src/components/Login/LoginSlider/style.tsx +68 -0
  26. package/src/components/Login/index.tsx +102 -58
  27. package/src/components/Login/style.tsx +121 -16
  28. package/src/components/MainWrapper/AppBar/index.tsx +5 -1
  29. package/src/components/MainWrapper/AppBar/style.tsx +17 -1
  30. package/src/components/MainWrapper/index.tsx +3 -1
  31. package/src/components/MainWrapper/style.tsx +35 -4
  32. package/src/components/TableList/index.tsx +3 -5
  33. package/src/containers/App/actions.tsx +23 -7
  34. package/src/containers/App/constants.tsx +2 -0
  35. package/src/containers/App/interfaces.tsx +6 -0
  36. package/src/containers/App/reducer.tsx +4 -0
  37. package/src/containers/Integrations/actions.tsx +32 -2
  38. package/src/modules/Login/index.tsx +16 -3
  39. package/src/modules/Settings/Integrations/BulkHeader/TableHeader/index.tsx +1 -1
  40. package/src/modules/Settings/Integrations/BulkHeader/TableHeader/style.tsx +2 -2
  41. package/src/modules/Settings/Integrations/IntegrationForm/index.tsx +2 -1
  42. package/src/modules/Settings/Integrations/IntegrationItem/index.tsx +27 -13
  43. package/src/modules/Settings/Integrations/IntegrationItem/style.tsx +32 -5
  44. package/src/modules/Settings/Integrations/index.tsx +114 -81
  45. package/src/modules/Settings/Integrations/style.tsx +20 -1
  46. package/src/modules/Sites/SitesList/index.tsx +18 -3
  47. package/src/modules/Sites/SitesList/style.tsx +28 -1
  48. package/src/modules/Sites/index.tsx +4 -1
  49. package/src/themes/theme.json +4 -0
  50. package/src/types/index.tsx +2 -0
@@ -12,6 +12,7 @@ const SET_GLOBAL_LANGUAGES = `${NAME}/SET_GLOBAL_LANGUAGES`;
12
12
  const SET_GLOBAL_SETTINGS = `${NAME}/SET_GLOBAL_SETTINGS`;
13
13
  const SET_USER = `${NAME}/SET_USER`;
14
14
  const SET_SESSION_STARTED_AT = `${NAME}/SET_SESSION_STARTED_AT`;
15
+ const SET_HAS_ANIMATION = `${NAME}/SET_HAS_ANIMATION`;
15
16
 
16
17
  export {
17
18
  SET_ERROR,
@@ -26,4 +27,5 @@ export {
26
27
  SET_GLOBAL_SETTINGS,
27
28
  SET_USER,
28
29
  SET_SESSION_STARTED_AT,
30
+ SET_HAS_ANIMATION,
29
31
  };
@@ -10,6 +10,7 @@ import {
10
10
  SET_GLOBAL_SETTINGS,
11
11
  SET_USER,
12
12
  SET_SESSION_STARTED_AT,
13
+ SET_HAS_ANIMATION,
13
14
  } from "./constants";
14
15
  import { IUser } from "./reducer";
15
16
 
@@ -70,6 +71,11 @@ export interface ISetSessionStartedAtAction {
70
71
  payload: { sessionStartedAt: null | Date };
71
72
  }
72
73
 
74
+ export interface ISetHasAnimation {
75
+ type: typeof SET_HAS_ANIMATION;
76
+ payload: { hasAnimation: boolean };
77
+ }
78
+
73
79
  export type AppActionsCreators = ISetIsLoading & ISetIsSaving;
74
80
 
75
81
  export type AuthActionsCreators = ISetTokenAction &
@@ -12,6 +12,7 @@ import {
12
12
  SET_GLOBAL_SETTINGS,
13
13
  SET_USER,
14
14
  SET_SESSION_STARTED_AT,
15
+ SET_HAS_ANIMATION,
15
16
  } from "./constants";
16
17
 
17
18
  export interface IAppState {
@@ -26,6 +27,7 @@ export interface IAppState {
26
27
  globalLangs: any;
27
28
  globalSettings: IGlobalSettings;
28
29
  sessionStartedAt: null | Date;
30
+ hasAnimation: boolean;
29
31
  }
30
32
  export interface IError {
31
33
  code?: any;
@@ -83,6 +85,7 @@ export const initialState = {
83
85
  autoTranslation: false,
84
86
  },
85
87
  sessionStartedAt: null,
88
+ hasAnimation: false,
86
89
  };
87
90
 
88
91
  export function reducer(state = initialState, action: any): IAppState {
@@ -99,6 +102,7 @@ export function reducer(state = initialState, action: any): IAppState {
99
102
  case SET_GLOBAL_SETTINGS:
100
103
  case SET_USER:
101
104
  case SET_SESSION_STARTED_AT:
105
+ case SET_HAS_ANIMATION:
102
106
  return { ...state, ...action.payload };
103
107
  case SET_ERROR:
104
108
  case RESET_ERROR:
@@ -31,7 +31,7 @@ function setCopyIntegration(
31
31
  return { type: SET_COPY_INTEGRATION, payload: { integrationCopy } };
32
32
  }
33
33
 
34
- function getIntegrations(site: number, params?: any, isPage?: boolean): (dispatch: Dispatch) => Promise<void> {
34
+ function getIntegrations(site: number, params?: any, skipLoading?: boolean): (dispatch: Dispatch) => Promise<void> {
35
35
  return async (dispatch) => {
36
36
  try {
37
37
  const callback = async () => integrations.getIntegrations(site, params);
@@ -43,7 +43,7 @@ function getIntegrations(site: number, params?: any, isPage?: boolean): (dispatc
43
43
  },
44
44
  handleError: (response: any) => appActions.handleError(response)(dispatch),
45
45
  };
46
- await handleRequest(callback, responseActions, isPage ? [] : [appActions.setIsLoading])(dispatch);
46
+ await handleRequest(callback, responseActions, skipLoading ? [] : [appActions.setIsLoading])(dispatch);
47
47
  } catch (e) {
48
48
  console.log(e);
49
49
  }
@@ -177,6 +177,35 @@ function copyIntegration(integration: Partial<IIntegration>): (dispatch: Dispatc
177
177
  };
178
178
  }
179
179
 
180
+ function orderIntegration(
181
+ id: number,
182
+ newOrder: number,
183
+ currentParams?: any
184
+ ): (dispatch: Dispatch, getState: any) => Promise<boolean> {
185
+ return async (dispatch, getState) => {
186
+ try {
187
+ const {
188
+ sites: { currentSiteInfo },
189
+ } = getState();
190
+
191
+ dispatch(appActions.setIsLoading(true));
192
+
193
+ const callback = async () => integrations.orderIntegration(id, newOrder);
194
+ const responseActions = {
195
+ handleSuccess: () => {
196
+ const params = { ...DEFAULT_PARAMS, ...currentParams };
197
+ getIntegrations(currentSiteInfo.id, params)(dispatch);
198
+ },
199
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
200
+ };
201
+ return await handleRequest(callback, responseActions, [])(dispatch);
202
+ } catch (e) {
203
+ console.log(e);
204
+ return false;
205
+ }
206
+ };
207
+ }
208
+
180
209
  export {
181
210
  getIntegrations,
182
211
  resetIntegrations,
@@ -187,4 +216,5 @@ export {
187
216
  updateIntegration,
188
217
  setCurrentIntegration,
189
218
  copyIntegration,
219
+ orderIntegration,
190
220
  };
@@ -7,7 +7,7 @@ import { Login } from "@ax/components";
7
7
  import { IGlobalSettings } from "@ax/containers/App/reducer";
8
8
 
9
9
  const LoginModule = (props: IProps) => {
10
- const { isLoggingIn, globalSettings, login, resetError, getGlobalSettings } = props;
10
+ const { isLoggingIn, globalSettings, login, resetError, getGlobalSettings, setHistoryPush } = props;
11
11
 
12
12
  const initState = {
13
13
  email: "",
@@ -16,6 +16,7 @@ const LoginModule = (props: IProps) => {
16
16
  };
17
17
 
18
18
  const [state, setState] = useState(initState);
19
+ const [isSuccess, setIsSuccess] = useState(false);
19
20
 
20
21
  useEffect(() => {
21
22
  const prefix = process.env.REACT_APP_SITE_TITLE ? process.env.REACT_APP_SITE_TITLE : "";
@@ -28,7 +29,10 @@ const LoginModule = (props: IProps) => {
28
29
 
29
30
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
30
31
  e.preventDefault();
31
- await login(state.email, state.password, state.rememberMe);
32
+ const isLogged = await login(state.email, state.password, state.rememberMe);
33
+ if(isLogged){
34
+ setIsSuccess(true);
35
+ }
32
36
  };
33
37
 
34
38
  const _handleEmail = (email: string) => {
@@ -41,6 +45,11 @@ const LoginModule = (props: IProps) => {
41
45
  setState({ ...state, password });
42
46
  };
43
47
 
48
+ const handleLoginSuccess = () => {
49
+ const welcomePageURI = "/sites";
50
+ setHistoryPush(welcomePageURI);
51
+ }
52
+
44
53
  const _handleRememberMe = () => setState({ ...state, rememberMe: !state.rememberMe });
45
54
 
46
55
  return (
@@ -54,6 +63,8 @@ const LoginModule = (props: IProps) => {
54
63
  settings={globalSettings}
55
64
  rememberMe={state.rememberMe}
56
65
  handleRememberMe={_handleRememberMe}
66
+ isLoginSuccess={isSuccess}
67
+ handleLoginSuccess={handleLoginSuccess}
57
68
  />
58
69
  );
59
70
  };
@@ -63,15 +74,17 @@ interface IProps {
63
74
  isLoggingIn: boolean;
64
75
  children: any;
65
76
  globalSettings: IGlobalSettings;
66
- login(email: string, password: string, rememberMe: boolean): Promise<void>;
77
+ login(email: string, password: string, rememberMe: boolean): Promise<boolean>;
67
78
  resetError(): void;
68
79
  getGlobalSettings(): void;
80
+ setHistoryPush(path: string): Promise<void>
69
81
  }
70
82
 
71
83
  const mapDispatchToProps = {
72
84
  login: appActions.login,
73
85
  resetError: appActions.resetError,
74
86
  getGlobalSettings: appActions.getGlobalSettings,
87
+ setHistoryPush: appActions.setHistoryPush,
75
88
  };
76
89
 
77
90
  const mapStateToProps = (state: IRootState) => ({
@@ -45,7 +45,7 @@ const TableHeader = (props: IProps): JSX.Element => {
45
45
  />
46
46
  </S.CheckHeader>
47
47
  <S.NameHeader>
48
- <NameFilter sortItems={sortByName} sortedState={sortedListStatus} />
48
+ Name
49
49
  </S.NameHeader>
50
50
  <S.DescriptionHeader></S.DescriptionHeader>
51
51
  <S.AppliedOnHeader>
@@ -14,8 +14,8 @@ const CheckHeader = styled(Header)`
14
14
  width: 32px;
15
15
  `;
16
16
 
17
- const NameHeader = styled.div`
18
- width: 200px;
17
+ const NameHeader = styled(Header)`
18
+ width: 350px;
19
19
  `;
20
20
 
21
21
  const DescriptionHeader = styled(Header)`
@@ -30,6 +30,7 @@ const IntegrationForm = (props: IProps) => {
30
30
  contentPresence: { presenceType: "all", relatedPages: [] },
31
31
  active: true,
32
32
  variables: [],
33
+ scriptOrder: 0,
33
34
  };
34
35
 
35
36
  const [form, setForm] = useState<IIntegration>(initState);
@@ -222,7 +223,7 @@ const IntegrationForm = (props: IProps) => {
222
223
  value={form.contentBodyPosition}
223
224
  options={bodyOptions}
224
225
  onChange={handleContentBodyPositionChange}
225
- disabled={!form.contentBody.trim().length}
226
+ disabled={!form.contentBody || !form.contentBody.trim().length}
226
227
  />
227
228
  <FieldsBehavior
228
229
  title="Add code to:"
@@ -1,8 +1,9 @@
1
1
  import React from "react";
2
2
  import { connect } from "react-redux";
3
+ import { DraggableProvided } from "react-beautiful-dnd";
3
4
 
4
- import { ICheck, IIntegration, IRootState } from "@ax/types";
5
- import { CheckField, Modal, Toast, ToggleField } from "@ax/components";
5
+ import { ICheck, IIntegration } from "@ax/types";
6
+ import { CheckField, Icon, Modal, Toast, ToggleField } from "@ax/components";
6
7
  import { useModal, useToast } from "@ax/hooks";
7
8
  import { integrations } from "@ax/api";
8
9
  import { integrationsActions } from "@ax/containers/Integrations";
@@ -21,6 +22,9 @@ const IntegrationItem = (props: IIntegrationItemProps): JSX.Element => {
21
22
  getParams,
22
23
  changeState,
23
24
  toggleToastChange,
25
+ innerRef,
26
+ provided,
27
+ listLength,
24
28
  } = props;
25
29
 
26
30
  const { isOpen: isOpenDelete, toggleModal: toggleModalDelete } = useModal();
@@ -113,12 +117,24 @@ const IntegrationItem = (props: IIntegrationItemProps): JSX.Element => {
113
117
 
114
118
  return (
115
119
  <>
116
- <S.ItemRow role="rowgroup" selected={isSelected} data-testid="integration-item-row">
117
- <S.CheckCell role="cell">
120
+ <S.ItemRow
121
+ role="rowgroup"
122
+ selected={isSelected}
123
+ ref={innerRef}
124
+ data-testid="integration-item-row"
125
+ {...provided?.draggableProps}
126
+ >
127
+ <S.HandleWrapper {...provided?.dragHandleProps} hidden={listLength < 2} data-testid="handle-wrapper">
128
+ <S.IconHandleWrapper>
129
+ <Icon name="drag" size="16" />
130
+ </S.IconHandleWrapper>
131
+ </S.HandleWrapper>
132
+ <S.CheckCell role="cell" hasHandle={listLength >= 2}>
118
133
  <CheckField name="check" value={integration.id ?? ""} checked={isSelected} onChange={handleOnChange} />
119
134
  </S.CheckCell>
120
135
  <S.NameCell role="cell" onClick={handleClick}>
121
- {integration.name}
136
+ <S.Order>#{integration.correlativeScriptOrder}</S.Order>
137
+ <div>{integration.name}</div>
122
138
  </S.NameCell>
123
139
  <S.DescriptionCell role="cell" onClick={handleClick}>
124
140
  {integration.description}
@@ -156,8 +172,8 @@ const IntegrationItem = (props: IIntegrationItemProps): JSX.Element => {
156
172
  >
157
173
  <S.ModalContent>
158
174
  Are you sure you want to {integration.active ? "disable" : "enable"}{" "}
159
- <strong>{integration.name} add-on</strong>? This add-on will {integration.active ? "stop" : "start"}{" "}
160
- working in the pages previously added.
175
+ <strong>{integration.name} add-on</strong>? This add-on will {integration.active ? "stop" : "start"} working
176
+ in the pages previously added.
161
177
  </S.ModalContent>
162
178
  </Modal>
163
179
  <CopyModal isOpen={isOpenCopy} hide={toggleModalCopy} action={copyIntegration} />
@@ -175,13 +191,11 @@ interface IProps {
175
191
  getParams: () => any;
176
192
  changeState: (integrationId: number, active: boolean, params: any) => Promise<boolean>;
177
193
  toggleToastChange: (state: { total: number; active: boolean }) => void;
178
- currentSiteID: number;
194
+ innerRef: any;
195
+ provided: DraggableProvided;
196
+ listLength: number;
179
197
  }
180
198
 
181
- const mapStateToProps = (state: IRootState) => ({
182
- currentSiteID: state.sites.currentSiteInfo && state.sites.currentSiteInfo.id,
183
- });
184
-
185
199
  const mapDispatchToProps = {
186
200
  setHistoryPush: appActions.setHistoryPush,
187
201
  setCurrentIntegration: integrationsActions.setCurrentIntegration,
@@ -194,4 +208,4 @@ interface IDispatchProps {
194
208
 
195
209
  export type IIntegrationItemProps = IProps & IDispatchProps;
196
210
 
197
- export default connect(mapStateToProps, mapDispatchToProps)(IntegrationItem);
211
+ export default connect(null, mapDispatchToProps)(IntegrationItem);
@@ -3,18 +3,26 @@ import styled from "styled-components";
3
3
  import { Cell, Row } from "@ax/components/TableList/TableItem/style";
4
4
  import { ActionMenu } from "@ax/components";
5
5
 
6
- const CheckCell = styled(Cell)`
7
- padding-left: ${(p) => p.theme.spacing.m};
8
- width: 32px;
6
+ const CheckCell = styled(Cell)<{ hasHandle: boolean }>`
7
+ width: 24px;
8
+ padding: ${(p) => p.hasHandle ? `${p.theme.spacing.s} ${p.theme.spacing.xs}`: `0 ${p.theme.spacing.s} 0 ${p.theme.spacing.m}`};
9
9
  label {
10
- margin-bottom: ${(p) => p.theme.spacing.s};
10
+ margin-bottom: 18px;
11
11
  }
12
12
  `;
13
13
 
14
14
  const NameCell = styled(Cell)`
15
15
  ${(p) => p.theme.textStyle.uiM};
16
16
  color: ${(p) => p.theme.color.textHighEmphasis};
17
- width: 200px;
17
+ width: 350px;
18
+ flex-flow: row wrap;
19
+ justify-content: flex-start;
20
+ align-items: center;
21
+ `;
22
+
23
+ const Order = styled.div`
24
+ color: ${(p) => p.theme.color.textMediumEmphasis};
25
+ margin-right: ${(p) => p.theme.spacing.xxs};
18
26
  `;
19
27
 
20
28
  const DescriptionCell = styled(Cell)`
@@ -67,6 +75,22 @@ const ItemRow = styled(Row)`
67
75
  }
68
76
  `;
69
77
 
78
+ const HandleWrapper = styled.div<{ hidden?: boolean }>`
79
+ padding-left: ${(p) => p.theme.spacing.xs};
80
+ display: ${(p) => (p.hidden ? "none" : "flex")};
81
+ align-items: center;
82
+ `;
83
+
84
+ const IconHandleWrapper = styled.div`
85
+ width: ${(p) => p.theme.spacing.s};
86
+ height: ${(p) => p.theme.spacing.s};
87
+ svg {
88
+ path {
89
+ fill: ${(p) => p.theme.color.textLowEmphasis};
90
+ }
91
+ }
92
+ `;
93
+
70
94
  export {
71
95
  CheckCell,
72
96
  NameCell,
@@ -78,4 +102,7 @@ export {
78
102
  ContentPresence,
79
103
  ModalContent,
80
104
  StyledActionMenu,
105
+ HandleWrapper,
106
+ IconHandleWrapper,
107
+ Order,
81
108
  };
@@ -1,5 +1,6 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { connect } from "react-redux";
3
+ import { DragDropContext, Droppable, Draggable, DropResult } from "react-beautiful-dnd";
3
4
 
4
5
  import { IEmptyStateProps, IIntegration, IRootState } from "@ax/types";
5
6
  import { MainWrapper, ErrorToast, TableList, EmptyState, Toast } from "@ax/components";
@@ -12,10 +13,8 @@ import IntegrationItem from "./IntegrationItem";
12
13
  import { useFilterQuery, useSortedListStatus } from "./hooks";
13
14
  import { getSortedListStatus } from "./utils";
14
15
  import { DeactivateModal, DeleteModal } from "./atoms";
15
- import * as S from "./style";
16
16
 
17
- const itemsPerPage = 50;
18
- const firstPage = 1;
17
+ import * as S from "./style";
19
18
 
20
19
  const Integrations = (props: IIntegrationsProps): JSX.Element => {
21
20
  const {
@@ -29,9 +28,13 @@ const Integrations = (props: IIntegrationsProps): JSX.Element => {
29
28
  changeIntegrationState,
30
29
  setHistoryPush,
31
30
  setCurrentIntegration,
31
+ orderIntegration,
32
32
  } = props;
33
33
 
34
- const [page, setPage] = useState(firstPage);
34
+ if (!currentSite) {
35
+ throw new Error(`ERROR: User reached Integrations with null site info`);
36
+ }
37
+
35
38
  const [isScrolling, setIsScrolling] = useState(false);
36
39
  const tableRef = useRef<HTMLDivElement>(null);
37
40
  const [currentFilterQuery, setCurrentFilterQuery] = useState("");
@@ -64,14 +67,12 @@ const Integrations = (props: IIntegrationsProps): JSX.Element => {
64
67
 
65
68
  const getParams = useCallback(() => {
66
69
  const params = {
67
- page,
68
- itemsPerPage,
69
- pagination: true,
70
+ pagination: false,
70
71
  filter: currentFilterQuery,
71
72
  };
72
73
 
73
74
  return params;
74
- }, [page, currentFilterQuery]);
75
+ }, [currentFilterQuery]);
75
76
 
76
77
  useEffect(() => {
77
78
  const params = getParams();
@@ -82,7 +83,7 @@ const Integrations = (props: IIntegrationsProps): JSX.Element => {
82
83
  resetIntegrations();
83
84
  };
84
85
  // eslint-disable-next-line react-hooks/exhaustive-deps
85
- }, [page, currentSite, currentFilterQuery]);
86
+ }, [currentSite, currentFilterQuery]);
86
87
 
87
88
  const {
88
89
  resetBulkSelection,
@@ -121,7 +122,6 @@ const Integrations = (props: IIntegrationsProps): JSX.Element => {
121
122
  const handleSelectAll = () => selectAllItems();
122
123
 
123
124
  const sortItems = async (orderPointer: string, isAscending: boolean) => {
124
- setPage(firstPage);
125
125
  const sortedState = getSortedListStatus(orderPointer, isAscending);
126
126
  setSortedListStatus(sortedState);
127
127
 
@@ -131,7 +131,6 @@ const Integrations = (props: IIntegrationsProps): JSX.Element => {
131
131
  };
132
132
 
133
133
  const filterItems = async (filterPointer: string, filtersSelected: string) => {
134
- setPage(firstPage);
135
134
  const filtersSelection = setFiltersSelection(filterPointer, filtersSelected);
136
135
  const filterQuery = setFilterQuery(filtersSelection);
137
136
  setCurrentFilterQuery(filterQuery);
@@ -156,11 +155,22 @@ const Integrations = (props: IIntegrationsProps): JSX.Element => {
156
155
 
157
156
  const onScroll = (e: any) => setIsScrolling(e.target.scrollTop > 0);
158
157
 
159
- const pagination = {
160
- setPage,
161
- itemsPerPage,
162
- totalItems,
163
- currPage: page,
158
+ const onDragEnd = (result: DropResult) => {
159
+ if (!result.destination) {
160
+ return;
161
+ }
162
+
163
+ if (result.destination.index === result.source.index) {
164
+ return;
165
+ }
166
+
167
+ const newScriptOrder = result.destination.index
168
+ ? integrations[result.destination.index - (result.destination.index > result.source.index ? 0 : 1)].scriptOrder +
169
+ 1
170
+ : 1;
171
+
172
+ const params = getParams();
173
+ orderIntegration(parseInt(result.draggableId), newScriptOrder, params);
164
174
  };
165
175
 
166
176
  const mainDeleteModalAction = {
@@ -192,70 +202,91 @@ const Integrations = (props: IIntegrationsProps): JSX.Element => {
192
202
  action: () => setHistoryPush(`/sites/settings/addons/new`),
193
203
  };
194
204
 
205
+ const ComponentList = React.memo(function ComponentList({ components }: any) {
206
+ return components.map((integration: IIntegration, index: number) => {
207
+ const isItemSelected = isSelected(integration.id);
208
+ return (
209
+ <Draggable draggableId={`${integration.id}`} index={index} key={integration.id}>
210
+ {(provided) => (
211
+ <IntegrationItem
212
+ key={`${integration.name}${integration.id}`}
213
+ integration={integration}
214
+ isSelected={isItemSelected}
215
+ onChange={addToBulkSelection}
216
+ toggleToastDelete={toggleToastDelete}
217
+ getParams={getParams}
218
+ deleteIntegration={deleteIntegration}
219
+ changeState={changeIntegrationState}
220
+ toggleToastChange={toggleToastChange}
221
+ innerRef={provided.innerRef}
222
+ provided={provided}
223
+ listLength={integrations.length}
224
+ />
225
+ )}
226
+ </Draggable>
227
+ );
228
+ });
229
+ });
230
+
195
231
  return (
196
- <>
197
- <MainWrapper backLink={false} title="Add-ons" rightButton={rightButtonProps}>
198
- <S.Wrapper data-testid="integrations-main-wrapper">
199
- <S.ContentWrapper>
200
- <ErrorToast />
201
- <S.TitleWrapper>
202
- <S.Title>Custom Code</S.Title>
203
- <S.Description>Add custom code to the head or body of your site&apos;s pages.</S.Description>
204
- </S.TitleWrapper>
205
- <S.TableWrapper>
206
- <TableList
207
- tableHeader={TableHeader}
208
- pagination={pagination}
209
- onScroll={onScroll}
210
- hasFixedHeader={true}
211
- tableRef={tableRef}
212
- >
213
- {isEmpty ? (
214
- <S.EmptyWrapper>
215
- <EmptyState {...emptyStateProps} />
216
- </S.EmptyWrapper>
217
- ) : (
218
- integrations.map((integration: IIntegration) => {
219
- const isItemSelected = isSelected(integration.id);
220
- return (
221
- <IntegrationItem
222
- key={`${integration.name}${integration.id}`}
223
- integration={integration}
224
- isSelected={isItemSelected}
225
- onChange={addToBulkSelection}
226
- toggleToastDelete={toggleToastDelete}
227
- getParams={getParams}
228
- deleteIntegration={deleteIntegration}
229
- changeState={changeIntegrationState}
230
- toggleToastChange={toggleToastChange}
231
- />
232
- );
233
- })
234
- )}
235
- </TableList>
236
- </S.TableWrapper>
237
- {isVisibleDelete && <Toast {...deletedToastProps} />}
238
- {isVisibleChange && <Toast {...changedToastProps} />}
239
- </S.ContentWrapper>
240
- </S.Wrapper>
241
- <DeleteModal
242
- isOpen={isOpenDelete}
243
- toggleModal={toggleModalDelete}
244
- secondaryModalAction={secondaryDeleteModalAction}
245
- mainModalAction={mainDeleteModalAction}
246
- integrations={integrations}
247
- selectedIds={selectedItems.all}
248
- />
249
- <DeactivateModal
250
- isOpen={isOpenDeactivate}
251
- toggleModal={toggleModalDeactivate}
252
- secondaryModalAction={secondaryDeactivateModalAction}
253
- mainModalAction={mainDeactivateModalAction}
254
- integrations={integrations}
255
- selectedIds={selectedItems.all}
256
- />
257
- </MainWrapper>
258
- </>
232
+ <MainWrapper backLink={false} title="Add-ons" rightButton={rightButtonProps}>
233
+ <S.Wrapper data-testid="integrations-main-wrapper">
234
+ <S.ContentWrapper>
235
+ <ErrorToast />
236
+ <S.TitleWrapper>
237
+ <S.Title>Custom Code</S.Title>
238
+ <S.Description>Add custom code to the head or body of your site&apos;s pages.</S.Description>
239
+ </S.TitleWrapper>
240
+ <S.TableWrapper>
241
+ <TableList tableHeader={TableHeader} onScroll={onScroll} hasFixedHeader={true} tableRef={tableRef}>
242
+ {isEmpty ? (
243
+ <S.EmptyWrapper>
244
+ <EmptyState {...emptyStateProps} />
245
+ </S.EmptyWrapper>
246
+ ) : (
247
+ <>
248
+ {integrations.length >= 2 && (
249
+ <S.OrderNote>
250
+ You can easily <strong>reorder</strong> the list using <strong>drag and drop</strong>. The order{" "}
251
+ <strong>determines the priority of the add-on</strong>, and the code appears on the page
252
+ accordingly.
253
+ </S.OrderNote>
254
+ )}
255
+ <DragDropContext onDragEnd={onDragEnd}>
256
+ <Droppable droppableId="integrationsList">
257
+ {(provided) => (
258
+ <div ref={provided.innerRef} {...provided.droppableProps} data-testid="droppable">
259
+ <ComponentList components={integrations} />
260
+ {provided.placeholder}
261
+ </div>
262
+ )}
263
+ </Droppable>
264
+ </DragDropContext>
265
+ </>
266
+ )}
267
+ </TableList>
268
+ </S.TableWrapper>
269
+ {isVisibleDelete && <Toast {...deletedToastProps} />}
270
+ {isVisibleChange && <Toast {...changedToastProps} />}
271
+ </S.ContentWrapper>
272
+ </S.Wrapper>
273
+ <DeleteModal
274
+ isOpen={isOpenDelete}
275
+ toggleModal={toggleModalDelete}
276
+ secondaryModalAction={secondaryDeleteModalAction}
277
+ mainModalAction={mainDeleteModalAction}
278
+ integrations={integrations}
279
+ selectedIds={selectedItems.all}
280
+ />
281
+ <DeactivateModal
282
+ isOpen={isOpenDeactivate}
283
+ toggleModal={toggleModalDeactivate}
284
+ secondaryModalAction={secondaryDeactivateModalAction}
285
+ mainModalAction={mainDeactivateModalAction}
286
+ integrations={integrations}
287
+ selectedIds={selectedItems.all}
288
+ />
289
+ </MainWrapper>
259
290
  );
260
291
  };
261
292
 
@@ -273,13 +304,14 @@ const mapDispatchToProps = {
273
304
  changeIntegrationState: integrationsActions.changeIntegrationState,
274
305
  setHistoryPush: appActions.setHistoryPush,
275
306
  setCurrentIntegration: integrationsActions.setCurrentIntegration,
307
+ orderIntegration: integrationsActions.orderIntegration,
276
308
  };
277
309
 
278
310
  interface IStateProps {
279
311
  isLoading: boolean;
280
312
  integrations: IIntegration[];
281
313
  totalItems: number;
282
- currentSite: number;
314
+ currentSite: number | null;
283
315
  }
284
316
 
285
317
  interface IDispatchProps {
@@ -287,11 +319,12 @@ interface IDispatchProps {
287
319
  resetIntegrations: () => void;
288
320
  deleteIntegration: (
289
321
  integrationId: number | number[],
290
- currentParams: { page: number; itemsPerPage: number; pagination: boolean; filter: any }
322
+ currentParams: { pagination: boolean; filter: any }
291
323
  ) => Promise<boolean>;
292
324
  changeIntegrationState: (integrationId: number | number[], active: boolean, currentParams?: any) => Promise<boolean>;
293
325
  setHistoryPush: (path: string) => void;
294
326
  setCurrentIntegration: (integration: IIntegration | null) => void;
327
+ orderIntegration(id: number, newOrder: number, currentParams?: any): Promise<boolean>;
295
328
  }
296
329
 
297
330
  export type IIntegrationsProps = IStateProps & IDispatchProps;