@griddo/ax 11.1.16 → 11.1.17-rc.0

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "11.1.16",
4
+ "version": "11.1.17-rc.0",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -225,5 +225,5 @@
225
225
  "publishConfig": {
226
226
  "access": "public"
227
227
  },
228
- "gitHead": "46a62886326c447820b5198ecc2da2c3d6246bb8"
228
+ "gitHead": "298fc1428705d2d43c148e6f05298001ced495c1"
229
229
  }
package/src/api/sites.tsx CHANGED
@@ -109,6 +109,11 @@ const SERVICES: { [key: string]: IServiceConfig } = {
109
109
  endpoint: ["/site/", "/pages/global/imports/bulk"],
110
110
  method: "DELETE",
111
111
  },
112
+ GET_SELECT_SITES: {
113
+ ...template,
114
+ endpoint: "/select/sites",
115
+ method: "GET",
116
+ },
112
117
  };
113
118
 
114
119
  const getAllSites = async (params: IGetSitesParams = { recentSitesNumber: 7 }) => {
@@ -361,8 +366,11 @@ const unpublishSite = (siteID: number) => {
361
366
  SERVICES.UNPUBLISH_SITE.dynamicUrl = `${host}${prefix}${siteID}${suffix}`;
362
367
  return sendRequest(SERVICES.UNPUBLISH_SITE);
363
368
  };
369
+
364
370
  const unpublishSiteBulk = (ids: number[]) => sendRequest(SERVICES.UNPUBLISH_SITE_BULK, { sites: ids });
365
371
 
372
+ const getSelectSites = () => sendRequest(SERVICES.GET_SELECT_SITES);
373
+
366
374
  export default {
367
375
  getAllSites,
368
376
  getSiteInfo,
@@ -384,4 +392,5 @@ export default {
384
392
  removePage,
385
393
  removePageBulk,
386
394
  removeUsersBulk,
395
+ getSelectSites,
387
396
  };
@@ -120,6 +120,11 @@ const SERVICES: { [key: string]: IServiceConfig } = {
120
120
  method: "POST",
121
121
  responseType: "blob",
122
122
  },
123
+ GET_AI_SEARCH_DATA: {
124
+ ...template,
125
+ endpoint: "/ai/search/content-types",
126
+ method: "GET",
127
+ },
123
128
  };
124
129
 
125
130
  const getData = (token: string | null, siteID?: number | null): Promise<AxiosResponse> => {
@@ -347,6 +352,8 @@ const exportDataContent = (
347
352
  return sendRequest(SERVICES.EXPORT_DATA_CONTENT, { ...data });
348
353
  };
349
354
 
355
+ const getAISearchData = (): Promise<AxiosResponse> => sendRequest(SERVICES.GET_AI_SEARCH_DATA);
356
+
350
357
  export default {
351
358
  getData,
352
359
  getDataContent,
@@ -371,4 +378,5 @@ export default {
371
378
  orderCategory,
372
379
  getGroup,
373
380
  exportDataContent,
381
+ getAISearchData,
374
382
  };
@@ -0,0 +1,154 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Button, FieldsBehavior, Icon } from "@ax/components";
3
+ import { sites, structuredData } from "@ax/api";
4
+ import { isReqOk } from "@ax/helpers";
5
+ import { IState } from "..";
6
+
7
+ import * as S from "./style";
8
+
9
+ const AIConfigPanel = (props: IAIConfigPanelProps): JSX.Element => {
10
+ const { value, onChange, lang, site, whiteList } = props;
11
+
12
+ const initialState: IState = {
13
+ prompt: "",
14
+ templates: [],
15
+ sites: site ? [site] : [],
16
+ language: lang,
17
+ limit: 0,
18
+ };
19
+
20
+ const formValues = value ? value : initialState;
21
+
22
+ const [form, setForm] = useState(formValues);
23
+ const [data, setData] = useState({ sites: [], templates: [] });
24
+
25
+ useEffect(() => {
26
+ const getItems = async () => {
27
+ try {
28
+ const items = { sites: [], templates: [] };
29
+ const resultSites = await sites.getSelectSites();
30
+ if (resultSites && isReqOk(resultSites.status)) {
31
+ const sites = resultSites.data.map((opt: { value: number; label: string }) => {
32
+ return { value: opt.value, name: opt.value, title: opt.label };
33
+ });
34
+ items.sites = sites;
35
+ }
36
+ const resultData = await structuredData.getAISearchData();
37
+ if (resultData && isReqOk(resultData.status)) {
38
+ const templates = resultData.data
39
+ .filter((opt: { id: string; name: string }) => (whiteList ? whiteList.includes(opt.id) : true))
40
+ .map((opt: { id: string; name: string }) => {
41
+ return { value: opt.id, name: opt.id, title: opt.name };
42
+ });
43
+ items.templates = templates;
44
+ }
45
+ setData(items);
46
+ } catch (e) {
47
+ console.log(e);
48
+ }
49
+ };
50
+
51
+ let isMounted = true;
52
+ if (isMounted) {
53
+ getItems();
54
+ }
55
+ return () => {
56
+ isMounted = false;
57
+ };
58
+ // eslint-disable-next-line react-hooks/exhaustive-deps
59
+ }, []);
60
+
61
+ const handleApply = () => onChange(form);
62
+ const handleClear = () => setForm({ ...initialState, sites: [] });
63
+
64
+ const handleSourceSelect = (templates: string[]) => setForm((state) => ({ ...state, templates }));
65
+
66
+ const handleSiteSelect = (sites: number[]) => setForm((state) => ({ ...state, sites }));
67
+
68
+ const handleLimit = (limit: number) => setForm((state) => ({ ...state, limit }));
69
+
70
+ const handlePrompt = (prompt: string) => setForm((state) => ({ ...state, prompt }));
71
+
72
+ const sitesPlaceholder = form.sites.length
73
+ ? `Selected ${form.sites.length} Site${form.sites.length > 1 ? "s" : ""}`
74
+ : "Select Sites";
75
+
76
+ const templatesPlaceholder = form.templates.length
77
+ ? `Selected ${form.templates.length} Content Type${form.templates.length > 1 ? "s" : ""}`
78
+ : "Select Content Type";
79
+
80
+ return (
81
+ <S.Wrapper>
82
+ <S.FormWrapper>
83
+ <S.Note>
84
+ <S.Title>
85
+ <S.IconWrapper>
86
+ <Icon name="Ia" size="16" />
87
+ </S.IconWrapper>
88
+ <div>AI distributor</div>
89
+ </S.Title>
90
+ <S.Text>
91
+ Configure the <strong>AI distributor</strong> to tailor content suggestions based on user data. Use these
92
+ settings to refine the type of content displayed and ensure a relevant user experience.
93
+ </S.Text>
94
+ </S.Note>
95
+ <FieldsBehavior
96
+ title="Data Source"
97
+ fieldType="MultiCheckSelect"
98
+ options={data.templates}
99
+ value={form.templates}
100
+ onChange={handleSourceSelect}
101
+ placeholder={templatesPlaceholder}
102
+ helptext="Select one or multiple content types to include in the search."
103
+ mandatory={true}
104
+ floating
105
+ />
106
+ <FieldsBehavior
107
+ title="Site Scope"
108
+ fieldType="MultiCheckSelect"
109
+ options={data.sites}
110
+ value={form.sites}
111
+ onChange={handleSiteSelect}
112
+ placeholder={sitesPlaceholder}
113
+ helptext="Specify the site for the search. By default, the search is limited to the current site."
114
+ mandatory={true}
115
+ floating
116
+ />
117
+ <FieldsBehavior
118
+ fieldType="TextArea"
119
+ title="Custom AI Prompt"
120
+ value={form.prompt}
121
+ onChange={handlePrompt}
122
+ placeholder="Example: Prioritising online programmes"
123
+ helptext="Specify the type of content you want to display. The AI will use this as guidance, but results may vary."
124
+ />
125
+ <FieldsBehavior
126
+ fieldType="NumberField"
127
+ title="Number of results"
128
+ value={form.limit}
129
+ onChange={handleLimit}
130
+ helptext="Set the number of results to display. Keep it concise to improve user experience."
131
+ mandatory={true}
132
+ />
133
+ </S.FormWrapper>
134
+ <S.ActionsWrapper>
135
+ <Button type="button" buttonStyle="line" onClick={handleClear}>
136
+ Clear Data
137
+ </Button>
138
+ <Button type="button" onClick={handleApply}>
139
+ Save Data
140
+ </Button>
141
+ </S.ActionsWrapper>
142
+ </S.Wrapper>
143
+ );
144
+ };
145
+
146
+ interface IAIConfigPanelProps {
147
+ value?: IState | null;
148
+ lang: number;
149
+ site?: number;
150
+ whiteList?: string[];
151
+ onChange: (value: IState) => void;
152
+ }
153
+
154
+ export default AIConfigPanel;
@@ -0,0 +1,57 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div`
4
+ display: flex;
5
+ flex-direction: column;
6
+ justify-content: space-between;
7
+ position: relative;
8
+ height: 100%;
9
+ padding-bottom: ${(p) => `calc(${p.theme.spacing.l} + ${p.theme.spacing.s})`};
10
+ `;
11
+
12
+ const FormWrapper = styled.div`
13
+ position: relative;
14
+ padding: ${(p) => p.theme.spacing.m};
15
+ height: 100%;
16
+ overflow: auto;
17
+ `;
18
+
19
+ const Note = styled.div`
20
+ background-color: ${(p) => p.theme.color.uiBackground03};
21
+ padding: ${(p) => p.theme.spacing.s};
22
+ border-radius: ${(p) => p.theme.radii.s};
23
+ margin-top: ${(p) => p.theme.spacing.xs};
24
+ margin-bottom: ${(p) => p.theme.spacing.m};
25
+ color: ${(p) => p.theme.color.textMediumEmphasis};
26
+ `;
27
+
28
+ const Text = styled.div`
29
+ ${(p) => p.theme.textStyle.uiXS};
30
+ `;
31
+
32
+ const Title = styled.div`
33
+ ${(p) => p.theme.textStyle.fieldLabel};
34
+ display: flex;
35
+ margin-bottom: ${(p) => p.theme.spacing.xs};
36
+ align-items: center;
37
+ `;
38
+
39
+ const IconWrapper = styled.div`
40
+ width: ${(p) => p.theme.spacing.s};
41
+ height: ${(p) => p.theme.spacing.s};
42
+ margin-right: ${(p) => p.theme.spacing.xxs};
43
+ `;
44
+
45
+ const ActionsWrapper = styled.div`
46
+ display: flex;
47
+ padding: ${(p) => `${p.theme.spacing.s} ${p.theme.spacing.m} ${p.theme.spacing.m} ${p.theme.spacing.m}`};
48
+ border-top: 1px solid ${(p) => p.theme.color.uiLine};
49
+ align-items: center;
50
+ justify-content: flex-end;
51
+ gap: ${(p) => p.theme.spacing.s};
52
+ button {
53
+ flex-shrink: 0;
54
+ }
55
+ `;
56
+
57
+ export { Wrapper, FormWrapper, Note, Title, Text, IconWrapper, ActionsWrapper };
@@ -0,0 +1,95 @@
1
+ import React from "react";
2
+ import { FloatingPanel, Icon } from "@ax/components";
3
+ import { useModal } from "@ax/hooks";
4
+ import { ISchemaField, ISite } from "@ax/types";
5
+ import AIConfigPanel from "./AIConfigPanel";
6
+
7
+ import * as S from "./style";
8
+
9
+ const AIReferenceField = (props: IProps): JSX.Element => {
10
+ const { value, onChange, disabled, lang, site, field } = props;
11
+
12
+ const { isOpen, toggleModal } = useModal();
13
+
14
+ const handleChange = (newValue: IState) => {
15
+ onChange(newValue);
16
+ toggleModal();
17
+ };
18
+
19
+ const IconStatus = (props: { type: "success" | "warning" | "close" }) => {
20
+ return (
21
+ <S.IconWrapper color={props.type}>
22
+ <Icon name={props.type} size="16" />
23
+ </S.IconWrapper>
24
+ );
25
+ };
26
+
27
+ return (
28
+ <S.Wrapper>
29
+ <S.Label>AI Distributor</S.Label>
30
+ <S.Content>
31
+ <S.Text>
32
+ Configure the <strong>AI distributor</strong> to tailor content suggestions based on user data. Refine and
33
+ ensure a relevant user experience. Only <strong>published</strong> &#40; <Icon name="active" size="16" />{" "}
34
+ &#41; data is displayed. &lsquo;Pending to publish&rsquo; &#40; <Icon name="uploadPending" size="16" /> &#41;
35
+ data won&apos;t appear until the publishing process is complete.
36
+ </S.Text>
37
+ {value && (
38
+ <S.StatusWrapper>
39
+ <S.Check>
40
+ <IconStatus type={value.templates.length > 0 ? "success" : "close"} />
41
+ <div>Selected Content Type</div>
42
+ </S.Check>
43
+ <S.Check>
44
+ <IconStatus type={value.sites.length > 0 ? "success" : "close"} />
45
+ <div>Selected Sites</div>
46
+ </S.Check>
47
+ <S.Check>
48
+ <IconStatus type={value.prompt.trim().length > 0 ? "success" : "warning"} />
49
+ <div>Custom AI Prompt</div>
50
+ </S.Check>
51
+ <S.Check>
52
+ <IconStatus type={value.limit > 0 ? "success" : "close"} />
53
+ <div>Number of Results</div>
54
+ </S.Check>
55
+ </S.StatusWrapper>
56
+ )}
57
+ <S.ButtonWrapper>
58
+ <S.StyledButton type="button" onClick={toggleModal} icon="Ia" disabled={disabled}>
59
+ {value ? "Edit settings AI distributor" : "Configure AI distributor"}
60
+ </S.StyledButton>
61
+ </S.ButtonWrapper>
62
+ </S.Content>
63
+ <FloatingPanel title="Configure AI Distributor" toggleModal={toggleModal} isOpen={isOpen}>
64
+ {isOpen && (
65
+ <AIConfigPanel
66
+ value={value}
67
+ onChange={handleChange}
68
+ lang={lang}
69
+ site={site?.id}
70
+ whiteList={field.whiteList}
71
+ />
72
+ )}
73
+ </FloatingPanel>
74
+ </S.Wrapper>
75
+ );
76
+ };
77
+
78
+ export interface IState {
79
+ prompt: string;
80
+ templates: string[];
81
+ sites: number[];
82
+ language: number;
83
+ limit: number;
84
+ }
85
+
86
+ interface IProps {
87
+ value?: IState | null;
88
+ onChange: (value: IState) => void;
89
+ disabled?: boolean;
90
+ lang: number;
91
+ site: ISite | null;
92
+ field: ISchemaField;
93
+ }
94
+
95
+ export default AIReferenceField;
@@ -0,0 +1,64 @@
1
+ import React from "react";
2
+ import styled from "styled-components";
3
+ import { Button } from "@ax/components";
4
+
5
+ const Wrapper = styled.div``;
6
+
7
+ const Label = styled.div`
8
+ ${(p) => p.theme.textStyle.headingXXS};
9
+ color: ${(p) => p.theme.colors.textMediumEmphasis};
10
+ padding-bottom: ${(p) => p.theme.spacing.xs};
11
+ border-bottom: 1px solid ${(p) => p.theme.color.uiLine};
12
+ `;
13
+
14
+ const Content = styled.div`
15
+ background-color: ${(p) => p.theme.color.uiBackground03};
16
+ padding: ${(p) => p.theme.spacing.s};
17
+ border-radius: ${(p) => p.theme.radii.s};
18
+ margin-top: ${(p) => p.theme.spacing.xs};
19
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
20
+ svg {
21
+ vertical-align: middle;
22
+ display: inline-block;
23
+ }
24
+ `;
25
+
26
+ const Text = styled.div`
27
+ ${(p) => p.theme.textStyle.uiXS};
28
+ color: ${(p) => p.theme.color.textMediumEmphasis};
29
+ `;
30
+
31
+ const ButtonWrapper = styled.div`
32
+ display: flex;
33
+ justify-content: center;
34
+ margin-top: ${(p) => p.theme.spacing.s};
35
+ `;
36
+
37
+ const StyledButton = styled((props) => <Button {...props} />)`
38
+ width: 100%;
39
+ max-width: 264px;
40
+ `;
41
+
42
+ const StatusWrapper = styled.div`
43
+ ${(p) => p.theme.textStyle.uiS};
44
+ color: ${(p) => p.theme.color.textHighEmphasis};
45
+ margin-top: ${(p) => p.theme.spacing.xs};
46
+ `;
47
+
48
+ const Check = styled.div`
49
+ display: flex;
50
+ align-items: center;
51
+ margin-top: ${(p) => p.theme.spacing.xxs};
52
+ `;
53
+
54
+ const IconWrapper = styled.div<{ color: "success" | "warning" | "close" }>`
55
+ width: ${(p) => p.theme.spacing.s};
56
+ height: ${(p) => p.theme.spacing.s};
57
+ margin-right: ${(p) => p.theme.spacing.xs};
58
+ svg path {
59
+ fill: ${(p) =>
60
+ p.color === "close" ? p.theme.color.error : p.color === "warning" ? p.theme.color.warning : p.theme.color.online};
61
+ }
62
+ `;
63
+
64
+ export { Wrapper, Label, Content, Text, ButtonWrapper, StyledButton, StatusWrapper, Check, IconWrapper };
@@ -4,7 +4,7 @@ const Wrapper = styled.div`
4
4
  position: relative;
5
5
  min-width: calc(${(p) => p.theme.spacing.xl} * 2);
6
6
  max-width: calc(${(p) => p.theme.spacing.xl} * 6);
7
- z-index: 2;
7
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
8
8
  `;
9
9
 
10
10
  const Field = styled.div<{ isOpen: boolean; disabled?: boolean }>`
@@ -17,11 +17,7 @@ const Field = styled.div<{ isOpen: boolean; disabled?: boolean }>`
17
17
  background-color: ${(p) => p.theme.color.interactiveBackground};
18
18
  border: 1px solid
19
19
  ${(p) =>
20
- p.isOpen
21
- ? p.theme.color.interactive01
22
- : p.disabled
23
- ? p.theme.color.interactiveDisabled
24
- : p.theme.color.uiLine};
20
+ p.isOpen ? p.theme.color.interactive01 : p.disabled ? p.theme.color.interactiveDisabled : p.theme.color.uiLine};
25
21
  border-radius: ${(p) => p.theme.radii.s};
26
22
  align-items: center;
27
23
  cursor: pointer;
@@ -50,7 +46,8 @@ const DropDown = styled.div<{ floating?: boolean; isOpen: boolean }>`
50
46
  background-color: ${(p) => p.theme.color.uiBarBackground};
51
47
  border: ${(p) => (p.isOpen ? `1px solid ${p.theme.color.uiLine}` : "none")};
52
48
  border-radius: ${(p) => p.theme.radii.s};
53
- padding: ${(p) => p.isOpen ? `${p.theme.spacing.s} ${p.theme.spacing.xs} ${p.theme.spacing.xs} ${p.theme.spacing.s}` : 0};
49
+ padding: ${(p) =>
50
+ p.isOpen ? `${p.theme.spacing.s} ${p.theme.spacing.xs} ${p.theme.spacing.xs} ${p.theme.spacing.s}` : 0};
54
51
  width: 100%;
55
52
  z-index: 2;
56
53
  height: ${(p) => (p.isOpen ? "100%" : 0)};
@@ -1,3 +1,4 @@
1
+ import AIReferenceField from "./AIReferenceField";
1
2
  import AnalyticsField from "./AnalyticsField";
2
3
  import ArrayFieldGroup from "./ArrayFieldGroup";
3
4
  import AsyncCheckGroup from "./AsyncCheckGroup";
@@ -43,6 +44,7 @@ import Wysiwyg from "./Wysiwyg";
43
44
  import IntegrationsField from "./IntegrationsField";
44
45
 
45
46
  export {
47
+ AIReferenceField,
46
48
  AnalyticsField,
47
49
  ArrayFieldGroup,
48
50
  AsyncCheckGroup,
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AIReferenceField,
2
3
  ArrayFieldGroup,
3
4
  AsyncCheckGroup,
4
5
  AsyncSelect,
@@ -115,6 +116,7 @@ import Toast from "./Toast";
115
116
 
116
117
  export {
117
118
  //Fields
119
+ AIReferenceField,
118
120
  ArrayFieldGroup,
119
121
  AsyncCheckGroup,
120
122
  AsyncSelect,
@@ -74,9 +74,11 @@ const DimensionPanel = (props: IProps): JSX.Element => {
74
74
  },
75
75
  ];
76
76
 
77
- const noteTitle = "Dimensions & Values";
77
+ const noteTitle = type === "dimensionsAndValues" ? "Dimensions & Values" : "Only Dimensions";
78
78
  const noteText =
79
- "Create a dimension and its values. You define the values now and select them on any page later. Your analytics data will be homogeneous avoiding duplicates that might cause inaccurate data results.";
79
+ type === "dimensionsAndValues"
80
+ ? "Create a dimension and its values. You define the values now and select them on any page later. Your analytics data will be homogeneous avoiding duplicates that might cause inaccurate data results."
81
+ : "Create only a dimension. You will define its values on a page later, and will only affect that page. Only use it when you need custom data.";
80
82
 
81
83
  return (
82
84
  <FloatingPanel title={title} toggleModal={toggleModal} isOpen={isOpen}>