@capillarytech/creatives-library 8.0.358 → 8.0.359-alpha.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.
Files changed (36) hide show
  1. package/index.html +0 -1
  2. package/package.json +1 -1
  3. package/utils/cdnTransformation.js +3 -75
  4. package/utils/tests/cdnTransformation.test.js +0 -127
  5. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +0 -16
  6. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +132 -14
  7. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +163 -54
  8. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +6 -52
  9. package/v2Components/CommonTestAndPreview/constants.js +0 -2
  10. package/v2Components/CommonTestAndPreview/index.js +231 -77
  11. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +0 -163
  12. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +364 -0
  13. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +0 -255
  14. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -2
  15. package/v2Components/CommonTestAndPreview/tests/index.test.js +0 -194
  16. package/v2Components/FormBuilder/index.js +52 -162
  17. package/v2Components/TestAndPreviewSlidebox/index.js +2 -2
  18. package/v2Containers/App/constants.js +0 -3
  19. package/v2Containers/CreativesContainer/index.js +24 -60
  20. package/v2Containers/Templates/_templates.scss +77 -0
  21. package/v2Containers/Templates/index.js +92 -82
  22. package/v2Containers/Templates/sagas.js +1 -6
  23. package/v2Containers/Templates/tests/sagas.test.js +6 -23
  24. package/v2Containers/Viber/constants.js +19 -0
  25. package/v2Containers/Viber/index.js +714 -47
  26. package/v2Containers/Viber/index.scss +148 -0
  27. package/v2Containers/Viber/messages.js +116 -0
  28. package/v2Containers/Viber/tests/index.test.js +80 -0
  29. package/v2Containers/WebPush/Create/index.js +8 -91
  30. package/v2Containers/WebPush/Create/index.scss +0 -7
  31. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +0 -169
  32. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +0 -522
  33. package/v2Containers/App/tests/constants.test.js +0 -61
  34. package/v2Containers/Templates/tests/webpush.test.js +0 -375
  35. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +0 -348
  36. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +0 -325
@@ -16,6 +16,9 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
16
16
  import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
17
17
  import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
18
18
  import CapRadioGroup from '@capillarytech/cap-ui-library/CapRadioGroup';
19
+ import CapTab from '@capillarytech/cap-ui-library/CapTab';
20
+ import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
21
+ import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
19
22
  import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
20
23
  import { GA } from '@capillarytech/cap-ui-utils';
21
24
  import * as globalActions from '../Cap/actions';
@@ -44,6 +47,18 @@ import {
44
47
  NONE,
45
48
  mediaRadioOptions,
46
49
  buttonRadioOptions,
50
+ AI_CONTENT_BOT_DISABLED,
51
+ VIBER_CAROUSEL_MAX_BUTTONS,
52
+ VIBER_CAROUSEL_MAX_CARDS,
53
+ VIBER_CAROUSEL_MIN_CARDS,
54
+ VIBER_CAROUSEL_CARD_TITLE_MIN_LENGTH,
55
+ VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH,
56
+ VIBER_CAROUSEL_FIRST_BUTTON_TITLE_MAX_LENGTH,
57
+ VIBER_CAROUSEL_SECOND_BUTTON_TITLE_MAX_LENGTH,
58
+ VIBER_CAROUSEL_BUTTON_URL_MAX_LENGTH,
59
+ VIBER_CAROUSEL_IMG_HEIGHT,
60
+ VIBER_CAROUSEL_IMG_WIDTH,
61
+ VIBER_CAROUSEL_IMG_SIZE,
47
62
  } from './constants';
48
63
  import withCreatives from '../../hoc/withCreatives';
