@plusscommunities/pluss-feature-builder-web-a 1.0.2-beta.5 → 1.0.2-beta.7
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 +827 -828
- package/package.json +1 -1
- package/src/actions/featureDefinitionsIndex.js +2 -3
- package/src/actions/formActions.js +148 -158
- package/src/actions/listingActions.js +1 -1
- package/src/actions/wizardActions.js +169 -184
- 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 +4 -1
- 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/index.js +6 -6
- package/src/reducers/featureBuilderReducer.js +14 -25
- package/src/screens/FormLayoutStep.jsx +10 -13
- package/src/screens/FormOverviewStep.jsx +351 -358
- package/src/screens/ListingScreen.jsx +0 -1
- package/src/selectors/featureBuilderSelectors.js +49 -44
|
@@ -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();
|
|
@@ -149,7 +149,10 @@ const SideBarInner = (props) => {
|
|
|
149
149
|
stepsWithUrls.forEach((step, index) => {
|
|
150
150
|
const navItem = Array.from(
|
|
151
151
|
document.querySelectorAll(".hub-wrapperContainer .sideNav-item"),
|
|
152
|
-
).find(
|
|
152
|
+
).find(
|
|
153
|
+
(item) =>
|
|
154
|
+
item.textContent && item.textContent.includes(`${index + 1}.`),
|
|
155
|
+
);
|
|
153
156
|
|
|
154
157
|
if (navItem) {
|
|
155
158
|
// Remove any existing click listeners
|
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}`}
|
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
|
|
|
@@ -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 && payload.featureDefinition) || payload;
|
|
281
|
+
const definition = (definitionWrapper && 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 && data.featureDefinition
|
|
514
|
+
(data && data.featureDefinition) || data;
|
|
522
515
|
definition =
|
|
523
|
-
featureDefinitionWrapper && featureDefinitionWrapper.definition
|
|
524
|
-
|
|
525
|
-
: featureDefinitionWrapper;
|
|
526
|
-
definitionId =
|
|
527
|
-
(featureDefinitionWrapper && featureDefinitionWrapper.id) ||
|
|
528
|
-
values.featureId;
|
|
516
|
+
(featureDefinitionWrapper && featureDefinitionWrapper.definition) || featureDefinitionWrapper;
|
|
517
|
+
definitionId = (featureDefinitionWrapper && 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
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect
|
|
1
|
+
import React, { useEffect } from "react";
|
|
2
2
|
import { SidebarLayout } from "../components/SidebarLayout.jsx";
|
|
3
3
|
import { values } from "../values.config.js";
|
|
4
4
|
import { PlussCore } from "../feature.config";
|
|
@@ -7,10 +7,8 @@ import {
|
|
|
7
7
|
Text,
|
|
8
8
|
Button,
|
|
9
9
|
LoadingState,
|
|
10
|
-
SkeletonLoader,
|
|
11
10
|
IconLoader,
|
|
12
11
|
ErrorBoundary,
|
|
13
|
-
CenteredContainer,
|
|
14
12
|
FeatureBuilderSuccessPopup,
|
|
15
13
|
} from "../components";
|
|
16
14
|
import ToastContainer from "../components/ToastContainer.jsx";
|
|
@@ -20,7 +18,6 @@ import {
|
|
|
20
18
|
setGridLayoutIcon,
|
|
21
19
|
submitForm,
|
|
22
20
|
clearFormSubmissionState,
|
|
23
|
-
setDisplayName,
|
|
24
21
|
setInitialValues,
|
|
25
22
|
} from "../actions/formActions";
|
|
26
23
|
import {
|
|
@@ -30,7 +27,6 @@ import {
|
|
|
30
27
|
selectIsEditMode,
|
|
31
28
|
selectIsStepValid,
|
|
32
29
|
selectStepErrors,
|
|
33
|
-
selectCurrentStep,
|
|
34
30
|
selectFormIsSubmitting,
|
|
35
31
|
selectFormSubmitError,
|
|
36
32
|
selectFormSubmitSuccess,
|
|
@@ -61,8 +57,7 @@ const FormLayoutStepInner = (props) => {
|
|
|
61
57
|
const isEditMode = useSelector(selectIsEditMode);
|
|
62
58
|
|
|
63
59
|
// Use custom hook to handle definition loading
|
|
64
|
-
const { definition, definitionIsLoading
|
|
65
|
-
useFeatureDefinitionLoader();
|
|
60
|
+
const { definition, definitionIsLoading } = useFeatureDefinitionLoader();
|
|
66
61
|
|
|
67
62
|
// Get form initialization state
|
|
68
63
|
const isFormInitial = useSelector(selectFormIsInitial);
|
|
@@ -145,25 +140,29 @@ const FormLayoutStepInner = (props) => {
|
|
|
145
140
|
value: "round",
|
|
146
141
|
title: "Round Images",
|
|
147
142
|
description: "Round photos in a grid",
|
|
148
|
-
image:
|
|
143
|
+
image:
|
|
144
|
+
"https://pluss-prd-uploads.s3.ap-southeast-2.amazonaws.com/uploads/users/ap-southeast-2:b5bebf26-ee4c-c29c-88c8-ec859245e17b/public/b8156f584c92a0edbe13a8e05d/fblayoutround.png",
|
|
149
145
|
},
|
|
150
146
|
{
|
|
151
147
|
value: "condensed",
|
|
152
148
|
title: "Compact List",
|
|
153
149
|
description: "Small photos in a list",
|
|
154
|
-
image:
|
|
150
|
+
image:
|
|
151
|
+
"https://pluss-prd-uploads.s3.ap-southeast-2.amazonaws.com/uploads/users/ap-southeast-2:b5bebf26-ee4c-c29c-88c8-ec859245e17b/public/dfec30d342249a4073e5ffc6b8/fblayoutcompact.png",
|
|
155
152
|
},
|
|
156
153
|
{
|
|
157
154
|
value: "square",
|
|
158
155
|
title: "Square Images",
|
|
159
156
|
description: "Square photos in a grid",
|
|
160
|
-
image:
|
|
157
|
+
image:
|
|
158
|
+
"https://pluss-prd-uploads.s3.ap-southeast-2.amazonaws.com/uploads/users/ap-southeast-2:b5bebf26-ee4c-c29c-88c8-ec859245e17b/public/771e4626462a93041746a746c8/fblayoutsquare.png",
|
|
161
159
|
},
|
|
162
160
|
{
|
|
163
161
|
value: "feature",
|
|
164
162
|
title: "Large Photos",
|
|
165
163
|
description: "Big photos with details",
|
|
166
|
-
image:
|
|
164
|
+
image:
|
|
165
|
+
"https://pluss-prd-uploads.s3.ap-southeast-2.amazonaws.com/uploads/users/ap-southeast-2:b5bebf26-ee4c-c29c-88c8-ec859245e17b/public/f48acc614508ba246186b12845/fblayoutcardslarge.png",
|
|
167
166
|
},
|
|
168
167
|
];
|
|
169
168
|
|
|
@@ -314,10 +313,8 @@ const FormLayoutStepInner = (props) => {
|
|
|
314
313
|
onChange={() => { }} // Disabled
|
|
315
314
|
onRemove={() => { }} // Disabled
|
|
316
315
|
featureId={definitionId || "new"}
|
|
317
|
-
|
|
318
316
|
/>
|
|
319
317
|
|
|
320
|
-
|
|
321
318
|
<Text type="help" color="#6c757d" className="marginTop-16">
|
|
322
319
|
We're working on bringing the ability to use custom grid icons
|
|
323
320
|
</Text>
|