@plusscommunities/pluss-feature-builder-web-b 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.
@@ -1,14 +1,12 @@
1
- import React, { useEffect, useMemo, useState } from "react";
1
+ import React, { useEffect, useState } from "react";
2
2
  import { SidebarLayout } from "../components/SidebarLayout.jsx";
3
3
  import {
4
- GenericInput,
5
- Text,
6
- Button,
7
- LoadingState,
8
- SkeletonLoader,
9
- ErrorBoundary,
10
- FeatureBuilderWelcomePopup,
11
- CenteredContainer,
4
+ GenericInput,
5
+ Text,
6
+ Button,
7
+ LoadingState,
8
+ ErrorBoundary,
9
+ FeatureBuilderWelcomePopup,
12
10
  } from "../components";
13
11
  import ToastContainer from "../components/ToastContainer.jsx";
14
12
  import { values } from "../values.config.js";
@@ -19,32 +17,28 @@ import { withRouter } from "react-router-dom";
19
17
 
20
18
  import { useDispatch, useSelector } from "react-redux";
21
19
  import {
22
- setDisplayName,
23
- setIcon,
24
- setInitialValues,
25
- setTitle,
26
- submitForm,
27
- clearFormSubmissionState,
20
+ setDisplayName,
21
+ clearFormSubmissionState,
22
+ setIcon,
23
+ setInitialValues,
24
+ setTitle,
25
+ submitForm,
28
26
  } from "../actions/formActions";
29
27
  import {
30
- selectFormIcon,
31
- selectFormDisplayName,
32
- selectFormTitle,
33
- selectDefinition,
34
- selectDefinitionIsLoading,
35
- selectIsCreateMode,
36
- selectIsEditMode,
37
- selectIsStepValid,
38
- selectStepErrors,
39
- selectWizardMode,
40
- selectFormIsSubmitting,
41
- selectFormSubmitError,
42
- selectFormSubmitSuccess,
28
+ selectFormIcon,
29
+ selectFormDisplayName,
30
+ selectFormTitle,
31
+ selectDefinition,
32
+ selectDefinitionIsLoading,
33
+ selectIsCreateMode,
34
+ selectIsEditMode,
35
+ selectIsStepValid,
36
+ selectStepErrors,
37
+ selectFormIsSubmitting,
38
+ selectFormSubmitError,
39
+ selectFormSubmitSuccess,
43
40
  } from "../selectors/featureBuilderSelectors";
44
- import {
45
- validateAndUpdateStep,
46
- setCurrentStepAndSave,
47
- } from "../actions/wizardActions";
41
+ import { validateAndUpdateStep } from "../actions/wizardActions";
48
42
  import IconSelector from "../components/IconSelector.jsx";
49
43
  import ExampleDisplay from "../components/ExampleDisplay.jsx";
50
44
 
@@ -65,332 +59,331 @@ import ExampleDisplay from "../components/ExampleDisplay.jsx";
65
59
  * <FormOverviewStep history={historyObject} location={locationObject} />
66
60
  */
