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