@plusscommunities/pluss-feature-builder-app-d 1.0.1-beta.3

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 (118) hide show
  1. package/dist/module/actions/featureBuilderActions.js +106 -0
  2. package/dist/module/actions/featureBuilderActions.js.map +1 -0
  3. package/dist/module/actions/featureBuilderStringsActions.js +106 -0
  4. package/dist/module/actions/featureBuilderStringsActions.js.map +1 -0
  5. package/dist/module/actions/index.js +12 -0
  6. package/dist/module/actions/index.js.map +1 -0
  7. package/dist/module/actions/types.js +7 -0
  8. package/dist/module/actions/types.js.map +1 -0
  9. package/dist/module/components/FeatureDetailScreen.js +725 -0
  10. package/dist/module/components/FeatureDetailScreen.js.map +1 -0
  11. package/dist/module/components/FeatureListItem.js +174 -0
  12. package/dist/module/components/FeatureListItem.js.map +1 -0
  13. package/dist/module/components/FeatureListScreen.js +159 -0
  14. package/dist/module/components/FeatureListScreen.js.map +1 -0
  15. package/dist/module/components/FieldRenderer.js +218 -0
  16. package/dist/module/components/FieldRenderer.js.map +1 -0
  17. package/dist/module/components/FileDownload.js +74 -0
  18. package/dist/module/components/FileDownload.js.map +1 -0
  19. package/dist/module/components/WidgetGrid.js +158 -0
  20. package/dist/module/components/WidgetGrid.js.map +1 -0
  21. package/dist/module/components/WidgetLarge.js +274 -0
  22. package/dist/module/components/WidgetLarge.js.map +1 -0
  23. package/dist/module/components/WidgetSmall.js +315 -0
  24. package/dist/module/components/WidgetSmall.js.map +1 -0
  25. package/dist/module/components/common/index.js +25 -0
  26. package/dist/module/components/common/index.js.map +1 -0
  27. package/dist/module/components/layouts/CondensedList.js +195 -0
  28. package/dist/module/components/layouts/CondensedList.js.map +1 -0
  29. package/dist/module/components/layouts/FeatureImageList.js +172 -0
  30. package/dist/module/components/layouts/FeatureImageList.js.map +1 -0
  31. package/dist/module/components/layouts/RoundImageList.js +198 -0
  32. package/dist/module/components/layouts/RoundImageList.js.map +1 -0
  33. package/dist/module/components/layouts/SquareImageList.js +185 -0
  34. package/dist/module/components/layouts/SquareImageList.js.map +1 -0
  35. package/dist/module/config/index.js +10 -0
  36. package/dist/module/config/index.js.map +1 -0
  37. package/dist/module/core.config.js +17 -0
  38. package/dist/module/core.config.js.map +1 -0
  39. package/dist/module/feature.config.js +113 -0
  40. package/dist/module/feature.config.js.map +1 -0
  41. package/dist/module/index.js +24 -0
  42. package/dist/module/index.js.map +1 -0
  43. package/dist/module/js/Colors.js +25 -0
  44. package/dist/module/js/Colors.js.map +1 -0
  45. package/dist/module/js/FieldTypes.js +123 -0
  46. package/dist/module/js/FieldTypes.js.map +1 -0
  47. package/dist/module/js/NavigationService.js +10 -0
  48. package/dist/module/js/NavigationService.js.map +1 -0
  49. package/dist/module/js/Styles.js +3 -0
  50. package/dist/module/js/Styles.js.map +1 -0
  51. package/dist/module/js/helpers.js +29 -0
  52. package/dist/module/js/helpers.js.map +1 -0
  53. package/dist/module/js/index.js +24 -0
  54. package/dist/module/js/index.js.map +1 -0
  55. package/dist/module/js/spacing.js +29 -0
  56. package/dist/module/js/spacing.js.map +1 -0
  57. package/dist/module/js/types.js +254 -0
  58. package/dist/module/js/types.js.map +1 -0
  59. package/dist/module/reducers/featureBuilderReducer.js +75 -0
  60. package/dist/module/reducers/featureBuilderReducer.js.map +1 -0
  61. package/dist/module/utils/featureSelectors.js +9 -0
  62. package/dist/module/utils/featureSelectors.js.map +1 -0
  63. package/dist/module/values.config.a.js +96 -0
  64. package/dist/module/values.config.a.js.map +1 -0
  65. package/dist/module/values.config.b.js +96 -0
  66. package/dist/module/values.config.b.js.map +1 -0
  67. package/dist/module/values.config.c.js +96 -0
  68. package/dist/module/values.config.c.js.map +1 -0
  69. package/dist/module/values.config.d.js +96 -0
  70. package/dist/module/values.config.d.js.map +1 -0
  71. package/dist/module/values.config.js +96 -0
  72. package/dist/module/values.config.js.map +1 -0
  73. package/dist/module/webapi/featureBuilderAPI.js +59 -0
  74. package/dist/module/webapi/featureBuilderAPI.js.map +1 -0
  75. package/dist/module/webapi/helper.js +4 -0
  76. package/dist/module/webapi/helper.js.map +1 -0
  77. package/dist/module/webapi/index.js +8 -0
  78. package/dist/module/webapi/index.js.map +1 -0
  79. package/package.json +62 -0
  80. package/src/actions/featureBuilderActions.js +112 -0
  81. package/src/actions/featureBuilderStringsActions.js +114 -0
  82. package/src/actions/index.js +12 -0
  83. package/src/actions/types.js +7 -0
  84. package/src/components/FeatureDetailScreen.js +817 -0
  85. package/src/components/FeatureListItem.js +198 -0
  86. package/src/components/FeatureListScreen.js +160 -0
  87. package/src/components/FieldRenderer.js +272 -0
  88. package/src/components/FileDownload.js +79 -0
  89. package/src/components/WidgetGrid.js +181 -0
  90. package/src/components/WidgetLarge.js +305 -0
  91. package/src/components/WidgetSmall.js +344 -0
  92. package/src/components/common/index.js +25 -0
  93. package/src/components/layouts/CondensedList.js +230 -0
  94. package/src/components/layouts/FeatureImageList.js +193 -0
  95. package/src/components/layouts/RoundImageList.js +219 -0
  96. package/src/components/layouts/SquareImageList.js +205 -0
  97. package/src/config/index.js +10 -0
  98. package/src/core.config.js +29 -0
  99. package/src/feature.config.js +127 -0
  100. package/src/index.js +27 -0
  101. package/src/js/Colors.js +30 -0
  102. package/src/js/FieldTypes.js +131 -0
  103. package/src/js/NavigationService.js +12 -0
  104. package/src/js/Styles.js +3 -0
  105. package/src/js/helpers.js +30 -0
  106. package/src/js/index.js +24 -0
  107. package/src/js/spacing.js +30 -0
  108. package/src/js/types.js +253 -0
  109. package/src/reducers/featureBuilderReducer.js +64 -0
  110. package/src/utils/featureSelectors.js +8 -0
  111. package/src/values.config.a.js +104 -0
  112. package/src/values.config.b.js +104 -0
  113. package/src/values.config.c.js +104 -0
  114. package/src/values.config.d.js +104 -0
  115. package/src/values.config.js +104 -0
  116. package/src/webapi/featureBuilderAPI.js +65 -0
  117. package/src/webapi/helper.js +4 -0
  118. package/src/webapi/index.js +8 -0
