@capillarytech/creatives-library 7.0.8 → 7.1.3

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.
@@ -0,0 +1,501 @@
1
+ import PropTypes from 'prop-types';
2
+ import React from 'react';
3
+ import { bindActionCreators } from 'redux';
4
+ import { FormattedMessage, intlShape } from 'react-intl';
5
+ import { createStructuredSelector } from 'reselect';
6
+ import {
7
+ CapRadioCard,
8
+ CapSpin,
9
+ CapHeading,
10
+ CapHeader,
11
+ CapButton,
12
+ CapIcon,
13
+ CapLink,
14
+ CapRow,
15
+ CapColumn,
16
+ CapInput,
17
+ CapLabel,
18
+ CapTag,
19
+ CapTreeSelect,
20
+ CapDivider,
21
+ } from '@capillarytech/cap-ui-library';
22
+ import { find, cloneDeep, findIndex, isEmpty, isEqual, filter } from 'lodash';
23
+ import * as actions from './actions';
24
+ import { makeSelectFTP, makeSelectMetaEntities } from './selectors';
25
+ import withCreatives from '../../hoc/withCreatives';
26
+ import messages from './messages';
27
+ import './_FTP.scss';
28
+ import * as globalActions from '../../containers/Cap/actions';
29
+ import { TagList } from '../TagList';
30
+ import { NO_COMMUNICATION } from '../App/constants';
31
+ import { getTreeStructuredTags } from '../../utils/common';
32
+
33
+ const UIView = {
34
+ serverSelection: 'serverSelection',
35
+ configureMessage: 'configureMessage',
36
+ };
37
+ const { CapLabelInline } = CapLabel;
38
+ export class FTP extends React.Component {
39
+ constructor(props) {
40
+ super(props);
41
+ this.state = this.getInitialState();
42
+ }
43
+ getInitialState = () => {
44
+ const mode = this.props.mode || 'create';
45
+ let stateObj = {};
46
+ if (mode === 'create') {
47
+ const { formatMessage } = this.props.intl;
48
+ const headerTagList = [{
49
+ header: formatMessage(messages.customerID),
50
+ tag: '{{user_id}}',
51
+ }];
52
+ stateObj = {
53
+ mode,
54
+ selectedServer: null,
55
+ viewMode: UIView.serverSelection,
56
+ headerTagList,
57
+ messageContent: '',
58
+ tagsTree: [],
59
+ };
60
+ } else {
61
+ const { messageContent = {} } = this.props.messageDetails || {};
62
+ const messageContent1 = (messageContent['message_content_id_1'] || {}).messageBody;
63
+ const headerTagList = (messageContent['message_content_id_1'] || {}).headerTagList || [];
64
+ stateObj = {
65
+ mode,
66
+ selectedServer: null,
67
+ viewMode: UIView.configureMessage,
68
+ headerTagList,
69
+ messageContent: messageContent1,
70
+ tagsTree: [],
71
+ };
72
+ }
73
+ return stateObj;
74
+ };
75
+
76
+ setFTPServerInState = (FTPServers) => {
77
+ const { deliverySetting = {} } = this.props.messageDetails;
78
+ const configId = deliverySetting.channelSetting[NO_COMMUNICATION].endPointConfigs.ftpConfigId;
79
+ const selectedServer = find(FTPServers, {id: configId});
80
+ this.setState({ selectedServer });
81
+ };
82
+
83
+ componentDidMount() {
84
+ this.props.actions.getFTPServers();
85
+ const query = {
86
+ layout: 'SMS',
87
+ type: 'TAG',
88
+ context: 'outbound',
89
+ embedded: 'embedded',
90
+ };
91
+ this.props.globalActions.fetchSchemaForEntity(query);
92
+ }
93
+
94
+ componentWillReceiveProps(nextProps) {
95
+ if (
96
+ !isEmpty(((nextProps.metaEntities || {}).tags || {}).standard) &&
97
+ !isEqual(((nextProps.metaEntities || {}).tags || {}).standard, ((this.props.metaEntities || {}).tags || {}).standard)
98
+ ) {
99
+ const tags = getTreeStructuredTags({tagsList: nextProps.metaEntities.tags.standard, offerDetails: nextProps.selectedOfferDetails});
100
+ this.setState({ tagsTree: tags}, () => this.disableUsedColumns());
101
+ }
102
+
103
+ if (
104
+ nextProps.mode === 'edit' &&
105
+ (nextProps.FTP || {}).FTPServers.length &&
106
+ !isEqual((nextProps.FTP || {}).FTPServers, (this.props.FTP || {}).FTPServers)
107
+ ) {
108
+ this.setFTPServerInState(nextProps.FTP.FTPServers);
109
+ }
110
+ }
111
+
112
+ onChangeFTPServer = (e) => {
113
+ const { value } = e.target;
114
+ const { FTPServers = [] } = this.props.FTP || {};
115
+ const selectedServer = find(FTPServers, {id: value});
116
+ this.setState({ selectedServer, viewMode: UIView.configureMessage});
117
+ };
118
+
119
+ onTagSelect = (data) => {
120
+ this.insertAtCursor(`{{${data}}}`);
121
+ };
122
+
123
+ setInputRef = (textarea) => {
124
+ this.textArea = textarea;
125
+ };
126
+
127
+ getServerSelectionContent = () => {
128
+ const { getFTPServersInProgress } = this.props.FTP || {};
129
+ const { formatMessage } = this.props.intl;
130
+ const serverOptions = this.getServerOptions();
131
+ const { selectedServer } = this.state;
132
+ return (
133
+ <CapSpin spinning={getFTPServersInProgress}>
134
+ <CapHeading
135
+ type="h3"
136
+ className="select-account-header"
137
+ >
138
+ {formatMessage(messages.ftpServer)}
139
+ </CapHeading>
140
+ <CapHeading type="h6" className="manage-ftp-setting-div">
141
+ {formatMessage(messages.ftpServerDesc)}
142
+ <CapLink
143
+ title={formatMessage(messages.manageFTPSettings)}
144
+ className="select-server"
145
+ onClick={this.handleManageFTPSettings}
146
+ />
147
+ </CapHeading>
148
+ <CapRadioCard
149
+ className="select-account"
150
+ onChange={this.onChangeFTPServer}
151
+ panes={serverOptions}
152
+ cardHeight="48px"
153
+ cardWidth="276px"
154
+ size="small"
155
+ defaultValue={selectedServer}
156
+ selected={selectedServer}
157
+ />
158
+ </CapSpin>
159
+ );
160
+ };
161
+
162
+ getServerOptions = () => {
163
+ const { FTPServers = [] } = this.props.FTP || {};
164
+ return (FTPServers).map(({
165
+ // eslint-disable-next-line camelcase
166
+ ftp_tag = '',
167
+ id,
168
+ }) => ({
169
+ key: ftp_tag,
170
+ value: id,
171
+ title: ftp_tag,
172
+ icon: <CapIcon.CapIconAvatar text={ftp_tag[0]} width="auto" height="2rem"/>,
173
+ }));
174
+ };
175
+
176
+ getMessageContent = () => {
177
+ const { messageContent } = this.state;
178
+ const { formatMessage } = this.props.intl;
179
+ const { metaEntities, selectedOfferDetails } = this.props;
180
+ const tagsRaw = metaEntities && metaEntities.tags ? metaEntities.tags.standard : [];
181
+ return (
182
+ <CapRow>
183
+ <CapColumn span={11}>
184
+ <div className="message-content-title">
185
+ <TagList
186
+ className="FTP-tagList"
187
+ label={formatMessage(messages.addLabel)}
188
+ tags={tagsRaw}
189
+ onTagSelect={this.onTagSelect}
190
+ selectedOfferDetails={selectedOfferDetails}
191
+ />
192
+ <CapInput.TextArea
193
+ setInputRef={this.setInputRef}
194
+ label={formatMessage(messages.messageHeader)}
195
+ onChange={this.updateMessageBody}
196
+ value={messageContent}
197
+ />
198
+ </div>
199
+ </CapColumn>
200
+ </CapRow>
201
+ );
202
+ };
203
+
204
+ getCsvColumns = () => {
205
+ const { headerTagList = [], tagsTree = [] } = this.state;
206
+ const { formatMessage } = this.props.intl;
207
+ const tagsWithoutOptout = tagsTree.length && tagsTree.filter((tag) => tag.value !== '{{optout}}');
208
+ const headerTagElements = headerTagList.map((item, index) => (
209
+ index !== 0 && <div className="column-selection-container">
210
+ <CapSpin spinning={!tagsWithoutOptout.length}>
211
+ <CapTreeSelect
212
+ dropdownClassName="select-csv-col-dropdown"
213
+ treeData={tagsWithoutOptout}
214
+ className="select-csv-column"
215
+ value={!isEmpty(item.header) ? item.header : formatMessage(messages.selectTag)}
216
+ onChange={(option, label) => this.selectCsvColumn(option, label, index)}
217
+ showSearch
218
+ />
219
+ <CapIcon
220
+ type="close"
221
+ size="m"
222
+ onClick={() => this.removeCsvColumn(item)}
223
+ className="remove-csv-column"
224
+ />
225
+ </CapSpin>
226
+ </div>
227
+ ));
228
+ headerTagElements.unshift(
229
+ <CapLabelInline
230
+ className="column-selection-container-cid"
231
+ type="label2"
232
+ style={{fontSize: 14, fontWeight: 500}}
233
+ >
234
+ <FormattedMessage {...messages.customerID}/>
235
+ </CapLabelInline>
236
+ );
237
+ return headerTagElements;
238
+ };
239
+
240
+ getConfigureCsvContent = () => {
241
+ const { formatMessage } = this.props.intl;
242
+ return (
243
+ <>
244
+ <CapRow className="configure-csv-container">
245
+ <CapColumn span={8} className="configure-csv">
246
+ <CapHeader
247
+ title={formatMessage(messages.configureCsv)}
248
+ size="regular"
249
+ className="select-account-header"
250
+ />
251
+ </CapColumn>
252
+ <CapColumn span={3} offset={13} className="add-column">
253
+ <CapButton
254
+ type="flat"
255
+ isAddBtn
256
+ onClick={this.addNewColumn}
257
+ >
258
+ <FormattedMessage {...messages.addColumn} />
259
+ </CapButton>
260
+ </CapColumn>
261
+ </CapRow>
262
+ {this.getCsvColumns()}
263
+ </>
264
+ );
265
+ }
266
+
267
+ getConfigureMessageContent = () => {
268
+ const { formatMessage } = this.props.intl;
269
+ const { selectedServer = {}, headerTagList = [] } = this.state;
270
+ const emptyHeaderTagList = filter(headerTagList, {tag: ''});
271
+ const disableDoneButton = isEmpty(selectedServer) || emptyHeaderTagList.length;
272
+ return (
273
+ <CapSpin spinning={false}>
274
+ <div className="scrollable-section">
275
+ <CapHeader
276
+ title={formatMessage(messages.ftpServer)}
277
+ size="regular"
278
+ className="select-account-header"
279
+ />
280
+ <div className="selected-server-name">
281
+ <CapIcon.CapIconAvatar
282
+ className="server-avtar"
283
+ text={((selectedServer || {}).ftp_tag || [])[0]}
284
+ width="auto"
285
+ height="2rem"
286
+ />
287
+ <CapHeading
288
+ type="h4"
289
+ className="server-name"
290
+ >
291
+ {(selectedServer || {}).ftp_tag}
292
+ </CapHeading>
293
+ <CapLink
294
+ title={formatMessage(messages.change)}
295
+ className="server-change"
296
+ onClick={this.showAccountSelection}
297
+ />
298
+ </div>
299
+ <CapDivider />
300
+ {this.getConfigureCsvContent()}
301
+ <CapDivider />
302
+ <div className="ftp-message-container">
303
+ <CapHeader
304
+ title={formatMessage(messages.messageContent)}
305
+ description={formatMessage(messages.optional)}
306
+ size="regular"
307
+ inline
308
+ />
309
+ {this.getMessageContent()}
310
+ </div>
311
+ </div>
312
+ <CapButton
313
+ type="primary"
314
+ disabled={disableDoneButton}
315
+ onClick={this.saveFtpMessage}
316
+ >
317
+ <FormattedMessage {...messages.done} />
318
+ </CapButton>
319
+ </CapSpin>
320
+ );
321
+ };
322
+
323
+ getColumnsHeaderPreview = (headerTagList) => (
324
+ <div className="ftp-preview-data-col-container">
325
+ { headerTagList.map((tag) => (
326
+ <CapTag type="static" className="ftp-preview-data-col-tag">
327
+ {tag.header}
328
+ </CapTag>
329
+ ))}
330
+ </div>
331
+ );
332
+
333
+ getPreviewModeContents = () => {
334
+ const { contents = [] } = this.props;
335
+ const { messageBody = '', headerTagList = [] } = contents[0] || {};
336
+ return (
337
+ <div className="ftp-preview-container">
338
+ <CapHeading className="ftp-preview-message-title" type="h3">
339
+ <FormattedMessage {...messages.messageContent}/>
340
+ </CapHeading>
341
+ <CapLabel type="label2" className="ftp-preview-message-content">
342
+ {messageBody}
343
+ </CapLabel>
344
+ <CapHeading className="ftp-preview-data-title" type="h3">
345
+ <FormattedMessage {...messages.dataPreview}/>
346
+ </CapHeading>
347
+ {this.getColumnsHeaderPreview(headerTagList)}
348
+ </div>
349
+ );
350
+ };
351
+
352
+ showAccountSelection = () => {
353
+ this.setState({ viewMode: UIView.serverSelection, selectedServer: null, headerTagList: [], messageContent: ''});
354
+ };
355
+
356
+ selectCsvColumn = (option, label, index) => {
357
+ const { headerTagList } = this.state;
358
+ const clonedHeaderTagList = cloneDeep(headerTagList);
359
+ const obj = {
360
+ header: label[0],
361
+ tag: option,
362
+ };
363
+ clonedHeaderTagList[index] = obj;
364
+ this.setState({ headerTagList: clonedHeaderTagList}, () => this.disableUsedColumns());
365
+ };
366
+
367
+ disableUsedColumns = () => {
368
+ const { headerTagList = [] } = this.state;
369
+ const { selectedOfferDetails = [], metaEntities = {} } = this.props;
370
+ const tags = getTreeStructuredTags({tagsList: (metaEntities.tags || {}).standard, offerDetails: selectedOfferDetails});
371
+ const clonnedTagsTree = cloneDeep(tags);
372
+ headerTagList.map((headerTag) => {
373
+ const pos = findIndex(clonnedTagsTree, {value: headerTag.tag});
374
+ if (pos > -1) {
375
+ clonnedTagsTree[pos] = {
376
+ disabled: true,
377
+ ...clonnedTagsTree[pos],
378
+ };
379
+ }
380
+ return null;
381
+ });
382
+ this.setState({tagsTree: clonnedTagsTree});
383
+ };
384
+
385
+ handleManageFTPSettings = () => {
386
+ const path = `/exports/configurations`;
387
+ window.open(path, '_blank');
388
+ };
389
+
390
+ removeCsvColumn = (item) => {
391
+ const { headerTagList } = this.state;
392
+ const clonedHeaderTagList = cloneDeep(headerTagList);
393
+ const index = findIndex(headerTagList, (val) => val.tag === item.tag);
394
+ clonedHeaderTagList.splice(index, 1);
395
+ this.setState({ headerTagList: clonedHeaderTagList}, () => this.disableUsedColumns());
396
+ };
397
+
398
+ addNewColumn = () => {
399
+ const { headerTagList } = this.state;
400
+ const clonedHeaderTagList = cloneDeep(headerTagList);
401
+ const newCsvObj = {
402
+ header: '',
403
+ tag: '',
404
+ };
405
+ clonedHeaderTagList.push(newCsvObj);
406
+ this.setState({ headerTagList: clonedHeaderTagList});
407
+ };
408
+
409
+ insertAtCursor = (myValue) => {
410
+ this.textArea.focus();
411
+ const textAreaRef = this.textArea.textAreaRef;
412
+ let pos = textAreaRef.selectionEnd + myValue.length;
413
+ //IE support
414
+ let newMessage = textAreaRef.value + myValue;
415
+ if (document.selection) {
416
+ this.textArea.focus();
417
+ const sel = document.selection.createRange();
418
+ sel.text = myValue;
419
+ } else if (textAreaRef.selectionStart || textAreaRef.selectionStart === '0') { //MOZILLA and others
420
+ const startPos = textAreaRef.selectionStart;
421
+ const endPos = textAreaRef.selectionEnd;
422
+ newMessage = textAreaRef.value.substring(0, startPos) + myValue + textAreaRef.value.substring(endPos, textAreaRef.value.length);
423
+ pos = startPos + myValue.length;
424
+ }
425
+ this.setState({
426
+ messageContent: newMessage,
427
+ }, () => {
428
+ textAreaRef.selectionStart = pos;
429
+ textAreaRef.selectionEnd = pos;
430
+ });
431
+ };
432
+
433
+ updateMessageBody = (e) => {
434
+ this.setState({
435
+ messageContent: e.target.value,
436
+ });
437
+ };
438
+
439
+ saveFtpMessage = () => {
440
+ const { onFTPSubmit } = this.props;
441
+ const { FTPServers = [] } = this.props.FTP || {};
442
+ const { selectedServer = {}, headerTagList = [], messageContent = '' } = this.state;
443
+ onFTPSubmit({
444
+ channel: "FTP",
445
+ endPointConfigs: {
446
+ endPointType: "FTP",
447
+ ftpConfigId: selectedServer.id,
448
+ },
449
+ headerTagList,
450
+ messageContent,
451
+ FTPServers,
452
+ });
453
+ };
454
+
455
+ render() {
456
+ const { viewMode } = this.state;
457
+ const { serverSelection } = UIView;
458
+ const { mode } = this.props;
459
+ return (
460
+ mode === 'preview' ?
461
+ this.getPreviewModeContents() :
462
+ (viewMode === serverSelection ?
463
+ this.getServerSelectionContent() :
464
+ this.getConfigureMessageContent()
465
+ )
466
+ );
467
+ }
468
+ }
469
+
470
+ FTP.propTypes = {
471
+ actions: PropTypes.object.isRequired,
472
+ FTP: PropTypes.object,
473
+ intl: intlShape.isRequired,
474
+ globalActions: PropTypes.object,
475
+ onFTPSubmit: PropTypes.func,
476
+ messageDetails: PropTypes.object,
477
+ mode: PropTypes.string,
478
+ contents: PropTypes.array,
479
+ metaEntities: PropTypes.object,
480
+ selectedOfferDetails: PropTypes.array,
481
+ };
482
+
483
+
484
+ function mapDispatchToProps(dispatch) {
485
+ return {
486
+ actions: bindActionCreators(actions, dispatch),
487
+ globalActions: bindActionCreators(globalActions, dispatch),
488
+ };
489
+ }
490
+
491
+ const mapStateToProps = createStructuredSelector({
492
+ FTP: makeSelectFTP(),
493
+ metaEntities: makeSelectMetaEntities(),
494
+ });
495
+
496
+ export default withCreatives({
497
+ WrappedComponent: FTP,
498
+ mapStateToProps,
499
+ mapDispatchToProps,
500
+ suserAuth: true,
501
+ });
@@ -0,0 +1,60 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ ftpServerDesc: {
5
+ id: 'creatives.containersV2.FTP.ftpServerDesc',
6
+ defaultMessage: 'Select the FTP server to send the message data. To add another FTP location, ',
7
+ },
8
+ ftpServer: {
9
+ id: 'creatives.containersV2.FTP.ftpServer',
10
+ defaultMessage: 'FTP Server',
11
+ },
12
+ configureCsv: {
13
+ id: 'creatives.containersV2.FTP.configureCsv',
14
+ defaultMessage: 'Configure CSV',
15
+ },
16
+ addColumn: {
17
+ id: 'creatives.containersV2.FTP.addColumn',
18
+ defaultMessage: 'Add column',
19
+ },
20
+ selectTag: {
21
+ id: 'creatives.containersV2.FTP.selectTag',
22
+ defaultMessage: 'Select tag',
23
+ },
24
+ done: {
25
+ id: 'creatives.containersV2.FTP.done',
26
+ defaultMessage: 'Done',
27
+ },
28
+ customerID: {
29
+ id: 'creatives.containersV2.FTP.customerID',
30
+ defaultMessage: 'Customer ID',
31
+ },
32
+ change: {
33
+ id: 'creatives.containersV2.FTP.change',
34
+ defaultMessage: 'Change',
35
+ },
36
+ manageFTPSettings: {
37
+ id: 'creatives.containersV2.FTP.manageFTPSettings',
38
+ defaultMessage: 'manage FTP settings',
39
+ },
40
+ addLabel: {
41
+ id: 'creatives.containersV2.FTP.addLabel',
42
+ defaultMessage: 'Add Labels',
43
+ },
44
+ messageHeader: {
45
+ id: 'creatives.containersV2.FTP.messageHeader',
46
+ defaultMessage: 'Message',
47
+ },
48
+ messageContent: {
49
+ id: 'creatives.containersV2.FTP.messageContent',
50
+ defaultMessage: 'Message Content',
51
+ },
52
+ optional: {
53
+ id: 'creatives.containersV2.FTP.optional',
54
+ defaultMessage: '(optional)',
55
+ },
56
+ dataPreview: {
57
+ id: 'creatives.containersV2.FTP.dataPreview',
58
+ defaultMessage: 'Data Preview',
59
+ },
60
+ });
@@ -0,0 +1,32 @@
1
+ import { fromJS } from 'immutable';
2
+ import * as types from './constants';
3
+
4
+ const initialState = fromJS({
5
+ getFTPServersInProgress: false,
6
+ FTPServers: [],
7
+ getFTPServersError: false,
8
+ });
9
+
10
+ function createReducer(state = initialState, action) {
11
+ switch (action.type) {
12
+ case types.GET_FTP_SERVERS_REQUEST:
13
+ return state
14
+ .set('getFTPServersInProgress', true)
15
+ .set('FTPServers', [])
16
+ .set('getFTPServersInProgress', true);
17
+ case types.GET_FTP_SERVERS_SUCCESS:
18
+ return state
19
+ .set('getFTPServersInProgress', false)
20
+ .set('FTPServers', action.data)
21
+ .set('getFTPServersInProgress', false);
22
+ case types.GET_FTP_SERVERS_FAILURE:
23
+ return state
24
+ .set('getFTPServersInProgress', false)
25
+ .set('FTPServers', [])
26
+ .set('getFTPServersInProgress', true);
27
+ default:
28
+ return state;
29
+ }
30
+ }
31
+
32
+ export default createReducer;
@@ -0,0 +1,26 @@
1
+ import { take, cancel, call, put, takeLatest } from 'redux-saga/effects';
2
+ import { LOCATION_CHANGE } from 'react-router-redux';
3
+ import * as Api from '../../services/api';
4
+ import * as types from './constants';
5
+
6
+ // Individual exports for testing
7
+ export function* getFTPServers() {
8
+ try {
9
+ const result = yield call(Api.getFTPServers);
10
+ yield put({ type: types.GET_FTP_SERVERS_SUCCESS, data: result.response });
11
+ } catch (error) {
12
+ yield put({ type: types.GET_FTP_SERVERS_FAILURE, error });
13
+ }
14
+ }
15
+
16
+
17
+ function* watchGetFTPServers() {
18
+ const watcher = yield takeLatest(types.GET_FTP_SERVERS_REQUEST, getFTPServers);
19
+ yield take(LOCATION_CHANGE);
20
+ yield cancel(watcher);
21
+ }
22
+
23
+ // All sagas to be loaded
24
+ export default [
25
+ watchGetFTPServers,
26
+ ];
@@ -0,0 +1,20 @@
1
+ import { createSelector } from 'reselect';
2
+ import { fromJS } from 'immutable';
3
+
4
+ const selectFTPMessageCreateDomain = () => (state) => state.get('FTP') || fromJS({});
5
+ const selectCapDomain = (state) => state.get('cap');
6
+
7
+ const makeSelectFTP = () => createSelector(
8
+ selectFTPMessageCreateDomain(),
9
+ (substate = fromJS({})) => substate.toJS()
10
+ );
11
+ const makeSelectMetaEntities = () => createSelector(
12
+ selectCapDomain,
13
+ (globalState = fromJS({})) => globalState.get('metaEntities')
14
+ );
15
+
16
+
17
+ export {
18
+ makeSelectFTP,
19
+ makeSelectMetaEntities,
20
+ };