@capillarytech/creatives-library 8.0.352 → 8.0.353-alpha.1

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.
@@ -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,35 @@ 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
+ });
100
+ const createEmptyCarouselCard = () => ({
101
+ id: getNextCarouselCardId(),
102
+ text: '',
103
+ mediaUrl: '',
104
+ buttons: [createEmptyCarouselButton()],
105
+ });
106
+ const createDefaultCarouselCards = () => [
107
+ createEmptyCarouselCard(),
108
+ createEmptyCarouselCard(),
109
+ ];
66
110
 
67
111
  export const Viber = (props) => {
68
112
  const {
@@ -111,6 +155,9 @@ export const Viber = (props) => {
111
155
  viberVideoPreviewImg: '',
112
156
  duration: 0,
113
157
  });
158
+ const [carouselCards, setCarouselCards] = useState(() => createDefaultCarouselCards());
159
+ const [activeCarouselCardIndex, setActiveCarouselCardIndex] = useState(0);
160
+ const [showCarouselValidationErrors, setShowCarouselValidationErrors] = useState(false);
114
161
  // cta button
115
162
  const [buttonType, setButtonType] = useState(NONE);
116
163
  const [ctaData, setCtadata] = useState({});
@@ -148,18 +195,37 @@ export const Viber = (props) => {
148
195
  button = {},
149
196
  image = {},
150
197
  video = {},
198
+ cards = [],
199
+ type = "",
151
200
  } = editViberContent || {};
152
201
  const { text: ctaBtnText = "", url = "" } = button || {};
153
202
  updateTextMessageTitle(editMessageTitle);
154
203
  updateTextMessageContent(text || "");
155
204
  updateButtonText(ctaBtnText);
156
205
  updateButtonUrl(url);
157
- setIsCtaSaved(true);
206
+ setIsCtaSaved(!isEmpty(button));
158
207
  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)) {
208
+ setCtadata(!isEmpty(button) ? { buttonText: button?.text, buttonURL: button?.url } : {});
209
+ if (type === VIBER_MEDIA_TYPES.CAROUSEL) {
210
+ const normalizedCards = (cards || []).map((card) => ({
211
+ id: card?.id || getNextCarouselCardId(),
212
+ text: card?.text || '',
213
+ mediaUrl: card?.mediaUrl || '',
214
+ buttons: ((card?.buttons || []).length ? card.buttons : [createEmptyCarouselButton()]).map((carouselButton) => ({
215
+ title: carouselButton?.title || '',
216
+ action: carouselButton?.action || '',
217
+ urlType: carouselButton?.urlType || STATIC_URL,
218
+ isSaved: Boolean(carouselButton?.title && carouselButton?.action),
219
+ hasAttemptedSave: Boolean(carouselButton?.hasAttemptedSave),
220
+ })),
221
+ }));
222
+ const cardsToSet = normalizedCards.length
223
+ ? normalizedCards
224
+ : createDefaultCarouselCards();
225
+ setTemplateMediaType(VIBER_MEDIA_TYPES.CAROUSEL);
226
+ setCarouselCards(cardsToSet.slice(0, VIBER_CAROUSEL_MAX_CARDS));
227
+ setActiveCarouselCardIndex(0);
228
+ } else if (!isEmpty(image)) {
163
229
  setTemplateMediaType(VIBER_MEDIA_TYPES.IMAGE);
164
230
  updateImageSrc(image?.url);
165
231
  } else if (!isEmpty(video)) {
@@ -207,6 +273,17 @@ export const Viber = (props) => {
207
273
  updateButtonUrl(newUrl);
208
274
  onChangeButtonUrl({ target: { value: newUrl } });
209
275
  };
276
+ const onCarouselCardTagSelect = (cardIndex, data) => {
277
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
278
+ if (index !== cardIndex) {
279
+ return card;
280
+ }
281
+ return {
282
+ ...card,
283
+ text: `${card?.text || ''}{{${data}}}`,
284
+ };
285
+ }));
286
+ };
210
287
 
