@plusscommunities/pluss-feeds-web 1.0.1-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.
Binary file
Binary file
package/src/index.js ADDED
@@ -0,0 +1,27 @@
1
+ import FeedHub from './screens/FeedHub';
2
+ import Feed from './screens/Feed';
3
+ import AddFeed from './screens/AddFeed';
4
+ // import { AnalyticsHub } from './components/AnalyticsHub.js';
5
+ import FeedReducer from './reducers/FeedReducer';
6
+ import { values } from './values.config';
7
+
8
+ export const Reducers = (() => {
9
+ const reducers = {};
10
+ reducers[values.reducerKey] = FeedReducer;
11
+ return reducers;
12
+ })();
13
+ export const Screens = (() => {
14
+ const screens = {};
15
+ screens[values.screenFeedHub] = FeedHub;
16
+ screens[values.screenFeed] = Feed;
17
+ screens[values.screenAddFeed] = AddFeed;
18
+ return screens;
19
+ })();
20
+ export { default as Config } from './feature.config';
21
+ // export { default as ActivityText } from './components/ActivityText';
22
+ export { default as ViewWidget } from './components/ViewWidget';
23
+ export { default as ViewFull } from './components/ViewFull';
24
+ export { default as PreviewWidget } from './components/PreviewWidget';
25
+ export { default as PreviewFull } from './components/PreviewFull';
26
+ export { default as PreviewGrid } from './components/PreviewGrid';
27
+ // export const Analytics = [AnalyticsHub];
@@ -0,0 +1,71 @@
1
+ import _ from 'lodash';
2
+
3
+ import {
4
+ FEEDS_LOADING,
5
+ FEEDS_LOADED,
6
+ FEEDS_REMOVED,
7
+ FEEDS_SUBMISSIONS_LOADED,
8
+ FEEDS_SUBMISSIONS_REMOVED,
9
+ FEED_TYPES_LOADED,
10
+ } from '../actions/types';
11
+
12
+ const INITIAL_STATE = {
13
+ feeds: [],
14
+ submissions: [],
15
+ feedTypes: [],
16
+ loading: false,
17
+ };
18
+
19
+ export default (state = INITIAL_STATE, action) => {
20
+ switch (action.type) {
21
+ case FEEDS_LOADING:
22
+ return { ...state, loading: true };
23
+ case FEEDS_LOADED:
24
+ const feeds = _.unionWith(action.payload, state.feeds, (v1, v2) => {
25
+ return v1 != null && v2 != null && v1.id === v2.id;
26
+ });
27
+ return {
28
+ ...state,
29
+ feeds: feeds.map((feedItem) => ({ ...feedItem })),
30
+ loading: false,
31
+ };
32
+ case FEEDS_REMOVED:
33
+ const fIndex = _.findIndex(state.feeds, (feed) => {
34
+ return feed != null && feed.id === action.payload;
35
+ });
36
+ if (fIndex > -1) {
37
+ const newFeeds = [...state.feeds];
38
+ newFeeds.splice(fIndex, 1);
39
+ return { ...state, feeds: newFeeds };
40
+ }
41
+ return state;
42
+ case FEEDS_SUBMISSIONS_LOADED:
43
+ var submissions = _.unionWith(action.payload, state.submissions, (v1, v2) => {
44
+ return v1 != null && v2 != null && v1.id === v2.id;
45
+ });
46
+ return {
47
+ ...state,
48
+ submissions: _.filter(submissions, (feed) => !feed.deleted),
49
+ };
50
+ case FEEDS_SUBMISSIONS_REMOVED:
51
+ const sIndex = _.findIndex(state.submissions, (submission) => {
52
+ return submission != null && submission.id === action.payload;
53
+ });
54
+ if (sIndex > -1) {
55
+ const newSubmissions = [...state.submissions];
56
+ newSubmissions.splice(sIndex, 1);
57
+ return { ...state, submissions: newSubmissions };
58
+ }
59
+ return state;
60
+ case FEED_TYPES_LOADED:
61
+ const feedTypes = _.unionWith(action.payload, state.feedTypes, (v1, v2) => {
62
+ return v1 != null && v2 != null && v1.id === v2.id;
63
+ });
64
+ return {
65
+ ...state,
66
+ feedTypes: feedTypes,
67
+ };
68
+ default:
69
+ return state;
70
+ }
71
+ };
@@ -0,0 +1,402 @@
1
+ import _ from 'lodash';
2
+ import React, { Component } from 'react';
3
+ import { withRouter } from 'react-router';
4
+ import { connect } from 'react-redux';
5
+ import { feedsLoaded, feedsUpdate } from '../actions';
6
+ import { PlussCore } from '../feature.config';
7
+ import { feedActions, userActions } from '../apis';
8
+ import { values } from '../values.config';
9
+
10
+ const { Components, Helper, Session } = PlussCore;
11
+
12
+ class AddFeed extends Component {
13
+ constructor(props) {
14
+ super(props);
15
+ this.imageInput = null;
16
+ const queryParams = new URLSearchParams(props.location.search);
17
+ this.state = {
18
+ feedId: Helper.safeReadParams(props, 'feedId') ? props.match.params.feedId : null,
19
+ feed: null,
20
+ updating: false,
21
+ types: [],
22
+ users: [],
23
+ photos: [],
24
+ userSearch: '',
25
+ userFilterOpen: false,
26
+ selectedUser: null,
27
+ id: null,
28
+ userId: queryParams.get('userId') || '',
29
+ userName: '',
30
+ title: '',
31
+ text: '',
32
+ type: '',
33
+ showWarnings: false,
34
+ success: false,
35
+ };
36
+ }
37
+
38
+ UNSAFE_componentWillMount() {
39
+ Session.checkLoggedIn(this, this.props.auth);
40
+ }
41
+
42
+ componentDidMount() {
43
+ this.getFeedTypes();
44
+ this.getUsers();
45
+ if (this.state.feedId) this.getFeed();
46
+ }
47
+
48
+ getFeed = async () => {
49
+ try {
50
+ const res = await feedActions.getFeed(this.state.feedId);
51
+ // console.log('getFeed:', res.data);
52
+ this.setState({ ...res.data, selectedUser: res.data.user, userId: res.data.user.id, userName: res.data.user.displayName });
53
+ this.checkSetImages(res.data.photos);
54
+ } catch (error) {
55
+ console.error('getFeed', error);
56
+ }
57
+ };
58
+
59
+ checkSetImages(photos) {
60
+ if (this.imageInput) {
61
+ if (!_.isEmpty(photos)) {
62
+ this.imageInput.getWrappedInstance().setValue(photos);
63
+ }
64
+ } else {
65
+ setTimeout(() => {
66
+ this.checkSetImages(photos);
67
+ }, 100);
68
+ }
69
+ }
70
+
71
+ getFeedTypes = async () => {
72
+ try {
73
+ const res = await feedActions.getFeedTypes(this.props.auth.site);
74
+ this.setState({ types: res.data }, this.setDefaultFeedType);
75
+ // if (res.data != null) this.props.feedTypesLoaded(res.data);
76
+ } catch (error) {
77
+ console.error('getFeedTypes', error);
78
+ }
79
+ };
80
+
81
+ setDefaultFeedType = () => {
82
+ const { types, feedId } = this.state;
83
+ if (types.length !== 0 && feedId == null) {
84
+ this.setState({ type: types[0].label });
85
+ }
86
+ };
87
+
88
+ getUsers = async () => {
89
+ try {
90
+ const res = await userActions.fetchUsers(this.props.auth.site);
91
+ if (res.userFetchFail) return;
92
+ if (res.data != null && !_.isEmpty(res.data.results.Items)) {
93
+ const items = res.data.results.Items;
94
+ this.setState(
95
+ {
96
+ users: _.sortBy(items, (u) => {
97
+ return (u.displayName || '').toLowerCase();
98
+ }),
99
+ },
100
+ this.setDefaultUser,
101
+ );
102
+ }
103
+ } catch (error) {
104
+ console.error('getUsers', error);
105
+ }
106
+ };
107
+
108
+ getUserId = (user) => user.userId || user.Id || user.id;
109
+
110
+ setDefaultUser = () => {
111
+ const { users, userId } = this.state;
112
+ // console.log('setDefaultUser', { userId });
113
+ if (userId) {
114
+ const user = users.find((u) => this.getUserId(u) === userId);
115
+ if (user) this.onSelectUser(user);
116
+ } else {
117
+ this.onUnselectUser();
118
+ }
119
+ };
120
+
121
+ onSelectType = (key) => {
122
+ this.setState({ type: key });
123
+ };
124
+
125
+ renderTypeOptions() {
126
+ const { types, type } = this.state;
127
+ return (
128
+ <div style={{ marginBottom: 15 }}>
129
+ <Components.Text type="formLabel">Select Type</Components.Text>
130
+ <div className="marginTop-4">
131
+ {types.map((ev) => {
132
+ if (ev === null) return null;
133
+ return (
134
+ <Components.Tag
135
+ key={ev.id}
136
+ text={ev.label}
137
+ style={{ backgroundColor: ev.colour, borderRadius: 8, marginRight: 10, opacity: ev.label === type ? 1 : 0.5 }}
138
+ onClick={() => this.onSelectType(ev.label)}
139
+ />
140
+ );
141
+ })}
142
+ </div>
143
+ </div>
144
+ );
145
+ }
146
+
147
+ onHandleChange = (event) => {
148
+ var stateChange = {};
149
+ stateChange[event.target.getAttribute('id')] = event.target.value;
150
+ this.setState(stateChange);
151
+ };
152
+
153
+ onOpenUserSelector = () => {
154
+ this.setState({ userFilterOpen: true });
155
+ };
156
+
157
+ onCloseUserSelector = () => {
158
+ this.setState({ userFilterOpen: false });
159
+ };
160
+
161
+ onSelectUser = (user) => {
162
+ // console.log('onSelectUser', user);
163
+ this.setState({ selectedUser: user, userId: this.getUserId(user), userName: user.displayName, userFilterOpen: false });
164
+ };
165
+
166
+ onUnselectUser = () => {
167
+ this.setState({ selectedUser: null, userId: '', userName: '' });
168
+ };
169
+
170
+ onSave = async () => {
171
+ try {
172
+ this.setState({ showWarnings: false });
173
+ if (!this.validateForm()) {
174
+ this.setState({ showWarnings: true });
175
+ return;
176
+ }
177
+ if (this.state.updating) return;
178
+ this.setState({ updating: true });
179
+ const feed = {
180
+ id: this.state.id,
181
+ userId: this.state.userId,
182
+ type: this.state.type,
183
+ title: this.state.title,
184
+ text: this.state.text,
185
+ photos: this.state.photos,
186
+ site: this.props.auth.site,
187
+ };
188
+
189
+ if (this.state.id !== null) {
190
+ await feedActions.editFeed(feed);
191
+ this.setState({ success: true, updating: false });
192
+ this.props.feedsLoaded([feed]);
193
+ } else {
194
+ await feedActions.createFeed(feed);
195
+ this.setState({ success: true, updating: false });
196
+ this.props.feedsUpdate(this.props.auth.site);
197
+ }
198
+ } catch (error) {
199
+ this.setState({ updating: false });
200
+ alert('Something went wrong with the request. Please try again.');
201
+ }
202
+ };
203
+
204
+ renderSuccess() {
205
+ if (!this.state.success) return null;
206
+
207
+ const title = this.props.strings[`${values.featureKey}_textTitleFeeds`] || values.textTitleFeeds;
208
+ return (
209
+ <Components.SuccessPopup
210
+ text={`Feed has been ${this.state.id != null ? 'edited' : 'added'}`}
211
+ buttons={[
212
+ {
213
+ type: 'outlined',
214
+ onClick: () => window.history.back(),
215
+ text: `Back to ${title}`,
216
+ },
217
+ ]}
218
+ />
219
+ );
220
+ }
221
+
222
+ validateForm() {
223
+ const { userId, userName, title } = this.state;
224
+ // console.log('validateForm', { userId, userName, title });
225
+
226
+ if (_.isEmpty(userId)) return false;
227
+ if (_.isEmpty(userName)) return false;
228
+ if (_.isEmpty(title)) return false;
229
+ return true;
230
+ }
231
+
232
+ renderSubmit() {
233
+ if (this.state.updating) {
234
+ return <Components.Button buttonType="secondary">Saving...</Components.Button>;
235
+ }
236
+
237
+ return (
238
+ <div>
239
+ <Components.Button
240
+ inline
241
+ buttonType="tertiary"
242
+ onClick={() => this.props.history.push(values.routeHub)}
243
+ isActive
244
+ style={{ marginRight: 16 }}
245
+ >
246
+ Cancel
247
+ </Components.Button>
248
+ <Components.Button inline buttonType="primary" onClick={this.onSave} isActive={this.validateForm()}>
249
+ Save
250
+ </Components.Button>
251
+ </div>
252
+ );
253
+ }
254
+
255
+ renderSelectUser() {
256
+ const { showWarnings, selectedUser } = this.state;
257
+ const isValid = !_.isNil(selectedUser);
258
+ const showError = showWarnings && !isValid;
259
+ return (
260
+ <div className={`genericInputContainer ${isValid ? 'genericInput-valid' : ''} ${showError ? 'genericInput-error' : ''}`.trim()}>
261
+ <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: 0, justifyContent: 'space-between' }}>
262
+ <div className="fieldLabel">Select User</div>
263
+ {showError ? <div className="fieldLabel fieldLabel-warning">Required</div> : null}
264
+ </div>
265
+ <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
266
+ <div className="inputRequired " />
267
+ {selectedUser ? (
268
+ <Components.Tag className="marginRight-10" rightIcon="close" rightClick={this.onUnselectUser}>
269
+ <Components.UserListing size={15} user={selectedUser} textClass="tag_text" />
270
+ </Components.Tag>
271
+ ) : (
272
+ <Components.Tag onClick={this.onOpenUserSelector} text="Select User" />
273
+ )}
274
+ </div>
275
+ </div>
276
+ );
277
+ }
278
+
279
+ renderMain() {
280
+ return (
281
+ <div style={{ marginBottom: 15 }}>
282
+ <div className="padding-60 paddingVertical-40 bottomDivideBorder">
283
+ <Components.Text type="formTitleLarge" className="marginBottom-24">
284
+ {this.state.feedId == null ? 'New' : 'Edit'} Feed
285
+ </Components.Text>
286
+ {/* Resident Information */}
287
+ {this.renderSelectUser()}
288
+ {this.renderTypeOptions()}
289
+ <Components.GenericInput
290
+ id="title"
291
+ label="Title"
292
+ type="textarea"
293
+ placeholder="Title"
294
+ value={this.state.title}
295
+ onChange={(e) => this.onHandleChange(e)}
296
+ inputStyle={{
297
+ height: 80,
298
+ }}
299
+ isRequired
300
+ isValid={() => {
301
+ return !_.isEmpty(this.state.title);
302
+ }}
303
+ showError={() => {
304
+ return this.state.showWarnings && _.isEmpty(this.state.title);
305
+ }}
306
+ alwaysShowLabel
307
+ />
308
+ <Components.GenericInput
309
+ id="text"
310
+ label="Text"
311
+ type="textarea"
312
+ placeholder="Text"
313
+ value={this.state.text}
314
+ onChange={(e) => this.onHandleChange(e)}
315
+ inputStyle={{
316
+ height: 80,
317
+ }}
318
+ alwaysShowLabel
319
+ />
320
+ <div className="marginBottom-16">
321
+ <Components.Text type="formLabel" className="marginBottom-4">
322
+ Images
323
+ </Components.Text>
324
+ <Components.ImageInput
325
+ ref={(ref) => (this.imageInput = ref)}
326
+ multiple
327
+ refreshCallback={(photos) => this.setState({ photos })}
328
+ />
329
+ </div>
330
+ </div>
331
+ </div>
332
+ );
333
+ }
334
+
335
+ renderUserFilterPopup() {
336
+ const { userFilterOpen, userSearch, users } = this.state;
337
+ if (!userFilterOpen) return null;
338
+ return (
339
+ <Components.Popup
340
+ title="Select Requestor"
341
+ maxWidth={600}
342
+ minWidth={400}
343
+ maxHeight={600}
344
+ minHeight={600}
345
+ hasPadding
346
+ onClose={this.onCloseUserSelector}
347
+ buttons={[
348
+ {
349
+ type: 'tertiary',
350
+ onClick: this.onCloseUserSelector,
351
+ isActive: true,
352
+ text: 'Cancel',
353
+ },
354
+ ]}
355
+ >
356
+ <Components.GenericInput
357
+ id="userSearch"
358
+ type="text"
359
+ label="Search User"
360
+ placeholder="Search name"
361
+ value={userSearch}
362
+ onChange={(e) => this.onHandleChange(e)}
363
+ alwaysShowLabel
364
+ />
365
+ {_.sortBy(users, (u) => u.displayName.toUpperCase())
366
+ .filter((u) => {
367
+ if (_.isEmpty(userSearch)) return true;
368
+ return u.displayName.toUpperCase().indexOf(userSearch.toUpperCase()) > -1;
369
+ })
370
+ .map((user) => {
371
+ return <Components.UserListing key={this.getUserId(user)} user={user} onClick={() => this.onSelectUser(user)} />;
372
+ })}
373
+ </Components.Popup>
374
+ );
375
+ }
376
+
377
+ render() {
378
+ const { success } = this.state;
379
+
380
+ return (
381
+ <Components.OverlayPage>
382
+ <Components.OverlayPageContents noBottomButtons={success}>
383
+ <Components.OverlayPageSection className="pageSectionWrapper--newPopup">
384
+ <div>
385
+ {this.renderSuccess()}
386
+ {!success && this.renderMain()}
387
+ </div>
388
+ {this.renderUserFilterPopup()}
389
+ </Components.OverlayPageSection>
390
+ </Components.OverlayPageContents>
391
+ <Components.OverlayPageBottomButtons>{this.renderSubmit()}</Components.OverlayPageBottomButtons>
392
+ </Components.OverlayPage>
393
+ );
394
+ }
395
+ }
396
+
397
+ const mapStateToProps = (state) => {
398
+ const { auth } = state;
399
+ return { auth, strings: (state.strings && state.strings.config) || {} };
400
+ };
401
+
402
+ export default connect(mapStateToProps, { feedsUpdate, feedsLoaded })(withRouter(AddFeed));