@plusscommunities/pluss-core-app 1.2.4 → 1.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-core-app",
3
- "version": "1.2.4",
3
+ "version": "1.3.0",
4
4
  "description": "Core extension package for Pluss Communities platform",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/apis/index.js CHANGED
@@ -4,4 +4,5 @@ export * from './followerActions';
4
4
  export * from './contactActions';
5
5
  export * from './eventActions';
6
6
  export * from './analyticsActions';
7
+ export * from './notificationActions';
7
8
  export { default as userActions } from './userActions';
@@ -0,0 +1,47 @@
1
+ import { getUrl } from '../helper';
2
+ import { authedFunction } from '../session';
3
+
4
+ export const notificationActions = {
5
+ getAppNotificationSetting: () => {
6
+ return authedFunction({
7
+ method: 'GET',
8
+ url: getUrl('notifications', 'getState/get', {
9
+ type: 'app',
10
+ }),
11
+ });
12
+ },
13
+ muteApp: () => {
14
+ return authedFunction({
15
+ method: 'POST',
16
+ url: getUrl('notifications', 'updateState/mute'),
17
+ data: { type: 'app' },
18
+ });
19
+ },
20
+ unmuteApp: () => {
21
+ return authedFunction({
22
+ method: 'POST',
23
+ url: getUrl('notifications', 'updateState/unmute'),
24
+ data: { type: 'app' },
25
+ });
26
+ },
27
+ getEntityNotificationSetting: (type, id) => {
28
+ return authedFunction({
29
+ method: 'GET',
30
+ url: getUrl('notifications', 'getState/get', { type, id }),
31
+ });
32
+ },
33
+ muteEntity: (type, id) => {
34
+ return authedFunction({
35
+ method: 'POST',
36
+ url: getUrl('notifications', 'updateState/mute'),
37
+ data: { type, id },
38
+ });
39
+ },
40
+ unmuteEntity: (type, id) => {
41
+ return authedFunction({
42
+ method: 'POST',
43
+ url: getUrl('notifications', 'updateState/unmute'),
44
+ data: { type, id },
45
+ });
46
+ },
47
+ };
@@ -1,16 +1,13 @@
1
1
  import React, { Component } from 'react';