49
64
  import {
@@ -63,6 +78,36 @@ import v2ViberReducer from './reducer';
63
78
 
64
79
 
65
80
  const { TextArea } = CapInput;
81
+ const STATIC_URL = 'STATIC_URL';
82
+ const DYNAMIC_URL = 'DYNAMIC_URL';
83
+ const CAROUSEL_URL_TYPE_OPTIONS = (formatMessage) => ([
84
+ { value: STATIC_URL, label: formatMessage(messages.carouselUrlTypeStatic) },
85
+ { value: DYNAMIC_URL, label: formatMessage(messages.carouselUrlTypeDynamic) },
86
+ ]);
87
+ let carouselCardIdSeed = 0;
88
+ const getNextCarouselCardId = () => {
89
+ const nextId = `viber-carousel-card-${carouselCardIdSeed}`;
90
+ carouselCardIdSeed += 1;
91
+ return nextId;
92
+ };
93
+ const createEmptyCarouselButton = () => ({
94
+ title: '',
95
+ action: '',
96
+ urlType: STATIC_URL,
97
+ isSaved: false,
98
+ hasAttemptedSave: false,
99
+ hasTouchedAction: false,
100
+ });
101
+ const createEmptyCarouselCard = () => ({
102
+ id: getNextCarouselCardId(),
103
+ text: '',
104
+ mediaUrl: '',
105
+ buttons: [createEmptyCarouselButton()],
106
+ });
107
+ const createDefaultCarouselCards = () => [
108
+ createEmptyCarouselCard(),
109
+ createEmptyCarouselCard(),
110
+ ];
66
111
 
67
112
  export const Viber = (props) => {
68
113
  const {
@@ -111,6 +156,9 @@ export const Viber = (props) => {
111
156
  viberVideoPreviewImg: '',
112
157
  duration: 0,
113
158
  });
159
+ const [carouselCards, setCarouselCards] = useState(() => createDefaultCarouselCards());
160
+ const [activeCarouselCardIndex, setActiveCarouselCardIndex] = useState(0);
161
+ const [showCarouselValidationErrors, setShowCarouselValidationErrors] = useState(false);
114
162
  // cta button
115
163
  const [buttonType, setButtonType] = useState(NONE);
116
164
  const [ctaData, setCtadata] = useState({});
@@ -148,18 +196,38 @@ export const Viber = (props) => {
148
196
  button = {},
149
197
  image = {},
150
198
  video = {},
199
+ cards = [],
200
+ type = "",
151
201
  } = editViberContent || {};
152
202
  const { text: ctaBtnText = "", url = "" } = button || {};
153
203
  updateTextMessageTitle(editMessageTitle);
154
204
  updateTextMessageContent(text || "");
155
205
  updateButtonText(ctaBtnText);
156
206
  updateButtonUrl(url);
157
- setIsCtaSaved(true);
207
+ setIsCtaSaved(!isEmpty(button));
158
208
  setButtonType(button?.text ? VIBER_BUTTON_TYPES.CTA : NONE);
159
- if (!isEmpty(button)) {
160
- setCtadata({ buttonText: button?.text, buttonURL: button?.url });
161
- }
162
- if (!isEmpty(image)) {
209
+ setCtadata(!isEmpty(button) ? { buttonText: button?.text, buttonURL: button?.url } : {});
210
+ if (type === VIBER_MEDIA_TYPES.CAROUSEL) {
211
+ const normalizedCards = (cards || []).map((card) => ({
212
+ id: card?.id || getNextCarouselCardId(),
213
+ text: card?.text || '',
214
+ mediaUrl: card?.mediaUrl || '',
215
+ buttons: ((card?.buttons || []).length ? card.buttons : [createEmptyCarouselButton()]).map((carouselButton) => ({
216
+ title: carouselButton?.title || '',
217
+ action: carouselButton?.action || '',
218
+ urlType: carouselButton?.urlType || STATIC_URL,
219
+ isSaved: Boolean(carouselButton?.title && carouselButton?.action),
220
+ hasAttemptedSave: Boolean(carouselButton?.hasAttemptedSave),
221
+ hasTouchedAction: Boolean(carouselButton?.hasTouchedAction),
222
+ })),
223
+ }));
224
+ const cardsToSet = normalizedCards.length
225
+ ? normalizedCards
226
+ : createDefaultCarouselCards();
227
+ setTemplateMediaType(VIBER_MEDIA_TYPES.CAROUSEL);
228
+ setCarouselCards(cardsToSet.slice(0, VIBER_CAROUSEL_MAX_CARDS));
229
+ setActiveCarouselCardIndex(0);
230
+ } else if (!isEmpty(image)) {
163
231
  setTemplateMediaType(VIBER_MEDIA_TYPES.IMAGE);
164
232
  updateImageSrc(image?.url);
165
233
  } else if (!isEmpty(video)) {
@@ -207,6 +275,17 @@ export const Viber = (props) => {
207
275
  updateButtonUrl(newUrl);
208
276
  onChangeButtonUrl({ target: { value: newUrl } });
209
277
  };
278
+ const onCarouselCardTagSelect = (cardIndex, data) => {
279
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
280
+ if (index !== cardIndex) {
281
+ return card;
282
+ }
283
+ return {
284
+ ...card,
285
+ text: `${card?.text || ''}{{${data}}}`,
286
+ };
287
+ }));
288
+ };
210
289
 
211
290
  const handleOnTagsContextChange = (data) => {
212
291
  const query = {
@@ -335,15 +414,130 @@ export const Viber = (props) => {
335
414
  // template media code start here
336
415
  const isMediaTypeImage = templateMediaType === VIBER_MEDIA_TYPES.IMAGE;
337
416
  const isMediaTypeVideo = templateMediaType === VIBER_MEDIA_TYPES.VIDEO;
417
+ const isMediaTypeCarousel = templateMediaType === VIBER_MEDIA_TYPES.CAROUSEL;
418
+ const getCarouselButtonTitleMaxLength = (buttonIndex) => (
419
+ buttonIndex === 0
420
+ ? VIBER_CAROUSEL_FIRST_BUTTON_TITLE_MAX_LENGTH
421
+ : VIBER_CAROUSEL_SECOND_BUTTON_TITLE_MAX_LENGTH
422
+ );
423
+ const renderLength = (len, max) => (
424
+ <CapHeading type="label1" className="viber-carousel-field-length-top">
425
+ {len || 0}
426
+ /
427
+ {max}
428
+ {' '}
429
+ <FormattedMessage {...messages.characters} />
430
+ </CapHeading>
431
+ );
432
+ const getCarouselCardTextError = (cardText = '', showEmptyError = true) => {
433
+ const trimmedCardText = (cardText || '').trim();
434
+ if (!trimmedCardText && showEmptyError) {
435
+ return formatMessage(messages.textCannotBeEmptyError);
436
+ }
437
+ if (!trimmedCardText) {
438
+ return false;
439
+ }
440
+ if (trimmedCardText?.length < VIBER_CAROUSEL_CARD_TITLE_MIN_LENGTH) {
441
+ return formatMessage(messages.carouselCardTitleMinLengthError);
442
+ }
443
+ if (cardText.length > VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH) {
444
+ return formatMessage(messages.carouselCardTitleMaxLengthError);
445
+ }
446
+ return false;
447
+ };
448
+ const getCarouselButtonTitleError = (button = {}, buttonIndex = 0, showEmptyError = true) => {
449
+ const title = button?.title || '';
450
+ const trimmedTitle = title.trim();
451
+ const maxLength = getCarouselButtonTitleMaxLength(buttonIndex);
452
+ if (!trimmedTitle && showEmptyError) {
453
+ return formatMessage(messages.textCannotBeEmptyError);
454
+ }
455
+ if (!trimmedTitle) {
456
+ return false;
457
+ }
458
+ if (title.length > maxLength) {
459
+ return buttonIndex === 0
460
+ ? formatMessage(messages.carouselFirstButtonTitleMaxLengthError)
461
+ : formatMessage(messages.carouselSecondButtonTitleMaxLengthError);
462
+ }
463
+ return false;
464
+ };
465
+ const getCarouselButtonActionError = (button = {}, showEmptyError = true) => {
466
+ const action = button?.action || '';
467
+ if (!action?.trim() && showEmptyError) {
468
+ return formatMessage(messages.urlCannotBeEmptyError);
469
+ }
470
+ if (!action?.trim()) {
471
+ return false;
472
+ }
473
+ if (action.length > VIBER_CAROUSEL_BUTTON_URL_MAX_LENGTH) {
474
+ return formatMessage(messages.carouselButtonUrlMaxLengthError);
475
+ }
476
+ if (!isUrl(action)) {
477
+ return formatMessage(messages.inValidUrliErrorMessage);
478
+ }
479
+ return false;
480
+ };
481
+ const hasInvalidCarouselCard = carouselCards.some(
482
+ (card) => Boolean(getCarouselCardTextError(card?.text)) || !isUrl(card?.mediaUrl || '')
483
+ );
484
+ const hasInvalidCarouselButton = carouselCards.some((card) => (card?.buttons || []).some(
485
+ (button, buttonIndex) => {
486
+ const hasAnyValue = Boolean(button?.title || button?.action);
487
+ if (buttonIndex !== 0 && !hasAnyValue) {
488
+ return false;
489
+ }
490
+ return Boolean(getCarouselButtonTitleError(button, buttonIndex))
491
+ || Boolean(getCarouselButtonActionError(button))
492
+ || !button?.isSaved;
493
+ }
494
+ ));
495
+ const isCarouselCardCountInvalid = carouselCards.length < VIBER_CAROUSEL_MIN_CARDS
496
+ || carouselCards.length > VIBER_CAROUSEL_MAX_CARDS;
497
+ const isCarouselButtonComplete = (button, buttonIndex) => {
498
+ const hasAnyValue = Boolean(button?.title || button?.action);
499
+ if (buttonIndex !== 0 && !hasAnyValue) {
500
+ return true;
501
+ }
502
+ return !getCarouselButtonTitleError(button, buttonIndex)
503
+ && !getCarouselButtonActionError(button)
504
+ && Boolean(button?.isSaved);
505
+ };
506
+ const isCarouselCardComplete = (card = {}) => (
507
+ !getCarouselCardTextError(card?.text)
508
+ && isUrl(card?.mediaUrl || '')
509
+ && (card?.buttons || []).every((button, buttonIndex) => isCarouselButtonComplete(button, buttonIndex))
510
+ );
511
+ const canAccessCarouselCardAtIndex = (targetIndex) => (
512
+ carouselCards.slice(0, targetIndex).every((card) => isCarouselCardComplete(card))
513
+ );
514
+ const isCarouselTabDisabled = (cardIndex) => (
515
+ cardIndex > 0 && !canAccessCarouselCardAtIndex(cardIndex)
516
+ );
338
517
 
339
518
  const onTemplateMediaTypeChange = ({ target: { value } }) => {
340
519
  setTemplateMediaType(value);
520
+ if (value === VIBER_MEDIA_TYPES.CAROUSEL && carouselCards.length === 0) {
521
+ setCarouselCards(createDefaultCarouselCards());
522
+ setActiveCarouselCardIndex(0);
523
+ }
524
+ if ([VIBER_MEDIA_TYPES.VIDEO, VIBER_MEDIA_TYPES.CAROUSEL].includes(value)) {
525
+ setButtonType(NONE);
526
+ setCtadata({});
527
+ updateButtonText('');
528
+ updateButtonUrl('');
529
+ setIsCtaSaved(false);
530
+ }
341
531
  };
342
532
 
343
533
  const uploadViberAsset = (file, type, fileParams) => {
344
534
  actions.uploadViberAsset(file, type, fileParams, 0);
345
535
  };
346
536
 
537
+ const uploadViberAssetByIndex = (file, type, fileParams, templateType = 0) => {
538
+ actions.uploadViberAsset(file, type, fileParams, templateType);
539
+ };
540
+
347
541
  const updateOnViberImageReUpload = useCallback(() => {
348
542
  setImageSrc("");
349
543
  }, [imageSrc]);
@@ -431,6 +625,412 @@ export const Viber = (props) => {
431
625
  );
432
626
  // template media code end here
433
627
 
628
+ const onCarouselCardChange = (cardIndex, key, value) => {
629
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
630
+ index === cardIndex ? { ...card, [key]: value } : card
631
+ )));
632
+ };
633
+
634
+ const addCarouselCard = () => {
635
+ if (carouselCards.length >= VIBER_CAROUSEL_MAX_CARDS) {
636
+ return;
637
+ }
638
+ if (!isCarouselCardComplete(carouselCards[carouselCards.length - 1])) {
639
+ return;
640
+ }
641
+ setCarouselCards((prevCards) => {
642
+ const updatedCards = [...prevCards, createEmptyCarouselCard()];
643
+ setActiveCarouselCardIndex(updatedCards.length - 1);
644
+ return updatedCards;
645
+ });
646
+ };
647
+
648
+ const removeCarouselCard = (cardIndex) => {
649
+ if (carouselCards.length <= VIBER_CAROUSEL_MIN_CARDS) {
650
+ return;
651
+ }
652
+ setCarouselCards((prevCards) => {
653
+ const updatedCards = prevCards.filter((_, index) => index !== cardIndex);
654
+ if (activeCarouselCardIndex >= updatedCards.length) {
655
+ setActiveCarouselCardIndex(updatedCards.length - 1);
656
+ } else if (activeCarouselCardIndex > cardIndex) {
657
+ setActiveCarouselCardIndex(activeCarouselCardIndex - 1);
658
+ }
659
+ return updatedCards;
660
+ });
661
+ };
662
+
663
+ const onCarouselTabChange = (cardIndex) => {
664
+ const targetIndex = Number(cardIndex);
665
+ if (Number.isNaN(targetIndex) || targetIndex < 0 || targetIndex >= carouselCards.length) {
666
+ return;
667
+ }
668
+ if (isCarouselTabDisabled(targetIndex)) {
669
+ return;
670
+ }
671
+ setActiveCarouselCardIndex(targetIndex);
672
+ };
673
+
674
+ const onCarouselButtonChange = (cardIndex, buttonIndex, key, value) => {
675
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
676
+ if (index !== cardIndex) {
677
+ return card;
678
+ }
679
+ return {
680
+ ...card,
681
+ buttons: (card?.buttons || []).map((button, idx) => (
682
+ idx === buttonIndex
683
+ ? {
684
+ ...button,
685
+ [key]: value,
686
+ isSaved: false,
687
+ ...(key === 'action' && value?.trim() ? { hasTouchedAction: true } : {}),
688
+ }
689
+ : button
690
+ )),
691
+ };
692
+ }));
693
+ };
694
+
695
+ const markCarouselButtonActionTouched = (cardIndex, buttonIndex) => {
696
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
697
+ if (index !== cardIndex) {
698
+ return card;
699
+ }
700
+ return {
701
+ ...card,
702
+ buttons: (card?.buttons || []).map((button, idx) => (
703
+ idx === buttonIndex ? { ...button, hasTouchedAction: true } : button
704
+ )),
705
+ };
706
+ }));
707
+ };
708
+
709
+ const addCarouselButton = (cardIndex) => {
710
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
711
+ if (index !== cardIndex || (card?.buttons || []).length >= VIBER_CAROUSEL_MAX_BUTTONS) {
712
+ return card;
713
+ }
714
+ return {
715
+ ...card,
716
+ buttons: [...(card?.buttons || []), createEmptyCarouselButton()],
717
+ };
718
+ }));
719
+ };
720
+
721
+ const removeCarouselButton = (cardIndex, buttonIndex) => {
722
+ if (buttonIndex === 0) {
723
+ return;
724
+ }
725
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
726
+ if (index !== cardIndex) {
727
+ return card;
728
+ }
729
+ return {
730
+ ...card,
731
+ buttons: (card?.buttons || []).filter((_, idx) => idx !== buttonIndex),
732
+ };
733
+ }));
734
+ };
735
+
736
+ const saveCarouselButton = (cardIndex, buttonIndex) => {
737
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
738
+ if (index !== cardIndex) {
739
+ return card;
740
+ }
741
+ return {
742
+ ...card,
743
+ buttons: (card?.buttons || []).map((button, idx) => {
744
+ if (idx !== buttonIndex) {
745
+ return button;
746
+ }
747
+ const nextButtonState = {
748
+ ...button,
749
+ hasAttemptedSave: true,
750
+ };
751
+ if (getCarouselButtonTitleError(nextButtonState, buttonIndex, true) || getCarouselButtonActionError(nextButtonState)) {
752
+ return nextButtonState;
753
+ }
754
+ return {
755
+ ...nextButtonState,
756
+ isSaved: true,
757
+ };
758
+ }),
759
+ };
760
+ }));
761
+ };
762
+
763
+ const editCarouselButton = (cardIndex, buttonIndex) => {
764
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
765
+ if (index !== cardIndex) {
766
+ return card;
767
+ }
768
+ return {
769
+ ...card,
770
+ buttons: (card?.buttons || []).map((button, idx) => (
771
+ idx === buttonIndex ? { ...button, isSaved: false } : button
772
+ )),
773
+ };
774
+ }));
775
+ };
776
+
777
+ const updateCarouselImageSrc = useCallback((cardIndex, url) => {
778
+ const transformedUrl = getCdnUrl({ url, channelName: 'VIBER' });
779
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
780
+ index === cardIndex ? { ...card, mediaUrl: transformedUrl } : card
781
+ )));
782
+ actions.clearViberAsset(cardIndex);
783
+ }, []);
784
+
785
+ const updateOnCarouselImageReUpload = useCallback((cardIndex) => {
786
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
787
+ index === cardIndex ? { ...card, mediaUrl: '' } : card
788
+ )));
789
+ actions.clearViberAsset(cardIndex);
790
+ }, []);
791
+
792
+ const getCarouselTabPanes = () => (
793
+ carouselCards.map((card, cardIndex) => {
794
+ const isTabDisabled = isCarouselTabDisabled(cardIndex);
795
+ return ({
796
+ key: `${cardIndex}`,
797
+ tab: (
798
+ <span className={isTabDisabled ? 'viber-carousel-tab-label-disabled' : ''}>
799
+ {cardIndex + 1}
800
+ </span>
801
+ ),
802
+ disabled: isTabDisabled,
803
+ content: (
804
+ <div className="viber-carousel-card">
805
+ <CapRow type="flex" justify="space-between" align="middle">
806
+ <CapHeading type="h5">
807
+ {formatMessage(messages.carouselCardHeading, { index: cardIndex + 1 })}
808
+ </CapHeading>
809
+ <CapButton
810
+ type="flat"
811
+ className="viber-carousel-delete-icon-btn"
812
+ disabled={carouselCards.length <= VIBER_CAROUSEL_MIN_CARDS}
813
+ onClick={() => removeCarouselCard(cardIndex)}
814
+ >
815
+ <CapIcon type="delete" size="s" />
816
+ </CapButton>
817
+ </CapRow>
818
+ <CapImageUpload
819
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX_VIBER}
820
+ imgSize={VIBER_CAROUSEL_IMG_SIZE}
821
+ imgWidth={VIBER_CAROUSEL_IMG_WIDTH}
822
+ imgHeight={VIBER_CAROUSEL_IMG_HEIGHT}
823
+ uploadAsset={uploadViberAssetByIndex}
824
+ isFullMode={isFullMode}
825
+ imageSrc={card?.mediaUrl || ''}
826
+ updateImageSrc={(url) => updateCarouselImageSrc(cardIndex, url)}
827
+ updateOnReUpload={() => updateOnCarouselImageReUpload(cardIndex)}
828
+ index={cardIndex}
829
+ className="cap-custom-image-upload"
830
+ key={`viber-carousel-image-upload-${card?.id}`}
831
+ imageData={viber}
832
+ channel={VIBER}
833
+ />
834
+ {showCarouselValidationErrors && !isUrl(card?.mediaUrl || '') && (
835
+ <CapLabel type="label3" className="viber-carousel-image-recommendation">
836
+ Supported image types are .jpg, .jpeg, .png. Max size: 10 MB. Recommended resolution: 696 px x 600 px.
837
+ </CapLabel>
838
+ )}
839
+ <CapRow className="viber-carousel-card-title-header" type="flex" justify="space-between">
840
+ <CapHeading type="h5">
841
+ {formatMessage(messages.carouselCardTextLabel)}
842
+ </CapHeading>
843
+ <TagList
844
+ key={`viber_carousel_card_tags_${cardIndex}`}
845
+ className="tag-list-viber"
846
+ moduleFilterEnabled={location?.query?.type !== "embedded"}
847
+ label={formatMessage(messages.addLabels)}
848
+ onTagSelect={(data) => onCarouselCardTagSelect(cardIndex, data)}
849
+ onContextChange={handleOnTagsContextChange}
850
+ location={location}
851
+ tags={tags}
852
+ injectedTags={injectedTags || {}}
853
+ id={`viber_carousel_card_tags_${cardIndex}`}
854
+ userLocale={localStorage.getItem("jlocale") || "en"}
855
+ selectedOfferDetails={selectedOfferDetails}
856
+ eventContextTags={eventContextTags}
857
+ />
858
+ </CapRow>
859
+ <CapInput
860
+ value={card?.text || ''}
861
+ onChange={({ target: { value } }) => onCarouselCardChange(cardIndex, 'text', value)}
862
+ placeholder={formatMessage(messages.carouselCardTextPlaceholder)}
863
+ errorMessage={getCarouselCardTextError(card?.text, showCarouselValidationErrors)}
864
+ />
865
+ {renderLength((card?.text || '').length, VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH)}
866
+ <CapHeading type="h5" className="viber-carousel-button-label">
867
+ {formatMessage(messages.btnLabel)}
868
+ </CapHeading>
869
+ {(card?.buttons || []).map((button, buttonIndex) => (
870
+ // eslint-disable-next-line react/no-array-index-key
871
+ <div className="viber-carousel-button" key={`carousel-button-${card?.id}-${buttonIndex}`}>
872
+ {button?.isSaved ? (
873
+ <CapRow className="viber-carousel-saved-button" align="middle" type="flex">
874
+ <CapIcon size="s" type="six-dots" className="viber-carousel-saved-button-icon" />
875
+ <CapIcon size="s" type="reply" className="viber-carousel-saved-button-icon" />
876
+ <CapLabel type="label4" className="viber-carousel-saved-button-text">
877
+ {button?.title}
878
+ </CapLabel>
879
+ <CapColumn className="button-edit-icon" onClick={() => editCarouselButton(cardIndex, buttonIndex)}>
880
+ <CapIcon type="edit" size="s" />
881
+ </CapColumn>
882
+ {buttonIndex > 0 && (
883
+ <CapButton
884
+ type="flat"
885
+ className="viber-carousel-delete-icon-btn"
886
+ onClick={() => removeCarouselButton(cardIndex, buttonIndex)}
887
+ >
888
+ <CapIcon type="delete" size="s" />
889
+ </CapButton>
890
+ )}
891
+ </CapRow>
892
+ ) : (
893
+ <div className="cta-section">
894
+ <CapRow
895
+ type="flex"
896
+ justify="space-between"
897
+ align="middle"
898
+ className="viber-carousel-button-title-header"
899
+ >
900
+ <CapHeading type="h5">
901
+ {formatMessage(messages.carouselButtonTitleLabel)}
902
+ </CapHeading>
903
+ </CapRow>
904
+ <CapInput
905
+ value={button?.title ?? ''}
906
+ onChange={({ target: { value } }) => onCarouselButtonChange(cardIndex, buttonIndex, 'title', value)}
907
+ placeholder={formatMessage(messages.carouselButtonTitlePlaceholder)}
908
+ errorMessage={getCarouselButtonTitleError(
909
+ button,
910
+ buttonIndex,
911
+ showCarouselValidationErrors || Boolean(button?.hasAttemptedSave),
912
+ )}
913
+ />
914
+ {renderLength((button?.title || '').length, getCarouselButtonTitleMaxLength(buttonIndex))}
915
+ <CapRow gutter={12}>
916
+ <CapColumn span={6}>
917
+ <CapHeading type="h4" className="cta-label">
918
+ {formatMessage(messages.carouselButtonUrlTypeLabel)}
919
+ </CapHeading>
920
+ <CapSelect
921
+ className="viber-carousel-url-type-select"
922
+ dropdownClassName="viber-carousel-url-type-dropdown"
923
+ dropdownMatchSelectWidth={false}
924
+ options={CAROUSEL_URL_TYPE_OPTIONS(formatMessage)}
925
+ value={button?.urlType || STATIC_URL}
926
+ onChange={(value) => onCarouselButtonChange(cardIndex, buttonIndex, 'urlType', value)}
927
+ />
928
+ </CapColumn>
929
+ <CapColumn span={18}>
930
+ <CapInput
931
+ label={formatMessage(messages.carouselButtonActionLabel)}
932
+ value={button?.action ?? ''}
933
+ onChange={({ target: { value } }) => onCarouselButtonChange(cardIndex, buttonIndex, 'action', value)}
934
+ onBlur={() => markCarouselButtonActionTouched(cardIndex, buttonIndex)}
935
+ placeholder={formatMessage(messages.carouselButtonActionPlaceholder)}
936
+ errorMessage={getCarouselButtonActionError(
937
+ button,
938
+ showCarouselValidationErrors
939
+ || Boolean(button?.hasAttemptedSave)
940
+ || Boolean(button?.hasTouchedAction),
941
+ )}
942
+ />
943
+ </CapColumn>
944
+ </CapRow>
945
+ <div className="cta-actions">
946
+ <CapButton
947
+ className="cta-btn-action"
948
+ onClick={() => saveCarouselButton(cardIndex, buttonIndex)}
949
+ >
950
+ {formatMessage(messages.save)}
951
+ </CapButton>
952
+ {buttonIndex > 0 && (
953
+ <CapButton
954
+ type="secondary"
955
+ onClick={() => removeCarouselButton(cardIndex, buttonIndex)}
956
+ >
957
+ {formatMessage(globalMessages.delete)}
958
+ </CapButton>
959
+ )}
960
+ </div>
961
+ </div>
962
+ )}
963
+ </div>
964
+ ))}
965
+ {(card?.buttons || []).length < VIBER_CAROUSEL_MAX_BUTTONS && (
966
+ <CapButton
967
+ type="flat"
968
+ className="viber-add-row-btn"
969
+ onClick={() => addCarouselButton(cardIndex)}
970
+ >
971
+ + Add Button
972
+ </CapButton>
973
+ )}
974
+ </div>
975
+ ),
976
+ });
977
+ })
978
+ );
979
+
980
+ const carouselTabOperations = (
981
+ <>
982
+ <CapDivider type="vertical" />
983
+ <CapButton
984
+ type="flat"
985
+ className="viber-carousel-tab-add-btn"
986
+ onClick={addCarouselCard}
987
+ disabled={
988
+ carouselCards.length >= VIBER_CAROUSEL_MAX_CARDS
989
+ || !isCarouselCardComplete(carouselCards[carouselCards.length - 1])
990
+ }
991
+ >
992
+ <CapIcon type="plus" />
993
+ </CapButton>
994
+ </>
995
+ );
996
+
997
+ const renderCarouselSection = () => (
998
+ <div className="viber-carousel-section">
999
+ <CapHeading type="h4" className="viber-render-heading">
1000
+ {formatMessage(messages.carouselCardsLabel)}
1001
+ </CapHeading>
1002
+ <CapRow className="viber-carousel-tab">
1003
+ <CapTab
1004
+ activeKey={`${activeCarouselCardIndex}`}
1005
+ tabBarExtraContent={carouselTabOperations}
1006
+ onChange={onCarouselTabChange}
1007
+ panes={getCarouselTabPanes()}
1008
+ />
1009
+ </CapRow>
1010
+ {isCarouselCardCountInvalid && (
1011
+ <CapLabel type="label3" className="viber-form-error">
1012
+ {formatMessage(messages.carouselCardsLimitError)}
1013
+ </CapLabel>
1014
+ )}
1015
+ {hasInvalidCarouselCard && (
1016
+ <CapLabel type="label3" className="viber-form-error">
1017
+ {formatMessage(messages.carouselCardError)}
1018
+ </CapLabel>
1019
+ )}
1020
+ {hasInvalidCarouselButton && (
1021
+ <CapLabel type="label3" className="viber-form-error">
1022
+ {formatMessage(messages.carouselButtonError)}
1023
+ </CapLabel>
1024
+ )}
1025
+ </div>
1026
+ );
1027
+
1028
+ const renderInteractiveSection = () => (
1029
+ <>
1030
+ {isMediaTypeCarousel && renderCarouselSection()}
1031
+ </>
1032
+ );
1033
+
434
1034
  // Button Code start here
