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