@capillarytech/creatives-library 9.0.53-alpha.3 → 9.0.54-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 (33) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/constants/unified.js +1 -0
  3. package/package.json +1 -1
  4. package/services/api.js +0 -9
  5. package/utils/common.js +4 -0
  6. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberCarouselPreviewCards.js +127 -0
  7. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +106 -15
  8. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +139 -1
  9. package/v2Components/CommonTestAndPreview/UnifiedPreview/_viberCarouselPreviewCards.scss +133 -0
  10. package/v2Components/CommonTestAndPreview/constants.js +2 -0
  11. package/v2Components/CommonTestAndPreview/index.js +243 -26
  12. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +364 -0
  13. package/v2Components/CommonTestAndPreview/tests/utils.test.js +49 -0
  14. package/v2Components/CommonTestAndPreview/utils.js +20 -0
  15. package/v2Containers/CommunicationFlow/CommunicationFlow.js +58 -93
  16. package/v2Containers/CommunicationFlow/CommunicationFlowCard.js +4 -20
  17. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +49 -194
  18. package/v2Containers/CommunicationFlow/Tests/CommunicationFlowCard.test.js +0 -16
  19. package/v2Containers/CommunicationFlow/constants.js +0 -47
  20. package/v2Containers/CommunicationFlow/index.js +20 -15
  21. package/v2Containers/CommunicationFlow/messages.js +0 -4
  22. package/v2Containers/CreativesContainer/index.js +2 -0
  23. package/v2Containers/Rcs/constants.js +1 -0
  24. package/v2Containers/Rcs/index.js +9 -1
  25. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +108 -0
  26. package/v2Containers/Rcs/tests/index.test.js +121 -0
  27. package/v2Containers/Templates/_templates.scss +179 -1
  28. package/v2Containers/Templates/index.js +120 -10
  29. package/v2Containers/Viber/constants.js +23 -0
  30. package/v2Containers/Viber/index.js +718 -43
  31. package/v2Containers/Viber/index.scss +175 -0
  32. package/v2Containers/Viber/messages.js +121 -0
  33. package/v2Containers/Viber/tests/index.test.js +80 -0
@@ -17,6 +17,9 @@ 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
19
  import ConfigProvider from 'antd/lib/config-provider';
20
+ import CapTab from '@capillarytech/cap-ui-library/CapTab';
21
+ import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
22
+ import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
20
23
  import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
21
24
  import { GA } from '@capillarytech/cap-ui-utils';
22
25
  import * as globalActions from '../Cap/actions';
@@ -45,6 +48,19 @@ import {
45
48
  NONE,
46
49
  mediaRadioOptions,
47
50
  buttonRadioOptions,
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,
62
+ STATIC_URL,
63
+ DYNAMIC_URL,
48
64
  } from './constants';
49
65
  import withCreatives from '../../hoc/withCreatives';
