@plusscommunities/pluss-feature-builder-web-a 1.0.2-beta.4 → 1.0.2-beta.6
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 +188 -144
- package/package.json +1 -1
- package/src/actions/featureDefinitionsIndex.js +15 -15
- package/src/actions/formActions.js +148 -158
- package/src/actions/listingActions.js +27 -25
- package/src/actions/wizardActions.js +174 -189
- 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 +0 -1
- package/src/components/SidebarLayout.jsx +47 -25
- package/src/components/SidebarLayout.module.css +0 -34
- package/src/components/index.js +0 -2
- package/src/components/listing/GalleryDisplay.jsx +0 -1
- package/src/components/listing/ListingGalleryInput.jsx +4 -1
- package/src/hooks/useFeatureDefinitionLoader.js +6 -2
- package/src/index.js +6 -6
- package/src/reducers/featureBuilderReducer.js +14 -25
- package/src/screens/Form.module.css +11 -1
- package/src/screens/FormLayoutStep.jsx +10 -13
- package/src/screens/FormOverviewStep.jsx +351 -357
- package/src/screens/ListingScreen.jsx +0 -1
- package/src/selectors/featureBuilderSelectors.js +52 -43
|
@@ -9,145 +9,145 @@ const { Apis } = PlussCore;
|
|
|
9
9
|
const { fileActions } = Apis;
|
|
10
10
|
|
|
11
11
|
const IconLoader = ({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
value,
|
|
13
|
+
defaultValue,
|
|
14
|
+
onChange,
|
|
15
|
+
onRemove,
|
|
16
|
+
disableRemove = false,
|
|
17
|
+
featureId,
|
|
18
18
|
}) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
19
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
20
|
+
const fileInputRef = useRef(null);
|
|
21
|
+
|
|
22
|
+
// Determine which icon to show
|
|
23
|
+
const hasCustomIcon =
|
|
24
|
+
value && typeof value === "string" && value.startsWith("http");
|
|
25
|
+
const iconToShow = defaultValue || "star";
|
|
26
|
+
|
|
27
|
+
// Generate filename with feature ID
|
|
28
|
+
const generateFilename = (file, featureId) => {
|
|
29
|
+
const timestamp = Date.now();
|
|
30
|
+
const ext = file.name.split(".").pop();
|
|
31
|
+
return `build-your-feature-${featureId || "new"}-${timestamp}.${ext}`;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Get FontAwesome icon component directly (no async needed)
|
|
35
|
+
const iconComponent = !hasCustomIcon ? iconImports[iconToShow] : null;
|
|
36
|
+
|
|
37
|
+
const handleFileSelect = async (event) => {
|
|
38
|
+
const file = event.target.files[0];
|
|
39
|
+
if (!file || isUploading) return;
|
|
40
|
+
|
|
41
|
+
// Validate file type - only accept images
|
|
42
|
+
if (!file.type.startsWith("image/")) {
|
|
43
|
+
alert("Please upload an image file (JPEG, PNG, GIF, or WebP)");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setIsUploading(true);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Compress and upload the image
|
|
51
|
+
const compressedFile = await fileActions.compressImage(
|
|
52
|
+
file,
|
|
53
|
+
1400,
|
|
54
|
+
0.8,
|
|
55
|
+
false,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const filename = generateFilename(compressedFile, featureId);
|
|
59
|
+
const url = await fileActions.uploadMediaAsync(compressedFile, filename);
|
|
60
|
+
|
|
61
|
+
if (onChange) {
|
|
62
|
+
onChange(url);
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
alert("Failed to upload image. Please try again.");
|
|
66
|
+
} finally {
|
|
67
|
+
setIsUploading(false);
|
|
68
|
+
// Reset file input
|
|
69
|
+
if (fileInputRef.current) {
|
|
70
|
+
fileInputRef.current.value = "";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleUploadClick = () => {
|
|
76
|
+
if (fileInputRef.current && !isUploading) {
|
|
77
|
+
fileInputRef.current.click();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleRemove = () => {
|
|
82
|
+
if (onRemove && !disableRemove) {
|
|
83
|
+
onRemove();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Container will use CSS modules classes
|
|
88
|
+
// Custom styling through props is deprecated in favor of CSS consistency
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className={`${styles.iconLoader} ${styles.iconLoader__container}`}>
|
|
92
|
+
{/* Show the icon/image */}
|
|
93
|
+
{hasCustomIcon ? (
|
|
94
|
+
<img
|
|
95
|
+
src={value}
|
|
96
|
+
alt="Custom icon"
|
|
97
|
+
className={styles.iconLoader__image}
|
|
98
|
+
/>
|
|
99
|
+
) : (
|
|
100
|
+
<div className={styles.iconLoader__iconContainer}>
|
|
101
|
+
{iconComponent ? (
|
|
102
|
+
<FontAwesomeIcon
|
|
103
|
+
icon={iconComponent}
|
|
104
|
+
className={`${styles.iconLoader__icon} ${styles.iconLoader__iconLarge}`}
|
|
105
|
+
/>
|
|
106
|
+
) : (
|
|
107
|
+
<div className={styles.iconLoader__fallbackText}>{iconToShow}</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{/* Upload/Delete button overlay - only shows on hover */}
|
|
113
|
+
<div className={styles.iconLoader__buttonOverlay}>
|
|
114
|
+
{hasCustomIcon ? (
|
|
115
|
+
<Button
|
|
116
|
+
buttonType="secondary"
|
|
117
|
+
isActive
|
|
118
|
+
onClick={handleRemove}
|
|
119
|
+
disabled={disableRemove || isUploading}
|
|
120
|
+
size="small"
|
|
121
|
+
leftIcon="times"
|
|
122
|
+
aria-label="Delete icon"
|
|
123
|
+
>
|
|
124
|
+
Delete
|
|
125
|
+
</Button>
|
|
126
|
+
) : (
|
|
127
|
+
<Button
|
|
128
|
+
buttonType="primary"
|
|
129
|
+
onClick={() => {}}
|
|
130
|
+
disabled={isUploading}
|
|
131
|
+
size="small"
|
|
132
|
+
leftIcon="upload"
|
|
133
|
+
loading={isUploading}
|
|
134
|
+
aria-label="Upload icon"
|
|
135
|
+
>
|
|
136
|
+
Upload
|
|
137
|
+
</Button>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
{/* Hidden file input */}
|
|
142
|
+
<input
|
|
143
|
+
ref={fileInputRef}
|
|
144
|
+
type="file"
|
|
145
|
+
accept="image/jpeg,image/png,image/gif,image/webp,image/svg"
|
|
146
|
+
onChange={handleFileSelect}
|
|
147
|
+
className={styles.iconLoader__hiddenInput}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
151
|
};
|
|
152
152
|
|
|
153
153
|
export default IconLoader;
|
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
import { fetchFeatureDefinitions } from "../actions/featureDefinitionsIndex.js";
|
|
33
33
|
import { values } from "../values.config.js";
|
|
34
34
|
|
|
35
|
-
|
|
36
35
|
const ListingEditor = ({ mode = "create", listingId, onSuccess, onCancel }) => {
|
|
37
36
|
const isEditMode = mode === "edit";
|
|
38
37
|
const dispatch = useDispatch();
|
|
@@ -48,8 +48,17 @@ const SideBarInner = (props) => {
|
|
|
48
48
|
|
|
49
49
|
// Define step configuration with dynamic URL based on mode
|
|
50
50
|
const getStepUrl = (stepKey) => {
|
|
51
|
-
//
|
|
52
|
-
|
|
51
|
+
// Use routes from values.config to support different variants (-a, -b, -c, -d)
|
|
52
|
+
switch (stepKey) {
|
|
53
|
+
case "overview":
|
|
54
|
+
return values.routeFormOverviewStep;
|
|
55
|
+
case "fields":
|
|
56
|
+
return values.routeFormFieldsStep;
|
|
57
|
+
case "layout":
|
|
58
|
+
return values.routeFormLayoutStep;
|
|
59
|
+
default:
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
53
62
|
};
|
|
54
63
|
|
|
55
64
|
const steps = [
|
|
@@ -75,6 +84,8 @@ const SideBarInner = (props) => {
|
|
|
75
84
|
|
|
76
85
|
// Build sidebar items based on mode
|
|
77
86
|
const buildSidebarItems = () => {
|
|
87
|
+
const isWizardMode = mode === "create" || mode === "edit";
|
|
88
|
+
|
|
78
89
|
return steps.map((step, index) => {
|
|
79
90
|
const isCompleted = selectIsStepComplete(step.key);
|
|
80
91
|
const isAccessible = selectIsStepAccessible(step.key);
|
|
@@ -87,17 +98,19 @@ const SideBarInner = (props) => {
|
|
|
87
98
|
text: stepText,
|
|
88
99
|
icon: step.icon,
|
|
89
100
|
selected: isSelected(step.url),
|
|
90
|
-
onclick:
|
|
91
|
-
?
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
101
|
+
onclick: isWizardMode
|
|
102
|
+
? null
|
|
103
|
+
: isAccessible
|
|
104
|
+
? () => {
|
|
105
|
+
goTo(step.url);
|
|
106
|
+
dispatch(goToStep(step.key));
|
|
107
|
+
}
|
|
108
|
+
: null,
|
|
96
109
|
isFontAwesome: true,
|
|
97
110
|
// Enhanced completion indicator
|
|
98
111
|
completed: isCompleted,
|
|
99
|
-
//
|
|
100
|
-
disabled: mode === "create" && !isAccessible,
|
|
112
|
+
// Disable all navigation in wizard mode
|
|
113
|
+
disabled: isWizardMode || (mode === "create" && !isAccessible),
|
|
101
114
|
};
|
|
102
115
|
|
|
103
116
|
return itemProps;
|
|
@@ -124,6 +137,8 @@ const SideBarInner = (props) => {
|
|
|
124
137
|
|
|
125
138
|
// Add effect to manually attach click handlers since HubSidebar might not use onclick properly
|
|
126
139
|
useEffect(() => {
|
|
140
|
+
const isWizardMode = mode === "create" || mode === "edit";
|
|
141
|
+
|
|
127
142
|
const attachClickHandlers = () => {
|
|
128
143
|
const stepsWithUrls = [
|
|
129
144
|
{ key: "overview", url: getStepUrl("overview") },
|
|
@@ -131,19 +146,12 @@ const SideBarInner = (props) => {
|
|
|
131
146
|
{ key: "layout", url: getStepUrl("layout") },
|
|
132
147
|
];
|
|
133
148
|
|
|
134
|
-
stepsWithUrls.forEach((step) => {
|
|
149
|
+
stepsWithUrls.forEach((step, index) => {
|
|
135
150
|
const navItem = Array.from(
|
|
136
|
-
document.querySelectorAll(".sideNav-item"),
|
|
151
|
+
document.querySelectorAll(".hub-wrapperContainer .sideNav-item"),
|
|
137
152
|
).find(
|
|
138
153
|
(item) =>
|
|
139
|
-
item.textContent &&
|
|
140
|
-
item.textContent.includes(
|
|
141
|
-
step.key === "overview"
|
|
142
|
-
? "Feature Overview"
|
|
143
|
-
: step.key === "fields"
|
|
144
|
-
? "Configure Fields"
|
|
145
|
-
: "Choose Layout",
|
|
146
|
-
),
|
|
154
|
+
item.textContent && item.textContent.includes(`${index + 1}.`),
|
|
147
155
|
);
|
|
148
156
|
|
|
149
157
|
if (navItem) {
|
|
@@ -162,8 +170,12 @@ const SideBarInner = (props) => {
|
|
|
162
170
|
},
|
|
163
171
|
});
|
|
164
172
|
|
|
165
|
-
//
|
|
166
|
-
|
|
173
|
+
// Check if this is the current step (selected)
|
|
174
|
+
const isCurrentStep = isSelected(step.url);
|
|
175
|
+
|
|
176
|
+
// In wizard mode, don't attach any click handlers
|
|
177
|
+
// Only attach click handler if not in wizard mode AND accessible
|
|
178
|
+
if (!isWizardMode && isAccessible) {
|
|
167
179
|
navItem.onclick = (event) => {
|
|
168
180
|
event.preventDefault();
|
|
169
181
|
event.stopPropagation();
|
|
@@ -173,9 +185,19 @@ const SideBarInner = (props) => {
|
|
|
173
185
|
}
|
|
174
186
|
|
|
175
187
|
// Make sure it's styled appropriately
|
|
176
|
-
|
|
177
|
-
navItem.style.pointerEvents = "auto";
|
|
178
|
-
|
|
188
|
+
// In wizard mode: block clicks, but keep current step visible
|
|
189
|
+
navItem.style.pointerEvents = isWizardMode ? "none" : "auto";
|
|
190
|
+
|
|
191
|
+
if (isWizardMode) {
|
|
192
|
+
// Current step: full opacity, not-allowed cursor
|
|
193
|
+
// Other steps: reduced opacity
|
|
194
|
+
navItem.style.opacity = isCurrentStep ? "1" : "0.4";
|
|
195
|
+
navItem.style.cursor = "not-allowed";
|
|
196
|
+
} else {
|
|
197
|
+
// Not in wizard mode: normal styling based on accessibility
|
|
198
|
+
navItem.style.cursor = isAccessible ? "pointer" : "not-allowed";
|
|
199
|
+
navItem.style.opacity = isAccessible ? "1" : "0.5";
|
|
200
|
+
}
|
|
179
201
|
}
|
|
180
202
|
});
|
|
181
203
|
};
|
|
@@ -58,40 +58,6 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/* Enhanced sidebar navigation for progress indication */
|
|
61
|
-
:global(.hub-sideBar) {
|
|
62
|
-
/* Make sidebar more prominent during creation mode */
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
:global(.sideNav-item.isCurrent) {
|
|
66
|
-
background: var(--colour-purple, #6e79c5) !important;
|
|
67
|
-
color: var(--colour-white, #ffffff) !important;
|
|
68
|
-
font-weight: 600 !important;
|
|
69
|
-
border-left: 4px solid var(--colour-branding-dark, #364196) !important;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
:global(.sideNav-item.isCurrent) i {
|
|
73
|
-
color: var(--colour-white, #ffffff) !important;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
:global(.sideNav-item.isCompleted) {
|
|
77
|
-
color: var(--colour-branding-dark, #364196) !important;
|
|
78
|
-
position: relative;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
:global(.sideNav-item.isCompleted::after) {
|
|
82
|
-
content: "✓";
|
|
83
|
-
position: absolute;
|
|
84
|
-
right: 1rem;
|
|
85
|
-
top: 50%;
|
|
86
|
-
transform: translateY(-50%);
|
|
87
|
-
color: var(--colour-branding-dark, #364196);
|
|
88
|
-
font-weight: bold;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
:global(.sideNav-item.isDisabled) {
|
|
92
|
-
opacity: 0.4;
|
|
93
|
-
cursor: not-allowed;
|
|
94
|
-
}
|
|
95
61
|
|
|
96
62
|
/* Enhanced progress section */
|
|
97
63
|
:global(.hub-sideBar-section) {
|
package/src/components/index.js
CHANGED
|
@@ -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,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/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["FormOverviewStep"] = FormOverviewStep;
|
|
22
|
+
screens["FormFieldsStep"] = FormFieldsStep;
|
|
23
|
+
screens["FormLayoutStep"] = FormLayoutStep;
|
|
24
|
+
screens["ListingScreen"] = ListingScreen;
|
|
25
|
+
screens["CreateListingPage"] = CreateListingPage;
|
|
26
|
+
screens["EditListingPage"] = EditListingPage;
|
|
27
27
|
return screens;
|
|
28
28
|
})();
|
|
29
29
|
|
|
@@ -277,28 +277,21 @@ const formReducer = (state = INITIAL_FORM_STATE, action) => {
|
|
|
277
277
|
switch (type) {
|
|
278
278
|
case formActionTypes.SET_INITIAL_VALUES: {
|
|
279
279
|
// The actual definition data is in payload.featureDefinition.definition
|
|
280
|
-
const definitionWrapper =
|
|
281
|
-
|
|
282
|
-
? payload.featureDefinition
|
|
283
|
-
: payload;
|
|
284
|
-
const definition =
|
|
285
|
-
definitionWrapper && definitionWrapper.definition
|
|
286
|
-
? definitionWrapper.definition
|
|
287
|
-
: definitionWrapper;
|
|
280
|
+
const definitionWrapper = payload?.featureDefinition ?? payload;
|
|
281
|
+
const definition = definitionWrapper?.definition ?? definitionWrapper;
|
|
288
282
|
|
|
289
283
|
// Validate and map definition data to form state structure
|
|
290
284
|
const mappedValues = {
|
|
291
|
-
title:
|
|
292
|
-
icon:
|
|
293
|
-
displayName:
|
|
294
|
-
layout:
|
|
285
|
+
title: definition?.title || "",
|
|
286
|
+
icon: definition?.icon || "star",
|
|
287
|
+
displayName: definition?.displayName || "",
|
|
288
|
+
layout: definition?.layout || {
|
|
295
289
|
gridIcon: undefined,
|
|
296
290
|
type: "round",
|
|
297
291
|
},
|
|
298
|
-
fields:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
: state.fields,
|
|
292
|
+
fields: Array.isArray(definition?.fields)
|
|
293
|
+
? definition.fields
|
|
294
|
+
: state.fields,
|
|
302
295
|
};
|
|
303
296
|
|
|
304
297
|
const newState = {
|
|
@@ -518,17 +511,13 @@ const definitionReducer = (state = INITIAL_STATE.definition, action) => {
|
|
|
518
511
|
// Extract from API response for edit mode
|
|
519
512
|
// Handle nested structure: data.featureDefinition.definition
|
|
520
513
|
const featureDefinitionWrapper =
|
|
521
|
-
data
|
|
514
|
+
data?.featureDefinition ?? data;
|
|
522
515
|
definition =
|
|
523
|
-
featureDefinitionWrapper
|
|
524
|
-
|
|
525
|
-
: featureDefinitionWrapper;
|
|
526
|
-
definitionId =
|
|
527
|
-
(featureDefinitionWrapper && featureDefinitionWrapper.id) ||
|
|
528
|
-
values.featureId;
|
|
516
|
+
featureDefinitionWrapper?.definition ?? featureDefinitionWrapper;
|
|
517
|
+
definitionId = featureDefinitionWrapper?.id ?? values.featureId;
|
|
529
518
|
|
|
530
519
|
// Ensure fields array exists and preserves order property
|
|
531
|
-
if (definition
|
|
520
|
+
if (definition?.fields) {
|
|
532
521
|
// Create a new array to ensure we preserve all field properties including order
|
|
533
522
|
definition.fields = definition.fields.map((field) => ({
|
|
534
523
|
...field,
|
|
@@ -566,7 +555,7 @@ const definitionReducer = (state = INITIAL_STATE.definition, action) => {
|
|
|
566
555
|
definition: payload, // Optimistically update with new definition
|
|
567
556
|
mode: "edit", // Switch to edit mode immediately
|
|
568
557
|
},
|
|
569
|
-
id: payload
|
|
558
|
+
id: payload?.id,
|
|
570
559
|
error: null,
|
|
571
560
|
};
|
|
572
561
|
|
|
@@ -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 {
|