211
288
  const handleOnTagsContextChange = (data) => {
212
289
  const query = {
@@ -335,15 +412,126 @@ export const Viber = (props) => {
335
412
  // template media code start here
336
413
  const isMediaTypeImage = templateMediaType === VIBER_MEDIA_TYPES.IMAGE;
337
414
  const isMediaTypeVideo = templateMediaType === VIBER_MEDIA_TYPES.VIDEO;
415
+ const isMediaTypeCarousel = templateMediaType === VIBER_MEDIA_TYPES.CAROUSEL;
416
+ const getCarouselButtonTitleMaxLength = (buttonIndex) => (
417
+ buttonIndex === 0
418
+ ? VIBER_CAROUSEL_FIRST_BUTTON_TITLE_MAX_LENGTH
419
+ : VIBER_CAROUSEL_SECOND_BUTTON_TITLE_MAX_LENGTH
420
+ );
421
+ const renderLength = (len, max) => (
422
+ <CapHeading type="label1" className="viber-carousel-field-length-top">
423
+ {len || 0}
424
+ /
425
+ {max}
426
+ {' '}
427
+ <FormattedMessage {...messages.characters} />
428
+ </CapHeading>
429
+ );
430
+ const getCarouselCardTextError = (cardText = '', showEmptyError = true) => {
431
+ const trimmedCardText = (cardText || '').trim();
432
+ if (!trimmedCardText && showEmptyError) {
433
+ return formatMessage(messages.textCannotBeEmptyError);
434
+ }
435
+ if (!trimmedCardText) {
436
+ return false;
437
+ }
438
+ if (trimmedCardText.length < VIBER_CAROUSEL_CARD_TITLE_MIN_LENGTH) {
439
+ return formatMessage(messages.carouselCardTitleMinLengthError);
440
+ }
441
+ if (cardText.length > VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH) {
442
+ return formatMessage(messages.carouselCardTitleMaxLengthError);
443
+ }
444
+ return false;
445
+ };
446
+ const getCarouselButtonTitleError = (button = {}, buttonIndex = 0, showEmptyError = true) => {
447
+ const title = button?.title || '';
448
+ const maxLength = getCarouselButtonTitleMaxLength(buttonIndex);
449
+ if (!title.trim() && showEmptyError) {
450
+ return formatMessage(messages.textCannotBeEmptyError);
451
+ }
452
+ if (!title.trim()) {
453
+ return false;
454
+ }
455
+ if (title.length > maxLength) {
456
+ return buttonIndex === 0
457
+ ? formatMessage(messages.carouselFirstButtonTitleMaxLengthError)
458
+ : formatMessage(messages.carouselSecondButtonTitleMaxLengthError);
459
+ }
460
+ return false;
461
+ };
462
+ const getCarouselButtonActionError = (button = {}) => {
463
+ const action = button?.action || '';
464
+ if (!action.trim()) {
465
+ return formatMessage(messages.urlCannotBeEmptyError);
466
+ }
467
+ if (action.length > VIBER_CAROUSEL_BUTTON_URL_MAX_LENGTH) {
468
+ return formatMessage(messages.carouselButtonUrlMaxLengthError);
469
+ }
470
+ if (!isUrl(action)) {
471
+ return formatMessage(messages.inValidUrliErrorMessage);
472
+ }
473
+ return false;
474
+ };
475
+ const hasInvalidCarouselCard = carouselCards.some(
476
+ (card) => Boolean(getCarouselCardTextError(card?.text)) || !isUrl(card?.mediaUrl || '')
477
+ );
478
+ const hasInvalidCarouselButton = carouselCards.some((card) => (card?.buttons || []).some(
479
+ (button, buttonIndex) => {
480
+ const hasAnyValue = Boolean(button?.title || button?.action);
481
+ if (buttonIndex !== 0 && !hasAnyValue) {
482
+ return false;
483
+ }
484
+ return Boolean(getCarouselButtonTitleError(button, buttonIndex))
485
+ || Boolean(getCarouselButtonActionError(button))
486
+ || !button?.isSaved;
487
+ }
488
+ ));
489
+ const isCarouselCardCountInvalid = carouselCards.length < VIBER_CAROUSEL_MIN_CARDS
490
+ || carouselCards.length > VIBER_CAROUSEL_MAX_CARDS;
491
+ const isCarouselButtonComplete = (button, buttonIndex) => {
492
+ const hasAnyValue = Boolean(button?.title || button?.action);
493
+ if (buttonIndex !== 0 && !hasAnyValue) {
494
+ return true;
495
+ }
496
+ return !getCarouselButtonTitleError(button, buttonIndex)
497
+ && !getCarouselButtonActionError(button)
498
+ && Boolean(button?.isSaved);
499
+ };
500
+ const isCarouselCardComplete = (card = {}) => (
501
+ !getCarouselCardTextError(card?.text)
502
+ && isUrl(card?.mediaUrl || '')
503
+ && (card?.buttons || []).every((button, buttonIndex) => isCarouselButtonComplete(button, buttonIndex))
504
+ );
505
+ const canAccessCarouselCardAtIndex = (targetIndex) => (
506
+ carouselCards.slice(0, targetIndex).every((card) => isCarouselCardComplete(card))
507
+ );
508
+ const isCarouselTabDisabled = (cardIndex) => (
509
+ cardIndex > 0 && !canAccessCarouselCardAtIndex(cardIndex)
510
+ );
338
511
 
339
512
  const onTemplateMediaTypeChange = ({ target: { value } }) => {
340
513
  setTemplateMediaType(value);
514
+ if (value === VIBER_MEDIA_TYPES.CAROUSEL && carouselCards.length === 0) {
515
+ setCarouselCards(createDefaultCarouselCards());
516
+ setActiveCarouselCardIndex(0);
517
+ }
518
+ if ([VIBER_MEDIA_TYPES.VIDEO, VIBER_MEDIA_TYPES.CAROUSEL].includes(value)) {
519
+ setButtonType(NONE);
520
+ setCtadata({});
521
+ updateButtonText('');
522
+ updateButtonUrl('');
523
+ setIsCtaSaved(false);
524
+ }
341
525
  };
342
526
 
343
527
  const uploadViberAsset = (file, type, fileParams) => {
344
528
  actions.uploadViberAsset(file, type, fileParams, 0);
345
529
  };
346
530
 
531
+ const uploadViberAssetByIndex = (file, type, fileParams, templateType = 0) => {
532
+ actions.uploadViberAsset(file, type, fileParams, templateType);
533
+ };
534
+
347
535
  const updateOnViberImageReUpload = useCallback(() => {
348
536
  setImageSrc("");
349
537
  }, [imageSrc]);
@@ -431,6 +619,383 @@ export const Viber = (props) => {
431
619
  );
432
620
  // template media code end here
433
621
 
622
+ const onCarouselCardChange = (cardIndex, key, value) => {
623
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
624
+ index === cardIndex ? { ...card, [key]: value } : card
625
+ )));
626
+ };
627
+
628
+ const addCarouselCard = () => {
629
+ if (carouselCards.length >= VIBER_CAROUSEL_MAX_CARDS) {
630
+ return;
631
+ }
632
+ if (!isCarouselCardComplete(carouselCards[carouselCards.length - 1])) {
633
+ return;
634
+ }
635
+ setCarouselCards((prevCards) => {
636
+ const updatedCards = [...prevCards, createEmptyCarouselCard()];
637
+ setActiveCarouselCardIndex(updatedCards.length - 1);
638
+ return updatedCards;
639
+ });
640
+ };
641
+
642
+ const removeCarouselCard = (cardIndex) => {
643
+ if (carouselCards.length <= VIBER_CAROUSEL_MIN_CARDS) {
644
+ return;
645
+ }
646
+ setCarouselCards((prevCards) => {
647
+ const updatedCards = prevCards.filter((_, index) => index !== cardIndex);
648
+ if (activeCarouselCardIndex >= updatedCards.length) {
649
+ setActiveCarouselCardIndex(updatedCards.length - 1);
650
+ } else if (activeCarouselCardIndex > cardIndex) {
651
+ setActiveCarouselCardIndex(activeCarouselCardIndex - 1);
652
+ }
653
+ return updatedCards;
654
+ });
655
+ };
656
+
657
+ const onCarouselTabChange = (cardIndex) => {
658
+ const targetIndex = Number(cardIndex);
659
+ if (Number.isNaN(targetIndex) || targetIndex < 0 || targetIndex >= carouselCards.length) {
660
+ return;
661
+ }
662
+ if (isCarouselTabDisabled(targetIndex)) {
663
+ return;
664
+ }
665
+ setActiveCarouselCardIndex(targetIndex);
666
+ };
667
+
668
+ const onCarouselButtonChange = (cardIndex, buttonIndex, key, value) => {
669
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
670
+ if (index !== cardIndex) {
671
+ return card;
672
+ }
673
+ return {
674
+ ...card,
675
+ buttons: (card?.buttons || []).map((button, idx) => (
676
+ idx === buttonIndex ? { ...button, [key]: value, isSaved: false } : button
677
+ )),
678
+ };
679
+ }));
680
+ };
681
+
682
+ const addCarouselButton = (cardIndex) => {
683
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
684
+ if (index !== cardIndex || (card?.buttons || []).length >= VIBER_CAROUSEL_MAX_BUTTONS) {
685
+ return card;
686
+ }
687
+ return {
688
+ ...card,
689
+ buttons: [...(card?.buttons || []), createEmptyCarouselButton()],
690
+ };
691
+ }));
692
+ };
693
+
694
+ const removeCarouselButton = (cardIndex, buttonIndex) => {
695
+ if (buttonIndex === 0) {
696
+ return;
697
+ }
698
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
699
+ if (index !== cardIndex) {
700
+ return card;
701
+ }
702
+ return {
703
+ ...card,
704
+ buttons: (card?.buttons || []).filter((_, idx) => idx !== buttonIndex),
705
+ };
706
+ }));
707
+ };
708
+
709
+ const saveCarouselButton = (cardIndex, buttonIndex) => {
710
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
711
+ if (index !== cardIndex) {
712
+ return card;
713
+ }
714
+ return {
715
+ ...card,
716
+ buttons: (card?.buttons || []).map((button, idx) => {
717
+ if (idx !== buttonIndex) {
718
+ return button;
719
+ }
720
+ const nextButtonState = {
721
+ ...button,
722
+ hasAttemptedSave: true,
723
+ };
724
+ if (getCarouselButtonTitleError(nextButtonState, buttonIndex, true) || getCarouselButtonActionError(nextButtonState)) {
725
+ return nextButtonState;
726
+ }
727
+ return {
728
+ ...nextButtonState,
729
+ isSaved: true,
730
+ };
731
+ }),
732
+ };
733
+ }));
734
+ };
735
+
736
+ const editCarouselButton = (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
+ idx === buttonIndex ? { ...button, isSaved: false } : button
745
+ )),
746
+ };
747
+ }));
748
+ };
749
+
750
+ const updateCarouselImageSrc = useCallback((cardIndex, url) => {
751
+ const transformedUrl = getCdnUrl({ url, channelName: 'VIBER' });
752
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
753
+ index === cardIndex ? { ...card, mediaUrl: transformedUrl } : card
754
+ )));
755
+ actions.clearViberAsset(cardIndex);
756
+ }, []);
757
+
758
+ const updateOnCarouselImageReUpload = useCallback((cardIndex) => {
759
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
760
+ index === cardIndex ? { ...card, mediaUrl: '' } : card
761
+ )));
762
+ actions.clearViberAsset(cardIndex);
763
+ }, []);
764
+
765
+ const getCarouselTabPanes = () => (
766
+ carouselCards.map((card, cardIndex) => {
767
+ const isTabDisabled = isCarouselTabDisabled(cardIndex);
768
+ return ({
769
+ key: `${cardIndex}`,
770
+ tab: (
771
+ <span className={isTabDisabled ? 'viber-carousel-tab-label-disabled' : ''}>
772
+ {cardIndex + 1}
773
+ </span>
774
+ ),
775
+ disabled: isTabDisabled,
776
+ content: (
777
+ <div className="viber-carousel-card">
778
+ <CapRow type="flex" justify="space-between" align="middle">
779
+ <CapHeading type="h5">
780
+ {formatMessage(messages.carouselCardHeading, { index: cardIndex + 1 })}
781
+ </CapHeading>
782
+ <CapButton
783
+ type="flat"
784
+ className="viber-carousel-delete-icon-btn"
785
+ disabled={carouselCards.length <= VIBER_CAROUSEL_MIN_CARDS}
786
+ onClick={() => removeCarouselCard(cardIndex)}
787
+ >
788
+ <CapIcon type="delete" size="s" />
789
+ </CapButton>
790
+ </CapRow>
791
+ <CapImageUpload
792
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX_VIBER}
793
+ imgSize={VIBER_CAROUSEL_IMG_SIZE}
794
+ imgWidth={VIBER_CAROUSEL_IMG_WIDTH}
795
+ imgHeight={VIBER_CAROUSEL_IMG_HEIGHT}
796
+ uploadAsset={uploadViberAssetByIndex}
797
+ isFullMode={isFullMode}
798
+ imageSrc={card?.mediaUrl || ''}
799
+ updateImageSrc={(url) => updateCarouselImageSrc(cardIndex, url)}
800
+ updateOnReUpload={() => updateOnCarouselImageReUpload(cardIndex)}
801
+ index={cardIndex}
802
+ className="cap-custom-image-upload"
803
+ key={`viber-carousel-image-upload-${card.id}`}
804
+ imageData={viber}
805
+ channel={VIBER}
806
+ />
807
+ <CapLabel type="label3" className="viber-carousel-image-recommendation">
808
+ Supported image types are .jpg, .jpeg, .png. Max size: 10 MB. Recommended resolution: 696 px x 600 px.
809
+ </CapLabel>
810
+ <CapRow className="viber-carousel-card-title-header" type="flex" justify="space-between">
811
+ <CapHeading type="h5">
812
+ {formatMessage(messages.carouselCardTextLabel)}
813
+ </CapHeading>
814
+ <TagList
815
+ key={`viber_carousel_card_tags_${cardIndex}`}
816
+ className="tag-list-viber"
817
+ moduleFilterEnabled={location?.query?.type !== "embedded"}
818
+ label={formatMessage(messages.addLabels)}
819
+ onTagSelect={(data) => onCarouselCardTagSelect(cardIndex, data)}
820
+ onContextChange={handleOnTagsContextChange}
821
+ location={location}
822
+ tags={tags}
823
+ injectedTags={injectedTags || {}}
824
+ id={`viber_carousel_card_tags_${cardIndex}`}
825
+ userLocale={localStorage.getItem("jlocale") || "en"}
826
+ selectedOfferDetails={selectedOfferDetails}
827
+ eventContextTags={eventContextTags}
828
+ />
829
+ </CapRow>
830
+ <CapInput
831
+ value={card?.text || ''}
832
+ onChange={({ target: { value } }) => onCarouselCardChange(cardIndex, 'text', value)}
833
+ placeholder={formatMessage(messages.carouselCardTextPlaceholder)}
834
+ errorMessage={getCarouselCardTextError(card?.text, showCarouselValidationErrors)}
835
+ />
836
+ {renderLength((card?.text || '').length, VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH)}
837
+ <CapHeading type="h5" className="viber-carousel-button-label">
838
+ {formatMessage(messages.btnLabel)}
839
+ </CapHeading>
840
+ {(card?.buttons || []).map((button, buttonIndex) => (
841
+ // eslint-disable-next-line react/no-array-index-key
842
+ <div className="viber-carousel-button" key={`carousel-button-${card.id}-${buttonIndex}`}>
843
+ {button.isSaved ? (
844
+ <CapRow className="viber-carousel-saved-button" align="middle" type="flex">
845
+ <CapIcon size="s" type="six-dots" className="viber-carousel-saved-button-icon" />
846
+ <CapIcon size="s" type="reply" className="viber-carousel-saved-button-icon" />
847
+ <CapLabel type="label4" className="viber-carousel-saved-button-text">
848
+ {button.title}
849
+ </CapLabel>
850
+ <CapColumn className="button-edit-icon" onClick={() => editCarouselButton(cardIndex, buttonIndex)}>
851
+ <CapIcon type="edit" size="s" />
852
+ </CapColumn>
853
+ {buttonIndex > 0 && (
854
+ <CapButton
855
+ type="flat"
856
+ className="viber-carousel-delete-icon-btn"
857
+ onClick={() => removeCarouselButton(cardIndex, buttonIndex)}
858
+ >
859
+ <CapIcon type="delete" size="s" />
860
+ </CapButton>
861
+ )}
862
+ </CapRow>
863
+ ) : (
864
+ <div className="cta-section">
865
+ <CapRow
866
+ type="flex"
867
+ justify="space-between"
868
+ align="middle"
869
+ className="viber-carousel-button-title-header"
870
+ >
871
+ <CapHeading type="h5">
872
+ {formatMessage(messages.carouselButtonTitleLabel)}
873
+ </CapHeading>
874
+ </CapRow>
875
+ <CapInput
876
+ value={button.title}
877
+ onChange={({ target: { value } }) => onCarouselButtonChange(cardIndex, buttonIndex, 'title', value)}
878
+ placeholder={formatMessage(messages.carouselButtonTitlePlaceholder)}
879
+ errorMessage={getCarouselButtonTitleError(
880
+ button,
881
+ buttonIndex,
882
+ showCarouselValidationErrors || Boolean(button?.hasAttemptedSave),
883
+ )}
884
+ />
885
+ {renderLength((button?.title || '').length, getCarouselButtonTitleMaxLength(buttonIndex))}
886
+ <CapRow gutter={12}>
887
+ <CapColumn span={6}>
888
+ <CapHeading type="h4" className="cta-label">
889
+ {formatMessage(messages.carouselButtonUrlTypeLabel)}
890
+ </CapHeading>
891
+ <CapSelect
892
+ className="viber-carousel-url-type-select"
893
+ dropdownClassName="viber-carousel-url-type-dropdown"
894
+ dropdownMatchSelectWidth={false}
895
+ options={CAROUSEL_URL_TYPE_OPTIONS(formatMessage)}
896
+ value={button?.urlType || STATIC_URL}
897
+ onChange={(value) => onCarouselButtonChange(cardIndex, buttonIndex, 'urlType', value)}
898
+ />
899
+ </CapColumn>
900
+ <CapColumn span={18}>
901
+ <CapInput
902
+ label={formatMessage(messages.carouselButtonActionLabel)}
903
+ value={button.action}
904
+ onChange={({ target: { value } }) => onCarouselButtonChange(cardIndex, buttonIndex, 'action', value)}
905
+ placeholder={formatMessage(messages.carouselButtonActionPlaceholder)}
906
+ errorMessage={getCarouselButtonActionError(button)}
907
+ />
908
+ </CapColumn>
909
+ </CapRow>
910
+ <div className="cta-actions">
911
+ <CapButton
912
+ className="cta-btn-action"
913
+ onClick={() => saveCarouselButton(cardIndex, buttonIndex)}
914
+ >
915
+ {formatMessage(messages.save)}
916
+ </CapButton>
917
+ {buttonIndex > 0 && (
918
+ <CapButton
919
+ type="secondary"
920
+ onClick={() => removeCarouselButton(cardIndex, buttonIndex)}
921
+ >
922
+ {formatMessage(globalMessages.delete)}
923
+ </CapButton>
924
+ )}
925
+ </div>
926
+ </div>
927
+ )}
928
+ </div>
929
+ ))}
930
+ {(card?.buttons || []).length < VIBER_CAROUSEL_MAX_BUTTONS && (
931
+ <CapButton
932
+ type="flat"
933
+ className="viber-add-row-btn"
934
+ onClick={() => addCarouselButton(cardIndex)}
935
+ >
936
+ + Add Button
937
+ </CapButton>
938
+ )}
939
+ </div>
940
+ ),
941
+ });
942
+ })
943
+ );
944
+
945
+ const carouselTabOperations = (
946
+ <>
947
+ <CapDivider type="vertical" />
948
+ <CapButton
949
+ type="flat"
950
+ className="viber-carousel-tab-add-btn"
951
+ onClick={addCarouselCard}
952
+ disabled={
953
+ carouselCards.length >= VIBER_CAROUSEL_MAX_CARDS
954
+ || !isCarouselCardComplete(carouselCards[carouselCards.length - 1])
955
+ }
956
+ >
957
+ <CapIcon type="plus" />
958
+ </CapButton>
959
+ </>
960
+ );
961
+
962
+ const renderCarouselSection = () => (
963
+ <div className="viber-carousel-section">
964
+ <CapHeading type="h4" className="viber-render-heading">
965
+ {formatMessage(messages.carouselCardsLabel)}
966
+ </CapHeading>
967
+ <CapRow className="viber-carousel-tab">
968
+ <CapTab
969
+ activeKey={`${activeCarouselCardIndex}`}
970
+ tabBarExtraContent={carouselTabOperations}
971
+ onChange={onCarouselTabChange}
972
+ panes={getCarouselTabPanes()}
973
+ />
974
+ </CapRow>
975
+ {isCarouselCardCountInvalid && (
976
+ <CapLabel type="label3" className="viber-form-error">
977
+ {formatMessage(messages.carouselCardsLimitError)}
978
+ </CapLabel>
979
+ )}
980
+ {hasInvalidCarouselCard && (
981
+ <CapLabel type="label3" className="viber-form-error">
982
+ {formatMessage(messages.carouselCardError)}
983
+ </CapLabel>
984
+ )}
985
+ {hasInvalidCarouselButton && (
986
+ <CapLabel type="label3" className="viber-form-error">
987
+ {formatMessage(messages.carouselButtonError)}
988
+ </CapLabel>
989
+ )}
990
+ </div>
991
+ );
992
+
993
+ const renderInteractiveSection = () => (
994
+ <>
995
+ {isMediaTypeCarousel && renderCarouselSection()}
996
+ </>
997
+ );
998
+
434
999
  // Button Code start here
