@plusscommunities/pluss-feature-builder-app-d 8.0.8-beta.0 → 8.0.9

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.
@@ -1,23 +1,22 @@
1
- import React, { Component } from "react";
2
- import {
3
- Text,
4
- View,
5
- ScrollView,
6
- StyleSheet,
7
- TouchableOpacity,
8
- Image,
9
- } from "react-native";
10
- import { connect } from "react-redux";
11
- import _ from "lodash";
12
- import { FontAwesome } from "@expo/vector-icons";
13
- import { Services } from "../feature.config";
14
- import { Components, Colours } from "../core.config";
15
- import { values } from "../values.config";
16
- import { selectListings, selectFeatureDefinition } from "../utils/selectors";
17
- import { SPACING } from "../js/spacing";
18
- import { getMainBrandingColourFromState } from "../js";
19
- import { getSummaryFieldValue } from "../js/helpers";
20
- import { loadTargetFeature } from "../actions/featureBuilderActions";
1
+ import React, { Component } from 'react';
2
+ import { Text, View, StyleSheet } from 'react-native';
3
+ import { connect } from 'react-redux';
4
+ import _ from 'lodash';
5
+ import { FontAwesome } from '@expo/vector-icons';
6
+ import { Services } from '../feature.config';
7
+ import { Components, Colours } from '../core.config';
8
+ import { values } from '../values.config';
9
+ import { selectListings, selectFeatureDefinition } from '../utils/selectors';
10
+ import { SPACING } from '../js/spacing';
11
+ import { getMainBrandingColourFromState } from '../js';
12
+ import { getSummaryFieldValue } from '../js/helpers';
13
+ import { loadTargetFeature } from '../actions/featureBuilderActions';
14
+
15
+ // Layouts
16
+ import RoundImageList from './layouts/RoundImageList';
17
+ import CondensedList from './layouts/CondensedList';
18
+ import SquareImageList from './layouts/SquareImageList';
19
+ import FeatureImageList from './layouts/FeatureImageList';
21
20
 
