@plusscommunities/pluss-newsletter-web-sharing 1.2.8

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 (52) hide show
  1. package/.babelrc +3 -0
  2. package/dist/index.cjs.js +7451 -0
  3. package/dist/index.esm.js +7399 -0
  4. package/dist/index.umd.js +7434 -0
  5. package/package.default.json +43 -0
  6. package/package.json +45 -0
  7. package/package.test.json +43 -0
  8. package/rollup.config.js +59 -0
  9. package/src/actions/NewsActions.js +102 -0
  10. package/src/actions/index.js +9 -0
  11. package/src/actions/types.js +10 -0
  12. package/src/components/ActivityText.js +73 -0
  13. package/src/components/MakerPopup/index.js +3 -0
  14. package/src/components/PreviewFull.js +32 -0
  15. package/src/components/PreviewGrid.js +19 -0
  16. package/src/components/PreviewWidget.js +28 -0
  17. package/src/components/Reactions/index.js +3 -0
  18. package/src/components/ViewFull.js +19 -0
  19. package/src/components/ViewWidget.js +17 -0
  20. package/src/components/index.js +25 -0
  21. package/src/components/text/index.js +4 -0
  22. package/src/config/index.js +9 -0
  23. package/src/feature.config.default.js +235 -0
  24. package/src/feature.config.js +235 -0
  25. package/src/feature.config.sharing.js +235 -0
  26. package/src/feature.config.test.js +235 -0
  27. package/src/helper/index.js +17 -0
  28. package/src/images/full.png +0 -0
  29. package/src/images/fullNoTitle.png +0 -0
  30. package/src/images/fullNoTitleCondensed.png +0 -0
  31. package/src/images/previewWidget.png +0 -0
  32. package/src/images/widget.png +0 -0
  33. package/src/index.js +33 -0
  34. package/src/js/Events.js +26 -0
  35. package/src/js/index.js +20 -0
  36. package/src/reducers/NewsReducer.js +82 -0
  37. package/src/screens/Newsletter/AddNewsletterEntry.js +1186 -0
  38. package/src/screens/Newsletter/AvailableNews.js +124 -0
  39. package/src/screens/Newsletter/GenerateNewsletter.js +891 -0
  40. package/src/screens/Newsletter/ListNewsletterEntries.js +234 -0
  41. package/src/screens/Newsletter/NewsHub.js +328 -0
  42. package/src/screens/Newsletter/NewsHubAnalytics.js +320 -0
  43. package/src/screens/Newsletter/NewsletterAnalytics.js +140 -0
  44. package/src/screens/Newsletter/NewsletterSubmission.js +476 -0
  45. package/src/screens/Newsletter/NewsletterSubmissions.js +278 -0
  46. package/src/screens/Newsletter/NewsletterTemplate.js +1051 -0
  47. package/src/screens/Newsletter/PublishAvailableNews.js +450 -0
  48. package/src/session/index.js +7 -0
  49. package/src/webapi/eventActions.js +18 -0
  50. package/src/webapi/helper.js +4 -0
  51. package/src/webapi/index.js +11 -0
  52. package/src/webapi/newsletterActions.js +153 -0
