@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,153 @@
1
+ import React, { useRef, useState, useEffect } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { ImageInput, Text, GenericInput } from "../../components";
4
+ import { iconImports } from "../../components/iconImports.js";
5
+ import styles from "./ListingField.module.css";
6
+
7
+ const ListingImageInput = ({
8
+ field,
9
+ value,
10
+ onChange,
11
+ showError,
12
+ errorMessage,
13
+ disabled = false,
14
+ }) => {
15
+ const imageInputRef = useRef(null);
16
+ const [imageUrl, setImageUrl] = useState("");
17
+ const [caption, setCaption] = useState("");
18
+
19
+ // Initialize state from value prop (handle backward compatibility)
20
+ useEffect(() => {
21
+ if (value) {
22
+ if (typeof value === "object" && value !== null) {
23
+ // New format: { url: "image-url", caption: "caption text" }
24
+ setImageUrl(value.url || "");
25
+ setCaption(value.caption || "");
26
+ } else {
27
+ // Legacy format: simple string URL
28
+ setImageUrl(value);
29
+ setCaption("");
30
+ }
31
+ } else {
32
+ setImageUrl("");
33
+ setCaption("");
34
+ }
35
+ }, [value]);
36
+
37
+ const handleImageChange = (imageData) => {
38
+ // Handle different data structures from ImageInput
39
+ let newImageUrl = imageData;
40
+
41
+ // If imageData is an object (from feature library), extract the URL
42
+ if (typeof imageData === "object" && imageData !== null) {
43
+ newImageUrl =
44
+ imageData.url ||
45
+ imageData.src ||
46
+ imageData.imageUrl ||
47
+ JSON.stringify(imageData);
48
+ }
49
+
50
+ setImageUrl(newImageUrl);
51
+ updateValue(newImageUrl, caption);
52
+ };
53
+
54
+ const handleCaptionChange = (newCaption) => {
55
+ setCaption(newCaption);
56
+ updateValue(imageUrl, newCaption);
57
+ };
58
+
59
+ const updateValue = (url, captionText) => {
60
+ // If captions are enabled, store as object, otherwise as string for backward compatibility
61
+ if (field.values.allowCaption) {
62
+ onChange(field.id, { url, caption: captionText });
63
+ } else {
64
+ onChange(field.id, url);
65
+ }
66
+ };
67
+
68
+ return (
69
+ <div>
70
+ {/* Field header with icon and description */}
71
+ <div className={styles.listingField__header}>
72
+ <div className={styles.listingField__icon}>
73
+ <FontAwesomeIcon icon={iconImports.image} />
74
+ </div>
75
+ <div className={styles.listingField__content}>
76
+ <h3 className={styles.listingField__title}>Image</h3>
77
+ <p className={styles.listingField__description}>
78
+ Upload a single photo or image to accompany your content
79
+ </p>
80
+ </div>
81
+ </div>
82
+
83
+ <Text type="formLabel" style={{ display: "block", marginBottom: "8px" }}>
84
+ {field.values.label}
85
+ {field.values.isRequired && (
86
+ <span className={styles.requiredAsterisk}>*</span>
87
+ )}
88
+ </Text>
89
+ <div style={{ marginTop: "12px" }}>
90
+ <div
91
+ style={{
92
+ border: showError ? "1px solid var(--colour-error)" : "none",
93
+ borderRadius: "4px",
94
+ padding: showError ? "0.1rem" : "0",
95
+ backgroundColor: showError
96
+ ? "var(--colour-error-light, rgba(220, 53, 69, 0.05))"
97
+ : "transparent",
98
+ }}
99
+ >
100
+ <ImageInput
101
+ ref={imageInputRef}
102
+ containerStyle={{ width: 293, height: 168 }}
103
+ className="imageInputOuter-single"
104
+ refreshCallback={handleImageChange}
105
+ hasDefault={imageUrl}
106
+ disabled={disabled}
107
+ />
108
+ </div>
109
+ {showError && errorMessage && (
110
+ <Text
111
+ type="help"
112
+ style={{
113
+ color: "var(--colour-red)",
114
+ marginTop: "0.2rem",
115
+ display: "block",
116
+ }}
117
+ >
118
+ {errorMessage}
119
+ </Text>
120
+ )}
121
+ </div>
122
+
123
+ {/* Caption input field - only show if captions are enabled for this field type */}
124
+ {field.values.allowCaption && (
125
+ <div style={{ marginTop: "12px" }}>
126
+ <GenericInput
127
+ type="text"
128
+ value={caption}
129
+ placeholder="Add a caption for this image (optional)"
130
+ label="Image Caption"
131
+ onChange={(e) => handleCaptionChange(e.target.value)}
132
+ alwaysShowLabel
133
+ />
134
+ </div>
135
+ )}
136
+
137
+ {field.values.helpText && (
138
+ <Text
139
+ type="help"
140
+ style={{
141
+ color: "var(--text-bluegrey)",
142
+ marginTop: "8px",
143
+ display: "block",
144
+ }}
145
+ >
146
+ {field.values.helpText}
147
+ </Text>
148
+ )}
149
+ </div>
150
+ );
151
+ };
152
+
153
+ export default ListingImageInput;
@@ -0,0 +1,72 @@
1
+ import React from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { GenericInput, Text } from "../index.js";
4
+ import { iconImports } from "../iconImports.js";
5
+ import styles from "./ListingField.module.css";
6
+
7
+ const ListingTextInput = ({
8
+ field,
9
+ value,
10
+ onChange,
11
+ showError,
12
+ errorMessage,
13
+ }) => {
14
+ const { label, placeholder, isRequired, helpText } = field.values;
15
+
16
+ const handleChange = (e) => {
17
+ onChange(field.id, e.target.value);
18
+ };
19
+
20
+ const isValid = () => {
21
+ if (isRequired) {
22
+ const isEmpty =
23
+ !value || (typeof value === "string" && value.trim() === "");
24
+ return !isEmpty;
25
+ }
26
+ return true;
27
+ };
28
+
29
+ const displayLabel = (
30
+ <>
31
+ {label}
32
+ {isRequired && <span className={styles.requiredAsterisk}>*</span>}
33
+ </>
34
+ );
35
+
36
+ return (
37
+ <>
38
+ {/* Field header with icon and description */}
39
+ <div className={styles.listingField__header}>
40
+ <div className={styles.listingField__icon}>
41
+ <FontAwesomeIcon icon={iconImports.pen} />
42
+ </div>
43
+ <div className={styles.listingField__content}>
44
+ <h3 className={styles.listingField__title}>Title</h3>
45
+ <p className={styles.listingField__description}>
46
+ The main title for you listing
47
+ </p>
48
+ </div>
49
+ </div>
50
+
51
+ <GenericInput
52
+ type="text"
53
+ label={displayLabel}
54
+ placeholder={placeholder || ""}
55
+ value={value || ""}
56
+ onChange={handleChange}
57
+ isRequired={isRequired}
58
+ showError={!!showError}
59
+ errorMessage={errorMessage}
60
+ isValid={isValid()}
61
+ alwaysShowLabel
62
+ />
63
+ {helpText && (
64
+ <div style={{ marginTop: "8px", color: "#6c7a90", fontSize: "14px" }}>
65
+ {helpText}
66
+ </div>
67
+ )}
68
+ </>
69
+ );
70
+ };
71
+
72
+ export default ListingTextInput;
@@ -0,0 +1,130 @@
1
+ // import * as PlussCore from '../../pluss-core/src';
2
+ import * as PlussCore from "@plusscommunities/pluss-core-web";
3
+ import { values } from "./values.config";
4
+ import { selectHasDefinition } from "./selectors/featureBuilderSelectors";
5
+
6
+ export { PlussCore };
7
+
8
+ const FeatureConfig = {
9
+ key: values.featureKey,
10
+ singularName: values.singularName,
11
+ description: values.description,
12
+ emptyText: values.emptyText,
13
+ widgetOptions: [],
14
+ hiddenFromFeaturePicker: (state) => {
15
+ // Hide feature builder from picker if no definitions exist
16
+ // This prevents users from adding feature builder when there are no feature definitions configured
17
+ return !selectHasDefinition(state);
18
+ },
19
+
20
+ addUrl: values.routeFormOverviewStep,
21
+ baseUrl: values.featureKeyRoute,
22
+ addPermission: values.permissionFeatureBuilderDefinition,
23
+ activities: [],
24
+ permissions: [
25
+ {
26
+ displayName: values.textPermissionFeatureBuilderDefinition,
27
+ key: values.permissionFeatureBuilderDefinition,
28
+ hq: true,
29
+ },
30
+ {
31
+ displayName: values.textPermissionFeatureBuilderContent,
32
+ key: values.permissionFeatureBuilderContent,
33
+ hq: false,
34
+ },
35
+ ],
36
+ menu: [
37
+ {
38
+ // HQ-only definition navitem
39
+ key: values.menuKey,
40
+ order: 16,
41
+ text: "Build Your Feature",
42
+ icon: values.defaultIcon,
43
+ isFontAwesome: true,
44
+ url: values.routeFormOverviewStep,
45
+ visibleExps: {
46
+ type: "and",
47
+ exps: [
48
+ {
49
+ type: "master",
50
+ value: false,
51
+ },
52
+ {
53
+ type: "hq",
54
+ value: true,
55
+ },
56
+ ],
57
+ },
58
+ },
59
+ {
60
+ order: values.menuOrder,
61
+ text: "",
62
+ icon: values.menuIcon,
63
+ isFontAwesome: values.menuIsFontAwesome,
64
+ url: values.featureKeyRoute + "/listing",
65
+ countProps: null,
66
+ visibleExps: {
67
+ type: "and",
68
+ exps: [
69
+ { type: "feature", value: values.featureKey },
70
+ { type: "permission", value: values.permissionFeatureBuilderContent },
71
+ ],
72
+ },
73
+ },
74
+ ],
75
+ routes: [
76
+ {
77
+ path: values.routeFormOverviewStep,
78
+ component: values.screenFormOverviewStep,
79
+ exact: false, // Changed from exact:true to allow internal navigation
80
+ },
81
+ {
82
+ path: values.routeFormFieldsStep,
83
+ component: values.screenFormFieldsStep,
84
+ exact: false,
85
+ },
86
+ {
87
+ path: values.routeFormLayoutStep,
88
+ component: values.screenFormLayoutStep,
89
+ exact: false,
90
+ },
91
+ {
92
+ path: values.routeListingScreen,
93
+ component: values.screenListingScreen,
94
+ exact: true,
95
+ },
96
+
97
+ {
98
+ path: values.routeCreateListing,
99
+ component: values.pageCreateListing,
100
+ exact: false,
101
+ },
102
+ {
103
+ path: values.routeEditListing,
104
+ component: values.pageEditListing,
105
+ exact: false,
106
+ },
107
+ ],
108
+ env: {
109
+ baseStage: "",
110
+ baseAPIUrl: "",
111
+ baseUploadsUrl: "",
112
+ uploadBucket: "",
113
+ colourBrandingMain: "",
114
+ colourBrandingOff: "",
115
+ colourBrandingApp: "",
116
+ defaultProfileImage: "",
117
+ utcOffset: "",
118
+ hasAvailableNews: false,
119
+ newsHaveTags: true,
120
+ defaultAllowComments: true,
121
+ makeApiKey: "",
122
+ logo: "",
123
+ clientName: "",
124
+ },
125
+ init: (environment) => {
126
+ FeatureConfig.env = environment;
127
+ PlussCore.Config.init(environment);
128
+ },
129
+ };
130
+ export default FeatureConfig;
@@ -0,0 +1,135 @@
1
+ import { PlussCore } from "../feature.config";
2
+
3
+ const { Helper } = PlussCore;
4
+
5
+ /**
6
+ * Validates if a string is a valid email format
7
+ *
8
+ * @param {string} email - Email address to validate
9
+ * @returns {boolean} True if valid email format, false otherwise
10
+ *
11
+ * @example
12
+ * // Returns true
13
+ * isEmail('user@example.com');
14
+ * // Returns false
15
+ * isEmail('invalid-email');
16
+ */
17
+ export const isEmail = Helper.isEmail;
18
+
19
+ /**
20
+ * Gets the plural form of a word based on count
21
+ *
22
+ * @param {number} count - Number to determine plural form
23
+ * @returns {string} Plural form of the word
24
+ *
25
+ * @example
26
+ * // Returns 'item'
27
+ * getPluralS(1);
28
+ * // Returns 'items'
29
+ * getPluralS(2);
30
+ */
31
+ export const getPluralS = Helper.getPluralS;
32
+
33
+ /**
34
+ * Parses URL parameters from a URL string
35
+ *
36
+ * @param {string} url - URL string to parse
37
+ * @returns {Object} Object containing URL parameters as key-value pairs
38
+ *
39
+ * @example
40
+ * // Returns { id: '123', tab: 'profile' }
41
+ * getUrlParams('/users?id=123&tab=profile');
42
+ */
43
+ export const getUrlParams = Helper.getUrlParams;
44
+
45
+ /**
46
+ * Safely reads and parses URL parameters
47
+ *
48
+ * @param {URLSearchParams} params - URL search parameters
49
+ * @returns {Object} Safely parsed parameters object
50
+ */
51
+ export const safeReadParams = Helper.safeReadParams;
52
+
53
+ /**
54
+ * Generates a 300x300 thumbnail URL
55
+ *
56
+ * @param {string} url - Original image URL
57
+ * @returns {string} Thumbnail URL with 300x300 dimensions
58
+ */
59
+ export const getThumb300 = Helper.getThumb300;
60
+
61
+ /**
62
+ * Gets 1400x0 size dimensions
63
+ *
64
+ * @param {string} url - Image URL to analyze
65
+ * @returns {Object} Object containing width and height
66
+ */
67
+ export const get1400 = Helper.get1400;
68
+
69
+ /**
70
+ * Converts time date pickers to UTC timestamps
71
+ *
72
+ * @param {Array} pickers - Array of time date picker objects
73
+ * @returns {Object} UTC timestamp conversion result
74
+ */
75
+ export const getUTCFromTimeDatePickers = Helper.getUTCFromTimeDatePickers;
76
+
77
+ /**
78
+ * Gets pluralization options for different languages
79
+ *
80
+ * @returns {Object} Pluralization configuration object
81
+ */
82
+ export const getPluralOptions = Helper.getPluralOptions;
83
+
84
+ /**
85
+ * Checks if a URL contains video content
86
+ *
87
+ * @param {string} url - URL to check
88
+ * @returns {boolean} True if contains video content
89
+ */
90
+ export const isVideo = Helper.isVideo;
91
+
92
+ /**
93
+ * Extracts filename from a URL or file path
94
+ *
95
+ * @param {string} path - Path or URL to extract filename from
96
+ * @returns {string} Filename without extension and path
97
+ *
98
+ * @example
99
+ * // Returns 'document'
100
+ * getFileName('/path/to/document.pdf');
101
+ */
102
+ export const getFileName = Helper.getFileName;
103
+
104
+ /**
105
+ * Gets site name from user roles array
106
+ *
107
+ * @param {Array} roles - Array of user role objects
108
+ * @returns {string} Site name derived from roles
109
+ */
110
+ export const getSiteNameFromRoles = Helper.getSiteNameFromRoles;
111
+
112
+ /**
113
+ * Filters string to only alphanumeric characters
114
+ *
115
+ * @param {string} str - String to filter
116
+ * @returns {string} Filtered string containing only alphanumeric characters
117
+ */
118
+ export const onlyAlphanumeric = Helper.onlyAlphanumeric;
119
+
120
+ /**
121
+ * Stores data in browser's localStorage
122
+ *
123
+ * @param {string} key - Storage key
124
+ * @param {*} value - Value to store
125
+ * @returns {boolean} True if successfully stored
126
+ */
127
+ export const setLocalStorage = Helper.setLocalStorage;
128
+
129
+ /**
130
+ * Reads and parses JSON data from localStorage
131
+ *
132
+ * @param {string} key - Storage key to read from
133
+ * @returns {Object|null} Parsed JSON object or null if not found
134
+ */
135
+ export const readJSONFromStorage = Helper.readJSONFromStorage;
@@ -0,0 +1,62 @@
1
+ import { useEffect } from "react";
2
+ import { useDispatch, useSelector } from "react-redux";
3
+ import { fetchFeatureDefinitions } from "../actions/featureDefinitionsIndex";
4
+ import {
5
+ selectDefinition,
6
+ selectDefinitionIsLoading,
7
+ selectDefinitionError,
8
+ } from "../selectors/featureBuilderSelectors";
9
+
10
+ /**
11
+ * Custom hook to handle loading feature definition in any step
12
+ * Ensures definition is loaded and provides loading/error states
13
+ * Triggers definition fetch when needed (e.g., on component mount or refresh)
14
+ *
15
+ * @param {Object} options - Configuration options
16
+ * @param {boolean} [options.forceReload=false] - Force reload even if definition exists
17
+ * @returns {Object} Loading state and error information
18
+ * @returns {boolean} returns.isLoading - Whether definition is currently loading
19
+ * @returns {Error|null} returns.error - Any error that occurred during loading
20
+ * @returns {Object|null} returns.definition - The loaded definition (if available)
21
+ * @returns {Function} returns.reloadDefinition - Function to manually reload definition
22
+ *
23
+ * @example
24
+ * const { isLoading, error, definition, reloadDefinition } = useFeatureDefinitionLoader();
25
+ *
26
+ * @example
27
+ * // Force reload on mount
28
+ * const { isLoading, error, definition } = useFeatureDefinitionLoader({ forceReload: true });
29
+ */
30
+ export const useFeatureDefinitionLoader = (options = {}) => {
31
+ const { forceReload = false } = options;
32
+ const dispatch = useDispatch();
33
+
34
+ // Selectors for definition state
35
+ const definition = useSelector(selectDefinition);
36
+ const isLoading = useSelector(selectDefinitionIsLoading);
37
+ const error = useSelector(selectDefinitionError);
38
+
39
+ // Determine if we need to load the definition
40
+ const shouldLoadDefinition = !definition || forceReload;
41
+
42
+ // Function to manually reload definition
43
+ const reloadDefinition = () => {
44
+ dispatch(fetchFeatureDefinitions());
45
+ };
46
+
47
+ // Load definition when needed
48
+ useEffect(() => {
49
+ if (shouldLoadDefinition && !isLoading) {
50
+ dispatch(fetchFeatureDefinitions());
51
+ }
52
+ }, [shouldLoadDefinition, isLoading, dispatch]);
53
+
54
+ return {
55
+ isLoading,
56
+ error,
57
+ definition,
58
+ reloadDefinition,
59
+ };
60
+ };
61
+
62
+ export default useFeatureDefinitionLoader;
Binary file
Binary file
Binary file
Binary file
package/src/index.js ADDED
@@ -0,0 +1,38 @@
1
+ import { FormOverviewStep } from "./screens/FormOverviewStep.jsx";
2
+ import { FormFieldsStep } from "./screens/FormFieldsStep.jsx";
3
+ import { FormLayoutStep } from "./screens/FormLayoutStep.jsx";
4
+ import { featureBuilderReducer } from "./reducers/featureBuilderReducer.js";
5
+
6
+ import ListingScreen from "./screens/ListingScreen.jsx";
7
+ import CreateListingPage from "./pages/CreateListingPage.jsx";
8
+ import EditListingPage from "./pages/EditListingPage.jsx";
9
+ import { values } from "./values.config.js";
10
+ import { fetchFeatureDefinitions } from "./actions/featureDefinitionsIndex";
11
+
12
+ export const Reducers = (() => {
13
+ const reducers = {};
14
+ reducers[values.reducerKey] = featureBuilderReducer;
15
+ return reducers;
16
+ })();
17
+
18
+ export const Screens = (() => {
19
+ const screens = {};
20
+
21
+ screens["FormOverviewStep"] = FormOverviewStep;
22
+ screens["FormFieldsStep"] = FormFieldsStep;
23
+ screens["FormLayoutStep"] = FormLayoutStep;
24
+ screens["ListingScreen"] = ListingScreen;
25
+ screens["CreateListingPage"] = CreateListingPage;
26
+ screens["EditListingPage"] = EditListingPage;
27
+ return screens;
28
+ })();
29
+
30
+ export { default as Config } from "./feature.config.js";
31
+ export { default as ViewWidget } from "./components/ViewWidget.js"; // View on the feature selector - widget size
32
+ export { default as ViewFull } from "./components/ViewFull.js"; // View view on the feature selector - full size
33
+ export { default as PreviewWidget } from "./components/PreviewWidget.js"; // Preview on the phone - widget size
34
+ export { default as PreviewFull } from "./components/PreviewFull.js"; // Preview on the phone - full size
35
+ export { default as PreviewGrid } from "./components/PreviewGrid.js"; // Preview on the phone - grid mode
36
+
37
+ // Extension startup action for feature definition fetching
38
+ export const getStartupAction = () => fetchFeatureDefinitions();
@@ -0,0 +1,49 @@
1
+ import React from "react";
2
+ import { useDispatch } from "react-redux";
3
+ import ListingEditor from "../components/ListingEditor.jsx";
4
+ import { ListingSuccessPopup } from "../components/ListingSuccessPopup.jsx";
5
+ import { fetchFeatureDefinitions } from "../actions/featureDefinitionsIndex.js";
6
+ import { values } from "../values.config.js";
7
+
8
+ const CreateListingPage = ({ history }) => {
9
+ const dispatch = useDispatch();
10
+ const [showSuccessPopup, setShowSuccessPopup] = React.useState(false);
11
+ const [createdListingId, setCreatedListingId] = React.useState(null);
12
+
13
+ // Fetch feature definitions only for create mode
14
+ React.useEffect(() => {
15
+ dispatch(fetchFeatureDefinitions());
16
+ }, [dispatch]);
17
+
18
+ const handleSuccess = (listingId) => {
19
+ setCreatedListingId(listingId);
20
+ setShowSuccessPopup(true);
21
+ };
22
+
23
+ const handleSuccessPopupClose = () => {
24
+ setShowSuccessPopup(false);
25
+ history.push(values.routeListingScreen);
26
+ };
27
+
28
+ const handleCancel = () => {
29
+ history.push(values.routeListingScreen);
30
+ };
31
+
32
+ return (
33
+ <>
34
+ <ListingEditor
35
+ mode="create"
36
+ onSuccess={handleSuccess}
37
+ onCancel={handleCancel}
38
+ />
39
+ <ListingSuccessPopup
40
+ isOpen={showSuccessPopup}
41
+ onClose={handleSuccessPopupClose}
42
+ mode="create"
43
+ listingId={createdListingId}
44
+ />
45
+ </>
46
+ );
47
+ };
48
+
49
+ export default CreateListingPage;