67
61
  const FormOverviewStepInner = (props) => {
68
- const { history, location } = props;
69
- const dispatch = useDispatch();
70
- const auth = useSelector((state) => state.auth);
71
-
72
- // Get wizard state
73
- const isCreateMode = useSelector(selectIsCreateMode);
74
- const isEditMode = useSelector(selectIsEditMode);
75
- const definition = useSelector(selectDefinition);
76
- const definitionIsLoading = useSelector(selectDefinitionIsLoading);
77
-
78
- // Get form state
79
- const selectedIcon = useSelector(selectFormIcon);
80
- const title = useSelector(selectFormTitle);
81
- const displayName = useSelector(selectFormDisplayName);
82
-
83
- // Get validation state
84
- const isStepValid = useSelector(selectIsStepValid("overview"));
85
- const stepErrors = useSelector(selectStepErrors("overview"));
86
-
87
- // Get submission state
88
- const isSubmitting = useSelector(selectFormIsSubmitting);
89
- const submitError = useSelector(selectFormSubmitError);
90
- const submitSuccess = useSelector(selectFormSubmitSuccess);
91
-
92
- // State for welcome popup (only show in create mode)
93
- const [showWelcomePopup, setShowWelcomePopup] = useState(false);
94
-
95
- // Toast state
96
- const [toasts, setToasts] = React.useState([]);
97
-
98
- // Toast management functions
99
- const addToast = (type, message) => {
100
- const id = Date.now();
101
- setToasts((prev) => [...prev, { id, type, message, isVisible: true }]);
102
- };
103
-
104
- const removeToast = (id) => {
105
- setToasts((prev) => prev.filter((toast) => toast.id !== id));
106
- };
107
-
108
- // Handle redirect after successful save in edit mode
109
- useEffect(() => {
110
- if (submitSuccess && isEditMode && !isSubmitting) {
111
- // Show toast notification
112
- addToast("success", "Changes saved");
113
-
114
- // Clear the success state after a short delay and redirect to overview
115
- const timer = setTimeout(() => {
116
- dispatch(clearFormSubmissionState());
117
- history.push(values.routeFormOverviewStep);
118
- }, 1500); // Show success message for 1.5 seconds before redirect
119
-
120
- return () => clearTimeout(timer);
121
- }
122
- }, [submitSuccess, isEditMode, isSubmitting, dispatch, history]);
123
-
124
- // Show toast notification for general success cases
125
- React.useEffect(() => {
126
- if (submitSuccess) {
127
- if (!isEditMode) {
128
- addToast("success", "Step saved");
129
- }
130
-
131
- // Clear submission state after showing toast (only for non-edit cases)
132
- if (!isEditMode) {
133
- const { clearFormSubmissionState } = require("../actions/formActions");
134
- dispatch(clearFormSubmissionState());
135
- }
136
- }
137
- }, [submitSuccess, isEditMode, dispatch]);
138
-
139
- // Handle submit error
140
- useEffect(() => {
141
- if (submitError) {
142
- addToast("error", "It didn't work. Please try again.");
143
- setTimeout(() => {
144
- window.location.reload();
145
- }, 1000);
146
- }
147
- }, [submitError]);
148
-
149
- // Error boundary handlers
150
- const handleRefresh = () => {
151
- // Refresh current step data
152
- dispatch(validateAndUpdateStep("overview"));
153
- };
154
-
155
- const handleBack = () => {
156
- // Always go to listing screen when going back
157
- history.push(values.routeListingScreen);
158
- };
159
-
160
- useEffect(() => {
161
- // Check if we should populate the form from definition
162
- // Primary condition: edit mode with definition available
163
- // Fallback condition: definition exists regardless of mode (handles timing issues)
164
- const shouldPopulateForm =
165
- definition &&
166
- !definitionIsLoading &&
167
- (isEditMode || (definition && !isCreateMode)); // Fallback: if definition exists and we're not in create mode
168
-
169
- if (shouldPopulateForm) {
170
- dispatch(setInitialValues(definition));
171
-
172
- // In edit mode, trigger validation after setting initial values to enable save button
173
- if (isEditMode) {
174
- setTimeout(() => {
175
- dispatch(validateAndUpdateStep("overview"));
176
- }, 100); // Small delay to ensure state is updated
177
- }
178
- }
179
- }, [definition, definitionIsLoading, isEditMode, isCreateMode, dispatch]);
180
-
181
- // Show welcome popup on component mount for create mode
182
- useEffect(() => {
183
- if (isCreateMode && !definitionIsLoading && !definition) {
184
- setShowWelcomePopup(true);
185
- }
186
- }, [isCreateMode, definitionIsLoading, definition]);
187
-
188
- // Handle welcome popup close
189
- const handleWelcomePopupClose = () => {
190
- setShowWelcomePopup(false);
191
- };
192
-
193
- function handleNext() {
194
- // Validate before proceeding - validation will update Redux state
195
- const validationResult = dispatch(validateAndUpdateStep("overview"));
196
-
197
- // If validation passes, navigate to next step
198
- if (validationResult && validationResult.isValid) {
199
- // Clear form submission state when changing steps
200
- dispatch(clearFormSubmissionState());
201
-
202
- if (isCreateMode) {
203
- history.push(values.routeFormFieldsStep);
204
- } else {
205
- // In edit mode, navigate directly
206
- history.push(values.routeFormFieldsStep);
207
- }
208
- }
209
- // If validation fails, the validation errors will be displayed and user can fix them
210
- }
211
-
212
- function handlePrevious() {
213
- // Clear form submission state when changing steps
214
- dispatch(clearFormSubmissionState());
215
-
216
- // Always go to listing screen after completion
217
- history.push(values.routeListingScreen);
218
- }
219
-
220
- function handleSaveStep() {
221
- // Validate before saving in edit mode
222
- const validationResult = dispatch(validateAndUpdateStep("overview"));
223
-
224
- // If validation passes, save the entire form
225
- if (validationResult && validationResult.isValid) {
226
- dispatch(submitForm());
227
- }
228
- }
229
-
230
- function handleTitleChange(ev) {
231
- dispatch(setTitle(ev.target.value));
232
- }
233
-
234
- function handleSelectIcon(icon) {
235
- dispatch(setIcon(icon));
236
- }
237
-
238
- function handleDisplayNameChange(ev) {
239
- dispatch(setDisplayName(ev.target.value));
240
- }
241
-
242
- // Check for definition management permission
243
- if (
244
- !PlussCore.Session.validateAccess(
245
- auth.site,
246
- values.permissionFeatureBuilderDefinition,
247
- auth,
248
- )
249
- ) {
250
- return (
251
- <div className="hub-wrapperContainer">
252
- <div className="hub-contentWrapper">
253
- <div className={styles.welcomeContainer}>
254
- <div className={styles.welcomeHeader}>
255
- <Text type="h1" className={styles.welcomeTitle}>
256
- Access Restricted
257
- </Text>
258
- <Text type="body" className={styles.welcomeSubtitle}>
259
- You don't have permission to manage feature definitions. Please
260
- contact your administrator if you need access.
261
- </Text>
262
- </div>
263
- </div>
264
- </div>
265
- </div>
266
- );
267
- }
268
-
269
- return (
270
- <ErrorBoundary
271
- title="Unable to load overview"
272
- message="If you continue to experience issues with the overview step, please try refreshing the page or contact support."
273
- onRetry={handleRefresh}
274
- >
275
- <SidebarLayout>
276
- <Text
277
- type="formTitleLarge"
278
- className={`${isEditMode ? styles.editMode : styles.createMode}`}
279
- >
280
- {definitionIsLoading
281
- ? "Loading..."
282
- : isEditMode
283
- ? `Edit Feature: ${capitalizeTextWithFallback(title, "Unnamed")}`
284
- : "Create New Feature"}
285
- </Text>
286
- {definitionIsLoading ? (
287
- <div className={styles.overviewLoadingContainer}>
288
- <LoadingState message="Loading feature..." size={48} />
289
- </div>
290
- ) : (
291
- <>
292
- <GenericInput
293
- label="Feature Name"
294
- help="Name your feature as it should appear in the Community Manager and the app"
295
- isRequired
296
- type="text"
297
- onChange={handleTitleChange}
298
- value={title}
299
- className="mt-8"
300
- showError={() => {
301
- return !isStepValid && stepErrors && stepErrors.title;
302
- }}
303
- errorMessage="Title is required"
304
- isValid={() => {
305
- return title && title.trim().length > 0;
306
- }}
307
- />
308
-
309
- <IconSelector
310
- selectedIcon={selectedIcon}
311
- onIconSelect={handleSelectIcon}
312
- className=""
313
- stepErrors={stepErrors}
314
- showError={() => {
315
- return !isStepValid && stepErrors && stepErrors.icon;
316
- }}
317
- />
318
-
319
- <GenericInput
320
- label="Display Name"
321
- help="Display name for this feature (used in buttons, notifications, and activity feeds)"
322
- isRequired
323
- type="text"
324
- onChange={handleDisplayNameChange}
325
- value={displayName}
326
- className="mt-8"
327
- showError={() => {
328
- return !isStepValid && stepErrors && stepErrors.displayName;
329
- }}
330
- errorMessage="Display name is required"
331
- isValid={() => {
332
- return displayName && displayName.trim().length > 0;
333
- }}
334
- />
335
-
336
- {/* Examples Display */}
337
- <ExampleDisplay displayName={displayName} />
338
- </>
339
- )}
340
-
341
- {/* Top-level validation message - positioned above action buttons */}
342
- {!isStepValid && Object.keys(stepErrors).length > 0 && (
343
- <div
344
- className={styles.validationErrorMessage}
345
- role="alert"
346
- aria-live="polite"
347
- >
348
- {Object.keys(stepErrors).length}{" "}
349
- {Object.keys(stepErrors).length === 1
350
- ? "field needs"
351
- : "fields need"}{" "}
352
- attention before proceeding
353
- </div>
354
- )}
355
-
356
- {/* Mode-aware navigation buttons */}
357
- <div className={styles.navigation}>
358
- {isCreateMode ? (
359
- <Button
360
- buttonType="primary"
361
- isActive
362
- onClick={handleNext}
363
- leftIcon="arrow-right"
364
- >
365
- Next step: Configure Fields
366
- </Button>
367
- ) : (
368
- <Button
369
- buttonType="primary"
370
- isActive
371
- onClick={handleSaveStep}
372
- disabled={!isStepValid || isSubmitting}
373
- leftIcon={isSubmitting ? "sync" : "save"}
374
- loading={isSubmitting}
375
- >
376
- {isSubmitting ? "Saving..." : "Save"}
377
- </Button>
378
- )}
379
- </div>
380
-
381
- {/* Submission feedback */}
382
- </SidebarLayout>
383
-
384
- {/* Welcome popup for create mode */}
385
- <FeatureBuilderWelcomePopup
386
- isOpen={showWelcomePopup}
387
- onClose={handleWelcomePopupClose}
388
- />
389
-
390
- {/* Toast Container for Notifications */}
391
- <ToastContainer toasts={toasts} onDismiss={removeToast} />
392
- </ErrorBoundary>
393
- );
62
+ const { history, location } = props;
63
+ const dispatch = useDispatch();
64
+ const auth = useSelector((state) => state.auth);
65
+
66
+ // Get wizard state
67
+ const isCreateMode = useSelector(selectIsCreateMode);
68
+ const isEditMode = useSelector(selectIsEditMode);
69
+ const definition = useSelector(selectDefinition);
70
+ const definitionIsLoading = useSelector(selectDefinitionIsLoading);
71
+
72
+ // Get form state
73
+ const selectedIcon = useSelector(selectFormIcon);
74
+ const title = useSelector(selectFormTitle);
75
+ const displayName = useSelector(selectFormDisplayName);
76
+
77
+ // Get validation state
78
+ const isStepValid = useSelector(selectIsStepValid("overview"));
79
+ const stepErrors = useSelector(selectStepErrors("overview"));
80
+
81
+ // Get submission state
82
+ const isSubmitting = useSelector(selectFormIsSubmitting);
83
+ const submitError = useSelector(selectFormSubmitError);
84
+ const submitSuccess = useSelector(selectFormSubmitSuccess);
85
+
86
+ // State for welcome popup (only show in create mode)
87
+ const [showWelcomePopup, setShowWelcomePopup] = useState(false);
88
+
89
+ // Toast state
90
+ const [toasts, setToasts] = React.useState([]);
91
+
92
+ // Toast management functions
93
+ const addToast = (type, message) => {
94
+ const id = Date.now();
95
+ setToasts((prev) => [...prev, { id, type, message, isVisible: true }]);
96
+ };
97
+
98
+ const removeToast = (id) => {
99
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
100
+ };
101
+
102
+ // Handle redirect after successful save in edit mode
103
+ useEffect(() => {
104
+ if (submitSuccess && isEditMode && !isSubmitting) {
105
+ // Show toast notification
106
+ addToast("success", "Changes saved");
107
+
108
+ // Clear the success state after a short delay and redirect to overview
109
+ const timer = setTimeout(() => {
110
+ dispatch(clearFormSubmissionState());
111
+ history.push(values.routeFormOverviewStep);
112
+ }, 1500); // Show success message for 1.5 seconds before redirect
113
+
114
+ return () => clearTimeout(timer);
115
+ }
116
+ }, [submitSuccess, isEditMode, isSubmitting, dispatch, history]);
117
+
118
+ // Show toast notification for general success cases
119
+ React.useEffect(() => {
120
+ if (submitSuccess) {
121
+ if (!isEditMode) {
122
+ addToast("success", "Step saved");
123
+ }
124
+
125
+ // Clear submission state after showing toast (only for non-edit cases)
126
+ if (!isEditMode) {
127
+ dispatch(clearFormSubmissionState());
128
+ }
129
+ }
130
+ }, [submitSuccess, isEditMode, dispatch]);
131
+
132
+ // Handle submit error
133
+ useEffect(() => {
134
+ if (submitError) {
135
+ addToast("error", "It didn't work. Please try again.");
136
+ setTimeout(() => {
137
+ window.location.reload();
138
+ }, 1000);
139
+ }
140
+ }, [submitError]);
141
+
142
+ // Error boundary handlers
143
+ const handleRefresh = () => {
144
+ // Refresh current step data
145
+ dispatch(validateAndUpdateStep("overview"));
146
+ };
147
+
148
+ const handleBack = () => {
149
+ // Always go to listing screen when going back
150
+ history.push(values.routeListingScreen);
151
+ };
152
+
153
+ useEffect(() => {
154
+ // Check if we should populate the form from definition
155
+ // Primary condition: edit mode with definition available
156
+ // Fallback condition: definition exists regardless of mode (handles timing issues)
157
+ const shouldPopulateForm =
158
+ definition &&
159
+ !definitionIsLoading &&
160
+ (isEditMode || (definition && !isCreateMode)); // Fallback: if definition exists and we're not in create mode
161
+
162
+ if (shouldPopulateForm) {
163
+ dispatch(setInitialValues(definition));
164
+
165
+ // In edit mode, trigger validation after setting initial values to enable save button
166
+ if (isEditMode) {
167
+ setTimeout(() => {
168
+ dispatch(validateAndUpdateStep("overview"));
169
+ }, 100); // Small delay to ensure state is updated
170
+ }
171
+ }
172
+ }, [definition, definitionIsLoading, isEditMode, isCreateMode, dispatch]);
173
+
174
+ // Show welcome popup on component mount for create mode
175
+ useEffect(() => {
176
+ if (isCreateMode && !definitionIsLoading && !definition) {
177
+ setShowWelcomePopup(true);
178
+ }
179
+ }, [isCreateMode, definitionIsLoading, definition]);
180
+
181
+ // Handle welcome popup close
182
+ const handleWelcomePopupClose = () => {
183
+ setShowWelcomePopup(false);
184
+ };
185
+
186
+ function handleNext() {
187
+ // Validate before proceeding - validation will update Redux state
188
+ const validationResult = dispatch(validateAndUpdateStep("overview"));
189
+
190
+ // If validation passes, navigate to next step
191
+ if (validationResult && validationResult.isValid) {
192
+ // Clear form submission state when changing steps
193
+ dispatch(clearFormSubmissionState());
194
+
195
+ if (isCreateMode) {
196
+ history.push(values.routeFormFieldsStep);
197
+ } else {
198
+ // In edit mode, navigate directly
199
+ history.push(values.routeFormFieldsStep);
200
+ }
201
+ }
202
+ // If validation fails, the validation errors will be displayed and user can fix them
203
+ }
204
+
205
+ function handlePrevious() {
206
+ // Clear form submission state when changing steps
207
+ dispatch(clearFormSubmissionState());
208
+
209
+ // Always go to listing screen after completion
210
+ history.push(values.routeListingScreen);
211
+ }
212
+
213
+ function handleSaveStep() {
214
+ // Validate before saving in edit mode
215
+ const validationResult = dispatch(validateAndUpdateStep("overview"));
216
+
217
+ // If validation passes, save the entire form
218
+ if (validationResult && validationResult.isValid) {
219
+ dispatch(submitForm());
220
+ }
221
+ }
222
+
223
+ function handleTitleChange(ev) {
224
+ dispatch(setTitle(ev.target.value));
225
+ }
226
+
227
+ function handleSelectIcon(icon) {
228
+ dispatch(setIcon(icon));
229
+ }
230
+
231
+ function handleDisplayNameChange(ev) {
232
+ dispatch(setDisplayName(ev.target.value));
233
+ }
234
+
235
+ // Check for definition management permission
236
+ if (
237
+ !PlussCore.Session.validateAccess(
238
+ auth.site,
239
+ values.permissionFeatureBuilderDefinition,
240
+ auth,
241
+ )
242
+ ) {
243
+ return (
244
+ <div className="hub-wrapperContainer">
245
+ <div className="hub-contentWrapper">
246
+ <div className={styles.welcomeContainer}>
247
+ <div className={styles.welcomeHeader}>
248
+ <Text type="h1" className={styles.welcomeTitle}>
249
+ Access Restricted
250
+ </Text>
251
+ <Text type="body" className={styles.welcomeSubtitle}>
252
+ You don't have permission to manage feature definitions. Please
253
+ contact your administrator if you need access.
254
+ </Text>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ );
260
+ }
261
+
262
+ return (
263
+ <ErrorBoundary
264
+ title="Unable to load overview"
265
+ message="If you continue to experience issues with the overview step, please try refreshing the page or contact support."
266
+ onRetry={handleRefresh}
267
+ >
268
+ <SidebarLayout>
269
+ <Text
270
+ type="formTitleLarge"
271
+ className={`${isEditMode ? styles.editMode : styles.createMode}`}
272
+ >
273
+ {definitionIsLoading
274
+ ? "Loading..."
275
+ : isEditMode
276
+ ? `Edit Feature: ${capitalizeTextWithFallback(title, "Unnamed")}`
277
+ : "Create New Feature"}
278
+ </Text>
279
+ {definitionIsLoading ? (
280
+ <div className={styles.overviewLoadingContainer}>
281
+ <LoadingState message="Loading feature..." size={48} />
282
+ </div>
283
+ ) : (
284
+ <>
285
+ <GenericInput
286
+ label="Feature Name"
287
+ help="Name your feature as it should appear in the Community Manager and the app"
288
+ isRequired
289
+ type="text"
290
+ onChange={handleTitleChange}
291
+ value={title}
292
+ className="mt-8"
293
+ showError={() => {
294
+ return !isStepValid && stepErrors && stepErrors.title;
295
+ }}
296
+ errorMessage="Title is required"
297
+ isValid={() => {
298
+ return title && title.trim().length > 0;
299
+ }}
300
+ />
301
+
302
+ <IconSelector
303
+ selectedIcon={selectedIcon}
304
+ onIconSelect={handleSelectIcon}
305
+ className=""
306
+ stepErrors={stepErrors}
307
+ showError={() => {
308
+ return !isStepValid && stepErrors && stepErrors.icon;
309
+ }}
310
+ />
311
+
312
+ <GenericInput
313
+ label="Display Name"
314
+ help="Display name for this feature (used in buttons, notifications, and activity feeds)"
315
+ isRequired
316
+ type="text"
317
+ onChange={handleDisplayNameChange}
318
+ value={displayName}
319
+ className="mt-8"
320
+ showError={() => {
321
+ return !isStepValid && stepErrors && stepErrors.displayName;
322
+ }}
323
+ errorMessage="Display name is required"
324
+ isValid={() => {
325
+ return displayName && displayName.trim().length > 0;
326
+ }}
327
+ />
328
+
329
+ {/* Examples Display */}
330
+ <ExampleDisplay displayName={displayName} />
331
+ </>
332
+ )}
333
+
334
+ {/* Top-level validation message - positioned above action buttons */}
335
+ {!isStepValid && Object.keys(stepErrors).length > 0 && (
336
+ <div
337
+ className={styles.validationErrorMessage}
338
+ role="alert"
339
+ aria-live="polite"
340
+ >
341
+ {Object.keys(stepErrors).length}{" "}
342
+ {Object.keys(stepErrors).length === 1
343
+ ? "field needs"
344
+ : "fields need"}{" "}
345
+ attention before proceeding
346
+ </div>
347
+ )}
348
+
349
+ {/* Mode-aware navigation buttons */}
350
+ <div className={styles.navigation}>
351
+ {isCreateMode ? (
352
+ <Button
353
+ buttonType="primary"
354
+ isActive
355
+ onClick={handleNext}
356
+ leftIcon="arrow-right"
357
+ >
358
+ Next step: Configure Fields
359
+ </Button>
360
+ ) : (
361
+ <Button
362
+ buttonType="primary"
363
+ isActive
364
+ onClick={handleSaveStep}
365
+ disabled={!isStepValid || isSubmitting}
366
+ leftIcon={isSubmitting ? "sync" : "save"}
367
+ loading={isSubmitting}
368
+ >
369
+ {isSubmitting ? "Saving..." : "Save"}
370
+ </Button>
371
+ )}
372
+ </div>
373
+
374
+ {/* Submission feedback */}
375
+ </SidebarLayout>
376
+
377
+ {/* Welcome popup for create mode */}
378
+ <FeatureBuilderWelcomePopup
379
+ isOpen={showWelcomePopup}
380
+ onClose={handleWelcomePopupClose}
381
+ />
382
+
383
+ {/* Toast Container for Notifications */}
384
+ <ToastContainer toasts={toasts} onDismiss={removeToast} />
385
+ </ErrorBoundary>
386
+ );
394
387
  };
395
388
 
396
389
  export const FormOverviewStep = withRouter(FormOverviewStepInner);