50
66
  import {
@@ -64,6 +80,41 @@ import v2ViberReducer from './reducer';
64
80
 
65
81
 
66
82
  const { TextArea } = CapInput;
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
+ let carouselButtonIdSeed = 0;
89
+ const getNextCarouselCardId = () => {
90
+ const nextId = `viber-carousel-card-${carouselCardIdSeed}`;
91
+ carouselCardIdSeed += 1;
92
+ return nextId;
93
+ };
94
+ const getNextCarouselButtonId = () => {
95
+ const nextId = `viber-carousel-button-${carouselButtonIdSeed}`;
96
+ carouselButtonIdSeed += 1;
97
+ return nextId;
98
+ };
99
+ const createEmptyCarouselButton = () => ({
100
+ id: getNextCarouselButtonId(),
101
+ title: '',
102
+ action: '',
103
+ urlType: STATIC_URL,
104
+ isSaved: false,
105
+ hasAttemptedSave: false,
106
+ hasTouchedAction: false,
107
+ });
108
+ const createEmptyCarouselCard = () => ({
109
+ id: getNextCarouselCardId(),
110
+ text: '',
111
+ mediaUrl: '',
112
+ buttons: [createEmptyCarouselButton()],
113
+ });
114
+ const createDefaultCarouselCards = () => [
115
+ createEmptyCarouselCard(),
116
+ createEmptyCarouselCard(),
117
+ ];
67
118
 
68
119
  export const Viber = (props) => {
69
120
  const {
@@ -112,6 +163,9 @@ export const Viber = (props) => {
112
163
  viberVideoPreviewImg: '',
113
164
  duration: 0,
114
165
  });
166
+ const [carouselCards, setCarouselCards] = useState(() => createDefaultCarouselCards());
167
+ const [activeCarouselCardIndex, setActiveCarouselCardIndex] = useState(0);
168
+ const [showCarouselValidationErrors, setShowCarouselValidationErrors] = useState(false);
115
169
  // cta button
116
170
  const [buttonType, setButtonType] = useState(NONE);
117
171
  const [ctaData, setCtadata] = useState({});
@@ -149,18 +203,39 @@ export const Viber = (props) => {
149
203
  button = {},
150
204
  image = {},
151
205
  video = {},
206
+ cards = [],
207
+ type = "",
152
208
  } = editViberContent || {};
153
209
  const { text: ctaBtnText = "", url = "" } = button || {};
154
210
  updateTextMessageTitle(editMessageTitle);
155
211
  updateTextMessageContent(text || "");
156
212
  updateButtonText(ctaBtnText);
157
213
  updateButtonUrl(url);
158
- setIsCtaSaved(true);
214
+ setIsCtaSaved(!isEmpty(button));
159
215
  setButtonType(button?.text ? VIBER_BUTTON_TYPES.CTA : NONE);
160
- if (!isEmpty(button)) {
161
- setCtadata({ buttonText: button?.text, buttonURL: button?.url });
162
- }
163
- if (!isEmpty(image)) {
216
+ setCtadata(!isEmpty(button) ? { buttonText: button?.text, buttonURL: button?.url } : {});
217
+ if (type === VIBER_MEDIA_TYPES.CAROUSEL) {
218
+ const normalizedCards = (cards || []).map((card) => ({
219
+ id: card?.id || getNextCarouselCardId(),
220
+ text: card?.text || '',
221
+ mediaUrl: card?.mediaUrl || '',
222
+ buttons: ((card?.buttons || []).length ? card.buttons : [createEmptyCarouselButton()]).map((carouselButton) => ({
223
+ id: carouselButton?.id || getNextCarouselButtonId(),
224
+ title: carouselButton?.title || '',
225
+ action: carouselButton?.action || '',
226
+ urlType: carouselButton?.urlType || STATIC_URL,
227
+ isSaved: Boolean(carouselButton?.title && carouselButton?.action),
228
+ hasAttemptedSave: Boolean(carouselButton?.hasAttemptedSave),
229
+ hasTouchedAction: Boolean(carouselButton?.hasTouchedAction),
230
+ })),
231
+ }));
232
+ const cardsToSet = normalizedCards.length
233
+ ? normalizedCards
234
+ : createDefaultCarouselCards();
235
+ setTemplateMediaType(VIBER_MEDIA_TYPES.CAROUSEL);
236
+ setCarouselCards(cardsToSet.slice(0, VIBER_CAROUSEL_MAX_CARDS));
237
+ setActiveCarouselCardIndex(0);
238
+ } else if (!isEmpty(image)) {
164
239
  setTemplateMediaType(VIBER_MEDIA_TYPES.IMAGE);
165
240
  updateImageSrc(image?.url);
166
241
  } else if (!isEmpty(video)) {
@@ -208,6 +283,17 @@ export const Viber = (props) => {
208
283
  updateButtonUrl(newUrl);
209
284
  onChangeButtonUrl({ target: { value: newUrl } });
210
285
  };
286
+ const onCarouselCardTagSelect = (cardIndex, data) => {
287
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
288
+ if (index !== cardIndex) {
289
+ return card;
290
+ }
291
+ return {
292
+ ...card,
293
+ text: `${card?.text || ''}{{${data}}}`,
294
+ };
295
+ }));
296
+ };
211
297
 
212
298
  const handleOnTagsContextChange = (data) => {
213
299
  const query = {
@@ -336,15 +422,127 @@ export const Viber = (props) => {
336
422
  // template media code start here
337
423
  const isMediaTypeImage = templateMediaType === VIBER_MEDIA_TYPES.IMAGE;
338
424
  const isMediaTypeVideo = templateMediaType === VIBER_MEDIA_TYPES.VIDEO;
425
+ const isMediaTypeCarousel = templateMediaType === VIBER_MEDIA_TYPES.CAROUSEL;
426
+ const getCarouselButtonTitleMaxLength = (buttonIndex) => (
427
+ buttonIndex === 0
428
+ ? VIBER_CAROUSEL_FIRST_BUTTON_TITLE_MAX_LENGTH
429
+ : VIBER_CAROUSEL_SECOND_BUTTON_TITLE_MAX_LENGTH
430
+ );
431
+ const renderLength = (len, max) => (
432
+ <CapHeading type="label1" className="viber-carousel-field-length-top">
433
+ {`${len || 0}/${max} `}
434
+ <FormattedMessage {...messages.characters} />
435
+ </CapHeading>
436
+ );
437
+ const getCarouselCardTextError = (cardText = '', showEmptyError = true) => {
438
+ const trimmedCardText = (cardText || '').trim();
439
+ if (!trimmedCardText && showEmptyError) {
440
+ return formatMessage(messages.textCannotBeEmptyError);
441
+ }
442
+ if (!trimmedCardText) {
443
+ return false;
444
+ }
445
+ if (trimmedCardText?.length < VIBER_CAROUSEL_CARD_TITLE_MIN_LENGTH) {
446
+ return formatMessage(messages.carouselCardTitleMinLengthError);
447
+ }
448
+ if (cardText.length > VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH) {
449
+ return formatMessage(messages.carouselCardTitleMaxLengthError);
450
+ }
451
+ return false;
452
+ };
453
+ const getCarouselButtonTitleError = (button = {}, buttonIndex = 0, showEmptyError = true) => {
454
+ const title = button?.title || '';
455
+ const trimmedTitle = title.trim();
456
+ const maxLength = getCarouselButtonTitleMaxLength(buttonIndex);
457
+ if (!trimmedTitle && showEmptyError) {
458
+ return formatMessage(messages.textCannotBeEmptyError);
459
+ }
460
+ if (!trimmedTitle) {
461
+ return false;
462
+ }
463
+ if (title.length > maxLength) {
464
+ return buttonIndex === 0
465
+ ? formatMessage(messages.carouselFirstButtonTitleMaxLengthError)
466
+ : formatMessage(messages.carouselSecondButtonTitleMaxLengthError);
467
+ }
468
+ return false;
469
+ };
470
+ const getCarouselButtonActionError = (button = {}, showEmptyError = true) => {
471
+ const action = button?.action || '';
472
+ if (!action?.trim() && showEmptyError) {
473
+ return formatMessage(messages.urlCannotBeEmptyError);
474
+ }
475
+ if (!action?.trim()) {
476
+ return false;
477
+ }
478
+ if (action.length > VIBER_CAROUSEL_BUTTON_URL_MAX_LENGTH) {
479
+ return formatMessage(messages.carouselButtonUrlMaxLengthError);
480
+ }
481
+ if (!isUrl(action)) {
482
+ return formatMessage(messages.inValidUrliErrorMessage);
483
+ }
484
+ return false;
485
+ };
486
+ const hasInvalidCarouselCard = carouselCards.some(
487
+ (card) => Boolean(getCarouselCardTextError(card?.text)) || !isUrl(card?.mediaUrl || '')
488
+ );
489
+ const hasInvalidCarouselButton = carouselCards.some((card) => (card?.buttons || []).some(
490
+ (button, buttonIndex) => {
491
+ const hasAnyValue = Boolean(button?.title || button?.action);
492
+ if (buttonIndex !== 0 && !hasAnyValue) {
493
+ return false;
494
+ }
495
+ return Boolean(getCarouselButtonTitleError(button, buttonIndex))
496
+ || Boolean(getCarouselButtonActionError(button))
497
+ || !button?.isSaved;
498
+ }
499
+ ));
500
+ const isCarouselCardCountInvalid = carouselCards.length < VIBER_CAROUSEL_MIN_CARDS
501
+ || carouselCards.length > VIBER_CAROUSEL_MAX_CARDS;
502
+ const isCarouselButtonComplete = (button, buttonIndex) => {
503
+ const hasAnyValue = Boolean(button?.title || button?.action);
504
+ if (buttonIndex !== 0 && !hasAnyValue) {
505
+ return true;
506
+ }
507
+ return !getCarouselButtonTitleError(button, buttonIndex)
508
+ && !getCarouselButtonActionError(button)
509
+ && Boolean(button?.isSaved);
510
+ };
511
+ const isCarouselCardComplete = (card = {}) => (
512
+ !getCarouselCardTextError(card?.text)
513
+ && isUrl(card?.mediaUrl || '')
514
+ && (card?.buttons || []).every((button, buttonIndex) => isCarouselButtonComplete(button, buttonIndex))
515
+ );
516
+ const canAccessCarouselCardAtIndex = (targetIndex) => (
517
+ carouselCards.slice(0, targetIndex).every((card) => isCarouselCardComplete(card))
518
+ );
519
+ const isCarouselTabDisabled = (cardIndex) => (
520
+ cardIndex > 0 && !canAccessCarouselCardAtIndex(cardIndex)
521
+ );
339
522
 
340
523
  const onTemplateMediaTypeChange = ({ target: { value } }) => {
341
524
  setTemplateMediaType(value);
525
+ if (value === VIBER_MEDIA_TYPES.CAROUSEL && carouselCards.length === 0) {
526
+ setCarouselCards(createDefaultCarouselCards());
527
+ setActiveCarouselCardIndex(0);
528
+ }
529
+ if ([VIBER_MEDIA_TYPES.VIDEO, VIBER_MEDIA_TYPES.CAROUSEL].includes(value)) {
530
+ setButtonType(NONE);
531
+ setCtadata({});
532
+ updateButtonText('');
533
+ updateButtonUrl('');
534
+ setIsCtaSaved(false);
535
+ }
342
536
  };
343
537
 
344
538
  const uploadViberAsset = (file, type, fileParams) => {
345
539
  actions.uploadViberAsset(file, type, fileParams, 0);
346
540
  };
347
541
 
542
+ const uploadViberAssetByIndex = (file, type, fileParams, templateType = 0) => {
543
+ actions.uploadViberAsset(file, type, fileParams, templateType);
544
+ };
545
+
348
546
  const updateOnViberImageReUpload = useCallback(() => {
349
547
  setImageSrc("");
350
548
  }, [imageSrc]);
@@ -434,6 +632,415 @@ export const Viber = (props) => {
434
632
  );
435
633
  // template media code end here
436
634
 
635
+ const onCarouselCardChange = (cardIndex, key, value) => {
636
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
637
+ index === cardIndex ? { ...card, [key]: value } : card
638
+ )));
639
+ };
640
+
641
+ const addCarouselCard = () => {
642
+ if (carouselCards.length >= VIBER_CAROUSEL_MAX_CARDS) {
643
+ return;
644
+ }
645
+ if (!isCarouselCardComplete(carouselCards[carouselCards.length - 1])) {
646
+ return;
647
+ }
648
+ setCarouselCards((prevCards) => {
649
+ const updatedCards = [...prevCards, createEmptyCarouselCard()];
650
+ setActiveCarouselCardIndex(updatedCards.length - 1);
651
+ return updatedCards;
652
+ });
653
+ };
654
+
655
+ const removeCarouselCard = (cardIndex) => {
656
+ if (carouselCards.length <= VIBER_CAROUSEL_MIN_CARDS) {
657
+ return;
658
+ }
659
+ setCarouselCards((prevCards) => {
660
+ const updatedCards = prevCards.filter((_, index) => index !== cardIndex);
661
+ if (activeCarouselCardIndex >= updatedCards.length) {
662
+ setActiveCarouselCardIndex(updatedCards.length - 1);
663
+ } else if (activeCarouselCardIndex > cardIndex) {
664
+ setActiveCarouselCardIndex(activeCarouselCardIndex - 1);
665
+ }
666
+ return updatedCards;
667
+ });
668
+ };
669
+
670
+ const onCarouselTabChange = (cardIndex) => {
671
+ const targetIndex = Number(cardIndex);
672
+ if (Number.isNaN(targetIndex) || targetIndex < 0 || targetIndex >= carouselCards.length) {
673
+ return;
674
+ }
675
+ if (isCarouselTabDisabled(targetIndex)) {
676
+ return;
677
+ }
678
+ setActiveCarouselCardIndex(targetIndex);
679
+ };
680
+
681
+ const onCarouselButtonChange = (cardIndex, buttonIndex, key, value) => {
682
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
683
+ if (index !== cardIndex) {
684
+ return card;
685
+ }
686
+ return {
687
+ ...card,
688
+ buttons: (card?.buttons || []).map((button, idx) => (
689
+ idx === buttonIndex
690
+ ? {
691
+ ...button,
692
+ [key]: value,
693
+ isSaved: false,
694
+ ...(key === 'action' && value?.trim() ? { hasTouchedAction: true } : {}),
695
+ }
696
+ : button
697
+ )),
698
+ };
699
+ }));
700
+ };
701
+
702
+ const markCarouselButtonActionTouched = (cardIndex, buttonIndex) => {
703
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
704
+ if (index !== cardIndex) {
705
+ return card;
706
+ }
707
+ return {
708
+ ...card,
709
+ buttons: (card?.buttons || []).map((button, idx) => (
710
+ idx === buttonIndex ? { ...button, hasTouchedAction: true } : button
711
+ )),
712
+ };
713
+ }));
714
+ };
715
+
716
+ const addCarouselButton = (cardIndex) => {
717
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
718
+ if (index !== cardIndex || (card?.buttons || []).length >= VIBER_CAROUSEL_MAX_BUTTONS) {
719
+ return card;
720
+ }
721
+ return {
722
+ ...card,
723
+ buttons: [...(card?.buttons || []), createEmptyCarouselButton()],
724
+ };
725
+ }));
726
+ };
727
+
728
+ const removeCarouselButton = (cardIndex, buttonIndex) => {
729
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
730
+ if (index !== cardIndex) {
731
+ return card;
732
+ }
733
+ const buttons = card?.buttons || [];
734
+ if (buttons.length <= 1) {
735
+ return card;
736
+ }
737
+ return {
738
+ ...card,
739
+ buttons: buttons.filter((_, idx) => idx !== buttonIndex),
740
+ };
741
+ }));
742
+ };
743
+
744
+ const canRemoveCarouselButton = (card) => (card?.buttons || []).length > 1;
745
+
746
+ const saveCarouselButton = (cardIndex, buttonIndex) => {
747
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
748
+ if (index !== cardIndex) {
749
+ return card;
750
+ }
751
+ return {
752
+ ...card,
753
+ buttons: (card?.buttons || []).map((button, idx) => {
754
+ if (idx !== buttonIndex) {
755
+ return button;
756
+ }
757
+ const nextButtonState = {
758
+ ...button,
759
+ hasAttemptedSave: true,
760
+ };
761
+ if (getCarouselButtonTitleError(nextButtonState, buttonIndex, true) || getCarouselButtonActionError(nextButtonState)) {
762
+ return nextButtonState;
763
+ }
764
+ return {
765
+ ...nextButtonState,
766
+ isSaved: true,
767
+ };
768
+ }),
769
+ };
770
+ }));
771
+ };
772
+
773
+ const editCarouselButton = (cardIndex, buttonIndex) => {
774
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
775
+ if (index !== cardIndex) {
776
+ return card;
777
+ }
778
+ return {
779
+ ...card,
780
+ buttons: (card?.buttons || []).map((button, idx) => (
781
+ idx === buttonIndex ? { ...button, isSaved: false } : button
782
+ )),
783
+ };
784
+ }));
785
+ };
786
+
787
+ const updateCarouselImageSrc = useCallback((cardIndex, url) => {
788
+ const transformedUrl = getCdnUrl({ url, channelName: 'VIBER' });
789
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
790
+ index === cardIndex ? { ...card, mediaUrl: transformedUrl } : card
791
+ )));
792
+ actions.clearViberAsset(cardIndex);
793
+ }, []);
794
+
795
+ const updateOnCarouselImageReUpload = useCallback((cardIndex) => {
796
+ setCarouselCards((prevCards) => prevCards.map((card, index) => (
797
+ index === cardIndex ? { ...card, mediaUrl: '' } : card
798
+ )));
799
+ actions.clearViberAsset(cardIndex);
800
+ }, []);
801
+
802
+ const getCarouselTabPanes = () => (
803
+ carouselCards.map((card, cardIndex) => {
804
+ const isTabDisabled = isCarouselTabDisabled(cardIndex);
805
+ return ({
806
+ key: `${cardIndex}`,
807
+ tab: (
808
+ <span className={isTabDisabled ? 'viber-carousel-tab-label-disabled' : ''}>
809
+ {cardIndex + 1}
810
+ </span>
811
+ ),
812
+ disabled: isTabDisabled,
813
+ content: (
814
+ <div className="viber-carousel-card">
815
+ <CapRow type="flex" justify="space-between" align="middle">
816
+ <CapHeading type="h5">
817
+ {formatMessage(messages.carouselCardHeading, { index: cardIndex + 1 })}
818
+ </CapHeading>
819
+ <CapButton
820
+ type="flat"
821
+ className="viber-carousel-delete-icon-btn"
822
+ disabled={carouselCards.length <= VIBER_CAROUSEL_MIN_CARDS}
823
+ onClick={() => removeCarouselCard(cardIndex)}
824
+ >
825
+ <CapIcon type="delete" size="s" />
826
+ </CapButton>
827
+ </CapRow>
828
+ <CapImageUpload
829
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX_VIBER}
830
+ imgSize={VIBER_CAROUSEL_IMG_SIZE}
831
+ imgWidth={VIBER_CAROUSEL_IMG_WIDTH}
832
+ imgHeight={VIBER_CAROUSEL_IMG_HEIGHT}
833
+ uploadAsset={uploadViberAssetByIndex}
834
+ isFullMode={isFullMode}
835
+ imageSrc={card?.mediaUrl || ''}
836
+ updateImageSrc={(url) => updateCarouselImageSrc(cardIndex, url)}
837
+ updateOnReUpload={() => updateOnCarouselImageReUpload(cardIndex)}
838
+ index={cardIndex}
839
+ className="cap-custom-image-upload"
840
+ key={`viber-carousel-image-upload-${card?.id}`}
841
+ imageData={viber}
842
+ channel={VIBER}
843
+ />
844
+ {showCarouselValidationErrors && !isUrl(card?.mediaUrl || '') && (
845
+ <CapLabel type="label3" className="viber-carousel-image-recommendation">
846
+ {formatMessage(messages.carouselImageRecommendation)}
847
+ </CapLabel>
848
+ )}
849
+ <CapRow className="viber-carousel-card-title-header" type="flex" justify="space-between">
850
+ <CapHeading type="h5">
851
+ {formatMessage(messages.carouselCardTextLabel)}
852
+ </CapHeading>
853
+ <TagList
854
+ key={`viber_carousel_card_tags_${cardIndex}`}
855
+ className="tag-list-viber"
856
+ moduleFilterEnabled={location?.query?.type !== "embedded"}
857
+ label={formatMessage(messages.addLabels)}
858
+ onTagSelect={(data) => onCarouselCardTagSelect(cardIndex, data)}
859
+ onContextChange={handleOnTagsContextChange}
860
+ location={location}
861
+ tags={tags}
862
+ injectedTags={injectedTags || {}}
863
+ id={`viber_carousel_card_tags_${cardIndex}`}
864
+ userLocale={localStorage.getItem("jlocale") || "en"}
865
+ selectedOfferDetails={selectedOfferDetails}
866
+ eventContextTags={eventContextTags}
867
+ />
868
+ </CapRow>
869
+ <CapInput
870
+ value={card?.text || ''}
871
+ onChange={({ target: { value } }) => onCarouselCardChange(cardIndex, 'text', value)}
872
+ placeholder={formatMessage(messages.carouselCardTextPlaceholder)}
873
+ errorMessage={getCarouselCardTextError(card?.text, showCarouselValidationErrors)}
874
+ />
875
+ {renderLength((card?.text || '').length, VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH)}
876
+ <CapHeading type="h5" className="viber-carousel-button-label">
877
+ {formatMessage(messages.btnLabel)}
878
+ </CapHeading>
879
+ {(card?.buttons || []).map((button, buttonIndex) => (
880
+ <div className="viber-carousel-button" key={button.id}>
881
+ {button?.isSaved ? (
882
+ <CapRow className="viber-carousel-saved-button" align="middle" type="flex">
883
+ <CapIcon size="s" type="drag" className="viber-carousel-saved-button-icon" />
884
+ <CapIcon size="s" type="reply" className="viber-carousel-saved-button-icon" />
885
+ <CapLabel type="label4" className="viber-carousel-saved-button-text">
886
+ {button?.title}
887
+ </CapLabel>
888
+ <CapRow className="viber-carousel-saved-button-actions" align="middle" type="flex">
889
+ <CapColumn className="button-edit-icon" onClick={() => editCarouselButton(cardIndex, buttonIndex)}>
890
+ <CapIcon type="edit" size="s" />
891
+ </CapColumn>
892
+ {canRemoveCarouselButton(card) && (
893
+ <CapColumn
894
+ className="button-edit-icon viber-carousel-delete-icon"
895
+ onClick={() => removeCarouselButton(cardIndex, buttonIndex)}
896
+ >
897
+ <CapIcon type="delete" size="s" />
898
+ </CapColumn>
899
+ )}
900
+ </CapRow>
901
+ </CapRow>
902
+ ) : (
903
+ <div className="cta-section">
904
+ <CapRow
905
+ type="flex"
906
+ justify="space-between"
907
+ align="middle"
908
+ className="viber-carousel-button-title-header"
909
+ >
910
+ <CapHeading type="h5">
911
+ {formatMessage(messages.carouselButtonTitleLabel)}
912
+ </CapHeading>
913
+ </CapRow>
914
+ <CapInput
915
+ value={button?.title ?? ''}
916
+ onChange={({ target: { value } }) => onCarouselButtonChange(cardIndex, buttonIndex, 'title', value)}
917
+ placeholder={formatMessage(messages.carouselButtonTitlePlaceholder)}
918
+ errorMessage={getCarouselButtonTitleError(
919
+ button,
920
+ buttonIndex,
921
+ showCarouselValidationErrors || Boolean(button?.hasAttemptedSave),
922
+ )}
923
+ />
924
+ {renderLength((button?.title || '').length, getCarouselButtonTitleMaxLength(buttonIndex))}
925
+ <CapRow gutter={12}>
926
+ <CapColumn span={6}>
927
+ <CapHeading type="h4" className="cta-label">
928
+ {formatMessage(messages.carouselButtonUrlTypeLabel)}
929
+ </CapHeading>
930
+ <CapSelect
931
+ className="viber-carousel-url-type-select"
932
+ dropdownClassName="viber-carousel-url-type-dropdown"
933
+ dropdownMatchSelectWidth={false}
934
+ options={CAROUSEL_URL_TYPE_OPTIONS(formatMessage)}
935
+ value={button?.urlType || STATIC_URL}
936
+ onChange={(value) => onCarouselButtonChange(cardIndex, buttonIndex, 'urlType', value)}
937
+ />
938
+ </CapColumn>
939
+ <CapColumn span={18}>
940
+ <CapInput
941
+ label={formatMessage(messages.carouselButtonActionLabel)}
942
+ value={button?.action ?? ''}
943
+ onChange={({ target: { value } }) => onCarouselButtonChange(cardIndex, buttonIndex, 'action', value)}
944
+ onBlur={() => markCarouselButtonActionTouched(cardIndex, buttonIndex)}
945
+ placeholder={formatMessage(messages.carouselButtonActionPlaceholder)}
946
+ errorMessage={getCarouselButtonActionError(
947
+ button,
948
+ showCarouselValidationErrors
949
+ || Boolean(button?.hasAttemptedSave)
950
+ || Boolean(button?.hasTouchedAction),
951
+ )}
952
+ />
953
+ </CapColumn>
954
+ </CapRow>
955
+ <div className="cta-actions">
956
+ <CapButton
957
+ className="cta-btn-action"
958
+ onClick={() => saveCarouselButton(cardIndex, buttonIndex)}
959
+ >
960
+ {formatMessage(messages.save)}
961
+ </CapButton>
962
+ {canRemoveCarouselButton(card) && (
963
+ <CapButton
964
+ type="secondary"
965
+ onClick={() => removeCarouselButton(cardIndex, buttonIndex)}
966
+ >
967
+ {formatMessage(globalMessages.delete)}
968
+ </CapButton>
969
+ )}
970
+ </div>
971
+ </div>
972
+ )}
973
+ </div>
974
+ ))}
975
+ {(card?.buttons || []).length < VIBER_CAROUSEL_MAX_BUTTONS && (
976
+ <CapButton
977
+ type="flat"
978
+ className="viber-add-row-btn"
979
+ onClick={() => addCarouselButton(cardIndex)}
980
+ >
981
+ {formatMessage(messages.addCarouselButton)}
982
+ </CapButton>
983
+ )}
984
+ </div>
985
+ ),
986
+ });
987
+ })
988
+ );
989
+
990
+ const carouselTabOperations = (
991
+ <>
992
+ <CapDivider type="vertical" />
993
+ <CapButton
994
+ type="flat"
995
+ className="viber-carousel-tab-add-btn"
996
+ onClick={addCarouselCard}
997
+ disabled={
998
+ carouselCards.length >= VIBER_CAROUSEL_MAX_CARDS
999
+ || !isCarouselCardComplete(carouselCards[carouselCards.length - 1])
1000
+ }
1001
+ >
1002
+ <CapIcon type="plus" />
1003
+ </CapButton>
1004
+ </>
1005
+ );
1006
+
1007
+ const renderCarouselSection = () => (
1008
+ <div className="viber-carousel-section">
1009
+ <CapHeading type="h4" className="viber-render-heading">
1010
+ {formatMessage(messages.carouselCardsLabel)}
1011
+ </CapHeading>
1012
+ <CapRow className="viber-carousel-tab">
1013
+ <CapTab
1014
+ activeKey={`${activeCarouselCardIndex}`}
1015
+ tabBarExtraContent={carouselTabOperations}
1016
+ onChange={onCarouselTabChange}
1017
+ panes={getCarouselTabPanes()}
1018
+ />
1019
+ </CapRow>
1020
+ {isCarouselCardCountInvalid && (
1021
+ <CapLabel type="label3" className="viber-form-error">
1022
+ {formatMessage(messages.carouselCardsLimitError)}
1023
+ </CapLabel>
1024
+ )}
1025
+ {hasInvalidCarouselCard && (
1026
+ <CapLabel type="label3" className="viber-form-error">
1027
+ {formatMessage(messages.carouselCardError)}
1028
+ </CapLabel>
1029
+ )}
1030
+ {hasInvalidCarouselButton && (
1031
+ <CapLabel type="label3" className="viber-form-error">
1032
+ {formatMessage(messages.carouselButtonError)}
1033
+ </CapLabel>
1034
+ )}
1035
+ </div>
1036
+ );
1037
+
1038
+ const renderInteractiveSection = () => (
1039
+ <>
1040
+ {isMediaTypeCarousel && renderCarouselSection()}
1041
+ </>
1042
+ );
1043
+
437
1044
  // Button Code start here