2
- import { View, Text, TouchableOpacity, Image } from 'react-native';
2
+ import { View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native';
3
3
  import _ from 'lodash';
4
4
  import moment from 'moment';
5
5
  import { connect } from 'react-redux';
6
6
  import { Icon } from 'react-native-elements';
7
- import { getPluralS, getThumb300, get1400 } from '../helper';
7
+ import { getPluralS, getThumb300, get1400, getSiteSettingFromState } from '../helper';
8
8
  import { getMainBrandingColourFromState, TEXT_DARKEST, BG_GREY, TEXT_LIGHT, LINEGREY } from '../colours';
9
- import { Spinner } from './Spinner';
10
- import { reactionActions } from '../apis';
11
- import { ConfirmPopup } from './ConfirmPopup';
12
- import { ProfilePic } from './ProfilePic';
13
- import { ImagePopup } from './ImagePopup';
9
+ import { reactionActions, notificationActions } from '../apis';
10
+ import { ConfirmPopup, ProfilePic, ImagePopup, InlineButton, Spinner } from './';
14
11
 
15
12
  class CommentSection extends Component {
16
13
  constructor(props) {
@@ -23,10 +20,16 @@ class CommentSection extends Component {
23
20
  commentToReport: null,
24
21
  commentReportedStatus: null,
25
22
  reportLoading: false,
23
+ processing: false,
24
+ muteExpiry: null,
25
+ muteLoaded: false,
26
26
  };
27
27
  }
28
28
 
29
29
  componentDidMount() {
30
+ this.getNotificationSate();
31
+ // setTimeout(this.getNotificationSate, 3000);
32
+
30
33
  if (!_.includes(this.props.user.hidden, 'viewComment')) {
31
34
  this.getComments();
32
35
  }
@@ -97,6 +100,64 @@ class CommentSection extends Component {
97
100
  this.props.commentReply.getWrappedInstance().focusInput();
98
101
  }
99
102
 
103
+ onMute = () => {
104
+ const { entityType, entityId } = this.props;
105
+ this.setState({ processing: true }, async () => {
106
+ try {
107
+ const { data } = await notificationActions.muteEntity(entityType, entityId);
108
+ // console.log('onMute', data);
109
+ const muteExpiry = moment(data.Expiry);
110
+ this.setState({ muteExpiry, processing: false });
111
+ } catch (error) {
112
+ console.error('onMute', error);
113
+ this.setState({ processing: false });
114
+ }
115
+ });
116
+ };
117
+
118
+ onUnmute = () => {
119
+ const { entityType, entityId } = this.props;
120
+ this.setState({ processing: true }, async () => {
121
+ try {
122
+ const { data } = await notificationActions.unmuteEntity(entityType, entityId);
123
+ // console.log('onUnmute', data);
124
+ this.setState({ muteExpiry: null, processing: false });
125
+ } catch (error) {
126
+ console.error('onUnmute', error);
127
+ this.setState({ processing: false });
128
+ }
129
+ });
130
+ };
131
+
132
+ isMuted = () => {
133
+ const { muteExpiry } = this.state;
134
+ return muteExpiry && moment() <= muteExpiry;
135
+ };
136
+
137
+ getMuteRemaining = () => {
138
+ const { muteExpiry } = this.state;
139
+ const actual = muteExpiry ? moment.duration(muteExpiry.diff(moment())).asHours() : 0;
140
+ return Math.abs(Math.ceil(actual));
141
+ };
142
+
143
+ getNotificationSate = () => {
144
+ const { processing } = this.state;
145
+ const { entityType, entityId, notificationsForComments } = this.props;
146
+ if (processing || !notificationsForComments) return;
147
+
148
+ this.setState({ processing: true }, async () => {
149
+ try {
150
+ const { data } = await notificationActions.getEntityNotificationSetting(entityType, entityId);
151
+ // console.log('getNotificationSate', data);
152
+ const muteExpiry = data ? moment(data.Expiry) : null;
153
+ this.setState({ muteExpiry, muteLoaded: true, processing: false });
154
+ } catch (error) {
155
+ console.error('getNotificationSate', error);
156
+ this.setState({ processing: false });
157
+ }
158
+ });
159
+ };
160
+
100
161
  getAdjustedSize(size) {
101
162
  if (this.props.scaleFont) {
102
163
  return size + this.props.user.fontScale;
@@ -288,6 +349,47 @@ class CommentSection extends Component {
288
349
  );
289
350
  }
290
351
 
352
+ renderMute() {
353
+ const { notificationsForComments } = this.props;
354
+ const { muteLoaded, muteExpiry, processing } = this.state;
355
+ if (!notificationsForComments || !muteLoaded) return null;
356
+
357
+ if (processing)
358
+ return (
359
+ <View style={styles.muteSpinnerContainer}>
360
+ <Spinner size={'small'} color={this.props.colourBrandingMain} />
361
+ </View>
362
+ );
363
+
364
+ const isMuted = this.isMuted();
365
+ const hours = this.getMuteRemaining();
366
+ const mutedFor = `Muted for ${hours} hour${getPluralS(hours)}`;
367
+ // console.log(muteExpiry.format('DD MMM YYYY hh:mm a'));
368
+
369
+ return (
370
+ <View style={styles.muteContainer}>
371
+ {isMuted && muteExpiry ? <Text style={[{ color: this.props.colourBrandingMain }, styles.mutedForText]}>{mutedFor}</Text> : null}
372
+ <InlineButton
373
+ onPress={isMuted ? this.onUnmute : this.onMute}
374
+ color="#fff"
375
+ style={[{ borderColor: this.props.colourBrandingMain }, styles.muteButton]}
376
+ disabled={processing}
377
+ disabledOpacity
378
+ noText
379
+ >
380
+ <View style={styles.muteButtonInner}>
381
+ <Icon
382
+ name={isMuted ? 'bell-o' : 'bell-slash-o'}
383
+ type={'font-awesome'}
384
+ iconStyle={[{ color: this.props.colourBrandingMain }, styles.muteButtonIcon]}
385
+ />
386
+ <Text style={[{ color: this.props.colourBrandingMain }, styles.muteButtonText]}>{isMuted ? 'Unmute' : 'Mute'}</Text>
387
+ </View>
388
+ </InlineButton>
389
+ </View>
390
+ );
391
+ }
392
+
291
393
  renderComments() {
292
394
  if (this.state.commentsLoading) {
293
395
  return (
@@ -317,11 +419,14 @@ class CommentSection extends Component {
317
419
  <View style={styles.commentSection}>
318
420
  {!this.isEmpty() && (
319
421
  <View style={styles.commentSectionTitleRow}>
320
- {!this.props.hideAddComment && this.state.comments.length > 2 && (
321
- <TouchableOpacity onPress={this.onGoToAdd.bind(this)}>
322
- <Text style={[styles.goToText, { color: this.props.colourBrandingMain }]}>Add a comment</Text>
323
- </TouchableOpacity>
324
- )}
422
+ <View style={{ alignItems: 'flex-end' }}>
423
+ {this.renderMute()}
424
+ {!this.props.hideAddComment && this.state.comments.length > 2 && (
425
+ <TouchableOpacity onPress={this.onGoToAdd.bind(this)}>
426
+ <Text style={[styles.goToText, { color: this.props.colourBrandingMain }]}>Add a comment</Text>
427
+ </TouchableOpacity>
428
+ )}
429
+ </View>
325
430
  <Text style={[styles.commentCount, { fontSize: this.getAdjustedSize(15) }]}>
326
431
  {this.state.comments.length}
327
432
  {` comment${getPluralS(this.state.comments.length)}`}
@@ -374,22 +479,22 @@ class CommentSection extends Component {
374
479
  }
375
480
  }
376
481
 
377
- const styles = {
482
+ const styles = StyleSheet.create({
378
483
  commentSectionOuter: {
379
484
  flex: 1,
380
485
  paddingHorizontal: 16,
381
486
  },
382
487
  commentSection: {
383
488
  flex: 1,
384
- paddingVertical: 16,
489
+ paddingVertical: 12,
385
490
  borderTopColor: LINEGREY,
386
491
  borderTopWidth: 1,
387
- borderTopStyle: 'solid',
388
492
  },
389
493
  commentCount: {
390
494
  fontFamily: 'sf-semibold',
391
495
  color: TEXT_DARKEST,
392
496
  flex: 1,
497
+ marginTop: 4,
393
498
  },
394
499
  comment: {
395
500
  marginTop: 16,
@@ -463,12 +568,47 @@ const styles = {
463
568
  reportLoadingContainer: {
464
569
  paddingVertical: 10,
465
570
  },
466
- };
571
+ goToText: {
572
+ marginTop: 4,
573
+ },
574
+ muteSpinnerContainer: {
575
+ width: 100,
576
+ marginBottom: 6,
577
+ },
578
+ muteContainer: {
579
+ flexDirection: 'row',
580
+ alignItems: 'center',
581
+ justifyContent: 'flex-end',
582
+ marginBottom: 6,
583
+ },
584
+ mutedForText: {
585
+ fontFamily: 'sf-semibold',
586
+ fontSize: 13,
587
+ marginRight: 8,
588
+ },
589
+ muteButton: {
590
+ borderWidth: 1,
591
+ paddingHorizontal: 8,
592
+ },
593
+ muteButtonInner: {
594
+ flexDirection: 'row',
595
+ alignItems: 'center',
596
+ },
597
+ muteButtonIcon: {
598
+ fontSize: 14,
599
+ },
600
+ muteButtonText: {
601
+ fontFamily: 'sf-semibold',
602
+ fontSize: 14,
603
+ marginLeft: 6,
604
+ },
605
+ });
467
606
 
468
607
  const mapStateToProps = state => {
469
608
  return {
470
609
  colourBrandingMain: getMainBrandingColourFromState(state),
471
610
  user: state.user,
611
+ notificationsForComments: getSiteSettingFromState(state, 'NotificationsForComments', false),
472
612
  };
473
613
  };
474
614