@capillarytech/creatives-library 3.8.10 → 3.9.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/index.js CHANGED
@@ -43,6 +43,10 @@ import Facebook from './v2Containers/Facebook';
43
43
  import facebookReducer from './v2Containers/Facebook/reducer';
44
44
  import facebookSaga from './v2Containers/Facebook/sagas';
45
45
 
46
+ import Gallery from './v2Containers/Assets/Gallery';
47
+ import galleryReducer from './v2Containers/Assets/Gallery/reducer';
48
+ import gallerySagas from './v2Containers/Assets/Gallery/sagas';
49
+
46
50
  import MobilepushCreate from './v2Containers/MobilePush/Create';
47
51
  import mobilepushCreateReducer from './v2Containers/MobilePush/Create/reducer';
48
52
  import mobilepushCreateSaga from './v2Containers/MobilePush/Create/sagas';
@@ -86,6 +90,7 @@ const LineCreateContainer = {LineCreate, lineCreateReducer, lineCreateSaga};
86
90
  const LineEditContainer = {LineEdit, lineEditReducer, lineEditSaga};
87
91
  const TemplatesV2Container = {TemplatesV2, templateReducer, templateSaga};
88
92
  const FacebookContainer = {Facebook, facebookReducer, facebookSaga};
93
+ const GalleryContainer = {Gallery, galleryReducer, gallerySagas};
89
94
 
90
95
  export {default as TagList} from './v2Containers/TagList/index';
91
96
  export {default as TagListSaga} from './v2Containers/TagList/sagas';
@@ -123,4 +128,5 @@ export { CapContainer,
123
128
  CreativesContainer,
124
129
  updateCharCount,
125
130
  FacebookContainer,
131
+ GalleryContainer,
126
132
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "3.8.10",
4
+ "version": "3.9.0",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -2594,6 +2594,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2594
2594
  image: undefined,
2595
2595
  imagePreview: undefined,
2596
2596
  },
2597
+ reUpload: true,
2597
2598
  });
2598
2599
  }
2599
2600
 
@@ -2833,7 +2834,14 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2833
2834
  );
2834
2835
  break;
2835
2836
  case 'upload':
2836
- const isImage = self.state && !!self.state.formData[`${self.state.currentTab - 1}`].image;
2837
+ const {
2838
+ formData: {
2839
+ [`${self.state.currentTab - 1}`]: {
2840
+ image,
2841
+ } = {},
2842
+ } = {},
2843
+ } = self.state;
2844
+ const isImage = !!image;
2837
2845
  ifError = (this.state.checkValidation || this.props.startValidation) && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
2838
2846
  const ImageComponent = (props) => (
2839
2847
  <div key={`${val.id}-${this.state.currentTab}`}>
@@ -2898,16 +2906,11 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2898
2906
  );
2899
2907
  break;
2900
2908
  case "tag-list":
2901
- let moduleFilterEnabled = this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded';
2902
- const channel = _.get(this.props, 'schema.channel', "");
2903
- if (channel === 'EMAIL') {
2904
- moduleFilterEnabled = this.props.isFullMode;
2905
- }
2906
2909
  columns.push(
2907
2910
  <CapColumn key={`input-${val.id}`} offset={val.offset} span={val.width ? val.width : ''} style={val.style ? val.style : {marginBottom: '16px'}}>
2908
2911
  <TagList
2909
2912
  key={`input-${val.id}`}
2910
- moduleFilterEnabled={moduleFilterEnabled}
2913
+ moduleFilterEnabled={this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded'}
2911
2914
  label={val.label ? val.label : ''}
2912
2915
  onTagSelect={(data) => this.callChildEvent(data, val, 'onTagSelect')}
2913
2916
  onContextChange={this.props.onContextChange}
@@ -3478,7 +3481,6 @@ FormBuilder.propTypes = {
3478
3481
  selectedOfferDetails: PropTypes.array,
3479
3482
  setDrawerVisibility: PropTypes.bool,
3480
3483
  capDrawerContent: PropTypes.array,
3481
- isFullMode: PropTypes.bool,
3482
3484
  };
3483
3485
 
3484
3486
  export default injectIntl(FormBuilder);
@@ -245,6 +245,7 @@ export class Gallery extends React.Component { // eslint-disable-line react/pref
245
245
  const fileParams = {
246
246
  width: this.width,
247
247
  height: this.height,
248
+ isGeneratePreview: true,
248
249
  };
249
250
  that.props.actions.uploadAsset(files[file], 'image', fileParams);
250
251
  };
@@ -73,6 +73,8 @@ function SlideBoxContent(props) {
73
73
  weChatMaptemplateStep,
74
74
  onWeChatMaptemplateStepChange,
75
75
  onFacebookSubmit,
76
+ saveMessage,
77
+ selectedAccount,
76
78
  } = props;
77
79
  const type = messageDetails.type.toLowerCase(); // type is context in get tags values : outbound | dvs | referral | loyalty | coupons
78
80
  const query = { type: !isFullMode && 'embedded', module: isFullMode ? 'default' : 'library', isEditFromCampaigns: (templateData || {}).isEditFromCampaigns};
@@ -360,7 +362,12 @@ function SlideBoxContent(props) {
360
362
  }}
361
363
  route={{ name: (templateData.modeType || (templateData.definition && templateData.definition.mode)) === 'image' ? 'edit_image' : 'edit_text' }}
362
364
  onCreateComplete={onCreateComplete}
363
- isFullMode={props.isFullMode}
365
+ isFullMode={isFullMode}
366
+ saveMessage={saveMessage}
367
+ getFormSubscriptionData={getFormData}
368
+ isGetFormData={isGetFormData}
369
+ templateData={templateData}
370
+ selectedAccount={selectedAccount}
364
371
  />
365
372
  )