435
1000
 
436
1001
  const isBtnTypeCta = buttonType === VIBER_BUTTON_TYPES.CTA;
@@ -454,6 +1019,11 @@ export const Viber = (props) => {
454
1019
  viberPreviewContent: {
455
1020
  ...(isMediaTypeImage && { imageURL: imageSrc }),
456
1021
  ...(isMediaTypeVideo && { videoParams: viberVideoSrcAndPreview }),
1022
+ ...(isMediaTypeCarousel && {
1023
+ cards: carouselCards,
1024
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1025
+ showCarouselEditorPreview: true,
1026
+ }),
457
1027
  buttonText,
458
1028
  messageContent,
459
1029
  },
@@ -477,6 +1047,11 @@ export const Viber = (props) => {
477
1047
  viberPreviewContent: {
478
1048
  ...(isMediaTypeImage && { imageURL: imageSrc }),
479
1049
  ...(isMediaTypeVideo && { videoParams: viberVideoSrcAndPreview }),
1050
+ ...(isMediaTypeCarousel && {
1051
+ cards: carouselCards,
1052
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1053
+ showCarouselEditorPreview: true,
1054
+ }),
480
1055
  buttonText: ctaData?.buttonText || buttonText,
481
1056
  messageContent,
482
1057
  },
@@ -499,8 +1074,19 @@ export const Viber = (props) => {
499
1074
  duration: viberVideoSrcAndPreview.duration,
500
1075
  },
501
1076
  }),