435
1035
 
436
1036
  const isBtnTypeCta = buttonType === VIBER_BUTTON_TYPES.CTA;
@@ -454,6 +1054,11 @@ export const Viber = (props) => {
454
1054
  viberPreviewContent: {
455
1055
  ...(isMediaTypeImage && { imageURL: imageSrc }),
456
1056
  ...(isMediaTypeVideo && { videoParams: viberVideoSrcAndPreview }),
1057
+ ...(isMediaTypeCarousel && {
1058
+ cards: carouselCards,
1059
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1060
+ showCarouselEditorPreview: true,
1061
+ }),
457
1062
  buttonText,
458
1063
  messageContent,
459
1064
  },
@@ -477,6 +1082,11 @@ export const Viber = (props) => {
477
1082
  viberPreviewContent: {
478
1083
  ...(isMediaTypeImage && { imageURL: imageSrc }),
479
1084
  ...(isMediaTypeVideo && { videoParams: viberVideoSrcAndPreview }),
1085
+ ...(isMediaTypeCarousel && {
1086
+ cards: carouselCards,
1087
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1088
+ showCarouselEditorPreview: true,
1089
+ }),
480
1090
  buttonText: ctaData?.buttonText || buttonText,
481
1091
  messageContent,
482
1092
  },
@@ -499,8 +1109,19 @@ export const Viber = (props) => {
499
1109
  duration: viberVideoSrcAndPreview.duration,
500
1110
  },
501
1111
  }),