366
373
  }
@@ -369,7 +376,12 @@ function SlideBoxContent(props) {
369
376
  <CreateLineWrapper
370
377
  location={creativesLocationProps}
371
378
  onCreateComplete={onCreateComplete}
372
- isFullMode={props.isFullMode}
379
+ isFullMode={isFullMode}
380
+ isGetFormData={isGetFormData}
381
+ saveMessage={saveMessage}
382
+ selectedAccount={selectedAccount}
383
+ templateData={templateData}
384
+ getFormSubscriptionData={getFormData}
373
385
  />
374
386
  )
375
387
  }
@@ -414,5 +426,7 @@ SlideBoxContent.propTypes = {
414
426
  weChatMaptemplateStep: PropTypes.string,
415
427
  onFacebookSubmit: PropTypes.func,
416
428
  onCreateComplete: PropTypes.func,
429
+ saveMessage: PropTypes.func,
430
+ selectedAccount: PropTypes.object,
417
431
  };
418
432
  export default SlideBoxContent;
@@ -12,7 +12,7 @@ import { bindActionCreators } from 'redux';
12
12
  import styled from 'styled-components';
13
13
 
14
14
  import * as actions from './actions';
15
- import {selectEmailLayout, uploadSelector, templateUserList} from '../Templates/selectors';
15
+ import { selectEmailLayout, uploadSelector, templateUserList, makeSelectTemplates } from '../Templates/selectors';
16
16
  import SlideBoxContent from './SlideBoxContent';
17
17
  import SlideBoxHeader from './SlideBoxHeader';
18
18
  import SlideBoxFooter from './SlideBoxFooter';
@@ -215,6 +215,33 @@ class Creatives extends React.Component {
215
215
  };
216
216
  break;
217
217
  }
218
+ case constants.LINE: {
219
+ const { messageBody } = templateData;
220
+ const {
221
+ messages,
222
+ to,
223
+ messages: [{
224
+ type,
225
+ }] = [{}],
226
+ } = JSON.parse(messageBody);
227
+ creativesTemplateData = {
228
+ type: constants.LINE,
229
+ edit: true,
230
+ definition: {
231
+ mode: type,
232
+ },
233
+ name: "Campaign message Line content",
234
+ versions: {
235
+ base: {
236
+ content: {
237
+ messages,
238
+ to,
239
+ },
240
+ },
241
+ },
242
+ };
243
+ break;
244
+ }
218
245
  default:
219
246
  break;
220
247
  }
@@ -280,6 +307,18 @@ class Creatives extends React.Component {
280
307
  templateData = { channel, accountId, messageBody: JSON.stringify(messageBody), previewContent};
281
308
  }
282
309
  break;
310
+ case constants.LINE:
311
+ if (template.value) {
312
+ const { versions } = template.value;
313
+ const { Templates } = this.props;
314
+ const accountDetails = Templates && Templates.selectedLineAccount ? JSON.stringify(Templates.selectedLineAccount) : '';
315
+ templateData = {
316
+ channel,
317
+ accountDetails,
318
+ messageBody: JSON.stringify(versions.base.content),
319
+ };
320
+ }
321
+ break;
283
322
  default:
284
323
  break;
285
324
  }
@@ -393,6 +432,11 @@ class Creatives extends React.Component {
393
432
  showFooter = showFooter && !this.state.isLoadingContent;
394
433
  }
395
434
  }
435
+
436
+ if (channel === constants.LINE) {
437
+ showFooter = showFooter && isFullMode;
438
+ }
439
+
396
440
  return showFooter;
397
441
  }
