@vonaffenfels/contentful-teasermanager 1.1.69 → 1.2.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@vonaffenfels/contentful-teasermanager",
3
- "version": "1.1.69",
3
+ "version": "1.2.0",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "prepublish": "yarn run build",
@@ -16,7 +16,7 @@
16
16
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
17
17
  "@babel/preset-env": "^7.13.15",
18
18
  "@babel/preset-react": "^7.13.13",
19
- "@contentful/app-sdk": "^3.33.0",
19
+ "@contentful/app-sdk": "^4.29.5",
20
20
  "@contentful/f36-components": "^4.50.2",
21
21
  "@contentful/field-editor-single-line": "^0.14.1",
22
22
  "@contentful/field-editor-test-utils": "^0.11.1",
@@ -57,13 +57,14 @@
57
57
  "@babel/plugin-syntax-dynamic-import": "^7.8.3",
58
58
  "@babel/preset-env": "^7.13.15",
59
59
  "@babel/preset-react": "^7.13.13",
60
- "@contentful/app-sdk": "^3.33.0",
60
+ "@contentful/app-sdk": "^4.29.5",
61
61
  "@contentful/f36-components": "^4.50.2",
62
62
  "@contentful/field-editor-single-line": "^0.14.1",
63
63
  "@contentful/field-editor-test-utils": "^0.11.1",
64
64
  "@contentful/forma-36-fcss": "^0.3.1",
65
65
  "@contentful/forma-36-react-components": "^3.88.3",
66
66
  "@contentful/forma-36-tokens": "^0.10.1",
67
+ "@dnd-kit/core": "^6.3.1",
67
68
  "@svgr/webpack": "^5.5.0",
68
69
  "babel-loader": "^8.2.2",
69
70
  "classnames": "^2.3.2",
@@ -96,10 +97,10 @@
96
97
  "webpack-dev-server": "^4.0.0-beta.2"
97
98
  },
98
99
  "dependencies": {
99
- "@vonaffenfels/slate-editor": "^1.1.69",
100
+ "@vonaffenfels/slate-editor": "^1.2.0",
100
101
  "webpack": "5.88.2"
101
102
  },
102
- "gitHead": "d16994da870bb2bc4798e09f17f938726136683b",
103
+ "gitHead": "fe9b4ce59e9b4dfae924c580f3a1247c180f4d9b",
103
104
  "publishConfig": {
104
105
  "access": "public"
105
106
  }
