@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,345 @@
1
+ /**
2
+ * Gallery field validation functions
3
+ * Provides comprehensive validation for image gallery fields
4
+ */
5
+
6
+ /**
7
+ * Validates minimum number of images
8
+ * @param {Array} images - Array of image URLs or objects
9
+ * @param {number} min - Minimum required images
10
+ * @returns {Object} - Validation result with isValid and message
11
+ */
12
+ export const minImages = (images, min) => {
13
+ if (!images || !Array.isArray(images)) {
14
+ return {
15
+ isValid: false,
16
+ message: "No images provided",
17
+ };
18
+ }
19
+
20
+ const validImages = images.filter((img) => img && img !== "");
21
+ const isValid = validImages.length >= min;
22
+
23
+ return {
24
+ isValid,
25
+ message: isValid
26
+ ? ""
27
+ : `Minimum ${min} image${min > 1 ? "s" : ""} required`,
28
+ };
29
+ };
30
+
31
+ /**
32
+ * Validates maximum number of images
33
+ * @param {Array} images - Array of image URLs or objects
34
+ * @param {number} max - Maximum allowed images
35
+ * @returns {Object} - Validation result with isValid and message
36
+ */
37
+ export const maxImages = (images, max) => {
38
+ if (!images || !Array.isArray(images)) {
39
+ return { isValid: true, message: "" };
40
+ }
41
+
42
+ const validImages = images.filter((img) => img && img !== "");
43
+ const isValid = validImages.length <= max;
44
+
45
+ return {
46
+ isValid,
47
+ message: isValid ? "" : `Maximum ${max} image${max > 1 ? "s" : ""} allowed`,
48
+ };
49
+ };
50
+
51
+ /**
52
+ * Validates file size for images (if file objects are provided)
53
+ * @param {Array} images - Array of image URLs or file objects
54
+ * @param {number} maxSizeMB - Maximum file size in MB
55
+ * @returns {Object} - Validation result with isValid and message
56
+ */
57
+ export const fileSize = (images, maxSizeMB) => {
58
+ if (!images || !Array.isArray(images)) {
59
+ return { isValid: true, message: "" };
60
+ }
61
+
62
+ const maxSizeBytes = maxSizeMB * 1024 * 1024;
63
+
64
+ for (let i = 0; i < images.length; i++) {
65
+ const image = images[i];
66
+
67
+ // Check if image is a file object with size property
68
+ if (image && typeof image === "object" && image.size) {
69
+ if (image.size > maxSizeBytes) {
70
+ return {
71
+ isValid: false,
72
+ message: `Image ${i + 1} exceeds ${maxSizeMB}MB limit`,
73
+ };
74
+ }
75
+ }
76
+ }
77
+
78
+ return { isValid: true, message: "" };
79
+ };
80
+
81
+ /**
82
+ * Validates file types for images
83
+ * @param {Array} images - Array of image URLs or file objects
84
+ * @param {string[]} allowedTypes - Array of allowed MIME types
85
+ * @returns {Object} - Validation result with isValid and message
86
+ */
87
+ export const fileType = (images, allowedTypes) => {
88
+ if (
89
+ !images ||
90
+ !Array.isArray(images) ||
91
+ !allowedTypes ||
92
+ !Array.isArray(allowedTypes)
93
+ ) {
94
+ return { isValid: true, message: "" };
95
+ }
96
+
97
+ const allowedExtensions = allowedTypes.flatMap((type) => {
98
+ // Convert MIME types to extensions for basic validation
99
+ if (type.includes("jpeg")) return ["jpg", "jpeg"];
100
+ if (type.includes("png")) return ["png"];
101
+ if (type.includes("webp")) return ["webp"];
102
+ if (type.includes("gif")) return ["gif"];
103
+ return [];
104
+ });
105
+
106
+ for (let i = 0; i < images.length; i++) {
107
+ const image = images[i];
108
+
109
+ if (image && typeof image === "object") {
110
+ // Check file object type
111
+ if (image.type && !allowedTypes.includes(image.type)) {
112
+ return {
113
+ isValid: false,
114
+ message: `Image ${i + 1} has unsupported file type`,
115
+ };
116
+ }
117
+ } else if (typeof image === "string") {
118
+ // Check URL extension (basic validation)
119
+ const extension = image.split(".").pop()?.toLowerCase();
120
+ if (extension && !allowedExtensions.includes(extension)) {
121
+ return {
122
+ isValid: false,
123
+ message: `Image ${i + 1} has unsupported file type`,
124
+ };
125
+ }
126
+ }
127
+ }
128
+
129
+ return { isValid: true, message: "" };
130
+ };
131
+
132
+ /**
133
+ * Validates aspect ratio for images (basic validation for file objects with dimensions)
134
+ * @param {Array} images - Array of image URLs or file objects
135
+ * @param {string} requiredRatio - Required aspect ratio ('16:9', '4:3', '1:1', 'any')
136
+ * @returns {Object} - Validation result with isValid and message
137
+ */
138
+ export const aspectRatio = (images, requiredRatio) => {
139
+ if (
140
+ !images ||
141
+ !Array.isArray(images) ||
142
+ !requiredRatio ||
143
+ requiredRatio === "any"
144
+ ) {
145
+ return { isValid: true, message: "" };
146
+ }
147
+
148
+ // Parse required aspect ratio
149
+ const ratioMap = {
150
+ "16:9": 16 / 9,
151
+ "4:3": 4 / 3,
152
+ "1:1": 1,
153
+ square: 1,
154
+ landscape: 16 / 9,
155
+ portrait: 3 / 4,
156
+ };
157
+
158
+ const targetRatio = ratioMap[requiredRatio.toLowerCase()];
159
+ if (!targetRatio) {
160
+ return { isValid: true, message: "" }; // Unknown ratio, skip validation
161
+ }
162
+
163
+ for (let i = 0; i < images.length; i++) {
164
+ const image = images[i];
165
+
166
+ // Only validate if we have dimension information (file objects with naturalWidth/Height)
167
+ if (image && typeof image === "object") {
168
+ if (image.naturalWidth && image.naturalHeight) {
169
+ const imageRatio = image.naturalWidth / image.naturalHeight;
170
+ const tolerance = 0.1; // 10% tolerance
171
+
172
+ if (Math.abs(imageRatio - targetRatio) > tolerance) {
173
+ return {
174
+ isValid: false,
175
+ message: `Image ${i + 1} does not match required aspect ratio (${requiredRatio})`,
176
+ };
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ return { isValid: true, message: "" };
183
+ };
184
+
185
+ /**
186
+ * Validates required field (at least one image)
187
+ * @param {Array} images - Array of image URLs or objects
188
+ * @returns {Object} - Validation result with isValid and message
189
+ */
190
+ export const required = (images) => {
191
+ if (!images || !Array.isArray(images)) {
192
+ return {
193
+ isValid: false,
194
+ message: "This field is required",
195
+ };
196
+ }
197
+
198
+ const validImages = images.filter((img) => img && img !== "");
199
+ const isValid = validImages.length > 0;
200
+
201
+ return {
202
+ isValid,
203
+ message: isValid ? "" : "At least one image is required",
204
+ };
205
+ };
206
+
207
+ /**
208
+ * Detects duplicate images in array
209
+ * @param {Array} images - Array of image URLs or objects
210
+ * @returns {Object} - Validation result with isValid and message
211
+ */
212
+ export const noDuplicates = (images) => {
213
+ if (!images || !Array.isArray(images)) {
214
+ return { isValid: true, message: "" };
215
+ }
216
+
217
+ const validImages = images.filter((img) => img && img !== "");
218
+ const uniqueImages = [...new Set(validImages)];
219
+
220
+ const hasDuplicates = validImages.length !== uniqueImages.length;
221
+
222
+ return {
223
+ isValid: !hasDuplicates,
224
+ message: hasDuplicates ? "Duplicate images detected" : "",
225
+ };
226
+ };
227
+
228
+ /**
229
+ * Comprehensive gallery field validation
230
+ * @param {Array} images - Array of image URLs or objects
231
+ * @param {Object} rules - Validation rules object
232
+ * @param {boolean} rules.isRequired - Whether field is required
233
+ * @param {number} rules.minImages - Minimum number of images
234
+ * @param {number} rules.maxImages - Maximum number of images
235
+ * @param {string} rules.maxFileSize - Maximum file size (e.g., "5MB")
236
+ * @param {string[]} rules.allowedTypes - Allowed file types
237
+ * @param {string} rules.aspectRatio - Required aspect ratio
238
+ * @param {Function[]} rules.customRules - Custom validation functions
239
+ * @returns {Object} - Validation result with isValid, errors, and warnings
240
+ */
241
+ export const validateGallery = (images, rules = {}) => {
242
+ const errors = [];
243
+ const warnings = [];
244
+
245
+ // Required validation
246
+ if (rules.isRequired) {
247
+ const requiredResult = required(images);
248
+ if (!requiredResult.isValid) {
249
+ errors.push(requiredResult.message);
250
+ }
251
+ }
252
+
253
+ // Only validate other rules if there are images or field is required
254
+ const hasImages = images && images.length > 0;
255
+ if (!hasImages && !rules.isRequired) {
256
+ return { isValid: true, errors: [], warnings: [] };
257
+ }
258
+
259
+ // Minimum images validation
260
+ if (rules.minImages) {
261
+ const minResult = minImages(images, rules.minImages);
262
+ if (!minResult.isValid) {
263
+ errors.push(minResult.message);
264
+ }
265
+ }
266
+
267
+ // Maximum images validation
268
+ if (rules.maxImages) {
269
+ const maxResult = maxImages(images, rules.maxImages);
270
+ if (!maxResult.isValid) {
271
+ errors.push(maxResult.message);
272
+ }
273
+ }
274
+
275
+ // File size validation
276
+ if (rules.maxFileSize) {
277
+ const sizeMB = parseFloat(rules.maxFileSize.replace(/[^0-9.]/g, ""));
278
+ const sizeResult = fileSize(images, sizeMB || 5);
279
+ if (!sizeResult.isValid) {
280
+ errors.push(sizeResult.message);
281
+ }
282
+ }
283
+
284
+ // File type validation
285
+ if (rules.allowedTypes && rules.allowedTypes.length > 0) {
286
+ const typeResult = fileType(images, rules.allowedTypes);
287
+ if (!typeResult.isValid) {
288
+ errors.push(typeResult.message);
289
+ }
290
+ }
291
+
292
+ // Aspect ratio validation
293
+ if (rules.aspectRatio && rules.aspectRatio !== "any") {
294
+ const ratioResult = aspectRatio(images, rules.aspectRatio);
295
+ if (!ratioResult.isValid) {
296
+ warnings.push(ratioResult.message); // Make aspect ratio a warning since it's harder to enforce
297
+ }
298
+ }
299
+
300
+ // Duplicate validation
301
+ const duplicateResult = noDuplicates(images);
302
+ if (!duplicateResult.isValid) {
303
+ warnings.push(duplicateResult.message);
304
+ }
305
+
306
+ // Custom validation rules
307
+ if (rules.customRules && Array.isArray(rules.customRules)) {
308
+ for (const customRule of rules.customRules) {
309
+ if (typeof customRule === "function") {
310
+ try {
311
+ const customResult = customRule(images);
312
+ if (customResult && typeof customResult === "object") {
313
+ if (!customResult.isValid) {
314
+ if (customResult.severity === "warning") {
315
+ warnings.push(customResult.message);
316
+ } else {
317
+ errors.push(customResult.message);
318
+ }
319
+ }
320
+ }
321
+ } catch (error) {
322
+ // Custom validation rule failed
323
+ }
324
+ }
325
+ }
326
+ }
327
+
328
+ return {
329
+ isValid: errors.length === 0,
330
+ errors,
331
+ warnings,
332
+ };
333
+ };
334
+
335
+ // Export individual validators for specific use cases
336
+ export default {
337
+ minImages,
338
+ maxImages,
339
+ fileSize,
340
+ fileType,
341
+ aspectRatio,
342
+ required,
343
+ noDuplicates,
344
+ validateGallery,
345
+ };
@@ -0,0 +1,49 @@
1
+ /*
2
+ "values" defines configurable options for this extension.
3
+ It can be swapped prior to publishing via copy:set
4
+ */
5
+ export const values = {
6
+ featureKey: "feature-builder-a",
7
+ featureKeyRoute: "/feature-builder-a", // Dynamic route prefix
8
+ featureId: "feature-builder-a-default", // Fixed ID for the single feature definition
9
+ reducerKey: "featureBuilderA", // Define reducer 'slice' key.
10
+ singularName: "Build Your Feature A",
11
+ description: "Create custom forms for mobile application (Variant A).",
12
+ emptyText: "No custom forms available",
13
+ textMenuTitle: "Build Your Feature A",
14
+ textPermissionFeatureBuilderDefinition: "Build Your Feature A Definition",
15
+ textPermissionFeatureBuilderContent:
16
+ "Create and Manage Content in Build Your Feature A",
17
+
18
+ // Routes
19
+ routeFormOverviewStep: "/feature-builder-a/definition/overview",
20
+ routeFormFieldsStep: "/feature-builder-a/definition/fields",
21
+ routeFormLayoutStep: "/feature-builder-a/definition/layout",
22
+ routeListingScreen: "/feature-builder-a/listing",
23
+ routeCreateListing: "/feature-builder-a/listing/create",
24
+
25
+ routeEditListing: "/feature-builder-a/listing/edit/:id",
26
+
27
+ // Screen names
28
+ screenFormOverviewStep: "FormOverviewStepA",
29
+ screenFormFieldsStep: "FormFieldsStepA",
30
+ screenFormLayoutStep: "FormLayoutStepA",
31
+ screenListingScreen: "ListingScreenA",
32
+
33
+ // Page names
34
+ pageCreateListing: "CreateListingPageA",
35
+ pageEditListing: "EditListingPageA",
36
+
37
+ // Permissions
38
+ permissionFeatureBuilderDefinition: "featureBuilderDefinitionA",
39
+ permissionFeatureBuilderContent: "featureBuilderContentA",
40
+
41
+ // Menu
42
+ menuIcon: "tool",
43
+ menuKey: "feature-builder-definition-a",
44
+ menuIsFontAwesome: true,
45
+ menuOrder: 20, // Lower number = higher priority
46
+
47
+ // Default Values
48
+ defaultIcon: "star",
49
+ };
@@ -0,0 +1,49 @@
1
+ /*
2
+ "values" defines configurable options for this extension.
3
+ It can be swapped prior to publishing via copy:set
4
+ */
5
+ export const values = {
6
+ featureKey: "feature-builder-b",
7
+ featureKeyRoute: "/feature-builder-b", // Dynamic route prefix
8
+ featureId: "feature-builder-b-default", // Fixed ID for the single feature definition
9
+ reducerKey: "featureBuilderB", // Define reducer 'slice' key.
10
+ singularName: "Build Your Feature B",
11
+ description: "Create custom forms for mobile application (Variant B).",
12
+ emptyText: "No custom forms available",
13
+ textMenuTitle: "Build Your Feature B",
14
+ textPermissionFeatureBuilderDefinition: "Build Your Feature B Definition",
15
+ textPermissionFeatureBuilderContent:
16
+ "Create and Manage Content in Build Your Feature B",
17
+
18
+ // Routes
19
+ routeFormOverviewStep: "/feature-builder-b/definition/overview",
20
+ routeFormFieldsStep: "/feature-builder-b/definition/fields",
21
+ routeFormLayoutStep: "/feature-builder-b/definition/layout",
22
+ routeListingScreen: "/feature-builder-b/listing",
23
+ routeCreateListing: "/feature-builder-b/listing/create",
24
+
25
+ routeEditListing: "/feature-builder-b/listing/edit/:id",
26
+
27
+ // Screen names
28
+ screenFormOverviewStep: "FormOverviewStepB",
29
+ screenFormFieldsStep: "FormFieldsStepB",
30
+ screenFormLayoutStep: "FormLayoutStepB",
31
+ screenListingScreen: "ListingScreenB",
32
+
33
+ // Page names
34
+ pageCreateListing: "CreateListingPageB",
35
+ pageEditListing: "EditListingPageB",
36
+
37
+ // Permissions
38
+ permissionFeatureBuilderDefinition: "featureBuilderDefinitionB",
39
+ permissionFeatureBuilderContent: "featureBuilderContentB",
40
+
41
+ // Menu
42
+ menuIcon: "tool",
43
+ menuKey: "feature-builder-definition-b",
44
+ menuIsFontAwesome: true,
45
+ menuOrder: 20, // Lower number = higher priority
46
+
47
+ // Default Values
48
+ defaultIcon: "star",
49
+ };
@@ -0,0 +1,49 @@
1
+ /*
2
+ "values" defines configurable options for this extension.
3
+ It can be swapped prior to publishing via copy:set
4
+ */
5
+ export const values = {
6
+ featureKey: "feature-builder-c",
7
+ featureKeyRoute: "/feature-builder-c", // Dynamic route prefix
8
+ featureId: "feature-builder-c-default", // Fixed ID for the single feature definition
9
+ reducerKey: "featureBuilderC", // Define reducer 'slice' key.
10
+ singularName: "Build Your Feature C",
11
+ description: "Create custom forms for mobile application (Variant C).",
12
+ emptyText: "No custom forms available",
13
+ textMenuTitle: "Build Your Feature C",
14
+ textPermissionFeatureBuilderDefinition: "Build Your Feature C Definition",
15
+ textPermissionFeatureBuilderContent:
16
+ "Create and Manage Content in Build Your Feature C",
17
+
18
+ // Routes
19
+ routeFormOverviewStep: "/feature-builder-c/definition/overview",
20
+ routeFormFieldsStep: "/feature-builder-c/definition/fields",
21
+ routeFormLayoutStep: "/feature-builder-c/definition/layout",
22
+ routeListingScreen: "/feature-builder-c/listing",
23
+ routeCreateListing: "/feature-builder-c/listing/create",
24
+
25
+ routeEditListing: "/feature-builder-c/listing/edit/:id",
26
+
27
+ // Screen names
28
+ screenFormOverviewStep: "FormOverviewStepC",
29
+ screenFormFieldsStep: "FormFieldsStepC",
30
+ screenFormLayoutStep: "FormLayoutStepC",
31
+ screenListingScreen: "ListingScreenC",
32
+
33
+ // Page names
34
+ pageCreateListing: "CreateListingPageC",
35
+ pageEditListing: "EditListingPageC",
36
+
37
+ // Permissions
38
+ permissionFeatureBuilderDefinition: "featureBuilderDefinitionC",
39
+ permissionFeatureBuilderContent: "featureBuilderContentC",
40
+
41
+ // Menu
42
+ menuIcon: "tool",
43
+ menuKey: "feature-builder-definition-c",
44
+ menuIsFontAwesome: true,
45
+ menuOrder: 20, // Lower number = higher priority
46
+
47
+ // Default Values
48
+ defaultIcon: "star",
49
+ };
@@ -0,0 +1,49 @@
1
+ /*
2
+ "values" defines configurable options for this extension.
3
+ It can be swapped prior to publishing via copy:set
4
+ */
5
+ export const values = {
6
+ featureKey: "feature-builder-d",
7
+ featureKeyRoute: "/feature-builder-d", // Dynamic route prefix
8
+ featureId: "feature-builder-d-default", // Fixed ID for the single feature definition
9
+ reducerKey: "featureBuilderD", // Define reducer 'slice' key.
10
+ singularName: "Build Your Feature D",
11
+ description: "Create custom forms for mobile application (Variant D).",
12
+ emptyText: "No custom forms available",
13
+ textMenuTitle: "Build Your Feature D",
14
+ textPermissionFeatureBuilderDefinition: "Build Your Feature D Definition",
15
+ textPermissionFeatureBuilderContent:
16
+ "Create and Manage Content in Build Your Feature D",
17
+
18
+ // Routes
19
+ routeFormOverviewStep: "/feature-builder-d/definition/overview",
20
+ routeFormFieldsStep: "/feature-builder-d/definition/fields",
21
+ routeFormLayoutStep: "/feature-builder-d/definition/layout",
22
+ routeListingScreen: "/feature-builder-d/listing",
23
+ routeCreateListing: "/feature-builder-d/listing/create",
24
+
25
+ routeEditListing: "/feature-builder-d/listing/edit/:id",
26
+
27
+ // Screen names
28
+ screenFormOverviewStep: "FormOverviewStepD",
29
+ screenFormFieldsStep: "FormFieldsStepD",
30
+ screenFormLayoutStep: "FormLayoutStepD",
31
+ screenListingScreen: "ListingScreenD",
32
+
33
+ // Page names
34
+ pageCreateListing: "CreateListingPageD",
35
+ pageEditListing: "EditListingPageD",
36
+
37
+ // Permissions
38
+ permissionFeatureBuilderDefinition: "featureBuilderDefinitionD",
39
+ permissionFeatureBuilderContent: "featureBuilderContentD",
40
+
41
+ // Menu
42
+ menuIcon: "tool",
43
+ menuKey: "feature-builder-definition-d",
44
+ menuIsFontAwesome: true,
45
+ menuOrder: 20, // Lower number = higher priority
46
+
47
+ // Default Values
48
+ defaultIcon: "star",
49
+ };
@@ -0,0 +1,49 @@
1
+ /*
2
+ "values" defines configurable options for this extension.
3
+ It can be swapped prior to publishing via copy:set
4
+ */
5
+ export const values = {
6
+ featureKey: "feature-builder-a",
7
+ featureKeyRoute: "/feature-builder-a", // Dynamic route prefix
8
+ featureId: "feature-builder-a-default", // Fixed ID for the single feature definition
9
+ reducerKey: "featureBuilderA", // Define reducer 'slice' key.
10
+ singularName: "Build Your Feature A",
11
+ description: "Create custom forms for mobile application (Variant A).",
12
+ emptyText: "No custom forms available",
13
+ textMenuTitle: "Build Your Feature A",
14
+ textPermissionFeatureBuilderDefinition: "Build Your Feature A Definition",
15
+ textPermissionFeatureBuilderContent:
16
+ "Create and Manage Content in Build Your Feature A",
17
+
18
+ // Routes
19
+ routeFormOverviewStep: "/feature-builder-a/definition/overview",
20
+ routeFormFieldsStep: "/feature-builder-a/definition/fields",
21
+ routeFormLayoutStep: "/feature-builder-a/definition/layout",
22
+ routeListingScreen: "/feature-builder-a/listing",
23
+ routeCreateListing: "/feature-builder-a/listing/create",
24
+
25
+ routeEditListing: "/feature-builder-a/listing/edit/:id",
26
+
27
+ // Screen names
28
+ screenFormOverviewStep: "FormOverviewStepA",
29
+ screenFormFieldsStep: "FormFieldsStepA",
30
+ screenFormLayoutStep: "FormLayoutStepA",
31
+ screenListingScreen: "ListingScreenA",
32
+
33
+ // Page names
34
+ pageCreateListing: "CreateListingPageA",
35
+ pageEditListing: "EditListingPageA",
36
+
37
+ // Permissions
38
+ permissionFeatureBuilderDefinition: "featureBuilderDefinitionA",
39
+ permissionFeatureBuilderContent: "featureBuilderContentA",
40
+
41
+ // Menu
42
+ menuIcon: "tool",
43
+ menuKey: "feature-builder-definition-a",
44
+ menuIsFontAwesome: true,
45
+ menuOrder: 20, // Lower number = higher priority
46
+
47
+ // Default Values
48
+ defaultIcon: "star",
49
+ };
File without changes
@@ -0,0 +1,90 @@
1
+ import { PlussCore } from "../feature.config";
2
+ const { Helper, Session } = PlussCore;
3
+
4
+ const { getUrl } = Helper;
5
+ const { authedFunction } = Session;
6
+
7
+ export const featureDefinitionActions = {
8
+ /**
9
+ * Get the single feature definition by ID
10
+ * Path: {id}
11
+ */
12
+ getSingle: async (id, site) => {
13
+ const query = { id, site };
14
+ return authedFunction({
15
+ method: "GET",
16
+ url: getUrl("feature-builder", "definition/get/single", query),
17
+ });
18
+ },
19
+
20
+ /**
21
+ * Creates a new feature definition with the provided configuration
22
+ *
23
+ * @param {string} id - The unique identifier for the new feature definition
24
+ * @param {string} site - The site ID where the feature definition will be created
25
+ * @param {FeatureDefinition} featureDefinition - The feature definition data to create
26
+ * @returns {Promise<ApiResponse>} Promise resolving to API response with created feature definition
27
+ * @throws {Error} When creation fails due to validation or API errors
28
+ *
29
+ */
30
+ create: async (id, site, featureDefinition) => {
31
+ return authedFunction({
32
+ method: "POST",
33
+ url: getUrl("feature-builder", "definition/update/create"),
34
+ data: { id, site, featureDefinition },
35
+ });
36
+ },
37
+
38
+ /**
39
+ * Updates an existing feature definition with new configuration
40
+ *
41
+ * @param {FeatureDefinition} featureDefinitionData - The updated feature definition data
42
+ * @param {string} featureDefinitionData.id - The unique identifier of the feature definition to update
43
+ * @param {string} [featureDefinitionData.displayName] - Updated display name
44
+ * @param {Field[]} [featureDefinitionData.fields] - Updated field definitions
45
+ * @param {Object} [featureDefinitionData.layout] - Updated layout configuration
46
+ * @param {string} site - The site ID where the feature definition exists
47
+ * @returns {Promise<ApiResponse>} Promise resolving to API response with updated feature definition
48
+ * @throws {Error} When update fails due to validation or API errors
49
+ *
50
+ * @example
51
+ * try {
52
+ * const response = await featureDefinitionActions.edit(
53
+ * {
54
+ * id: 'feature-123',
55
+ * displayName: 'Updated Form',
56
+ * fields: [...]
57
+ * },
58
+ * 'site-123'
59
+ * );
60
+ */
61
+ edit: async (featureDefinitionData, site) => {
62
+ // Ensure site is included in the request body
63
+ const dataWithSite = {
64
+ site: site,
65
+ ...featureDefinitionData,
66
+ };
67
+ return authedFunction({
68
+ method: "POST",
69
+ url: getUrl("feature-builder", "definition/update/edit"),
70
+ data: dataWithSite,
71
+ });
72
+ },
73
+
74
+ /**
75
+ * Soft deletes a feature definition (marks as deleted but doesn't permanently remove)
76
+ *
77
+ * @param {string} id - The unique identifier of the feature definition to delete
78
+ * @param {string} site - The site ID where the feature definition exists
79
+ * @returns {Promise<ApiResponse>} Promise resolving to API response confirming deletion
80
+ * @throws {Error} When deletion fails or feature definition is not found
81
+ *
82
+ */
83
+ delete: async (id, site) => {
84
+ return authedFunction({
85
+ method: "POST",
86
+ url: getUrl("feature-builder", "definition/update/delete"),
87
+ data: { id, site },
88
+ });
89
+ },
90
+ };
@@ -0,0 +1,4 @@
1
+ import { PlussCore } from "../feature.config";
2
+
3
+ const { Helper } = PlussCore;
4
+ export const getUrl = Helper.getUrl;