1112
+ ...(isMediaTypeCarousel && {
1113
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1114
+ cards: carouselCards.map((card) => ({
1115
+ text: card?.text ?? '',
1116
+ mediaUrl: card?.mediaUrl ?? '',
1117
+ buttons: (card?.buttons ?? []).map((button) => ({
1118
+ title: button?.title ?? '',
1119
+ action: button?.action ?? '',
1120
+ })),
1121
+ })),
1122
+ }),
502
1123
  // Add button if present (for payload)
503
- ...((ctaData?.buttonText || buttonText) && (ctaData?.buttonURL || buttonURL) && {
1124
+ ...(!isMediaTypeCarousel && (ctaData?.buttonText || buttonText) && (ctaData?.buttonURL || buttonURL) && {
504
1125
  button: {
505
1126
  text: ctaData?.buttonText || buttonText,
506
1127
  url: ctaData?.buttonURL || buttonURL,
@@ -518,7 +1139,20 @@ export const Viber = (props) => {
518
1139
  sender: viberData?.selectedViberAccount?.sender || 'test1',
519
1140
  };
520
1141
  return templateContent;
521
- }, [isMediaTypeImage, isMediaTypeVideo, imageSrc, viberVideoSrcAndPreview, ctaData, buttonText, buttonURL, messageContent, accountName, viberData]);
1142
+ }, [
1143
+ isMediaTypeImage,
1144
+ isMediaTypeVideo,
1145
+ isMediaTypeCarousel,
1146
+ imageSrc,
1147
+ viberVideoSrcAndPreview,
1148
+ carouselCards,
1149
+ ctaData,
1150
+ buttonText,
1151
+ buttonURL,
1152
+ messageContent,
1153
+ accountName,
1154
+ viberData,
1155
+ ]);
522
1156
 
523
1157
  // Handle Test and Preview button click
524
1158
  const handleTestAndPreview = useCallback(() => {
@@ -632,39 +1266,44 @@ export const Viber = (props) => {
632
1266
  setIsCtaSaved(false);
633
1267
  };
634
1268
 
635
- const renderButtonsSection = () => (
636
- <div className="button-section">
637
- <CapHeader
638
- className="viber-render-heading"
639
- title={(
640
- <CapRow type="flex">
641
- <CapHeading type="h4">
642
- {formatMessage(messages.btnLabel)}
643
- </CapHeading>
644
- <CapHeading className="viber-optional-label">
645
- {formatMessage(messages.optional)}
646
- </CapHeading>
647
- </CapRow>
1269
+ const renderButtonsSection = () => {
1270
+ if (isMediaTypeCarousel) {
1271
+ return null;
1272
+ }
1273
+ return (
1274
+ <div className="button-section">
1275
+ <CapHeader
1276
+ className="viber-render-heading"
1277
+ title={(
1278
+ <CapRow type="flex">
1279
+ <CapHeading type="h4">
1280
+ {formatMessage(messages.btnLabel)}
1281
+ </CapHeading>
1282
+ <CapHeading className="viber-optional-label">
1283
+ {formatMessage(messages.optional)}
1284
+ </CapHeading>
1285
+ </CapRow>
1286
+ )}
1287
+ description={
1288
+ <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
1289
+ }
1290
+ />
1291
+ {templateMediaType === VIBER_MEDIA_TYPES.VIDEO && (
1292
+ <CapLabel type="label3">
1293
+ {formatMessage(messages.videoButtonDisabled)}
1294
+ </CapLabel>
648
1295
  )}
649
- description={
650
- <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
651
- }
652
- />
653
- {templateMediaType === VIBER_MEDIA_TYPES.VIDEO && (
654
- <CapLabel type="label3">
655
- {formatMessage(messages.videoButtonDisabled)}
656
- </CapLabel>
657
- )}
658
- <CapRadioGroup
659
- options={buttonRadioOptions}
660
- value={buttonType}
661
- onChange={onChangeButtonType}
662
- disabled={templateMediaType === VIBER_MEDIA_TYPES.VIDEO}
663
- className="viber-btn-radio-group"
664
- />
665
- {isBtnTypeCta && ButtonViber}
666
- </div>
667
- );
1296
+ <CapRadioGroup
1297
+ options={buttonRadioOptions}
1298
+ value={buttonType}
1299
+ onChange={onChangeButtonType}
1300
+ disabled={templateMediaType === VIBER_MEDIA_TYPES.VIDEO}
1301
+ className="viber-btn-radio-group"
1302
+ />
1303
+ {isBtnTypeCta && ButtonViber}
1304
+ </div>
1305
+ );
1306
+ };
668
1307
  // Button Code End here
669
1308
 
670
1309
  // to generate payload for create and edit
@@ -683,8 +1322,19 @@ export const Viber = (props) => {
683
1322
  messageData.video.thumbnailUrl = viberVideoPreviewImg;
684
1323
  messageData.video.duration = duration; // integer value in seconds
685
1324
  }
1325
+ if (isMediaTypeCarousel) {
1326
+ messageData.type = VIBER_MEDIA_TYPES.CAROUSEL;
1327
+ messageData.cards = carouselCards.map((card) => ({
1328
+ text: card?.text ?? '',
1329
+ mediaUrl: card?.mediaUrl ?? '',
1330
+ buttons: (card?.buttons ?? []).map((button) => ({
1331
+ title: button?.title ?? '',
1332
+ action: button?.action ?? '',
1333
+ })),
1334
+ }));
1335
+ }
686
1336
 
687
- if (!isEmpty(ctaData)) {
1337
+ if (!isMediaTypeCarousel && !isEmpty(ctaData)) {
688
1338
  messageData.button = {};
689
1339
  messageData.button.text = ctaData?.buttonText;
690
1340
  messageData.button.url = ctaData?.buttonURL;
@@ -693,13 +1343,15 @@ export const Viber = (props) => {
693
1343
  versions: {
694
1344
  base: {
695
1345
  content: {
696
- destinations: [
697
- {
698
- to: {
699
- phoneNumber: "{{viber_user_id}}",
1346
+ ...(isMediaTypeCarousel && {
1347
+ destinations: [
1348
+ {
1349
+ to: {
1350
+ phoneNumber: "{{viber_user_id}}",
1351
+ },
700
1352
  },
701
- },
702
- ],
1353
+ ],
1354
+ }),
703
1355
  content: {...messageData},
704
1356
  },
705
1357
  },
@@ -780,6 +1432,20 @@ export const Viber = (props) => {
780
1432
  });
781
1433
  };
782
1434
 
1435
+ const hasCarouselValidationError = isCarouselCardCountInvalid || hasInvalidCarouselCard || hasInvalidCarouselButton;
1436
+ const getDoneHandler = () => {
1437
+ const doneCallback = onDoneCallback();
1438
+ return () => {
1439
+ if (isMediaTypeCarousel) {
1440
+ setShowCarouselValidationErrors(true);
1441
+ if (hasCarouselValidationError) {
1442
+ return;
1443
+ }
1444
+ }
1445
+ doneCallback();
1446
+ };
1447
+ };
1448
+
783
1449
  const isDisableDone = () => {
784
1450
  // textbox area should not empty and should have max 1000 charactor
785
1451
  if (messageContent?.trim() === '' || errorMessageTextarea) {
@@ -829,6 +1495,7 @@ export const Viber = (props) => {
829
1495
  {renderMediaSection()}
830
1496
  {renderMediaComponent()}
831
1497
  {renderTextAreaViber()}
1498
+ {renderInteractiveSection()}
832
1499
  {renderButtonsSection()}
833
1500
  <div style={{marginBottom: '100px'}} />
834
1501
  </CapColumn>
@@ -858,7 +1525,7 @@ export const Viber = (props) => {
858
1525
  </CapRow>
859
1526
  <ViberFooter>
860
1527
  <CapButton
861
- onClick={onDoneCallback()}
1528
+ onClick={getDoneHandler()}
862
1529
  disabled={isDisableDone()}
863
1530
  className="create-msg viber-create-msg"
864
1531
  >