398
442
  shouldShowDoneFooter = () => {
@@ -540,6 +584,8 @@ class Creatives extends React.Component {
540
584
  selectedWeChatAccount={selectedWeChatAccount}
541
585
  onWeChatMaptemplateStepChange={this.onWeChatMaptemplateStepChange}
542
586
  onFacebookSubmit={getCreativesData}
587
+ saveMessage={this.saveMessage}
588
+ selectedAccount={this.props.selectedAccount}
543
589
  />
544
590
  }
545
591
  footer={this.shouldShowFooter() &&
@@ -589,12 +635,15 @@ Creatives.propTypes = {
589
635
  selectedOfferDetails: PropTypes.array,
590
636
  channelsToDisable: PropTypes.array,
591
637
  selectedWeChatAccount: PropTypes.object,
638
+ Templates: PropTypes.object,
639
+ selectedAccount: PropTypes.object,
592
640
  };
593
641
  const mapStatesToProps = () => createStructuredSelector({
594
642
  isLoading: isLoadingSelector(),
595
643
  templateUserList: templateUserList(),
596
644
  EmailLayout: selectEmailLayout(),
597
645
  isUploading: uploadSelector(),
646
+ Templates: makeSelectTemplates(),
598
647
  });
599
648
  function mapDispatchToProps(dispatch) {
600
649
  return {
@@ -20,7 +20,7 @@ import * as templatesActionsCreators from '../Templates/actions';
20
20
  import Email from '../Email';
21
21
  import CmsTemplatesComponent from '../../v2Components/CmsTemplatesComponent';
22
22
  import messages from './messages';
23
- import { CHANNEL_CREATE_TRACK_MAPPING } from '../App/constants';
23
+ import { CHANNEL_CREATE_TRACK_MAPPING } from '../App/constants'
24
24
 
25
25
  const CapRadioCardWithLabel = ComponentWithLabelHOC(CapRadioCard);
26
26
  const { timeTracker } = GA;
@@ -110,15 +110,16 @@ export class EmailWrapper extends React.Component { // eslint-disable-line react
110
110
  };
111
111
  CapNotification.error(message);
112
112
  }
113
- stopTimerGA=() => {
113
+ stopTimerGA=()=>{
114
114
  // stop timer
115
- timeTracker.stopTimer(CHANNEL_CREATE_TRACK_MAPPING.email, {
115
+ timeTracker.stopTimer(CHANNEL_CREATE_TRACK_MAPPING['email'], {
116
116
  category: 'Creatives',
117
117
  action: 'Create',
118
118
  label: 'uploadZip',
119
119
  });
120
120
  }
121
121
  handleFileUpload = (file) => {
122
+
122
123
  const { templatesActions, intl, showNextStep, isUploading } = this.props;
123
124
  if (!isUploading) {
124
125
  const fileExtension = file.name.split('.').pop();
@@ -130,6 +131,7 @@ export class EmailWrapper extends React.Component { // eslint-disable-line react
130
131
  this.setState({ modeContent: { file } }, showNextStep);
131
132
  stopTimerGA();
132
133
  }, this.handleZipUploadError);
134
+
133
135
  } else if (fileExtension === 'html' || fileExtension === 'htm') {
134
136
  const reader = new FileReader();
135
137
  reader.onload = () => {
@@ -47,11 +47,12 @@ export function clearAsset() {
47
47
  // FOR EDIT
48
48
 
49
49
 
50
- export function editTemplate(template) {
50
+ export function editTemplate(template, callback) {
51
51
  console.log('action trigereed editTemplate line @@@');
52
52
  return {
53
53
  type: types.EDIT_TEMPLATE_REQUEST,
54
54
  template,
55
+ callback,
55
56
  };
56
57
  }
57
58
 
@@ -1,18 +1,18 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React, { Component } from 'react';
3
3
  import get from 'lodash/get';
4
+ import set from 'lodash/set';
4
5
  import { bindActionCreators } from 'redux';
5
6
  import { intlShape } from 'react-intl';
6
7
  import { createStructuredSelector } from 'reselect';
7
- import { Row, Col, Spin } from 'antd';
8
8
  import _ from 'lodash';
9
- import { CapHeading } from '@capillarytech/cap-ui-library';
9
+ import { CapSpin, CapHeading, CapRow, CapColumn } from '@capillarytech/cap-ui-library';
10
10
  import messages from './messages';
11
11
  import * as actions from './actions';
12
12
  import Gallery from '../../Assets/Gallery';
13
13
  import FormBuilder from '../../../v2Components/FormBuilder';
14
14
  //import { makeSelectCreate, makeSelectCreateResponse, makeSelectEdit, makeSelectEditResponse, makeSelectTemplateDetailsResponse } from './selectors';
15
- import {makeSelectCreateLine} from './selectors';
15
+ import {makeSelectCreateLine, makeSelectLine} from './selectors';
16
16
  import { makeSelectMetaEntities, setInjectedTags } from '../../Cap/selectors';
17
17
  import {getMessageObject} from '../../../utils/messageUtils';
18
18
  import { makeSelectTemplates } from '../../Templates/selectors';
@@ -44,7 +44,7 @@ export class Line extends Component {
44
44
  'title-tagList': {
45
45
  onTagSelect: this.onTagSelectTitle,
46
46
  },
47
- pane: {
47
+ 'pane': {
48
48
  onTabChange: this.onTabChange,
49
49
  },
50
50
  'line-template': {
@@ -53,7 +53,7 @@ export class Line extends Component {
53
53
  'image-upload-line': {
54
54
  onUpload: this.uploadImage,
55
55
  },
56
- tagList: {
56
+ 'tagList': {
57
57
  onTagSelect: this.onTagSelect,
58
58
  },
59
59
  'line-editor': {
@@ -88,7 +88,7 @@ export class Line extends Component {
88
88
  isReady: false,
89
89
  modeType: null,
90
90
  isDrawerRequired: false,
91
- galleryPage: 1,
91
+ startValidation: false,
92
92
  };
93
93
  }
94
94
 
@@ -127,8 +127,26 @@ export class Line extends Component {
127
127
  if (modeType) {
128
128
  this.setState({ modeType, schema: {} });
129
129
  }
130
- if (nextProps.isGetFormData) {
131
- nextProps.getFormLibraryData(this.getFormData());
130
+ const {
131
+ isGetFormData,
132
+ templateData,
133
+ } = nextProps;
134
+ if (isGetFormData) {
135
+ const {
136
+ lineData,
137
+ getFormSubscriptionData,
138
+ } = nextProps;
139
+ let selectedAccount = {};
140
+ if (lineData) {
141
+ const {
142
+ weCrmAccounts = [],
143
+ } = lineData;
144
+ selectedAccount = weCrmAccounts.filter((account) => account === selectedAccount);
145
+ }
146
+ getFormSubscriptionData({ ...this.getFormData(), accountDetails: selectedAccount});
147
+ }
148
+ if (templateData && templateData.edit) {
149
+ this.setEditState(templateData);
132
150
  }
133
151
  if (isSchemaReady) {
134
152
  // set schema value as per mode type. Supported mode (text & image)
@@ -221,24 +239,6 @@ export class Line extends Component {
221
239
  this.props.actions.clearData();
222
240
  }
223
241
 
224
- const isEditSucceed =
225
- this.state.isEdit &&
226
- nextProps.Line &&
227
- nextProps.Line.editResponse &&
228
- nextProps.Line.editResponse.templateId;
229
- if (isEditSucceed) {
230
- this.setState({ formData: {} });
231
- const message = getMessageObject(
232
- 'success',
233
- this.props.intl.formatMessage(
234
- messages['Line Template Edited Successfully']
235
- ),
236
- true
237
- );
238
- this.props.globalActions.addMessageToQueue(message);
239
- this.props.actions.clearEditResponse();
240
- }
241
-
242
242
  if (
243
243
  nextProps.Line.editTemplateError &&
244
244
  !_.isEqual(
@@ -272,6 +272,40 @@ export class Line extends Component {
272
272
  }
273
273
  }
274
274
 
275
+ setEditSuccessMessage = () => {
276
+ this.setState({ formData: {} });
277
+ const message = getMessageObject(
278
+ 'success',
279
+ this.props.intl.formatMessage(
280
+ messages['Line Template Edited Successfully']
281
+ ),
282
+ true
283
+ );
284
+ this.props.globalActions.addMessageToQueue(message);
285
+ this.props.actions.clearEditResponse();
286
+ }
287
+
288
+ setEditState = (data) => {
289
+ const { formData: previousFormData } = this.state;
290
+ const { text, originalContentUrl, imagePreview, image } = get(data, "versions.base.content.messages[0]");
291
+ const retrievedData = {
292
+ ...previousFormData[0],
293
+ };
294
+ if (text) {
295
+ retrievedData["message-editor"] = text;
296
+ }
297
+ const lineImage = originalContentUrl || image;
298
+ if (lineImage) {
299
+ retrievedData.image = lineImage;
300
+ retrievedData.imagePreview = imagePreview;
301
+ }
302
+ const formData = {
303
+ 0: retrievedData,
304
+ base: retrievedData,
305
+ };
306
+ this.setState({ formData });
307
+ }
308
+
275
309
  componentWillUnmount() {
276
310
  window.removeEventListener('message', this.handleFrameTasks);
277
311
  }
@@ -297,7 +331,7 @@ export class Line extends Component {
297
331
  }
298
332
 
299
333
  onFormDataChange = (formData, tabCount, currentTab) => {
300
- this.setState({ formData });
334
+ this.setState({ formData: { ...formData } });
301
335
  if (tabCount) {
302
336
  this.setState({ tabCount });
303
337
  }
@@ -468,13 +502,30 @@ export class Line extends Component {
468
502
  return;
469
503
  }
470
504
  }
471
- if (this.state.isEdit) {
472
- this.props.actions.editTemplate(obj, this.props.onCreateComplete);
473
- } else {
474
- this.props.actions.createTemplate(
475
- obj,
476
- this.props.onCreateComplete
477
- );
505
+ if (!this.state.startValidation) {
506
+ this.setState({
507
+ startValidation: true,
508
+ }, () => {
509
+ const {
510
+ isFullMode,
511
+ saveMessage,
512
+ actions,
513
+ onCreateComplete,
514
+ } = this.props;
515
+ if (!isFullMode) {
516
+ saveMessage();
517
+ } else if (this.state.isEdit) {
518
+ actions.editTemplate(obj, () => {
519
+ this.setEditSuccessMessage();
520
+ onCreateComplete();
521
+ });
522
+ } else {
523
+ actions.createTemplate(
524
+ obj,
525
+ onCreateComplete
526
+ );
527
+ }
528
+ });
478
529
  }
479
530
  };
480
531
 
@@ -763,16 +814,24 @@ export class Line extends Component {
763
814
 
764
815
  removeStandAlone = () => {
765
816
  const schema = _.cloneDeep(this.state.schema);
766
- if (
767
- this.props.location.query.type === 'embedded' &&
768
- this.props.location.query.module === 'loyalty'
769
- ) {
817
+ const {
818
+ location: {
819
+ query: {
820
+ type,
821
+ mode,
822
+ module,
823
+ },
824
+ },
825
+ } = this.props;
826
+ if (type === 'embedded' && module === 'loyalty') {
770
827
  schema.standalone.sections.splice(0, 1);
771
- } else if (
772
- this.props.location.query.type === 'embedded' &&
773
- typeof this.props.location.query.mode !== 'undefined'
774
- ) {
775
- delete schema.standalone;
828
+ } else if (type === 'embedded' && typeof mode !== 'undefined') {
829
+ delete schema.standalone;
830
+ } else if (!this.props.isFullMode) {
831
+ const childSections = get(schema, 'standalone.sections[0].childSections');
832
+ const fields = get(childSections, '[1].childSections[0].childSections');//removing template name section
833
+ fields.splice(0, 1);
834
+ set(childSections, '[1].childSections[0].childSections', fields);
776
835
  } else {
777
836
  schema.standalone.sections.splice(1, 1);
778
837
  }
@@ -910,18 +969,31 @@ export class Line extends Component {
910
969
  };
911
970
 
912
971
  render() {
913
- if (
914
- this.state.formData &&
915
- this.state.formData['0'] &&
916
- this.state.formData['0'].image
917
- ) {
918
- console.log('before render@@@', this.state.formData['0'].image);
919
- }
920
- let loading = this.props.Line.createTemplateInProgress
921
- ? this.props.Line.createTemplateInProgress
922
- : false;
923
- if (this.props.Line.assetUploading) {
924
- loading = true;
972
+ const {
973
+ Line: {
974
+ createTemplateInProgress,
975
+ assetUploading,
976
+ } = {},
977
+ templateData: {
978
+ edit,
979
+ mode,
980
+ orgId,
981
+ } = {},
982
+ params: {
983
+ id,
984
+ } = {},
985
+ } = this.props;
986
+ let loading = createTemplateInProgress || false;
987
+ loading = !!assetUploading;
988
+ if (edit || mode === 'edit' || (orgId && id)) {
989
+ const {
990
+ base: {
991
+ 'message-editor': msgEditor,
992
+ image: uploadedAsset,
993
+ } = {},
994
+ reUpload,
995
+ } = this.state.formData;
996
+ loading = typeof reUpload === 'undefined' ? !(msgEditor || uploadedAsset) : !reUpload;
925
997
  }
926
998
  let tags =
927
999
  this.props.metaEntities && this.props.metaEntities.tags
@@ -931,9 +1003,9 @@ export class Line extends Component {
931
1003
  tags = this.props.supportedTags;
932
1004
  }
933
1005
  return (
934
- <Spin spinning={loading}>
935
- <Row>
936
- <Col>
1006
+ <CapSpin spinning={loading}>
1007
+ <CapRow>
1008
+ <CapColumn>
937
1009
  <FormBuilder
938
1010
  key={'form builder'}
939
1011
  schema={this.state.schema}
@@ -965,10 +1037,11 @@ export class Line extends Component {
965
1037
  setDrawerVisibility={(drawervisibleFlag) =>
966
1038
  this.setState({ isDrawerRequired: drawervisibleFlag })
967
1039
  }
1040
+ startValidation={this.state.startValidation}
968
1041
  />
969
- </Col>
970
- </Row>
971
- </Spin>
1042
+ </CapColumn>
1043
+ </CapRow>
1044
+ </CapSpin>
972
1045
  );
973
1046
  }
974
1047
  }
@@ -989,10 +1062,13 @@ Line.propTypes = {
989
1062
  injectedTags: PropTypes.object,
990
1063
  onCreateComplete: PropTypes.func,
991
1064
  isFullMode: PropTypes.bool,
1065
+ saveMessage: PropTypes.func,
1066
+ templateData: PropTypes.object,
992
1067
  };
993
1068
 
994
1069
  const mapStateToProps = createStructuredSelector({
995
1070
  Line: makeSelectCreateLine(),
1071
+ lineData: makeSelectLine(),
996
1072
  Templates: makeSelectTemplates(),
997
1073
  metaEntities: makeSelectMetaEntities(),
998
1074
  injectedTags: setInjectedTags(),
@@ -16,7 +16,7 @@ export function* createTemplate({ template, callback }) {
16
16
  }
17
17
  yield put({ type: types.CREATE_TEMPLATE_SUCCESS, data: result.response, statusCode: result.status ? result.status.code : '', errorMsg });
18
18
  if (callback) {
19
- callback();
19
+ callback(result.response);
20
20
  }
21
21
  } catch (error) {
22
22
  yield put({ type: types.CREATE_TEMPLATE_FAILURE, error, errorMsg });
@@ -56,7 +56,7 @@ export function* editTemplate({ template, callback }) {
56
56
  errorMsg = result.message;
57
57
  }
58
58
  if (callback) {
59
- callback();
59
+ callback(result.response);
60
60
  }
61
61
  yield put({ type: types.EDIT_TEMPLATE_SUCCESS, data: result.response, statusCode: result.status ? result.status.code : '', errorMsg });
62
62
  } catch (error) {
@@ -17,19 +17,14 @@ const selectLineMessageEditDomain = () => (state) => state.get('lineCreate');
17
17
 
18
18
  const makeSelectLine = () => createSelector(
19
19
  selectLineDomain(),
20
- (substate) => substate.toJS()
20
+ (substate) => substate && substate.toJS()
21
21
  );
22
22
 
23
23
  const makeSelectCreateLine = () => createSelector(
24
24
  selectLineMessageEditDomain(),
25
- (substate) => substate.toJS()
25
+ (substate) => substate && substate.toJS()
26
26
  );
27
27
 
28
- // const makeSelectDuplicate = () => createSelector(
29
- // selectLineDuplicateDomain(),
30
- // (substate) => substate.toJS()
31
- // );
32
-
33
28
  export {
34
29
  makeSelectLine, makeSelectCreateLine,
35
30
  };
@@ -5,7 +5,8 @@
5
5
  */
6
6
  import React, {useState} from 'react';
7
7
  import { injectIntl, FormattedMessage, intlShape } from 'react-intl';
8
- import {CapRadioCard, CapLabel, CapButton} from '@capillarytech/cap-ui-library';
8
+ import PropTypes from 'prop-types';
9
+ import { CapRadioCard, CapLabel, CapButton } from '@capillarytech/cap-ui-library';
9
10
  import ComponentWithLabelHOC from '@capillarytech/cap-ui-library/assets/HOCs/ComponentWithLabelHOC';
10
11
  import styled from 'styled-components';
11
12
  import { CAP_SPACE_04, CAP_SPACE_08, CAP_SPACE_16 } from '@capillarytech/cap-ui-library/styled/variables';
@@ -33,7 +34,7 @@ const CardContainer = styled.div`
33
34
  `;
34
35
 
35
36
  const CreateLineWrapper = (props) => {
36
- const { intl: { formatMessage } } = props;
37
+ const { intl: { formatMessage }, selectedAccount } = props;
37
38
 
38
39
  const getModes = (modeList) => (
39
40
  modeList.map(({ label, imageSrc, type, title }) => ({
@@ -103,12 +104,14 @@ const CreateLineWrapper = (props) => {
103
104
  {...props}
104
105
  params={{mode: selectedLineTemplateOption}}
105
106
  route={{name: selectedLineTemplateOption}}
107
+ selectedAccount={selectedAccount}
106
108
  />
107
109
  );
108
110
  };
109
111
 
110
112
  CreateLineWrapper.propTypes = {
111
113
  intl: intlShape.isRequired,
114
+ selectedAccount: PropTypes.object,
112
115
  };
113
116
 
114
117
  export default injectIntl(CreateLineWrapper);
@@ -40,4 +40,16 @@ export default defineMessages({
40
40
  id: `${prefix}.continue`,
41
41
  defaultMessage: 'Continue',
42
42
  },
43
+ "accountLineListTitle": {
44
+ id: `${prefix}.accountLineListTitle`,
45
+ defaultMessage: 'Line accounts',
46
+ },
47
+ "accountLineListDescription": {
48
+ id: `${prefix}.accountLineListDescription`,
49
+ defaultMessage: 'Choose the account to send content',
50
+ },
51
+ "accountLineNoListTitle": {
52
+ id: `${prefix}.accountLineNoListTitle`,
53
+ defaultMessage: 'No line accounts present',
54
+ },
43
55
  });
@@ -13,23 +13,27 @@
13
13
  &:nth-child(4n+4) { //every 4th child
14
14
  margin-right: unset;
15
15
  }
16
+ }
17
+ }
18
+ }
19
+ }
16
20
 
17
- .LINE {
18
- .ant-card-body {
19
- background-color: $CAP_G09;
20
- height: 320px;
21
- }
22
-
23
- &.no-image .ant-card-meta {
24
- height: 276px;
25
- background: $CAP_WHITE;
26
- padding: $CAP_SPACE_16 $CAP_SPACE_12;
27
-
28
- .ant-card-meta-description {
29
- height: 250px;
30
- word-wrap: break-word;
31
- }
32
- }
21
+ .creatives-templates-list {
22
+ .pagination-container {
23
+ .LINE {
24
+ .ant-card-body {
25
+ background-color: $CAP_G09;
26
+ height: 320px;
27
+ }
28
+
29
+ &.no-image .ant-card-meta {
30
+ height: 276px;
31
+ background: $CAP_WHITE;
32
+ padding: $CAP_SPACE_16 $CAP_SPACE_12;
33
+
34
+ .ant-card-meta-description {
35
+ height: 250px;
36
+ word-wrap: break-word;
33
37
  }
34
38
  }
35
39
  }
@@ -52,6 +52,13 @@
52
52
  };
53
53
  }
54
54
 
55
+ export function setLineAccount(lineAccount) {
56
+ return {
57
+ type: types.SET_LINE_ACCOUNT,
58
+ lineAccount,
59
+ };
60
+ }
61
+
55
62
  export function handleHtmlUpload(selectedTemplate) {
56
63
  return {
57
64
  type: types.SET_SELECTED_LAYOUT,
@@ -43,3 +43,5 @@ export const RESET_TEMPLATE = "app/v2Containers/Templates/RESET_TEMPLATE";
43
43
  export const RESET_TEMPLATE_DATA = "app/v2Containers/Templates/RESET_TEMPLATE_DATA";
44
44
  export const RESET_UPLOAD_DATA = "app/v2Containers/Templates/RESET_UPLOAD_DATA";
45
45
  export const CLEAR_TEMPLATE_STORE_DATA = "app/v2Containers/Templates/CLEAR_TEMPLATE_STORE_DATA";
46
+
47
+ export const SET_LINE_ACCOUNT = "app/v2Containers/Templates/SET_LINE_ACCOUNT";
@@ -160,6 +160,10 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
160
160
  break;
161
161
  case LINE:
162
162
  channel = "Line";
163
+ if (!this.props.isFullMode) {
164
+ activeMode = ACCOUNT_SELECTION_MODE;
165
+ this.props.actions.getWeCrmAccounts(channel);
166
+ }
163
167
  break;
164
168
  default:
165
169
  channel = '';
@@ -239,6 +243,10 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
239
243
  channel = 'Ebill';
240
244
  } else if (nextProps.route.name.toLowerCase() === 'line') {
241
245
  channel = 'Line';
246
+ if (!this.props.isFullMode) {
247
+ this.setState({defaultAccount: true});
248
+ nextProps.actions.getWeCrmAccounts(channel);
249
+ }
242
250
  }
243
251
  this.setState({ channel }, () => {
244
252
  if (this.state.channel.toLowerCase() === 'line' || this.state.channel.toLowerCase() === 'sms' || this.state.channel.toLowerCase() === 'email' || this.state.channel.toLowerCase() === 'ebill' || (this.state.channel.toLowerCase() === 'wechat' && !isEmpty(nextProps.Templates.selectedWeChatAccount))) {
@@ -421,6 +429,8 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
421
429
  } else {
422
430
  this.setState({selectedAccountError: false});
423
431
  }
432
+ } else if (this.state.channel.toLowerCase() === 'line') {
433
+ this.props.actions.setLineAccount(this.props.Templates.weCrmAccounts.find(item => item.name === this.state.selectedAccount));
424
434
  }
425
435
  this.setState({activeMode: TEMPLATES_MODE});
426
436
  console.log("getAllTemplates params: ", params);
@@ -460,6 +470,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
460
470
  } else {
461
471
  creativesParams._id = pathname.split('/')[3];
462
472
  creativesParams.modeType = pathname.split('/')[4];
473
+ creativesParams.account = pathname.split('/')[5];
463
474
  }
464
475
  creativesParams.type = this.state.channel.toUpperCase();
465
476
  }
@@ -577,7 +588,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
577
588
  onClick={() => { if (this.props.isFullMode) { handlers.handlePreviewClick(template); } else { this.props.handlePeviewTemplate(template); } }}
578
589
  />,
579
590
  ],
580
- hoverOption: <CapButton className={this.props.isFullMode ? `edit-${channelLowerCase}` : `select-${channelLowerCase}`} onClick={(e) => handlers.handleEditClick(e, template)}>{this.props.intl.formatMessage(this.props.isFullMode ? messages.creativesTemplatesEdit : messages.selectButton)}</CapButton>,
591
+ hoverOption: <CapButton className={this.props.isFullMode ? `edit-${channelLowerCase}` : `select-${channelLowerCase}`} onClick={(e) => handlers.handleEditClick(e, template, undefined, undefined, {account: this.state.selectedAccount})}>{this.props.intl.formatMessage(this.props.isFullMode ? messages.creativesTemplatesEdit : messages.selectButton)}</CapButton>,
581
592
  };
582
593
  if (this.props.isFullMode) {
583
594
  templateData.extra.push(<CapDropdown
@@ -646,7 +657,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
646
657
  <div className={!isEmpty(this.state.searchText) && isEmpty(cardDataList) ? '' : "pagination-container"}>
647
658
  <CapCustomCardList key={`${currentChannel}-card-list`} cardList={cardDataList} type={currentChannel} style={{marginLeft: '16px'}}/>
648
659
  </div>)
649
- : ['sms', "email"].includes(this.state.channel.toLowerCase()) && <CapIllustration buttonClassName={`create-new-${channelLowerCase}`} {...this.getChannelTypeIllustrationInfo(currentChannel)} />
660
+ : ['sms', "email"].includes(this.state.channel.toLowerCase()) && <CapIllustration {...this.getChannelTypeIllustrationInfo(currentChannel)} />
650
661
  }
651
662
 
652
663
  {(this.state.selectedAccount === '' && isEmpty(this.props.Templates.selectedWeChatAccount)) && (this.state.channel.toLowerCase() === 'wechat' || this.state.channel.toLowerCase() === "mobilepush") &&
@@ -668,7 +679,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
668
679
  isEmpty(filteredTemplates) &&
669
680
  isEmpty(this.state.searchText) &&
670
681
  <div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto'} : {}}>
671
- <CapIllustration buttonClassName={`create-new-${channelLowerCase}`} {...this.getChannelTypeIllustrationInfo(currentChannel)} />
682
+ <CapIllustration {...this.getChannelTypeIllustrationInfo(currentChannel)} />
672
683
  </div>
673
684
  )
674
685
  }
@@ -1119,7 +1130,14 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1119
1130
  } else if (this.state.channel.toLowerCase() === "email") {
1120
1131
  this.props.emailActions.duplicateTemplate(template._id, "EMAIL");
1121
1132
  } else if (this.state.channel.toLowerCase() === "line") {
1122
- this.props.lineActions.createTemplate(duplicateObj);
1133
+ this.props.lineActions.createTemplate(duplicateObj, (response) => {
1134
+ if (response && response.templateId) {
1135
+ const message = `${this.state.channel} ${this.props.intl.formatMessage(messages.templateDuplicateSuccess)}`;
1136
+ CapNotification.success({key: 'duplicateSucess', message});
1137
+ this.getAllTemplates({params, resetPage: true});
1138
+ this.props.lineActions.clearCreateResponse();
1139
+ }
1140
+ });
1123
1141
  }
1124
1142
  }
1125
1143
 
@@ -1161,7 +1179,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1161
1179
  }
1162
1180
  };
1163
1181
 
1164
- handleEditClick(e, template, modeType, path) {
1182
+ handleEditClick(e, template, modeType, path, options) {
1165
1183
  timeTracker.startTimer(CHANNEL_EDIT_TRACK_MAPPING[this.state.channel.toLowerCase()]);
1166
1184
  if (modeType && modeType !== undefined) {
1167
1185
  this.setState({modeType});
@@ -1197,6 +1215,9 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1197
1215
  const { messages } = template.versions.base.content;
1198
1216
  const type = messages && messages[0] && messages[0].type;
1199
1217
  pathName = `/line/edit/${id}/${type}`;
1218
+ if (options && options.account) {
1219
+ pathName += `/${options.account}`;
1220
+ }
1200
1221
  }
1201
1222
  if (this.state.channel.toUpperCase() === WECHAT) {
1202
1223
  if (get(template, 'definition.msgcontent', '') === "RICH_MEDIA_WECHAT") {
@@ -1577,10 +1598,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1577
1598
  const { weCrmAccounts = [], fetchingWeCrmAccounts = false } = templates;
1578
1599
  const isWechatChannel = channel === WECHAT;
1579
1600
  const isMobilePushChannel = channel === MOBILE_PUSH;
1601
+ const isLineChannel = channel === LINE;
1580
1602
 
1581
- if ((isWechatChannel || isMobilePushChannel) && !isEmpty(weCrmAccounts)) {
1603
+ if ((isWechatChannel || isMobilePushChannel || isLineChannel) && !isEmpty(weCrmAccounts)) {
1582
1604
  forEach(weCrmAccounts, (account) => {
1583
- if ((isWechatChannel && account.configs && account.configs.is_wecrm_enabled) || isMobilePushChannel) {
1605
+ if ((isWechatChannel && account.configs && account.configs.is_wecrm_enabled) || isMobilePushChannel || isLineChannel) {
1584
1606
  if (query.type === 'embedded' && (!query.module || (query.module && query.module !== 'library'))) {
1585
1607
  if (query.source_account_id && account.sourceAccountIdentifier === query.source_account_id) {
1586
1608
  accountOptions.push(
@@ -1613,11 +1635,37 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1613
1635
  }
1614
1636
  });
1615
1637
  }
1638
+ const {
1639
+ intl: {
1640
+ formatMessage,
1641
+ },
1642
+ } = this.props;
1643
+ let accountHeader = formatMessage(messages.mobilepushAccount);
1644
+ let noAccountHeader = messages.noAccountsPresent;
1645
+ switch (channel) {
1646
+ case MOBILE_PUSH: {
1647
+ accountHeader = formatMessage(messages.mobilepushAccount);
1648
+ noAccountHeader = messages.noAccountsPresent;
1649
+ break;
1650
+ }
1651
+ case WECHAT: {
1652
+ accountHeader = formatMessage(messages.noAccountsPresentWeChat);
1653
+ noAccountHeader = messages.noAccountsPresent;
1654
+ break;
1655
+ }
1656
+ case LINE: {
1657
+ accountHeader = formatMessage(messages.lineAccount);
1658
+ noAccountHeader = messages.noAccountsPresentLine;
1659
+ break;
1660
+ }
1661
+ default:
1662
+ break;
1663
+ }
1616
1664
  return (<CapSpin spinning={fetchingWeCrmAccounts}>
1617
- {isEmpty(weCrmAccounts) && !fetchingWeCrmAccounts ? <FormattedMessage {...(channel === WECHAT ? messages.noAccountsPresentWeChat : messages.noAccountsPresent)} /> :
1665
+ {isEmpty(weCrmAccounts) && !fetchingWeCrmAccounts ? <FormattedMessage {...noAccountHeader} /> :
1618
1666
  <div style={{ overflowX: "auto", paddingBottom: CAP_SPACE_16 }}>
1619
1667
  <CapHeader
1620
- title={isMobilePushChannel ? this.props.intl.formatMessage(messages.mobilepushAccount) : this.props.intl.formatMessage(messages.wechatAccount)}
1668
+ title={accountHeader}
1621
1669
  description={this.props.intl.formatMessage(messages.chooseAccount)}
1622
1670
  size="regular"
1623
1671
  className="select-account-header"
@@ -1798,6 +1846,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1798
1846
  channel={route.name.toUpperCase()}
1799
1847
  handleCloseCreatives={this.onCreateComplete}
1800
1848
  selectedWeChatAccount={get(this.props, 'Templates.selectedWeChatAccount', {})}
1849
+ selectedAccount={this.state.selectedAccount}
1801
1850
  />
1802
1851
  }
1803
1852
  </CapRow>
@@ -126,6 +126,10 @@ export default defineMessages({
126
126
  id: `${scope}.noAccountsPresentWeChat`,
127
127
  defaultMessage: "Wechat accounts are not setup for your brand",
128
128
  },
129
+ "noAccountsPresentLine": {
130
+ id: `${scope}.noAccountsPresentLine`,
131
+ defaultMessage: "Line accounts are not setup for your brand",
132
+ },
129
133
  "noTemplatesMessage": {
130
134
  id: `${scope}.noTemplatesMessage`,
131
135
  defaultMessage: "No templates available",
@@ -326,6 +330,10 @@ export default defineMessages({
326
330
  id: `${scope}.mobilepushAccount`,
327
331
  defaultMessage: 'Mobilepush account',
328
332
  },
333
+ "lineAccount": {
334
+ id: `${scope}.lineAccount`,
335
+ defaultMessage: 'Line account',
336
+ },
329
337
  "all": {
330
338
  id: `${scope}.all`,
331
339
  defaultMessage: 'All',
@@ -71,6 +71,8 @@ function templatesReducer(state = initialState, action) {
71
71
  case types.SET_WECHAT_ACCOUNT:
72
72
  return state
73
73
  .set('selectedWeChatAccount', fromJS(action.weChatAccount));
74
+ case types.SET_LINE_ACCOUNT:
75
+ return state.set('selectedLineAccount', fromJS(action.lineAccount));
74
76
  case types.RESET_ACCOUNT:
75
77
  return state
76
78
  .remove('selectedWeChatAccount')