@@ -0,0 +1,320 @@
1
+ import React, { Component } from 'react';
2
+ import { Table } from 'react-bootstrap';
3
+ import FontAwesome from 'react-fontawesome';
4
+ import { connect } from 'react-redux';
5
+ import { withRouter } from 'react-router';
6
+ import moment from 'moment';
7
+ import _ from 'lodash';
8
+ import { usersLoaded } from '../../actions';
9
+ import { checkLoggedIn, validateAccess } from '../../session';
10
+ import { analyticsActions } from '../../webapi';
11
+ import { getPluralS } from '../../helper';
12
+ import { compileStats, getInsight, getAnalyticsFilterOptions } from '../../js';
13
+ import { UTC_OFFSET } from '../../config';
14
+ import { Reactions } from '../../components/Reactions';
15
+ import { AnalyticsFilter } from '../../components';
16
+ import { Text } from '../../components/text';
17
+ import { values } from '../../feature.config';
18
+
19
+ class NewsHubAnalytics extends Component {
20
+ constructor(props) {
21
+ super(props);
22
+ const filterOptions = getAnalyticsFilterOptions();
23
+ const selectedFilter = filterOptions[1];
24
+ this.state = {
25
+ loading: true,
26
+ stats: {},
27
+ totalStats: {},
28
+ previousStats: null,
29
+ selectedFilter: filterOptions[1],
30
+ currentDayCount: selectedFilter.dayCount,
31
+ };
32
+ }
33
+
34
+ UNSAFE_componentWillMount() {
35
+ checkLoggedIn(this, this.props.auth);
36
+ }
37
+
38
+ componentDidMount() {
39
+ if (!validateAccess(this.props.auth.site, values.permissionNewsletter, this.props.auth, true)) {
40
+ this.props.history.push('/mastermenu');
41
+ } else {
42
+ this.getStats();
43
+ }
44
+ }
45
+
46
+ getTotalStat(key) {
47
+ if (!this.state.totalStats[key]) {
48
+ return 0;
49
+ }
50
+ if (Array.isArray(this.state.totalStats[key])) {
51
+ return this.state.totalStats[key].length;
52
+ }
53
+ return this.state.totalStats[key];
54
+ }
55
+
56
+ getStat(id, key) {
57
+ if (!this.state.stats[id]) {
58
+ return 0;
59
+ }
60
+ if (!this.state.stats[id][key]) {
61
+ return 0;
62
+ }
63
+ if (Array.isArray(this.state.stats[id][key])) {
64
+ return this.state.stats[id][key].length;
65
+ }
66
+ return this.state.stats[id][key];
67
+ }
68
+
69
+ getStats(min, max) {
70
+ this.setState({
71
+ loading: true,
72
+ });
73
+ const lastDay = max ? moment(max).utc() : moment.utc();
74
+ const dayCount = min ? moment(max).diff(moment(min), 'days') : this.state.selectedFilter.dayCount;
75
+ console.log(`getStats: lastDay:${moment(lastDay).format('DD/MM/YYYY')}, dayCount:${dayCount}`);
76
+
77
+ const startOfLastDay = lastDay.utcOffset(UTC_OFFSET).startOf('d');
78
+ const minTime = moment(startOfLastDay).add(-dayCount, 'd').valueOf() - 1;
79
+ const maxTime = moment(startOfLastDay).valueOf() - 1;
80
+
81
+ analyticsActions.getAggregateEntityStats(this.props.auth.site, values.analyticsKey, minTime, maxTime).then((res) => {
82
+ console.log(res.data);
83
+ console.log(compileStats(res.data));
84
+ this.setState({
85
+ stats: res.data,
86
+ totalStats: compileStats(res.data),
87
+ loading: false,
88
+ currentDayCount: dayCount,
89
+ });
90
+ const prevMinTime =
91
+ moment(startOfLastDay)
92
+ .add(2 * -dayCount, 'd')
93
+ .valueOf() - 1;
94
+ analyticsActions.getAggregateEntityStats(this.props.auth.site, values.analyticsKey, prevMinTime, minTime).then((oldRes) => {
95
+ console.log(oldRes.data);
96
+ console.log(compileStats(oldRes.data));
97
+ this.setState({
98
+ previousStats: oldRes.data,
99
+ previousTotalStats: compileStats(oldRes.data),
100
+ });
101
+ });
102
+ });
103
+ }
104
+
105
+ getReactions(key) {
106
+ const result = {};
107
+ const statsToUse = this.state.stats[key];
108
+ if (!statsToUse) {
109
+ return result;
110
+ }
111
+ Object.keys(statsToUse).forEach((key) => {
112
+ if (key.indexOf('Reaction_') === 0) {
113
+ result[key.substring(9)] = statsToUse[key];
114
+ }
115
+ });
116
+ return result;
117
+ }
118
+
119
+ filterChanged = (selectedFilter) => {
120
+ this.setState({ selectedFilter }, () => {
121
+ this.getStats();
122
+ });
123
+ };
124
+
125
+ filterDateRangeChanged = (startDate, endDate) => {
126
+ this.getStats(startDate, endDate);
127
+ };
128
+
129
+ renderStats() {
130
+ if (this.state.loading) {
131
+ return null;
132
+ }
133
+ return (
134
+ <div className="newsanalytics--overview paddingTop-40">
135
+ <Text type="formTitleSmall" className="marginBottom-16">
136
+ Overview
137
+ </Text>
138
+ <div className="polloverview polloverview--newsAnalytics">
139
+ <div className="polloverview_section">
140
+ <FontAwesome className="polloverview_icon" name="eye" />
141
+ <div className="polloverview_right">
142
+ <p className="polloverview_count">{this.getTotalStat('PageView')}</p>
143
+ <p className="polloverview_text">Page View{getPluralS(this.getTotalStat('PageView'))}</p>
144
+ </div>
145
+ </div>
146
+ <div className="polloverview_section">
147
+ <FontAwesome className="polloverview_icon" name="user" />
148
+ <div className="polloverview_right">
149
+ <p className="polloverview_count">{this.getTotalStat('UniquePageView')}</p>
150
+ <p className="polloverview_text">Unique Visitor{getPluralS(this.getTotalStat('UniquePageView'))}</p>
151
+ </div>
152
+ </div>
153
+ <div className="polloverview_section">
154
+ <FontAwesome className="polloverview_icon" name="undo" />
155
+ <div className="polloverview_right">
156
+ <p className="polloverview_count">{this.getTotalStat('PageView') - this.getTotalStat('UniquePageView')}</p>
157
+ <p className="polloverview_text">
158
+ Returning Visitor{getPluralS(this.getTotalStat('PageView') - this.getTotalStat('UniquePageView'))}
159
+ </p>
160
+ </div>
161
+ </div>
162
+ <div className="polloverview_section">
163
+ <FontAwesome className="polloverview_icon" name="smile-o" />
164
+ <div className="polloverview_right">
165
+ <p className="polloverview_count">{this.getTotalStat('Reaction')}</p>
166
+ <p className="polloverview_text">Reaction{getPluralS(this.getTotalStat('Reaction'))}</p>
167
+ </div>
168
+ </div>
169
+ {/* <div className='polloverview_section'>
170
+ <FontAwesome className='polloverview_icon' name='exclamation-triangle' />
171
+ <div className='polloverview_right'>
172
+ <p className='polloverview_count'>3</p>
173
+ <p className='polloverview_text'>Pending Posts</p>
174
+ </div>
175
+ </div> */}
176
+ </div>
177
+ </div>
178
+ );
179
+ }
180
+
181
+ renderInsight(update, title, primaryStat) {
182
+ if (!update || !update.Entity || !update.Stats) {
183
+ return null;
184
+ }
185
+ const currentStat = update.Stats && update.Stats[primaryStat] ? update.Stats[primaryStat] : 0;
186
+ const prevStat = update.PreviousStats && update.PreviousStats[primaryStat] ? update.PreviousStats[primaryStat] : 0;
187
+ const change = currentStat - prevStat;
188
+ const prevText = this.state.selectedFilter.prevText.replace('*', this.state.currentDayCount);
189
+
190
+ return (
191
+ <div className="insights">
192
+ <div className="insightstitle">
193
+ <Text type="formTitleSmall">{title}</Text>
194
+ <div className="hrline"></div>
195
+ </div>
196
+ <div>
197
+ <p className="fontMedium fontSize-26 text-dusk lineClamp-2">{update.Entity.Title}</p>
198
+ {change === 0 ? (
199
+ <p className="fontHeavy fontSize-13 text-brandingAction">No change on {prevText}</p>
200
+ ) : (
201
+ <p className="fontHeavy fontSize-13 text-brandingAction">
202
+ <FontAwesome className="insightsstat" name={`caret-${change > 0 ? 'up' : 'down'}`} />
203
+ <b>
204
+ {change} on {prevText}
205
+ </b>
206
+ </p>
207
+ )}
208
+
209
+ <div className="insightsnumber">
210
+ <div className="insightsnumleft">
211
+ <p className="polloverview_text marginBottom-8 text-mid">Page View{getPluralS(update.Stats.PageView)}</p>
212
+ <p className="polloverview_text marginBottom-8 text-mid">Reactions {this.state.selectedFilter.text.toLowerCase()}:</p>
213
+ </div>
214
+ <div className="insightsnumright">
215
+ <p className="polloverview_num marginBottom-8">{update.Stats.PageView || 0}</p>
216
+ </div>
217
+ </div>
218
+ <div className="insight_reactions">
219
+ <Reactions reactions={this.getReactions(update.Entity.RowId)} compact />
220
+ </div>
221
+ </div>
222
+ </div>
223
+ );
224
+ }
225
+
226
+ renderInsights() {
227
+ if (this.state.loading) {
228
+ return null;
229
+ }
230
+ const mostViewed = getInsight(this.state.stats, this.state.previousStats, this.props.news, 'RowId', 'PageView');
231
+ const mostReacted = getInsight(this.state.stats, this.state.previousStats, this.props.news, 'RowId', 'Reaction');
232
+ if (!mostViewed && !mostReacted) {
233
+ return null;
234
+ }
235
+ return (
236
+ <div className="serviceanalytics--insights paddingTop-40">
237
+ <Text type="formTitleSmall" className="marginBottom-16">
238
+ Insights
239
+ </Text>
240
+
241
+ <div className="insightscontainer">
242
+ {this.renderInsight(mostViewed, 'Most Viewed', 'PageView')}
243
+ {this.renderInsight(mostReacted, 'Most Reactions', 'Reaction')}
244
+ </div>
245
+ </div>
246
+ );
247
+ }
248
+
249
+ renderNewsList() {
250
+ if (this.state.loading) {
251
+ return null;
252
+ }
253
+ return (
254
+ <div className="paddingTop-40">
255
+ <Table className="plussTable" striped bordered condensed hover style={{ minWidth: '100%' }}>
256
+ <thead>
257
+ <tr>
258
+ <th style={{ width: 100 }}>Date published</th>
259
+ <th>Title</th>
260
+ <th style={{ width: 100 }}>Views</th>
261
+ <th style={{ width: 300, textAlign: 'center' }}>Reactions</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody>
265
+ {_.sortBy(_.sortBy(this.props.news, 'Title'), (update) => {
266
+ return this.getStat(update.RowId, 'PageView');
267
+ })
268
+ .reverse()
269
+ .map((update) => {
270
+ if (!this.state.stats[update.RowId]) {
271
+ return null;
272
+ }
273
+ return (
274
+ <tr key={update.RowId}>
275
+ <td>{moment.utc(update.Timestamp).local().format('DD-MM-YYYY')}</td>
276
+ <td>{update.Title}</td>
277
+ <td>{this.getStat(update.RowId, 'PageView')}</td>
278
+ <td>
279
+ <Reactions reactions={this.getReactions(update.RowId)} compact />
280
+ </td>
281
+ </tr>
282
+ );
283
+ })}
284
+ </tbody>
285
+ </Table>
286
+ </div>
287
+ );
288
+ }
289
+
290
+ renderFilter() {
291
+ return (
292
+ <AnalyticsFilter
293
+ defaultFilter={this.state.selectedFilter}
294
+ filterChanged={this.filterChanged}
295
+ filterDateRangeChanged={this.filterDateRangeChanged}
296
+ />
297
+ );
298
+ }
299
+
300
+ render() {
301
+ return (
302
+ <div style={{ minWidth: '100%' }}>
303
+ <p className="fontMedium fontSize-36 text-dark">Analytics</p>
304
+ {this.renderFilter()}
305
+ {this.renderStats()}
306
+ {this.renderInsights()}
307
+ {this.renderNewsList()}
308
+ </div>
309
+ );
310
+ }
311
+ }
312
+
313
+ const mapStateToProps = (state) => {
314
+ const { auth } = state;
315
+ return {
316
+ auth,
317
+ };
318
+ };
319
+
320
+ export default connect(mapStateToProps, { usersLoaded })(withRouter(NewsHubAnalytics));
@@ -0,0 +1,140 @@
1
+ import React, { Component } from 'react';
2
+ import { connect } from 'react-redux';
3
+ import { Button, OverlayPage, OverlayPageContents, OverlayPageSection, OverlayPageBottomButtons } from '../../components';
4
+ import { analyticsActions, newsletterActions } from '../../webapi';
5
+ import { checkLoggedIn, validateAccess } from '../../session';
6
+ import { safeReadParams, getPluralS } from '../../helper';
7
+ import FontAwesome from 'react-fontawesome';
8
+ import { Reactions } from '../../components/Reactions';
9
+ import { values } from '../../feature.config';
10
+
11
+ class NewsletterAnalytics extends Component {
12
+ initialState = {
13
+ infoId: safeReadParams(this.props, 'infoId'),
14
+ update: {},
15
+ PageView: 0,
16
+ ReturningPageView: 0,
17
+ UniquePageView: 0,
18
+ };
19
+
20
+ state = { ...this.initialState };
21
+
22
+ UNSAFE_componentWillMount() {
23
+ checkLoggedIn(this, this.props.auth);
24
+ }
25
+
26
+ componentDidMount() {
27
+ setTimeout(() => {
28
+ if (!validateAccess(this.props.auth.site, values.permissionNewsletter, this.props.auth, true)) {
29
+ window.history.back();
30
+ } else {
31
+ this.getUpdate();
32
+ this.getStats();
33
+ }
34
+ }, 500);
35
+ }
36
+
37
+ getUpdate() {
38
+ newsletterActions
39
+ .getNewsletterEntry(this.state.infoId)
40
+ .then((res) => {
41
+ this.setState({
42
+ update: res.data,
43
+ });
44
+ })
45
+ .catch((error) => {});
46
+ }
47
+
48
+ getStats() {
49
+ analyticsActions.getEntityStats(this.state.infoId, values.analyticsKey).then((res) => {
50
+ console.log('getStats', res.data);
51
+ this.setState({ ...res.data, statsLoaded: true });
52
+ });
53
+ }
54
+
55
+ renderOverview() {
56
+ if (!this.state.statsLoaded) {
57
+ return null;
58
+ }
59
+ return (
60
+ <div className="padding-60 paddingVertical-40 bottomDivideBorder">
61
+ <p className="newTopBarTitle text-sectionTitle">PAGE VIEWS</p>
62
+ <div className="polloverview">
63
+ <div className="polloverview_section">
64
+ <FontAwesome className="polloverview_icon" name="eye" />
65
+ <div className="polloverview_right">
66
+ <p className="polloverview_count">{this.state.PageView}</p>
67
+ <p className="polloverview_text">Page View{getPluralS(this.state.PageView)}</p>
68
+ </div>
69
+ </div>
70
+ <div className="polloverview_section">
71
+ <FontAwesome className="polloverview_icon" name="user" />
72
+ <div className="polloverview_right">
73
+ <p className="polloverview_count">{this.state.UniquePageView}</p>
74
+ <p className="polloverview_text">Unique Visitor{getPluralS(this.state.UniquePageView)}</p>
75
+ </div>
76
+ </div>
77
+ <div className="polloverview_section">
78
+ <FontAwesome className="polloverview_icon" name="repeat" />
79
+ <div className="polloverview_right">
80
+ <p className="polloverview_count">{this.state.ReturningPageView}</p>
81
+ <p className="polloverview_text">Returning Visitor{getPluralS(this.state.ReturningPageView)}</p>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ <div className="marginTop-32">
86
+ <Reactions entity={this.state.update} />
87
+ </div>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ renderBack() {
93
+ return (
94
+ <div>
95
+ <Button
96
+ inline
97
+ buttonType="primary"
98
+ onClick={() => {
99
+ window.history.back();
100
+ }}
101
+ isActive
102
+ >
103
+ {values.textBackToNews}
104
+ </Button>
105
+ </div>
106
+ );
107
+ }
108
+
109
+ renderMain() {
110
+ return (
111
+ <div>
112
+ <div className="padding-60 paddingBottom-40 bottomDivideBorder">
113
+ <div className="newTopBar clearfix">
114
+ <p className="newTopBarTitle text-sectionTitle">{values.textNewsAnalytics}</p>
115
+ </div>
116
+ <p className="pollTitle">{this.state.update.Title}</p>
117
+ </div>
118
+ {this.renderOverview()}
119
+ </div>
120
+ );
121
+ }
122
+
123
+ render() {
124
+ return (
125
+ <OverlayPage>
126
+ <OverlayPageContents id="NewsContainer" noBottomButtons={this.state.success}>
127
+ <OverlayPageSection className="pageSectionWrapper--fixedPopupSize">{this.renderMain()}</OverlayPageSection>
128
+ </OverlayPageContents>
129
+ <OverlayPageBottomButtons>{this.renderBack()}</OverlayPageBottomButtons>
130
+ </OverlayPage>
131
+ );
132
+ }
133
+ }
134
+
135
+ const mapStateToProps = (state) => {
136
+ const { auth } = state;
137
+ return { auth };
138
+ };
139
+
140
+ export default connect(mapStateToProps, {})(NewsletterAnalytics);