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