@capillarytech/creatives-library 7.12.41 → 7.12.43

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.
@@ -10,6 +10,7 @@ import CapDrawer from '@capillarytech/cap-ui-library/CapDrawer';
10
10
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
11
11
  import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
12
12
  import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
13
+ import isEmpty from 'lodash/isEmpty';
13
14
  import LineDrawer from '../Drawer';
14
15
  import style from './style';
15
16
  import { CAP_G06, FONT_COLOR_01, FONT_COLOR_02, CAP_SPACE_08, CAP_WHITE, CAP_SPACE_24, CAP_SPACE_04 } from '@capillarytech/cap-ui-library/styled/variables';
@@ -52,8 +53,9 @@ export const LineImageCarousel = (props) => {
52
53
  const defaultCarouselContent = {
53
54
  messageTitle: '',
54
55
  originalContentUrl: '',
56
+ aspectRatio: '',
55
57
  previewImageUrl: '',
56
- type: 'image_carousel',
58
+ type: 'carousel',
57
59
  };
58
60
 
59
61
  const [carouselImages, updateCarouselImage] = useState([defaultCarouselContent]);
@@ -77,31 +79,66 @@ export const LineImageCarousel = (props) => {
77
79
  updateTitle(altText);
78
80
  }
79
81
  if (template) {
80
- const { columns } = template;
81
- if (columns && columns.length) {
82
- const validCarouselImages = [];
83
- columns.forEach((column = {}, index) => {
84
- const {
85
- action: {
86
- label,
87
- text,
88
- type,
89
- uri,
90
- } = {},
91
- imageUrl,
92
- } = column;
93
-
94
- validCarouselImages.push({
95
- activeIndex: index,
96
- originalContentUrl: imageUrl,
97
- selectedActionType: type ? (type === MESSAGE_ACTION_TYPE ? TEXT_ACTION_TYPE : URL_ACTION_TYPE) : '',
98
- actionContent: text || uri,
99
- actionLabel: label,
100
- type: 'image_carousel',
101
- });
102
- })
103
- updateCarouselImage(validCarouselImages);
82
+ const validCarouselImages = [];
83
+ if (template.type != undefined && template.type === "carousel") {
84
+ const { contents = [] } = template;
85
+ if(contents && contents.length) {
86
+ contents.forEach((content = {}, index1) => {
87
+ const {
88
+ footer: {
89
+ contents: [{
90
+ action: {
91
+ text = '',
92
+ label = '',
93
+ type: buttonType = '',
94
+ uri,
95
+ } = {},
96
+ }] = [],
97
+ } = {},
98
+ hero: {
99
+ url = '',
100
+ aspectRatio = '',
101
+ } = {},
102
+ } = content || {};
103
+ validCarouselImages.push({
104
+ activeIndex: index1,
105
+ originalContentUrl: url,
106
+ aspectRatio,
107
+ selectedActionType: buttonType ? (buttonType === MESSAGE_ACTION_TYPE ? TEXT_ACTION_TYPE : URL_ACTION_TYPE) : '',
108
+ actionContent: text || uri,
109
+ actionLabel: label,
110
+ type: 'carousel',
111
+ });
112
+ })
113
+ }
114
+ } else {
115
+ const { columns } = template;
116
+ if (columns && columns.length) {
117
+ columns.forEach((column = {}, index) => {
118
+ const {
119
+ action: {
120
+ label,
121
+ text,
122
+ type,
123
+ uri,
124
+ } = {},
125
+ imageUrl,
126
+ aspectRatio,
127
+ } = column;
128
+
129
+ validCarouselImages.push({
130
+ activeIndex: index,
131
+ originalContentUrl: imageUrl,
132
+ selectedActionType: type ? (type === MESSAGE_ACTION_TYPE ? TEXT_ACTION_TYPE : URL_ACTION_TYPE) : '',
133
+ actionContent: text || uri,
134
+ actionLabel: label,
135
+ aspectRatio,
136
+ type: 'carousel',
137
+ });
138
+ })
139
+ }
104
140
  }
141
+ updateCarouselImage(validCarouselImages);
105
142
  }
106
143
  }
107
144
  }, [content]);
