@capillarytech/creatives-library 7.10.59 → 7.10.60

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.
@@ -22,7 +22,31 @@ import find from 'lodash/find';
22
22
  import map from 'lodash/map';
23
23
  import { bindActionCreators } from 'redux';
24
24
  import { Modal, Form} from 'antd';
25
- import {CapMenu, CapDropdown, CapButton, CapInput, CapIcon, CapRadioCard, CapPopover, CapSpin, CapCustomCard, CapRow, CapSlideBox, CapLink, CapHeading, CapNotification, CapIllustration, CapHeader, CapLabel, CapRadio} from '@capillarytech/cap-ui-library';
25
+ import {
26
+ CapMenu,
27
+ CapDropdown,
28
+ CapButton,
29
+ CapInput,
30
+ CapIcon,
31
+ CapRadioCard,
32
+ CapPopover,
33
+ CapSpin,
34
+ CapCustomCard,
35
+ CapRow,
36
+ CapSlideBox,
37
+ CapLink,
38
+ CapHeading,
39
+ CapNotification,
40
+ CapIllustration,
41
+ CapHeader,
42
+ CapLabel,
43
+ CapRadio,
44
+ CapSelectFilter,
45
+ CapTag,
46
+ CapTooltip,
47
+ CapAlert,
48
+ CapModal,
49
+ } from '@capillarytech/cap-ui-library';
26
50
  import { makeSelectTemplates, makeSelectTemplatesResponse } from './selectors';
27
51
  import { makeSelectCreate as makeSelectCreateSms} from '../Sms/Create/selectors';
28
52
  import { makeSelectCreate as makeSelectCreateMobilePush } from '../MobilePush/Create/selectors';
@@ -32,6 +56,7 @@ import { makeSelectEdit } from '../Sms/Edit/selectors';
32
56
  import { makeSelectLine } from '../Line/Container/selectors';
33
57
  import { makeSelectViber } from '../Viber/selectors';
34
58
  import { UserIsAuthenticated } from '../../utils/authWrapper';
59
+ import { getObjFromQueryParams } from '../../utils/v2common';
35
60
  import messages from './messages';
36
61
  import {checkUnicode} from '../../utils/smsCharCountV2';
37
62
  import * as actions from './actions';
@@ -43,7 +68,7 @@ import * as emailActions from '../Email/actions';
43
68
  import * as lineActions from '../Line/Container/actions';
44
69
  import * as viberActions from '../Viber/actions';
45
70
  import * as facebookActions from '../Facebook/actions';
46
-
71
+ import * as whatsappActions from '../Whatsapp/actions';
47
72
  import CardGrid from '../../v2Components/CardGrid';
48
73
  import config from '../../config/app';
49
74
  import './_templates.scss';
@@ -53,21 +78,33 @@ import * as commonUtil from '../../utils/common';
53
78
  import Pagination from '../../v2Components/Pagination';
54
79
  import EmailPreview from '../../v2Components/EmailPreview';
55
80
  import CreativesContainer from '../CreativesContainer';
81
+ import WhatsappStatusContainer from '../../v2Components/WhatsappStatusContainer';
56
82
  import WechatRichmediaTemplatePreview from '../../v2Components/TemplatePreview/WechatRichmediaTemplatePreview';
57
83
  import {
58
84
  CHANNEL_CREATE_TRACK_MAPPING,
59
85
  CHANNEL_EDIT_TRACK_MAPPING,
60
86
  VIBER as VIBER_CHANNEL,
61
87
  FACEBOOK as FACEBOOK_CHANNEL,
88
+ CREATE,
62
89
  } from '../App/constants';
63
- import { MOBILE_PUSH, WECHAT, SMS, EMAIL, EBILL, LINE, VIBER, FACEBOOK } from '../CreativesContainer/constants';
90
+ import { MAX_WHATSAPP_TEMPLATES, WARNING_WHATSAPP_TEMPLATES } from './constants';
91
+ import {
92
+ CATEGORY_OPTIONS,
93
+ STATUS_OPTIONS,
94
+ CATEGORY as WHATSAPP_CATEGORY,
95
+ STATUS as WHATSAPP_STATUS,
96
+ WHATSAPP_STATUSES,
97
+ } from '../Whatsapp/constants';
98
+ import { getWhatsappContent, getWhatsappStatus, getWhatsappCategory } from '../Whatsapp/utils';
99
+ import { MOBILE_PUSH, WECHAT, SMS, EMAIL, EBILL, LINE, VIBER, FACEBOOK, WHATSAPP } from '../CreativesContainer/constants';
64
100
 
65
101
  import {CREATIVE} from '../Facebook/constants';
66
102
 
67
103
  import emailIllustration from '../Assets/images/emailIllustration.svg';
68
104
  import smsIllustration from '../Assets/images/smsIllustration.svg';
69
105
  import pushIllustration from '../Assets/images/pushIllustration.svg';
70
- import { CAP_SPACE_16 } from '@capillarytech/cap-ui-library/styled/variables';
106
+ import whatsappIllustration from '../Assets/images/whatsappIllustration.png';
107
+ import { CAP_SPACE_16, CAP_SPACE_44 } from '@capillarytech/cap-ui-library/styled/variables';
71
108
  import { GA } from '@capillarytech/cap-ui-utils';
72
109
  const { timeTracker } = GA;
73
110
  const {CapCustomCardList} = CapCustomCard;
@@ -105,6 +142,11 @@ const SMS_FILTERS = {
105
142
  SERVICE_IMPLICIT: 'implicit',
106
143
  };
107
144
 
