@plusscommunities/pluss-feature-builder-web-d 1.0.7 → 1.0.9-beta.3
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 → index.js} +3803 -3504
- package/dist/index.js.map +1 -0
- package/package.json +20 -27
- package/.babelrc +0 -4
- package/rollup.config.js +0 -69
- package/src/actions/featureBuilderStringsActions.js +0 -88
- package/src/actions/featureDefinitionsIndex.js +0 -258
- package/src/actions/formActions.js +0 -301
- package/src/actions/index.js +0 -12
- package/src/actions/listingActions.js +0 -352
- package/src/actions/wizardActions.js +0 -228
- package/src/components/ActivityCardExample.jsx +0 -86
- package/src/components/ActivityCardExample.module.css +0 -130
- package/src/components/BackgroundLoader.jsx +0 -33
- package/src/components/BackgroundLoader.module.css +0 -46
- package/src/components/BaseFieldConfig.jsx +0 -305
- package/src/components/BaseFieldConfig.module.css +0 -42
- package/src/components/CenteredContainer.jsx +0 -29
- package/src/components/CenteredContainer.module.css +0 -171
- package/src/components/DeleteConfirmationPopup.jsx +0 -95
- package/src/components/DeleteConfirmationPopup.module.css +0 -12
- package/src/components/ErrorBoundary.jsx +0 -134
- package/src/components/ErrorBoundary.module.css +0 -77
- package/src/components/ErrorMessage.jsx +0 -85
- package/src/components/ErrorMessage.module.css +0 -116
- package/src/components/ExampleDisplay.jsx +0 -26
- package/src/components/ExampleDisplay.module.css +0 -3
- package/src/components/FeatureBuilderSidebar.jsx +0 -84
- package/src/components/FeatureBuilderSuccessPopup.jsx +0 -49
- package/src/components/FeatureBuilderSuccessPopup.module.css +0 -41
- package/src/components/FeatureBuilderWelcomePopup.jsx +0 -51
- package/src/components/FeatureBuilderWelcomePopup.module.css +0 -21
- package/src/components/FeatureListingCard.jsx +0 -104
- package/src/components/FeatureListingCard.module.css +0 -62
- package/src/components/Fields.jsx +0 -423
- package/src/components/Fields.module.css +0 -159
- package/src/components/IconLoader.jsx +0 -153
- package/src/components/IconLoader.module.css +0 -92
- package/src/components/IconSelector.jsx +0 -111
- package/src/components/IconSelector.module.css +0 -197
- package/src/components/ListingEditor.jsx +0 -405
- package/src/components/ListingEditor.module.css +0 -14
- package/src/components/ListingSuccessPopup.jsx +0 -52
- package/src/components/LoadingScreen.jsx +0 -54
- package/src/components/LoadingScreen.module.css +0 -103
- package/src/components/LoadingState.jsx +0 -40
- package/src/components/LoadingState.module.css +0 -18
- package/src/components/PreviewFull.js +0 -24
- package/src/components/PreviewFull.module.css +0 -11
- package/src/components/PreviewGrid.js +0 -14
- package/src/components/PreviewWidget.js +0 -27
- package/src/components/PreviewWidget.module.css +0 -15
- package/src/components/SidebarLayout.jsx +0 -252
- package/src/components/SidebarLayout.module.css +0 -71
- package/src/components/SkeletonLoader.jsx +0 -128
- package/src/components/SkeletonLoader.module.css +0 -295
- package/src/components/SortButtonGroup.jsx +0 -34
- package/src/components/SortButtonGroup.module.css +0 -51
- package/src/components/ToastContainer.jsx +0 -98
- package/src/components/ToastContainer.module.css +0 -156
- package/src/components/ToggleSwitch.js +0 -40
- package/src/components/ToggleSwitch.module.css +0 -48
- package/src/components/TwoColumnInput.jsx +0 -29
- package/src/components/TwoColumnInput.module.css +0 -32
- package/src/components/ViewFull.js +0 -139
- package/src/components/ViewFull.module.css +0 -71
- package/src/components/ViewWidget.js +0 -62
- package/src/components/ViewWidget.module.css +0 -28
- package/src/components/iconCategories.js +0 -135
- package/src/components/iconImports.js +0 -409
- package/src/components/index.js +0 -59
- package/src/components/listing/FileListItem.jsx +0 -86
- package/src/components/listing/GalleryDisplay.jsx +0 -330
- package/src/components/listing/GalleryDisplay.module.css +0 -309
- package/src/components/listing/ListingCTAInput.jsx +0 -82
- package/src/components/listing/ListingDescriptionInput.jsx +0 -73
- package/src/components/listing/ListingField.jsx +0 -101
- package/src/components/listing/ListingField.module.css +0 -106
- package/src/components/listing/ListingFileInput.jsx +0 -273
- package/src/components/listing/ListingFileInput.module.css +0 -189
- package/src/components/listing/ListingForm.jsx +0 -90
- package/src/components/listing/ListingForm.module.css +0 -38
- package/src/components/listing/ListingGalleryInput.jsx +0 -239
- package/src/components/listing/ListingGalleryInput.module.css +0 -132
- package/src/components/listing/ListingImageInput.jsx +0 -153
- package/src/components/listing/ListingTextInput.jsx +0 -72
- package/src/feature.config.js +0 -130
- package/src/helper/index.js +0 -135
- package/src/hooks/useFeatureDefinitionLoader.js +0 -66
- 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 +0 -38
- package/src/pages/CreateListingPage.jsx +0 -49
- package/src/pages/EditListingPage.jsx +0 -58
- package/src/reducers/featureBuilderReducer.js +0 -739
- package/src/screens/CreateListing.module.css +0 -45
- package/src/screens/Form.module.css +0 -744
- package/src/screens/FormFieldsStep.jsx +0 -626
- package/src/screens/FormLayoutStep.jsx +0 -405
- package/src/screens/FormOverviewStep.jsx +0 -389
- package/src/screens/ListingScreen.jsx +0 -477
- package/src/screens/ListingScreen.module.css +0 -333
- package/src/selectors/featureBuilderSelectors.js +0 -533
- package/src/types/index.js +0 -91
- package/src/utils/textUtils.js +0 -89
- package/src/validators/galleryValidators.js +0 -345
- package/src/values.config.a.js +0 -49
- package/src/values.config.b.js +0 -49
- package/src/values.config.c.js +0 -49
- package/src/values.config.d.js +0 -49
- package/src/values.config.default.js +0 -49
- package/src/values.config.js +0 -49
- package/src/webapi/featureDefinitionActions.js +0 -0
- package/src/webapi/featuresActions.js +0 -90
- package/src/webapi/helper.js +0 -4
- package/src/webapi/index.js +0 -12
- package/src/webapi/listingActions.js +0 -176
|
@@ -1,626 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
-
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
|
4
|
-
import { SidebarLayout } from "../components/SidebarLayout.jsx";
|
|
5
|
-
import { values } from "../values.config.js";
|
|
6
|
-
import { PlussCore } from "../feature.config";
|
|
7
|
-
import styles from "./Form.module.css";
|
|
8
|
-
import { Field } from "../components/Fields.jsx";
|
|
9
|
-
import { iconImports } from "../components/iconImports";
|
|
10
|
-
import {
|
|
11
|
-
Text,
|
|
12
|
-
LoadingState,
|
|
13
|
-
Button,
|
|
14
|
-
ErrorBoundary,
|
|
15
|
-
Popup,
|
|
16
|
-
} from "../components";
|
|
17
|
-
|
|
18
|
-
import ToastContainer from "../components/ToastContainer.jsx";
|
|
19
|
-
|
|
20
|
-
import { withRouter } from "react-router-dom";
|
|
21
|
-
import { useDispatch, useSelector } from "react-redux";
|
|
22
|
-
import {
|
|
23
|
-
selectFormField,
|
|
24
|
-
selectFormFields,
|
|
25
|
-
selectFormDisplayName,
|
|
26
|
-
selectIsCreateMode,
|
|
27
|
-
selectIsEditMode,
|
|
28
|
-
selectIsStepValid,
|
|
29
|
-
selectStepErrors,
|
|
30
|
-
selectFormIsSubmitting,
|
|
31
|
-
selectFormSubmitError,
|
|
32
|
-
selectFormSubmitSuccess,
|
|
33
|
-
selectFormIsInitial,
|
|
34
|
-
} from "../selectors/featureBuilderSelectors";
|
|
35
|
-
import { useFeatureDefinitionLoader } from "../hooks/useFeatureDefinitionLoader";
|
|
36
|
-
import {
|
|
37
|
-
addField,
|
|
38
|
-
deleteField,
|
|
39
|
-
updateFieldById as updateFieldValuesById,
|
|
40
|
-
setSummaryField,
|
|
41
|
-
submitForm,
|
|
42
|
-
clearFormSubmissionState,
|
|
43
|
-
setInitialValues,
|
|
44
|
-
} from "../actions/formActions";
|
|
45
|
-
import {
|
|
46
|
-
validateAndUpdateStep,
|
|
47
|
-
setCurrentStepAndSave,
|
|
48
|
-
} from "../actions/wizardActions";
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Form Fields Step component for feature builder wizard
|
|
52
|
-
* Provides field management interface with add, edit, delete functionality
|
|
53
|
-
* Supports multiple field types (text, image, file, CTA, feature-image, description)
|
|
54
|
-
* Includes validation, error handling, and step navigation
|
|
55
|
-
*
|
|
56
|
-
* @param {Object} props - Component props
|
|
57
|
-
* @param {Object} props.history - React Router history object for navigation
|
|
58
|
-
* @returns {React.ReactElement} Form fields configuration interface
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* <FormFieldsStep history={historyObject} />
|
|
62
|
-
*/
|
|
63
|
-
const FormFieldsStepInner = (props) => {
|
|
64
|
-
const { history } = props;
|
|
65
|
-
const dispatch = useDispatch();
|
|
66
|
-
const auth = useSelector((state) => state.auth);
|
|
67
|
-
const fields = useSelector(selectFormFields);
|
|
68
|
-
|
|
69
|
-
// Get form initialization state
|
|
70
|
-
const isFormInitial = useSelector(selectFormIsInitial);
|
|
71
|
-
|
|
72
|
-
// Field selection popup state
|
|
73
|
-
const [showFieldSelector, setShowFieldSelector] = useState(false);
|
|
74
|
-
const [replacingFieldIndex, setReplacingFieldIndex] = useState(null);
|
|
75
|
-
|
|
76
|
-
// Toast state
|
|
77
|
-
const [toasts, setToasts] = React.useState([]);
|
|
78
|
-
|
|
79
|
-
// Available field types for card selection
|
|
80
|
-
// Note: We exclude "title" type since it's already included as base field
|
|
81
|
-
// UX-optimized order: text-first, media-second pattern for natural content creation workflow
|
|
82
|
-
const fieldTypes = [
|
|
83
|
-
{
|
|
84
|
-
Key: "description",
|
|
85
|
-
Title: "Description",
|
|
86
|
-
Description: "Add detailed text content",
|
|
87
|
-
UseCase: "Provide information, details, or descriptions",
|
|
88
|
-
Icon: "paragraph",
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
Key: "image",
|
|
92
|
-
Title: "Image",
|
|
93
|
-
Description: "Add photos or visual content",
|
|
94
|
-
UseCase: "Show facility photos, event pictures, product images",
|
|
95
|
-
Icon: "image",
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
Key: "gallery",
|
|
99
|
-
Title: "Gallery",
|
|
100
|
-
Description: "Add multiple photos in a gallery layout",
|
|
101
|
-
UseCase: "Create photo albums, event galleries, showcase multiple images",
|
|
102
|
-
Icon: "th",
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
Key: "file",
|
|
106
|
-
Title: "Files",
|
|
107
|
-
Description: "Share downloadable documents",
|
|
108
|
-
UseCase: "Upload menus, brochures, PDFs, or resources",
|
|
109
|
-
Icon: "paperclip",
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
Key: "cta",
|
|
113
|
-
Title: "Action Button",
|
|
114
|
-
Description: "Add clickable action button",
|
|
115
|
-
UseCase: "Link to reservations, bookings, or external sites",
|
|
116
|
-
Icon: "arrow-circle-right",
|
|
117
|
-
},
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
const { definition, definitionIsLoading, reloadDefinition } =
|
|
121
|
-
useFeatureDefinitionLoader();
|
|
122
|
-
|
|
123
|
-
const formDisplayName = useSelector(selectFormDisplayName);
|
|
124
|
-
|
|
125
|
-
const isCreateMode = useSelector(selectIsCreateMode);
|
|
126
|
-
const isEditMode = useSelector(selectIsEditMode);
|
|
127
|
-
const isStepValid = useSelector(selectIsStepValid("fields"));
|
|
128
|
-
const stepErrors = useSelector(selectStepErrors("fields"));
|
|
129
|
-
const showWarnings = !isStepValid && Object.keys(stepErrors).length > 0;
|
|
130
|
-
const isSubmitting = useSelector(selectFormIsSubmitting);
|
|
131
|
-
const submitError = useSelector(selectFormSubmitError);
|
|
132
|
-
const submitSuccess = useSelector(selectFormSubmitSuccess);
|
|
133
|
-
|
|
134
|
-
const addToast = (type, message) => {
|
|
135
|
-
const id = Date.now();
|
|
136
|
-
setToasts((prev) => [...prev, { id, type, message, isVisible: true }]);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const removeToast = (id) => {
|
|
140
|
-
setToasts((prev) => prev.filter((toast) => toast.id !== id));
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
// Handle successful submission with optimistic update and redirect
|
|
144
|
-
React.useEffect(() => {
|
|
145
|
-
if (submitSuccess && !isSubmitting) {
|
|
146
|
-
if (isEditMode) {
|
|
147
|
-
addToast("success", "Changes saved");
|
|
148
|
-
dispatch(clearFormSubmissionState());
|
|
149
|
-
} else {
|
|
150
|
-
addToast("success", "Feature created successfully");
|
|
151
|
-
dispatch(clearFormSubmissionState());
|
|
152
|
-
setTimeout(() => {
|
|
153
|
-
history.push(values.routeFormOverviewStep);
|
|
154
|
-
}, 1000);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}, [submitSuccess, isEditMode, isSubmitting, dispatch, history]);
|
|
158
|
-
|
|
159
|
-
// Handle submit error
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (submitError) {
|
|
162
|
-
addToast("error", "It didn't work. Please try again.");
|
|
163
|
-
setTimeout(() => {
|
|
164
|
-
window.location.reload();
|
|
165
|
-
}, 1000);
|
|
166
|
-
}
|
|
167
|
-
}, [submitError]);
|
|
168
|
-
|
|
169
|
-
useEffect(() => {
|
|
170
|
-
if (showWarnings) {
|
|
171
|
-
// Scroll to top of form to show validation errors
|
|
172
|
-
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
173
|
-
}
|
|
174
|
-
}, [showWarnings]);
|
|
175
|
-
|
|
176
|
-
const handleRefresh = () => {
|
|
177
|
-
dispatch(validateAndUpdateStep("fields"));
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
useEffect(() => {
|
|
181
|
-
dispatch(setCurrentStepAndSave("fields"));
|
|
182
|
-
}, [dispatch]);
|
|
183
|
-
|
|
184
|
-
useEffect(() => {
|
|
185
|
-
if (definition && !definitionIsLoading && isFormInitial) {
|
|
186
|
-
dispatch(setInitialValues(definition));
|
|
187
|
-
|
|
188
|
-
if (isEditMode) {
|
|
189
|
-
setTimeout(() => {
|
|
190
|
-
dispatch(validateAndUpdateStep("fields"));
|
|
191
|
-
}, 100);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}, [definition, definitionIsLoading, isFormInitial, isEditMode, dispatch]);
|
|
195
|
-
|
|
196
|
-
useEffect(() => {
|
|
197
|
-
// In edit mode, trigger validation when definition is available
|
|
198
|
-
if (isEditMode && definition && !definitionIsLoading && !isFormInitial) {
|
|
199
|
-
setTimeout(() => {
|
|
200
|
-
dispatch(validateAndUpdateStep("fields"));
|
|
201
|
-
}, 100);
|
|
202
|
-
}
|
|
203
|
-
}, [definition, definitionIsLoading, isEditMode, isFormInitial, dispatch]);
|
|
204
|
-
|
|
205
|
-
function handleAddField(fieldType) {
|
|
206
|
-
dispatch(addField(fieldType));
|
|
207
|
-
setShowFieldSelector(false);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function handleOpenFieldSelector() {
|
|
211
|
-
setReplacingFieldIndex(null);
|
|
212
|
-
setShowFieldSelector(true);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function handleCloseFieldSelector() {
|
|
216
|
-
setReplacingFieldIndex(null);
|
|
217
|
-
setShowFieldSelector(false);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function handleDeleteField(fieldId) {
|
|
221
|
-
dispatch(deleteField(fieldId));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function handleReplaceField(fieldIndex) {
|
|
225
|
-
setReplacingFieldIndex(fieldIndex);
|
|
226
|
-
setShowFieldSelector(true);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function handleAddReplacementField(fieldType) {
|
|
230
|
-
const fieldIndex = replacingFieldIndex;
|
|
231
|
-
if (fieldIndex !== null) {
|
|
232
|
-
const currentField = allFields[fieldIndex];
|
|
233
|
-
if (currentField && !currentField.isMandatory) {
|
|
234
|
-
dispatch(deleteField(currentField.id));
|
|
235
|
-
}
|
|
236
|
-
dispatch(addField(fieldType));
|
|
237
|
-
setReplacingFieldIndex(null);
|
|
238
|
-
setShowFieldSelector(false);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function handleNext() {
|
|
243
|
-
const validationResult = dispatch(validateAndUpdateStep("fields"));
|
|
244
|
-
if (validationResult?.isValid) {
|
|
245
|
-
dispatch(clearFormSubmissionState());
|
|
246
|
-
if (isCreateMode) {
|
|
247
|
-
history.push(values.routeFormLayoutStep);
|
|
248
|
-
} else {
|
|
249
|
-
history.push(values.routeFormLayoutStep);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// If validation fails, scroll to top to show error summary
|
|
253
|
-
else {
|
|
254
|
-
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function handlePrevious() {
|
|
259
|
-
dispatch(clearFormSubmissionState());
|
|
260
|
-
if (isCreateMode) {
|
|
261
|
-
history.push(values.routeFormOverviewStep);
|
|
262
|
-
} else {
|
|
263
|
-
history.push(values.routeFormOverviewStep);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function handleSaveStep() {
|
|
268
|
-
const validationResult = dispatch(validateAndUpdateStep("fields"));
|
|
269
|
-
if (validationResult?.isValid) {
|
|
270
|
-
dispatch(submitForm());
|
|
271
|
-
}
|
|
272
|
-
// If validation fails, scroll to top to show error summary
|
|
273
|
-
else {
|
|
274
|
-
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const allFields = fields || [];
|
|
279
|
-
const sortedFields = allFields.slice().sort((a, b) => a.order - b.order);
|
|
280
|
-
|
|
281
|
-
// Check for definition management permission
|
|
282
|
-
if (
|
|
283
|
-
!PlussCore.Session.validateAccess(
|
|
284
|
-
auth.site,
|
|
285
|
-
values.permissionFeatureBuilderDefinition,
|
|
286
|
-
auth,
|
|
287
|
-
)
|
|
288
|
-
) {
|
|
289
|
-
return (
|
|
290
|
-
<div className="hub-wrapperContainer">
|
|
291
|
-
<div className="hub-contentWrapper">
|
|
292
|
-
<div className={styles.welcomeContainer}>
|
|
293
|
-
<div className={styles.welcomeHeader}>
|
|
294
|
-
<Text type="h1" className={styles.welcomeTitle}>
|
|
295
|
-
Access Restricted
|
|
296
|
-
</Text>
|
|
297
|
-
<Text type="body" className={styles.welcomeSubtitle}>
|
|
298
|
-
You don't have permission to manage feature definitions. Please
|
|
299
|
-
contact your administrator if you need access.
|
|
300
|
-
</Text>
|
|
301
|
-
</div>
|
|
302
|
-
</div>
|
|
303
|
-
</div>
|
|
304
|
-
</div>
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return (
|
|
309
|
-
<ErrorBoundary
|
|
310
|
-
title="Unable to load fields configuration"
|
|
311
|
-
message="If you continue to experience issues with the fields configuration, please try refreshing the page or contact support."
|
|
312
|
-
onRetry={handleRefresh}
|
|
313
|
-
>
|
|
314
|
-
<SidebarLayout>
|
|
315
|
-
<div className={styles.formHeader}>
|
|
316
|
-
<Text
|
|
317
|
-
type="formTitleLarge"
|
|
318
|
-
className={` ${isEditMode ? styles.editMode : styles.createMode}`}
|
|
319
|
-
>
|
|
320
|
-
Configure fields
|
|
321
|
-
</Text>
|
|
322
|
-
</div>
|
|
323
|
-
<Text type="body" className="marginBottom-16">
|
|
324
|
-
Add fields to define the shape of your feature's listings.
|
|
325
|
-
</Text>
|
|
326
|
-
|
|
327
|
-
{/* Unified Fields Section */}
|
|
328
|
-
<div className={styles.section}>
|
|
329
|
-
{/* Show loading spinner while loading */}
|
|
330
|
-
{definitionIsLoading ? (
|
|
331
|
-
<div className={styles.fieldsLoadingContainer}>
|
|
332
|
-
<LoadingState message="Loading fields..." />
|
|
333
|
-
</div>
|
|
334
|
-
) : (
|
|
335
|
-
sortedFields.map((field, fieldIndex) => (
|
|
336
|
-
<FormField
|
|
337
|
-
key={field.id}
|
|
338
|
-
id={field.id}
|
|
339
|
-
fieldIndex={fieldIndex}
|
|
340
|
-
handleDelete={
|
|
341
|
-
field.isMandatory ? null : () => handleDeleteField(field.id)
|
|
342
|
-
}
|
|
343
|
-
onReplaceField={
|
|
344
|
-
field.isMandatory
|
|
345
|
-
? null
|
|
346
|
-
: () => handleReplaceField(fieldIndex)
|
|
347
|
-
}
|
|
348
|
-
showWarnings={showWarnings}
|
|
349
|
-
/>
|
|
350
|
-
))
|
|
351
|
-
)}
|
|
352
|
-
|
|
353
|
-
{/* Add Field Button at bottom */}
|
|
354
|
-
<div className={styles.addFieldContainer}>
|
|
355
|
-
<div className={styles.fieldNumberContainer}>
|
|
356
|
-
{/* Empty spacer to align with field numbers */}
|
|
357
|
-
</div>
|
|
358
|
-
<div className={styles.addFieldSection}>
|
|
359
|
-
<Button
|
|
360
|
-
buttonType="primary"
|
|
361
|
-
isActive
|
|
362
|
-
onClick={handleOpenFieldSelector}
|
|
363
|
-
leftIcon="plus"
|
|
364
|
-
aria-label="Add a new content field"
|
|
365
|
-
>
|
|
366
|
-
Add Content Field
|
|
367
|
-
</Button>
|
|
368
|
-
</div>
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
|
|
372
|
-
{/* Top-level validation message - positioned above action buttons */}
|
|
373
|
-
{!isStepValid && Object.keys(stepErrors).length > 0 && (
|
|
374
|
-
<div
|
|
375
|
-
className={styles.validationErrorMessage}
|
|
376
|
-
role="alert"
|
|
377
|
-
aria-live="polite"
|
|
378
|
-
>
|
|
379
|
-
{stepErrors.summary ? (
|
|
380
|
-
<>
|
|
381
|
-
{stepErrors.summary}
|
|
382
|
-
<br />
|
|
383
|
-
<span className={styles.helpNote}>
|
|
384
|
-
Only one description field can be marked as "Show as preview
|
|
385
|
-
text".
|
|
386
|
-
</span>
|
|
387
|
-
</>
|
|
388
|
-
) : (
|
|
389
|
-
"The form has error, please review the fields above."
|
|
390
|
-
)}
|
|
391
|
-
</div>
|
|
392
|
-
)}
|
|
393
|
-
|
|
394
|
-
{/* Mode-aware navigation buttons */}
|
|
395
|
-
<div className={styles.navigation}>
|
|
396
|
-
{isCreateMode ? (
|
|
397
|
-
<>
|
|
398
|
-
<Button
|
|
399
|
-
buttonType="secondary"
|
|
400
|
-
isActive
|
|
401
|
-
onClick={handlePrevious}
|
|
402
|
-
leftIcon="arrow-left"
|
|
403
|
-
>
|
|
404
|
-
Previous step: Configure Fields
|
|
405
|
-
</Button>
|
|
406
|
-
<Button
|
|
407
|
-
buttonType="primary"
|
|
408
|
-
isActive
|
|
409
|
-
onClick={handleNext}
|
|
410
|
-
leftIcon="arrow-right"
|
|
411
|
-
>
|
|
412
|
-
Next step: Choose Layout
|
|
413
|
-
</Button>
|
|
414
|
-
</>
|
|
415
|
-
) : (
|
|
416
|
-
<Button
|
|
417
|
-
buttonType="primary"
|
|
418
|
-
isActive
|
|
419
|
-
onClick={handleSaveStep}
|
|
420
|
-
disabled={isSubmitting}
|
|
421
|
-
leftIcon={isSubmitting ? "sync" : "save"}
|
|
422
|
-
loading={isSubmitting}
|
|
423
|
-
>
|
|
424
|
-
{isSubmitting ? "Saving..." : "Save"}
|
|
425
|
-
</Button>
|
|
426
|
-
)}
|
|
427
|
-
</div>
|
|
428
|
-
</SidebarLayout>
|
|
429
|
-
|
|
430
|
-
{/* Field Selector Popup */}
|
|
431
|
-
{showFieldSelector && (
|
|
432
|
-
<Popup
|
|
433
|
-
title={
|
|
434
|
-
replacingFieldIndex !== null ? "Replace Field" : "Add Content Field"
|
|
435
|
-
}
|
|
436
|
-
onClose={handleCloseFieldSelector}
|
|
437
|
-
buttons={[
|
|
438
|
-
{
|
|
439
|
-
text: "Cancel",
|
|
440
|
-
type: "secondary",
|
|
441
|
-
onClick: handleCloseFieldSelector,
|
|
442
|
-
},
|
|
443
|
-
]}
|
|
444
|
-
hasPadding
|
|
445
|
-
width="1200px"
|
|
446
|
-
>
|
|
447
|
-
{/* Help Section - Visible for all users */}
|
|
448
|
-
<div className={styles.helpSection}>
|
|
449
|
-
<Text type="formTitleSmall" className={styles.helpText}>
|
|
450
|
-
<FontAwesomeIcon icon={faInfoCircle} /> Add fields to the
|
|
451
|
-
template.
|
|
452
|
-
<br />
|
|
453
|
-
These fields determine layout for every{" "}
|
|
454
|
-
{formDisplayName || "Feature"} visible to residents.
|
|
455
|
-
</Text>
|
|
456
|
-
</div>
|
|
457
|
-
|
|
458
|
-
<div className={styles.fieldTypeCards}>
|
|
459
|
-
{fieldTypes.map((fieldType) => (
|
|
460
|
-
<div
|
|
461
|
-
key={fieldType.Key}
|
|
462
|
-
className={styles.fieldTypeCard}
|
|
463
|
-
onClick={() =>
|
|
464
|
-
replacingFieldIndex !== null
|
|
465
|
-
? handleAddReplacementField(fieldType.Key)
|
|
466
|
-
: handleAddField(fieldType.Key)
|
|
467
|
-
}
|
|
468
|
-
role="button"
|
|
469
|
-
tabIndex={0}
|
|
470
|
-
onKeyDown={(e) => {
|
|
471
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
472
|
-
e.preventDefault();
|
|
473
|
-
if (replacingFieldIndex !== null) {
|
|
474
|
-
handleAddReplacementField(fieldType.Key);
|
|
475
|
-
} else {
|
|
476
|
-
handleAddField(fieldType.Key);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}}
|
|
480
|
-
>
|
|
481
|
-
<div className={styles.fieldTypeCardIcon}>
|
|
482
|
-
<FieldTypeIcon iconName={fieldType.Icon} />
|
|
483
|
-
</div>
|
|
484
|
-
<div className={styles.fieldTypeCardContent}>
|
|
485
|
-
<Text type="h5" className={styles.fieldTypeCardTitle}>
|
|
486
|
-
{fieldType.Title}
|
|
487
|
-
</Text>
|
|
488
|
-
<Text type="body" className={styles.fieldTypeCardDescription}>
|
|
489
|
-
{fieldType.Description}
|
|
490
|
-
</Text>
|
|
491
|
-
<Text type="help" className={styles.fieldTypeCardUseCase}>
|
|
492
|
-
<strong>Use case:</strong> {fieldType.UseCase}
|
|
493
|
-
</Text>
|
|
494
|
-
</div>
|
|
495
|
-
</div>
|
|
496
|
-
))}
|
|
497
|
-
</div>
|
|
498
|
-
</Popup>
|
|
499
|
-
)}
|
|
500
|
-
|
|
501
|
-
{/* Toast Container for Notifications */}
|
|
502
|
-
<ToastContainer toasts={toasts} onDismiss={removeToast} />
|
|
503
|
-
</ErrorBoundary>
|
|
504
|
-
);
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
export const FormFieldsStep = withRouter(FormFieldsStepInner);
|
|
508
|
-
|
|
509
|
-
// FieldTypeIcon component for popup cards
|
|
510
|
-
const FieldTypeIcon = ({ iconName }) => {
|
|
511
|
-
const icon = iconImports[iconName];
|
|
512
|
-
|
|
513
|
-
return (
|
|
514
|
-
<span className={styles.fieldTypeCardIcon}>
|
|
515
|
-
<FontAwesomeIcon icon={icon} />
|
|
516
|
-
</span>
|
|
517
|
-
);
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
const FormField = (props) => {
|
|
521
|
-
const { id, handleDelete, showWarnings, fieldIndex, onReplaceField } = props;
|
|
522
|
-
const field = useField(id);
|
|
523
|
-
const stepErrors = useSelector(selectStepErrors("fields"));
|
|
524
|
-
const hasError = showWarnings && stepErrors && stepErrors[id];
|
|
525
|
-
|
|
526
|
-
// The Field component now handles its own styling, so we just render it directly
|
|
527
|
-
// Error states are passed through via props
|
|
528
|
-
return (
|
|
529
|
-
<Field
|
|
530
|
-
{...field}
|
|
531
|
-
fieldIndex={fieldIndex}
|
|
532
|
-
handleDelete={handleDelete}
|
|
533
|
-
onReplaceField={onReplaceField}
|
|
534
|
-
stepErrors={stepErrors}
|
|
535
|
-
showWarnings={showWarnings}
|
|
536
|
-
/>
|
|
537
|
-
);
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Custom hook for managing individual field state and operations
|
|
542
|
-
* Provides convenient methods to update field properties with Redux dispatch
|
|
543
|
-
*
|
|
544
|
-
* @param {string} id - Unique identifier of field to manage
|
|
545
|
-
* @returns {Object} Field object with setter methods for field properties
|
|
546
|
-
* @returns {Object} returns.field - Current field state from Redux store
|
|
547
|
-
* @returns {Function} returns.setLabel - Update field label
|
|
548
|
-
* @returns {Function} returns.toggleIsRequired - Toggle field required status
|
|
549
|
-
* @returns {Function} returns.setPlaceholder - Update field placeholder text
|
|
550
|
-
* @returns {Function} returns.setUrl - Update field URL
|
|
551
|
-
* @returns {Function} returns.setHelpText - Update field help text
|
|
552
|
-
* @returns {Function} returns.setAllowCaption - Update caption allowance setting
|
|
553
|
-
* @returns {Function} returns.toggleAllowCaption - Toggle caption allowance setting
|
|
554
|
-
* @returns {Function} returns.setUseAsSummary - Update use as summary setting for description fields
|
|
555
|
-
*
|
|
556
|
-
* @example
|
|
557
|
-
* const field = useField('field-123');
|
|
558
|
-
* field.setLabel('New Label');
|
|
559
|
-
* field.toggleIsRequired();
|
|
560
|
-
*/
|
|
561
|
-
const useField = (id) => {
|
|
562
|
-
const field = useSelector(selectFormField(id));
|
|
563
|
-
const dispatch = useDispatch();
|
|
564
|
-
const { values } = field;
|
|
565
|
-
|
|
566
|
-
function setPlaceholder(value) {
|
|
567
|
-
const updatedField = { values: { ...values, placeholder: value } };
|
|
568
|
-
dispatch(updateFieldValuesById(id, updatedField));
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function setLabel(value) {
|
|
572
|
-
const updatedField = { values: { ...values, label: value } };
|
|
573
|
-
dispatch(updateFieldValuesById(id, updatedField));
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function toggleIsRequired() {
|
|
577
|
-
const updatedField = {
|
|
578
|
-
values: { ...values, isRequired: !values.isRequired },
|
|
579
|
-
};
|
|
580
|
-
dispatch(updateFieldValuesById(id, updatedField));
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
function setUrl(value) {
|
|
584
|
-
const updatedField = { values: { ...values, url: value } };
|
|
585
|
-
dispatch(updateFieldValuesById(id, updatedField));
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
function setHelpText(value) {
|
|
589
|
-
const updatedField = { values: { ...values, helpText: value } };
|
|
590
|
-
dispatch(updateFieldValuesById(id, updatedField));
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function setAllowCaption(value) {
|
|
594
|
-
const updatedField = { values: { ...values, allowCaption: value } };
|
|
595
|
-
|
|
596
|
-
dispatch(updateFieldValuesById(id, updatedField));
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function toggleAllowCaption() {
|
|
600
|
-
const updatedField = {
|
|
601
|
-
values: { ...values, allowCaption: !values.allowCaption },
|
|
602
|
-
};
|
|
603
|
-
dispatch(updateFieldValuesById(id, updatedField));
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
function setUseAsSummary(value) {
|
|
607
|
-
if (value) {
|
|
608
|
-
dispatch(setSummaryField(id));
|
|
609
|
-
} else {
|
|
610
|
-
const updatedField = { values: { ...values, useAsSummary: false } };
|
|
611
|
-
dispatch(updateFieldValuesById(id, updatedField));
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
return {
|
|
616
|
-
...field,
|
|
617
|
-
setLabel,
|
|
618
|
-
toggleIsRequired,
|
|
619
|
-
setPlaceholder,
|
|
620
|
-
setUrl,
|
|
621
|
-
setHelpText,
|
|
622
|
-
setAllowCaption,
|
|
623
|
-
toggleAllowCaption,
|
|
624
|
-
setUseAsSummary,
|
|
625
|
-
};
|
|
626
|
-
};
|