22
21
  class WidgetLarge extends Component {
23
22
  constructor(props) {
@@ -42,22 +41,34 @@ class WidgetLarge extends Component {
42
41
  if (featureDefinition?.title || featureDefinition?.displayName) {
43
42
  return featureDefinition.title || featureDefinition.displayName;
44
43
  }
45
- return values.featureName || "Custom Features";
44
+ return values.featureName || 'Custom Features';
46
45
  };
47
46
 
48
47
  getEmptyStateText = () => {
49
- const { options } = this.props;
48
+ const { options, featureDefinition } = this.props;
50
49
  if (options && !_.isEmpty(options.EmptyText)) return options.EmptyText;
51
- return "No custom features available";
50
+
51
+ const featureName = featureDefinition?.title || featureDefinition?.displayName || values.featureName || 'Feature';
52
+ return `No listings for ${featureName}`;
53
+ };
54
+
55
+ getLayoutComponent = layoutType => {
56
+ switch (layoutType) {
57
+ case values.layoutTypes.round:
58
+ return RoundImageList;
59
+ case values.layoutTypes.square:
60
+ return SquareImageList;
61
+ case values.layoutTypes.feature:
62
+ return FeatureImageList;
63
+ default:
64
+ return CondensedList;
65
+ }
52
66
  };
53
67
 
54
68
  refresh = () => {
55
69
  this.onLoadingChanged(true, async () => {
56
70
  try {
57
- await loadTargetFeature()(
58
- this.props.dispatch,
59
- () => this.props.getState,
60
- );
71
+ await this.props.dispatch(loadTargetFeature());
61
72
  } finally {
62
73
  this.onLoadingChanged(false);
63
74
  }
@@ -66,138 +77,65 @@ class WidgetLarge extends Component {
66
77
 
67
78
  onLoadingChanged = (loading, callback) => {
68
79
  this.setState({ loading }, () => {
69
- if (this.props.onLoadingChanged)
70
- this.props.onLoadingChanged(this.state.loading);
80
+ if (this.props.onLoadingChanged) this.props.onLoadingChanged(this.state.loading);
71
81
  if (callback) callback();
72
82
  });
73
83
  };
74
84
 
75
- getImageSource = (imageField) => {
76
- if (!imageField) return null;
77
-
78
- if (typeof imageField === "string") {
79
- return { uri: imageField };
80
- }
81
-
82
- if (typeof imageField === "object" && imageField.uri) {
83
- return imageField;
84
- }
85
-
86
- if (typeof imageField === "object" && imageField.url) {
87
- return { uri: imageField.url };
88
- }
89
-
90
- return null;
91
- };
92
-
93
- onListingPress = (listing) => {
85
+ onItemPress = item => {
94
86
  const { featureDefinition } = this.props;
95
- const title = this.getTitle();
96
- Services.navigation.navigate(values.screens.featureDetail, {
97
- listing: listing,
98
- featureDefinition: featureDefinition,
99
- featureTitle: title,
100
- });
101
- };
102
-
103
- renderListingCard = (listing) => {
104
- const { colourBrandingMain, featureDefinition } = this.props;
105
- const title = listing.fields?.[values.mandatoryFields.title] || "Untitled";
106
- const summary = getSummaryFieldValue(listing, featureDefinition);
107
- const imageField = listing.fields?.[values.mandatoryFields.featureImage];
108
- const imageSource = this.getImageSource(imageField);
109
-
110
- return (
111
- <TouchableOpacity
112
- key={listing.id}
113
- style={styles.listItem}
114
- onPress={() => this.onListingPress(listing)}
115
- >
116
- {/* Small circular avatar image */}
117
- <View style={styles.avatarContainer}>
118
- {imageSource ? (
119
- <Image
120
- source={imageSource}
121
- style={styles.avatar}
122
- resizeMode="cover"
123
- />
124
- ) : (
125
- <View style={[styles.avatar, styles.placeholderAvatar]}>
126
- <FontAwesome name="image" size={16} color={Colours.TEXT_LIGHT} />
127
- </View>
128
- )}
129
- </View>
130
-
131
- {/* Text content */}
132
- <View style={styles.textContainer}>
133
- <Text style={styles.titleText} numberOfLines={1}>
134
- {title}
135
- </Text>
136
- {summary && (
137
- <Text style={styles.descriptionText} numberOfLines={1}>
138
- {summary}
139
- </Text>
140
- )}
141
- </View>
142
-
143
- {/* Chevron icon */}
144
- <View style={styles.chevronContainer}>
145
- <FontAwesome
146
- name="chevron-right"
147
- size={14}
148
- color={Colours.TEXT_LIGHT}
149
- />
150
- </View>
151
- </TouchableOpacity>
152
- );
87
+ if (featureDefinition) {
88
+ Services.navigation.navigate(values.screens.featureDetail, {
89
+ listing: item,
90
+ featureDefinition: featureDefinition,
91
+ featureTitle: featureDefinition.title || featureDefinition.displayName,
92
+ });
93
+ }
153
94
  };
154
95
 
155
96
  renderContent() {
156
- const { listings } = this.props;
97
+ const { listings, featureDefinition, colourBrandingMain } = this.props;
157
98
 
158
- // Don't render widget if no listings are available (and not loading)
159
- if (_.isEmpty(listings) && !this.state.loading) {
160
- return null;
99
+ // Show loading state if no definition or loading
100
+ if (!featureDefinition || this.state.loading) {
101
+ return (
102
+ <View style={styles.loadingPadding}>
103
+ <Components.LoadingStateWidget height={300} />
104
+ </View>
105
+ );
161
106
  }
162
107
 
108
+ // Show empty state if no listings are available
163
109
  if (_.isEmpty(listings)) {
164
- if (this.state.loading) {
165
- return (
166
- <View style={styles.loadingPadding}>
167
- <Components.LoadingStateWidget height={300} />
168
- </View>
169
- );
170
- }
171
- return null;
110
+ return (
111
+ <View style={styles.emptyPadding}>
112
+ <Components.EmptyStateWidget title={this.getTitle()} height={120} />
113
+ </View>
114
+ );
172
115
  }
173
116
 
117
+ const layoutType = featureDefinition.layout?.type || 'condensed';
118
+ const LayoutComponent = this.getLayoutComponent(layoutType);
119
+ const title = this.getTitle();
120
+
174
121
  return (
175
- <ScrollView
176
- contentContainerStyle={styles.featuresContainer}
177
- showsVerticalScrollIndicator={false}
178
- >
179
- {listings.map((listing) => this.renderListingCard(listing))}
180
- </ScrollView>
122
+ <LayoutComponent
123
+ listings={listings}
124
+ featureDefinition={featureDefinition}
125
+ colourBrandingMain={colourBrandingMain}
126
+ title={title}
127
+ onItemPress={this.onItemPress}
128
+ />
181
129
  );
182
130
  }
183
131
 
184
132
  render() {
185
- const { colourBrandingMain } = this.props;
186
-
187
- const content = this.renderContent();
188
- // Don't render widget section if content is null (no features available)
189
- if (content === null) {
190
- return null;
191
- }
192
-
193
133
  return (
194
134
  <View style={styles.sectionContainer}>
195
135
  <View style={styles.sectionPadding}>
196
- <View style={styles.sectionHeading}>
197
- <Text style={styles.sectionTitle}>{this.getTitle()}</Text>
198
- </View>
136
+ <Text style={styles.sectionTitle}>{this.getTitle()}</Text>
199
137
  </View>
200
- {content}
138
+ {this.renderContent()}
201
139
  </View>
202
140
  );
203
141
  }
@@ -205,7 +143,7 @@ class WidgetLarge extends Component {
205
143
 
206
144
  const styles = StyleSheet.create({
207
145
  sectionContainer: {
208
- backgroundColor: "#fff",
146
+ backgroundColor: '#fff',
209
147
  flex: 1,
210
148
  },
211
149
  sectionPadding: {
@@ -216,65 +154,17 @@ const styles = StyleSheet.create({
216
154
  loadingPadding: {
217
155
  paddingHorizontal: SPACING.MD,
218
156
  },
219
- sectionHeading: {
220
- marginBottom: SPACING.MD,
157
+ emptyPadding: {
158
+ paddingHorizontal: SPACING.MD,
221
159
  },
222
160
  sectionTitle: {
223
- fontFamily: "sf-bold",
161
+ fontFamily: 'sf-bold',
224
162
  fontSize: 24,
225
- color: "#000",
226
- },
227
- featuresContainer: {
228
- paddingHorizontal: SPACING.MD,
229
- paddingBottom: SPACING.MD,
230
- },
231
- // Listing item styles (similar to CondensedListItem)
232
- listItem: {
233
- flexDirection: "row",
234
- alignItems: "center",
235
- paddingHorizontal: 16,
236
- paddingVertical: 8,
237
- backgroundColor: "#fff",
238
- },
239
- avatarContainer: {
240
- width: 40,
241
- height: 40,
242
- borderRadius: 20,
243
- marginRight: 12,
244
- },
245
- avatar: {
246
- width: 40,
247
- height: 40,
248
- borderRadius: 20,
249
- backgroundColor: Colours.BACKGROUND_LIGHT || "#f0f0f0",
250
- },
251
- placeholderAvatar: {
252
- backgroundColor: Colours.BACKGROUND_LIGHT || "#f0f0f0",
253
- justifyContent: "center",
254
- alignItems: "center",
255
- },
256
- textContainer: {
257
- flex: 1,
258
- justifyContent: "center",
259
- },
260
- titleText: {
261
- fontFamily: "sf-semibold",
262
- fontSize: 16,
263
- color: Colours.TEXT_DARK || "#333",
264
- marginBottom: 2,
265
- },
266
- descriptionText: {
267
- fontFamily: "sf-regular",
268
- fontSize: 14,
269
- color: Colours.TEXT_LIGHT || "#666",
270
- lineHeight: 18,
271
- },
272
- chevronContainer: {
273
- paddingLeft: 8,
163
+ color: '#000',
274
164
  },
275
165
  });
276
166
 
277
- const mapStateToProps = (state) => {
167
+ const mapStateToProps = state => {
278
168
  const { user, notifications } = state;
279
169
 
280
170
  return {
@@ -287,6 +177,4 @@ const mapStateToProps = (state) => {
287
177
  };
288
178
  };
289
179
 
290
- export default connect(mapStateToProps, null, null, { forwardRef: true })(
291
- WidgetLarge,
292
- );
180
+ export default connect(mapStateToProps, null, null, { forwardRef: true })(WidgetLarge);