@capillarytech/creatives-library 9.0.13-alpha.0 → 9.0.13-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.
Files changed (60) hide show
  1. package/constants/unified.js +0 -3
  2. package/package.json +1 -1
  3. package/utils/common.js +0 -8
  4. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberCarouselPreviewCards.js +132 -0
  5. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +108 -15
  6. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +141 -1
  7. package/v2Components/CommonTestAndPreview/UnifiedPreview/_viberCarouselPreviewCards.scss +132 -0
  8. package/v2Components/CommonTestAndPreview/index.js +244 -26
  9. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +364 -0
  10. package/v2Components/FormBuilder/_formBuilder.scss +0 -8
  11. package/v2Components/FormBuilder/index.js +4479 -41
  12. package/v2Containers/Templates/_templates.scss +83 -0
  13. package/v2Containers/Templates/index.js +90 -10
  14. package/v2Containers/Viber/constants.js +21 -0
  15. package/v2Containers/Viber/index.js +719 -49
  16. package/v2Containers/Viber/index.scss +175 -0
  17. package/v2Containers/Viber/messages.js +121 -0
  18. package/v2Containers/Viber/tests/index.test.js +80 -0
  19. package/v2Components/FormBuilder/Classic.js +0 -4487
  20. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +0 -369
  21. package/v2Components/FormBuilder/Functional/channels/registry.js +0 -17
  22. package/v2Components/FormBuilder/Functional/channels/sms/buildSubmitPayload.js +0 -9
  23. package/v2Components/FormBuilder/Functional/channels/sms/config.js +0 -30
  24. package/v2Components/FormBuilder/Functional/channels/sms/getEditorErrorDescriptor.js +0 -46
  25. package/v2Components/FormBuilder/Functional/channels/sms/getLiquidContent.js +0 -13
  26. package/v2Components/FormBuilder/Functional/channels/sms/index.js +0 -22
  27. package/v2Components/FormBuilder/Functional/channels/sms/tests/getEditorErrorDescriptor.test.js +0 -52
  28. package/v2Components/FormBuilder/Functional/channels/sms/tests/getLiquidContent.test.js +0 -25
  29. package/v2Components/FormBuilder/Functional/channels/sms/tests/validate.test.js +0 -87
  30. package/v2Components/FormBuilder/Functional/channels/sms/validate.js +0 -89
  31. package/v2Components/FormBuilder/Functional/constants.js +0 -39
  32. package/v2Components/FormBuilder/Functional/core/schema/fieldRegistry.js +0 -38
  33. package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +0 -85
  34. package/v2Components/FormBuilder/Functional/core/store/formReducer.js +0 -81
  35. package/v2Components/FormBuilder/Functional/core/store/selectors.js +0 -30
  36. package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +0 -91
  37. package/v2Components/FormBuilder/Functional/index.js +0 -26
  38. package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +0 -59
  39. package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +0 -32
  40. package/v2Components/FormBuilder/Functional/layout/Section.js +0 -118
  41. package/v2Components/FormBuilder/Functional/renderers/smsRenderers.js +0 -265
  42. package/v2Components/FormBuilder/Functional/tests/channelRegistry.test.js +0 -21
  43. package/v2Components/FormBuilder/Functional/tests/fieldRegistry.test.js +0 -65
  44. package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +0 -97
  45. package/v2Components/FormBuilder/Functional/tests/fixtures/smsParityCases.js +0 -192
  46. package/v2Components/FormBuilder/Functional/tests/formReducer.test.js +0 -129
  47. package/v2Components/FormBuilder/Functional/tests/initializeFormState.test.js +0 -132
  48. package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +0 -40
  49. package/v2Components/FormBuilder/Functional/tests/section.test.js +0 -99
  50. package/v2Components/FormBuilder/Functional/tests/selectors.test.js +0 -67
  51. package/v2Components/FormBuilder/Functional/tests/sms.crossFlowParity.test.js +0 -155
  52. package/v2Components/FormBuilder/Functional/tests/sms.liquid.test.js +0 -172
  53. package/v2Components/FormBuilder/Functional/tests/sms.rollout.test.js +0 -122
  54. package/v2Components/FormBuilder/Functional/tests/sms.shell.parity.test.js +0 -329
  55. package/v2Components/FormBuilder/Functional/tests/smsRenderers.test.js +0 -162
  56. package/v2Components/FormBuilder/Functional/tests/toLegacyFormData.test.js +0 -95
  57. package/v2Components/FormBuilder/tests/__snapshots__/sms.characterization.test.js.snap +0 -114
  58. package/v2Components/FormBuilder/tests/entryGate.test.js +0 -106
  59. package/v2Components/FormBuilder/tests/sms.characterization.test.js +0 -336
  60. package/v2Components/TemplatePreview/coderabbits_comments +0 -171