@@ -0,0 +1,294 @@
1
+ import {
2
+ TextInput,
3
+ Pill,
4
+ SectionHeading,
5
+ Checkbox,
6
+ Button,
7
+ DragHandle,
8
+ } from "@contentful/f36-components";
9
+ import {
10
+ DndContext, useDraggable, useDroppable,
11
+ PointerSensor, useSensor, useSensors,
12
+ } from '@dnd-kit/core';
13
+ import {
14
+ useEffect, useState,
15
+ } from "react";
16
+ import useDebounce from "../../../hooks/useDebounce";
17
+ import {getContentfulClient} from "../../../lib/contentfulClient";
18
+ import {PlusIcon} from "@contentful/f36-icons";
19
+ import {CSS} from "@dnd-kit/utilities";
20
+
21
+ export const LogicEditor = (props) => {
22
+ const [selectedTags, setSelectedTags] = useState([]);
23
+
24
+ useEffect(() => {
25
+ setSelectedTags(props?.logicData?.tags || []);
26
+ }, [props?.logicData?.tags]);
27
+
28
+ const handleDragEnd = (event) => {
29
+ const {
30
+ over,
31
+ active,
32
+ } = event;
33
+
34
+ if (over) {
35
+ const draggedTagIndex = selectedTags.findIndex(tag => tag.id === active.id);
36
+ if (draggedTagIndex !== -1) {
37
+ const updatedTags = [...selectedTags];
38
+ updatedTags[draggedTagIndex] = {
39
+ ...updatedTags[draggedTagIndex],
40
+ type: over.id, // "and" or "or"
41
+ };
42
+ setSelectedTags(updatedTags);
43
+ }
44
+ }
45
+ };
46
+
47
+ return <DndContext onDragEnd={handleDragEnd}>
48
+ <LogicEditorInner {...props} selectedTags={selectedTags} setSelectedTags={setSelectedTags} />
49
+ </DndContext>;
50
+ };
51
+
52
+ const LogicEditorInner = ({
53
+ onSave = () => alert("onSave missing"),
54
+ sdk,
55
+ logicData = {
56
+ timepoints: [7, 12, 18],
57
+ tags: [],
58
+ },
59
+ selectedTags,
60
+ setSelectedTags,
61
+ }) => {
62
+ const contentfulClient = getContentfulClient();
63
+ const [loading, setLoading] = useState(false);
64
+ const [data, setData] = useState([]);
65
+ const [searchQuery, setSearchQuery] = useState("");
66
+ const [addTimepoint, setAddTimepoint] = useState(7);
67
+ const debouncedSearch = useDebounce(searchQuery, 500);
68
+ const [selectedTimepoints, setSelectedTimepoints] = useState([]);
69
+ useEffect(() => {
70
+ setSelectedTimepoints(logicData?.timepoints || []);
71
+ }, [logicData?.timepoints]);
72
+ const {
73
+ setNodeRef: setOrNodeRef,
74
+ isOver: isOrOver,
75
+ node: orNode,
76
+ } = useDroppable({id: 'or'});
77
+ const {
78
+ setNodeRef: setAndNodeRef,
79
+ isOver: isAndOver,
80
+ node: andNode,
81
+ } = useDroppable({id: "and"});
82
+
83
+ const {
84
+ portal,
85
+ locale = "de",
86
+ } = sdk.parameters.invocation;
87
+
88
+ useEffect(() => {
89
+ setLoading(true);
90
+ const params = {
91
+ limit: 100,
92
+ skip: 0,
93
+ content_type: "tags",
94
+ locale,
95
+ order: "-sys.updatedAt",
96
+ "fields.portal": portal,
97
+ select: [
98
+ "fields.title",
99
+ "fields.isCategory",
100
+ "fields.hidden",
101
+ "sys.publishedAt",
102
+ "sys.id",
103
+ ].join(","),
104
+ };
105
+
106
+ if (debouncedSearch) {
107
+ params["fields.title[match]"] = debouncedSearch;
108
+ }
109
+
110
+ contentfulClient.getEntries(params).then((response) => {
111
+ setLoading(false);
112
+ setData(response);
113
+ });
114
+ }, [debouncedSearch, portal, locale]);
115
+
116
+ const andTags = selectedTags.filter((t) => t.type === "and");
117
+ const orTags = selectedTags.filter((t) => t.type === "or");
118
+
119
+ return <div className="flex flex-col gap-2 p-4">
120
+ <div className="sticky top-0 z-10 flex w-full flex-col gap-4 border-b border-gray-200 bg-white py-4">
121
+ <div className="flex w-full flex-row items-center gap-2" style={{minHeight: 36}}>
122
+ <SectionHeading style={{marginBottom: 0}}>Gewählte Zeitpunkte:</SectionHeading>
123
+ <div className="flex flex-row items-center gap-2">
124
+ {selectedTimepoints.map((timepoint) => {
125
+ return <Pill
126
+ key={timepoint}
127
+ draggable={false}
128
+ testId="pill-item"
129
+ variant="active"
130
+ label={`${timepoint} Uhr`}
131
+ onClose={() => {
132
+ setSelectedTimepoints(selectedTimepoints.filter((t) => t !== timepoint));
133
+ }}
134
+ />;
135
+ })}
136
+ <TextInput
137
+ type="number"
138
+ min={1}
139
+ max={24}
140
+ value={addTimepoint}
141
+ onChange={(e) => {
142
+ setAddTimepoint(e.target.value);
143
+ }}
144
+ />
145
+ <SectionHeading style={{marginBottom: 0}}>Uhr</SectionHeading>
146
+ <Button
147
+ variant="secondary"
148
+ onClick={() => {
149
+ if (selectedTimepoints.includes(addTimepoint)) {
150
+ return;
151
+ }
152
+
153
+ setSelectedTimepoints([...selectedTimepoints, addTimepoint]);
154
+ }}
155
+ >
156
+ <PlusIcon />
157
+ </Button>
158
+ </div>
159
+ </div>
160
+ <div className="flex w-full flex-row items-center gap-2" style={{minHeight: 36}}>
161
+ <SectionHeading style={{marginBottom: 0}}>Gewählte Kategorien & Tags:</SectionHeading>
162
+ <div className="grid w-full grid-cols-2 gap-4">
163
+ <div>
164
+ <SectionHeading style={{marginBottom: 0}}>Oder</SectionHeading>
165
+ </div>
166
+ <div>
167
+ <SectionHeading style={{marginBottom: 0}}>Und</SectionHeading>
168
+ </div>
169
+ <div
170
+ ref={setOrNodeRef}
171
+ style={{
172
+ minHeight: 36,
173
+ backgroundColor: isOrOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
174
+ }}
175
+ className="flex w-full flex-row flex-wrap gap-2"
176
+ >
177
+ {orTags.map((tag) => {
178
+ return <DraggablePill
179
+ key={tag.id}
180
+ id={tag.id}
181
+ variant="active"
182
+ label={(`${tag.label}`)}
183
+ onClose={() => {
184
+ setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
185
+ }}
186
+ />;
187
+ })}
188
+ </div>
189
+ <div
190
+ ref={setAndNodeRef}
191
+ style={{
192
+ minHeight: 36,
193
+ backgroundColor: isAndOver ? "rgba(0, 255, 0, 0.3)" : "transparent",
194
+ }}
195
+ className="flex w-full flex-row flex-wrap gap-2"
196
+ >
197
+ {andTags.map((tag) => {
198
+ return <DraggablePill
199
+ key={tag.id}
200
+ id={tag.id}
201
+ testId={tag.id}
202
+ variant="active"
203
+ label={(`${tag.label}`)}
204
+ onClose={() => {
205
+ setSelectedTags(selectedTags.filter((t) => t.id !== tag.id));
206
+ }}
207
+ />;
208
+ })}
209
+ </div>
210
+ </div>
211
+ </div>
212
+ <div className="flex w-full flex-row gap-2">
213
+ <TextInput
214
+ placeholder="Suche nach Kategorie oder Tag"
215
+ value={searchQuery}
216
+ onChange={(e) => setSearchQuery(e.target.value)}
217
+ />
218
+ </div>
219
+ </div>
220
+ <div className="grid grid-cols-3 gap-4">
221
+ {data?.items?.map((tag) => {
222
+ return (
223
+ <Checkbox
224
+ key={tag.sys.id}
225
+ isChecked={!!selectedTags.find((t) => t.id === tag.sys.id)}
226
+ onChange={(e) => {
227
+ if (e.target.checked) {
228
+ setSelectedTags([...selectedTags, {
229
+ label: tag.fields.title[locale],
230
+ id: tag.sys.id,
231
+ type: "or",
232
+ }]);
233
+ } else {
234
+ setSelectedTags(selectedTags.filter((t) => t.id !== tag.sys.id));
235
+ }
236
+ }}
237
+ >
238
+ {tag.fields.title[locale]}
239
+ </Checkbox>
240
+ );
241
+ })}
242
+ </div>
243
+ <div className="sticky bottom-0 flex w-full flex-row justify-between gap-2 border-t border-gray-200 bg-white py-4">
244
+ <Button
245
+ variant="positive"
246
+ type="button"
247
+ onClick={() => onSave({
248
+ tags: selectedTags,
249
+ timepoints: selectedTimepoints,
250
+ })}>Speichern</Button>
251
+ <Button
252
+ variant="negative"
253
+ type="button"
254
+ onClick={() => onSave({
255
+ tags: [],
256
+ timepoints: [],
257
+ })}>Logik löschen</Button>
258
+ </div>
259
+ </div>;
260
+ };
261
+
262
+
263
+ function DraggablePill({
264
+ id, ...props
265
+ }) {
266
+ const {
267
+ attributes,
268
+ listeners,
269
+ setNodeRef,
270
+ transform,
271
+ transition,
272
+ } = useDraggable({id});
273
+ const style = {
274
+ transform: CSS.Translate.toString(transform),
275
+ transition,
276
+ };
277
+
278
+ return (
279
+ <Pill
280
+ dragHandleComponent={
281
+ <DragHandle
282
+ label="Reorder item"
283
+ variant="transparent"
284
+ {...attributes}
285
+ {...listeners}
286
+ />
287
+ }
288
+ isDraggable
289
+ ref={setNodeRef}
290
+ style={style}
291
+ {...props}
292
+ />
293
+ );
294
+ }
@@ -1,7 +1,11 @@
1
- import {EntityList, Pagination, Spinner, Select, Autocomplete, TextInput} from "@contentful/f36-components";
1
+ import {
2
+ EntityList, Pagination, Spinner, Select, Autocomplete, TextInput,
3
+ } from "@contentful/f36-components";
2
4
  import {getContentfulClient} from "../../../lib/contentfulClient";
