@plusscommunities/pluss-feature-builder-web-d 1.0.2-beta.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/.babelrc +4 -0
- package/dist/index.cjs.js +7792 -0
- package/package.json +54 -0
- package/rollup.config.js +68 -0
- package/src/actions/featureBuilderStringsActions.js +88 -0
- package/src/actions/featureDefinitionsIndex.js +258 -0
- package/src/actions/formActions.js +311 -0
- package/src/actions/index.js +12 -0
- package/src/actions/listingActions.js +350 -0
- package/src/actions/wizardActions.js +240 -0
- package/src/components/ActivityCardExample.jsx +86 -0
- package/src/components/ActivityCardExample.module.css +130 -0
- package/src/components/BackgroundLoader.jsx +33 -0
- package/src/components/BackgroundLoader.module.css +46 -0
- package/src/components/BaseFieldConfig.jsx +305 -0
- package/src/components/BaseFieldConfig.module.css +42 -0
- package/src/components/CenteredContainer.jsx +29 -0
- package/src/components/CenteredContainer.module.css +171 -0
- package/src/components/DeleteConfirmationPopup.jsx +95 -0
- package/src/components/DeleteConfirmationPopup.module.css +12 -0
- package/src/components/ErrorBoundary.jsx +134 -0
- package/src/components/ErrorBoundary.module.css +77 -0
- package/src/components/ErrorMessage.jsx +85 -0
- package/src/components/ErrorMessage.module.css +116 -0
- package/src/components/ExampleDisplay.jsx +26 -0
- package/src/components/ExampleDisplay.module.css +3 -0
- package/src/components/FeatureBuilderSidebar.jsx +84 -0
- package/src/components/FeatureBuilderSuccessPopup.jsx +55 -0
- package/src/components/FeatureBuilderSuccessPopup.module.css +43 -0
- package/src/components/FeatureBuilderWelcomePopup.jsx +51 -0
- package/src/components/FeatureBuilderWelcomePopup.module.css +21 -0
- package/src/components/FeatureListingCard.jsx +104 -0
- package/src/components/FeatureListingCard.module.css +62 -0
- package/src/components/Fields.jsx +460 -0
- package/src/components/Fields.module.css +159 -0
- package/src/components/IconLoader.jsx +153 -0
- package/src/components/IconLoader.module.css +92 -0
- package/src/components/IconSelector.jsx +112 -0
- package/src/components/IconSelector.module.css +197 -0
- package/src/components/ListingEditor.jsx +406 -0
- package/src/components/ListingEditor.module.css +14 -0
- package/src/components/ListingSuccessPopup.jsx +52 -0
- package/src/components/LoadingScreen.jsx +54 -0
- package/src/components/LoadingScreen.module.css +103 -0
- package/src/components/LoadingState.jsx +40 -0
- package/src/components/LoadingState.module.css +18 -0
- package/src/components/PreviewFull.js +24 -0
- package/src/components/PreviewFull.module.css +11 -0
- package/src/components/PreviewGrid.js +14 -0
- package/src/components/PreviewWidget.js +27 -0
- package/src/components/PreviewWidget.module.css +15 -0
- package/src/components/SidebarLayout.jsx +292 -0
- package/src/components/SidebarLayout.module.css +145 -0
- package/src/components/SkeletonLoader.jsx +128 -0
- package/src/components/SkeletonLoader.module.css +295 -0
- package/src/components/SortButtonGroup.jsx +34 -0
- package/src/components/SortButtonGroup.module.css +51 -0
- package/src/components/ToastContainer.jsx +98 -0
- package/src/components/ToastContainer.module.css +156 -0
- package/src/components/ToggleSwitch.js +40 -0
- package/src/components/ToggleSwitch.module.css +48 -0
- package/src/components/TwoColumnInput.jsx +29 -0
- package/src/components/TwoColumnInput.module.css +32 -0
- package/src/components/ViewFull.js +139 -0
- package/src/components/ViewFull.module.css +71 -0
- package/src/components/ViewWidget.js +62 -0
- package/src/components/ViewWidget.module.css +28 -0
- package/src/components/iconCategories.js +135 -0
- package/src/components/iconImports.js +409 -0
- package/src/components/index.js +61 -0
- package/src/components/listing/FileListItem.jsx +86 -0
- package/src/components/listing/GalleryDisplay.jsx +331 -0
- package/src/components/listing/GalleryDisplay.module.css +309 -0
- package/src/components/listing/ListingCTAInput.jsx +82 -0
- package/src/components/listing/ListingDescriptionInput.jsx +73 -0
- package/src/components/listing/ListingField.jsx +101 -0
- package/src/components/listing/ListingField.module.css +106 -0
- package/src/components/listing/ListingFileInput.jsx +255 -0
- package/src/components/listing/ListingFileInput.module.css +192 -0
- package/src/components/listing/ListingForm.jsx +90 -0
- package/src/components/listing/ListingForm.module.css +38 -0
- package/src/components/listing/ListingGalleryInput.jsx +236 -0
- package/src/components/listing/ListingGalleryInput.module.css +131 -0
- package/src/components/listing/ListingImageInput.jsx +153 -0
- package/src/components/listing/ListingTextInput.jsx +72 -0
- package/src/feature.config.js +130 -0
- package/src/helper/index.js +135 -0
- package/src/hooks/useFeatureDefinitionLoader.js +62 -0
- 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 +38 -0
- package/src/pages/CreateListingPage.jsx +49 -0
- package/src/pages/EditListingPage.jsx +58 -0
- package/src/reducers/featureBuilderReducer.js +744 -0
- package/src/screens/CreateListing.module.css +45 -0
- package/src/screens/Form.module.css +734 -0
- package/src/screens/FormFieldsStep.jsx +689 -0
- package/src/screens/FormLayoutStep.jsx +445 -0
- package/src/screens/FormOverviewStep.jsx +396 -0
- package/src/screens/ListingScreen.jsx +478 -0
- package/src/screens/ListingScreen.module.css +333 -0
- package/src/selectors/featureBuilderSelectors.js +529 -0
- package/src/types/index.js +91 -0
- package/src/utils/textUtils.js +89 -0
- package/src/validators/galleryValidators.js +345 -0
- package/src/values.config.a.js +49 -0
- package/src/values.config.b.js +49 -0
- package/src/values.config.c.js +49 -0
- package/src/values.config.d.js +49 -0
- package/src/values.config.js +49 -0
- package/src/webapi/featureDefinitionActions.js +0 -0
- package/src/webapi/featuresActions.js +90 -0
- package/src/webapi/helper.js +4 -0
- package/src/webapi/index.js +12 -0
- package/src/webapi/listingActions.js +176 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { SidebarLayout } from "../components/SidebarLayout.jsx";
|
|
3
|
+
import { values } from "../values.config.js";
|
|
4
|
+
import { PlussCore } from "../feature.config";
|
|
5
|
+
import styles from "./Form.module.css";
|
|
6
|
+
import {
|
|
7
|
+
Text,
|
|
8
|
+
Button,
|
|
9
|
+
LoadingState,
|
|
10
|
+
SkeletonLoader,
|
|
11
|
+
IconLoader,
|
|
12
|
+
ErrorBoundary,
|
|
13
|
+
CenteredContainer,
|
|
14
|
+
FeatureBuilderSuccessPopup,
|
|
15
|
+
} from "../components";
|
|
16
|
+
import ToastContainer from "../components/ToastContainer.jsx";
|
|
17
|
+
import { useDispatch, useSelector } from "react-redux";
|
|
18
|
+
import {
|
|
19
|
+
setLayoutType,
|
|
20
|
+
setGridLayoutIcon,
|
|
21
|
+
submitForm,
|
|
22
|
+
clearFormSubmissionState,
|
|
23
|
+
setDisplayName,
|
|
24
|
+
setInitialValues,
|
|
25
|
+
} from "../actions/formActions";
|
|
26
|
+
import {
|
|
27
|
+
selectFormLayout,
|
|
28
|
+
selectFormIcon,
|
|
29
|
+
selectIsCreateMode,
|
|
30
|
+
selectIsEditMode,
|
|
31
|
+
selectIsStepValid,
|
|
32
|
+
selectStepErrors,
|
|
33
|
+
selectCurrentStep,
|
|
34
|
+
selectFormIsSubmitting,
|
|
35
|
+
selectFormSubmitError,
|
|
36
|
+
selectFormSubmitSuccess,
|
|
37
|
+
selectFormDisplayName,
|
|
38
|
+
selectFormTitle,
|
|
39
|
+
selectDefinitionId,
|
|
40
|
+
selectFormIsInitial,
|
|
41
|
+
} from "../selectors/featureBuilderSelectors";
|
|
42
|
+
import { useFeatureDefinitionLoader } from "../hooks/useFeatureDefinitionLoader";
|
|
43
|
+
import {
|
|
44
|
+
validateAndUpdateStep,
|
|
45
|
+
setCurrentStepAndSave,
|
|
46
|
+
} from "../actions/wizardActions";
|
|
47
|
+
import { withRouter } from "react-router-dom";
|
|
48
|
+
|
|
49
|
+
const FormLayoutStepInner = (props) => {
|
|
50
|
+
const { history } = props;
|
|
51
|
+
const dispatch = useDispatch();
|
|
52
|
+
const auth = useSelector((state) => state.auth);
|
|
53
|
+
const layout = useSelector(selectFormLayout);
|
|
54
|
+
const displayName = useSelector(selectFormDisplayName);
|
|
55
|
+
const featureFormName = useSelector(selectFormTitle);
|
|
56
|
+
const overviewIcon = useSelector(selectFormIcon);
|
|
57
|
+
const definitionId = useSelector(selectDefinitionId);
|
|
58
|
+
const layoutType = layout?.type || "round";
|
|
59
|
+
// Get wizard state
|
|
60
|
+
const isCreateMode = useSelector(selectIsCreateMode);
|
|
61
|
+
const isEditMode = useSelector(selectIsEditMode);
|
|
62
|
+
|
|
63
|
+
// Use custom hook to handle definition loading
|
|
64
|
+
const { definition, definitionIsLoading, reloadDefinition } =
|
|
65
|
+
useFeatureDefinitionLoader();
|
|
66
|
+
|
|
67
|
+
// Get form initialization state
|
|
68
|
+
const isFormInitial = useSelector(selectFormIsInitial);
|
|
69
|
+
|
|
70
|
+
// Get validation state
|
|
71
|
+
const isStepValid = useSelector(selectIsStepValid("layout"));
|
|
72
|
+
const stepErrors = useSelector(selectStepErrors("layout"));
|
|
73
|
+
|
|
74
|
+
// Get submission state
|
|
75
|
+
const isSubmitting = useSelector(selectFormIsSubmitting);
|
|
76
|
+
const submitError = useSelector(selectFormSubmitError);
|
|
77
|
+
const submitSuccess = useSelector(selectFormSubmitSuccess);
|
|
78
|
+
|
|
79
|
+
// Toast state
|
|
80
|
+
const [toasts, setToasts] = React.useState([]);
|
|
81
|
+
|
|
82
|
+
// Success popup state
|
|
83
|
+
const [showSuccessPopup, setShowSuccessPopup] = React.useState(false);
|
|
84
|
+
|
|
85
|
+
// Toast management functions
|
|
86
|
+
const addToast = (type, message) => {
|
|
87
|
+
const id = Date.now();
|
|
88
|
+
setToasts((prev) => [...prev, { id, type, message, isVisible: true }]);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const removeToast = (id) => {
|
|
92
|
+
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Handle success popup close
|
|
96
|
+
const handleSuccessPopupClose = () => {
|
|
97
|
+
setShowSuccessPopup(false);
|
|
98
|
+
dispatch(clearFormSubmissionState());
|
|
99
|
+
history.push(values.routeFormOverviewStep);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Handle success popup button click
|
|
103
|
+
const handleSuccessPopupButtonClick = () => {
|
|
104
|
+
setShowSuccessPopup(false);
|
|
105
|
+
dispatch(clearFormSubmissionState());
|
|
106
|
+
history.push(values.routeFormOverviewStep);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Handle successful submission with popup and redirect
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (submitSuccess && !isSubmitting) {
|
|
112
|
+
if (isEditMode) {
|
|
113
|
+
addToast("success", "Changes saved");
|
|
114
|
+
dispatch(clearFormSubmissionState());
|
|
115
|
+
} else {
|
|
116
|
+
// In create mode, show success popup
|
|
117
|
+
setShowSuccessPopup(true);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}, [submitSuccess, isEditMode, isSubmitting, dispatch]);
|
|
121
|
+
|
|
122
|
+
// Handle submit error
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (submitError) {
|
|
125
|
+
addToast("error", "It didn't work. Please try again.");
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
window.location.reload();
|
|
128
|
+
}, 1000);
|
|
129
|
+
}
|
|
130
|
+
}, [submitError]);
|
|
131
|
+
|
|
132
|
+
// Error boundary handlers
|
|
133
|
+
const handleRefresh = () => {
|
|
134
|
+
// Refresh current step data
|
|
135
|
+
dispatch(validateAndUpdateStep("layout"));
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const handleBack = () => {
|
|
139
|
+
// Go to overview step
|
|
140
|
+
history.push(values.routeFormOverviewStep);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const layoutOptions = [
|
|
144
|
+
{
|
|
145
|
+
value: "round",
|
|
146
|
+
title: "Round Images",
|
|
147
|
+
description: "Round photos in a grid",
|
|
148
|
+
image: "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
|
+
},
|
|
150
|
+
{
|
|
151
|
+
value: "condensed",
|
|
152
|
+
title: "Compact List",
|
|
153
|
+
description: "Small photos in a list",
|
|
154
|
+
image: "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
|
+
},
|
|
156
|
+
{
|
|
157
|
+
value: "square",
|
|
158
|
+
title: "Square Images",
|
|
159
|
+
description: "Square photos in a grid",
|
|
160
|
+
image: "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
|
+
},
|
|
162
|
+
{
|
|
163
|
+
value: "feature",
|
|
164
|
+
title: "Large Photos",
|
|
165
|
+
description: "Big photos with details",
|
|
166
|
+
image: "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
|
+
},
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
// Set current step when component mounts
|
|
172
|
+
dispatch(setCurrentStepAndSave("layout"));
|
|
173
|
+
}, [dispatch]);
|
|
174
|
+
|
|
175
|
+
// ADD THIS EFFECT: Hydrate form data from definition on refresh
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (definition && !definitionIsLoading && isFormInitial) {
|
|
178
|
+
dispatch(setInitialValues(definition));
|
|
179
|
+
|
|
180
|
+
// In edit mode, trigger validation after setting initial values
|
|
181
|
+
if (isEditMode) {
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
dispatch(validateAndUpdateStep("layout"));
|
|
184
|
+
}, 100);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}, [definition, definitionIsLoading, isFormInitial, isEditMode, dispatch]);
|
|
188
|
+
|
|
189
|
+
// Add effect to handle definition loading and validation in edit mode
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
// In edit mode, trigger validation when definition is available
|
|
192
|
+
// Note: The new effect above handles data population, this handles re-validation
|
|
193
|
+
if (isEditMode && definition && !definitionIsLoading && !isFormInitial) {
|
|
194
|
+
// Only validate if form is NOT initial (meaning it has data)
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
dispatch(validateAndUpdateStep("layout"));
|
|
197
|
+
}, 100);
|
|
198
|
+
}
|
|
199
|
+
}, [definition, definitionIsLoading, isEditMode, isFormInitial, dispatch]);
|
|
200
|
+
|
|
201
|
+
function handleSelectLayout(layoutType) {
|
|
202
|
+
dispatch(setLayoutType(layoutType));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function handleGridIconChange(iconUrl) {
|
|
206
|
+
dispatch(setGridLayoutIcon(iconUrl));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function handleGridIconRemove() {
|
|
210
|
+
// When custom grid icon is removed, set it to undefined to trigger fallback to overview icon
|
|
211
|
+
dispatch(setGridLayoutIcon(undefined));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function handlePrevious() {
|
|
215
|
+
// Clear form submission state when changing steps
|
|
216
|
+
dispatch(clearFormSubmissionState());
|
|
217
|
+
|
|
218
|
+
if (isCreateMode) {
|
|
219
|
+
history.push(values.routeFormFieldsStep);
|
|
220
|
+
} else {
|
|
221
|
+
// In edit mode, go back to fields screen
|
|
222
|
+
history.push(values.routeFormFieldsStep);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function handleNext() {
|
|
227
|
+
// Validate before proceeding
|
|
228
|
+
const validationResult = dispatch(validateAndUpdateStep("layout"));
|
|
229
|
+
|
|
230
|
+
// If validation passes, proceed with submission/navigation
|
|
231
|
+
if (validationResult?.isValid) {
|
|
232
|
+
if (isCreateMode) {
|
|
233
|
+
// In create mode, submit form - success popup will be shown by useEffect
|
|
234
|
+
dispatch(submitForm());
|
|
235
|
+
} else {
|
|
236
|
+
// In edit mode, just save changes
|
|
237
|
+
dispatch(submitForm());
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// If validation fails, validation errors will be displayed
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function handleSaveStep() {
|
|
244
|
+
// Validate before saving in edit mode
|
|
245
|
+
const validationResult = dispatch(validateAndUpdateStep("layout"));
|
|
246
|
+
|
|
247
|
+
// If validation passes, proceed with saving
|
|
248
|
+
if (validationResult?.isValid) {
|
|
249
|
+
dispatch(submitForm());
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check for definition management permission
|
|
254
|
+
if (
|
|
255
|
+
!PlussCore.Session.validateAccess(
|
|
256
|
+
auth.site,
|
|
257
|
+
values.permissionFeatureBuilderDefinition,
|
|
258
|
+
auth,
|
|
259
|
+
)
|
|
260
|
+
) {
|
|
261
|
+
return (
|
|
262
|
+
<div className="hub-wrapperContainer">
|
|
263
|
+
<div className="hub-contentWrapper">
|
|
264
|
+
<div className={styles.welcomeContainer}>
|
|
265
|
+
<div className={styles.welcomeHeader}>
|
|
266
|
+
<Text type="h1" className={styles.welcomeTitle}>
|
|
267
|
+
Access Restricted
|
|
268
|
+
</Text>
|
|
269
|
+
<Text type="body" className={styles.welcomeSubtitle}>
|
|
270
|
+
You don't have permission to manage feature definitions. Please
|
|
271
|
+
contact your administrator if you need access.
|
|
272
|
+
</Text>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<ErrorBoundary
|
|
282
|
+
title="Unable to load layout design"
|
|
283
|
+
message="If you continue to experience issues with the layout design, please try refreshing the page or contact support."
|
|
284
|
+
onRetry={handleRefresh}
|
|
285
|
+
>
|
|
286
|
+
<SidebarLayout>
|
|
287
|
+
<div className={styles.formLayoutHeader}>
|
|
288
|
+
<Text
|
|
289
|
+
type="formTitleLarge"
|
|
290
|
+
className={`${isEditMode ? styles.editMode : styles.createMode}`}
|
|
291
|
+
>
|
|
292
|
+
{isEditMode ? "In-App Design" : "In-App Design"}
|
|
293
|
+
</Text>
|
|
294
|
+
</div>
|
|
295
|
+
<Text type="body" className="paddingBottom-16">
|
|
296
|
+
{isCreateMode
|
|
297
|
+
? "Pick how your feature looks. Choose a layout and add a grid icon if you want."
|
|
298
|
+
: "Change how your feature looks. You can update the layout and grid icon anytime."}
|
|
299
|
+
</Text>
|
|
300
|
+
|
|
301
|
+
{/* Grid Icon Section */}
|
|
302
|
+
<div className={styles.gridIconSection}>
|
|
303
|
+
<Text type="formTitleSmall" className="">
|
|
304
|
+
Grid Icon (Optional)
|
|
305
|
+
</Text>
|
|
306
|
+
<Text type="body" color="#6c757d" className="paddingBottom-16">
|
|
307
|
+
Upload a grid icon for your feature. If you don't upload one, we'll
|
|
308
|
+
use your main icon.
|
|
309
|
+
</Text>
|
|
310
|
+
|
|
311
|
+
<IconLoader
|
|
312
|
+
value={layout?.gridIcon}
|
|
313
|
+
defaultValue={overviewIcon}
|
|
314
|
+
onChange={() => { }} // Disabled
|
|
315
|
+
onRemove={() => { }} // Disabled
|
|
316
|
+
featureId={definitionId || "new"}
|
|
317
|
+
|
|
318
|
+
/>
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
<Text type="help" color="#6c757d" className="marginTop-16">
|
|
322
|
+
We're working on bringing the ability to use custom grid icons
|
|
323
|
+
</Text>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
{/* Layout Selection Section */}
|
|
327
|
+
<div className={styles.layoutSection}>
|
|
328
|
+
<Text type="formTitleSmall">In-App Layout</Text>
|
|
329
|
+
<Text type="body" color="#6c757d" className="paddingBottom-16">
|
|
330
|
+
Select how your feature content will be displayed in the app
|
|
331
|
+
</Text>
|
|
332
|
+
<div className={styles.grid__four}>
|
|
333
|
+
{definitionIsLoading ? (
|
|
334
|
+
<div className={styles.gridIconLoading}>
|
|
335
|
+
<LoadingState message="Loading layout options..." />
|
|
336
|
+
</div>
|
|
337
|
+
) : (
|
|
338
|
+
layoutOptions.map((option) => {
|
|
339
|
+
const hasError =
|
|
340
|
+
!isStepValid && stepErrors && stepErrors.layoutType;
|
|
341
|
+
const isSelected = layoutType === option.value;
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<div
|
|
345
|
+
key={option.value}
|
|
346
|
+
className={`${styles.layoutOption} ${hasError ? styles.hasError : ""
|
|
347
|
+
} ${isSelected ? styles.selected : ""}`}
|
|
348
|
+
onClick={() => handleSelectLayout(option.value)}
|
|
349
|
+
>
|
|
350
|
+
<div className={styles.layoutOptionImage}>
|
|
351
|
+
<img
|
|
352
|
+
src={option.image}
|
|
353
|
+
alt={option.title}
|
|
354
|
+
className={styles.layoutOptionImg}
|
|
355
|
+
/>
|
|
356
|
+
</div>
|
|
357
|
+
<div className={styles.layoutOptionContent}>
|
|
358
|
+
<div className={styles.layoutOptionTitle}>
|
|
359
|
+
{option.title}
|
|
360
|
+
</div>
|
|
361
|
+
<div className={styles.layoutOptionDescription}>
|
|
362
|
+
{option.description}
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
{hasError && (
|
|
366
|
+
<div className={styles.fieldError}>
|
|
367
|
+
<span className={styles.errorIcon}>!</span>
|
|
368
|
+
Layout selection is required
|
|
369
|
+
</div>
|
|
370
|
+
)}
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
})
|
|
374
|
+
)}
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
|
|
378
|
+
{/* Top-level validation message - positioned above action buttons */}
|
|
379
|
+
{!isStepValid && Object.keys(stepErrors).length > 0 && (
|
|
380
|
+
<div
|
|
381
|
+
className={styles.validationErrorMessage}
|
|
382
|
+
role="alert"
|
|
383
|
+
aria-live="polite"
|
|
384
|
+
>
|
|
385
|
+
{Object.keys(stepErrors).length}{" "}
|
|
386
|
+
{Object.keys(stepErrors).length === 1
|
|
387
|
+
? "field needs"
|
|
388
|
+
: "fields need"}{" "}
|
|
389
|
+
attention before proceeding
|
|
390
|
+
</div>
|
|
391
|
+
)}
|
|
392
|
+
|
|
393
|
+
{/* Mode-aware navigation buttons */}
|
|
394
|
+
<div className={styles.navigation}>
|
|
395
|
+
{isCreateMode ? (
|
|
396
|
+
<>
|
|
397
|
+
<Button
|
|
398
|
+
buttonType="secondary"
|
|
399
|
+
isActive
|
|
400
|
+
onClick={handlePrevious}
|
|
401
|
+
leftIcon="arrow-left"
|
|
402
|
+
>
|
|
403
|
+
Previous step: Choose Layout
|
|
404
|
+
</Button>
|
|
405
|
+
<Button
|
|
406
|
+
buttonType="primary"
|
|
407
|
+
isActive
|
|
408
|
+
onClick={handleNext}
|
|
409
|
+
leftIcon="check"
|
|
410
|
+
disabled={isSubmitting}
|
|
411
|
+
loading={isSubmitting}
|
|
412
|
+
>
|
|
413
|
+
{isSubmitting ? "Creating..." : "Complete Feature"}
|
|
414
|
+
</Button>
|
|
415
|
+
</>
|
|
416
|
+
) : (
|
|
417
|
+
<Button
|
|
418
|
+
buttonType="primary"
|
|
419
|
+
isActive
|
|
420
|
+
onClick={handleSaveStep}
|
|
421
|
+
disabled={!isStepValid || isSubmitting}
|
|
422
|
+
leftIcon={isSubmitting ? "sync" : "save"}
|
|
423
|
+
loading={isSubmitting}
|
|
424
|
+
>
|
|
425
|
+
{isSubmitting ? "Saving..." : "Save"}
|
|
426
|
+
</Button>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
</SidebarLayout>
|
|
430
|
+
|
|
431
|
+
{/* Success Popup */}
|
|
432
|
+
<FeatureBuilderSuccessPopup
|
|
433
|
+
isOpen={showSuccessPopup}
|
|
434
|
+
onClose={handleSuccessPopupClose}
|
|
435
|
+
featureName={featureFormName}
|
|
436
|
+
displayName={displayName}
|
|
437
|
+
/>
|
|
438
|
+
|
|
439
|
+
{/* Toast Container for Notifications */}
|
|
440
|
+
<ToastContainer toasts={toasts} onDismiss={removeToast} />
|
|
441
|
+
</ErrorBoundary>
|
|
442
|
+
);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
export const FormLayoutStep = withRouter(FormLayoutStepInner);
|