@plusscommunities/pluss-feature-builder-web-b 1.0.2-beta.0 → 1.0.2-beta.10
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/dist/index.cjs.js +1380 -1337
- package/package.json +1 -1
- package/rollup.config.js +1 -0
- package/src/actions/featureDefinitionsIndex.js +15 -15
- package/src/actions/formActions.js +31 -30
- package/src/actions/listingActions.js +27 -25
- package/src/actions/wizardActions.js +23 -35
- package/src/components/BaseFieldConfig.jsx +234 -234
- package/src/components/IconLoader.jsx +138 -138
- package/src/components/IconSelector.jsx +0 -1
- package/src/components/ListingEditor.jsx +2 -3
- package/src/components/SidebarLayout.jsx +49 -89
- package/src/components/SidebarLayout.module.css +0 -74
- package/src/components/index.js +0 -2
- package/src/components/listing/GalleryDisplay.jsx +0 -1
- package/src/components/listing/ListingFileInput.jsx +98 -80
- package/src/components/listing/ListingFileInput.module.css +1 -4
- package/src/components/listing/ListingGalleryInput.jsx +4 -1
- package/src/components/listing/ListingGalleryInput.module.css +2 -1
- package/src/hooks/useFeatureDefinitionLoader.js +6 -2
- package/src/images/full.png +0 -0
- package/src/images/fullNoTitle.png +0 -0
- package/src/images/previewWidget.png +0 -0
- package/src/images/widget.png +0 -0
- package/src/index.js +6 -6
- package/src/reducers/featureBuilderReducer.js +18 -22
- package/src/screens/Form.module.css +11 -1
- package/src/screens/FormLayoutStep.jsx +422 -424
- package/src/screens/FormOverviewStep.jsx +3 -10
- package/src/screens/ListingScreen.jsx +0 -1
- package/src/selectors/featureBuilderSelectors.js +47 -43
|
@@ -14,27 +14,26 @@ const ListingFileInput = ({
|
|
|
14
14
|
errorMessage,
|
|
15
15
|
disabled = false,
|
|
16
16
|
}) => {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const [inputs, setInputs] = useState([{ id: 0, fileUrl: null }]);
|
|
18
|
+
const nextIdRef = useRef(1);
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// Initialize inputs from value prop - map each existing file to an input
|
|
21
21
|
useEffect(() => {
|
|
22
22
|
if (value) {
|
|
23
|
+
let fileData = [];
|
|
24
|
+
|
|
23
25
|
// Handle multiple files - could be array of URLs or array of file objects
|
|
24
26
|
if (Array.isArray(value)) {
|
|
25
|
-
|
|
27
|
+
fileData = value
|
|
26
28
|
.map((file) => {
|
|
27
29
|
if (typeof file === "string") {
|
|
28
|
-
// Backward compatibility: array of URLs
|
|
29
30
|
const fileName = file.split("/").pop() || "Unknown File";
|
|
30
31
|
return {
|
|
31
32
|
url: file,
|
|
32
|
-
name: fileName.replace(/\.[^/.]+$/, ""),
|
|
33
|
+
name: fileName.replace(/\.[^/.]+$/, ""),
|
|
33
34
|
originalName: fileName,
|
|
34
|
-
uploading: false,
|
|
35
35
|
};
|
|
36
36
|
} else if (file && typeof file === "object" && file.url) {
|
|
37
|
-
// Enhanced structure
|
|
38
37
|
return {
|
|
39
38
|
url: file.url,
|
|
40
39
|
name: file.name || file.originalName || "Unknown File",
|
|
@@ -42,27 +41,22 @@ const ListingFileInput = ({
|
|
|
42
41
|
file.originalName ||
|
|
43
42
|
file.url.split("/").pop() ||
|
|
44
43
|
"Unknown File",
|
|
45
|
-
uploading: false,
|
|
46
44
|
};
|
|
47
45
|
}
|
|
48
46
|
return null;
|
|
49
47
|
})
|
|
50
48
|
.filter(Boolean);
|
|
51
|
-
setInternalFiles(normalizedFiles);
|
|
52
49
|
} else {
|
|
53
|
-
// Handle single file values
|
|
50
|
+
// Handle single file values
|
|
54
51
|
let singleFile = null;
|
|
55
52
|
if (typeof value === "string") {
|
|
56
|
-
// Backward compatibility: single URL
|
|
57
53
|
const fileName = value.split("/").pop() || "Unknown File";
|
|
58
54
|
singleFile = {
|
|
59
55
|
url: value,
|
|
60
56
|
name: fileName.replace(/\.[^/.]+$/, ""),
|
|
61
57
|
originalName: fileName,
|
|
62
|
-
uploading: false,
|
|
63
58
|
};
|
|
64
59
|
} else if (value && typeof value === "object" && value.url) {
|
|
65
|
-
// Enhanced structure - preserve empty name if explicitly set
|
|
66
60
|
singleFile = {
|
|
67
61
|
url: value.url,
|
|
68
62
|
name:
|
|
@@ -73,60 +67,104 @@ const ListingFileInput = ({
|
|
|
73
67
|
value.originalName ||
|
|
74
68
|
value.url.split("/").pop() ||
|
|
75
69
|
"Unknown File",
|
|
76
|
-
uploading: false,
|
|
77
70
|
};
|
|
78
71
|
}
|
|
79
|
-
|
|
72
|
+
fileData = singleFile ? [singleFile] : [];
|
|
80
73
|
}
|
|
74
|
+
|
|
75
|
+
// Create inputs for each existing file
|
|
76
|
+
const existingInputs = fileData.map((file, index) => ({
|
|
77
|
+
id: index,
|
|
78
|
+
fileUrl: file.url,
|
|
79
|
+
name: file.name,
|
|
80
|
+
originalName: file.originalName,
|
|
81
|
+
}));
|
|
82
|
+
// Add one empty input at the end
|
|
83
|
+
nextIdRef.current = fileData.length;
|
|
84
|
+
setInputs([...existingInputs, { id: nextIdRef.current, fileUrl: null }]);
|
|
85
|
+
nextIdRef.current++;
|
|
81
86
|
} else {
|
|
82
|
-
|
|
87
|
+
setInputs([{ id: 0, fileUrl: null }]);
|
|
83
88
|
}
|
|
84
89
|
}, [value]);
|
|
85
90
|
|
|
86
91
|
// Handle new file upload from FileInput component
|
|
87
|
-
const handleFileUpload = (uploadedUrl) => {
|
|
88
|
-
// Handle case where uploadedUrl might be undefined
|
|
92
|
+
const handleFileUpload = (inputId, uploadedUrl) => {
|
|
89
93
|
if (!uploadedUrl) {
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
const fileName = uploadedUrl.split("/").pop() || "Unknown File";
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
|
|
99
|
+
// Find the input that triggered this upload and update it
|
|
100
|
+
setInputs((prevInputs) => {
|
|
101
|
+
const updatedInputs = prevInputs.map((input) => {
|
|
102
|
+
if (input.id === inputId) {
|
|
103
|
+
return {
|
|
104
|
+
...input,
|
|
105
|
+
fileUrl: uploadedUrl,
|
|
106
|
+
name: "",
|
|
107
|
+
originalName: fileName,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return input;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Add a new empty input for the next upload
|
|
114
|
+
updatedInputs.push({ id: nextIdRef.current, fileUrl: null });
|
|
115
|
+
nextIdRef.current++;
|
|
116
|
+
|
|
117
|
+
return updatedInputs;
|
|
118
|
+
});
|
|
105
119
|
};
|
|
106
120
|
|
|
107
121
|
// Handle file name change
|
|
108
122
|
const handleNameChange = (url, newName) => {
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
setInputs((prevInputs) =>
|
|
124
|
+
prevInputs.map((input) =>
|
|
125
|
+
input.fileUrl === url ? { ...input, name: newName } : input,
|
|
126
|
+
),
|
|
111
127
|
);
|
|
112
|
-
setInternalFiles(updatedFiles);
|
|
113
|
-
notifyParent(updatedFiles);
|
|
114
128
|
};
|
|
115
129
|
|
|
116
130
|
// Handle file removal
|
|
117
131
|
const handleFileRemove = (url) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
setInputs((prevInputs) => {
|
|
133
|
+
return prevInputs.map((input) => {
|
|
134
|
+
if (input.fileUrl === url) {
|
|
135
|
+
return { ...input, fileUrl: null };
|
|
136
|
+
}
|
|
137
|
+
return input;
|
|
138
|
+
});
|
|
139
|
+
});
|
|
121
140
|
};
|
|
122
141
|
|
|
123
|
-
//
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
142
|
+
// Get all uploaded files from inputs
|
|
143
|
+
const getUploadedFiles = () => {
|
|
144
|
+
return inputs
|
|
145
|
+
.filter((input) => input.fileUrl !== null)
|
|
146
|
+
.map((input) => ({
|
|
147
|
+
url: input.fileUrl,
|
|
148
|
+
name: input.name,
|
|
149
|
+
originalName: input.originalName,
|
|
150
|
+
}));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Track previous uploaded files to prevent infinite loops
|
|
154
|
+
const prevFilesRef = useRef([]);
|
|
155
|
+
|
|
156
|
+
// Sync with parent when uploaded files actually change
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const files = getUploadedFiles();
|
|
159
|
+
const currentUrls = files.map((f) => f.url).join(",");
|
|
160
|
+
const prevUrls = prevFilesRef.current.map((f) => f.url).join(",");
|
|
161
|
+
|
|
162
|
+
// Only call onChange if the file list actually changed
|
|
163
|
+
if (currentUrls !== prevUrls && onChange) {
|
|
164
|
+
prevFilesRef.current = files;
|
|
127
165
|
onChange(field.id, files);
|
|
128
166
|
}
|
|
129
|
-
};
|
|
167
|
+
}, [inputs]);
|
|
130
168
|
|
|
131
169
|
// Define acceptable file types for documents and common formats
|
|
132
170
|
const acceptTypes = {
|
|
@@ -145,7 +183,11 @@ const ListingFileInput = ({
|
|
|
145
183
|
"image/png": [".png"],
|
|
146
184
|
};
|
|
147
185
|
|
|
148
|
-
//
|
|
186
|
+
// Get the last empty input (the one to show)
|
|
187
|
+
const emptyInput = inputs.find((input) => input.fileUrl === null);
|
|
188
|
+
|
|
189
|
+
// Get uploaded files for display
|
|
190
|
+
const uploadedFiles = getUploadedFiles();
|
|
149
191
|
|
|
150
192
|
return (
|
|
151
193
|
<div className={styles.listingFileInput}>
|
|
@@ -169,25 +211,26 @@ const ListingFileInput = ({
|
|
|
169
211
|
)}
|
|
170
212
|
</Text>
|
|
171
213
|
|
|
172
|
-
{/* Drop zone */}
|
|
214
|
+
{/* Drop zone - always show exactly one empty input */}
|
|
173
215
|
<div
|
|
174
216
|
className={`${styles.listingFileInput__dropZone} ${showError ? styles["listingFileInput__dropZone--error"] : ""}`}
|
|
175
217
|
>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
218
|
+
{emptyInput && (
|
|
219
|
+
<FileInput
|
|
220
|
+
key={emptyInput.id}
|
|
221
|
+
refreshCallback={(url) => handleFileUpload(emptyInput.id, url)}
|
|
222
|
+
hasDefault={null}
|
|
223
|
+
accept={acceptTypes}
|
|
224
|
+
multiple={false}
|
|
225
|
+
disabled={disabled}
|
|
226
|
+
/>
|
|
227
|
+
)}
|
|
185
228
|
</div>
|
|
186
229
|
|
|
187
230
|
{/* File list */}
|
|
188
|
-
{
|
|
231
|
+
{uploadedFiles.length > 0 && (
|
|
189
232
|
<div className={styles.listingFileInput__fileList}>
|
|
190
|
-
{
|
|
233
|
+
{uploadedFiles.map((file) => (
|
|
191
234
|
<FileListItem
|
|
192
235
|
key={file.url}
|
|
193
236
|
file={file}
|
|
@@ -199,31 +242,6 @@ const ListingFileInput = ({
|
|
|
199
242
|
</div>
|
|
200
243
|
)}
|
|
201
244
|
|
|
202
|
-
{/* Add more files button - always show when files exist */}
|
|
203
|
-
{internalFiles.length > 0 && (
|
|
204
|
-
<div>
|
|
205
|
-
<FileInput
|
|
206
|
-
ref={fileInputRef}
|
|
207
|
-
className="imageInputOuter-single"
|
|
208
|
-
refreshCallback={handleFileUpload}
|
|
209
|
-
hasDefault={null}
|
|
210
|
-
accept={acceptTypes}
|
|
211
|
-
multiple={false}
|
|
212
|
-
disabled={disabled}
|
|
213
|
-
customButton={
|
|
214
|
-
<Button
|
|
215
|
-
buttonType="secondary"
|
|
216
|
-
className={styles.listingFileInput__addMoreButton}
|
|
217
|
-
disabled={disabled}
|
|
218
|
-
>
|
|
219
|
-
<FontAwesomeIcon icon={iconImports.plus} />
|
|
220
|
-
Add More Files
|
|
221
|
-
</Button>
|
|
222
|
-
}
|
|
223
|
-
/>
|
|
224
|
-
</div>
|
|
225
|
-
)}
|
|
226
|
-
|
|
227
245
|
{showError && errorMessage && (
|
|
228
246
|
<Text
|
|
229
247
|
type="help"
|
|
@@ -38,9 +38,7 @@
|
|
|
38
38
|
display: flex;
|
|
39
39
|
align-items: center;
|
|
40
40
|
gap: 0.75rem;
|
|
41
|
-
padding: 0.75rem;
|
|
42
41
|
background-color: var(--bg-white);
|
|
43
|
-
margin-bottom: 0.5rem;
|
|
44
42
|
transition: all var(--transition-base) ease-in-out;
|
|
45
43
|
}
|
|
46
44
|
|
|
@@ -50,9 +48,8 @@
|
|
|
50
48
|
|
|
51
49
|
.fileListItem__icon {
|
|
52
50
|
color: var(--text-bluegrey);
|
|
53
|
-
font-size:
|
|
51
|
+
font-size: 2rem;
|
|
54
52
|
flex-shrink: 0;
|
|
55
|
-
width: 1.5rem;
|
|
56
53
|
text-align: center;
|
|
57
54
|
}
|
|
58
55
|
|
|
@@ -186,7 +186,10 @@ const ListingGalleryInput = ({
|
|
|
186
186
|
>
|
|
187
187
|
{images.map((image, index) => {
|
|
188
188
|
return (
|
|
189
|
-
<div
|
|
189
|
+
<div
|
|
190
|
+
key={`image-${index}`}
|
|
191
|
+
className={galleryStyles.imageItem}
|
|
192
|
+
>
|
|
190
193
|
<img
|
|
191
194
|
src={image}
|
|
192
195
|
alt={`Gallery item ${index + 1}`}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/* Gallery container styles */
|
|
4
4
|
.galleryContainer {
|
|
5
5
|
margin-top: 0.75rem;
|
|
6
|
+
max-width: 200px;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
/* Error border wrapper */
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
|
|
21
22
|
.imageGrid {
|
|
22
23
|
display: grid;
|
|
23
|
-
grid-template-columns: repeat(
|
|
24
|
+
grid-template-columns: repeat(4, 160px);
|
|
24
25
|
gap: 1rem;
|
|
25
26
|
margin-top: 0.5rem;
|
|
26
27
|
}
|
|
@@ -3,8 +3,9 @@ import { useDispatch, useSelector } from "react-redux";
|
|
|
3
3
|
import { fetchFeatureDefinitions } from "../actions/featureDefinitionsIndex";
|
|
4
4
|
import {
|
|
5
5
|
selectDefinition,
|
|
6
|
-
selectDefinitionIsLoading,
|
|
7
6
|
selectDefinitionError,
|
|
7
|
+
selectDefinitionIsLoading,
|
|
8
|
+
selectDefinitionMode,
|
|
8
9
|
} from "../selectors/featureBuilderSelectors";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -35,9 +36,12 @@ export const useFeatureDefinitionLoader = (options = {}) => {
|
|
|
35
36
|
const definition = useSelector(selectDefinition);
|
|
36
37
|
const isLoading = useSelector(selectDefinitionIsLoading);
|
|
37
38
|
const error = useSelector(selectDefinitionError);
|
|
39
|
+
const mode = useSelector(selectDefinitionMode);
|
|
38
40
|
|
|
39
41
|
// Determine if we need to load the definition
|
|
40
|
-
|
|
42
|
+
// Only load if: definition is missing AND mode is not yet set (meaning we haven't tried to fetch yet)
|
|
43
|
+
// OR forceReload is true
|
|
44
|
+
const shouldLoadDefinition = (!definition && !mode) || forceReload;
|
|
41
45
|
|
|
42
46
|
// Function to manually reload definition
|
|
43
47
|
const reloadDefinition = () => {
|
package/src/images/full.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/images/widget.png
CHANGED
|
Binary file
|
package/src/index.js
CHANGED
|
@@ -18,12 +18,12 @@ export const Reducers = (() => {
|
|
|
18
18
|
export const Screens = (() => {
|
|
19
19
|
const screens = {};
|
|
20
20
|
|
|
21
|
-
screens[
|
|
22
|
-
screens[
|
|
23
|
-
screens[
|
|
24
|
-
screens[
|
|
25
|
-
screens[
|
|
26
|
-
screens[
|
|
21
|
+
screens[values.screenFormOverviewStep] = FormOverviewStep;
|
|
22
|
+
screens[values.screenFormFieldsStep] = FormFieldsStep;
|
|
23
|
+
screens[values.screenFormLayoutStep] = FormLayoutStep;
|
|
24
|
+
screens[values.screenListingScreen] = ListingScreen;
|
|
25
|
+
screens[values.pageCreateListing] = CreateListingPage;
|
|
26
|
+
screens[values.pageEditListing] = EditListingPage;
|
|
27
27
|
return screens;
|
|
28
28
|
})();
|
|
29
29
|
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
// Import action types from existing files
|
|
25
25
|
import { actionsTypes as formActionTypes } from "../actions/formActions";
|
|
26
|
+
const { SUBMIT_FORM_SUCCESS } = formActionTypes;
|
|
26
27
|
import {
|
|
27
28
|
FETCH_FEATURES_REQUEST,
|
|
28
29
|
FETCH_FEATURES_SUCCESS,
|
|
@@ -86,7 +87,7 @@ const DEFAULT_NEW_FIELD = {
|
|
|
86
87
|
allowCaption: false, // Default to not allowing captions for image fields
|
|
87
88
|
useAsSummary: false, // Default to not using as summary for description fields
|
|
88
89
|
minImages: 1, // Gallery-specific default
|
|
89
|
-
maxImages:
|
|
90
|
+
maxImages: 16, // Gallery-specific default
|
|
90
91
|
maxFileSize: "5MB", // Gallery-specific default
|
|
91
92
|
allowedTypes: [], // Gallery-specific default
|
|
92
93
|
},
|
|
@@ -138,7 +139,7 @@ function createNewField(id, type = "text", existingFields = []) {
|
|
|
138
139
|
baseField.values.label = "Image Gallery";
|
|
139
140
|
baseField.values.helpText = "Upload multiple images to create a gallery";
|
|
140
141
|
baseField.values.minImages = 1;
|
|
141
|
-
baseField.values.maxImages =
|
|
142
|
+
baseField.values.maxImages = 16;
|
|
142
143
|
baseField.values.maxFileSize = "5MB";
|
|
143
144
|
baseField.values.allowedTypes = ["image/jpeg", "image/png", "image/webp"];
|
|
144
145
|
const {
|
|
@@ -278,27 +279,23 @@ const formReducer = (state = INITIAL_FORM_STATE, action) => {
|
|
|
278
279
|
case formActionTypes.SET_INITIAL_VALUES: {
|
|
279
280
|
// The actual definition data is in payload.featureDefinition.definition
|
|
280
281
|
const definitionWrapper =
|
|
281
|
-
payload && payload.featureDefinition
|
|
282
|
-
? payload.featureDefinition
|
|
283
|
-
: payload;
|
|
282
|
+
(payload && payload.featureDefinition) || payload;
|
|
284
283
|
const definition =
|
|
285
|
-
definitionWrapper && definitionWrapper.definition
|
|
286
|
-
|
|
287
|
-
: definitionWrapper;
|
|
284
|
+
(definitionWrapper && definitionWrapper.definition) ||
|
|
285
|
+
definitionWrapper;
|
|
288
286
|
|
|
289
287
|
// Validate and map definition data to form state structure
|
|
290
288
|
const mappedValues = {
|
|
291
|
-
title:
|
|
292
|
-
icon:
|
|
293
|
-
displayName:
|
|
294
|
-
layout:
|
|
289
|
+
title: definition?.title || "",
|
|
290
|
+
icon: definition?.icon || "star",
|
|
291
|
+
displayName: definition?.displayName || "",
|
|
292
|
+
layout: definition?.layout || {
|
|
295
293
|
gridIcon: undefined,
|
|
296
294
|
type: "round",
|
|
297
295
|
},
|
|
298
|
-
fields:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
: state.fields,
|
|
296
|
+
fields: Array.isArray(definition?.fields)
|
|
297
|
+
? definition.fields
|
|
298
|
+
: state.fields,
|
|
302
299
|
};
|
|
303
300
|
|
|
304
301
|
const newState = {
|
|
@@ -518,17 +515,16 @@ const definitionReducer = (state = INITIAL_STATE.definition, action) => {
|
|
|
518
515
|
// Extract from API response for edit mode
|
|
519
516
|
// Handle nested structure: data.featureDefinition.definition
|
|
520
517
|
const featureDefinitionWrapper =
|
|
521
|
-
data && data.featureDefinition
|
|
518
|
+
(data && data.featureDefinition) || data;
|
|
522
519
|
definition =
|
|
523
|
-
featureDefinitionWrapper && featureDefinitionWrapper.definition
|
|
524
|
-
|
|
525
|
-
: featureDefinitionWrapper;
|
|
520
|
+
(featureDefinitionWrapper && featureDefinitionWrapper.definition) ||
|
|
521
|
+
featureDefinitionWrapper;
|
|
526
522
|
definitionId =
|
|
527
523
|
(featureDefinitionWrapper && featureDefinitionWrapper.id) ||
|
|
528
524
|
values.featureId;
|
|
529
525
|
|
|
530
526
|
// Ensure fields array exists and preserves order property
|
|
531
|
-
if (definition
|
|
527
|
+
if (definition?.fields) {
|
|
532
528
|
// Create a new array to ensure we preserve all field properties including order
|
|
533
529
|
definition.fields = definition.fields.map((field) => ({
|
|
534
530
|
...field,
|
|
@@ -566,7 +562,7 @@ const definitionReducer = (state = INITIAL_STATE.definition, action) => {
|
|
|
566
562
|
definition: payload, // Optimistically update with new definition
|
|
567
563
|
mode: "edit", // Switch to edit mode immediately
|
|
568
564
|
},
|
|
569
|
-
id: payload
|
|
565
|
+
id: payload?.id,
|
|
570
566
|
error: null,
|
|
571
567
|
};
|
|
572
568
|
|
|
@@ -426,6 +426,11 @@
|
|
|
426
426
|
cursor: pointer;
|
|
427
427
|
transition: all 0.2s ease;
|
|
428
428
|
background-color: transparent;
|
|
429
|
+
opacity: 0.6;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.layoutOption:hover {
|
|
433
|
+
opacity: 1;
|
|
429
434
|
}
|
|
430
435
|
|
|
431
436
|
.layoutOptionImage {
|
|
@@ -439,10 +444,14 @@
|
|
|
439
444
|
}
|
|
440
445
|
|
|
441
446
|
.layoutOption.selected .layoutOptionImage {
|
|
442
|
-
border-color: var(--
|
|
447
|
+
border-color: var(--colour-purple, #6e79c5);
|
|
443
448
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
444
449
|
}
|
|
445
450
|
|
|
451
|
+
.layoutOption.selected {
|
|
452
|
+
opacity: 1;
|
|
453
|
+
}
|
|
454
|
+
|
|
446
455
|
.layoutOptionImg {
|
|
447
456
|
width: 100%;
|
|
448
457
|
height: 100%;
|
|
@@ -451,6 +460,7 @@
|
|
|
451
460
|
|
|
452
461
|
.layoutOptionContent {
|
|
453
462
|
max-width: 250px;
|
|
463
|
+
margin-bottom: 0.5rem;
|
|
454
464
|
}
|
|
455
465
|
|
|
456
466
|
.layoutOptionTitle {
|