@plusscommunities/pluss-feature-builder-web-a 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.
- package/dist/index.cjs.js +827 -828
- package/package.json +1 -1
- package/src/actions/featureDefinitionsIndex.js +2 -3
- package/src/actions/formActions.js +148 -158
- package/src/actions/listingActions.js +1 -1
- package/src/actions/wizardActions.js +169 -184
- package/src/components/BaseFieldConfig.jsx +234 -234
- package/src/components/IconLoader.jsx +138 -138
- package/src/components/IconSelector.jsx +0 -1
- package/src/components/ListingEditor.jsx +0 -1
- package/src/components/SidebarLayout.jsx +4 -1
- package/src/components/index.js +0 -2
- package/src/components/listing/GalleryDisplay.jsx +0 -1
- package/src/components/listing/ListingGalleryInput.jsx +4 -1
- package/src/index.js +6 -6
- package/src/reducers/featureBuilderReducer.js +14 -25
- package/src/screens/FormLayoutStep.jsx +10 -13
- package/src/screens/FormOverviewStep.jsx +351 -358
- package/src/screens/ListingScreen.jsx +0 -1
- package/src/selectors/featureBuilderSelectors.js +49 -44
package/package.json
CHANGED
|
@@ -23,8 +23,7 @@ export const FEATURE_DELETE_REQUEST = `${REDUCER_PREFIX}_FEATURE_DELETE_REQUEST`
|
|
|
23
23
|
export const FEATURE_DELETE_FAILURE = `${REDUCER_PREFIX}_FEATURE_DELETE_FAILURE`;
|
|
24
24
|
|
|
25
25
|
// Wizard synchronization action types
|
|
26
|
-
export const SYNC_WIZARD_MODE_FROM_DEFINITION =
|
|
27
|
-
`${REDUCER_PREFIX}_SYNC_WIZARD_MODE_FROM_DEFINITION`;
|
|
26
|
+
export const SYNC_WIZARD_MODE_FROM_DEFINITION = `${REDUCER_PREFIX}_SYNC_WIZARD_MODE_FROM_DEFINITION`;
|
|
28
27
|
export const SET_WIZARD_MODE = `${REDUCER_PREFIX}_SET_WIZARD_MODE`;
|
|
29
28
|
|
|
30
29
|
function fetchFeaturesRequest() {
|
|
@@ -161,7 +160,7 @@ export function fetchFeatureDefinitions() {
|
|
|
161
160
|
}
|
|
162
161
|
} catch (err) {
|
|
163
162
|
// Check if it's a 404 (feature doesn't exist)
|
|
164
|
-
if (err.response
|
|
163
|
+
if (err.response?.status === 404) {
|
|
165
164
|
// 404: Feature doesn't exist → create mode
|
|
166
165
|
dispatch(fetchFeaturesSuccess(null, "create"));
|
|
167
166
|
|
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Form Actions for Feature Builder
|
|
3
|
-
* Manages form state including title, icon, fields, layout, and submission
|
|
4
|
-
* Handles CRUD operations for form fields and layout configuration
|
|
5
|
-
* Coordinates with external menu updates and feature definition actions
|
|
6
|
-
*
|
|
7
|
-
* @namespace formActions
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { featureDefinitionActions } from "../webapi/featuresActions";
|
|
11
2
|
import { values } from "../values.config";
|
|
12
3
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
updateFeatureBuilderString,
|
|
5
|
+
updateFeatureBuilderIcon,
|
|
15
6
|
} from "./featureBuilderStringsActions";
|
|
16
|
-
import { toTitleCase } from "../utils/textUtils";
|
|
17
7
|
|
|
18
8
|
/**
|
|
19
9
|
* @typedef {Object} FieldValues
|
|
@@ -40,20 +30,20 @@ import { toTitleCase } from "../utils/textUtils";
|
|
|
40
30
|
*/
|
|
41
31
|
|
|
42
32
|
export const actionsTypes = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
33
|
+
SET_INITIAL_VALUES: `${values.reducerKey.toUpperCase()}_SET_INITIAL_VALUES`,
|
|
34
|
+
SET_TITLE: `${values.reducerKey.toUpperCase()}_SET_TITLE`,
|
|
35
|
+
SET_ICON: `${values.reducerKey.toUpperCase()}_SET_ICON`,
|
|
36
|
+
SET_DISPLAY_NAME: `${values.reducerKey.toUpperCase()}_SET_DISPLAY_NAME`,
|
|
37
|
+
ADD_FIELD: `${values.reducerKey.toUpperCase()}_ADD_FIELD`,
|
|
38
|
+
DELETE_FIELD: `${values.reducerKey.toUpperCase()}_DELETE_FIELD`,
|
|
39
|
+
UPDATE_FIELD: `${values.reducerKey.toUpperCase()}_UPDATE_FIELD`,
|
|
40
|
+
SET_LAYOUT_GRID_ICON: `${values.reducerKey.toUpperCase()}_SET_LAYOUT_GRID_ICON`,
|
|
41
|
+
SET_LAYOUT_TYPE: `${values.reducerKey.toUpperCase()}_SET_LAYOUT_TYPE`,
|
|
42
|
+
SUBMIT_FORM_REQUEST: `${values.reducerKey.toUpperCase()}_SUBMIT_FORM_REQUEST`,
|
|
43
|
+
SUBMIT_FORM_SUCCESS: `${values.reducerKey.toUpperCase()}_SUBMIT_FORM_SUCCESS`,
|
|
44
|
+
SUBMIT_FORM_FAILURE: `${values.reducerKey.toUpperCase()}_SUBMIT_FORM_FAILURE`,
|
|
45
|
+
CLEAR_FORM_SUBMISSION_STATE: `${values.reducerKey.toUpperCase()}_CLEAR_FORM_SUBMISSION_STATE`,
|
|
46
|
+
SET_SUMMARY_FIELD: `${values.reducerKey.toUpperCase()}_SET_SUMMARY_FIELD`,
|
|
57
47
|
};
|
|
58
48
|
|
|
59
49
|
/**
|
|
@@ -76,11 +66,11 @@ export const actionsTypes = {
|
|
|
76
66
|
* }));
|
|
77
67
|
*/
|
|
78
68
|
export const setInitialValues = (initialValues) => {
|
|
79
|
-
|
|
69
|
+
return { type: actionsTypes.SET_INITIAL_VALUES, payload: initialValues };
|
|
80
70
|
};
|
|
81
71
|
|
|
82
72
|
export const setTitle = (title) => {
|
|
83
|
-
|
|
73
|
+
return { type: actionsTypes.SET_TITLE, payload: title };
|
|
84
74
|
};
|
|
85
75
|
|
|
86
76
|
/**
|
|
@@ -95,16 +85,16 @@ export const setTitle = (title) => {
|
|
|
95
85
|
* dispatch(setDisplayName('Contact Information'));
|
|
96
86
|
*/
|
|
97
87
|
export const setDisplayName = (displayName) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
88
|
+
return (dispatch) => {
|
|
89
|
+
// Update menu string when display name changes (will be title cased in updateFeatureBuilderString)
|
|
90
|
+
dispatch(updateFeatureBuilderString(displayName));
|
|
101
91
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
92
|
+
// Dispatch the actual action
|
|
93
|
+
dispatch({
|
|
94
|
+
type: actionsTypes.SET_DISPLAY_NAME,
|
|
95
|
+
payload: displayName,
|
|
96
|
+
});
|
|
97
|
+
};
|
|
108
98
|
};
|
|
109
99
|
|
|
110
100
|
/**
|
|
@@ -118,16 +108,16 @@ export const setDisplayName = (displayName) => {
|
|
|
118
108
|
* dispatch(setIcon('fa-user'));
|
|
119
109
|
*/
|
|
120
110
|
export const setIcon = (icon) => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
111
|
+
return (dispatch) => {
|
|
112
|
+
// Update menu icon when icon changes
|
|
113
|
+
dispatch(updateFeatureBuilderIcon(icon));
|
|
124
114
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
115
|
+
// Dispatch the actual action
|
|
116
|
+
dispatch({
|
|
117
|
+
type: actionsTypes.SET_ICON,
|
|
118
|
+
payload: icon,
|
|
119
|
+
});
|
|
120
|
+
};
|
|
131
121
|
};
|
|
132
122
|
|
|
133
123
|
/**
|
|
@@ -142,20 +132,20 @@ export const setIcon = (icon) => {
|
|
|
142
132
|
* dispatch(addField('image'));
|
|
143
133
|
*/
|
|
144
134
|
export const addField = (fieldType = "text") => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
135
|
+
// Generate a unique ID for the new field
|
|
136
|
+
const fieldId = `custom-field-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
137
|
+
return {
|
|
138
|
+
type: actionsTypes.ADD_FIELD,
|
|
139
|
+
payload: { id: fieldId, type: fieldType },
|
|
140
|
+
};
|
|
151
141
|
};
|
|
152
142
|
|
|
153
143
|
export const deleteField = (id) => {
|
|
154
|
-
|
|
144
|
+
return { type: actionsTypes.DELETE_FIELD, payload: id };
|
|
155
145
|
};
|
|
156
146
|
|
|
157
147
|
export const updateFieldById = (id, updatedField) => {
|
|
158
|
-
|
|
148
|
+
return { type: actionsTypes.UPDATE_FIELD, payload: { id, updatedField } };
|
|
159
149
|
};
|
|
160
150
|
|
|
161
151
|
/**
|
|
@@ -170,31 +160,31 @@ export const updateFieldById = (id, updatedField) => {
|
|
|
170
160
|
* dispatch(setSummaryField('field-description-123'));
|
|
171
161
|
*/
|
|
172
162
|
export const setSummaryField = (fieldId) => {
|
|
173
|
-
|
|
163
|
+
return { type: actionsTypes.SET_SUMMARY_FIELD, payload: fieldId };
|
|
174
164
|
};
|
|
175
165
|
|
|
176
166
|
export const setGridLayoutIcon = (imageUrl) => {
|
|
177
|
-
|
|
167
|
+
return { type: actionsTypes.SET_LAYOUT_GRID_ICON, payload: imageUrl };
|
|
178
168
|
};
|
|
179
169
|
|
|
180
170
|
export const setLayoutType = (layoutType) => {
|
|
181
|
-
|
|
171
|
+
return { type: actionsTypes.SET_LAYOUT_TYPE, payload: layoutType };
|
|
182
172
|
};
|
|
183
173
|
|
|
184
174
|
const submitFormRequest = () => {
|
|
185
|
-
|
|
175
|
+
return { type: actionsTypes.SUBMIT_FORM_REQUEST };
|
|
186
176
|
};
|
|
187
177
|
|
|
188
178
|
const submitFormSuccess = () => {
|
|
189
|
-
|
|
179
|
+
return { type: actionsTypes.SUBMIT_FORM_SUCCESS };
|
|
190
180
|
};
|
|
191
181
|
|
|
192
182
|
const submitFormFailure = (error) => {
|
|
193
|
-
|
|
183
|
+
return { type: actionsTypes.SUBMIT_FORM_FAILURE, payload: error };
|
|
194
184
|
};
|
|
195
185
|
|
|
196
186
|
export const clearFormSubmissionState = () => {
|
|
197
|
-
|
|
187
|
+
return { type: actionsTypes.CLEAR_FORM_SUBMISSION_STATE };
|
|
198
188
|
};
|
|
199
189
|
|
|
200
190
|
/**
|
|
@@ -205,107 +195,107 @@ export const clearFormSubmissionState = () => {
|
|
|
205
195
|
* @throws {Error} When form validation fails or API submission encounters error
|
|
206
196
|
*/
|
|
207
197
|
export function submitForm() {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
198
|
+
return async (dispatch, getState) => {
|
|
199
|
+
const state = getState()[values.reducerKey];
|
|
200
|
+
const form = state?.form;
|
|
201
|
+
if (!form) {
|
|
202
|
+
dispatch(
|
|
203
|
+
submitFormFailure(
|
|
204
|
+
new Error(
|
|
205
|
+
"Form data is missing. Please refresh the page and try again.",
|
|
206
|
+
),
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
221
211
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
212
|
+
dispatch(submitFormRequest());
|
|
213
|
+
try {
|
|
214
|
+
// Get site from auth store
|
|
215
|
+
const site = getState().auth.site;
|
|
216
|
+
if (!site) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
219
|
+
);
|
|
220
|
+
}
|
|
231
221
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
222
|
+
// Use mode from fetch instead of checking definition ID
|
|
223
|
+
const definitionState = state?.definition;
|
|
224
|
+
const mode = definitionState?.mode; // Use stored mode from fetch
|
|
225
|
+
const definitionId = definitionState?.id;
|
|
236
226
|
|
|
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
|
-
|
|
227
|
+
if (mode === "edit") {
|
|
228
|
+
// Always update when in edit mode
|
|
229
|
+
const updatedDefinition = {
|
|
230
|
+
id: definitionId,
|
|
231
|
+
site: site, // Include site from auth store
|
|
232
|
+
featureDefinition: {
|
|
233
|
+
// Wrap in expected structure for backend
|
|
234
|
+
title: form.title,
|
|
235
|
+
icon: form.icon,
|
|
236
|
+
displayName: form.displayName,
|
|
237
|
+
layout: form.layout,
|
|
238
|
+
fields: form.fields,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
await featureDefinitionActions.edit(updatedDefinition, site);
|
|
242
|
+
} else {
|
|
243
|
+
// Always create when in create mode (or mode is undefined/null)
|
|
244
|
+
if (!values.featureId || !site) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
"Authentication error: Missing required context (featureId or site).",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
await featureDefinitionActions.create(
|
|
250
|
+
values.featureId,
|
|
251
|
+
site, // Use actual site from auth store
|
|
252
|
+
{
|
|
253
|
+
title: form.title,
|
|
254
|
+
icon: form.icon,
|
|
255
|
+
displayName: form.displayName,
|
|
256
|
+
layout: form.layout,
|
|
257
|
+
fields: form.fields,
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
}
|
|
271
261
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
262
|
+
dispatch(submitFormSuccess());
|
|
263
|
+
} catch (err) {
|
|
264
|
+
// Handle different types of errors
|
|
265
|
+
let errorToDisplay = err;
|
|
276
266
|
|
|
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
|
-
|
|
267
|
+
if (err.response) {
|
|
268
|
+
// API error (400, 401, 404, 500, etc.)
|
|
269
|
+
const { status, data } = err.response;
|
|
270
|
+
if (status === 400 && data?.error) {
|
|
271
|
+
errorToDisplay = new Error(`Validation error: ${data.error}`);
|
|
272
|
+
} else if (status === 401) {
|
|
273
|
+
errorToDisplay = new Error(
|
|
274
|
+
"You are not authorized to perform this action",
|
|
275
|
+
);
|
|
276
|
+
} else if (status === 404) {
|
|
277
|
+
errorToDisplay = new Error("Feature definition not found");
|
|
278
|
+
} else if (status >= 500) {
|
|
279
|
+
errorToDisplay = new Error("Server error. Please try again later.");
|
|
280
|
+
} else {
|
|
281
|
+
errorToDisplay = new Error(
|
|
282
|
+
data?.error || `Request failed with status ${status}`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
} else if (err.request) {
|
|
286
|
+
// Network error (no response received)
|
|
287
|
+
errorToDisplay = new Error(
|
|
288
|
+
"Network error. Please check your connection and try again.",
|
|
289
|
+
);
|
|
290
|
+
} else if (err.message) {
|
|
291
|
+
// Other JavaScript errors
|
|
292
|
+
errorToDisplay = err;
|
|
293
|
+
} else {
|
|
294
|
+
// Unknown error
|
|
295
|
+
errorToDisplay = new Error("An unexpected error occurred while saving");
|
|
296
|
+
}
|
|
307
297
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
298
|
+
dispatch(submitFormFailure(errorToDisplay));
|
|
299
|
+
}
|
|
300
|
+
};
|
|
311
301
|
}
|
|
@@ -212,7 +212,7 @@ export const undeleteListing = (id) => {
|
|
|
212
212
|
const response = await webApiActions.undelete(id, site);
|
|
213
213
|
// If API returns the restored listing, use it; otherwise we'll need to refetch
|
|
214
214
|
const restoredListing = response.data;
|
|
215
|
-
if (restoredListing
|
|
215
|
+
if (restoredListing?.id) {
|
|
216
216
|
dispatch(undeleteListingSuccess(restoredListing));
|
|
217
217
|
} else {
|
|
218
218
|
// Trigger a refetch by dispatching the success with just ID, then fetch updated listings
|