3
- import {useEffect, useState} from "react";
4
- import format from "date-fns/format"
5
+ import {
6
+ useEffect, useState,
7
+ } from "react";
8
+ import format from "date-fns/format";
5
9
  import useDebounce from "../../../hooks/useDebounce";
6
10
  import classNames from "classnames";
7
11
 
@@ -12,7 +16,10 @@ export const NewestArticles = ({
12
16
  onEntryClick = () => alert("onEntryClick missing"),
13
17
  getArticleThumbnailUrl = (article) => alert("getArticleThumbnailUrl missing"),
14
18
  }) => {
15
- const {portal, slotId} = sdk.parameters.invocation;
19
+ const {
20
+ portal,
21
+ slotId,
22
+ } = sdk.parameters.invocation;
16
23
  const contentfulClient = getContentfulClient();
17
24
  const limit = 25;
18
25
  const [searchQuery, setSearchQuery] = useState("");
@@ -20,7 +27,10 @@ export const NewestArticles = ({
20
27
  const [enabledPortals, setEnabledPortals] = useState([portal]);
21
28
  const [selectedPortal, setSelectedPortal] = useState(portal);
22
29
  const [loading, setLoading] = useState(true);
23
- const [data, setData] = useState({items: [], total: 0});
30
+ const [data, setData] = useState({
31
+ items: [],
32
+ total: 0,
33
+ });
24
34
  const [page, setPage] = useState(0);
25
35
  const [selectedAuthor, setSelectedAuthor] = useState(null);
26
36
  const [authors, setAuthors] = useState([]);
@@ -51,6 +61,16 @@ export const NewestArticles = ({
51
61
  content_type: "article",
52
62
  order: "-fields.teaserDate",
53
63
  "fields.portal": selectedPortal,
64
+ select: [
65
+ "fields.title",
66
+ "fields.authors",
67
+ "fields.teaserDate",
68
+ "fields.image",
69
+ "fields.date",
70
+ "fields.portal",
71
+ "sys.publishedAt",
72
+ "sys.id",
73
+ ].join(","),
54
74
  };
55
75
 
56
76
  if (searchQuery) {
@@ -65,7 +85,7 @@ export const NewestArticles = ({
65
85
  setLoading(false);
66
86
  setData(response);
67
87
  });
68
- }, [debouncedSearch, selectedAuthor,selectedPortal, limit, page]);
88
+ }, [debouncedSearch, selectedAuthor, selectedPortal, limit, page]);
69
89
 
70
90
  useEffect(() => {
71
91
  setAuthorsLoading(true);
@@ -77,7 +97,7 @@ export const NewestArticles = ({
77
97
  };
78
98
 
79
99
  if (debouncedAuthorSearch) {
80
- params["fields.name[match]"] = debouncedAuthorSearch
100
+ params["fields.name[match]"] = debouncedAuthorSearch;
81
101
  } else {
82
102
  setSelectedAuthor(null);
83
103
  }
@@ -97,15 +117,15 @@ export const NewestArticles = ({
97
117
  }, [authors, selectedAuthor]);
98
118
 
99
119
  return (
100
- <div className="p-4 flex flex-col space-y-4">
120
+ <div className="flex flex-col space-y-4 p-4">
101
121
  <div className="flex space-x-2">
102
- <div className="flex-grow">
122
+ <div className="grow">
103
123
  <TextInput
104
124
  width={"full"}
105
125
  placeholder={"Suche nach Titel"}
106
126
  onChange={(e) => setSearchQuery(e.target.value)}/>
107
127
  </div>
108
- <div className="flex-grow-0">
128
+ <div className="grow-0">
109
129
  <Autocomplete
110
130
  isLoading={authorsLoading}
111
131
  onSelectItem={item => setSelectedAuthor(item?.sys?.id)}
@@ -119,11 +139,11 @@ export const NewestArticles = ({
119
139
  noMatchesMessage={'Kein Autor gefunden'}
120
140
  >
121
141
  {(options) => {
122
- return options.map(option => <span key={option.value}>{option.label}</span>)
142
+ return options.map(option => <span key={option.value}>{option.label}</span>);
123
143
  }}
124
144
  </Autocomplete>
125
145
  </div>
126
- <div className="flex-grow-0">
146
+ <div className="grow-0">
127
147
  <Select value={selectedPortal} onChange={e => setSelectedPortal(e.target.value)}>
128
148
  {Object.keys(portals).filter(v => enabledPortals.includes(v)).sort().map(optionValue => <Select.Option
129
149
  key={optionValue}
@@ -135,7 +155,7 @@ export const NewestArticles = ({
135
155
  {loading && <Spinner/>}
136
156
  {!loading && data.items.map((entry) => {
137
157
  const isCurrentlySelectedInOtherSlot = !! Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot !== slotId);
138
- const isCurrentlySelectedInSlot = !!Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot === slotId);
158
+ const isCurrentlySelectedInSlot = !!Object.keys(slotState || {}).find(slot => slotState[slot] === entry.sys.id && slot === slotId);
139
159
 
140
160
  let status = "draft";
141
161
  if (entry.sys.publishedAt) {
@@ -146,7 +166,7 @@ export const NewestArticles = ({
146
166
  if (isCurrentlySelectedInSlot) {
147
167
  description = "Aktuell ausgewählt";
148
168
  } else if (isCurrentlySelectedInOtherSlot) {
149
- description = "Aktuell in anderem Teaser ausgewählt"
169
+ description = "Aktuell in anderem Teaser ausgewählt";
150
170
  }
151
171
 
152
172
  return <div
@@ -165,7 +185,7 @@ export const NewestArticles = ({
165
185
  description={description}
166
186
  status={status}
167
187
  />
168
- </div>
188
+ </div>;
169
189
  })}
170
190
  </EntityList>
171
191
  <Pagination
@@ -176,4 +196,4 @@ export const NewestArticles = ({
176
196
  />
177
197
  </div>
178
198
  );
179
- }
199
+ };
@@ -3,6 +3,7 @@ import React, {
3
3
  } from 'react';
4
4
  import {NewestArticles} from "./Dialog/NewestArticles";
5
5
  import {NoAccess} from "../NoAccess";
6
+ import {LogicEditor} from './Dialog/LogicEditor';
6
7
 
7
8
  const Dialog = ({
8
9
  sdk,
@@ -10,9 +11,20 @@ const Dialog = ({
10
11
  getArticleThumbnailUrl,
11
12
  }) => {
12
13
  const [slotState, setSlotState] = useState({});
14
+ const [logicData, setLogicData] = useState({});
13
15
 
14
16
  const selectEntry = (entry) => {
15
- sdk.close(entry);
17
+ sdk.close({
18
+ entry,
19
+ isArticleResponse: true,
20
+ });
21
+ };
22
+
23
+ const onSave = (data) => {
24
+ sdk.close({
25
+ data,
26
+ isLogicResponse: true,
27
+ });
16
28
  };
17
29
 
18
30
  const loadSlotStateForPage = async () => {
@@ -28,30 +40,41 @@ const Dialog = ({
28
40
  console.error(response.message);
29
41
  }
30
42
 
31
- return response?.data || {};
43
+ return response || {};
32
44
  } catch (e) {
33
45
  console.error(e);
34
46
  }
35
47
  };
36
48
 
37
49
  useEffect(() => {
38
- loadSlotStateForPage().then(setSlotState);
50
+ loadSlotStateForPage().then(({
51
+ data = {},
52
+ logicData = {},
53
+ }) => {
54
+ setSlotState(data);
55
+ setLogicData(logicData[sdk.parameters.invocation.slotId]);
56
+ });
39
57
  }, [sdk.parameters.invocation]);
40
58
 
41
59
  return <div style={{
42
60
  backgroundColor: "#FFFFFF",
43
61
  minHeight: "100vh",
44
62
  }}>
45
- <NewestArticles sdk={sdk} onEntryClick={selectEntry} slotState={slotState} getArticleThumbnailUrl={getArticleThumbnailUrl} portals={portals}/>
46
- {/*
47
- <Tabs defaultTab="newest">
48
- <Tabs.List>
49
- <Tabs.Tab panelId="newest">Neuste Artikel</Tabs.Tab>
50
- </Tabs.List>
51
- <Tabs.Panel id="newest">
52
- </Tabs.Panel>
53
- </Tabs>
54
- */}
63
+ {sdk.parameters.invocation.isLogicEditor ?
64
+ <LogicEditor
65
+ sdk={sdk}
66
+ onSave={onSave}
67
+ logicData={logicData}
68
+ portals={portals}
69
+ /> :
70
+ <NewestArticles
71
+ sdk={sdk}
72
+ onEntryClick={selectEntry}
73
+ slotState={slotState}
74
+ getArticleThumbnailUrl={getArticleThumbnailUrl}
75
+ portals={portals}
76
+ />
77
+ }
55
78
  </div>;
56
79
  };
57
80
 
@@ -42,8 +42,11 @@ const Entry = ({sdk}) => {
42
42
  slotId: slotId,
43
43
  entryId: sdk.entry.getSys().id,
44
44
  },
45
- }).then(async (entry) => {
46
- if (!entry) {
45
+ }).then(async ({
46
+ entry,
47
+ isLogicResponse,
48
+ } = {}) => {
49
+ if (!entry || isLogicResponse) {
47
50
  return;
48
51
  }
49
52
 
@@ -69,12 +72,61 @@ const Entry = ({sdk}) => {
69
72
  });
70
73
  };
71
74
 
75
+ const onSlotClickLogic = (slotId, currentDate) => {
76
+ const entryId = sdk.entry.getSys().id;
77
+ return sdk.dialogs.openCurrentApp({
78
+ title: "Positionslogik bearbeiten",
79
+ width: "fullWidth",
80
+ position: "top",
81
+ allowHeightOverflow: true,
82
+ minHeight: "500px",
83
+ shouldCloseOnOverlayClick: true,
84
+ shouldCloseOnEscapePress: true,
85
+ parameters: {
86
+ portal,
87
+ slotId: slotId,
88
+ entryId: entryId,
89
+ isLogicEditor: true,
90
+ date: currentDate,
91
+ },
92
+ }).then(async ({
93
+ data: {
94
+ timepoints,
95
+ tags,
96
+ } = {},
97
+ isArticleResponse,
98
+ } = {}) => {
99
+ if (!timepoints || !tags || isArticleResponse) {
100
+ return;
101
+ }
102
+
103
+ console.log(`Selected timepoints ${timepoints.map(v => `${v} Uhr`).join(", ")} and tags ${tags.map(v => `${v.label} (${v.id})`).join(", ")} for page ${entryId} with portal ${portal} for slot ${slotId}`);
104
+
105
+ const {apiRoot} = sdk.parameters.instance;
106
+ try {
107
+ await fetch(`${apiRoot}/api/saveLogicForSlot`, {
108
+ method: "POST",
109
+ body: JSON.stringify({
110
+ "project": portal,
111
+ "slot": slotId,
112
+ "page": entryId,
113
+ "timepoints": timepoints,
114
+ "tags": tags,
115
+ }),
116
+ });
117
+ } catch (e) {
118
+ console.error(e);
119
+ alert("Fehler beim speichern!");
120
+ }
121
+ });
122
+ };
123
+
72
124
  const removeSlotData = async (slotId, currentDate) => {
73
125
  console.log(`Removing slot data for page ${sdk.entry.getSys().id} with portal ${portal} for slot ${slotId} and date ${currentDate}`);
74
126
 
75
127
  try {
76
128
  const apiRoot = sdk.parameters.instance.apiRoot;
77
- const response = await fetch(`${apiRoot}/api/saveStateForSlot`, {
129
+ await fetch(`${apiRoot}/api/saveStateForSlot`, {
78
130
  method: "POST",
79
131
  body: JSON.stringify({
80
132
  "project": portal,
@@ -100,7 +152,10 @@ const Entry = ({sdk}) => {
100
152
  console.error(response.message);
101
153
  }
102
154
 
103
- return response?.data || {};
155
+ return response || {
156
+ data: {},
157
+ logicData: {},
158
+ };
104
159
  } catch (e) {
105
160
  console.error(e);
106
161
  }
@@ -133,7 +188,9 @@ const Entry = ({sdk}) => {
133
188
  loadSlotStateForPage={loadSlotStateForPage}
134
189
  removeSlotData={removeSlotData}
135
190
  loadTimelineStateForPage={loadTimelineStateForPage}
136
- onSlotClick={onSlotClick}/>;
191
+ onSlotClick={onSlotClick}
192
+ onSlotClickLogic={onSlotClickLogic}
193
+ />;
137
194
  };
138
195
 
139
196
  export default Entry;