@@ -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,34 @@ 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
+ const getNextCarouselCardId = () => {
89
+ const nextId = `viber-carousel-card-${carouselCardIdSeed}`;
90
+ carouselCardIdSeed += 1;
91
+ return nextId;
92
+ };
93
+ const createEmptyCarouselButton = () => ({
94
+ title: '',
95
+ action: '',
96
+ urlType: STATIC_URL,
97
+ isSaved: false,
98
+ hasAttemptedSave: false,
99
+ hasTouchedAction: false,
100
+ });
101
+ const createEmptyCarouselCard = () => ({
102
+ id: getNextCarouselCardId(),
103
+ text: '',
104
+ mediaUrl: '',
105
+ buttons: [createEmptyCarouselButton()],
106
+ });
107
+ const createDefaultCarouselCards = () => [
108
+ createEmptyCarouselCard(),
109
+ createEmptyCarouselCard(),
110
+ ];
67
111
 
68
112
  export const Viber = (props) => {
69
113
  const {
@@ -112,6 +156,9 @@ export const Viber = (props) => {
112
156
  viberVideoPreviewImg: '',
113
157
  duration: 0,
114
158
  });
159
+ const [carouselCards, setCarouselCards] = useState(() => createDefaultCarouselCards());
160
+ const [activeCarouselCardIndex, setActiveCarouselCardIndex] = useState(0);
161
+ const [showCarouselValidationErrors, setShowCarouselValidationErrors] = useState(false);
115
162
  // cta button
116
163
  const [buttonType, setButtonType] = useState(NONE);
117
164
  const [ctaData, setCtadata] = useState({});
@@ -149,18 +196,38 @@ export const Viber = (props) => {
149
196
  button = {},
150
197
  image = {},
151
198
  video = {},
199
+ cards = [],
200
+ type = "",
152
201
  } = editViberContent || {};
153
202
  const { text: ctaBtnText = "", url = "" } = button || {};
154
203
  updateTextMessageTitle(editMessageTitle);
155
204
  updateTextMessageContent(text || "");
156
205
  updateButtonText(ctaBtnText);
157
206
  updateButtonUrl(url);
158
- setIsCtaSaved(true);
207
+ setIsCtaSaved(!isEmpty(button));
159
208
  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)) {
209
+ setCtadata(!isEmpty(button) ? { buttonText: button?.text, buttonURL: button?.url } : {});
210
+ if (type === VIBER_MEDIA_TYPES.CAROUSEL) {
211
+ const normalizedCards = (cards || []).map((card) => ({
212
+ id: card?.id || getNextCarouselCardId(),
213
+ text: card?.text || '',
214
+ mediaUrl: card?.mediaUrl || '',
215
+ buttons: ((card?.buttons || []).length ? card.buttons : [createEmptyCarouselButton()]).map((carouselButton) => ({
216
+ title: carouselButton?.title || '',
217
+ action: carouselButton?.action || '',
218
+ urlType: carouselButton?.urlType || STATIC_URL,
219
+ isSaved: Boolean(carouselButton?.title && carouselButton?.action),
220
+ hasAttemptedSave: Boolean(carouselButton?.hasAttemptedSave),
221
+ hasTouchedAction: Boolean(carouselButton?.hasTouchedAction),
222
+ })),
223
+ }));
224
+ const cardsToSet = normalizedCards.length
225
+ ? normalizedCards
226
+ : createDefaultCarouselCards();
227
+ setTemplateMediaType(VIBER_MEDIA_TYPES.CAROUSEL);
228
+ setCarouselCards(cardsToSet.slice(0, VIBER_CAROUSEL_MAX_CARDS));
229
+ setActiveCarouselCardIndex(0);
230
+ } else if (!isEmpty(image)) {
164
231
  setTemplateMediaType(VIBER_MEDIA_TYPES.IMAGE);
165
232
  updateImageSrc(image?.url);
166
233
  } else if (!isEmpty(video)) {
@@ -208,6 +275,17 @@ export const Viber = (props) => {
208
275
  updateButtonUrl(newUrl);
209
276
  onChangeButtonUrl({ target: { value: newUrl } });
210
277
  };
278
+ const onCarouselCardTagSelect = (cardIndex, data) => {
279
+ setCarouselCards((prevCards) => prevCards.map((card, index) => {
280
+ if (index !== cardIndex) {
281
+ return card;
282
+ }
283
+ return {
284
+ ...card,
285
+ text: `${card?.text || ''}{{${data}}}`,
286
+ };
287
+ }));
288
+ };
211
289
 
212
290
  const handleOnTagsContextChange = (data) => {
213
291
  const query = {
@@ -336,15 +414,127 @@ export const Viber = (props) => {
336
414
  // template media code start here
337
415
  const isMediaTypeImage = templateMediaType === VIBER_MEDIA_TYPES.IMAGE;
338
416
  const isMediaTypeVideo = templateMediaType === VIBER_MEDIA_TYPES.VIDEO;
417
+ const isMediaTypeCarousel = templateMediaType === VIBER_MEDIA_TYPES.CAROUSEL;
418
+ const getCarouselButtonTitleMaxLength = (buttonIndex) => (
419
+ buttonIndex === 0
420
+ ? VIBER_CAROUSEL_FIRST_BUTTON_TITLE_MAX_LENGTH
421
+ : VIBER_CAROUSEL_SECOND_BUTTON_TITLE_MAX_LENGTH
422
+ );
423
+ const renderLength = (len, max) => (
424
+ <CapHeading type="label1" className="viber-carousel-field-length-top">
425
+ {`${len || 0}/${max} `}
426
+ <FormattedMessage {...messages.characters} />
427
+ </CapHeading>
428
+ );
429
+ const getCarouselCardTextError = (cardText = '', showEmptyError = true) => {
430
+ const trimmedCardText = (cardText || '').trim();
431
+ if (!trimmedCardText && showEmptyError) {
432
+ return formatMessage(messages.textCannotBeEmptyError);
433
+ }
434
+ if (!trimmedCardText) {
435
+ return false;
436
+ }
437
+ if (trimmedCardText?.length < VIBER_CAROUSEL_CARD_TITLE_MIN_LENGTH) {
438
+ return formatMessage(messages.carouselCardTitleMinLengthError);
439
+ }
440
+ if (cardText.length > VIBER_CAROUSEL_CARD_TITLE_MAX_LENGTH) {
441
+ return formatMessage(messages.carouselCardTitleMaxLengthError);
442
+ }
443
+ return false;
444
+ };
445
+ const getCarouselButtonTitleError = (button = {}, buttonIndex = 0, showEmptyError = true) => {
446
+ const title = button?.title || '';
447
+ const trimmedTitle = title.trim();
448
+ const maxLength = getCarouselButtonTitleMaxLength(buttonIndex);
449
+ if (!trimmedTitle && showEmptyError) {
450
+ return formatMessage(messages.textCannotBeEmptyError);
451
+ }
452
+ if (!trimmedTitle) {
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 = {}, showEmptyError = true) => {
463
+ const action = button?.action || '';
464
+ if (!action?.trim() && showEmptyError) {
465
+ return formatMessage(messages.urlCannotBeEmptyError);
466
+ }
467
+ if (!action?.trim()) {
468
+ return false;
469
+ }
470
+ if (action.length > VIBER_CAROUSEL_BUTTON_URL_MAX_LENGTH) {
471
+ return formatMessage(messages.carouselButtonUrlMaxLengthError);
472
+ }
473
+ if (!isUrl(action)) {
474
+ return formatMessage(messages.inValidUrliErrorMessage);
475
+ }
476
+ return false;
477
+ };
478
+ const hasInvalidCarouselCard = carouselCards.some(
479
+ (card) => Boolean(getCarouselCardTextError(card?.text)) || !isUrl(card?.mediaUrl || '')
480
+ );
481
+ const hasInvalidCarouselButton = carouselCards.some((card) => (card?.buttons || []).some(
482
+ (button, buttonIndex) => {
483
+ const hasAnyValue = Boolean(button?.title || button?.action);
484
+ if (buttonIndex !== 0 && !hasAnyValue) {
485
+ return false;
486
+ }
487
+ return Boolean(getCarouselButtonTitleError(button, buttonIndex))
488
+ || Boolean(getCarouselButtonActionError(button))
489
+ || !button?.isSaved;
490
+ }
491
+ ));
492
+ const isCarouselCardCountInvalid = carouselCards.length < VIBER_CAROUSEL_MIN_CARDS
493
+ || carouselCards.length > VIBER_CAROUSEL_MAX_CARDS;
494
+ const isCarouselButtonComplete = (button, buttonIndex) => {
495
+ const hasAnyValue = Boolean(button?.title || button?.action);
496
+ if (buttonIndex !== 0 && !hasAnyValue) {
497
+ return true;
498
+ }
499
+ return !getCarouselButtonTitleError(button, buttonIndex)
500
+ && !getCarouselButtonActionError(button)
501
+ && Boolean(button?.isSaved);
502
+ };
503
+ const isCarouselCardComplete = (card = {}) => (
504
+ !getCarouselCardTextError(card?.text)
505
+ && isUrl(card?.mediaUrl || '')
506
+ && (card?.buttons || []).every((button, buttonIndex) => isCarouselButtonComplete(button, buttonIndex))
507
+ );
508
+ const canAccessCarouselCardAtIndex = (targetIndex) => (
509
+ carouselCards.slice(0, targetIndex).every((card) => isCarouselCardComplete(card))
510
+ );
511
+ const isCarouselTabDisabled = (cardIndex) => (
512
+ cardIndex > 0 && !canAccessCarouselCardAtIndex(cardIndex)
513
+ );
339
514
 
340
515
  const onTemplateMediaTypeChange = ({ target: { value } }) => {
341
516
  setTemplateMediaType(value);
517
+ if (value === VIBER_MEDIA_TYPES.CAROUSEL && carouselCards.length === 0) {
518
+ setCarouselCards(createDefaultCarouselCards());
519
+ setActiveCarouselCardIndex(0);
520
+ }
521
+ if ([VIBER_MEDIA_TYPES.VIDEO, VIBER_MEDIA_TYPES.CAROUSEL].includes(value)) {
522
+ setButtonType(NONE);
523
+ setCtadata({});
524
+ updateButtonText('');
525
+ updateButtonUrl('');
526
+ setIsCtaSaved(false);
527
+ }
342
528
  };
343
529
 
344
530
  const uploadViberAsset = (file, type, fileParams) => {
345
531
  actions.uploadViberAsset(file, type, fileParams, 0);
346
532
  };
347
533
 
534
+ const uploadViberAssetByIndex = (file, type, fileParams, templateType = 0) => {
535
+ actions.uploadViberAsset(file, type, fileParams, templateType);
536
+ };
537
+
348
538
  const updateOnViberImageReUpload = useCallback(() => {
349
539
  setImageSrc("");
350
540
  }, [imageSrc]);
@@ -434,6 +624,416 @@ export const Viber = (props) => {
434
624
  );
435
625
  // template media code end here
436
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
+ {formatMessage(messages.carouselImageRecommendation)}
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="drag" 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
+
437
1037
  // Button Code start here
438
1038
 
439
1039
  const isBtnTypeCta = buttonType === VIBER_BUTTON_TYPES.CTA;
@@ -457,6 +1057,11 @@ export const Viber = (props) => {
457
1057
  viberPreviewContent: {
458
1058
  ...(isMediaTypeImage && { imageURL: imageSrc }),
459
1059
  ...(isMediaTypeVideo && { videoParams: viberVideoSrcAndPreview }),
1060
+ ...(isMediaTypeCarousel && {
1061
+ cards: carouselCards,
1062
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1063
+ showCarouselEditorPreview: true,
1064
+ }),
460
1065
  buttonText,
461
1066
  messageContent,
462
1067
  },
@@ -480,6 +1085,11 @@ export const Viber = (props) => {
480
1085
  viberPreviewContent: {
481
1086
  ...(isMediaTypeImage && { imageURL: imageSrc }),
482
1087
  ...(isMediaTypeVideo && { videoParams: viberVideoSrcAndPreview }),
1088
+ ...(isMediaTypeCarousel && {
1089
+ cards: carouselCards,
1090
+ type: VIBER_MEDIA_TYPES.CAROUSEL,
1091
+ showCarouselEditorPreview: true,
1092
+ }),
483
1093
  buttonText: ctaData?.buttonText || buttonText,
484
1094
  messageContent,
485
1095
  },
@@ -502,8 +1112,19 @@ export const Viber = (props) => {
502
1112
  duration: viberVideoSrcAndPreview.duration,
503
1113
  },
504
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
+ }),
505
1126
  // Add button if present (for payload)
506
- ...((ctaData?.buttonText || buttonText) && (ctaData?.buttonURL || buttonURL) && {
1127
+ ...(!isMediaTypeCarousel && (ctaData?.buttonText || buttonText) && (ctaData?.buttonURL || buttonURL) && {
507
1128
  button: {
508
1129
  text: ctaData?.buttonText || buttonText,
509
1130
  url: ctaData?.buttonURL || buttonURL,
@@ -521,7 +1142,20 @@ export const Viber = (props) => {
521
1142
  sender: viberData?.selectedViberAccount?.sender || 'test1',
522
1143
  };
523
1144
  return templateContent;
524
- }, [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
+ ]);
525
1159
 
526
1160
  // Handle Test and Preview button click
527
1161
  const handleTestAndPreview = useCallback(() => {
@@ -637,41 +1271,46 @@ export const Viber = (props) => {
637
1271
  setIsCtaSaved(false);
638
1272
  };
639
1273
 
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"
1274
+ const renderButtonsSection = () => {
1275
+ if (isMediaTypeCarousel) {
1276
+ return null;
1277
+ }
1278
+ return (
1279
+ <div className="button-section">
1280
+ <CapHeader
1281
+ className="viber-render-heading"
1282
+ title={(
1283
+ <CapRow type="flex">
1284
+ <CapHeading type="h4">
1285
+ {formatMessage(messages.btnLabel)}
1286
+ </CapHeading>
1287
+ <CapHeading className="viber-optional-label">
1288
+ {formatMessage(messages.optional)}
1289
+ </CapHeading>
1290
+ </CapRow>
1291
+ )}
1292
+ description={
1293
+ <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
1294
+ }
670
1295
  />
671
- </ConfigProvider>
672
- {isBtnTypeCta && ButtonViber}
673
- </div>
674
- );
1296
+ {templateMediaType === VIBER_MEDIA_TYPES.VIDEO && (
1297
+ <CapLabel type="label3">
1298
+ {formatMessage(messages.videoButtonDisabled)}
1299
+ </CapLabel>
1300
+ )}
1301
+ <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
1302
+ <CapRadioGroup
1303
+ options={buttonRadioOptions}
1304
+ value={buttonType}
1305
+ onChange={onChangeButtonType}
1306
+ disabled={templateMediaType === VIBER_MEDIA_TYPES.VIDEO}
1307
+ className="viber-btn-radio-group"
1308
+ />
1309
+ </ConfigProvider>
1310
+ {isBtnTypeCta && ButtonViber}
1311
+ </div>
1312
+ );
1313
+ };
675
1314
  // Button Code End here
676
1315
 
677
1316
  // to generate payload for create and edit
@@ -690,8 +1329,19 @@ export const Viber = (props) => {
690
1329
  messageData.video.thumbnailUrl = viberVideoPreviewImg;
691
1330
  messageData.video.duration = duration; // integer value in seconds
692
1331
  }
1332
+ if (isMediaTypeCarousel) {
1333
+ messageData.type = VIBER_MEDIA_TYPES.CAROUSEL;
1334
+ messageData.cards = carouselCards.map((card) => ({
1335
+ text: card?.text ?? '',
1336
+ mediaUrl: card?.mediaUrl ?? '',
1337
+ buttons: (card?.buttons ?? []).map((button) => ({
1338
+ title: button?.title ?? '',
1339
+ action: button?.action ?? '',
1340
+ })),
1341
+ }));
1342
+ }
693
1343
 
694
- if (!isEmpty(ctaData)) {
1344
+ if (!isMediaTypeCarousel && !isEmpty(ctaData)) {
695
1345
  messageData.button = {};
696
1346
  messageData.button.text = ctaData?.buttonText;
697
1347
  messageData.button.url = ctaData?.buttonURL;
@@ -700,13 +1350,15 @@ export const Viber = (props) => {
700
1350
  versions: {
701
1351
  base: {
702
1352
  content: {
703
- destinations: [
704
- {
705
- to: {
706
- phoneNumber: "{{viber_user_id}}",
1353
+ ...(isMediaTypeCarousel && {
1354
+ destinations: [
1355
+ {
1356
+ to: {
1357
+ phoneNumber: "{{viber_user_id}}",
1358
+ },
707
1359
  },
708
- },
709
- ],
1360
+ ],
1361
+ }),
710
1362
  content: {...messageData},
711
1363
  },
712
1364
  },
@@ -787,6 +1439,20 @@ export const Viber = (props) => {
787
1439
  });
788
1440
  };
789
1441
 
1442
+ const hasCarouselValidationError = isCarouselCardCountInvalid || hasInvalidCarouselCard || hasInvalidCarouselButton;
1443
+ const getDoneHandler = () => {
1444
+ const doneCallback = onDoneCallback();
1445
+ return () => {
1446
+ if (isMediaTypeCarousel) {
1447
+ setShowCarouselValidationErrors(true);
1448
+ if (hasCarouselValidationError) {
1449
+ return;
1450
+ }
1451
+ }
1452
+ doneCallback();
1453
+ };
1454
+ };
1455
+
790
1456
  const isDisableDone = () => {
791
1457
  // textbox area should not empty and should have max 1000 charactor
792
1458
  if (messageContent?.trim() === '' || errorMessageTextarea) {
@@ -807,6 +1473,9 @@ export const Viber = (props) => {
807
1473
  if ((isMediaTypeImage || isMediaTypeVideo) && viber?.assetUploading) {
808
1474
  return true;
809
1475
  }
1476
+ if (isMediaTypeCarousel && (isCarouselCardCountInvalid || hasInvalidCarouselCard || hasInvalidCarouselButton)) {
1477
+ return true;
1478
+ }
810
1479
  if (isBtnTypeCta && !isCtaSaved) {
811
1480
  return true;
812
1481
  }
@@ -836,6 +1505,7 @@ export const Viber = (props) => {
836
1505
  {renderMediaSection()}
837
1506
  {renderMediaComponent()}
838
1507
  {renderTextAreaViber()}
1508
+ {renderInteractiveSection()}
839
1509
  {renderButtonsSection()}
840
1510
  <div style={{marginBottom: '100px'}} />
841
1511
  </CapColumn>
@@ -865,7 +1535,7 @@ export const Viber = (props) => {
865
1535
  </CapRow>
866
1536
  <ViberFooter>
867
1537
  <CapButton
868
- onClick={onDoneCallback()}
1538
+ onClick={getDoneHandler()}
869
1539
  disabled={isDisableDone()}
870
1540
  className="create-msg viber-create-msg"
871
1541
  >