@plusscommunities/pluss-feature-builder-web-a 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,311 @@
|
|
|
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
|
+
import { featureDefinitionActions } from "../webapi/featuresActions";
|
|
11
|
+
import { values } from "../values.config";
|
|
12
|
+
import {
|
|
13
|
+
updateFeatureBuilderString,
|
|
14
|
+
updateFeatureBuilderIcon,
|
|
15
|
+
} from "./featureBuilderStringsActions";
|
|
16
|
+
import { toTitleCase } from "../utils/textUtils";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} FieldValues
|
|
20
|
+
* @property {string} [label] - Field label text
|
|
21
|
+
* @property {string} [placeholder] - Placeholder text for input
|
|
22
|
+
* @property {boolean} [isRequired] - Whether field is required
|
|
23
|
+
* @property {string} [helpText] - Help text for field guidance
|
|
24
|
+
* @property {boolean} [allowCaption] - Whether field allows captions
|
|
25
|
+
* @property {boolean} [useAsSummary] - Whether field is used as summary
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} FormField
|
|
30
|
+
* @property {string} id - Unique field identifier
|
|
31
|
+
* @property {string} type - Field type (text, title, description, image, file, cta, feature-image)
|
|
32
|
+
* @property {boolean} isMandatory - Whether field is mandatory system field
|
|
33
|
+
* @property {FieldValues} values - Field configuration values
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} LayoutConfig
|
|
38
|
+
* @property {string} type - Layout type (round, square, etc.)
|
|
39
|
+
* @property {string} [gridIcon] - Background image for grid layout
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export const actionsTypes = {
|
|
43
|
+
SET_INITIAL_VALUES: "SET_INITIAL_VALUES",
|
|
44
|
+
SET_TITLE: "SET_TITLE",
|
|
45
|
+
SET_ICON: "SET_ICON",
|
|
46
|
+
SET_DISPLAY_NAME: "SET_DISPLAY_NAME",
|
|
47
|
+
ADD_FIELD: "ADD_FIELD",
|
|
48
|
+
DELETE_FIELD: "DELETE_FIELD",
|
|
49
|
+
UPDATE_FIELD: "UPDATE_FIELD",
|
|
50
|
+
SET_LAYOUT_GRID_ICON: "SET_LAYOUT_GRID_ICON",
|
|
51
|
+
SET_LAYOUT_TYPE: "SET_LAYOUT_TYPE",
|
|
52
|
+
SUBMIT_FORM_REQUEST: "SUBMIT_FORM_REQUEST",
|
|
53
|
+
SUBMIT_FORM_SUCCESS: "SUBMIT_FORM_SUCCESS",
|
|
54
|
+
SUBMIT_FORM_FAILURE: "SUBMIT_FORM_FAILURE",
|
|
55
|
+
CLEAR_FORM_SUBMISSION_STATE: "CLEAR_FORM_SUBMISSION_STATE",
|
|
56
|
+
SET_SUMMARY_FIELD: "SET_SUMMARY_FIELD",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Action creator to set initial form values
|
|
61
|
+
* Initializes form state with existing feature definition data
|
|
62
|
+
*
|
|
63
|
+
* @param {Object} initialValues - Initial form values object
|
|
64
|
+
* @param {string} initialValues.title - Feature title
|
|
65
|
+
* @param {string} initialValues.icon - Feature icon
|
|
66
|
+
* @param {string} initialValues.displayName - Feature display name
|
|
67
|
+
* @param {LayoutConfig} initialValues.layout - Layout configuration
|
|
68
|
+
* @param {FormField[]} initialValues.fields - Form fields array
|
|
69
|
+
* @returns {Object} Redux action object with type and payload
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* dispatch(setInitialValues({
|
|
73
|
+
* title: 'Contact Form',
|
|
74
|
+
* icon: 'envelope',
|
|
75
|
+
* fields: []
|
|
76
|
+
* }));
|
|
77
|
+
*/
|
|
78
|
+
export const setInitialValues = (initialValues) => {
|
|
79
|
+
return { type: actionsTypes.SET_INITIAL_VALUES, payload: initialValues };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const setTitle = (title) => {
|
|
83
|
+
return { type: actionsTypes.SET_TITLE, payload: title };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Action creator to set form display name and update menu
|
|
88
|
+
* Updates both form state and external menu string
|
|
89
|
+
* Title cases the display name before updating the strings store
|
|
90
|
+
*
|
|
91
|
+
* @param {string} displayName - New display name for form
|
|
92
|
+
* @returns {Function} Thunk function for Redux
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* dispatch(setDisplayName('Contact Information'));
|
|
96
|
+
*/
|
|
97
|
+
export const setDisplayName = (displayName) => {
|
|
98
|
+
return (dispatch) => {
|
|
99
|
+
// Update menu string when display name changes (will be title cased in updateFeatureBuilderString)
|
|
100
|
+
dispatch(updateFeatureBuilderString(displayName));
|
|
101
|
+
|
|
102
|
+
// Dispatch the actual action
|
|
103
|
+
dispatch({
|
|
104
|
+
type: actionsTypes.SET_DISPLAY_NAME,
|
|
105
|
+
payload: displayName,
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Action creator to set form icon and update menu
|
|
112
|
+
* Updates both form state and external menu icon
|
|
113
|
+
*
|
|
114
|
+
* @param {string} icon - Icon identifier or FontAwesome icon class
|
|
115
|
+
* @returns {Function} Thunk function for Redux
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* dispatch(setIcon('fa-user'));
|
|
119
|
+
*/
|
|
120
|
+
export const setIcon = (icon) => {
|
|
121
|
+
return (dispatch) => {
|
|
122
|
+
// Update menu icon when icon changes
|
|
123
|
+
dispatch(updateFeatureBuilderIcon(icon));
|
|
124
|
+
|
|
125
|
+
// Dispatch the actual action
|
|
126
|
+
dispatch({
|
|
127
|
+
type: actionsTypes.SET_ICON,
|
|
128
|
+
payload: icon,
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Action creator to add a new field to the form
|
|
135
|
+
* Generates unique ID and supports custom field types
|
|
136
|
+
*
|
|
137
|
+
* @param {string} [fieldType="text"] - Type of field to add (text, title, description, image, gallery, file, cta, feature-image)
|
|
138
|
+
* @returns {Object} Redux action object with field ID and type
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* dispatch(addField('text'));
|
|
142
|
+
* dispatch(addField('image'));
|
|
143
|
+
*/
|
|
144
|
+
export const addField = (fieldType = "text") => {
|
|
145
|
+
// Generate a unique ID for the new field
|
|
146
|
+
const fieldId = `custom-field-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
147
|
+
return {
|
|
148
|
+
type: actionsTypes.ADD_FIELD,
|
|
149
|
+
payload: { id: fieldId, type: fieldType },
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const deleteField = (id) => {
|
|
154
|
+
return { type: actionsTypes.DELETE_FIELD, payload: id };
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export const updateFieldById = (id, updatedField) => {
|
|
158
|
+
return { type: actionsTypes.UPDATE_FIELD, payload: { id, updatedField } };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Action creator to set a description field as the summary field
|
|
163
|
+
* Ensures only one description field can be marked as summary by
|
|
164
|
+
* automatically unsetting all other description fields when a new one is selected
|
|
165
|
+
*
|
|
166
|
+
* @param {string} fieldId - ID of the description field to set as summary
|
|
167
|
+
* @returns {Object} Redux action object for summary field selection
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* dispatch(setSummaryField('field-description-123'));
|
|
171
|
+
*/
|
|
172
|
+
export const setSummaryField = (fieldId) => {
|
|
173
|
+
return { type: actionsTypes.SET_SUMMARY_FIELD, payload: fieldId };
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const setGridLayoutIcon = (imageUrl) => {
|
|
177
|
+
return { type: actionsTypes.SET_LAYOUT_GRID_ICON, payload: imageUrl };
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const setLayoutType = (layoutType) => {
|
|
181
|
+
return { type: actionsTypes.SET_LAYOUT_TYPE, payload: layoutType };
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const submitFormRequest = () => {
|
|
185
|
+
return { type: actionsTypes.SUBMIT_FORM_REQUEST };
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const submitFormSuccess = () => {
|
|
189
|
+
return { type: actionsTypes.SUBMIT_FORM_SUCCESS };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const submitFormFailure = (error) => {
|
|
193
|
+
return { type: actionsTypes.SUBMIT_FORM_FAILURE, payload: error };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const clearFormSubmissionState = () => {
|
|
197
|
+
return { type: actionsTypes.CLEAR_FORM_SUBMISSION_STATE };
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Submits the complete feature form to the server
|
|
202
|
+
* Handles form validation, API submission, and error handling
|
|
203
|
+
*
|
|
204
|
+
* @returns {Function} Async thunk function for Redux
|
|
205
|
+
* @throws {Error} When form validation fails or API submission encounters error
|
|
206
|
+
*/
|
|
207
|
+
export function submitForm() {
|
|
208
|
+
return async (dispatch, getState) => {
|
|
209
|
+
const state = getState()[values.reducerKey];
|
|
210
|
+
const form = state && state.form;
|
|
211
|
+
if (!form) {
|
|
212
|
+
dispatch(
|
|
213
|
+
submitFormFailure(
|
|
214
|
+
new Error(
|
|
215
|
+
"Form data is missing. Please refresh the page and try again.",
|
|
216
|
+
),
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
dispatch(submitFormRequest());
|
|
223
|
+
try {
|
|
224
|
+
// Get site from auth store
|
|
225
|
+
const site = getState().auth.site;
|
|
226
|
+
if (!site) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Use mode from fetch instead of checking definition ID
|
|
233
|
+
const definitionState = state && state.definition;
|
|
234
|
+
const mode = definitionState && definitionState.mode; // Use stored mode from fetch
|
|
235
|
+
const definitionId = definitionState && definitionState.id;
|
|
236
|
+
|
|
237
|
+
if (mode === "edit") {
|
|
238
|
+
// Always update when in edit mode
|
|
239
|
+
const updatedDefinition = {
|
|
240
|
+
id: definitionId,
|
|
241
|
+
site: site, // Include site from auth store
|
|
242
|
+
featureDefinition: {
|
|
243
|
+
// Wrap in expected structure for backend
|
|
244
|
+
title: form.title,
|
|
245
|
+
icon: form.icon,
|
|
246
|
+
displayName: form.displayName,
|
|
247
|
+
layout: form.layout,
|
|
248
|
+
fields: form.fields,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
await featureDefinitionActions.edit(updatedDefinition, site);
|
|
252
|
+
} else {
|
|
253
|
+
// Always create when in create mode (or mode is undefined/null)
|
|
254
|
+
if (!values.featureId || !site) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
"Authentication error: Missing required context (featureId or site).",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
await featureDefinitionActions.create(
|
|
260
|
+
values.featureId,
|
|
261
|
+
site, // Use actual site from auth store
|
|
262
|
+
{
|
|
263
|
+
title: form.title,
|
|
264
|
+
icon: form.icon,
|
|
265
|
+
displayName: form.displayName,
|
|
266
|
+
layout: form.layout,
|
|
267
|
+
fields: form.fields,
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
dispatch(submitFormSuccess());
|
|
273
|
+
} catch (err) {
|
|
274
|
+
// Handle different types of errors
|
|
275
|
+
let errorToDisplay = err;
|
|
276
|
+
|
|
277
|
+
if (err.response) {
|
|
278
|
+
// API error (400, 401, 404, 500, etc.)
|
|
279
|
+
const { status, data } = err.response;
|
|
280
|
+
if (status === 400 && data && data.error) {
|
|
281
|
+
errorToDisplay = new Error(`Validation error: ${data.error}`);
|
|
282
|
+
} else if (status === 401) {
|
|
283
|
+
errorToDisplay = new Error(
|
|
284
|
+
"You are not authorized to perform this action",
|
|
285
|
+
);
|
|
286
|
+
} else if (status === 404) {
|
|
287
|
+
errorToDisplay = new Error("Feature definition not found");
|
|
288
|
+
} else if (status >= 500) {
|
|
289
|
+
errorToDisplay = new Error("Server error. Please try again later.");
|
|
290
|
+
} else {
|
|
291
|
+
errorToDisplay = new Error(
|
|
292
|
+
(data && data.error) || `Request failed with status ${status}`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
} else if (err.request) {
|
|
296
|
+
// Network error (no response received)
|
|
297
|
+
errorToDisplay = new Error(
|
|
298
|
+
"Network error. Please check your connection and try again.",
|
|
299
|
+
);
|
|
300
|
+
} else if (err.message) {
|
|
301
|
+
// Other JavaScript errors
|
|
302
|
+
errorToDisplay = err;
|
|
303
|
+
} else {
|
|
304
|
+
// Unknown error
|
|
305
|
+
errorToDisplay = new Error("An unexpected error occurred while saving");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
dispatch(submitFormFailure(errorToDisplay));
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PlussCore } from "../feature.config";
|
|
2
|
+
|
|
3
|
+
const { Actions } = PlussCore;
|
|
4
|
+
export const setAuth = Actions.setAuth;
|
|
5
|
+
export const usersLoaded = Actions.usersLoaded;
|
|
6
|
+
export const addRecentlyCreated = Actions.addRecentlyCreated;
|
|
7
|
+
export const setNavData = Actions.setNavData;
|
|
8
|
+
|
|
9
|
+
// Feature Builder specific actions
|
|
10
|
+
export * from "./featureDefinitionsIndex";
|
|
11
|
+
export * from "./listingActions";
|
|
12
|
+
export * from "./formActions";
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { arrayMove } from "@dnd-kit/sortable";
|
|
2
|
+
import { listingActions as webApiActions } from "../webapi/listingActions";
|
|
3
|
+
|
|
4
|
+
// Action Types
|
|
5
|
+
export const FETCH_LISTING_REQUEST = "FETCH_LISTING_REQUEST";
|
|
6
|
+
export const FETCH_LISTING_SUCCESS = "FETCH_LISTING_SUCCESS";
|
|
7
|
+
export const FETCH_LISTING_FAILURE = "FETCH_LISTING_FAILURE";
|
|
8
|
+
export const FETCH_LISTING_SILENT_SUCCESS = "FETCH_LISTING_SILENT_SUCCESS";
|
|
9
|
+
export const FETCH_LISTING_SILENT_FAILURE = "FETCH_LISTING_SILENT_FAILURE";
|
|
10
|
+
|
|
11
|
+
export const REORDER_LISTING_SUCCESS = "REORDER_LISTING_SUCCESS";
|
|
12
|
+
export const REORDER_LISTING_FAILURE = "REORDER_LISTING_FAILURE";
|
|
13
|
+
|
|
14
|
+
export const DELETE_LISTING_REQUEST = "DELETE_LISTING_REQUEST";
|
|
15
|
+
export const DELETE_LISTING_SUCCESS = "DELETE_LISTING_SUCCESS";
|
|
16
|
+
export const DELETE_LISTING_FAILURE = "DELETE_LISTING_FAILURE";
|
|
17
|
+
|
|
18
|
+
export const UNDELETE_LISTING_REQUEST = "UNDELETE_LISTING_REQUEST";
|
|
19
|
+
export const UNDELETE_LISTING_SUCCESS = "UNDELETE_LISTING_SUCCESS";
|
|
20
|
+
export const UNDELETE_LISTING_FAILURE = "UNDELETE_LISTING_FAILURE";
|
|
21
|
+
|
|
22
|
+
export const CREATE_LISTING_REQUEST = "CREATE_LISTING_REQUEST";
|
|
23
|
+
export const CREATE_LISTING_SUCCESS = "CREATE_LISTING_SUCCESS";
|
|
24
|
+
export const CREATE_LISTING_FAILURE = "CREATE_LISTING_FAILURE";
|
|
25
|
+
|
|
26
|
+
export const EDIT_LISTING_REQUEST = "EDIT_LISTING_REQUEST";
|
|
27
|
+
export const EDIT_LISTING_SUCCESS = "EDIT_LISTING_SUCCESS";
|
|
28
|
+
export const EDIT_LISTING_FAILURE = "EDIT_LISTING_FAILURE";
|
|
29
|
+
|
|
30
|
+
export const TOGGLE_LISTING_REQUEST = "TOGGLE_LISTING_REQUEST";
|
|
31
|
+
export const TOGGLE_LISTING_SUCCESS = "TOGGLE_LISTING_SUCCESS";
|
|
32
|
+
export const TOGGLE_LISTING_FAILURE = "TOGGLE_LISTING_FAILURE";
|
|
33
|
+
|
|
34
|
+
export const SET_SORT_BY = "SET_SORT_BY";
|
|
35
|
+
export const SET_SHOW_DELETED = "SET_SHOW_DELETED";
|
|
36
|
+
|
|
37
|
+
export const fetchListingRequest = () => ({ type: FETCH_LISTING_REQUEST });
|
|
38
|
+
export const fetchListingSuccess = (listings) => ({
|
|
39
|
+
type: FETCH_LISTING_SUCCESS,
|
|
40
|
+
payload: listings,
|
|
41
|
+
});
|
|
42
|
+
export const fetchListingFailure = (error) => ({
|
|
43
|
+
type: FETCH_LISTING_FAILURE,
|
|
44
|
+
payload: error,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const fetchListingSilentSuccess = (listings) => ({
|
|
48
|
+
type: FETCH_LISTING_SILENT_SUCCESS,
|
|
49
|
+
payload: listings,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const fetchListingSilentFailure = (error) => ({
|
|
53
|
+
type: FETCH_LISTING_SILENT_FAILURE,
|
|
54
|
+
payload: error,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const fetchListing = () => {
|
|
58
|
+
return async (dispatch, getState) => {
|
|
59
|
+
dispatch(fetchListingRequest());
|
|
60
|
+
try {
|
|
61
|
+
// Get site from auth store
|
|
62
|
+
const site = getState().auth.site;
|
|
63
|
+
if (!site) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const response = await webApiActions.getAll(site);
|
|
69
|
+
// Assuming your API returns the listings directly or in a `data` property
|
|
70
|
+
const listings = response.data.listings;
|
|
71
|
+
|
|
72
|
+
dispatch(fetchListingSuccess(listings));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
dispatch(
|
|
75
|
+
fetchListingFailure(error.message || "Failed to fetch listings"),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const fetchListingSilent = () => {
|
|
82
|
+
return async (dispatch, getState) => {
|
|
83
|
+
try {
|
|
84
|
+
// Get site from auth store
|
|
85
|
+
const site = getState().auth.site;
|
|
86
|
+
if (!site) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const response = await webApiActions.getAll(site);
|
|
92
|
+
// Assuming your API returns the listings directly or in a `data` property
|
|
93
|
+
const listings = response.data.listings;
|
|
94
|
+
|
|
95
|
+
dispatch(fetchListingSilentSuccess(listings));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
dispatch(
|
|
98
|
+
fetchListingSilentFailure(error.message || "Failed to fetch listings"),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const fetchSingleListing = (id) => {
|
|
105
|
+
return async (dispatch, getState) => {
|
|
106
|
+
dispatch(fetchListingRequest());
|
|
107
|
+
try {
|
|
108
|
+
// Get site from auth store
|
|
109
|
+
const site = getState().auth.site;
|
|
110
|
+
if (!site) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const response = await webApiActions.getSingle(id, site);
|
|
116
|
+
dispatch(fetchListingSuccess([response.data])); // Return as array for consistency
|
|
117
|
+
} catch (error) {
|
|
118
|
+
dispatch(fetchListingFailure(error.message || "Failed to fetch listing"));
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const reorderListingSuccess = (listings) => ({
|
|
124
|
+
type: REORDER_LISTING_SUCCESS,
|
|
125
|
+
payload: listings,
|
|
126
|
+
});
|
|
127
|
+
export const reorderListingFailure = (error) => ({
|
|
128
|
+
type: REORDER_LISTING_FAILURE,
|
|
129
|
+
payload: error,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
export const reorderListing = (listings, oldIndex, newIndex) => {
|
|
133
|
+
return async (dispatch) => {
|
|
134
|
+
// Optimistic UI update: update the state immediately
|
|
135
|
+
const reorderedListings = arrayMove(listings, oldIndex, newIndex);
|
|
136
|
+
dispatch(reorderListingSuccess(reorderedListings));
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Note: Reorder functionality not available in current API spec
|
|
140
|
+
// This would need to be implemented if needed
|
|
141
|
+
// await listingActions.updateOrder(reorderedListings);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// On failure, you might want to revert the state.
|
|
144
|
+
dispatch(
|
|
145
|
+
reorderListingFailure(error.message || "Failed to save new order"),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const deleteListingRequest = (id) => ({
|
|
152
|
+
type: DELETE_LISTING_REQUEST,
|
|
153
|
+
payload: id,
|
|
154
|
+
});
|
|
155
|
+
export const deleteListingSuccess = (id) => ({
|
|
156
|
+
type: DELETE_LISTING_SUCCESS,
|
|
157
|
+
payload: id,
|
|
158
|
+
});
|
|
159
|
+
export const deleteListingFailure = (error) => ({
|
|
160
|
+
type: DELETE_LISTING_FAILURE,
|
|
161
|
+
payload: error,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
export const deleteListing = (id) => {
|
|
165
|
+
return async (dispatch, getState) => {
|
|
166
|
+
dispatch(deleteListingRequest(id));
|
|
167
|
+
try {
|
|
168
|
+
// Get site from auth store
|
|
169
|
+
const site = getState().auth.site;
|
|
170
|
+
if (!site) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
await webApiActions.delete(id, site);
|
|
176
|
+
dispatch(deleteListingSuccess(id));
|
|
177
|
+
return { success: true };
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const errorMessage = error.message || "Failed to delete listing";
|
|
180
|
+
dispatch(deleteListingFailure(errorMessage));
|
|
181
|
+
throw new Error(errorMessage);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const undeleteListingRequest = (id) => ({
|
|
187
|
+
type: UNDELETE_LISTING_REQUEST,
|
|
188
|
+
payload: id,
|
|
189
|
+
});
|
|
190
|
+
export const undeleteListingSuccess = (listing) => ({
|
|
191
|
+
type: UNDELETE_LISTING_SUCCESS,
|
|
192
|
+
payload: listing,
|
|
193
|
+
});
|
|
194
|
+
export const undeleteListingFailure = (error) => ({
|
|
195
|
+
type: UNDELETE_LISTING_FAILURE,
|
|
196
|
+
payload: error,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export const undeleteListing = (id) => {
|
|
200
|
+
return async (dispatch, getState) => {
|
|
201
|
+
dispatch(undeleteListingRequest(id));
|
|
202
|
+
try {
|
|
203
|
+
// Get site from auth store
|
|
204
|
+
const site = getState().auth.site;
|
|
205
|
+
if (!site) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const response = await webApiActions.undelete(id, site);
|
|
211
|
+
// If API returns the restored listing, use it; otherwise we'll need to refetch
|
|
212
|
+
const restoredListing = response.data;
|
|
213
|
+
if (restoredListing && restoredListing.id) {
|
|
214
|
+
dispatch(undeleteListingSuccess(restoredListing));
|
|
215
|
+
} else {
|
|
216
|
+
// Trigger a refetch by dispatching the success with just ID, then fetch updated listings
|
|
217
|
+
dispatch(undeleteListingSuccess(id));
|
|
218
|
+
// Fetch updated listings to get the restored one
|
|
219
|
+
dispatch(fetchListingSilent());
|
|
220
|
+
}
|
|
221
|
+
return { success: true };
|
|
222
|
+
} catch (error) {
|
|
223
|
+
const errorMessage = error.message || "Failed to restore listing";
|
|
224
|
+
dispatch(undeleteListingFailure(errorMessage));
|
|
225
|
+
throw new Error(errorMessage);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const createListingRequest = () => ({ type: CREATE_LISTING_REQUEST });
|
|
231
|
+
export const createListingSuccess = (listing) => ({
|
|
232
|
+
type: CREATE_LISTING_SUCCESS,
|
|
233
|
+
payload: listing,
|
|
234
|
+
});
|
|
235
|
+
export const createListingFailure = (error) => ({
|
|
236
|
+
type: CREATE_LISTING_FAILURE,
|
|
237
|
+
payload: error,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
export const createListing = (listing) => {
|
|
241
|
+
return async (dispatch, getState) => {
|
|
242
|
+
dispatch(createListingRequest());
|
|
243
|
+
try {
|
|
244
|
+
// Get site from auth store
|
|
245
|
+
const site = getState().auth.site;
|
|
246
|
+
if (!site) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const response = await webApiActions.create(listing, site);
|
|
252
|
+
|
|
253
|
+
// Assuming the API returns the newly created listing
|
|
254
|
+
const newListing = response.data || response;
|
|
255
|
+
dispatch(createListingSuccess(newListing));
|
|
256
|
+
return newListing;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const errorMessage =
|
|
259
|
+
error.response?.data?.message ||
|
|
260
|
+
error.message ||
|
|
261
|
+
"Failed to create listing";
|
|
262
|
+
dispatch(createListingFailure(errorMessage));
|
|
263
|
+
throw error; // Re-throw to allow component to handle
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const editListingRequest = () => ({ type: EDIT_LISTING_REQUEST });
|
|
269
|
+
export const editListingSuccess = (listing) => ({
|
|
270
|
+
type: EDIT_LISTING_SUCCESS,
|
|
271
|
+
payload: listing,
|
|
272
|
+
});
|
|
273
|
+
export const editListingFailure = (error) => ({
|
|
274
|
+
type: EDIT_LISTING_FAILURE,
|
|
275
|
+
payload: error,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
export const editListing = (listing) => {
|
|
279
|
+
return async (dispatch, getState) => {
|
|
280
|
+
dispatch(editListingRequest());
|
|
281
|
+
try {
|
|
282
|
+
// Get site from auth store
|
|
283
|
+
const site = getState().auth.site;
|
|
284
|
+
if (!site) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
const response = await webApiActions.edit(listing, site);
|
|
290
|
+
const updatedListing = response.data || response;
|
|
291
|
+
dispatch(editListingSuccess(updatedListing));
|
|
292
|
+
return updatedListing;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
const errorMessage =
|
|
295
|
+
error.response?.data?.message ||
|
|
296
|
+
error.message ||
|
|
297
|
+
"Failed to update listing";
|
|
298
|
+
dispatch(editListingFailure(errorMessage));
|
|
299
|
+
throw error; // Re-throw to allow component to handle
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export const toggleListingRequest = (id) => ({
|
|
305
|
+
type: TOGGLE_LISTING_REQUEST,
|
|
306
|
+
payload: id,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
export const toggleListingSuccess = (id, isActive) => ({
|
|
310
|
+
type: TOGGLE_LISTING_SUCCESS,
|
|
311
|
+
payload: { id, isActive },
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
export const toggleListingFailure = (error) => ({
|
|
315
|
+
type: TOGGLE_LISTING_FAILURE,
|
|
316
|
+
payload: error,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
export const toggleListing = (id, isActive) => {
|
|
320
|
+
return async (dispatch, getState) => {
|
|
321
|
+
dispatch(toggleListingRequest(id));
|
|
322
|
+
try {
|
|
323
|
+
// Get site from auth store
|
|
324
|
+
const site = getState().auth.site;
|
|
325
|
+
if (!site) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
"Authentication error: Site context not found. Please refresh and login again.",
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
// Update the listing with the new active state
|
|
331
|
+
await webApiActions.edit({ id, isActive }, site);
|
|
332
|
+
dispatch(toggleListingSuccess(id, isActive));
|
|
333
|
+
return { success: true };
|
|
334
|
+
} catch (error) {
|
|
335
|
+
const errorMessage = error.message || "Failed to toggle listing";
|
|
336
|
+
dispatch(toggleListingFailure(errorMessage));
|
|
337
|
+
throw new Error(errorMessage);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export const setSortBy = (sortBy) => ({
|
|
343
|
+
type: SET_SORT_BY,
|
|
344
|
+
payload: sortBy,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
export const setShowDeleted = (showDeleted) => ({
|
|
348
|
+
type: SET_SHOW_DELETED,
|
|
349
|
+
payload: showDeleted,
|
|
350
|
+
});
|