1077
+ ...(isMediaTypeCarousel && {
1078
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1079
+ cards: carouselCards.map((card) => ({
1080
+ text: card.text,
1081
+ mediaUrl: card.mediaUrl,
1082
+ buttons: (card.buttons || []).map((button) => ({
1083
+ title: button.title,
1084
+ action: button.action,
1085
+ })),
1086
+ })),
1087
+ }),
502
1088
  // Add button if present (for payload)
503
- ...((ctaData?.buttonText || buttonText) && (ctaData?.buttonURL || buttonURL) && {
1089
+ ...(!isMediaTypeCarousel && (ctaData?.buttonText || buttonText) && (ctaData?.buttonURL || buttonURL) && {
504
1090
  button: {
505
1091
  text: ctaData?.buttonText || buttonText,
506
1092
  url: ctaData?.buttonURL || buttonURL,
@@ -518,7 +1104,20 @@ export const Viber = (props) => {
518
1104
  sender: viberData?.selectedViberAccount?.sender || 'test1',
519
1105
  };
520
1106
  return templateContent;
521
- }, [isMediaTypeImage, isMediaTypeVideo, imageSrc, viberVideoSrcAndPreview, ctaData, buttonText, buttonURL, messageContent, accountName, viberData]);
1107
+ }, [
1108
+ isMediaTypeImage,
1109
+ isMediaTypeVideo,
1110
+ isMediaTypeCarousel,
1111
+ imageSrc,
1112
+ viberVideoSrcAndPreview,
1113
+ carouselCards,
1114
+ ctaData,
1115
+ buttonText,
1116
+ buttonURL,
1117
+ messageContent,
1118
+ accountName,
1119
+ viberData,
1120
+ ]);
522
1121
 
523
1122
  // Handle Test and Preview button click
524
1123
  const handleTestAndPreview = useCallback(() => {
@@ -632,39 +1231,44 @@ export const Viber = (props) => {
632
1231
  setIsCtaSaved(false);
633
1232
  };
634
1233
 
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>
1234
+ const renderButtonsSection = () => {
1235
+ if (isMediaTypeCarousel) {
1236
+ return null;
1237
+ }
1238
+ return (
1239
+ <div className="button-section">
1240
+ <CapHeader
1241
+ className="viber-render-heading"
1242
+ title={(
1243
+ <CapRow type="flex">
1244
+ <CapHeading type="h4">
1245
+ {formatMessage(messages.btnLabel)}
1246
+ </CapHeading>
1247
+ <CapHeading className="viber-optional-label">
1248
+ {formatMessage(messages.optional)}
1249
+ </CapHeading>
1250
+ </CapRow>
1251
+ )}
1252
+ description={
1253
+ <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
1254
+ }
1255
+ />
1256
+ {templateMediaType === VIBER_MEDIA_TYPES.VIDEO && (
1257
+ <CapLabel type="label3">
1258
+ {formatMessage(messages.videoButtonDisabled)}
1259
+ </CapLabel>
648
1260
  )}
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
- );
1261
+ <CapRadioGroup
1262
+ options={buttonRadioOptions}
1263
+ value={buttonType}
1264
+ onChange={onChangeButtonType}
1265
+ disabled={templateMediaType === VIBER_MEDIA_TYPES.VIDEO}
1266
+ className="viber-btn-radio-group"
1267
+ />
1268
+ {isBtnTypeCta && ButtonViber}
1269
+ </div>
1270
+ );
1271
+ };
668
1272
  // Button Code End here
669
1273
 
670
1274
  // to generate payload for create and edit
@@ -683,8 +1287,19 @@ export const Viber = (props) => {
683
1287
  messageData.video.thumbnailUrl = viberVideoPreviewImg;
684
1288
  messageData.video.duration = duration; // integer value in seconds
685
1289
  }
1290
+ if (isMediaTypeCarousel) {
1291
+ messageData.type = VIBER_MEDIA_TYPES.CAROUSEL;
1292
+ messageData.cards = carouselCards.map((card) => ({
1293
+ text: card.text,
1294
+ mediaUrl: card.mediaUrl,
1295
+ buttons: (card.buttons || []).map((button) => ({
1296
+ title: button.title,
1297
+ action: button.action,
1298
+ })),
1299
+ }));
1300
+ }
686
1301
 
687
- if (!isEmpty(ctaData)) {
1302
+ if (!isMediaTypeCarousel && !isEmpty(ctaData)) {
688
1303
  messageData.button = {};
689
1304
  messageData.button.text = ctaData?.buttonText;
690
1305
  messageData.button.url = ctaData?.buttonURL;
@@ -693,13 +1308,15 @@ export const Viber = (props) => {
693
1308
  versions: {
694
1309
  base: {
695
1310
  content: {
696
- destinations: [
697
- {
698
- to: {
699
- phoneNumber: "{{viber_user_id}}",
1311
+ ...(isMediaTypeCarousel && {
1312
+ destinations: [
1313
+ {
1314
+ to: {
1315
+ phoneNumber: "{{viber_user_id}}",
1316
+ },
700
1317
  },
701
- },
702
- ],
1318
+ ],
1319
+ }),
703
1320
  content: {...messageData},
704
1321
  },
705
1322
  },
@@ -829,6 +1446,7 @@ export const Viber = (props) => {
829
1446
  {renderMediaSection()}
830
1447
  {renderMediaComponent()}
831
1448
  {renderTextAreaViber()}
1449
+ {renderInteractiveSection()}
832
1450
  {renderButtonsSection()}
833
1451
  <div style={{marginBottom: '100px'}} />
834
1452
  </CapColumn>