@@ -111,8 +148,9 @@ export const LineImageCarousel = (props) => {
111
148
  const existingTitle = inheritedTitle !== undefined ? inheritedTitle : altText;
112
149
  let isErrorIndex;
113
150
  const columns = [];
114
- (newCarouselImages || carouselImages).forEach(({
151
+ (newCarouselImages || []).forEach(({
115
152
  originalContentUrl,
153
+ aspectRatio = '1:1',
116
154
  selectedActionType,
117
155
  actionLabel,
118
156
  actionContent,
@@ -125,6 +163,7 @@ export const LineImageCarousel = (props) => {
125
163
  : ((!originalContentUrl || !selectedActionType || actionContentErrorMessage || actionLabelErrorMessage || !actionContent) && activeIndex);
126
164
  columns.push({
127
165
  imageUrl: originalContentUrl,
166
+ aspectRatio,
128
167
  action: {
129
168
  type: selectedActionType ? (selectedActionType === TEXT_ACTION_TYPE ? MESSAGE_ACTION_TYPE : URL_ACTION_TYPE) : '',
130
169
  label: actionLabel,
@@ -344,19 +383,32 @@ export const LineImageCarousel = (props) => {
344
383
  const {
345
384
  content: {
346
385
  messages: [{
386
+ contents: imageCarouselContents,
347
387
  template: imageCarouselTemplate,
348
388
  }] = [{}],
349
389
  } = {},
350
390
  } = template.versions.base;
351
- templateData.url = (imageCarouselTemplate ? get(imageCarouselTemplate, 'columns.0.imageUrl', '') : '');
352
- templateData.title = template.name;
353
- templateData.key = template.name;
391
+ let isNewLineFormat = false;
392
+ if(!imageCarouselTemplate && imageCarouselContents) {
393
+ isNewLineFormat = true;
394
+ }
395
+ if(isNewLineFormat) {
396
+ templateData.url = (imageCarouselContents ? get(imageCarouselContents, 'contents.0.hero.url', '') : '');
397
+ templateData.title = template.name;
398
+ templateData.key = template.name;
399
+
400
+ } else {
401
+ templateData.url = (imageCarouselTemplate ? get(imageCarouselTemplate, 'columns.0.imageUrl', '') : '');
402
+ templateData.title = template.name;
403
+ templateData.key = template.name;
404
+ }
405
+
354
406
  templateData.hoverOption = (
355
407
  <CapButton onClick={() => onTemplateSelection(template)}>
356
408
  <FormattedMessage {...messages.select} />
357
409
  </CapButton>
358
410
  );
359
- if (imageCarouselTemplate) {
411
+ if (imageCarouselTemplate || imageCarouselContents) {
360
412
  cardDataList.push(templateData);
361
413
  }
362
414
  });
@@ -383,32 +435,75 @@ export const LineImageCarousel = (props) => {
383
435
 
384
436
  const onTemplateSelection = (templateData) => {
385
437
  const template = get(templateData, 'versions.base.content.messages[0].template', {});
438
+ const contents1 = get(templateData, 'versions.base.content.messages[0].contents', {});
386
439
  const altText = get(templateData, 'versions.base.content.messages[0].altText', '');
387
- const { columns } = template;
388
- if (columns && columns.length) {
389
- const validCarouselImages = [];
390
- columns.forEach((column = {}, index) => {
391
- const {
392
- action: {
393
- label,
394
- text,
395
- type,
396
- uri,
397
- } = {},
398
- imageUrl,
399
- } = column;
400
-
401
- validCarouselImages.push({
402
- activeIndex: index,
403
- originalContentUrl: imageUrl,
404
- selectedActionType: type ? (type === MESSAGE_ACTION_TYPE ? TEXT_ACTION_TYPE : URL_ACTION_TYPE) : '',
405
- actionContent: text || uri,
406
- actionLabel: label,
407
- type: 'image_carousel',
408
- });
409
- })
410
- updateCarouselImage(validCarouselImages);
440
+ let isNewLineFormat = false;
441
+ const validCarouselImages = [];
442
+
443
+ if (!isEmpty(contents1) && isEmpty(template)) {
444
+ isNewLineFormat = true;
411
445
  }
446
+ if (isNewLineFormat) {
447
+ const { contents = [] } = contents1;
448
+
449
+ if(contents && contents.length) {
450
+ contents.forEach((content = {}, index1) => {
451
+ const {
452
+ footer: {
453
+ contents: [{
454
+ action: {
455
+ text = '',
456
+ label = '',
457
+ type: buttonType = '',
458
+ uri,
459
+ } = {},
460
+ }] = [],
461
+ } = {},
462
+ hero: {
463
+ url = '',
464
+ aspectRatio = '',
465
+ } = {},
466
+ } = content || {};
467
+ validCarouselImages.push({
468
+ activeIndex: index1,
469
+ originalContentUrl: url,
470
+ aspectRatio,
471
+ selectedActionType: buttonType ? (buttonType === MESSAGE_ACTION_TYPE ? TEXT_ACTION_TYPE : URL_ACTION_TYPE) : '',
472
+ actionContent: text || uri,
473
+ actionLabel: label,
474
+ type: 'carousel',
475
+ });
476
+ })
477
+ }
478
+
479
+ } else {
480
+ const { columns } = template;
481
+ if (columns && columns.length) {
482
+ columns.forEach((column = {}, index) => {
483
+ const {
484
+ action: {
485
+ label,
486
+ text,
487
+ type,
488
+ uri,
489
+ } = {},
490
+ imageUrl,
491
+ } = column;
492
+
493
+ validCarouselImages.push({
494
+ activeIndex: index,
495
+ originalContentUrl: imageUrl,
496
+ selectedActionType: type ? (type === MESSAGE_ACTION_TYPE ? TEXT_ACTION_TYPE : URL_ACTION_TYPE) : '',
497
+ actionContent: text || uri,
498
+ actionLabel: label,
499
+ type: 'carousel',
500
+ });
501
+ })
502
+
503
+ }
504
+ }
505
+ updateCarouselImage(validCarouselImages);
506
+
412
507
  updateSelectedTemplate(template);
413
508
  updateTitle(altText);
414
509
  updateTemplateDrawerRequirement(false);
@@ -37,7 +37,7 @@ export default defineMessages({
37
37
  },
38
38
  "imageDimenstionDescription": {
39
39
  id: `${scope}.imageDimenstionDescription`,
40
- defaultMessage: 'Format: JPEG, PNG Aspect Ratio: 1:1 Dimensions: 1024px x 1024px Size: 1 MB',
40
+ defaultMessage: 'Format: JPEG, PNG Dimensions: 1024px x 1024px Size: 1 MB',
41
41
  },
42
42
  "imageReUpload": {
43
43
  id: `${scope}.imageReUpload`,
@@ -115,7 +115,13 @@ export const LineContainer = ({
115
115
  const selectedTemplate = get(Line, 'templateDetails.selectedTemplate');
116
116
  const assetType = get(Line, 'templateDetails.assetType');
117
117
  const actionLinks = get(Line, 'templateDetails.actionLinks');
118
- const template = get(Line, `templateDetails.versions.base.content.messages.0.template`);
118
+ const templateType = get(Line, `templateDetails.versions.base.content.messages.0.type`);
119
+ let template = "";
120
+ if (templateType === "flex") {
121
+ template = get(Line, `templateDetails.versions.base.content.messages.0.contents`);
122
+ } else {
123
+ template = get(Line, `templateDetails.versions.base.content.messages.0.template`);
124
+ }
119
125
  const video = get(Line, `templateDetails.versions.base.content.messages.0.video`);
120
126
  const videoName = get(Line, `templateDetails.versions.base.content.messages.0.videoName`);
121
127
  const videoDuration = get(Line, `templateDetails.versions.base.content.messages.0.videoDuration`);
@@ -202,6 +208,7 @@ export const LineContainer = ({
202
208
  lineContent.push(obj);
203
209
  });
204
210
  updateLineWrapperContent(lineContent);
211
+
205
212
  }
206
213
  }, [templateData]);
207
214
 
@@ -294,7 +301,6 @@ export const LineContainer = ({
294
301
  if (supportedTags) {
295
302
  tags = supportedTags;
296
303
  }
297
-
298
304
  return (
299
305
  <LineWrapper
300
306
  id={`${lineWrapperContent.type}__${index}`}
@@ -387,7 +393,7 @@ export const LineContainer = ({
387
393
  videoWidth,
388
394
  } = {}] = lineWrapperContents;
389
395
  let messageData = { type };
390
- messageData[messageType] = messageContent;
396
+ if (messageType && messageContent) messageData[messageType] = messageContent;
391
397
  if (previewImageUrl) {
392
398
  messageData.previewImageUrl = previewImageUrl;
393
399
  }
@@ -407,9 +413,36 @@ export const LineContainer = ({
407
413
  if (baseSize) {
408
414
  messageData.baseSize = baseSize;
409
415
  }
416
+
410
417
  if (template) {
411
- messageData.template = template;
418
+ messageData.type = "flex";
412
419
  messageData.altText = altText;
420
+ messageData.contents = {};
421
+ const imageContents = (template?.columns || []).map((item) => {
422
+ return {
423
+ type: 'bubble',
424
+ hero: {
425
+ type: 'image',
426
+ url: item.imageUrl,
427
+ size: 'full',
428
+ aspectRatio: item.aspectRatio,
429
+ aspectMode: 'cover',
430
+ },
431
+ footer: {
432
+ type: 'box',
433
+ layout: 'vertical',
434
+ spacing: 'xs',
435
+ contents: [{
436
+ type: 'button',
437
+ style: 'link',
438
+ height: 'sm',
439
+ action: item.action,
440
+ }],
441
+ },
442
+ };
443
+ });
444
+ messageData.contents.contents = imageContents;
445
+ messageData.contents.type = "carousel";
413
446
  }
414
447
  if (videoSrc) {
415
448
  delete messageData.previewImageUrl;
@@ -513,7 +546,33 @@ export const LineContainer = ({
513
546
  messageData.actionLinks = actionLinks;
514
547
  }
515
548
  if (template) {
516
- messageData.template = template;
549
+ let contentsLocal = {};
550
+ const imageContents = (template?.columns || []).map((item) => {
551
+ return {
552
+ type: 'bubble',
553
+ hero: {
554
+ type: 'image',
555
+ url: item.imageUrl,
556
+ size: 'full',
557
+ aspectRatio: item.aspectRatio,
558
+ aspectMode: 'cover',
559
+ },
560
+ footer: {
561
+ type: 'box',
562
+ layout: 'vertical',
563
+ spacing: 'xs',
564
+ contents: [{
565
+ type: 'button',
566
+ style: 'link',
567
+ height: 'sm',
568
+ action: item.action,
569
+ }],
570
+ },
571
+ };
572
+ });
573
+ contentsLocal.contents = imageContents;
574
+ contentsLocal.type = "carousel";
575
+ messageData.template = contentsLocal;
517
576
  messageData.altText = altText;
518
577
  }
519
578
  if (videoSrc) {
@@ -595,7 +654,21 @@ export const LineContainer = ({
595
654
 
596
655
  const getPreviewSection = () => {
597
656
  const accountName = get(lineData, 'selectedLineAccount.name', '');
598
- const templateContent = lineWrapperContents.map(({ messageContent, type, previewImageUrl, selectedSticker, baseUrl, template, videoSrc, actionUrl }) => ({
657
+ const templateContent = lineWrapperContents.map(({ messageContent, type, previewImageUrl, selectedSticker, baseUrl, template, videoSrc, actionUrl }) => {
658
+ const isNewLineFormat = template && isEmpty(template.columns) && !isEmpty(template.contents);
659
+ let imageCarousel = [];
660
+ if (isNewLineFormat) {
661
+ imageCarousel = get(template, 'contents', [{}]).map(({
662
+ hero: { url = ''} = {},
663
+ footer: { contents: [{ action: { label = '' } = {} }] = [{}] } = {},
664
+ }) => ({
665
+ imageUrl: url,
666
+ actionLabel: label,
667
+ }));
668
+ } else {
669
+ imageCarousel = get(template, 'columns', [{}]).map(({ imageUrl, action: { label: actionLabel } = {} }) => ({ imageUrl, actionLabel }) );
670
+ }
671
+ return {
599
672
  messageContent,
600
673
  previewImageUrl,
601
674
  selectedSticker,
@@ -603,9 +676,9 @@ export const LineContainer = ({
603
676
  baseUrl,
604
677
  video: videoSrc,
605
678
  actionUrl,
606
- imageCarousel: get(template, 'columns', [{}]).map(({ imageUrl, action: { label: actionLabel } = {} }) => ({ imageUrl, actionLabel }) ),
607
- }));
608
-
679
+ imageCarousel,
680
+ };
681
+ });
609
682
  return (
610
683
  <TemplatePreview
611
684
  channel="LINE"
@@ -846,7 +846,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
846
846
  );
847
847
 
848
848
  getTemplateDataForGrid = ({templates, handlers, filterContent, channel, isLoading, loadingTip}) => {
849
- const currentChannel = channel.toUpperCase();
849
+ const currentChannel = channel.toUpperCase();
850
850
  const {channel: stateChannel} = this.state;
851
851
  const channelLowerCase = stateChannel.toLowerCase();
852
852
  let filteredTemplates = templates;
@@ -868,7 +868,6 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
868
868
  default:
869
869
  break;
870
870
  }
871
-
872
871
  const hoverButtonText =
873
872
  this.props.intl.formatMessage(
874
873
  this.props.isFullMode
@@ -979,48 +978,62 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
979
978
  const {
980
979
  content: {
981
980
  messages: [{
982
- text = '',
983
- originalContentUrl = '',
984
- stickerUrl = '',
985
- animatedStickerUrl = '',
986
- baseUrl = '',
987
- template: carouselTemplate,
988
- video: {
989
- originalContentUrl: originalVideoContentUrl,
981
+ type = '',
982
+ contents: {
983
+ contents = [],
990
984
  } = {},
991
- }] = [{}],
985
+ } = {}] = [],
992
986
  } = {},
993
987
  } = template.versions.base;
994
- templateData.content = text;
995
- templateData.url = originalContentUrl || animatedStickerUrl || stickerUrl || (baseUrl ? `${baseUrl}/1040` : '');
996
- if (originalVideoContentUrl) {
997
- templateData.content = (
998
- <video height="279">
999
- <source src={originalVideoContentUrl} type="video/mp4" />
1000
- </video>
1001
- );
1002
- }
1003
- if (carouselTemplate) {
1004
- const carouselContent = get(carouselTemplate, 'columns', [{}]).map(({ imageUrl, action: { label: actionLabel } = {} }) => ({ imageUrl, actionLabel }) );
1005
- templateData.content = (
1006
- <div style={{ display: 'flex'}}>
1007
- {
1008
- carouselContent.map(({
1009
- imageUrl,
1010
- actionLabel,
1011
- }) => (
1012
- <img
1013
- width={208}
1014
- height={279}
1015
- src={imageUrl}
1016
- alt={actionLabel}
1017
- key={`${imageUrl}_${actionLabel}`}
1018
- />
1019
- ))
1020
- }
1021
- </div>
1022
- );
1023
- templateData.className = 'carousel-img-section';
988
+ if (type === 'flex') {
989
+ templateData.url = contents[0]?.hero?.url;
990
+ } else {
991
+ const {
992
+ content: {
993
+ messages: [{
994
+ text = '',
995
+ originalContentUrl = '',
996
+ stickerUrl = '',
997
+ animatedStickerUrl = '',
998
+ baseUrl = '',
999
+ template: carouselTemplate,
1000
+ video: {
1001
+ originalContentUrl: originalVideoContentUrl,
1002
+ } = {},
1003
+ }] = [{}],
1004
+ } = {},
1005
+ } = template.versions.base;
1006
+ templateData.content = text;
1007
+ templateData.url = originalContentUrl || animatedStickerUrl || stickerUrl || (baseUrl ? `${baseUrl}/1040` : '');
1008
+ if (originalVideoContentUrl) {
1009
+ templateData.content = (
1010
+ <video height="279">
1011
+ <source src={originalVideoContentUrl} type="video/mp4" />
1012
+ </video>
1013
+ );
1014
+ }
1015
+ if (carouselTemplate) {
1016
+ const carouselContent = get(carouselTemplate, 'columns', [{}]).map(({ imageUrl, action: { label: actionLabel } = {} }) => ({ imageUrl, actionLabel }) );
1017
+ templateData.content = (
1018
+ <div style={{ display: 'flex'}}>
1019
+ {
1020
+ carouselContent.map(({
1021
+ imageUrl,
1022
+ actionLabel,
1023
+ }) => (
1024
+ <img
1025
+ width={208}
1026
+ height={279}
1027
+ src={imageUrl}
1028
+ alt={actionLabel}
1029
+ key={`${imageUrl}_${actionLabel}`}
1030
+ />
1031
+ ))
1032
+ }
1033
+ </div>
1034
+ );
1035
+ templateData.className = 'carousel-img-section';
1036
+ }
1024
1037
  }
1025
1038
  break;
1026
1039
  }
@@ -1069,6 +1082,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1069
1082
  }
1070
1083
  return templateData;
1071
1084
  }) : [];
1085
+
1072
1086
  const accountId = get(this.props, 'Templates.selectedWeChatAccount.uuid');
1073
1087
  const accounts = get(this.props, 'Templates.weCrmAccounts');
1074
1088
  const noWhatsappTemplates = this.isFullMode() && isEmpty(templates);
@@ -609,6 +609,7 @@ export const Whatsapp = (props) => {
609
609
  }
610
610
  />
611
611
  <CapRadioGroup
612
+ disabled={isEditFlow}
612
613
  options={buttonRadioOptions || []}
613
614
  value={buttonType}
614
615
  onChange={onChangeButtonType}
@@ -841,11 +842,24 @@ export const Whatsapp = (props) => {
841
842
  if (templateMessage.trim() === '' || templateMessageError) {
842
843
  return true;
843
844
  }
844
- if (buttonType === WHATSAPP_BUTTON_TYPES.CTA) {
845
- return !ctaData.every((i) => i.isSaved === true);
845
+ //cta
846
+ if (buttonType === WHATSAPP_BUTTON_TYPES.CTA) {
847
+ if (ctaData.length === 1 && ctaData[0].isSaved) {
848
+ //if button type is cta and 1st button is saved
849
+ return false;
850
+ } else if (
851
+ ctaData.length === 2 &&
852
+ ctaData[0].isSaved &&
853
+ ctaData[1].isSaved
854
+ ) {
855
+ //if button type is cta and 2nd button is saved
856
+ return false;
846
857
  }
847
- return false;
848
- };
858
+ //if button type is cta and there are no buttons saved
859
+ return true;
860
+ }
861
+ return false;
862
+ };
849
863
 
850
864
  const createModeContent = (
851
865
  <>
@@ -1160,6 +1174,13 @@ export const Whatsapp = (props) => {
1160
1174
 
1161
1175
  {renderMessageLength()}
1162
1176
  {isTagValidationError && tagValidationErrorMessage()}
1177
+ {computeTextLength() > TEMPLATE_MESSAGE_MAX_LENGTH ? (
1178
+ <CapError>
1179
+ {formatMessage(messages.templateMessageLengthError)}
1180
+ </CapError>
1181
+ ) : (
1182
+ ''
1183
+ )}
1163
1184
 
1164
1185
  <CapTooltip
1165
1186
  placement="bottom"
@@ -1202,7 +1223,8 @@ export const Whatsapp = (props) => {
1202
1223
 
1203
1224
  const isEditDoneDisabled = () =>
1204
1225
  isTagValidationError ||
1205
- Object.values(varMap).some((inputValue) => inputValue === '');
1226
+ Object.values(varMap).some((inputValue) => inputValue === '') ||
1227
+ computeTextLength() > TEMPLATE_MESSAGE_MAX_LENGTH;
1206
1228
  return (
1207
1229
  <CapSpin spinning={spin}>
1208
1230
  <CapRow className="cap-whatsapp-creatives">