@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,744 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Builder Redux Reducer
|
|
3
|
+
* Manages state for feature creation wizard including:
|
|
4
|
+
* - Form state (title, icon, fields, layout)
|
|
5
|
+
* - Validation and step management
|
|
6
|
+
* - Loading and error states
|
|
7
|
+
* - Feature definitions CRUD operations
|
|
8
|
+
*
|
|
9
|
+
* @typedef {Object} FeatureBuilderState
|
|
10
|
+
* @property {Object} formState - Current form configuration
|
|
11
|
+
* @property {Array} featureDefinitions - Available feature definitions
|
|
12
|
+
* @property {Object} wizardState - Wizard navigation and validation state
|
|
13
|
+
* @property {boolean} loading - Loading state for async operations
|
|
14
|
+
* @property {Error} error - Current error state
|
|
15
|
+
*
|
|
16
|
+
* @param {FeatureBuilderState} state - Current Redux state
|
|
17
|
+
* @param {Object} action - Redux action to process
|
|
18
|
+
* @returns {FeatureBuilderState} New Redux state
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Reducer is used automatically with Redux
|
|
22
|
+
* const store = createStore(featureBuilderReducer, initialState);
|
|
23
|
+
*/
|
|
24
|
+
// Import action types from existing files
|
|
25
|
+
import { actionsTypes as formActionTypes } from "../actions/formActions";
|
|
26
|
+
import {
|
|
27
|
+
FETCH_FEATURES_REQUEST,
|
|
28
|
+
FETCH_FEATURES_SUCCESS,
|
|
29
|
+
FETCH_FEATURES_FAILURE,
|
|
30
|
+
FEATURE_CREATE_REQUEST,
|
|
31
|
+
FEATURE_CREATE_SUCCESS,
|
|
32
|
+
FEATURE_CREATE_FAILURE,
|
|
33
|
+
FEATURE_EDIT_REQUEST,
|
|
34
|
+
FEATURE_EDIT_SUCCESS,
|
|
35
|
+
FEATURE_EDIT_FAILURE,
|
|
36
|
+
FEATURE_DELETE_REQUEST,
|
|
37
|
+
FEATURE_DELETE_FAILURE,
|
|
38
|
+
SYNC_WIZARD_MODE_FROM_DEFINITION,
|
|
39
|
+
SET_WIZARD_MODE,
|
|
40
|
+
} from "../actions/featureDefinitionsIndex";
|
|
41
|
+
|
|
42
|
+
import {
|
|
43
|
+
FETCH_LISTING_REQUEST,
|
|
44
|
+
FETCH_LISTING_SUCCESS,
|
|
45
|
+
FETCH_LISTING_FAILURE,
|
|
46
|
+
FETCH_LISTING_SILENT_SUCCESS,
|
|
47
|
+
FETCH_LISTING_SILENT_FAILURE,
|
|
48
|
+
REORDER_LISTING_SUCCESS,
|
|
49
|
+
REORDER_LISTING_FAILURE,
|
|
50
|
+
DELETE_LISTING_REQUEST,
|
|
51
|
+
DELETE_LISTING_SUCCESS,
|
|
52
|
+
DELETE_LISTING_FAILURE,
|
|
53
|
+
UNDELETE_LISTING_REQUEST,
|
|
54
|
+
UNDELETE_LISTING_SUCCESS,
|
|
55
|
+
UNDELETE_LISTING_FAILURE,
|
|
56
|
+
CREATE_LISTING_REQUEST,
|
|
57
|
+
CREATE_LISTING_SUCCESS,
|
|
58
|
+
CREATE_LISTING_FAILURE,
|
|
59
|
+
EDIT_LISTING_REQUEST,
|
|
60
|
+
EDIT_LISTING_SUCCESS,
|
|
61
|
+
EDIT_LISTING_FAILURE,
|
|
62
|
+
TOGGLE_LISTING_REQUEST,
|
|
63
|
+
TOGGLE_LISTING_SUCCESS,
|
|
64
|
+
TOGGLE_LISTING_FAILURE,
|
|
65
|
+
SET_SORT_BY,
|
|
66
|
+
SET_SHOW_DELETED,
|
|
67
|
+
} from "../actions/listingActions";
|
|
68
|
+
|
|
69
|
+
import { values } from "../values.config";
|
|
70
|
+
|
|
71
|
+
// Wizard action types (local declarations only)
|
|
72
|
+
const SET_NAVIGATION_STATE = "SET_NAVIGATION_STATE";
|
|
73
|
+
const UPDATE_STEP_VALIDATION = "UPDATE_STEP_VALIDATION";
|
|
74
|
+
const MARK_STEP_COMPLETE = "MARK_STEP_COMPLETE";
|
|
75
|
+
const RESET_WIZARD_STATE = "RESET_WIZARD_STATE";
|
|
76
|
+
|
|
77
|
+
// Default new field definition
|
|
78
|
+
const DEFAULT_NEW_FIELD = {
|
|
79
|
+
type: "text",
|
|
80
|
+
isMandatory: false,
|
|
81
|
+
values: {
|
|
82
|
+
label: "",
|
|
83
|
+
placeholder: "",
|
|
84
|
+
isRequired: false,
|
|
85
|
+
helpText: "",
|
|
86
|
+
allowCaption: false, // Default to not allowing captions for image fields
|
|
87
|
+
useAsSummary: false, // Default to not using as summary for description fields
|
|
88
|
+
minImages: 1, // Gallery-specific default
|
|
89
|
+
maxImages: 10, // Gallery-specific default
|
|
90
|
+
maxFileSize: "5MB", // Gallery-specific default
|
|
91
|
+
allowedTypes: [], // Gallery-specific default
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function createNewField(id, type = "text", existingFields = []) {
|
|
96
|
+
const baseField = {
|
|
97
|
+
...DEFAULT_NEW_FIELD,
|
|
98
|
+
id,
|
|
99
|
+
type,
|
|
100
|
+
values: { ...DEFAULT_NEW_FIELD.values },
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Calculate order for new field
|
|
104
|
+
const maxOrder = Math.max(...existingFields.map((f) => f.order || 0), 1); // Start from 1 since title is order 1
|
|
105
|
+
baseField.order = maxOrder + 1;
|
|
106
|
+
|
|
107
|
+
// Set default values based on field type
|
|
108
|
+
switch (type) {
|
|
109
|
+
case "title":
|
|
110
|
+
baseField.values.label = "Title";
|
|
111
|
+
baseField.values.placeholder = "Enter title";
|
|
112
|
+
baseField.values.helpText = "The main title or heading for this item";
|
|
113
|
+
baseField.values.isRequired = true;
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case "text":
|
|
117
|
+
baseField.values.label = "Text";
|
|
118
|
+
baseField.values.placeholder = "Enter text";
|
|
119
|
+
baseField.values.helpText = "Provide additional text information";
|
|
120
|
+
baseField.values.isRequired = true;
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case "description":
|
|
124
|
+
baseField.values.label = "Description";
|
|
125
|
+
baseField.values.placeholder = "Enter description";
|
|
126
|
+
baseField.values.helpText = "Detailed description of the item";
|
|
127
|
+
break;
|
|
128
|
+
|
|
129
|
+
case "image": {
|
|
130
|
+
baseField.values.label = "Image Upload";
|
|
131
|
+
baseField.values.helpText = "Upload an image to display";
|
|
132
|
+
const { placeholder, ...valuesWithoutPlaceholder } = baseField.values;
|
|
133
|
+
baseField.values = valuesWithoutPlaceholder;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
case "gallery": {
|
|
138
|
+
baseField.values.label = "Image Gallery";
|
|
139
|
+
baseField.values.helpText = "Upload multiple images to create a gallery";
|
|
140
|
+
baseField.values.minImages = 1;
|
|
141
|
+
baseField.values.maxImages = 10;
|
|
142
|
+
baseField.values.maxFileSize = "5MB";
|
|
143
|
+
baseField.values.allowedTypes = ["image/jpeg", "image/png", "image/webp"];
|
|
144
|
+
const {
|
|
145
|
+
placeholder: galleryPlaceholder,
|
|
146
|
+
...galleryValuesWithoutPlaceholder
|
|
147
|
+
} = baseField.values;
|
|
148
|
+
baseField.values = galleryValuesWithoutPlaceholder;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case "feature-image": {
|
|
153
|
+
baseField.values.label = "Feature Image";
|
|
154
|
+
baseField.values.helpText = "Main featured image for this item";
|
|
155
|
+
baseField.values.isRequired = true;
|
|
156
|
+
const {
|
|
157
|
+
placeholder: ph,
|
|
158
|
+
allowCaption,
|
|
159
|
+
...valuesWithoutCaptionAndPlaceholder
|
|
160
|
+
} = baseField.values;
|
|
161
|
+
baseField.values = valuesWithoutCaptionAndPlaceholder;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case "cta":
|
|
166
|
+
baseField.values.label = "Action Button";
|
|
167
|
+
baseField.values.placeholder = "Button text";
|
|
168
|
+
baseField.values.helpText = "Text to display on the action button";
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
case "file":
|
|
172
|
+
baseField.values.label = "File";
|
|
173
|
+
baseField.values.placeholder = "Select file";
|
|
174
|
+
baseField.values.helpText = "Upload a file for users to download";
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
default:
|
|
178
|
+
baseField.values.label = "Field";
|
|
179
|
+
baseField.values.placeholder = "Enter value";
|
|
180
|
+
baseField.values.helpText = "Provide information for this field";
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return baseField;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Initial form state (from existing formReducer)
|
|
188
|
+
const INITIAL_FORM_STATE = {
|
|
189
|
+
_isInitial: true,
|
|
190
|
+
title: "",
|
|
191
|
+
icon: values.defaultIcon,
|
|
192
|
+
displayName: "",
|
|
193
|
+
layout: {
|
|
194
|
+
gridIcon: undefined,
|
|
195
|
+
type: "round",
|
|
196
|
+
},
|
|
197
|
+
fields: [
|
|
198
|
+
{
|
|
199
|
+
id: "mandatory-feature-image",
|
|
200
|
+
type: "feature-image",
|
|
201
|
+
isMandatory: true,
|
|
202
|
+
order: 0,
|
|
203
|
+
values: {
|
|
204
|
+
label: "Feature Image",
|
|
205
|
+
isRequired: true, // Feature image is always required
|
|
206
|
+
helpText: "The main picture to display on the content",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: "mandatory-title",
|
|
211
|
+
type: "title",
|
|
212
|
+
isMandatory: true,
|
|
213
|
+
order: 1,
|
|
214
|
+
values: {
|
|
215
|
+
label: "Title",
|
|
216
|
+
placeholder: "Enter title here",
|
|
217
|
+
isRequired: true,
|
|
218
|
+
helpText: "The title of your listing",
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Helper function to get initial wizard state
|
|
225
|
+
const getInitialWizardState = () => {
|
|
226
|
+
return {
|
|
227
|
+
mode: "create", // 'create' | 'edit'
|
|
228
|
+
navigation: {
|
|
229
|
+
currentStep: "welcome", // 'welcome' | 'overview' | 'fields' | 'layout'
|
|
230
|
+
previousStep: null,
|
|
231
|
+
canGoBack: false,
|
|
232
|
+
canGoForward: true,
|
|
233
|
+
},
|
|
234
|
+
stepValidation: {
|
|
235
|
+
overview: { isValid: false, errors: {} },
|
|
236
|
+
fields: { isValid: false, errors: {} },
|
|
237
|
+
layout: { isValid: false, errors: {} },
|
|
238
|
+
},
|
|
239
|
+
stepCompletion: {
|
|
240
|
+
overview: false,
|
|
241
|
+
fields: false,
|
|
242
|
+
layout: false,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Initial consolidated state
|
|
248
|
+
const INITIAL_STATE = {
|
|
249
|
+
form: INITIAL_FORM_STATE,
|
|
250
|
+
definition: {
|
|
251
|
+
id: null,
|
|
252
|
+
definition: null,
|
|
253
|
+
isLoading: false,
|
|
254
|
+
error: null,
|
|
255
|
+
isCreating: false,
|
|
256
|
+
isEditing: false,
|
|
257
|
+
mode: null, // Add mode field
|
|
258
|
+
},
|
|
259
|
+
listings: {
|
|
260
|
+
listings: [],
|
|
261
|
+
isLoading: false,
|
|
262
|
+
isCreating: false,
|
|
263
|
+
isEditing: false,
|
|
264
|
+
isRestoring: false,
|
|
265
|
+
error: null,
|
|
266
|
+
sortBy: "newest",
|
|
267
|
+
showDeleted: false,
|
|
268
|
+
isInitiallyLoaded: false,
|
|
269
|
+
},
|
|
270
|
+
wizard: getInitialWizardState(),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Form reducer logic (from existing formReducer)
|
|
274
|
+
const formReducer = (state = INITIAL_FORM_STATE, action) => {
|
|
275
|
+
const { type, payload } = action;
|
|
276
|
+
|
|
277
|
+
switch (type) {
|
|
278
|
+
case formActionTypes.SET_INITIAL_VALUES: {
|
|
279
|
+
// The actual definition data is in payload.featureDefinition.definition
|
|
280
|
+
const definitionWrapper =
|
|
281
|
+
payload && payload.featureDefinition
|
|
282
|
+
? payload.featureDefinition
|
|
283
|
+
: payload;
|
|
284
|
+
const definition =
|
|
285
|
+
definitionWrapper && definitionWrapper.definition
|
|
286
|
+
? definitionWrapper.definition
|
|
287
|
+
: definitionWrapper;
|
|
288
|
+
|
|
289
|
+
// Validate and map definition data to form state structure
|
|
290
|
+
const mappedValues = {
|
|
291
|
+
title: (definition && definition.title) || "",
|
|
292
|
+
icon: (definition && definition.icon) || "star",
|
|
293
|
+
displayName: (definition && definition.displayName) || "",
|
|
294
|
+
layout: (definition && definition.layout) || {
|
|
295
|
+
gridIcon: undefined,
|
|
296
|
+
type: "round",
|
|
297
|
+
},
|
|
298
|
+
fields:
|
|
299
|
+
definition && Array.isArray(definition.fields)
|
|
300
|
+
? definition.fields
|
|
301
|
+
: state.fields,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const newState = {
|
|
305
|
+
...state,
|
|
306
|
+
...mappedValues,
|
|
307
|
+
_isInitial: false,
|
|
308
|
+
};
|
|
309
|
+
return newState;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
case formActionTypes.SET_TITLE:
|
|
313
|
+
return { ...state, title: payload };
|
|
314
|
+
|
|
315
|
+
case formActionTypes.SET_ICON:
|
|
316
|
+
return { ...state, icon: payload };
|
|
317
|
+
|
|
318
|
+
case formActionTypes.SET_DISPLAY_NAME:
|
|
319
|
+
return { ...state, displayName: payload };
|
|
320
|
+
|
|
321
|
+
case formActionTypes.ADD_FIELD: {
|
|
322
|
+
// Safety check for payload
|
|
323
|
+
if (!payload || !payload.id) {
|
|
324
|
+
return state;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const newField = createNewField(payload.id, payload.type, state.fields);
|
|
328
|
+
const newFields = [...state.fields, newField];
|
|
329
|
+
return { ...state, fields: newFields };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
case formActionTypes.DELETE_FIELD: {
|
|
333
|
+
const newFields = state.fields.filter((field) => field.id !== payload);
|
|
334
|
+
return { ...state, fields: newFields };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
case formActionTypes.UPDATE_FIELD: {
|
|
338
|
+
const { id, updatedField } = payload;
|
|
339
|
+
|
|
340
|
+
// Safety check: ensure payload is valid
|
|
341
|
+
if (!id || !updatedField || typeof updatedField !== "object") {
|
|
342
|
+
return state;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const newFields = state.fields.map((field) => {
|
|
346
|
+
if (field.id === id) {
|
|
347
|
+
// Preserve existing field structure and merge updates safely
|
|
348
|
+
const updatedFieldWithSafety = {
|
|
349
|
+
...field, // Preserve all existing properties
|
|
350
|
+
...updatedField, // Merge updates (should be {values: {...}})
|
|
351
|
+
};
|
|
352
|
+
return updatedFieldWithSafety;
|
|
353
|
+
}
|
|
354
|
+
return field;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
return { ...state, fields: newFields };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
case formActionTypes.SET_SUMMARY_FIELD: {
|
|
361
|
+
// Safety check: ensure payload is valid
|
|
362
|
+
if (!payload) {
|
|
363
|
+
return state;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const newFields = state.fields.map((field) => {
|
|
367
|
+
if (field.id === payload) {
|
|
368
|
+
// Set the selected field as summary
|
|
369
|
+
return {
|
|
370
|
+
...field,
|
|
371
|
+
values: {
|
|
372
|
+
...field.values,
|
|
373
|
+
useAsSummary: true,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
} else if (field.type === "description") {
|
|
377
|
+
// Unset all other description fields
|
|
378
|
+
return {
|
|
379
|
+
...field,
|
|
380
|
+
values: {
|
|
381
|
+
...field.values,
|
|
382
|
+
useAsSummary: false,
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return field;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return { ...state, fields: newFields };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
case formActionTypes.SET_LAYOUT_GRID_ICON: {
|
|
393
|
+
return {
|
|
394
|
+
...state,
|
|
395
|
+
layout: { ...state.layout, gridIcon: payload },
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
case formActionTypes.SET_LAYOUT_TYPE: {
|
|
400
|
+
return {
|
|
401
|
+
...state,
|
|
402
|
+
layout: { ...state.layout, type: payload },
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
case formActionTypes.SUBMIT_FORM_REQUEST:
|
|
407
|
+
return {
|
|
408
|
+
...state,
|
|
409
|
+
_isSubmitting: true,
|
|
410
|
+
_submitError: null,
|
|
411
|
+
_submitSuccess: false,
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
case formActionTypes.SUBMIT_FORM_SUCCESS:
|
|
415
|
+
return {
|
|
416
|
+
...state,
|
|
417
|
+
_isSubmitting: false,
|
|
418
|
+
_submitError: null,
|
|
419
|
+
_submitSuccess: true,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
case formActionTypes.SUBMIT_FORM_FAILURE:
|
|
423
|
+
return {
|
|
424
|
+
...state,
|
|
425
|
+
_isSubmitting: false,
|
|
426
|
+
_submitError: payload,
|
|
427
|
+
_submitSuccess: false,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
case formActionTypes.CLEAR_FORM_SUBMISSION_STATE:
|
|
431
|
+
return {
|
|
432
|
+
...state,
|
|
433
|
+
_isSubmitting: false,
|
|
434
|
+
_submitError: null,
|
|
435
|
+
_submitSuccess: false,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
default:
|
|
439
|
+
return state;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// Wizard reducer logic - now a pure function
|
|
444
|
+
const wizardReducer = (state = INITIAL_STATE.wizard, action) => {
|
|
445
|
+
const { type, payload } = action;
|
|
446
|
+
|
|
447
|
+
switch (type) {
|
|
448
|
+
case SYNC_WIZARD_MODE_FROM_DEFINITION:
|
|
449
|
+
return {
|
|
450
|
+
...state,
|
|
451
|
+
mode: payload,
|
|
452
|
+
navigation: {
|
|
453
|
+
...state.navigation,
|
|
454
|
+
currentStep: "overview",
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
case SET_WIZARD_MODE:
|
|
459
|
+
return {
|
|
460
|
+
...state,
|
|
461
|
+
mode: payload,
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
case SET_NAVIGATION_STATE:
|
|
465
|
+
return {
|
|
466
|
+
...state,
|
|
467
|
+
navigation: {
|
|
468
|
+
...state.navigation,
|
|
469
|
+
...payload,
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
case UPDATE_STEP_VALIDATION:
|
|
474
|
+
return {
|
|
475
|
+
...state,
|
|
476
|
+
stepValidation: {
|
|
477
|
+
...state.stepValidation,
|
|
478
|
+
[payload.step]: {
|
|
479
|
+
isValid: payload.isValid,
|
|
480
|
+
errors: payload.errors || {},
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
case MARK_STEP_COMPLETE:
|
|
486
|
+
return {
|
|
487
|
+
...state,
|
|
488
|
+
stepCompletion: {
|
|
489
|
+
...state.stepCompletion,
|
|
490
|
+
[payload]: true,
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
case RESET_WIZARD_STATE:
|
|
495
|
+
return INITIAL_STATE.wizard;
|
|
496
|
+
|
|
497
|
+
default:
|
|
498
|
+
return state;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// Definition reducer logic (simplified for single definition)
|
|
503
|
+
const definitionReducer = (state = INITIAL_STATE.definition, action) => {
|
|
504
|
+
const { type, payload } = action;
|
|
505
|
+
|
|
506
|
+
switch (type) {
|
|
507
|
+
case FETCH_FEATURES_REQUEST:
|
|
508
|
+
return { ...state, isLoading: true, error: null };
|
|
509
|
+
|
|
510
|
+
case FETCH_FEATURES_SUCCESS: {
|
|
511
|
+
// payload now includes mode: 'edit' or 'create'
|
|
512
|
+
const { data, mode } = payload || {};
|
|
513
|
+
|
|
514
|
+
let definition = null;
|
|
515
|
+
let definitionId = values.featureId; // Always use hardcoded ID
|
|
516
|
+
|
|
517
|
+
if (mode === "edit" && data) {
|
|
518
|
+
// Extract from API response for edit mode
|
|
519
|
+
// Handle nested structure: data.featureDefinition.definition
|
|
520
|
+
const featureDefinitionWrapper =
|
|
521
|
+
data && data.featureDefinition ? data.featureDefinition : data;
|
|
522
|
+
definition =
|
|
523
|
+
featureDefinitionWrapper && featureDefinitionWrapper.definition
|
|
524
|
+
? featureDefinitionWrapper.definition
|
|
525
|
+
: featureDefinitionWrapper;
|
|
526
|
+
definitionId =
|
|
527
|
+
(featureDefinitionWrapper && featureDefinitionWrapper.id) ||
|
|
528
|
+
values.featureId;
|
|
529
|
+
|
|
530
|
+
// Ensure fields array exists and preserves order property
|
|
531
|
+
if (definition && definition.fields) {
|
|
532
|
+
// Create a new array to ensure we preserve all field properties including order
|
|
533
|
+
definition.fields = definition.fields.map((field) => ({
|
|
534
|
+
...field,
|
|
535
|
+
// Ensure order property exists, fallback to array index if missing
|
|
536
|
+
order: field.order !== undefined ? field.order : 999,
|
|
537
|
+
}));
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return {
|
|
542
|
+
...state,
|
|
543
|
+
definition,
|
|
544
|
+
id: definitionId,
|
|
545
|
+
isLoading: false,
|
|
546
|
+
error: null,
|
|
547
|
+
mode, // Store the mode from fetch
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
case FETCH_FEATURES_FAILURE:
|
|
552
|
+
return { ...state, isLoading: false, error: payload };
|
|
553
|
+
|
|
554
|
+
case FEATURE_CREATE_REQUEST:
|
|
555
|
+
return { ...state, isCreating: true, error: null };
|
|
556
|
+
|
|
557
|
+
case FEATURE_CREATE_FAILURE:
|
|
558
|
+
return { ...state, isCreating: false, error: payload };
|
|
559
|
+
|
|
560
|
+
case FEATURE_CREATE_SUCCESS:
|
|
561
|
+
return {
|
|
562
|
+
...state,
|
|
563
|
+
isCreating: false,
|
|
564
|
+
definition: {
|
|
565
|
+
...state.definition,
|
|
566
|
+
definition: payload, // Optimistically update with new definition
|
|
567
|
+
mode: "edit", // Switch to edit mode immediately
|
|
568
|
+
},
|
|
569
|
+
id: payload && payload.id,
|
|
570
|
+
error: null,
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
case FEATURE_EDIT_REQUEST:
|
|
574
|
+
return { ...state, isEditing: true, error: null };
|
|
575
|
+
|
|
576
|
+
case FEATURE_EDIT_FAILURE:
|
|
577
|
+
return { ...state, isEditing: false, error: payload };
|
|
578
|
+
|
|
579
|
+
case FEATURE_EDIT_SUCCESS:
|
|
580
|
+
return {
|
|
581
|
+
...state,
|
|
582
|
+
isEditing: false,
|
|
583
|
+
definition: payload,
|
|
584
|
+
error: null,
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
case FEATURE_DELETE_REQUEST:
|
|
588
|
+
return { ...state, isLoading: true };
|
|
589
|
+
|
|
590
|
+
case FEATURE_DELETE_FAILURE:
|
|
591
|
+
return { ...state, isLoading: false, error: payload };
|
|
592
|
+
|
|
593
|
+
default:
|
|
594
|
+
return state;
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// Listings reducer logic (updated to use 'listings' instead of 'items')
|
|
599
|
+
const listingsReducer = (state = INITIAL_STATE.listings, action) => {
|
|
600
|
+
const { type, payload } = action;
|
|
601
|
+
|
|
602
|
+
switch (type) {
|
|
603
|
+
case FETCH_LISTING_REQUEST:
|
|
604
|
+
return { ...state, isLoading: true, error: null };
|
|
605
|
+
|
|
606
|
+
case FETCH_LISTING_SUCCESS:
|
|
607
|
+
return {
|
|
608
|
+
...state,
|
|
609
|
+
isLoading: false,
|
|
610
|
+
listings: payload,
|
|
611
|
+
isInitiallyLoaded: true,
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
case FETCH_LISTING_FAILURE:
|
|
615
|
+
return { ...state, isLoading: false, error: payload };
|
|
616
|
+
|
|
617
|
+
case FETCH_LISTING_SILENT_SUCCESS:
|
|
618
|
+
return { ...state, listings: payload, isInitiallyLoaded: true };
|
|
619
|
+
|
|
620
|
+
case FETCH_LISTING_SILENT_FAILURE:
|
|
621
|
+
return { ...state, error: payload };
|
|
622
|
+
|
|
623
|
+
case REORDER_LISTING_SUCCESS:
|
|
624
|
+
return { ...state, listings: payload };
|
|
625
|
+
|
|
626
|
+
case DELETE_LISTING_SUCCESS:
|
|
627
|
+
return {
|
|
628
|
+
...state,
|
|
629
|
+
listings: state.listings.map((listing) =>
|
|
630
|
+
listing.id === payload
|
|
631
|
+
? { ...listing, deletedAt: new Date().toISOString() }
|
|
632
|
+
: listing,
|
|
633
|
+
),
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
case DELETE_LISTING_FAILURE:
|
|
637
|
+
return { ...state, error: payload };
|
|
638
|
+
|
|
639
|
+
case UNDELETE_LISTING_REQUEST:
|
|
640
|
+
return { ...state, isRestoring: true, error: null };
|
|
641
|
+
|
|
642
|
+
case UNDELETE_LISTING_SUCCESS:
|
|
643
|
+
// Handle both scenarios: restored listing data or just ID
|
|
644
|
+
if (typeof payload === "object" && payload.id) {
|
|
645
|
+
// We have the restored listing data - add it back to the list
|
|
646
|
+
const restoredListing = payload;
|
|
647
|
+
const listingExists = state.listings.some(
|
|
648
|
+
(listing) => listing.id === restoredListing.id,
|
|
649
|
+
);
|
|
650
|
+
if (!listingExists) {
|
|
651
|
+
return {
|
|
652
|
+
...state,
|
|
653
|
+
listings: [...state.listings, restoredListing],
|
|
654
|
+
error: null,
|
|
655
|
+
isRestoring: false,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
// Listing already exists, update it
|
|
659
|
+
return {
|
|
660
|
+
...state,
|
|
661
|
+
listings: state.listings.map((listing) =>
|
|
662
|
+
listing.id === restoredListing.id ? restoredListing : listing,
|
|
663
|
+
),
|
|
664
|
+
error: null,
|
|
665
|
+
isRestoring: false,
|
|
666
|
+
};
|
|
667
|
+
} else {
|
|
668
|
+
// We only have the ID - don't set loading as fetchListingSilent will handle the update
|
|
669
|
+
return { ...state, error: null, isRestoring: false };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
case UNDELETE_LISTING_FAILURE:
|
|
673
|
+
return { ...state, error: payload, isRestoring: false };
|
|
674
|
+
|
|
675
|
+
case CREATE_LISTING_REQUEST:
|
|
676
|
+
return { ...state, isCreating: true, error: null };
|
|
677
|
+
|
|
678
|
+
case CREATE_LISTING_SUCCESS:
|
|
679
|
+
return {
|
|
680
|
+
...state,
|
|
681
|
+
isCreating: false,
|
|
682
|
+
listings: [...state.listings, payload],
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
case CREATE_LISTING_FAILURE:
|
|
686
|
+
return { ...state, isCreating: false, error: payload };
|
|
687
|
+
|
|
688
|
+
case EDIT_LISTING_REQUEST:
|
|
689
|
+
return { ...state, isEditing: true, error: null };
|
|
690
|
+
|
|
691
|
+
case EDIT_LISTING_SUCCESS:
|
|
692
|
+
return {
|
|
693
|
+
...state,
|
|
694
|
+
isEditing: false,
|
|
695
|
+
listings: state.listings.map((listing) =>
|
|
696
|
+
listing.id === payload.id ? payload : listing,
|
|
697
|
+
),
|
|
698
|
+
error: null,
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
case EDIT_LISTING_FAILURE:
|
|
702
|
+
return { ...state, isEditing: false, error: payload };
|
|
703
|
+
|
|
704
|
+
case TOGGLE_LISTING_SUCCESS:
|
|
705
|
+
return {
|
|
706
|
+
...state,
|
|
707
|
+
listings: state.listings.map((listing) =>
|
|
708
|
+
listing.id === payload.id
|
|
709
|
+
? { ...listing, isActive: payload.isActive }
|
|
710
|
+
: listing,
|
|
711
|
+
),
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
case TOGGLE_LISTING_FAILURE:
|
|
715
|
+
return { ...state, error: payload };
|
|
716
|
+
|
|
717
|
+
case SET_SORT_BY:
|
|
718
|
+
return { ...state, sortBy: payload };
|
|
719
|
+
|
|
720
|
+
case SET_SHOW_DELETED:
|
|
721
|
+
return { ...state, showDeleted: payload };
|
|
722
|
+
|
|
723
|
+
default:
|
|
724
|
+
return state;
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// Main consolidated reducer - now eliminates circular dependencies
|
|
729
|
+
export function featureBuilderReducer(state = INITIAL_STATE, action) {
|
|
730
|
+
// Calculate new states for all reducers independently
|
|
731
|
+
const newForm = formReducer(state.form, action);
|
|
732
|
+
const newDefinition = definitionReducer(state.definition, action);
|
|
733
|
+
const newListings = listingsReducer(state.listings, action);
|
|
734
|
+
const newWizard = wizardReducer(state.wizard, action);
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
form: newForm,
|
|
738
|
+
definition: newDefinition,
|
|
739
|
+
listings: newListings,
|
|
740
|
+
wizard: newWizard,
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
export default featureBuilderReducer;
|