@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,20 @@
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 { Services } from '../feature.config';
6
+ import { Components, Colours } from '../core.config';
7
+ import { values } from '../values.config';
8
+ import { selectListings, selectFeatureDefinition } from '../utils/selectors';
9
+ import { SPACING } from '../js/spacing';
10
+ import { getMainBrandingColourFromState } from '../js';
11
+ import { loadTargetFeature } from '../actions/featureBuilderActions';
12
+
13
+ // Layouts
14
+ import RoundImageList from './layouts/RoundImageList';
15
+ import CondensedList from './layouts/CondensedList';
16
+ import SquareImageList from './layouts/SquareImageList';
17
+ import FeatureImageList from './layouts/FeatureImageList';
21
18
 
22
19
  class WidgetSmall extends Component {
23
20
  constructor(props) {
@@ -42,22 +39,34 @@ class WidgetSmall extends Component {
42
39
  if (featureDefinition?.title || featureDefinition?.displayName) {
43
40
  return featureDefinition.title || featureDefinition.displayName;
44
41
  }
45
- return values.featureName || "Features";
42
+ return values.featureName || 'Features';
46
43
  };
47
44
 
48
45
  getEmptyStateText = () => {
49
- const { options } = this.props;
46
+ const { options, featureDefinition } = this.props;
50
47
  if (options && !_.isEmpty(options.EmptyText)) return options.EmptyText;
51
- return values.labels.emptyState;
48
+
49
+ const featureName = featureDefinition?.title || featureDefinition?.displayName || values.featureName || 'Feature';
50
+ return `No listings for ${featureName}`;
51
+ };
52
+
53
+ getLayoutComponent = layoutType => {
54
+ switch (layoutType) {
55
+ case values.layoutTypes.round:
56
+ return RoundImageList;
57
+ case values.layoutTypes.square:
58
+ return SquareImageList;
59
+ case values.layoutTypes.feature:
60
+ return FeatureImageList;
61
+ default:
62
+ return CondensedList;
63
+ }
52
64
  };
53
65
 
54
66
  refresh = () => {
55
67
  this.onLoadingChanged(true, async () => {
56
68
  try {
57
- await loadTargetFeature()(
58
- this.props.dispatch,
59
- () => this.props.getState,
60
- );
69
+ await this.props.dispatch(loadTargetFeature());
61
70
  } finally {
62
71
  this.onLoadingChanged(false);
63
72
  }
@@ -66,156 +75,65 @@ class WidgetSmall extends Component {
66
75
 
67
76
  onLoadingChanged = (loading, callback) => {
68
77
  this.setState({ loading }, () => {
69
- if (this.props.onLoadingChanged)
70
- this.props.onLoadingChanged(this.state.loading);
78
+ if (this.props.onLoadingChanged) this.props.onLoadingChanged(this.state.loading);
71
79
  if (callback) callback();
72
80
  });
73
81
  };
74
82
 
75
- onPressAll = () => {
76
- Services.navigation.navigate(values.screens.featureList, {
77
- options: this.props.options,
78
- });
79
- };
80
-
81
- getImageSource = (imageField) => {
82
- if (!imageField) return null;
83
-
84
- if (typeof imageField === "string") {
85
- return { uri: imageField };
86
- }
87
-
88
- if (typeof imageField === "object" && imageField.uri) {
89
- return imageField;
90
- }
91
-
92
- if (typeof imageField === "object" && imageField.url) {
93
- return { uri: imageField.url };
94
- }
95
-
96
- return null;
97
- };
98
-
99
- onListingPress = (listing) => {
83
+ onItemPress = item => {
100
84
  const { featureDefinition } = this.props;
101
- const title = this.getTitle();
102
- Services.navigation.navigate(values.screens.featureDetail, {
103
- listing: listing,
104
- featureDefinition: featureDefinition,
105
- featureTitle: title,
106
- });
107
- };
108
-
109
- renderListingCard = (listing) => {
110
- const { colourBrandingMain, featureDefinition } = this.props;
111
- const title = listing.fields?.[values.mandatoryFields.title] || "Untitled";
112
- const summary = getSummaryFieldValue(listing, featureDefinition);
113
- const imageField = listing.fields?.[values.mandatoryFields.featureImage];
114
- const imageSource = this.getImageSource(imageField);
115
-
116
- return (
117
- <TouchableOpacity
118
- key={listing.id}
119
- style={styles.cardContainer}
120
- onPress={() => this.onListingPress(listing)}
121
- >
122
- <View style={styles.borderContainer}>
123
- {imageSource ? (
124
- <Image
125
- source={imageSource}
126
- style={styles.cardImage}
127
- resizeMode="cover"
128
- />
129
- ) : (
130
- <View style={[styles.cardImage, styles.placeholderImage]}>
131
- <FontAwesome name="image" size={24} color={Colours.TEXT_LIGHT} />
132
- </View>
133
- )}
134
- <Text style={styles.cardTitle} numberOfLines={1}>
135
- {title}
136
- </Text>
137
- {summary && (
138
- <Text style={styles.cardSummary} numberOfLines={1}>
139
- {summary}
140
- </Text>
141
- )}
142
- </View>
143
- </TouchableOpacity>
144
- );
85
+ if (featureDefinition) {
86
+ Services.navigation.navigate(values.screens.featureDetail, {
87
+ listing: item,
88
+ featureDefinition: featureDefinition,
89
+ featureTitle: featureDefinition.title || featureDefinition.displayName,
90
+ });
91
+ }
145
92
  };
146
93
 
147
94
  renderContent() {
148
- const { listings } = this.props;
95
+ const { listings, featureDefinition, colourBrandingMain } = this.props;
149
96
 
150
- // Don't render widget if no listings are available (and not loading)
151
- if (_.isEmpty(listings) && !this.state.loading) {
152
- return null;
97
+ // Show loading state if no definition or loading
98
+ if (!featureDefinition || this.state.loading) {
99
+ return (
100
+ <View style={styles.loadingPadding}>
101
+ <Components.LoadingStateWidget height={180} />
102
+ </View>
103
+ );
153
104
  }
154
105
 
106
+ // Show empty state if no listings are available
155
107
  if (_.isEmpty(listings)) {
156
- if (this.state.loading) {
157
- return (
158
- <View style={styles.loadingPadding}>
159
- <Components.LoadingStateWidget height={180} />
160
- </View>
161
- );
162
- }
163
- return null;
108
+ return (
109
+ <View style={styles.emptyPadding}>
110
+ <Components.EmptyStateWidget title={this.getTitle()} height={120} />
111
+ </View>
112
+ );
164
113
  }
165
114
 
166
- // Show first 3 listings with peeking indicator
167
- const listingsToShow = listings.slice(0, values.widget.maxItems);
115
+ const layoutType = featureDefinition.layout?.type || 'condensed';
116
+ const LayoutComponent = this.getLayoutComponent(layoutType);
117
+ const title = this.getTitle();
168
118
 
169
119
  return (
170
- <ScrollView
171
- horizontal
172
- contentContainerStyle={{
173
- paddingLeft: SPACING.XS + 2,
174
- paddingRight: SPACING.SM,
175
- }}
176
- showsHorizontalScrollIndicator={false}
177
- >
178
- {listingsToShow.map((listing) => this.renderListingCard(listing))}
179
- {listings.length > values.widget.maxItems && (
180
- <TouchableOpacity
181
- style={[styles.cardContainer, styles.viewMoreCard]}
182
- onPress={this.onPressAll}
183
- >
184
- <View style={[styles.borderContainer, styles.viewMoreContainer]}>
185
- <Text style={styles.viewMoreText}>View all</Text>
186
- <Text style={styles.viewMoreCount}>{listings.length} items</Text>
187
- </View>
188
- </TouchableOpacity>
189
- )}
190
- </ScrollView>
120
+ <LayoutComponent
121
+ listings={listings}
122
+ featureDefinition={featureDefinition}
123
+ colourBrandingMain={colourBrandingMain}
124
+ title={title}
125
+ onItemPress={this.onItemPress}
126
+ />
191
127
  );
192
128
  }
193
129
 
194
130
  render() {
195
- const { colourBrandingMain } = this.props;
196
-
197
- const content = this.renderContent();
198
- // Don't render widget section if content is null (no features available)
199
- if (content === null) {
200
- return null;
201
- }
202
-
203
131
  return (
204
132
  <View style={styles.sectionContainer}>
205
133
  <View style={styles.sectionPadding}>
206
- <View style={styles.sectionHeading}>
207
- <Text style={styles.sectionTitle}>{this.getTitle()}</Text>
208
- <Components.InlineButton
209
- onPress={this.onPressAll}
210
- color={colourBrandingMain}
211
- touchableStyle={{ paddingTop: SPACING.XS + 2 }}
212
- textStyle={{ color: "#fff" }}
213
- >
214
- {values.labels.viewAll}
215
- </Components.InlineButton>
216
- </View>
134
+ <Text style={styles.sectionTitle}>{this.getTitle()}</Text>
217
135
  </View>
218
- {content}
136
+ {this.renderContent()}
219
137
  </View>
220
138
  );
221
139
  }
@@ -223,7 +141,7 @@ class WidgetSmall extends Component {
223
141
 
224
142
  const styles = StyleSheet.create({
225
143
  sectionContainer: {
226
- backgroundColor: "#fff",
144
+ backgroundColor: '#fff',
227
145
  paddingTop: SPACING.MD,
228
146
  },
229
147
  sectionPadding: {
@@ -233,98 +151,29 @@ const styles = StyleSheet.create({
233
151
  loadingPadding: {
234
152
  paddingHorizontal: SPACING.MD,
235
153
  },
236
- sectionHeading: {
237
- marginBottom: SPACING.XS,
238
- flexDirection: "row",
239
- alignContent: "flex-start",
240
- justifyContent: "space-between",
154
+ emptyPadding: {
155
+ paddingHorizontal: SPACING.MD,
241
156
  },
242
157
  sectionTitle: {
243
- fontFamily: "sf-bold",
158
+ fontFamily: 'sf-bold',
244
159
  fontSize: 24,
245
- color: "#000",
246
- },
247
- // Listing card styles
248
- cardContainer: {
249
- marginRight: SPACING.SM + 4,
250
- width: values.widget.itemMaxWidth,
251
- },
252
- borderContainer: {
253
- borderRadius: SPACING.SM,
254
- backgroundColor: "#fff",
255
- shadowColor: "#000",
256
- shadowOffset: {
257
- width: 0,
258
- height: 2,
259
- },
260
- shadowOpacity: 0.1,
261
- shadowRadius: 3.84,
262
- elevation: 4,
263
- padding: SPACING.SM,
264
- margin: SPACING.SM,
265
- minHeight: values.widget.itemMinWidth,
266
- },
267
- cardImage: {
268
- width: 88,
269
- height: 88,
270
- borderRadius: 44,
271
- backgroundColor: "#f0f0f0",
272
- alignSelf: "center",
273
- marginBottom: SPACING.XS,
274
- },
275
- placeholderImage: {
276
- justifyContent: "center",
277
- alignItems: "center",
278
- backgroundColor: "#f0f0f0",
279
- },
280
- cardTitle: {
281
- fontFamily: "sf-semibold",
282
- fontSize: 14,
283
- color: "#000",
284
- textAlign: "center",
285
- marginBottom: 4,
286
- },
287
- cardSummary: {
288
- fontFamily: "sf-regular",
289
- fontSize: 12,
290
- color: "#666",
291
- textAlign: "center",
292
- },
293
- // View more card
294
- viewMoreCard: {
295
- justifyContent: "center",
296
- },
297
- viewMoreContainer: {
298
- justifyContent: "center",
299
- alignItems: "center",
300
- minHeight: values.widget.itemMinWidth,
301
- },
302
- viewMoreText: {
303
- fontFamily: "sf-semibold",
304
- fontSize: 14,
305
- color: Colours.TEXT_DARK || "#000",
306
- },
307
- viewMoreCount: {
308
- fontFamily: "sf-regular",
309
- fontSize: 12,
310
- color: "#666",
311
- marginTop: 2,
160
+ color: '#000',
312
161
  },
313
162
  });
314
163
 
315
- const mapStateToProps = (state) => {
164
+ const mapStateToProps = state => {
316
165
  const { user, notifications } = state;
166
+ const listings = selectListings(state);
167
+ const featureDefinition = selectFeatureDefinition(state);
317
168
 
318
169
  return {
319
170
  colourBrandingMain: getMainBrandingColourFromState(state),
320
- listings: selectListings(state),
321
- featureDefinition: selectFeatureDefinition(state),
171
+ listings: listings,
172
+ featureDefinition: featureDefinition,
322
173
  site: user.site,
323
174
  dataUpdated: notifications.dataUpdated[values.notificationKey],
324
175
  strings: state.strings?.config || {},
325
176
  };
326
177
  };
327
178
 
328
- export default connect(mapStateToProps, null, null, { forwardRef: true })(
329
- WidgetSmall,
330
- );
179
+ export default connect(mapStateToProps, null, null, { forwardRef: true })(WidgetSmall);
@@ -121,7 +121,8 @@ const FeatureConfig = {
121
121
 
122
122
  afterStoreInit: (dispatch, getState) => {
123
123
  // Get the site from state (guaranteed to be available due to polling)
124
- const site = getState().user?.site;
124
+ const state = getState();
125
+ const site = state?.user?.site;
125
126
 
126
127
  if (site) {
127
128
  // Show loading state before fetching