@@ -0,0 +1,127 @@
1
+ import _ from "lodash";
2
+ // import * as PlussCore from '../../pluss-core/src';
3
+ import * as PlussCore from "@plusscommunities/pluss-core-app";
4
+ import { values } from "./values.config";
5
+
6
+ export { PlussCore };
7
+
8
+ export const Services = {
9
+ navigation: null,
10
+ };
11
+
12
+ export const BaseComponents = {
13
+ NotificationBell: null,
14
+ };
15
+
16
+ // These will be set when the components are imported in index.js
17
+ export const setWidgets = (small, large, grid) => {
18
+ WidgetSmall = small;
19
+ WidgetLarge = large;
20
+ WidgetGrid = grid;
21
+ };
22
+
23
+ const fetchFeatured = async (site, store, props) => {
24
+ // No-op: listings are already loaded by loadTargetFeature()
25
+ return [];
26
+ };
27
+
28
+ const getFeatured = (state, features) => {
29
+ if (!_.includes(features, values.featureKey)) return null;
30
+
31
+ const listings = state[values.reducerKey]?.listings || [];
32
+ if (!listings || listings.length === 0) return [];
33
+
34
+ // Get latest 3-4 listings, sorted by createdAt or updatedAt
35
+ const latestListings = _.orderBy(
36
+ listings,
37
+ [(item) => item.createdAt || item.updatedAt],
38
+ ["desc"]
39
+ ).slice(0, 4);
40
+
41
+ return latestListings.map((listing) => {
42
+ const title = listing.fields?.[values.mandatoryFields.title] || values.labels.defaultTitle;
43
+ const imageField = listing.fields?.[values.mandatoryFields.featureImage];
44
+ let imageSource = null;
45
+
46
+ if (imageField) {
47
+ if (typeof imageField === "string") {
48
+ imageSource = imageField;
49
+ } else if (imageField?.url) {
50
+ imageSource = imageField.url;
51
+ } else if (imageField?.uri) {
52
+ imageSource = imageField.uri;
53
+ }
54
+ }
55
+
56
+ return {
57
+ id: listing.id,
58
+ type: values.featureKey,
59
+ title: title,
60
+ description: "",
61
+ image: imageSource,
62
+ date: listing.createdAt || listing.updatedAt || 0,
63
+ data: listing,
64
+ };
65
+ });
66
+ };
67
+
68
+ const FeatureConfig = {
69
+ key: values.featureKey,
70
+ aliases: ["feature-builder", "custom-features"],
71
+ title: values.featureName,
72
+
73
+ gridMenu: {
74
+ icon: values.navigation.gridMenu.icon,
75
+ viewBox: values.navigation.gridMenu.viewBox,
76
+ navigate: values.navigation.gridMenu.navigate,
77
+ options: values.navigation.gridMenu.options,
78
+ },
79
+
80
+ // For now, keep other menus minimal since this is primarily a display feature
81
+ addMenu: null,
82
+ moreMenu: null,
83
+ kioskAction: null,
84
+
85
+ notification: null, // No notifications for MVP
86
+ submission: null, // No submissions for MVP
87
+
88
+ featured: {
89
+ type: values.featureKey,
90
+ prop: values.featureKey,
91
+ reducer: {
92
+ key: values.featureKey,
93
+ prop: "list",
94
+ },
95
+ updateKey: values.featureKey,
96
+ fetch: fetchFeatured,
97
+ get: getFeatured,
98
+ },
99
+
100
+ hideTabBar: [], // No special tab bar hiding needed
101
+
102
+ env: {
103
+ baseStage: "",
104
+ baseAPIUrl: "",
105
+ hasGradientHeader: false,
106
+ defaultProfileImage: "",
107
+ tinyChatDefault: "",
108
+ baseUploadsUrl: "",
109
+ allowMediaDownload: false,
110
+ allowMediaSharing: false,
111
+ awsUploadsBucket: "",
112
+ awsStorageBucket: "",
113
+ preferredSite: "",
114
+ strings: {},
115
+ newEventDefaults: "",
116
+ defaultAllowComments: true,
117
+ },
118
+
119
+ init: (environment, navigation, notificationBell) => {
120
+ FeatureConfig.env = environment;
121
+ Services.navigation = navigation;
122
+ BaseComponents.NotificationBell = notificationBell;
123
+ PlussCore.Config.init(environment, navigation);
124
+ },
125
+ };
126
+
127
+ export default FeatureConfig;
package/src/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import FeatureConfig, { setWidgets } from "./feature.config";
2
+ import featureBuilderReducer from "./reducers/featureBuilderReducer";
3
+ import FeatureListScreen from "./components/FeatureListScreen";
4
+ import FeatureDetailScreen from "./components/FeatureDetailScreen";
5
+ import WidgetSmall from "./components/WidgetSmall";
6
+ import WidgetLarge from "./components/WidgetLarge";
7
+ import WidgetGrid from "./components/WidgetGrid";
8
+ import { values } from "./values.config";
9
+
10
+ // Set widgets in the config to avoid circular dependencies
11
+ setWidgets(WidgetSmall, WidgetLarge, WidgetGrid);
12
+
13
+ export const Reducers = {
14
+ [values.reducerKey]: featureBuilderReducer,
15
+ };
16
+
17
+ export const Screens = {
18
+ FeatureBuilderList: FeatureListScreen,
19
+ FeatureBuilderDetail: FeatureDetailScreen,
20
+ };
21
+
22
+ export { default as Config } from "./feature.config";
23
+ export { default as WidgetSmall } from "./components/WidgetSmall";
24
+ export { default as WidgetLarge } from "./components/WidgetLarge";
25
+ export { default as WidgetGrid } from "./components/WidgetGrid";
26
+
27
+ export { FeatureConfig };
@@ -0,0 +1,30 @@
1
+ import { Colours } from "../core.config";
2
+ import { values } from "../values.config";
3
+
4
+ export const getMainBrandingColourFromState =
5
+ typeof Colours?.getMainBrandingColourFromState === "function"
6
+ ? Colours.getMainBrandingColourFromState
7
+ : (state) =>
8
+ state?.user?.siteBranding?.MainBrandingColour ||
9
+ values.colors.defaultBrandMain;
10
+
11
+ export const getLightBrandingColourFromState =
12
+ typeof Colours?.getLightBrandingColourFromState === "function"
13
+ ? Colours.getLightBrandingColourFromState
14
+ : (state) =>
15
+ state?.user?.siteBranding?.LightBrandingColour ||
16
+ values.colors.defaultBrandLight;
17
+ export const LINEGREY = Colours.LINEGREY;
18
+ export const TEXT_DARKEST = Colours.TEXT_DARKEST;
19
+ export const TEXT_DARK = Colours.TEXT_DARK;
20
+ export const TEXT_MID = Colours.TEXT_MID;
21
+ export const TEXT_LIGHT = Colours.TEXT_LIGHT;
22
+ export const TEXT_LIGHTER = Colours.TEXT_LIGHTER;
23
+ export const TEXT_BLUEGREY = Colours.TEXT_BLUEGREY;
24
+ export const COLOUR_GREEN = Colours.COLOUR_GREEN;
25
+ export const COLOUR_TRANSPARENT = Colours.COLOUR_TRANSPARENT;
26
+ export const COLOUR_TEAL = Colours.COLOUR_TEAL;
27
+ export const COLOUR_PURPLE = Colours.COLOUR_PURPLE;
28
+ export const COLOUR_TANGERINE = Colours.COLOUR_TANGERINE;
29
+
30
+ // TODO: Add required references
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Field Types Constants for Feature Builder
3
+ * Defines all supported field types and their properties
4
+ */
5
+
6
+ export const FIELD_TYPES = {
7
+ TITLE: "title",
8
+ TEXT: "text",
9
+ DESCRIPTION: "description",
10
+ IMAGE: "image",
11
+ GALLERY: "gallery",
12
+ FEATURE_IMAGE: "feature-image",
13
+ EMAIL: "email",
14
+ NUMBER: "number",
15
+ DATE: "date",
16
+ CTA: "cta",
17
+ FILE: "file",
18
+ };
19
+
20
+ /**
21
+ * Field type display labels for UI
22
+ */
23
+ export const FIELD_TYPE_LABELS = {
24
+ [FIELD_TYPES.TITLE]: "Title",
25
+ [FIELD_TYPES.TEXT]: "Text",
26
+ [FIELD_TYPES.DESCRIPTION]: "Description",
27
+ [FIELD_TYPES.IMAGE]: "Image",
28
+ [FIELD_TYPES.GALLERY]: "Gallery",
29
+ [FIELD_TYPES.FEATURE_IMAGE]: "Feature Image",
30
+ [FIELD_TYPES.EMAIL]: "Email",
31
+ [FIELD_TYPES.NUMBER]: "Number",
32
+ [FIELD_TYPES.DATE]: "Date",
33
+ [FIELD_TYPES.CTA]: "CTA Button",
34
+ [FIELD_TYPES.FILE]: "File",
35
+ };
36
+
37
+ /**
38
+ * Field type validation rules
39
+ */
40
+ export const FIELD_VALIDATION = {
41
+ [FIELD_TYPES.TITLE]: {
42
+ required: true,
43
+ minLength: 1,
44
+ },
45
+ [FIELD_TYPES.TEXT]: {
46
+ required: true,
47
+ minLength: 1,
48
+ },
49
+ [FIELD_TYPES.DESCRIPTION]: {
50
+ required: false,
51
+ minLength: 0,
52
+ },
53
+ [FIELD_TYPES.IMAGE]: {
54
+ required: false,
55
+ allowedTypes: ["image/jpeg", "image/png", "image/gif"],
56
+ },
57
+ [FIELD_TYPES.GALLERY]: {
58
+ required: false,
59
+ allowedTypes: ["image/jpeg", "image/png", "image/gif"],
60
+ allowMultiple: true,
61
+ },
62
+ [FIELD_TYPES.FEATURE_IMAGE]: {
63
+ required: true,
64
+ allowedTypes: ["image/jpeg", "image/png", "image/gif"],
65
+ },
66
+ [FIELD_TYPES.EMAIL]: {
67
+ required: false,
68
+ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
69
+ },
70
+ [FIELD_TYPES.NUMBER]: {
71
+ required: false,
72
+ pattern: /^\d+\.?\d*$/,
73
+ },
74
+ [FIELD_TYPES.DATE]: {
75
+ required: false,
76
+ },
77
+ [FIELD_TYPES.CTA]: {
78
+ required: false,
79
+ fields: ["label", "url"],
80
+ },
81
+ [FIELD_TYPES.FILE]: {
82
+ required: false,
83
+ allowMultiple: true,
84
+ },
85
+ };
86
+
87
+ /**
88
+ * Get display label for field type
89
+ */
90
+ export const getFieldTypeLabel = (fieldType) => {
91
+ return FIELD_TYPE_LABELS[fieldType] || fieldType;
92
+ };
93
+
94
+ /**
95
+ * Check if field type is a CTA (has label and url structure)
96
+ */
97
+ export const isCTAField = (fieldValue) => {
98
+ return (
99
+ fieldValue &&
100
+ typeof fieldValue === "object" &&
101
+ fieldValue.label &&
102
+ fieldValue.url
103
+ );
104
+ };
105
+
106
+ /**
107
+ * Check if field value represents an image
108
+ */
109
+ export const isImageField = (fieldValue) => {
110
+ if (typeof fieldValue === "string") {
111
+ return (
112
+ fieldValue.startsWith("http://") || fieldValue.startsWith("https://")
113
+ );
114
+ }
115
+ if (typeof fieldValue === "object" && (fieldValue.uri || fieldValue.url)) {
116
+ return true;
117
+ }
118
+ return false;
119
+ };
120
+
121
+ /**
122
+ * Check if field value represents a link/URL
123
+ */
124
+ export const isLinkField = (fieldValue) => {
125
+ if (typeof fieldValue === "string") {
126
+ return (
127
+ fieldValue.startsWith("http://") || fieldValue.startsWith("https://")
128
+ );
129
+ }
130
+ return false;
131
+ };
@@ -0,0 +1,12 @@
1
+ import { Services } from "../feature.config";
2
+
3
+ const navService = {
4
+ navigate: (routeName, params) =>
5
+ Services.navigation.navigate(routeName, params),
6
+ goBack: (routeName) => Services.navigation.goBack(routeName),
7
+ getParentRoute: () => Services.navigation.getParentRoute(),
8
+ };
9
+
10
+ export default navService;
11
+
12
+ // TODO: Add required references
@@ -0,0 +1,3 @@
1
+ import { Styles } from "../core.config";
2
+
3
+ export default Styles;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Helper functions for Feature Builder
3
+ */
4
+
5
+ /**
6
+ * Get the summary field value from a listing based on feature definition
7
+ * Looks for a field marked with useAsSummary: true, then falls back to first description field
8
+ * @param {Object} listing - The listing object containing fields
9
+ * @param {Object} featureDefinition - The feature definition containing field definitions
10
+ * @returns {string} The summary field value or empty string
11
+ */
12
+ export const getSummaryFieldValue = (listing, featureDefinition) => {
13
+ // First, look for field marked with useAsSummary: true
14
+ const summaryField = featureDefinition?.fields?.find(
15
+ (field) => field.useAsSummary === true,
16
+ );
17
+ if (summaryField) {
18
+ return listing.fields?.[summaryField.id] || "";
19
+ }
20
+
21
+ // Fall back to first description-type field
22
+ const descriptionField = featureDefinition?.fields?.find(
23
+ (field) => field.type === "description",
24
+ );
25
+ if (descriptionField) {
26
+ return listing.fields?.[descriptionField.id] || "";
27
+ }
28
+
29
+ return "";
30
+ };
@@ -0,0 +1,24 @@
1
+ import { Session, Helper } from "../core.config";
2
+
3
+ // Session
4
+ export const authedFunction = async (request) => {
5
+ const result = await Session.authedFunction(request);
6
+ return result;
7
+ };
8
+ export const getSessionTokenAWS = Session.getSessionTokenAWS;
9
+ export const getSessionUidAWS = Session.getSessionUidAWS;
10
+
11
+ // Helper
12
+ export const getFirstName = Helper.getFirstName;
13
+ export const getImageSource = Helper.getImageSource;
14
+ export const StatusBarHeight = Helper.StatusBarHeight;
15
+ export const getPluralS = Helper.getPluralS;
16
+ export const getShadowStyle = Helper.getShadowStyle;
17
+ export const getSite = Helper.getSite;
18
+ export const getValueOrDefault = Helper.getValueOrDefault;
19
+ export const usersToSearchResult = Helper.usersToSearchResult;
20
+ export const searchUsers = Helper.searchUsers;
21
+
22
+ export * from "./Colors";
23
+
24
+ // TODO: Add required references
@@ -0,0 +1,30 @@
1
+ export const SPACING = {
2
+ // Base spacing unit (4px)
3
+ XS: 4,
4
+ // Small spacing (8px)
5
+ SM: 8,
6
+ // Medium spacing (16px)
7
+ MD: 16,
8
+ // Large spacing (24px)
9
+ LG: 24,
10
+ // Extra large spacing (32px)
11
+ XL: 32,
12
+ // Double large spacing (48px)
13
+ XXL: 48,
14
+ };
15
+
16
+ export const commonSpacing = {
17
+ marginTiny: SPACING.XS,
18
+ marginSmall: SPACING.SM,
19
+ marginMedium: SPACING.MD,
20
+ marginLarge: SPACING.LG,
21
+ marginXLarge: SPACING.XL,
22
+ marginXXLarge: SPACING.XXL,
23
+
24
+ paddingTiny: SPACING.XS,
25
+ paddingSmall: SPACING.SM,
26
+ paddingMedium: SPACING.MD,
27
+ paddingLarge: SPACING.LG,
28
+ paddingXLarge: SPACING.XL,
29
+ paddingXXLarge: SPACING.XXL,
30
+ };
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Core type definitions for Feature Builder Mobile Extension
3
+ * This file contains JSDoc type definitions for all major entities used throughout the feature builder
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} FieldDefinition
8
+ * @property {string} id - Unique identifier for the field (e.g., 'mandatory-title', 'mandatory-feature-image')
9
+ * @property {string} type - Field data type ('string', 'number', 'boolean', 'image', 'pdf', 'link')
10
+ * @property {string} label - Human-readable label for the field
11
+ * @property {boolean} isMandatory - Whether this field is required for all feature definitions
12
+ * @property {Object} [validation] - Validation rules for the field
13
+ * @property {string} [validation.required] - Required validation message
14
+ * @property {string} [validation.min] - Minimum length/value validation
15
+ * @property {string} [validation.max] - Maximum length/value validation
16
+ * @property {string} [validation.pattern] - Regex pattern for validation
17
+ * @property {Object} [options] - Additional options specific to field type
18
+ * @property {string} [options.placeholder] - Placeholder text for input fields
19
+ * @property {string} [options.helpText] - Help text displayed below field
20
+ * @property {string[]} [options.allowedTypes] - Allowed file types for file fields
21
+ * @property {number} [options.maxSize] - Maximum file size in bytes
22
+ * @property {number} order - Display order of the field in forms
23
+ * @example
24
+ * const titleField = {
25
+ * id: 'mandatory-title',
26
+ * type: 'string',
27
+ * label: 'Title',
28
+ * isMandatory: true,
29
+ * validation: { required: 'Title is required' },
30
+ * options: { placeholder: 'Enter feature title' },
31
+ * order: 1
32
+ * };
33
+ */
34
+
35
+ /**
36
+ * @typedef {Object} FeatureDefinition
37
+ * @property {string} id - Unique identifier for the feature definition
38
+ * @property {string} site - Site identifier this feature belongs to
39
+ * @property {string} title - Human-readable title for the feature
40
+ * @property {string} displayName - Display name shown in UI
41
+ * @property {string} icon - Icon identifier for the feature
42
+ * @property {FieldDefinition[]} fields - Array of field definitions that make up this feature
43
+ * @property {LayoutConfig} layout - Layout configuration for displaying listings
44
+ * @property {string} createdAt - ISO timestamp when feature was created
45
+ * @property {string} editedAt - ISO timestamp when feature was last edited
46
+ * @property {string|null} deletedAt - ISO timestamp when feature was deleted (null if active)
47
+ * @example
48
+ * const featureDefinition = {
49
+ * id: 'restaurant-menu',
50
+ * site: 'pluss60-demo',
51
+ * title: 'Restaurant Menu',
52
+ * displayName: 'Menu Items',
53
+ * icon: 'restaurant',
54
+ * fields: [titleField, imageField, descriptionField],
55
+ * layout: { type: 'round', iconGrid: false },
56
+ * createdAt: '2023-12-01T10:00:00Z',
57
+ * editedAt: '2023-12-01T10:00:00Z',
58
+ * deletedAt: null
59
+ * };
60
+ */
61
+
62
+ /**
63
+ * @typedef {Object} LayoutConfig
64
+ * @property {string} type - Layout type ('round', 'square', 'condensed', 'feature')
65
+ * @property {boolean} [iconGrid] - Whether to use icon grid layout (for round/square types)
66
+ * @property {Object} [options] - Additional layout-specific options
67
+ * @property {number} [options.columns] - Number of columns for grid layouts
68
+ * @property {number} [options.itemSpacing] - Spacing between items
69
+ * @property {Object} [options.textStyling] - Text styling options
70
+ * @example
71
+ * const layoutConfig = {
72
+ * type: 'round',
73
+ * iconGrid: true,
74
+ * options: {
75
+ * columns: 2,
76
+ * itemSpacing: 16,
77
+ * textStyling: { showTitle: true, showDescription: false }
78
+ * }
79
+ * };
80
+ */
81
+
82
+ /**
83
+ * @typedef {Object} Listing
84
+ * @property {string} id - Unique identifier for the listing (UUID)
85
+ * @property {string} featureDefinitionId - ID of the feature definition this listing belongs to
86
+ * @property {Object} fields - Key-value pairs of field data
87
+ * @property {string} [fields.mandatory-title] - Title field value
88
+ * @property {string} [fields.mandatory-feature-image] - Feature image URL
89
+ * @property {*} [fields.*] - Additional field values based on feature definition
90
+ * @property {string} createdAt - ISO timestamp when listing was created
91
+ * @property {string} editedAt - ISO timestamp when listing was last edited
92
+ * @property {string|null} deletedAt - ISO timestamp when listing was deleted (null if active)
93
+ * @property {Object} [metadata] - Additional metadata about the listing
94
+ * @property {string} [metadata.createdBy] - User ID who created the listing
95
+ * @property {string} [metadata.updatedBy] - User ID who last updated the listing
96
+ * @example
97
+ * const listing = {
98
+ * id: '123e4567-e89b-12d3-a456-426614174000',
99
+ * featureDefinitionId: 'restaurant-menu',
100
+ * fields: {
101
+ * 'mandatory-title': 'Margherita Pizza',
102
+ * 'mandatory-feature-image': 'https://example.com/pizza.jpg',
103
+ * 'description': 'Classic margherita with fresh basil',
104
+ * 'price': 12.99
105
+ * },
106
+ * createdAt: '2023-12-01T10:00:00Z',
107
+ * editedAt: '2023-12-01T10:00:00Z',
108
+ * deletedAt: null
109
+ * };
110
+ */
111
+
112
+ /**
113
+ * @typedef {Object} ListingItem
114
+ * @property {string} id - Listing ID
115
+ * @property {string} title - Listing title (extracted from mandatory-title field)
116
+ * @property {string} [image] - Listing image URL (extracted from mandatory-feature-image field)
117
+ * @property {Object} fields - All field values for the listing
118
+ * @property {string} featureDefinitionId - ID of parent feature definition
119
+ * @property {FeatureDefinition} [featureDefinition] - Parent feature definition (if loaded)
120
+ * @property {boolean} isActive - Whether listing is active (not deleted)
121
+ * @property {string} createdAt - Creation timestamp
122
+ * @property {string} editedAt - Last edit timestamp
123
+ * @example
124
+ * const listItem = {
125
+ * id: '123e4567-e89b-12d3-a456-426614174000',
126
+ * title: 'Margherita Pizza',
127
+ * image: 'https://example.com/pizza.jpg',
128
+ * fields: { 'mandatory-title': 'Margherita Pizza', ... },
129
+ * featureDefinitionId: 'restaurant-menu',
130
+ * featureDefinition: restaurantMenuDefinition,
131
+ * isActive: true,
132
+ * createdAt: '2023-12-01T10:00:00Z',
133
+ * editedAt: '2023-12-01T10:00:00Z'
134
+ * };
135
+ */
136
+
137
+ /**
138
+ * @typedef {Object} FeatureBuilderState
139
+ * @property {FeatureDefinition|null} feature - Currently loaded feature definition
140
+ * @property {Listing[]} listings - Listings for the current feature
141
+ * @property {boolean} loading - Global loading state
142
+ * @property {string|null} error - Global error message
143
+ * @example
144
+ * const featureBuilderState = {
145
+ * feature: restaurantMenuDefinition,
146
+ * listings: [pizzaListing, pastaListing],
147
+ * loading: false,
148
+ * error: null
149
+ * };
150
+ */
151
+
152
+ /**
153
+ * @typedef {Object} NavigationParams
154
+ * @property {Object} [listing] - Listing data for detail view
155
+ * @property {FeatureDefinition} [featureDefinition] - Feature definition object
156
+ * @property {string} [featureTitle] - Feature title for header display
157
+ * @property {string} [mode] - Navigation mode ('view', 'edit', 'create')
158
+ * @property {Object} [metadata] - Additional navigation metadata
159
+ * @example
160
+ * const navParams = {
161
+ * listing: pizzaListing,
162
+ * featureDefinition: restaurantMenuDefinition,
163
+ * featureTitle: 'Restaurant Menu',
164
+ * mode: 'view',
165
+ * metadata: { source: 'widget' }
166
+ * };
167
+ */
168
+
169
+ /**
170
+ * @typedef {Object} WidgetProps
171
+ * @property {Listing[]} items - Listings to display in widget
172
+ * @property {FeatureDefinition} featureDefinition - Feature definition for styling
173
+ * @property {string} colourBrandingMain - Main branding color
174
+ * @property {Function} onItemPress - Callback when item is pressed
175
+ * @property {Function} [onRefresh] - Callback for pull-to-refresh
176
+ * @property {boolean} refreshing - Whether widget is currently refreshing
177
+ * @property {Object} [style] - Additional styles for widget
178
+ * @property {number} [maxItems] - Maximum number of items to display
179
+ * @example
180
+ * const widgetProps = {
181
+ * items: [pizzaListing, pastaListing],
182
+ * featureDefinition: restaurantMenuDefinition,
183
+ * colourBrandingMain: '#FF6363',
184
+ * onItemPress: (item) => navigation.navigate('Detail', { listing: item }),
185
+ * refreshing: false,
186
+ * onRefresh: handleRefresh,
187
+ * maxItems: 2
188
+ * };
189
+ */
190
+
191
+ /**
192
+ * @typedef {Object} ApiResponse
193
+ * @property {boolean} success - Whether the API call was successful
194
+ * @property {*} data - Response data payload
195
+ * @property {string} [message] - Optional message from the server
196
+ * @property {string} [error] - Error message if the call failed
197
+ * @property {number} [status] - HTTP status code
198
+ * @property {Object} [metadata] - Additional response metadata
199
+ * @example
200
+ * const apiResponse = {
201
+ * success: true,
202
+ * data: { listings: [pizzaListing, pastaListing] },
203
+ * message: 'Listings fetched successfully',
204
+ * status: 200,
205
+ * metadata: { count: 2, hasMore: false }
206
+ * };
207
+ */
208
+
209
+ /**
210
+ * @typedef {Object} FieldComponentProps
211
+ * @property {FieldDefinition} fieldDefinition - Field definition configuration
212
+ * @property {*} value - Current field value
213
+ * @property {Function} onChange - Callback when field value changes
214
+ * @property {string} [error] - Validation error message
215
+ * @property {boolean} [disabled] - Whether field is disabled
216
+ * @property {Object} [style] - Additional styles for field component
217
+ * @property {Object} [theme] - Theme configuration
218
+ * @example
219
+ * const fieldProps = {
220
+ * fieldDefinition: titleField,
221
+ * value: 'Margherita Pizza',
222
+ * onChange: (newValue) => setFieldValue('title', newValue),
223
+ * error: null,
224
+ * disabled: false,
225
+ * style: { marginBottom: 16 },
226
+ * theme: { primaryColor: '#FF6363' }
227
+ * };
228
+ */
229
+
230
+ /**
231
+ * @typedef {Object} LayoutComponentProps
232
+ * @property {Listing[]} listings - Array of listings to display
233
+ * @property {FeatureDefinition} featureDefinition - Feature definition for layout configuration
234
+ * @property {string} colourBrandingMain - Main branding color
235
+ * @property {Function} onItemPress - Callback when listing item is pressed
236
+ * @property {Function} [onRefresh] - Callback for pull-to-refresh
237
+ * @property {boolean} [refreshing] - Whether layout is currently refreshing
238
+ * @property {Object} [style] - Additional styles for layout component
239
+ * @property {Object} [layoutOptions] - Layout-specific options
240
+ * @example
241
+ * const layoutProps = {
242
+ * listings: [pizzaListing, pastaListing],
243
+ * featureDefinition: restaurantMenuDefinition,
244
+ * colourBrandingMain: '#FF6363',
245
+ * onItemPress: handleItemPress,
246
+ * refreshing: false,
247
+ * onRefresh: handleRefresh,
248
+ * layoutOptions: { showImages: true, columns: 2 }
249
+ * };
250
+ */
251
+
252
+ // Export empty object to make this a module
253
+ module.exports = {};