145
+ const WHATSAPP_LOWERCASE = WHATSAPP.toLowerCase();
146
+ const SMS_LOWERCASE = SMS.toLowerCase();
147
+ const MOBILE_PUSH_LOWERCASE = MOBILE_PUSH.toLowerCase();
148
+ const EBILL_LOWERCASE = EBILL.toLowerCase();
149
+ const LINE_LOWERCASE = LINE.toLowerCase();
108
150
  export class Templates extends React.Component { // eslint-disable-line react/prefer-stateless-function
109
151
  constructor(props) {
110
152
  super(props);
@@ -133,6 +175,8 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
133
175
  wechatFilter: WECHAT_FILTERS.ALL,
134
176
  lineFilter: LINE_FILTERS.ALL,
135
177
  smsFilter: SMS_FILTERS.ALL,
178
+ selectedWhatsappStatus: '',
179
+ selectedWhatsappCategory: ''
136
180
  };
137
181
 
138
182
  this.handleOnHoverItem = this.handleOnHoverItem.bind(this);
@@ -198,6 +242,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
198
242
  activeMode = ACCOUNT_SELECTION_MODE;
199
243
  this.props.actions.getAccountsSettings();
200
244
  break;
245
+ case WHATSAPP:
246
+ channel = 'Whatsapp';
247
+ activeMode = ACCOUNT_SELECTION_MODE;
248
+ this.props.actions.getWeCrmAccounts(channel);
249
+ break;
201
250
  default:
202
251
  channel = '';
203
252
  }
@@ -218,6 +267,13 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
218
267
  if (this.props.location.query.type === 'embedded' && this.isEnabledInLibraryModule("setLocale")) {
219
268
  this.setLocale();
220
269
  }
270
+
271
+ // opening the create slidebox based on query params
272
+ const queryItems = getObjFromQueryParams(location.search);
273
+ if (queryItems.channel === WHATSAPP_LOWERCASE && queryItems.mode === CREATE) {
274
+ this.createTemplate();
275
+ }
276
+
221
277
  if (this.props.location.query.type === 'embedded') {
222
278
  this.props.actions.resetAccount();
223
279
  }
@@ -245,9 +301,8 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
245
301
  };
246
302
  const { totalCount: nextPropsTotalTemplatesCount } = nextProps?.TemplatesList?.[0] || {};
247
303
  const { totalCount: propsTotalTemplatesCount } = this.props?.TemplatesList?.[0] || {};
248
- const { templatesCount } = this.state || {};
249
304
 