438
1045
 
439
1046
  const isBtnTypeCta = buttonType === VIBER_BUTTON_TYPES.CTA;
@@ -457,6 +1064,11 @@ export const Viber = (props) => {
457
1064
  viberPreviewContent: {
458
1065
  ...(isMediaTypeImage && { imageURL: imageSrc }),
459
1066
  ...(isMediaTypeVideo && { videoParams: viberVideoSrcAndPreview }),
1067
+ ...(isMediaTypeCarousel && {
1068
+ cards: carouselCards,
1069
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1070
+ showCarouselEditorPreview: true,
1071
+ }),
460
1072
  buttonText,
461
1073
  messageContent,
462
1074
  },
@@ -480,6 +1092,11 @@ export const Viber = (props) => {
480
1092
  viberPreviewContent: {
481
1093
  ...(isMediaTypeImage && { imageURL: imageSrc }),
482
1094
  ...(isMediaTypeVideo && { videoParams: viberVideoSrcAndPreview }),
1095
+ ...(isMediaTypeCarousel && {
1096
+ cards: carouselCards,
1097
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1098
+ showCarouselEditorPreview: true,
1099
+ }),
483
1100
  buttonText: ctaData?.buttonText || buttonText,
484
1101
  messageContent,
485
1102
  },
@@ -502,8 +1119,19 @@ export const Viber = (props) => {
502
1119
  duration: viberVideoSrcAndPreview.duration,
503
1120
  },
504
1121
  }),
1122
+ ...(isMediaTypeCarousel && {
1123
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1124
+ cards: carouselCards.map((card) => ({
1125
+ text: card?.text ?? '',
1126
+ mediaUrl: card?.mediaUrl ?? '',
1127
+ buttons: (card?.buttons ?? []).map((button) => ({
1128
+ title: button?.title ?? '',
1129
+ action: button?.action ?? '',
1130
+ })),
1131
+ })),
1132
+ }),
505
1133
  // Add button if present (for payload)
506
- ...((ctaData?.buttonText || buttonText) && (ctaData?.buttonURL || buttonURL) && {
1134
+ ...(!isMediaTypeCarousel && (ctaData?.buttonText || buttonText) && (ctaData?.buttonURL || buttonURL) && {
507
1135
  button: {
508
1136
  text: ctaData?.buttonText || buttonText,
509
1137
  url: ctaData?.buttonURL || buttonURL,
@@ -521,7 +1149,20 @@ export const Viber = (props) => {
521
1149
  sender: viberData?.selectedViberAccount?.sender || 'test1',
522
1150
  };
523
1151
  return templateContent;
524
- }, [isMediaTypeImage, isMediaTypeVideo, imageSrc, viberVideoSrcAndPreview, ctaData, buttonText, buttonURL, messageContent, accountName, viberData]);
1152
+ }, [
1153
+ isMediaTypeImage,
1154
+ isMediaTypeVideo,
1155
+ isMediaTypeCarousel,
1156
+ imageSrc,
1157
+ viberVideoSrcAndPreview,
1158
+ carouselCards,
1159
+ ctaData,
1160
+ buttonText,
1161
+ buttonURL,
1162
+ messageContent,
1163
+ accountName,
1164
+ viberData,
1165
+ ]);
525
1166
 
526
1167
  // Handle Test and Preview button click
527
1168
  const handleTestAndPreview = useCallback(() => {
@@ -637,41 +1278,46 @@ export const Viber = (props) => {
637
1278
  setIsCtaSaved(false);
638
1279
  };
639
1280
 
640
- const renderButtonsSection = () => (
641
- <div className="button-section">
642
- <CapHeader
643
- className="viber-render-heading"
644
- title={(
645
- <CapRow type="flex">
646
- <CapHeading type="h4">
647
- {formatMessage(messages.btnLabel)}
648
- </CapHeading>
649
- <CapHeading className="viber-optional-label">
650
- {formatMessage(messages.optional)}
651
- </CapHeading>
652
- </CapRow>
653
- )}
654
- description={
655
- <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
656
- }
657
- />
658
- {templateMediaType === VIBER_MEDIA_TYPES.VIDEO && (
659
- <CapLabel type="label3">
660
- {formatMessage(messages.videoButtonDisabled)}
661
- </CapLabel>
662
- )}
663
- <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
664
- <CapRadioGroup
665
- options={buttonRadioOptions}
666
- value={buttonType}
667
- onChange={onChangeButtonType}
668
- disabled={templateMediaType === VIBER_MEDIA_TYPES.VIDEO}
669
- className="viber-btn-radio-group"
1281
+ const renderButtonsSection = () => {
1282
+ if (isMediaTypeCarousel) {
1283
+ return null;
1284
+ }
1285
+ return (
1286
+ <CapRow type="flex" vertical className="button-section">
1287
+ <CapHeader
1288
+ className="viber-render-heading"
1289
+ title={(
1290
+ <CapRow type="flex">
1291
+ <CapHeading type="h4">
1292
+ {formatMessage(messages.btnLabel)}
1293
+ </CapHeading>
1294
+ <CapHeading className="viber-optional-label">
1295
+ {formatMessage(messages.optional)}
1296
+ </CapHeading>
1297
+ </CapRow>
1298
+ )}
1299
+ description={
1300
+ <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
1301
+ }
670
1302
  />
671
- </ConfigProvider>
672
- {isBtnTypeCta && ButtonViber}
673
- </div>
674
- );
1303
+ {templateMediaType === VIBER_MEDIA_TYPES.VIDEO && (
1304
+ <CapLabel type="label3">
1305
+ {formatMessage(messages.videoButtonDisabled)}
1306
+ </CapLabel>
1307
+ )}
1308
+ <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
1309
+ <CapRadioGroup
1310
+ options={buttonRadioOptions}
1311
+ value={buttonType}
1312
+ onChange={onChangeButtonType}
1313
+ disabled={templateMediaType === VIBER_MEDIA_TYPES.VIDEO}
1314
+ className="viber-btn-radio-group"
1315
+ />
1316
+ </ConfigProvider>
1317
+ {isBtnTypeCta && ButtonViber}
1318
+ </CapRow>
1319
+ );
1320
+ };
675
1321
  // Button Code End here
676
1322
 
677
1323
  // to generate payload for create and edit
@@ -690,8 +1336,19 @@ export const Viber = (props) => {
690
1336
  messageData.video.thumbnailUrl = viberVideoPreviewImg;
691
1337
  messageData.video.duration = duration; // integer value in seconds
692
1338
  }
1339
+ if (isMediaTypeCarousel) {
1340
+ messageData.type = VIBER_MEDIA_TYPES.CAROUSEL;
1341
+ messageData.cards = carouselCards.map((card) => ({
1342
+ text: card?.text ?? '',
1343
+ mediaUrl: card?.mediaUrl ?? '',
1344
+ buttons: (card?.buttons ?? []).map((button) => ({
1345
+ title: button?.title ?? '',
1346
+ action: button?.action ?? '',
1347
+ })),
1348
+ }));
1349
+ }
693
1350
 
694
- if (!isEmpty(ctaData)) {
1351
+ if (!isMediaTypeCarousel && !isEmpty(ctaData)) {
695
1352
  messageData.button = {};
696
1353
  messageData.button.text = ctaData?.buttonText;
697
1354
  messageData.button.url = ctaData?.buttonURL;
@@ -787,6 +1444,20 @@ export const Viber = (props) => {
787
1444
  });
788
1445
  };
789
1446
 
1447
+ const hasCarouselValidationError = isCarouselCardCountInvalid || hasInvalidCarouselCard || hasInvalidCarouselButton;
1448
+ const getDoneHandler = () => {
1449
+ const doneCallback = onDoneCallback();
1450
+ return () => {
1451
+ if (isMediaTypeCarousel) {
1452
+ setShowCarouselValidationErrors(true);
1453
+ if (hasCarouselValidationError) {
1454
+ return;
1455
+ }
1456
+ }
1457
+ doneCallback();
1458
+ };
1459
+ };
1460
+
790
1461
  const isDisableDone = () => {
791
1462
  // textbox area should not empty and should have max 1000 charactor
792
1463
  if (messageContent?.trim() === '' || errorMessageTextarea) {
@@ -807,6 +1478,9 @@ export const Viber = (props) => {
807
1478
  if ((isMediaTypeImage || isMediaTypeVideo) && viber?.assetUploading) {
808
1479
  return true;
809
1480
  }
1481
+ if (isMediaTypeCarousel && (isCarouselCardCountInvalid || hasInvalidCarouselCard || hasInvalidCarouselButton)) {
1482
+ return true;
1483
+ }
810
1484
  if (isBtnTypeCta && !isCtaSaved) {
811
1485
  return true;
812
1486
  }
@@ -836,6 +1510,7 @@ export const Viber = (props) => {
836
1510
  {renderMediaSection()}
837
1511
  {renderMediaComponent()}
838
1512
  {renderTextAreaViber()}
1513
+ {renderInteractiveSection()}
839
1514
  {renderButtonsSection()}
840
1515
  <div style={{marginBottom: '100px'}} />
841
1516
  </CapColumn>
@@ -865,7 +1540,7 @@ export const Viber = (props) => {
865
1540
  </CapRow>
866
1541
  <ViberFooter>
867
1542
  <CapButton
868
- onClick={onDoneCallback()}
1543
+ onClick={getDoneHandler()}
869
1544
  disabled={isDisableDone()}
870
1545
  className="create-msg viber-create-msg"
871
1546
  >