250
- if (nextPropsTotalTemplatesCount && nextPropsTotalTemplatesCount !== propsTotalTemplatesCount && templatesCount === 0) {
305
+ if (nextPropsTotalTemplatesCount && nextPropsTotalTemplatesCount !== propsTotalTemplatesCount) {
251
306
  this.setState({ templatesCount: nextPropsTotalTemplatesCount });
252
307
  }
253
308
  if (!isEqual(nextProps.route.name, this.props.route.name)) {
@@ -409,20 +464,31 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
409
464
  if ((this.state.channel || '').toLowerCase() !== "sms") {
410
465
  CapNotification.error({key: 'somethingWrong', message});
411
466
  }
412
- if ( this.state.channel) {
413
- if (this.state.channel.toLowerCase() === "sms") {
414
- this.props.smsActions.clearCreateResponse();
415
- } else if (this.state.channel.toLowerCase() === "mobilepush") {
416
- this.props.mobilepushActions.clearCreateResponse();
417
- } else if (this.state.channel.toLowerCase() === "ebill") {
418
- this.props.ebillActions.clearCreateResponse();
419
- } else if (this.state.channel.toLowerCase() === "line") {
420
- this.props.lineActions.clearCreateResponse();
421
- } else if (this.state.channel.toLowerCase() === VIBER_CHANNEL) {
422
- this.props.viberActions.clearCreateResponse();
423
- } else if (this.state.channel.toLowerCase() === FACEBOOK_CHANNEL) {
424
- this.props.facebookActions.clearCreateResponse();
425
- }
467
+ const { smsActions, mobilepushActions, ebillActions, lineActions, viberActions, facebookActions, whatsappActions } = this.props;
468
+ switch (selectedChannel) {
469
+ case SMS_LOWERCASE:
470
+ smsActions.clearCreateResponse();
471
+ break;
472
+ case MOBILE_PUSH_LOWERCASE:
473
+ mobilepushActions.clearCreateResponse();
474
+ break;
475
+ case EBILL_LOWERCASE:
476
+ ebillActions.clearCreateResponse();
477
+ break;
478
+ case LINE_LOWERCASE:
479
+ lineActions.clearCreateResponse();
480
+ break;
481
+ case VIBER_CHANNEL:
482
+ viberActions.clearCreateResponse();
483
+ break;
484
+ case FACEBOOK_CHANNEL:
485
+ facebookActions.clearCreateResponse();
486
+ break;
487
+ case WHATSAPP_LOWERCASE:
488
+ whatsappActions.clearCreateResponse();
489
+ break;
490
+ default:
491
+ break;
426
492
  }
427
493
  }
428
494
 
@@ -463,16 +529,25 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
463
529
  this.setState({previewTemplate: nextProps.Templates.templateDetails});
464
530
  }
465
531
 
466
- if (nextProps.Templates.weCrmAccounts !== undefined && nextProps.Templates.weCrmAccounts.length === 1 && this.state.defaultAccount && (['wechat'].indexOf(this.state.channel.toLowerCase()) > -1)) {
532
+ if (nextProps.Templates.weCrmAccounts !== undefined && nextProps.Templates.weCrmAccounts.length === 1 && this.state.defaultAccount && (['wechat', WHATSAPP_LOWERCASE].indexOf(this.state.channel.toLowerCase()) > -1 )) {
467
533
  const paramsDefault = {
468
534
  name: this.state.searchText,
469
535
  sortBy: this.state.sortBy,
470
536
  };
471
- if (this.state.channel.toLowerCase() === 'wechat') {
537
+ if (['wechat', WHATSAPP_LOWERCASE].includes(this.state.channel.toLowerCase())) {
472
538
  paramsDefault.wecrmId = nextProps.Templates.weCrmAccounts[0].configs.wecrm_app_id;
473
539
  paramsDefault.wecrmToken = nextProps.Templates.weCrmAccounts[0].configs.wecrm_token;
474
540
  paramsDefault.originalId = nextProps.Templates.weCrmAccounts[0].sourceAccountIdentifier;
475
- nextProps.actions.setWeChatAccount(nextProps.Templates.weCrmAccounts[0]);
541
+ if (this.state.channel.toLowerCase() === WHATSAPP_LOWERCASE) {
542
+ nextProps.actions.setChannelAccount(
543
+ WHATSAPP,
544
+ nextProps.Templates.weCrmAccounts[0],
545
+ );
546
+ } else {
547
+ nextProps.actions.setWeChatAccount(
548
+ nextProps.Templates.weCrmAccounts[0],
549
+ );
550
+ }
476
551
  }
477
552
  this.setState({defaultAccount: false, activeMode: TEMPLATES_MODE, selectedAccount: nextProps.Templates.weCrmAccounts[0].name });
478
553
  this.getAllTemplates({params: paramsDefault}, true);
@@ -512,8 +587,10 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
512
587
  } else {
513
588
  this.setState({selectedAccountError: false});
514
589
  }
515
- } else if (this.state.channel.toLowerCase() === 'line') {
516
- this.props.actions.setLineAccount(this.props.Templates.weCrmAccounts.find((item) => item.name === this.state.selectedAccount));
590
+ } else if (this.state.channel.toLowerCase() === LINE.toLowerCase()) {
591
+ this.props.actions.setChannelAccount(LINE, this.props.Templates.weCrmAccounts.find((item) => item.name === this.state.selectedAccount));
592
+ } else if (this.state.channel.toLowerCase() === WHATSAPP_LOWERCASE) {
593
+ this.props.actions.setChannelAccount(WHATSAPP, this.props.Templates.weCrmAccounts.find((item) => item.name === this.state.selectedAccount));
517
594
  }
518
595
 
519
596
  this.setState({activeMode: TEMPLATES_MODE});
@@ -526,7 +603,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
526
603
  }
527
604
  onCreateComplete = (isReloadTemplates) => {
528
605
  this.setState({routeParams: null, previewOpen: false}, () => {
529
- if (this.isFullMode() && isReloadTemplates) {
606
+ if (this.isFullMode() && (isReloadTemplates)) {
530
607
  this.getAllTemplates({params: {}}, true);
531
608
  }
532
609
  });
@@ -546,7 +623,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
546
623
  const { pathname } = routeParams;
547
624
  if (pathname.includes('create')) {
548
625
  creativesParams.mode = 'create';
549
- } else if (pathname.includes('edit')) {
626
+ } else if (pathname.includes('edit') || pathname.includes('overview')) {
550
627
  creativesParams.mode = 'edit';
551
628
  if (pathname.includes('richmedia')) {
552
629
  creativesParams._id = pathname.split('/')[4];
@@ -557,6 +634,12 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
557
634
  creativesParams._id = pathname.split('/')[3];
558
635
  creativesParams.modeType = pathname.split('/')[4];
559
636
  creativesParams.account = pathname.split('/')[5];
637
+ if (this.state.channel.toLowerCase() === WHATSAPP_LOWERCASE) {
638
+ const whatsappSelectedTemplateData = this.selectTemplate(creativesParams._id) || {};
639
+ const { name = '', versions = {} } = whatsappSelectedTemplateData;
640
+ creativesParams.whatsappTemplateName = name;
641
+ creativesParams.whatsappTemplateCategory = get(versions, `base.content.whatsapp.category`, '');
642
+ }
560
643
  }
561
644
  creativesParams.type = this.state.channel.toUpperCase();
562
645
  }
@@ -654,6 +737,36 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
654
737
  return templates;
655
738
  }
656
739
  }
740
+
741
+ isStatusMatching = (status, selectedWhatsappStatus) => {
742
+ if(this.props.isFullMode && selectedWhatsappStatus) {
743
+ if ( [WHATSAPP_STATUSES.unsubmitted, WHATSAPP_STATUSES.pending].includes(status) ) {
744
+ return selectedWhatsappStatus === WHATSAPP_STATUSES.awaitingApproval;
745
+ }
746
+ return selectedWhatsappStatus === status;
747
+ }
748
+ else if (!this.props.isFullMode){
749
+ return status === WHATSAPP_STATUSES.approved;
750
+ }
751
+ return true;
752
+ }
753
+
754
+ filterWhatsappTemplates = (templates) => {
755
+ const { selectedWhatsappStatus, selectedWhatsappCategory } = this.state;
756
+ return templates.filter((template) => {
757
+ const { [WHATSAPP_CATEGORY]: category, [WHATSAPP_STATUS]: status } = template?.versions?.base?.content?.[WHATSAPP_LOWERCASE] || {};
758
+
759
+ // if status or category filter is applied, filter templates which match these filters
760
+ return (
761
+ this.isStatusMatching(status, selectedWhatsappStatus)
762
+ ) && (
763
+ selectedWhatsappCategory
764
+ ? category === selectedWhatsappCategory
765
+ : true
766
+ );
767
+ });
768
+ }
769
+
657
770
  filterSMSTemplates = (templates) => {
658
771
  const { smsFilter } = this.state;
659
772
  if (SMS_FILTERS.ALL === smsFilter) {
@@ -662,22 +775,44 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
662
775
  return templates.filter((template) => template.versions.base.type?.toLowerCase()?.includes(smsFilter));
663
776
  }
664
777
 
778
+ getCreativesEditText = (channel) => {
779
+ if (channel === WHATSAPP_LOWERCASE) {
780
+ return messages.whatsappOverview;
781
+ }
782
+ return messages.creativesTemplatesEdit;
783
+ }
784
+
665
785
  getTemplateDataForGrid = ({templates, handlers, filterContent, channel, isLoading, loadingTip}) => {
666
786
  const currentChannel = channel.toUpperCase();
667
787
  const {channel: stateChannel} = this.state;
668
788
  const channelLowerCase = stateChannel.toLowerCase();
669
789
  let filteredTemplates = templates;
670
790
  let isTraiDltFeature = false;
671
- if (currentChannel === WECHAT) {
672
- filteredTemplates = this.filterWechatTemplates(templates);
673
- }
674
- if (currentChannel === LINE) {
675
- filteredTemplates = this.filterLineTemplates(templates);
676
- }
677
- if (currentChannel === SMS) {
678
- filteredTemplates = this.filterSMSTemplates(templates);
679
- isTraiDltFeature = this.checkDLTfeatureEnable();
791
+ switch (currentChannel) {
792
+ case WECHAT:
793
+ filteredTemplates = this.filterWechatTemplates(templates);
794
+ break;
795
+ case WHATSAPP:
796
+ filteredTemplates = this.filterWhatsappTemplates(templates);
797
+ break;
798
+ case LINE:
799
+ filteredTemplates = this.filterLineTemplates(templates);
800
+ break;
801
+ case SMS:
802
+ filteredTemplates = this.filterSMSTemplates(templates);
803
+ isTraiDltFeature = this.checkDLTfeatureEnable();
804
+ break;
805
+ default:
806
+ break;
680
807
  }
808
+
809
+ const hoverButtonText =
810
+ this.props.intl.formatMessage(
811
+ this.props.isFullMode
812
+ ? this.getCreativesEditText(channelLowerCase)
813
+ : messages.selectButton
814
+ );
815
+
681
816
  const cardDataList = filteredTemplates && filteredTemplates.length ? map(filteredTemplates, (template) => {
682
817
  const templateData =
683
818
  {
@@ -691,18 +826,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
691
826
  onClick={() => { if (this.props.isFullMode) { handlers.handlePreviewClick(template); } else { this.props.handlePeviewTemplate(template); } }}
692
827
  />,
693
828
  ],
694
- 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>,
829
+ hoverOption: <CapButton className={this.props.isFullMode ? `edit-${channelLowerCase}` : `select-${channelLowerCase}`} onClick={(e) => handlers.handleEditClick(e, template, undefined, undefined, {account: this.state.selectedAccount})}>{hoverButtonText}</CapButton>,
695
830
  };
696
831
  if (this.props.isFullMode) {
697
832
  templateData.extra.push(<CapDropdown
698
833
  overlay={
699
834
  <CapMenu>
700
- {this.state.channel.toUpperCase() !== WECHAT && !isTraiDltFeature && <CapMenu.Item className={`duplicate-${channelLowerCase}`} onClick={() => this.duplicateTemplate(template)}>
835
+ { ![WECHAT, WHATSAPP].includes(this.state.channel.toUpperCase()) && !isTraiDltFeature && <CapMenu.Item className={`duplicate-${channelLowerCase}`} onClick={() => this.duplicateTemplate(template)}>
701
836
  <FormattedMessage {...messages.duplicateButton} />
702
837
  </CapMenu.Item>}
703
838
  <CapMenu.Item
704
839
  className={this.state.channel.toUpperCase() === WECHAT && !template.definition ? `unmap-${channelLowerCase}` : `delete-${channelLowerCase}`}
705
- onClick={() => this.deleteTemplate(template)}
840
+ onClick={() => this.toggleDeleteTemplateModal(template)}
706
841
  >
707
842
  {this.state.channel.toUpperCase() === WECHAT && !template.definition ? <FormattedMessage {...messages.unMapButton} /> : <FormattedMessage {...messages.deleteButton} />}
708
843
  </CapMenu.Item>
@@ -834,6 +969,16 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
834
969
  }
835
970
  break;
836
971
  }
972
+ case WHATSAPP:
973
+ templateData.title = (
974
+ <CapRow className="whatsapp-card-title">
975
+ <CapLabel>{template.name}</CapLabel>
976
+ <WhatsappStatusContainer template={template} />
977
+ </CapRow>
978
+ );
979
+
980
+ templateData.content = getWhatsappContent(template);
981
+ break;
837
982
  default:
838
983
  templateData.content = "";
839
984
  }
@@ -842,8 +987,9 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
842
987
  const accountId = get(this.props, 'Templates.selectedWeChatAccount.uuid');
843
988
  const accounts = get(this.props, 'Templates.weCrmAccounts');
844
989
  return (<div>
845
- { [WECHAT, MOBILE_PUSH].includes(currentChannel) && this.showAccountName() }
990
+ {[WECHAT, MOBILE_PUSH].includes(currentChannel) && this.showAccountName()}
846
991
  {filterContent}
992
+ {[WHATSAPP].includes(currentChannel) && this.selectedFilters()}
847
993
  <CapSpin spinning={isLoading} tip={loadingTip} style={{width: '100%'}}>
848
994
  {<div>
849
995
  {!isEmpty(filteredTemplates) || !isEmpty(this.state.searchText) || !isEmpty(this.props.Templates.templateError) ? (
@@ -865,7 +1011,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
865
1011
  </div>
866
1012
  }
867
1013
 
868
- { !isEmpty(this.props.Templates.selectedWeChatAccount) && this.state.channel.toLowerCase() === "mobilepush" && this.state.selectedAccountError ?
1014
+ { !isEmpty(this.props.Templates.selectedWeChatAccount) && ["mobilepush"].includes(this.state.channel.toLowerCase()) && this.state.selectedAccountError ?
869
1015
  <div style={{ height: '65vh', textAlign: 'center', lineHeight: '65vh'}}>
870
1016
  <CapHeading type="h6" >
871
1017
  {this.props.intl.formatMessage(messages.accountConfigError)}
@@ -882,6 +1028,35 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
882
1028
  </div>
883
1029
  )
884
1030
  }
1031
+ { !isEmpty(this.props.Templates.selectedWhatsappAccount) && [WHATSAPP_LOWERCASE].includes(this.state.channel.toLowerCase()) && this.state.selectedAccountError ?
1032
+ <div style={{ height: '65vh', textAlign: 'center', lineHeight: '65vh'}}>
1033
+ <CapHeading type="h6" >
1034
+ {this.props.intl.formatMessage(messages.accountConfigError)}
1035
+ <InlineDiv>
1036
+ <CapLink title="here" style={{display: 'inline'}} onClick={() => { window.open(config.production.accountConfig`${accountId}`); }}/>
1037
+ </InlineDiv>
1038
+ </CapHeading>
1039
+ </div> : (
1040
+ [WHATSAPP_LOWERCASE].includes(this.state.channel.toLowerCase()) &&
1041
+ isEmpty(this.state.searchText) &&
1042
+ !isLoading &&
1043
+ <div style={this.isFullMode() ? { height: "calc(100vh - 325px)", overflow: 'auto' } : {}}>
1044
+ {isEmpty(templates) && <CapIllustration buttonClassName={`create-new-${channelLowerCase}`} {...this.getChannelTypeIllustrationInfo(currentChannel)} />}
1045
+ {!isEmpty(templates) && isEmpty(filteredTemplates) && <CapHeader
1046
+ title={
1047
+ <CapHeading type="h3" style={{ textAlign: "center", marginTop: CAP_SPACE_44 }}>
1048
+ {this.props.intl.formatMessage(messages.noFilteredWhatsappTemplatesTitle)}
1049
+ </CapHeading>
1050
+ }
1051
+ description={
1052
+ <CapLabel style={{ textAlign: "center" }}>
1053
+ {this.props.intl.formatMessage(messages.noFilteredWhatsappTemplatesDesc)}
1054
+ </CapLabel>
1055
+ }
1056
+ />}
1057
+ </div>
1058
+ )
1059
+ }
885
1060
  </div>
886
1061
  }
887
1062
 
@@ -1188,7 +1363,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1188
1363
  } else {
1189
1364
  this.props.createNew();
1190
1365
  }
1191
- } else if (ev.key && (ev.key.toLowerCase() === "text" || ev.key.toLowerCase() === "image")) {
1366
+ } else if (ev?.key && (ev.key.toLowerCase() === "text" || ev.key.toLowerCase() === "image")) {
1192
1367
  routeParams = {
1193
1368
  pathname: `/${channel === 'line' ? 'line' : 'mobilepush'}/create/${ev.key.toLowerCase()}`,
1194
1369
  query: type === 'embedded' ? {type: 'embedded', module} : {module},
@@ -1456,6 +1631,9 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1456
1631
  pathName = `/wechat/edit/${id}`;
1457
1632
  }
1458
1633
  }
1634
+ if (this.state.channel.toUpperCase() === WHATSAPP) {
1635
+ pathName = `/whatsapp/edit/${id}/`;
1636
+ }
1459
1637
  if (this.isEnabledInLibraryModule("callSelectFromProps")) {
1460
1638
  let data = id;
1461
1639
  if (this.props.fbAdManager) {
@@ -1716,7 +1894,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1716
1894
  if (this.props.location.query.type === "embedded" && this.props.location.query.module === "library") {
1717
1895
  switch (property) {
1718
1896
  case "showAccountSelection":
1719
- return (this.props.route && (this.props.route.name.toLowerCase() === "wechat" || this.props.route.name.toLowerCase() === "mobilepush"));
1897
+ return (this.props.route && (['wechat', 'mobilepush', WHATSAPP_LOWERCASE].includes(this.props.route.name.toLowerCase())));
1720
1898
  case "setLocale":
1721
1899
  return true;
1722
1900
  case "callCreateFromProps":
@@ -1734,9 +1912,13 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1734
1912
  isCreateDisabled = () => {
1735
1913
  let isDisabled = this.isLoading();
1736
1914
  const channel = this.state.channel.toUpperCase();
1737
- if (channel === MOBILE_PUSH || channel === WECHAT) {
1915
+ if (channel === MOBILE_PUSH || channel === WECHAT || channel === WHATSAPP) {
1738
1916
  isDisabled = isDisabled || isEmpty(this.props.Templates.selectedWeChatAccount);
1739
1917
  }
1918
+ if (channel === WHATSAPP) {
1919
+ // limit on max number of templates allowed for whatsapp
1920
+ isDisabled = this.state.templatesCount >= MAX_WHATSAPP_TEMPLATES;
1921
+ }
1740
1922
  if (channel === MOBILE_PUSH) {
1741
1923
  isDisabled = this.checkSearchDisabled();
1742
1924
  }
@@ -1758,6 +1940,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1758
1940
  const facebookLoader = (getAllTemplatesInProgress && this.state.channel.toLowerCase() === FACEBOOK_CHANNEL);
1759
1941
 
1760
1942
  const wechatLoader = (((this.state.selectedAccount !== '' && getAllTemplatesInProgress) || (fetchingWeCrmAccounts)) && this.state.channel.toLowerCase() === 'wechat');
1943
+ const whatsappLoader = (((this.state.selectedAccount !== '' && getAllTemplatesInProgress) || (fetchingWeCrmAccounts)) && this.state.channel.toLowerCase() === WHATSAPP_LOWERCASE);
1761
1944
  const mobilePushLoader = (((getAllTemplatesInProgress) || (fetchingWeCrmAccounts)) && this.state.channel.toLowerCase() === 'mobilepush');
1762
1945
  const emailLoader = (
1763
1946
  (getAllTemplatesInProgress ||
@@ -1766,6 +1949,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1766
1949
  EmailCreate.duplicateTemplateInProgress) && this.state.channel.toLowerCase() === 'email');
1767
1950
  const ebillLoader = ((getAllTemplatesInProgress || sendingFile) && this.state.channel.toLowerCase() === 'ebill');
1768
1951
  const isLoading = ((wechatLoader !== undefined) ? wechatLoader : false) ||
1952
+ (whatsappLoader !== undefined ? whatsappLoader : false) ||
1769
1953
  (smsLoader !== undefined ? smsLoader : false) ||
1770
1954
  (emailLoader !== undefined ? emailLoader : false) ||
1771
1955
  (ebillLoader !== undefined ? ebillLoader : false) ||
@@ -1801,6 +1985,16 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1801
1985
  illustrationImage: pushIllustration,
1802
1986
  title: <FormattedMessage {...messages.pushTitleIllustartion} values={{ template: templateText }} />,
1803
1987
  };
1988
+ case WHATSAPP:
1989
+ return {
1990
+ buttonLabel: <FormattedMessage {...messages.newWhatsappTemplate} values={{ template: templateText }} />,
1991
+ onClick: this.createTemplate,
1992
+ illustrationImage: whatsappIllustration,
1993
+ title: <FormattedMessage {...messages.whatsappTitleIllustration} values={{ template: templateText }} />,
1994
+ description: <FormattedMessage {...messages.whatsappDescIllustration} />,
1995
+ descriptionPosition: 'bottom',
1996
+ descriptionClassName: 'whatsapp-illustration-desc',
1997
+ };
1804
1998
  default:
1805
1999
  return {};
1806
2000
  }
@@ -1824,21 +2018,25 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1824
2018
  );
1825
2019
  }
1826
2020
  renderDeleteConfirmationModal = () => {
1827
- const modal =
1828
- (<Modal
2021
+ const {
2022
+ intl: {
2023
+ formatMessage,
2024
+ } = {},
2025
+ } = this.props;
2026
+
2027
+ const modal = ( <CapModal
2028
+ className="delete-template-confirm"
2029
+ title={formatMessage(messages.areYouSureText)}
1829
2030
  visible={this.state.showModal}
1830
- title={"Alert"}
1831
2031
  onOk={this.handleOk}
1832
2032
  onCancel={this.handleCancel}
1833
- footer={[
1834
- <CapButton key="back" onClick={this.handleCancel}>{this.props.intl.formatMessage(messages.cancelText)}</CapButton>,
1835
- <CapButton key="submit" type="primary" id="delete-version" onClick={this.handleOk}>
1836
- {this.props.intl.formatMessage(messages.yesText)}
1837
- </CapButton>,
1838
- ]}
2033
+ centered
2034
+ okText={formatMessage(messages.yesText)}
2035
+ closeText={formatMessage(messages.cancelText)}
1839
2036
  >
1840
- {this.props.intl.formatMessage(messages.templateDeleteConfirm)}
1841
- </Modal>);
2037
+ {formatMessage(messages.templateDeleteConfirm)}
2038
+ </CapModal>
2039
+ );
1842
2040
  return modal;
1843
2041
  };
1844
2042
 
@@ -1852,13 +2050,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1852
2050
  const { weCrmAccounts = [], fetchingWeCrmAccounts = false, campaignSettings = {} } = templates;
1853
2051
  const isWechatChannel = channel === WECHAT;
1854
2052
  const isMobilePushChannel = channel === MOBILE_PUSH;
1855
- const isLineChannel = channel === LINE;
1856
- const isFacebookChannel = channel === FACEBOOK;
1857
-
2053
+ const isFacebookChannel = channel === FACEBOOK;
1858
2054
 
1859
- if ((isWechatChannel || isMobilePushChannel || isLineChannel) && !isEmpty(weCrmAccounts) && !isFacebookChannel ) {
2055
+ if ([WECHAT, MOBILE_PUSH, LINE, WHATSAPP].includes(channel) && !isEmpty(weCrmAccounts) && !isFacebookChannel) {
1860
2056
  forEach(weCrmAccounts, (account) => {
1861
- if ((isWechatChannel && account.configs && account.configs.is_wecrm_enabled) || isMobilePushChannel || isLineChannel) {
2057
+ if ((isWechatChannel && account.configs && account.configs.is_wecrm_enabled) || [MOBILE_PUSH, LINE, WHATSAPP].includes(channel)) {
1862
2058
  if (query.type === 'embedded' && (!query.module || (query.module && query.module !== 'library'))) {
1863
2059
  if (query.source_account_id && account.sourceAccountIdentifier === query.source_account_id) {
1864
2060
  accountOptions.push(
@@ -1922,6 +2118,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1922
2118
  noAccountHeader = messages.noAccountsPresentFacebook;
1923
2119
  break;
1924
2120
  }
2121
+ case WHATSAPP: {
2122
+ accountHeader = formatMessage(messages.whatsappAccount);
2123
+ noAccountHeader = messages.noAccountsPresentWhatsapp;
2124
+ break;
2125
+ }
1925
2126
  default:
1926
2127
  break;
1927
2128
  }
@@ -1977,27 +2178,102 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1977
2178
  this.setState({activeMode: TEMPLATES_MODE});
1978
2179
  }
1979
2180
 
2181
+ accountNameMessage = (channel) => {
2182
+ const {
2183
+ intl: {
2184
+ formatMessage,
2185
+ },
2186
+ } = this.props;
2187
+
2188
+ const channelObj = {
2189
+ [MOBILE_PUSH]: 'mobilepushAccount',
2190
+ [WECHAT]: 'wechatAccount',
2191
+ [WHATSAPP]: 'whatsappAccount',
2192
+ }
2193
+ return formatMessage(messages[channelObj[channel?.toUpperCase()]]);
2194
+ }
2195
+
1980
2196
  showAccountName = () => {
1981
2197
  const { activeMode, selectedAccount, channel } = this.state;
1982
2198
  const { Templates: templates = {}} = this.props;
1983
- const { weCrmAccounts = []} = templates;
1984
- const isMobilePushChannel = channel === MOBILE_PUSH;
2199
+ const { weCrmAccounts = [] } = templates;
1985
2200
  return (activeMode !== ACCOUNT_CHANGE_MODE &&
1986
2201
  <CapHeader
1987
- title={<CapHeading type="h4">{isMobilePushChannel ? this.props.intl.formatMessage(messages.mobilepushAccount) : this.props.intl.formatMessage(messages.wechatAccount)}</CapHeading>}
2202
+ title={<CapHeading type="h4">{this.accountNameMessage(channel)}</CapHeading>}
1988
2203
  description={<><CapHeading type="h3">{selectedAccount} {weCrmAccounts.length > 1 && <CapLink onClick={this.setAccountChangeMode} style={{display: 'inline-block', position: 'relative', top: '3px'}} fontWeight="regular" fontSize="14px" title="change"/>} </CapHeading></>}
1989
2204
  size="regular"
1990
2205
  />);
1991
2206
  }
1992
2207
 
2208
+ removeWhatsappFilter = (filter) => {
2209
+ switch (filter) {
2210
+ case WHATSAPP_STATUS:
2211
+ this.setState({ selectedWhatsappStatus: null });
2212
+ break;
2213
+ case WHATSAPP_CATEGORY:
2214
+ this.setState({ selectedWhatsappCategory: null });
2215
+ break;
2216
+ default:
2217
+ break;
2218
+ }
2219
+ }
2220
+
2221
+ selectedFilters = () => {
2222
+ const { selectedWhatsappStatus, selectedWhatsappCategory } = this.state;
2223
+ const {
2224
+ intl: {
2225
+ formatMessage,
2226
+ },
2227
+ } = this.props;
2228
+ return (
2229
+ <CapRow type="flex" align="middle" className="selected-whatsapp-filters">
2230
+ {
2231
+ selectedWhatsappStatus ? (
2232
+ <CapTag closable onClose={() => this.removeWhatsappFilter(WHATSAPP_STATUS)}>
2233
+ {formatMessage(messages.status)}: {getWhatsappStatus(selectedWhatsappStatus)?.label || selectedWhatsappStatus}
2234
+ </CapTag>
2235
+ ) : null
2236
+ }
2237
+ {
2238
+ selectedWhatsappCategory ? (
2239
+ <CapTag closable onClose={() => this.removeWhatsappFilter(WHATSAPP_CATEGORY)}>
2240
+ {formatMessage(messages.category)}: {getWhatsappCategory(selectedWhatsappCategory)?.label || selectedWhatsappCategory}
2241
+ </CapTag>
2242
+ ) : null
2243
+ }
2244
+ {
2245
+ (selectedWhatsappStatus || selectedWhatsappCategory) ? (
2246
+ <CapLink
2247
+ onClick={() => {
2248
+ this.removeWhatsappFilter(WHATSAPP_STATUS);
2249
+ this.removeWhatsappFilter(WHATSAPP_CATEGORY);
2250
+ }}
2251
+ title={this.props.intl.formatMessage(messages.clearAll)}
2252
+ />
2253
+ ) : null
2254
+ }
2255
+ </CapRow>
2256
+ );
2257
+ }
2258
+
1993
2259
  setWechatFilter = (e) => this.setState({wechatFilter: e.target.value});
2260
+ setWhatsappStatus = (value) => this.setState({selectedWhatsappStatus: value?.item?.props?.value});
2261
+ setWhatsappCategory = (value) => this.setState({selectedWhatsappCategory: value?.item?.props?.value});
1994
2262
  setLineFilter = (e) => this.setState({lineFilter: e.target.value});
1995
2263
  setSMSFilter = (e) => this.setState({smsFilter: e.target.value});
1996
2264
 
2265
+ openCreativesFullMode = () => {
2266
+ window.open(`/creatives/ui/v2?channel=${WHATSAPP_LOWERCASE}&mode=${CREATE}`, "_blank");
2267
+ }
1997
2268
 
1998
2269
  render() {
1999
2270
  const isFullMode = this.isFullMode();
2000
- const { activeMode, channel, wechatFilter, lineFilter, smsFilter } = this.state;
2271
+ const { activeMode, channel, wechatFilter, lineFilter, smsFilter, templatesCount } = this.state;
2272
+ const {
2273
+ intl: {
2274
+ formatMessage,
2275
+ } = {},
2276
+ } = this.props || {};
2001
2277
  let showFbAds = false;
2002
2278
  if (channel.toLowerCase() === FACEBOOK_CHANNEL) {
2003
2279
  const {campaignSettings, fetchedOrgLevelCampaignSettings} = this?.props?.Templates || {};
@@ -2061,6 +2337,33 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2061
2337
  const isWechatEmbedded = !this.props.isFullMode && channel.toUpperCase() === WECHAT;
2062
2338
  const channelLowerCase = (channel || '').toLowerCase();
2063
2339
  const isTraiDltFeature = this.checkDLTfeatureEnable();
2340
+
2341
+ const createButton =
2342
+ ( channelLowerCase === WHATSAPP_LOWERCASE && !this.props.isFullMode )
2343
+ ? (
2344
+ <CapLink
2345
+ onClick={this.openCreativesFullMode}
2346
+ title={formatMessage(messages.createNewTemplateLink)}
2347
+ suffix={<CapIcon type="open-in-new" size="s" />}
2348
+ className="create-new-link"
2349
+ />
2350
+ )
2351
+ : (
2352
+ <CapButton
2353
+ className={`create-new-${channelLowerCase}`}
2354
+ type={"primary"}
2355
+ disabled={this.isCreateDisabled()}
2356
+ style={{marginLeft: '8px', marginBottom: '12px'}}
2357
+ onClick={this.createTemplate}
2358
+ >
2359
+ { this.props.intl.formatMessage((isTraiDltFeature && channel.toUpperCase() === SMS ) ? messages.uploadTemplate : messages.createNewActionButton) }
2360
+ </CapButton>
2361
+ );
2362
+
2363
+ const isWhatsappCountExeeded = templatesCount >= MAX_WHATSAPP_TEMPLATES;
2364
+ const showWhatsappCountWarning = templatesCount >= WARNING_WHATSAPP_TEMPLATES;
2365
+ const whatsappCountExceedText = <FormattedMessage {...messages.whatsappMaxTemplates} values={{ maxCount: MAX_WHATSAPP_TEMPLATES }}/>;
2366
+
2064
2367
  const filterContent = (( isfilterContentVisisble || [WECHAT, MOBILE_PUSH].includes(this.state.channel.toUpperCase())) && <div className="action-container">
2065
2368
  {isfilterContentVisisble && <CapInput.Search
2066
2369
  className="search-text"
@@ -2085,6 +2388,36 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2085
2388
  </CapLabel></CapRadio.Button>
2086
2389
  </CapRadio.CapRadioGroup>
2087
2390
  }
2391
+ {
2392
+ channel.toUpperCase() === WHATSAPP && (
2393
+ <div className="whatsapp-filters">
2394
+ {
2395
+ this.props.isFullMode ? (
2396
+ <CapSelectFilter
2397
+ placement="bottomLeft"
2398
+ data={STATUS_OPTIONS}
2399
+ onSelect={this.setWhatsappStatus}
2400
+ selectedValue={this.state.selectedWhatsappStatus}
2401
+ placeholder="Status"
2402
+ showBadge={true}
2403
+ width="90px"
2404
+ />
2405
+ ) : null
2406
+ }
2407
+ <CapSelectFilter
2408
+ placement="bottomLeft"
2409
+ data={CATEGORY_OPTIONS}
2410
+ onSelect={this.setWhatsappCategory}
2411
+ selectedValue={this.state.selectedWhatsappCategory}
2412
+ placeholder="Category"
2413
+ showBadge={true}
2414
+ width="105px"
2415
+ overlayStyle={{ overflowY: "hidden" }}
2416
+ />
2417
+ </div>
2418
+
2419
+ )
2420
+ }
2088
2421
  {
2089
2422
  channel.toUpperCase() === LINE &&
2090
2423
  isFullMode
@@ -2152,14 +2485,16 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2152
2485
  : () => null
2153
2486
  }
2154
2487
  <div style={{display: "flex", justifyContent: "space-between", alignItems: 'center'}}>
2155
- {isfilterContentVisisble && !isWechatEmbedded && <CapButton
2156
- className={`create-new-${channelLowerCase}`}
2157
- type={"primary"}
2158
- disabled={this.isCreateDisabled()}
2159
- style={{marginLeft: '8px', marginBottom: '12px'}}
2160
- onClick={this.createTemplate} >
2161
- { this.props.intl.formatMessage((isTraiDltFeature && channel.toUpperCase() === SMS ) ? messages.uploadTemplate : messages.createNewActionButton) }
2162
- </CapButton>}
2488
+ {
2489
+ this.state.channel.toLowerCase() === WHATSAPP_LOWERCASE && isWhatsappCountExeeded ? (
2490
+ <CapTooltip title={whatsappCountExceedText}>
2491
+ <div className="button-disabled-tooltip-wrapper">
2492
+ {createButton}
2493
+ </div>
2494
+ </CapTooltip>
2495
+ )
2496
+ : isfilterContentVisisble && !isWechatEmbedded && createButton
2497
+ }
2163
2498
  </div>
2164
2499
 
2165
2500
  </div>);
@@ -2193,6 +2528,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2193
2528
  <div className={`creatives-templates-list ${this.props.isFullMode ? 'full-mode' : 'library-mode'}`}>
2194
2529
  <input type="file" id="filename" style={{ display: 'none'}} accept=".zip, .html, .htm" onChange={(event) => this.handleFileUpload(event, {files: event.target.files})} />
2195
2530
 
2531
+ {
2532
+ channel.toLowerCase() === WHATSAPP_LOWERCASE && showWhatsappCountWarning ? (
2533
+ <CapAlert message={whatsappCountExceedText} type="info" />
2534
+ ) : null
2535
+ }
2196
2536
 
2197
2537
  <CapRow>
2198
2538
  <Pagination onPageChange={templates.length ? this.onPaginationChange : () => {}} paginationSelector="pagination-container">
@@ -2200,6 +2540,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2200
2540
  </Pagination>
2201
2541
  {(((this.state.selectedAccount !== '' && !isEmpty(this.props.Templates.selectedWeChatAccount) && this.state.channel.toLowerCase() === 'wechat') || this.state.channel.toLowerCase() !== 'wechat') && isEmpty(this.props.TemplatesList)) && !this.props.Templates.getAllTemplatesInProgress && !isEmpty(this.state.searchText) &&
2202
2542
  <div style={{ height: '65vh', textAlign: 'center', lineHeight: '65vh'}}>
2543
+ {
2544
+ channel.toLowerCase() === WHATSAPP_LOWERCASE ? (
2545
+ <CapAlert message={whatsappCountExceedText} type="info" />
2546
+ ) : null
2547
+ }
2203
2548
  <CapHeading type="h6">{this.props.intl.formatMessage(messages.noTemplatesFound)}</CapHeading>
2204
2549
  </div>}
2205
2550
  </CapRow>
@@ -2277,6 +2622,7 @@ Templates.propTypes = {
2277
2622
  router: PropTypes.object,
2278
2623
  viberActions: PropTypes.object,
2279
2624
  facebookActions: PropTypes.object,
2625
+ whatsappActions: PropTypes.object,
2280
2626
  smsRegister: PropTypes.any,
2281
2627
  };
2282
2628
 
@@ -2305,6 +2651,7 @@ function mapDispatchToProps(dispatch) {
2305
2651
  lineActions: bindActionCreators(lineActions, dispatch),
2306
2652
  viberActions: bindActionCreators(viberActions, dispatch),
2307
2653
  facebookActions: bindActionCreators(facebookActions, dispatch),
2654
+ whatsappActions: bindActionCreators(whatsappActions, dispatch),
2308
2655
  };
2309
2656
  }
2310
2657