@capillarytech/creatives-library 8.0.330-alpha.0 → 8.0.330-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 (75) hide show
  1. package/package.json +1 -1
  2. package/utils/tests/tagValidations.test.js +20 -0
  3. package/v2Components/CapActionButton/constants.js +7 -0
  4. package/v2Components/CapActionButton/index.js +167 -109
  5. package/v2Components/CapActionButton/index.scss +157 -6
  6. package/v2Components/CapActionButton/messages.js +19 -3
  7. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  8. package/v2Components/CapTagList/index.js +28 -23
  9. package/v2Components/CapTagList/style.scss +29 -0
  10. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  11. package/v2Components/CapTagListWithInput/index.js +4 -0
  12. package/v2Components/CapWhatsappCTA/index.js +2 -0
  13. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +1 -0
  14. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
  15. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
  16. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +323 -77
  17. package/v2Components/CommonTestAndPreview/messages.js +8 -0
  18. package/v2Components/CommonTestAndPreview/reducer.js +3 -1
  19. package/v2Components/CommonTestAndPreview/sagas.js +2 -1
  20. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  21. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  22. package/v2Components/FormBuilder/index.js +1 -0
  23. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  24. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  25. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  26. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  27. package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
  28. package/v2Components/TemplatePreview/constants.js +2 -0
  29. package/v2Components/TemplatePreview/index.js +143 -28
  30. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  31. package/v2Components/mockdata.js +1 -0
  32. package/v2Containers/BeeEditor/index.js +19 -1
  33. package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
  34. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -0
  35. package/v2Containers/Email/index.js +78 -39
  36. package/v2Containers/Email/reducer.js +2 -2
  37. package/v2Containers/Email/sagas.js +3 -1
  38. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -2
  39. package/v2Containers/Email/tests/sagas.test.js +230 -0
  40. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +6 -1
  41. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  42. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  43. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  44. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -1
  45. package/v2Containers/EmailWrapper/index.js +4 -0
  46. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  47. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  48. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +1 -0
  49. package/v2Containers/MobilePush/Create/index.js +2 -0
  50. package/v2Containers/MobilePush/Edit/index.js +2 -0
  51. package/v2Containers/MobilepushWrapper/index.js +3 -1
  52. package/v2Containers/Rcs/constants.js +79 -5
  53. package/v2Containers/Rcs/index.js +1374 -73
  54. package/v2Containers/Rcs/index.js.rej +1336 -0
  55. package/v2Containers/Rcs/index.scss +191 -0
  56. package/v2Containers/Rcs/index.scss.rej +74 -0
  57. package/v2Containers/Rcs/messages.js +26 -1
  58. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +69173 -118166
  59. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
  60. package/v2Containers/Rcs/tests/index.test.js +132 -94
  61. package/v2Containers/Rcs/tests/utils.test.js +220 -38
  62. package/v2Containers/Rcs/utils.js +77 -1
  63. package/v2Containers/Sms/Edit/index.js +2 -0
  64. package/v2Containers/SmsWrapper/index.js +2 -0
  65. package/v2Containers/TagList/index.js +73 -20
  66. package/v2Containers/TagList/messages.js +4 -0
  67. package/v2Containers/TagList/tests/TagList.test.js +124 -20
  68. package/v2Containers/TagList/tests/mockdata.js +17 -0
  69. package/v2Containers/Templates/_templates.scss +99 -0
  70. package/v2Containers/Templates/index.js +29 -14
  71. package/v2Containers/Viber/index.js +3 -0
  72. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  73. package/v2Containers/WebPush/Create/index.js +10 -2
  74. package/v2Containers/Whatsapp/index.js +5 -0
  75. package/v2Containers/Zalo/index.js +2 -0
@@ -21,20 +21,42 @@ import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
21
21
  import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
22
22
  import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
23
23
  import CapImage from '@capillarytech/cap-ui-library/CapImage';
24
+ import CapCard from '@capillarytech/cap-ui-library/CapCard';
24
25
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
25
26
  import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
27
+ import CapCustomCard from '@capillarytech/cap-ui-library/CapCustomCard';
28
+ import CapDropdown from '@capillarytech/cap-ui-library/CapDropdown';
29
+ import CapMenu from '@capillarytech/cap-ui-library/CapMenu';
26
30
  import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
31
+ import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
27
32
  import CapError from '@capillarytech/cap-ui-library/CapError';
33
+ import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
28
34
  import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
35
+ import CapLink from '@capillarytech/cap-ui-library/CapLink';
36
+ import CapTab from '@capillarytech/cap-ui-library/CapTab';
37
+
38
+ import {
39
+ CAP_G01,
40
+ CAP_SPACE_04,
41
+ CAP_SPACE_16,
42
+ CAP_SPACE_24,
43
+ CAP_SPACE_28,
44
+ CAP_SPACE_32,
45
+ CAP_WHITE,
46
+ CAP_SECONDARY,
47
+ } from '@capillarytech/cap-ui-library/styled/variables';
29
48
 
30
49
  import CapVideoUpload from '../../v2Components/CapVideoUpload';
31
50
  import * as globalActions from '../Cap/actions';
32
51
  import CapActionButton from '../../v2Components/CapActionButton';
52
+ import TemplatePreview from '../../v2Components/TemplatePreview';
33
53
  import { makeSelectRcs, makeSelectAccount } from './selectors';
54
+ import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
34
55
  import {
35
56
  isLoadingMetaEntities,
36
57
  makeSelectMetaEntities,
37
58
  setInjectedTags,
59
+ selectCurrentOrgDetails,
38
60
  } from '../Cap/selectors';
39
61
  import * as RcsActions from './actions';
40
62
  import { isAiContentBotDisabled } from '../../utils/common';
@@ -67,11 +89,14 @@ import {
67
89
  RCS_IMG_SIZE,
68
90
  RCS_DLT_MODE,
69
91
  CTA,
92
+ AI_CONTENT_BOT_DISABLED,
70
93
  RCS_STATUSES,
71
94
  TITLE_TEXT,
72
95
  MESSAGE_TEXT,
73
96
  ALLOWED_EXTENSIONS_VIDEO_REGEX,
74
97
  RCS_VIDEO_SIZE,
98
+ TEMPLATE_HEADER_MAX_LENGTH,
99
+ TEMPLATE_MESSAGE_MAX_LENGTH,
75
100
  RCS_THUMBNAIL_MIN_SIZE,
76
101
  RCS_THUMBNAIL_MAX_SIZE,
77
102
  contentType,
@@ -79,16 +104,30 @@ import {
79
104
  rcsVarTestRegex,
80
105
  RCS_IMAGE_DIMENSIONS,
81
106
  RCS_TEXT_MESSAGE_MAX_LENGTH,
107
+ RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP,
82
108
  RCS_RICH_CARD_MAX_LENGTH,
83
109
  RCS_VIDEO_THUMBNAIL_DIMENSIONS,
110
+ RCS_CAROUSEL_IMAGE_DIMENSIONS,
111
+ RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS,
112
+ RCS_CAROUSEL_IMG_SIZE,
113
+ RCS_CAROUSEL_VIDEO_SIZE,
84
114
  MAX_BUTTONS,
115
+ INITIAL_SUGGESTIONS,
85
116
  INITIAL_SUGGESTIONS_DATA_STOP,
86
117
  RCS_BUTTON_TYPES,
118
+ titletype,
119
+ descType,
87
120
  STANDALONE,
88
121
  VERTICAL,
89
122
  SMALL,
90
123
  MEDIUM,
91
124
  RICHCARD,
125
+ HOST_INFOBIP,
126
+ HOST_ICS,
127
+ CAROUSEL_HEIGHT_OPTIONS,
128
+ CAROUSEL_WIDTH_OPTIONS,
129
+ STOP,
130
+ RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
92
131
  RCS_NUMERIC_VAR_NAME_REGEX,
93
132
  RCS_NUMERIC_VAR_TOKEN_REGEX,
94
133
  RCS_TAG_AREA_FIELD_TITLE,
@@ -125,6 +164,8 @@ import {
125
164
  sanitizeCardVarMappedValue,
126
165
  } from './utils';
127
166
 
167
+
168
+ const { Group: CapCheckboxGroup } = CapCheckbox;
128
169
  export const Rcs = (props) => {
129
170
  const {
130
171
  intl,
@@ -149,6 +190,7 @@ export const Rcs = (props) => {
149
190
  selectedOfferDetails,
150
191
  eventContextTags,
151
192
  accountData = {},
193
+ currentOrgDetails,
152
194
  // TestAndPreviewSlidebox props
153
195
  showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
154
196
  handleTestAndPreview: propsHandleTestAndPreview,
@@ -156,6 +198,24 @@ export const Rcs = (props) => {
156
198
  } = props || {};
157
199
  const { formatMessage } = intl;
158
200
  const { TextArea } = CapInput;
201
+ const { CapCustomCardList } = CapCustomCard;
202
+
203
+ // Defensive: React cannot render plain objects as children (crashes with
204
+ // "Objects are not valid as a React child"). Some campaigns (!isFullMode) flows
205
+ // can accidentally set an error state to an object (e.g. `{}`).
206
+ const normalizeErrorMessage = (err) => {
207
+ if (!err) return '';
208
+ if (React.isValidElement(err)) return err;
209
+ if (typeof err === 'string') return err;
210
+ if (typeof err === 'number') return String(err);
211
+ if (typeof err === 'object' && typeof err.message === 'string') return err.message;
212
+ try {
213
+ return JSON.stringify(err);
214
+ } catch (e) {
215
+ return String(err);
216
+ }
217
+ };
218
+
159
219
  const [isEditFlow, setEditFlow] = useState(false);
160
220
  const isEditLike = isEditFlow || !isFullMode;
161
221
  const [tags, updateTags] = useState([]);
@@ -179,16 +239,28 @@ export const Rcs = (props) => {
179
239
  const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
180
240
  const buttonType = RCS_BUTTON_TYPES.NONE;
181
241
  const [suggestionError, setSuggestionError] = useState(true);
242
+ const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
182
243
  const [templateType, setTemplateType] = useState('text_message');
183
244
  const [titleVarMappedData, setTitleVarMappedData] = useState({});
184
245
  const [descVarMappedData, setDescVarMappedData] = useState({});
185
246
  const [titleTextAreaId, setTitleTextAreaId] = useState();
186
247
  const [descTextAreaId, setDescTextAreaId] = useState();
187
248
  const [assetList, setAssetList] = useState({});
249
+ const [assetListImage, setAssetListImage] = useState('');
188
250
  const [rcsImageSrc, updateRcsImageSrc] = useState('');
189
251
  const [rcsVideoSrc, setRcsVideoSrc] = useState({});
190
252
  const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
191
253
  const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
254
+ // Carousel (UI-only) state
255
+ const [selectedCarousel, setSelectedCarousel] = useState('');
256
+ const [selectedCarouselHeight, setSelectedCarouselHeight] = useState(MEDIUM);
257
+ const [selectedCarouselWidth, setSelectedCarouselWidth] = useState(SMALL);
258
+ const [carouselData, setCarouselData] = useState([]);
259
+ const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
260
+ const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
261
+ const [carouselResetNonce, setCarouselResetNonce] = useState(0);
262
+ const [carouselFocusedVarId, setCarouselFocusedVarId] = useState('');
263
+ const [imageError, setImageError] = useState(null);
192
264
  const [templateTitleError, setTemplateTitleError] = useState(false);
193
265
  const [cardVarMapped, setCardVarMapped] = useState({});
194
266
  /** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
@@ -239,11 +311,21 @@ export const Rcs = (props) => {
239
311
  // Handle close Test and Preview slidebox
240
312
  const handleCloseTestAndPreview = useCallback(() => {
241
313
  setShowTestAndPreviewSlidebox(false);
314
+ setIsTestAndPreviewMode(false);
242
315
  if (propsHandleCloseTestAndPreview) {
243
316
  propsHandleCloseTestAndPreview();
244
317
  }
245
318
  }, [propsHandleCloseTestAndPreview]);
246
319
 
320
+ // Helper to get RCS orientation from selectedDimension
321
+ const getRcsOrientation = () => {
322
+ const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
323
+ if (isMediaTypeImage) {
324
+ return RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
325
+ }
326
+ // For video
327
+ return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
328
+ };
247
329
  /** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
248
330
  const handleSmsFallbackEditorStateChange = useCallback((patch) => {
249
331
  setSmsFallbackData((prev) => {
@@ -264,6 +346,12 @@ export const Rcs = (props) => {
264
346
  const [accessToken, setAccessToken] = useState('');
265
347
  const [hostName, setHostName] = useState('');
266
348
  const [accountName, setAccountName] = useState('');
349
+ const isHostInfoBip = hostName === HOST_INFOBIP;
350
+ const isHostIcs = hostName === HOST_ICS;
351
+
352
+ useEffect(() => {
353
+ setSuggestions(isHostIcs ? INITIAL_SUGGESTIONS_DATA_STOP : []);
354
+ }, [isHostIcs]);
267
355
  useEffect(() => {
268
356
  const accountObj = accountData.selectedRcsAccount || {};
269
357
  if (!isEmpty(accountObj)) {
@@ -291,6 +379,668 @@ export const Rcs = (props) => {
291
379
  const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
292
380
  const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
293
381
  const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
382
+ const isCarouselType = templateType === contentType.carousel;
383
+
384
+ const MAX_RCS_CAROUSEL_ALLOWED = 10;
385
+ // Uploads for RCS are stored in redux under dynamic keys `uploadedAssetData${index}`.
386
+ // Carousel needs per-card indices; otherwise all cards "restore" the last uploaded asset
387
+ // and show the same media/thumbnail.
388
+ const RCS_CAROUSEL_ASSET_INDEX_BASE = 10; // keep away from standalone indices 0 (media) and 1 (thumbnail)
389
+ const getCarouselImageAssetIndex = (cardIndex) =>
390
+ RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
391
+ const getCarouselVideoAssetIndex = (cardIndex) =>
392
+ RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 1;
393
+ const getCarouselThumbnailAssetIndex = (cardIndex) =>
394
+ RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 2;
395
+ const isThumbnailAssetIndex = (index) => {
396
+ if (index === 1) return true; // standalone thumbnail
397
+ if (index >= RCS_CAROUSEL_ASSET_INDEX_BASE) {
398
+ return ((index - RCS_CAROUSEL_ASSET_INDEX_BASE) % 3) === 2; // carousel thumbnail slot
399
+ }
400
+ return false;
401
+ };
402
+
403
+ // Carousel dimension key: `${HEIGHT}_${WIDTH}` (e.g., SHORT_SMALL)
404
+ const getCarouselDimensionKey = () => `${selectedCarouselHeight}_${selectedCarouselWidth}`;
405
+
406
+ const clearCarouselCardMedia = (cardIndex, { clearImage = true, clearVideo = true, clearThumb = true } = {}) => {
407
+ setCarouselData((prev = []) => {
408
+ const updated = cloneDeep(prev);
409
+ if (!updated?.[cardIndex]) return updated;
410
+ if (clearImage) updated[cardIndex].imageSrc = '';
411
+ if (clearVideo) updated[cardIndex].videoAsset = {};
412
+ if (clearThumb) updated[cardIndex].thumbnailSrc = '';
413
+ return updated;
414
+ });
415
+
416
+ if (clearImage) actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIndex));
417
+ if (clearVideo) actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIndex));
418
+ if (clearThumb) actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIndex));
419
+ };
420
+
421
+ const resetCarouselMediaForAllCards = () => {
422
+ setCarouselData((prev = []) => {
423
+ const updated = cloneDeep(prev);
424
+ updated.forEach((card) => {
425
+ if (!card) return;
426
+ card.imageSrc = '';
427
+ card.videoAsset = {};
428
+ card.thumbnailSrc = '';
429
+ });
430
+ return updated;
431
+ });
432
+ (carouselData || []).forEach((_, idx) => {
433
+ actions.clearRcsMediaAsset(getCarouselImageAssetIndex(idx));
434
+ actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(idx));
435
+ actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(idx));
436
+ });
437
+ // Force tab panes to remount after global reset (some tab implementations cache inactive panes)
438
+ setCarouselResetNonce((n) => n + 1);
439
+ };
440
+
441
+ const RCS_CAROUSEL_INITIAL_CARD = {
442
+ title: '',
443
+ description: '',
444
+ mediaType: RCS_MEDIA_TYPES.IMAGE, // per-card
445
+ imageSrc: '',
446
+ videoAsset: {}, // CapVideoUpload object shape
447
+ thumbnailSrc: '',
448
+ suggestions: [],
449
+ };
450
+
451
+ const RCS_CAROUSEL_INITIAL_FIRST_CARD = {
452
+ ...RCS_CAROUSEL_INITIAL_CARD,
453
+ suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
454
+ };
455
+
456
+ const ensureFirstCardDefaultPhoneSuggestions = (cards) => {
457
+ const next = cloneDeep(cards || []);
458
+ if (next.length === 0) return next;
459
+ const s = next[0].suggestions;
460
+ if (!Array.isArray(s) || s.length === 0) {
461
+ next[0] = {
462
+ ...next[0],
463
+ suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
464
+ };
465
+ }
466
+ return next;
467
+ };
468
+
469
+ // Always use functional updates: image upload completes in CapImageUpload's useEffect and calls
470
+ // updateImageSrc → this handler. If we cloned carouselData from render, we'd overwrite title/body/
471
+ // suggestions typed since the last commit (stale closure).
472
+ const handleCarouselValueChange = (carouselIndex, fields) => {
473
+ setCarouselData((prev = []) => {
474
+ const updated = cloneDeep(prev);
475
+ if (!updated[carouselIndex]) return prev;
476
+ fields.forEach(({ fieldName, value }) => {
477
+ updated[carouselIndex][fieldName] = value;
478
+ });
479
+ return updated;
480
+ });
481
+ };
482
+
483
+ const updateCarouselErrors = (carouselIndex, patch) => {
484
+ setCarouselErrors((prev = []) => {
485
+ const next = Array.isArray(prev) ? [...prev] : [];
486
+ next[carouselIndex] = { ...(next[carouselIndex] || {}), ...patch };
487
+ return next;
488
+ });
489
+ };
490
+
491
+ const deleteCarouselCard = (index) => {
492
+ let nextIdx = 0;
493
+ flushSync(() => {
494
+ setCarouselData((prev = []) => {
495
+ const updated = cloneDeep(prev);
496
+ if (index < 0 || index >= updated.length) return updated;
497
+ updated.splice(index, 1);
498
+ nextIdx = Math.max(0, Math.min(index - 1, updated.length - 1));
499
+ return ensureFirstCardDefaultPhoneSuggestions(updated);
500
+ });
501
+ });
502
+ setCarouselErrors((prev = []) => {
503
+ const next = Array.isArray(prev) ? [...prev] : [];
504
+ next.splice(index, 1);
505
+ return next;
506
+ });
507
+ setActiveCarouselIndex(`${nextIdx}`);
508
+ };
509
+
510
+ const carouselButtonTextHasForbiddenChars = (value) => {
511
+ if (!value) return false;
512
+ if (value.includes('[') || value.includes(']')) return true;
513
+ const withoutValidVariables = value.replace(/\{\{[^}]*\}\}/g, '');
514
+ if (withoutValidVariables.includes('{') || withoutValidVariables.includes('}')) return true;
515
+ return false;
516
+ };
517
+
518
+ const isCompleteSavedCarouselSuggestion = (s) => {
519
+ if (!s || !s.isSaved) return false;
520
+ const text = (s.text || '').trim();
521
+ if (!text || !isValidText(text) || carouselButtonTextHasForbiddenChars(text)) return false;
522
+ if (s.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
523
+ return String(s.phoneNumber || '').length >= 5;
524
+ }
525
+ if (s.type === RCS_BUTTON_TYPES.CTA) {
526
+ const url = String(s.url || '').trim();
527
+ if (!url || url.length > URL_MAX_LENGTH) return false;
528
+ const subtype = s.urlType || RCS_CTA_URL_TYPE.STATIC;
529
+ if (subtype === RCS_CTA_URL_TYPE.DYNAMIC) {
530
+ return true;
531
+ }
532
+ if (!isUrl(url)) return false;
533
+ const varMatches = url.match(invalidVarRegex);
534
+ return !(varMatches && varMatches.length > 0);
535
+ }
536
+ if (s.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
537
+ return true;
538
+ }
539
+ return false;
540
+ };
541
+
542
+ const isCarouselCardValid = (card, cardIndex) => {
543
+ if (!card) return false;
544
+ if (!card.title || !card.title.trim()) return false;
545
+ if ((card.title || '').length > TEMPLATE_TITLE_MAX_LENGTH) return false;
546
+ if (!card.description || !card.description.trim()) return false;
547
+ if ((card.description || '').length > RCS_RICH_CARD_MAX_LENGTH) return false;
548
+ let mediaOk = false;
549
+ if (card.mediaType === RCS_MEDIA_TYPES.IMAGE) {
550
+ mediaOk = !!(card.imageSrc && String(card.imageSrc).trim());
551
+ } else if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
552
+ const hasVideo = !!(card.videoAsset && card.videoAsset.videoSrc && String(card.videoAsset.videoSrc).trim());
553
+ const hasThumb = !!(card.thumbnailSrc && String(card.thumbnailSrc).trim());
554
+ mediaOk = hasVideo && hasThumb;
555
+ } else {
556
+ return false;
557
+ }
558
+ if (!mediaOk) return false;
559
+ if (cardIndex === 0) {
560
+ const sugg = Array.isArray(card.suggestions) ? card.suggestions : [];
561
+ if (!sugg.some(isCompleteSavedCarouselSuggestion)) return false;
562
+ }
563
+ return true;
564
+ };
565
+
566
+ const checkDisableAddCarouselButton = () => {
567
+ const idx = parseInt(activeCarouselIndex, 10);
568
+ const activeCard = carouselData?.[idx];
569
+ return !isCarouselCardValid(activeCard, idx);
570
+ };
571
+
572
+ const addCarouselCard = () => {
573
+ let newIndex = 0;
574
+ flushSync(() => {
575
+ setCarouselData((prev = []) => {
576
+ const updated = cloneDeep(prev);
577
+ updated.push(cloneDeep(RCS_CAROUSEL_INITIAL_CARD));
578
+ newIndex = updated.length - 1;
579
+ return updated;
580
+ });
581
+ });
582
+ setCarouselErrors((prev = []) => ([...(Array.isArray(prev) ? prev : []), {}]));
583
+ setActiveCarouselIndex(`${newIndex}`);
584
+ };
585
+
586
+ // Initialize carousel data when switching to carousel type
587
+ useEffect(() => {
588
+ if (!isCarouselType) return;
589
+ if (!carouselData || carouselData.length === 0) {
590
+ setCarouselData([cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)]);
591
+ setCarouselErrors([{}]);
592
+ setActiveCarouselIndex('0');
593
+ }
594
+ }, [isCarouselType]);
595
+
596
+ // keep derived carousel key in sync
597
+ useEffect(() => {
598
+ if (!isCarouselType) return;
599
+ if (!selectedCarouselHeight || !selectedCarouselWidth) return;
600
+ // Required format: HEIGHT_WIDTH
601
+ setSelectedCarousel(`${selectedCarouselHeight}_${selectedCarouselWidth}`);
602
+ }, [isCarouselType, selectedCarouselHeight, selectedCarouselWidth]);
603
+
604
+ const renderCarouselDimensionSelection = () => {
605
+ if (!isCarouselType) return null;
606
+ return (
607
+ <CapRow className="rcs-carousel-dimension-section">
608
+ <CapRow gutter={16} className="rcs-carousel-dimension-row">
609
+ <CapColumn span={12}>
610
+ <CapHeading type="h4" className="rcs-carousel-dimension-label">Card height</CapHeading>
611
+ <CapSelect
612
+ id="rcs-carousel-height-select"
613
+ value={selectedCarouselHeight}
614
+ onChange={(val) => {
615
+ // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
616
+ resetCarouselMediaForAllCards();
617
+ setSelectedCarouselHeight(val);
618
+ }}
619
+ options={CAROUSEL_HEIGHT_OPTIONS}
620
+ disabled={isEditFlow || !isFullMode}
621
+ />
622
+ </CapColumn>
623
+ <CapColumn span={12}>
624
+ <CapHeading type="h4" className="rcs-carousel-dimension-label">Card width</CapHeading>
625
+ <CapSelect
626
+ id="rcs-carousel-width-select"
627
+ value={selectedCarouselWidth}
628
+ onChange={(val) => {
629
+ // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
630
+ resetCarouselMediaForAllCards();
631
+ setSelectedCarouselWidth(val);
632
+ }}
633
+ options={CAROUSEL_WIDTH_OPTIONS}
634
+ disabled={isEditFlow || !isFullMode}
635
+ />
636
+ </CapColumn>
637
+ </CapRow>
638
+ {!!selectedCarousel && (
639
+ <CapLabel type="label3" className="rcs-carousel-selected-dimension">
640
+ Selected: {selectedCarousel}
641
+ </CapLabel>
642
+ )}
643
+ </CapRow>
644
+ );
645
+ };
646
+
647
+ // Reuse rich-card buttons UI per carousel card
648
+ const renderButtonComponentForCarouselCard = (cardIndex) => {
649
+ const card = carouselData?.[cardIndex] || {};
650
+ const suggestionsForCard = card.suggestions || [];
651
+ return (
652
+ <>
653
+ <CapHeader
654
+ className="rcs-button-cta"
655
+ title={(
656
+ <CapRow type="flex">
657
+ <CapHeading type="h4">
658
+ {formatMessage(messages.btnLabel)}
659
+ </CapHeading>
660
+ </CapRow>
661
+ )}
662
+ description={(
663
+ <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
664
+ )}
665
+ />
666
+ <CapActionButton
667
+ buttonType={RCS_BUTTON_TYPES.NONE}
668
+ updateButtonChange={(data, btnIndex) => {
669
+ // Match existing behavior: allow CapActionButton to manage save gating.
670
+ const updated = cloneDeep(suggestionsForCard);
671
+ if (btnIndex === MAX_BUTTONS) {
672
+ handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: data }]);
673
+ return;
674
+ }
675
+ updated[btnIndex] = data;
676
+ handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
677
+ }}
678
+ deleteButtonHandler={(btnIndex) => {
679
+ if (cardIndex === 0 && btnIndex === 0) {
680
+ return;
681
+ }
682
+ const savedCount = (suggestionsForCard || []).filter((x) => x && x.isSaved).length;
683
+ const target = (suggestionsForCard || []).find((s) => s && s.index === btnIndex);
684
+ if (cardIndex === 0 && target?.isSaved && savedCount <= 1) {
685
+ return;
686
+ }
687
+ const updated = cloneDeep(suggestionsForCard)
688
+ .filter((i) => i.index !== btnIndex)
689
+ .map((i, idx) => ({ ...i, index: idx }));
690
+ handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
691
+ }}
692
+ suggestions={suggestionsForCard}
693
+ isEditFlow={isEditFlow}
694
+ isFullMode={isFullMode}
695
+ maxButtons={MAX_BUTTONS}
696
+ host={hostName}
697
+ minSavedSuggestions={cardIndex === 0 ? 1 : 0}
698
+ hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
699
+ />
700
+ </>
701
+ );
702
+ };
703
+
704
+ const renderCarouselCardMedia = (cardIndex) => {
705
+ const card = carouselData?.[cardIndex] || {};
706
+ const dimKey = getCarouselDimensionKey();
707
+
708
+ if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
709
+ return (
710
+ <>
711
+ <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Video</CapHeading>
712
+ <CapVideoUpload
713
+ index={getCarouselVideoAssetIndex(cardIndex)}
714
+ allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
715
+ videoSize={RCS_CAROUSEL_VIDEO_SIZE}
716
+ isFullMode={isFullMode}
717
+ uploadAsset={uploadRcsVideo}
718
+ uploadedAssetList={card.videoAsset || {}}
719
+ onVideoUploadUpdateAssestList={(_, val) => {
720
+ handleCarouselValueChange(cardIndex, [{ fieldName: 'videoAsset', value: val }]);
721
+ }}
722
+ videoData={rcsData}
723
+ className="cap-custom-video-upload"
724
+ formClassName={"rcs-video-upload"}
725
+ channel={RCS}
726
+ errorMessage={formatMessage(messages.videoErrorMessage)}
727
+ showVideoNameAndDuration={false}
728
+ showReUploadButton={!isEditFlow && isFullMode}
729
+ channelSpecificStyle={!isFullMode}
730
+ />
731
+
732
+ <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
733
+ <CapImageUpload
734
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
735
+ imgWidth={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.width}
736
+ imgHeight={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.height}
737
+ imgSize={RCS_THUMBNAIL_MAX_SIZE}
738
+ uploadAsset={uploadRcsImage}
739
+ isFullMode={isFullMode}
740
+ imageSrc={card.thumbnailSrc}
741
+ updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: val }])}
742
+ updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: '' }])}
743
+ minImgSize={RCS_THUMBNAIL_MIN_SIZE}
744
+ index={getCarouselThumbnailAssetIndex(cardIndex)}
745
+ className="cap-custom-image-upload"
746
+ key={`rcs-carousel-thumb-${cardIndex}-${dimKey}`}
747
+ imageData={rcsData}
748
+ channel={RCS}
749
+ channelSpecificStyle={!isFullMode}
750
+ skipDimensionValidation={true}
751
+ showReUploadButton={!isEditFlow && isFullMode}
752
+ disabled={isEditFlow || !isFullMode}
753
+ />
754
+ </>
755
+ );
756
+ }
757
+
758
+ // Default: IMAGE
759
+ return (
760
+ <>
761
+ <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Image</CapHeading>
762
+ <CapImageUpload
763
+ allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
764
+ imgWidth={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.width}
765
+ imgHeight={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.height}
766
+ imgSize={RCS_CAROUSEL_IMG_SIZE}
767
+ uploadAsset={uploadRcsImage}
768
+ isFullMode={isFullMode}
769
+ imageSrc={card.imageSrc}
770
+ updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: val }])}
771
+ updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: '' }])}
772
+ index={getCarouselImageAssetIndex(cardIndex)}
773
+ className="cap-custom-image-upload"
774
+ key={`rcs-carousel-image-${cardIndex}-${dimKey}`}
775
+ imageData={rcsData}
776
+ channel={RCS}
777
+ channelSpecificStyle={!isFullMode}
778
+ skipDimensionValidation={true}
779
+ showReUploadButton={!isEditFlow && isFullMode}
780
+ disabled={isEditFlow || !isFullMode}
781
+ />
782
+ </>
783
+ );
784
+ };
785
+
786
+ const renderCarouselCardButtons = (cardIndex) => {
787
+ return renderButtonComponentForCarouselCard(cardIndex);
788
+ };
789
+
790
+ const getCarouselTabPanes = () => {
791
+ return (carouselData || []).map((card, index) => {
792
+ return {
793
+ key: index,
794
+ tab: index + 1,
795
+ content: (
796
+ <CapCard
797
+ title={`Card ${index + 1}`}
798
+ extra={
799
+ !isEditFlow &&
800
+ (carouselData.length === 1 ? (
801
+ <CapTooltip title={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}>
802
+ <span className="button-disabled-tooltip-wrapper rcs-carousel-delete-tooltip-wrap">
803
+ <CapButton
804
+ className="rcs-carousel-card-delete"
805
+ type="flat"
806
+ onClick={() => deleteCarouselCard(index)}
807
+ disabled
808
+ aria-label={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}
809
+ >
810
+ <CapIcon type="delete" />
811
+ </CapButton>
812
+ </span>
813
+ </CapTooltip>
814
+ ) : (
815
+ <CapButton
816
+ className="rcs-carousel-card-delete"
817
+ type="flat"
818
+ onClick={() => deleteCarouselCard(index)}
819
+ aria-label={formatMessage(globalMessages.delete)}
820
+ >
821
+ <CapIcon type="delete" />
822
+ </CapButton>
823
+ ))
824
+ }
825
+ className="rcs-carousel-card"
826
+ >
827
+ {/* Media selection should be at top of card */}
828
+ <CapRow className="rcs-carousel-media-selection">
829
+ <CapColumn className="rcs-carousel-media-selection-heading">
830
+ <CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
831
+ </CapColumn>
832
+ <CapColumn>
833
+ <CapRadioGroup
834
+ id={`rcs-carousel-media-radio-${index}`}
835
+ options={mediaRadioOptions}
836
+ value={card.mediaType}
837
+ onChange={({ target: { value } }) => {
838
+ // Reset media fields when switching type
839
+ if (value === RCS_MEDIA_TYPES.IMAGE) {
840
+ // Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
841
+ clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
842
+ handleCarouselValueChange(index, [
843
+ { fieldName: 'mediaType', value },
844
+ { fieldName: 'videoAsset', value: {} },
845
+ { fieldName: 'thumbnailSrc', value: '' },
846
+ ]);
847
+ } else {
848
+ // Switching to VIDEO: clear image upload so it doesn't auto-restore.
849
+ clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
850
+ handleCarouselValueChange(index, [
851
+ { fieldName: 'mediaType', value },
852
+ { fieldName: 'imageSrc', value: '' },
853
+ ]);
854
+ }
855
+ }}
856
+ disabled={isEditFlow || !isFullMode}
857
+ className="rcs-radio"
858
+ />
859
+ </CapColumn>
860
+ </CapRow>
861
+
862
+ <CapRow className="rcs-carousel-media-upload">
863
+ {renderCarouselCardMedia(index)}
864
+ </CapRow>
865
+
866
+ {/* Title after media */}
867
+ <CapRow className="rcs-carousel-card-row">
868
+ <CapHeader
869
+ className="rcs-template-title-label"
870
+ title={<CapHeading type="h4">Card title</CapHeading>}
871
+ suffix={
872
+ (isEditFlow || !isFullMode) ? (
873
+ <TagList
874
+ label={formatMessage(globalMessages.addLabels)}
875
+ onTagSelect={onCarouselTagSelect}
876
+ location={location}
877
+ tags={getRcsTags()}
878
+ onContextChange={handleOnTagsContextChange}
879
+ injectedTags={injectedTags || {}}
880
+ selectedOfferDetails={selectedOfferDetails}
881
+ />
882
+ ) : (
883
+ <CapButton
884
+ data-testid={`rcs-carousel-title-add-var-${index}`}
885
+ type="flat"
886
+ isAddBtn
887
+ onClick={() => appendVarToCarouselField(index, 'title')}
888
+ disabled={!!carouselErrors?.[index]?.title}
889
+ >
890
+ {formatMessage(messages.addVar)}
891
+ </CapButton>
892
+ )
893
+ }
894
+ />
895
+ <CapRow className="rcs_text_area_wrapper">
896
+ {(isEditFlow || !isFullMode) ? (
897
+ renderCarouselEditMessage(card.title || '')
898
+ ) : (
899
+ <CapInput
900
+ value={card.title || ''}
901
+ placeholder={formatMessage(messages.templateTitlePlaceholder)}
902
+ onChange={({ target: { value } }) => {
903
+ let error = false;
904
+ if (value?.length > TEMPLATE_TITLE_MAX_LENGTH) {
905
+ error = formatMessage(messages.templateHeaderLengthError);
906
+ } else {
907
+ error = variableErrorHandling(value);
908
+ }
909
+ updateCarouselErrors(index, { title: error });
910
+ handleCarouselValueChange(index, [{ fieldName: 'title', value }]);
911
+ }}
912
+ disabled={isEditFlow || !isFullMode}
913
+ errorMessage={carouselErrors?.[index]?.title}
914
+ />
915
+ )}
916
+ </CapRow>
917
+ </CapRow>
918
+ {!isEditFlow && (
919
+ <CapRow className="rcs-carousel-character-count-row">
920
+ {renderCarouselCharacterCount(
921
+ getCarouselTitleCharacterCount(index),
922
+ getTitleMaxLength(),
923
+ )}
924
+ </CapRow>
925
+ )}
926
+
927
+ {/* Description after title */}
928
+ <CapRow className="rcs-carousel-card-row">
929
+ <CapHeader
930
+ title={<CapHeading type="h4">Card body text</CapHeading>}
931
+ suffix={
932
+ (isEditFlow || !isFullMode) ? (
933
+ <TagList
934
+ label={formatMessage(globalMessages.addLabels)}
935
+ onTagSelect={onCarouselTagSelect}
936
+ location={location}
937
+ tags={getRcsTags()}
938
+ onContextChange={handleOnTagsContextChange}
939
+ injectedTags={injectedTags || {}}
940
+ selectedOfferDetails={selectedOfferDetails}
941
+ />
942
+ ) : (
943
+ <CapButton
944
+ data-testid={`rcs-carousel-desc-add-var-${index}`}
945
+ type="flat"
946
+ isAddBtn
947
+ onClick={() => appendVarToCarouselField(index, 'description')}
948
+ disabled={!!carouselErrors?.[index]?.description}
949
+ >
950
+ {formatMessage(messages.addVar)}
951
+ </CapButton>
952
+ )
953
+ }
954
+ />
955
+ <CapRow className="rcs_text_area_wrapper">
956
+ {(isEditFlow || !isFullMode) ? (
957
+ renderCarouselEditMessage(card.description || '')
958
+ ) : (
959
+ <TextArea
960
+ autosize={{ minRows: 3, maxRows: 5 }}
961
+ value={card.description || ''}
962
+ placeholder={formatMessage(messages.templateDescPlaceholder)}
963
+ onChange={({ target: { value } }) => {
964
+ let error = false;
965
+ if (value?.length > RCS_RICH_CARD_MAX_LENGTH) {
966
+ error = formatMessage(messages.templateMessageLengthError);
967
+ } else {
968
+ error = variableErrorHandling(value);
969
+ }
970
+ updateCarouselErrors(index, { description: error });
971
+ handleCarouselValueChange(index, [{ fieldName: 'description', value }]);
972
+ }}
973
+ disabled={isEditFlow || !isFullMode}
974
+ errorMessage={
975
+ carouselErrors?.[index]?.description && (
976
+ <CapError className="rcs-template-message-error">
977
+ {carouselErrors[index].description}
978
+ </CapError>
979
+ )
980
+ }
981
+ />
982
+ )}
983
+ </CapRow>
984
+ </CapRow>
985
+ {!isEditFlow && (
986
+ <CapRow className="rcs-carousel-character-count-row">
987
+ {renderCarouselCharacterCount(
988
+ getCarouselDescriptionCharacterCount(index),
989
+ getDescriptionMaxLength(),
990
+ )}
991
+ </CapRow>
992
+ )}
993
+
994
+ <CapDivider className="rcs-carousel-card-divider" />
995
+ {renderCarouselCardButtons(index)}
996
+ </CapCard>
997
+ ),
998
+ };
999
+ });
1000
+ };
1001
+
1002
+ const renderCarouselSection = () => {
1003
+ if (!isCarouselType) return null;
1004
+
1005
+ const operations = (
1006
+ <>
1007
+ <CapDivider type="vertical" />
1008
+ <CapButton
1009
+ onClick={addCarouselCard}
1010
+ type="flat"
1011
+ className="add-carousel-content-button"
1012
+ disabled={
1013
+ isEditFlow ||
1014
+ !isFullMode ||
1015
+ MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0) ||
1016
+ checkDisableAddCarouselButton()
1017
+ }
1018
+ >
1019
+ <CapIcon type="plus" />
1020
+ </CapButton>
1021
+ </>
1022
+ );
1023
+
1024
+ return (
1025
+ <CapRow className="rcs-carousel-section">
1026
+ {renderCarouselDimensionSelection()}
1027
+ <CapRow className="rcs-carousel-tab">
1028
+ <CapTab
1029
+ key={`rcs-carousel-tab-${carouselResetNonce}`}
1030
+ defaultActiveKey="0"
1031
+ activeKey={activeCarouselIndex}
1032
+ tabBarExtraContent={operations}
1033
+ onChange={(key) => {
1034
+ setActiveCarouselIndex(`${key}`);
1035
+ // reset focused var when switching cards (as per requirement)
1036
+ setCarouselFocusedVarId('');
1037
+ }}
1038
+ panes={getCarouselTabPanes()}
1039
+ />
1040
+ </CapRow>
1041
+ </CapRow>
1042
+ );
1043
+ };
294
1044
 
295
1045
  const mediaRadioOptions = [
296
1046
  {
@@ -299,11 +1049,16 @@ export const Rcs = (props) => {
299
1049
  },
300
1050
  {
301
1051
  value: RCS_MEDIA_TYPES.VIDEO,
302
- label: formatMessage(messages.mediaVideo),
1052
+ label: formatMessage(
1053
+ templateType === contentType.carousel
1054
+ ? messages.carouselMediaVideoOption
1055
+ : messages.mediaVideo,
1056
+ ),
303
1057
  },
304
1058
  ];
305
1059
  const aiContentBotDisabled = isAiContentBotDisabled();
306
1060
 
1061
+
307
1062
  const updateButtonChange = (data, index) => {
308
1063
  if (data && data.text) {
309
1064
  const forbiddenError = forbiddenCharactersValidation(data.text);
@@ -369,10 +1124,16 @@ export const Rcs = (props) => {
369
1124
  tagModule: getDefaultTags,
370
1125
  isFullMode,
371
1126
  }) || {};
372
- const errorMsg =
373
- (validationResponse?.isBraceError &&
374
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
375
- false;
1127
+ const unsupportedTagsLengthCheck =
1128
+ validationResponse?.unsupportedTags?.length > 0;
1129
+ const errorMsg =
1130
+ (unsupportedTagsLengthCheck &&
1131
+ formatMessage(globalMessages.unsupportedTagsValidationError, {
1132
+ unsupportedTags: validationResponse.unsupportedTags,
1133
+ })) ||
1134
+ (validationResponse.isBraceError &&
1135
+ formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
1136
+ false;
376
1137
  if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
377
1138
  if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
378
1139
  };
@@ -385,6 +1146,50 @@ export const Rcs = (props) => {
385
1146
  validateResolvedTagsForType(MESSAGE_TEXT);
386
1147
  }, [cardVarMapped, templateDesc, tags, injectedTags, loadingTags]);
387
1148
 
1149
+ useEffect(() => {
1150
+ if (isFullMode || !isCarouselType) return;
1151
+ if (loadingTags || !tags || tags.length === 0) return;
1152
+ (carouselData || []).forEach((card, idx) => {
1153
+ ['title', 'description'].forEach((field) => {
1154
+ const templateStr = card?.[field] || '';
1155
+ if (!templateStr) return;
1156
+ const resolved = resolveTemplateWithMap(templateStr);
1157
+ if (!resolved) {
1158
+ updateCarouselErrors(idx, { [field]: false });
1159
+ return;
1160
+ }
1161
+ let contentForValidation = resolved;
1162
+ const placeholderTokens = templateStr.match(rcsVarRegex) || [];
1163
+ placeholderTokens.forEach((t) => {
1164
+ const escaped = t.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1165
+ contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
1166
+ });
1167
+ if (!contentForValidation.trim()) {
1168
+ updateCarouselErrors(idx, { [field]: false });
1169
+ return;
1170
+ }
1171
+ const validationResponse = validateTags({
1172
+ content: contentForValidation,
1173
+ tagsParam: tags,
1174
+ injectedTagsParams: injectedTags,
1175
+ location,
1176
+ tagModule: getDefaultTags,
1177
+ eventContextTags,
1178
+ isFullMode,
1179
+ }) || {};
1180
+ const errorMsg =
1181
+ (validationResponse?.unsupportedTags?.length > 0 &&
1182
+ formatMessage(globalMessages.unsupportedTagsValidationError, {
1183
+ unsupportedTags: validationResponse.unsupportedTags,
1184
+ })) ||
1185
+ (validationResponse.isBraceError &&
1186
+ formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
1187
+ false;
1188
+ updateCarouselErrors(idx, { [field]: errorMsg });
1189
+ });
1190
+ });
1191
+ }, [cardVarMapped, carouselData, tags, injectedTags, loadingTags, isCarouselType]);
1192
+
388
1193
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
389
1194
 
390
1195
  const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
@@ -413,6 +1218,10 @@ export const Rcs = (props) => {
413
1218
  return null;
414
1219
  };
415
1220
 
1221
+ /**
1222
+ * Master-branch resolve: uses numeric slot keys + semantic spanning detection for correct
1223
+ * multi-field variable resolution.
1224
+ */
416
1225
  const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
417
1226
  if (!str) return '';
418
1227
  const arr = splitTemplateVarStringRcs(str);
@@ -436,12 +1245,45 @@ export const Rcs = (props) => {
436
1245
  }).join('');
437
1246
  };
438
1247
 
1248
+ const buildCarouselCardsForPreview = (cards = []) => (cards || []).map((card = {}) => {
1249
+ const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
1250
+ const videoSrc = card.videoAsset?.videoSrc || '';
1251
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(card.title || '') : (card.title || '');
1252
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(card.description || '') : (card.description || '');
1253
+ return {
1254
+ mediaType: (card.mediaType || '').toLowerCase(),
1255
+ imageSrc: card.imageSrc || '',
1256
+ videoSrc,
1257
+ videoPreviewImg: videoThumb,
1258
+ title: resolvedTitle,
1259
+ bodyText: resolvedDesc,
1260
+ suggestions: card.suggestions || [],
1261
+ };
1262
+ });
1263
+
439
1264
  /**
440
1265
  * Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
441
1266
  * (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
442
1267
  * TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
443
1268
  */
444
1269
  const getTemplateContent = useCallback(() => {
1270
+ if (templateType === contentType.carousel) {
1271
+ const carouselDimKey = getCarouselDimensionKey();
1272
+ const carouselImgDims =
1273
+ RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
1274
+ const carouselVidDims =
1275
+ RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
1276
+ || RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
1277
+ return {
1278
+ carouselData: buildCarouselCardsForPreview(carouselData),
1279
+ carouselPreviewDimensions: {
1280
+ imageWidth: carouselImgDims.width,
1281
+ imageHeight: carouselImgDims.height,
1282
+ videoThumbWidth: carouselVidDims.width,
1283
+ videoThumbHeight: carouselVidDims.height,
1284
+ },
1285
+ };
1286
+ }
445
1287
  const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
446
1288
  const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
447
1289
  const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
@@ -495,21 +1337,41 @@ export const Rcs = (props) => {
495
1337
  isEditFlow,
496
1338
  cardVarMapped,
497
1339
  rcsSpanningSemanticVarNames,
1340
+ carouselData,
1341
+ selectedCarouselHeight,
1342
+ selectedCarouselWidth,
498
1343
  ]);
499
1344
 
500
1345
  const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
501
1346
 
502
1347
  const paramObj = params || {};
503
1348
  useEffect(() => {
504
- const { id } = paramObj;
505
- if (id && isFullMode) {
506
- setSpin(true);
507
- actions.getTemplateDetails(id, setSpin);
508
- }
509
- return () => {
510
- actions.clearEditResponse();
511
- };
512
- }, [paramObj.id, isFullMode]);
1349
+ const { id } = paramObj;
1350
+ if (id && isFullMode) {
1351
+ setSpin(true);
1352
+ actions.getTemplateDetails(id, setSpin);
1353
+ } else if (!id && isFullMode) {
1354
+ // Create New: clear standalone media and ALL possible carousel card Redux slots.
1355
+ // Redux persists across mounts so we must clear unconditionally (carouselData may be [] on fresh mount).
1356
+ updateRcsImageSrc('');
1357
+ setRcsVideoSrc({});
1358
+ setRcsThumbnailSrc('');
1359
+ setAssetList({});
1360
+ setEditFlow(false);
1361
+ actions.clearRcsMediaAsset(0);
1362
+ actions.clearRcsMediaAsset(1);
1363
+ for (let cardIdx = 0; cardIdx < MAX_RCS_CAROUSEL_ALLOWED; cardIdx += 1) {
1364
+ actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIdx));
1365
+ actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIdx));
1366
+ actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIdx));
1367
+ }
1368
+ setCarouselData([]);
1369
+ setCarouselErrors([]);
1370
+ }
1371
+ return () => {
1372
+ actions.clearEditResponse();
1373
+ };
1374
+ }, [paramObj.id]);
513
1375
 
514
1376
  useEffect(() => {
515
1377
  if (!(isEditFlow || !isFullMode)) return;
@@ -552,8 +1414,7 @@ export const Rcs = (props) => {
552
1414
  if (!isEditFlow && isFullMode) {
553
1415
  setRcsVideoSrc({});
554
1416
  updateRcsImageSrc('');
555
- setUpdateRcsImageSrc('');
556
- updateRcsThumbnailSrc('');
1417
+ setRcsThumbnailSrc('');
557
1418
  setAssetList({});
558
1419
  }
559
1420
  }, [templateMediaType]);
@@ -596,6 +1457,7 @@ export const Rcs = (props) => {
596
1457
  if (mediaType) {
597
1458
  setTemplateMediaType(mediaType);
598
1459
  }
1460
+ const tempOrientation = cardSettings.cardOrientation;
599
1461
  const tempAlignment = cardSettings.mediaAlignment;
600
1462
  const tempHeight = mediaData.height;
601
1463
 
@@ -645,6 +1507,12 @@ export const Rcs = (props) => {
645
1507
  details,
646
1508
  'versions.base.content.RCS.rcsContent.cardContent[0]',
647
1509
  );
1510
+ const rcsContent = get(details, 'versions.base.content.RCS.rcsContent', {});
1511
+ const cardType = (rcsContent?.cardType || '').toString().toLowerCase();
1512
+
1513
+ setEditFlow(true);
1514
+ setTemplateName(details?.name || details?.creativeName || '');
1515
+
648
1516
  const cardFromTop = get(details, 'rcsContent.cardContent[0]');
649
1517
  const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
650
1518
  const cardVarMappedFromCardContent =
@@ -714,16 +1582,83 @@ export const Rcs = (props) => {
714
1582
  }
715
1583
  return hydratedCardVarMappedResult;
716
1584
  });
1585
+
1586
+ if (cardType === contentType.carousel) {
1587
+
1588
+ setTemplateType(contentType.carousel);
1589
+ setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
1590
+ const cardSettings = rcsContent?.cardSettings || {};
1591
+ const cardWidth = cardSettings?.cardWidth || SMALL;
1592
+ setSelectedCarouselWidth(cardWidth);
1593
+
1594
+ const cards = Array.isArray(rcsContent?.cardContent) ? rcsContent.cardContent : [];
1595
+ const firstHeight = cards?.[0]?.media?.height || MEDIUM;
1596
+ setSelectedCarouselHeight(firstHeight);
1597
+ setSelectedCarousel(`${firstHeight}_${cardWidth}`);
1598
+ setActiveCarouselIndex('0');
1599
+
1600
+ const hydratedCards = cards.map((c = {}, idx) => {
1601
+ const mediaType = c.mediaType;
1602
+ const media = c.media || {};
1603
+ const mediaUrl = media.mediaUrl || '';
1604
+ const thumbUrl = media.thumbnailUrl || '';
1605
+ const rawSuggestions = Array.isArray(c.suggestions) ? c.suggestions : [];
1606
+ const suggestions = idx === 0 && rawSuggestions.length === 0
1607
+ ? cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS)
1608
+ : rawSuggestions;
1609
+ return {
1610
+ title: c.title || '',
1611
+ description: c.description || '',
1612
+ mediaType,
1613
+ imageSrc: mediaType === RCS_MEDIA_TYPES.IMAGE ? mediaUrl : '',
1614
+ videoAsset: mediaType === RCS_MEDIA_TYPES.VIDEO ? {
1615
+ videoSrc: mediaUrl,
1616
+ previewUrl: thumbUrl,
1617
+ videoThumbnail: thumbUrl,
1618
+ videoName: c?.media?.videoName || '',
1619
+ } : {},
1620
+ thumbnailSrc: mediaType === RCS_MEDIA_TYPES.VIDEO ? thumbUrl : '',
1621
+ suggestions,
1622
+ };
1623
+ });
1624
+ setCarouselData(
1625
+ hydratedCards.length > 0
1626
+ ? ensureFirstCardDefaultPhoneSuggestions(hydratedCards)
1627
+ : [cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)],
1628
+ );
1629
+ setCarouselErrors(new Array(hydratedCards.length > 0 ? hydratedCards.length : 1).fill({}));
1630
+
1631
+ // Status bar uses first card's Status, keep existing behavior.
1632
+ if (isHostInfoBip) {
1633
+ setTemplateStatus('');
1634
+ } else {
1635
+ const firstCard = cards?.[0] || {};
1636
+ const cardForCarouselStatus = {
1637
+ ...firstCard,
1638
+ Status:
1639
+ firstCard.Status
1640
+ ?? firstCard.status
1641
+ ?? firstCard.approvalStatus
1642
+ ?? get(details, 'templateStatus')
1643
+ ?? get(details, 'approvalStatus')
1644
+ ?? get(details, 'creativeStatus')
1645
+ ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
1646
+ ?? '',
1647
+ };
1648
+ templateStatusHelper(cardForCarouselStatus);
1649
+ }
1650
+ return;
1651
+ }
1652
+
717
1653
  const mediaType =
718
1654
  card0.mediaType
719
1655
  || get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
720
- if (mediaType === RCS_MEDIA_TYPES.NONE) {
1656
+ if (cardType !== contentType.carousel && mediaType === RCS_MEDIA_TYPES.NONE) {
721
1657
  setTemplateType(contentType.text_message);
722
- } else {
1658
+ } else if (cardType !== contentType.carousel && mediaType !== RCS_MEDIA_TYPES.NONE) {
723
1659
  setTemplateType(contentType.rich_card);
724
1660
  }
725
- setEditFlow(true);
726
- setTemplateName(details?.name || details?.creativeName || '');
1661
+
727
1662
  const loadedTitle = loadedTitleForMap;
728
1663
  const loadedDesc = loadedDescForMap;
729
1664
  const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
@@ -752,7 +1687,11 @@ export const Rcs = (props) => {
752
1687
  ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
753
1688
  ?? '',
754
1689
  };
755
- templateStatusHelper(cardForStatus);
1690
+ if (isHostInfoBip) {
1691
+ setTemplateStatus('');
1692
+ } else {
1693
+ templateStatusHelper(cardForStatus);
1694
+ }
756
1695
  const mediaData =
757
1696
  card0.media != null && card0.media !== ''
758
1697
  ? card0.media
@@ -827,7 +1766,7 @@ export const Rcs = (props) => {
827
1766
  setSmsFallbackData(null);
828
1767
  }
829
1768
  }
830
- }, [rcsHydrationDetails, isFullMode]);
1769
+ }, [rcsHydrationDetails, isFullMode, isHostInfoBip]);
831
1770
 
832
1771
  useEffect(() => {
833
1772
  if (templateType === contentType.text_message) {
@@ -905,6 +1844,7 @@ export const Rcs = (props) => {
905
1844
  query.context = getDefaultTags;
906
1845
  }
907
1846
  fetchTagSchemaIfNewQuery(query);
1847
+ globalActions.fetchSchemaForEntity(query);
908
1848
  };
909
1849
 
910
1850
  const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
@@ -1003,6 +1943,31 @@ export const Rcs = (props) => {
1003
1943
 
1004
1944
  const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
1005
1945
 
1946
+ const onCarouselTagSelect = (data) => {
1947
+ if (!carouselFocusedVarId) return;
1948
+ const sep = carouselFocusedVarId.lastIndexOf('_');
1949
+ if (sep === -1) return;
1950
+ const token = carouselFocusedVarId.slice(0, sep);
1951
+ const variableName = getVarNameFromToken(token);
1952
+ if (!variableName) return;
1953
+ setCardVarMapped((prev) => {
1954
+ const base = (prev?.[variableName] ?? '').toString();
1955
+ const nextVal = `${base}{{${data}}}`;
1956
+ return {
1957
+ ...(prev || {}),
1958
+ [variableName]: nextVal,
1959
+ };
1960
+ });
1961
+ };
1962
+
1963
+ const onTagSelectFallback = (data) => {
1964
+ const tempMsg = `${fallbackMessage}{{${data}}}`;
1965
+ const error = fallbackMessageErrorHandler(tempMsg);
1966
+ setFallbackMessage(tempMsg);
1967
+ setFallbackMessageError(error);
1968
+ };
1969
+
1970
+
1006
1971
  //removing optout tag for rcs
1007
1972
  const getRcsTags = () => {
1008
1973
  const tempTags = cloneDeep(tags);
@@ -1038,15 +2003,14 @@ export const Rcs = (props) => {
1038
2003
  value: contentType.rich_card,
1039
2004
  label: formatMessage(messages.richCard),
1040
2005
  },
1041
- {
1042
- value: contentType.carousel,
1043
- label: (
1044
- <CapTooltip title={formatMessage(messages.disabledCarouselTooltip)}>
1045
- {formatMessage(messages.carousel)}
1046
- </CapTooltip>
1047
- ),
1048
- disabled: true,
1049
- },
2006
+ ...(!isHostInfoBip
2007
+ ? [
2008
+ {
2009
+ value: contentType.carousel,
2010
+ label: formatMessage(messages.carousel),
2011
+ },
2012
+ ]
2013
+ : []),
1050
2014
  ];
1051
2015
 
1052
2016
  const onTemplateNameChange = ({ target: { value } }) => {
@@ -1057,6 +2021,10 @@ export const Rcs = (props) => {
1057
2021
 
1058
2022
  const onTemplateTypeChange = ({ target: { value } }) => {
1059
2023
  setTemplateType(value);
2024
+ // Carousel has per-card media; keep template-level media type neutral.
2025
+ if (value === contentType.carousel) {
2026
+ setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
2027
+ }
1060
2028
  };
1061
2029
 
1062
2030
 
@@ -1077,8 +2045,39 @@ export const Rcs = (props) => {
1077
2045
  const onTemplateMediaTypeChange = ({ target: { value } }) => {
1078
2046
  setTemplateMediaType(value);
1079
2047
  };
1080
-
1081
-
2048
+ const renderedRCSEditMessage = (descArray, type) => {
2049
+ const renderArray = [];
2050
+ if (descArray?.length) {
2051
+ descArray.forEach((elem, index) => {
2052
+ if (rcsVarTestRegex.test(elem)) {
2053
+ // Variable input
2054
+ renderArray.push(
2055
+ <TextArea
2056
+ id={`${elem}_${index}`}
2057
+ key={`${elem}_${index}`}
2058
+ placeholder={`enter the value for ${elem}`}
2059
+ autosize={{ minRows: 1, maxRows: 3 }}
2060
+ onChange={e => textAreaValueChange(e, type)}
2061
+ value={textAreaValue(index, type)}
2062
+ onFocus={(e) => setTextAreaId(e, type)}
2063
+ />
2064
+ );
2065
+ } else if (elem) {
2066
+ // Static text
2067
+ renderArray.push(
2068
+ <TextArea
2069
+ key={`static_${index}`}
2070
+ value={elem}
2071
+ autosize={{ minRows: 1, maxRows: 3 }}
2072
+ disabled
2073
+ className="rcs-edit-template-message-static-textarea"
2074
+ />
2075
+ );
2076
+ }
2077
+ });
2078
+ }
2079
+ return renderArray;
2080
+ };
1082
2081
  const onTemplateTitleChange = ({ target: { value } }) => {
1083
2082
  let errorMessage = false;
1084
2083
  if (templateType === contentType.rich_card && !value.trim()) {
@@ -1094,7 +2093,7 @@ export const Rcs = (props) => {
1094
2093
 
1095
2094
  const onTemplateDescChange = ({ target: { value } }) => {
1096
2095
  let errorMessage = false;
1097
- if(templateType === contentType.text_message && value?.length > RCS_TEXT_MESSAGE_MAX_LENGTH){
2096
+ if(templateType === contentType.text_message && value?.length > (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)){
1098
2097
  errorMessage = formatMessage(messages.templateMessageLengthError);
1099
2098
  } else if(templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH){
1100
2099
  errorMessage = formatMessage(messages.templateMessageLengthError);
@@ -1107,6 +2106,62 @@ export const Rcs = (props) => {
1107
2106
  setTemplateDescError(error);
1108
2107
  };
1109
2108
 
2109
+
2110
+ const templateDescErrorHandler = (value) => {
2111
+ let errorMessage = false;
2112
+ const { unsupportedTags, isBraceError } = validateTags({
2113
+ content: value,
2114
+ tagsParam: tags,
2115
+ injectedTagsParams: injectedTags,
2116
+ location,
2117
+ tagModule: getDefaultTags,
2118
+ }) || {};
2119
+
2120
+ const maxLength = templateType === contentType.text_message
2121
+ ? (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)
2122
+ : RCS_RICH_CARD_MAX_LENGTH;
2123
+
2124
+ if (value === '' && isMediaTypeText) {
2125
+ errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
2126
+ } else if (value?.length > maxLength) {
2127
+ errorMessage = formatMessage(messages.templateMessageLengthError);
2128
+ }
2129
+
2130
+ if (isBraceError) {
2131
+ errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
2132
+ }
2133
+ return errorMessage;
2134
+ };
2135
+
2136
+
2137
+ const onFallbackMessageChange = ({ target: { value } }) => {
2138
+ const error = fallbackMessageErrorHandler(value);
2139
+ setFallbackMessage(value);
2140
+ setFallbackMessageError(error);
2141
+ };
2142
+
2143
+ const fallbackMessageErrorHandler = (value) => {
2144
+ let errorMessage = false;
2145
+ const { unsupportedTags } = validateTags({
2146
+ content: value,
2147
+ tagsParam: tags,
2148
+ injectedTagsParams: injectedTags,
2149
+ location,
2150
+ tagModule: getDefaultTags,
2151
+ }) || {};
2152
+ if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
2153
+ errorMessage = formatMessage(messages.fallbackMsgLenError);
2154
+ } else if (unsupportedTags?.length > 0) {
2155
+ errorMessage = formatMessage(
2156
+ globalMessages.unsupportedTagsValidationError,
2157
+ {
2158
+ unsupportedTags,
2159
+ },
2160
+ );
2161
+ }
2162
+ return errorMessage;
2163
+ };
2164
+
1110
2165
  // Check for forbidden characters: square brackets [] and single curly braces {}
1111
2166
  const forbiddenCharactersValidation = (value) => {
1112
2167
  if (!value) return false;
@@ -1217,6 +2272,117 @@ const onTitleAddVar = () => {
1217
2272
  setTemplateTitleError(error);
1218
2273
  };
1219
2274
 
2275
+ // Carousel: global variables across the whole carousel (all cards, title+body)
2276
+ const getNextCarouselVarToken = () => {
2277
+ const nums = [];
2278
+ (carouselData || []).forEach((c = {}) => {
2279
+ const s1 = (c.title || '').match(/\{\{(\d+)\}\}/g) || [];
2280
+ const s2 = (c.description || '').match(/\{\{(\d+)\}\}/g) || [];
2281
+ [...s1, ...s2].forEach((tok) => {
2282
+ const n = parseInt((tok.match(/\d+/) || [])[0], 10);
2283
+ if (!Number.isNaN(n)) nums.push(n);
2284
+ });
2285
+ });
2286
+ const existing = new Set(nums);
2287
+ let nextNumber = 1;
2288
+ while (existing.has(nextNumber)) nextNumber++;
2289
+ if (nextNumber > 19) return '';
2290
+ return `{{${nextNumber}}}`;
2291
+ };
2292
+
2293
+ const appendVarToCarouselField = (cardIndex, fieldName) => {
2294
+ const token = getNextCarouselVarToken();
2295
+ if (!token) return;
2296
+ setCarouselData((prev = []) => {
2297
+ const updated = cloneDeep(prev);
2298
+ if (!updated[cardIndex]) return prev;
2299
+ const current = (updated[cardIndex][fieldName] || '').toString();
2300
+ updated[cardIndex][fieldName] = `${current}${token}`;
2301
+ return updated;
2302
+ });
2303
+ };
2304
+
2305
+ const textAreaValue = (idValue, type) => {
2306
+ if (idValue >= 0) {
2307
+ const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
2308
+ const templateArr = splitTemplateVarString(templateStr);
2309
+ const token = templateArr?.[idValue] || "";
2310
+ if (token && rcsVarTestRegex.test(token)) {
2311
+ const varName = getVarNameFromToken(token);
2312
+ return (cardVarMapped?.[varName] ?? '').toString();
2313
+ }
2314
+ return "";
2315
+ }
2316
+ return "";
2317
+ };
2318
+
2319
+ // Carousel: render variable-value editor for a given template string (title/description).
2320
+ // This matches rich-card/text edit behavior: static pieces are read-only, variable tokens are editable.
2321
+ const renderCarouselEditMessage = (templateStr) => {
2322
+ const renderArray = [];
2323
+ const templateArr = splitTemplateVarString(templateStr);
2324
+ if (templateArr?.length) {
2325
+ templateArr.forEach((elem, index) => {
2326
+ if (rcsVarTestRegex.test(elem)) {
2327
+ const varName = getVarNameFromToken(elem);
2328
+ renderArray.push(
2329
+ <div key={`${elem}_${index}`} className="var-segment-message-editor__var-slot">
2330
+ <TextArea
2331
+ id={`${elem}_${index}`}
2332
+ placeholder={`enter the value for ${elem}`}
2333
+ autosize={{ minRows: 1, maxRows: 3 }}
2334
+ onChange={(e) => textAreaValueChange(e, TITLE_TEXT)}
2335
+ value={varName ? ((cardVarMapped?.[varName] ?? '').toString()) : ''}
2336
+ onFocus={(e) => {
2337
+ const id = e?.target?.id || e?.currentTarget?.id || '';
2338
+ setCarouselFocusedVarId(id);
2339
+ }}
2340
+ />
2341
+ </div>
2342
+ );
2343
+ } else if (elem) {
2344
+ renderArray.push(
2345
+ <CapHeading
2346
+ key={`static_${index}`}
2347
+ type="h4"
2348
+ className="rcs-edit-template-message-split"
2349
+ >
2350
+ {elem}
2351
+ </CapHeading>
2352
+ );
2353
+ }
2354
+ });
2355
+ }
2356
+ return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
2357
+ };
2358
+
2359
+ const textAreaValueChange = (e, type) => {
2360
+ const value = e?.target?.value ?? '';
2361
+ const id = e?.target?.id || e?.currentTarget?.id || '';
2362
+ if (!id) return;
2363
+ const sep = id.lastIndexOf('_');
2364
+ if (sep === -1) return;
2365
+ const isInvalidValue = value?.trim() === "";
2366
+ const token = id.slice(0, sep);
2367
+ const variableName = getVarNameFromToken(token);
2368
+
2369
+ if (variableName) {
2370
+ setCardVarMapped((prev) => ({
2371
+ ...prev,
2372
+ [variableName]: isInvalidValue ? "" : value,
2373
+ }));
2374
+ }
2375
+ };
2376
+
2377
+ const setTextAreaId = (e, type) => {
2378
+ // VarSegmentMessageEditor calls onFocus(id) with a plain string; DOM events
2379
+ // have an `.target.id` shape. Support both.
2380
+ const id = typeof e === 'string' ? e : (e?.target?.id || e?.currentTarget?.id || '');
2381
+ if (!id) return;
2382
+ if (type === TITLE_TEXT) setTitleTextAreaId(id);
2383
+ else setDescTextAreaId(id);
2384
+ };
2385
+
1220
2386
  const renderButtonComponent = () => {
1221
2387
  return (
1222
2388
  <>
@@ -1242,7 +2408,8 @@ const onTitleAddVar = () => {
1242
2408
  isEditFlow={isEditFlow}
1243
2409
  isFullMode={isFullMode}
1244
2410
  maxButtons={MAX_BUTTONS}
1245
- />
2411
+ host={hostName}
2412
+ />
1246
2413
  </>
1247
2414
  );
1248
2415
  };
@@ -1325,6 +2492,7 @@ const onTitleAddVar = () => {
1325
2492
  }
1326
2493
  suffix={
1327
2494
  <>
2495
+
1328
2496
  {(isEditFlow || !isFullMode) ? (
1329
2497
  <TagList
1330
2498
  label={formatMessage(globalMessages.addLabels)}
@@ -1341,15 +2509,16 @@ const onTitleAddVar = () => {
1341
2509
  type="flat"
1342
2510
  isAddBtn
1343
2511
  onClick={onTitleAddVar}
1344
- disabled={!!templateTitleError}
2512
+ disabled={templateTitleError}
1345
2513
  >
1346
2514
  {formatMessage(messages.addVar)}
1347
2515
  </CapButton>
1348
- )}
2516
+ )}
1349
2517
  </>
1350
- }
2518
+ }
1351
2519
  />
1352
- {(isEditFlow || !isFullMode) ? (
2520
+
2521
+ {(isEditFlow || !isFullMode) ? (
1353
2522
  <VarSegmentMessageEditor
1354
2523
  key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
1355
2524
  templateString={templateTitle}
@@ -1383,7 +2552,7 @@ const onTitleAddVar = () => {
1383
2552
  )}
1384
2553
  {!isEditFlow && isFullMode && renderTitleCharacterCount()}
1385
2554
  </>
1386
- )}
2555
+ )}
1387
2556
 
1388
2557
  {/* Template Message */}
1389
2558
  <CapRow id="rcs-template-message-label">
@@ -1421,10 +2590,10 @@ const onTitleAddVar = () => {
1421
2590
  />
1422
2591
  </CapRow>
1423
2592
  <CapRow className="rcs-create-template-message-input">
1424
- <div className="rcs_text_area_wrapper">
1425
2593
  {/* Edit/library: segmented inputs (split on {{…}}). Full-mode create: single TextArea below — manual entry there never hits segment split. TagList replaces {{n}} in template string here. */}
1426
- {(isEditFlow || !isFullMode)
1427
- ? (
2594
+ <CapRow className="rcs_text_area_wrapper">
2595
+ {(isEditFlow || !isFullMode)?
2596
+ (
1428
2597
  <VarSegmentMessageEditor
1429
2598
  key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
1430
2599
  templateString={templateDesc}
@@ -1473,7 +2642,7 @@ const onTitleAddVar = () => {
1473
2642
  </>
1474
2643
  )
1475
2644
  }
1476
- </div>
2645
+ </CapRow>
1477
2646
  {(isEditFlow || !isFullMode) && templateDescError && (
1478
2647
  <CapError className="rcs-template-message-error">
1479
2648
  {templateDescError}
@@ -1495,7 +2664,8 @@ const onTitleAddVar = () => {
1495
2664
  />
1496
2665
  )}
1497
2666
  </CapRow>
1498
- {renderButtonComponent()}
2667
+ {((!isEditFlow && isFullMode) || (isEditFlow && (suggestions?.length ?? 0) > 0)) &&
2668
+ renderButtonComponent()}
1499
2669
  </>
1500
2670
 
1501
2671
  );
@@ -1515,7 +2685,7 @@ const onTitleAddVar = () => {
1515
2685
  // Get max length for description based on template type
1516
2686
  const getDescriptionMaxLength = () => {
1517
2687
  return templateType === contentType.text_message
1518
- ? RCS_TEXT_MESSAGE_MAX_LENGTH // 160 for text message
2688
+ ? (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH) // 160 for text message
1519
2689
  : RCS_RICH_CARD_MAX_LENGTH; // 2000 for rich card description
1520
2690
  };
1521
2691
 
@@ -1541,6 +2711,63 @@ const onTitleAddVar = () => {
1541
2711
  );
1542
2712
  };
1543
2713
 
2714
+ const rcsDltCardDeleteHandler = () => {
2715
+ closeDltContainerHandler();
2716
+ setDltEditData({});
2717
+ setFallbackMessage('');
2718
+ setFallbackMessageError(false);
2719
+ setShowDltCard(false);
2720
+ };
2721
+
2722
+ const dltFallbackListingPreviewhandler = (data) => {
2723
+ const {
2724
+ 'updated-sms-editor': updatedSmsEditor = [],
2725
+ 'sms-editor': smsEditor = '',
2726
+ } = data.versions.base || {};
2727
+ setFallbackPreviewmode(true);
2728
+ setDltPreviewData(
2729
+ updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
2730
+ );
2731
+ };
2732
+
2733
+ const getDltContentCardList = (content, channel) => {
2734
+ const extra = [
2735
+ <CapIcon
2736
+ type="edit"
2737
+ style={{ marginRight: '8px' }}
2738
+ onClick={() => rcsDltEditSelectHandler(dltEditData)}
2739
+ />,
2740
+ <CapDropdown
2741
+ overlay={(
2742
+ <CapMenu>
2743
+ <>
2744
+ <CapMenu.Item
2745
+ className="ant-dropdown-menu-item"
2746
+ onClick={() => setFallbackPreviewmode(true)}
2747
+ >
2748
+ {formatMessage(globalMessages.preview)}
2749
+ </CapMenu.Item>
2750
+ <CapMenu.Item
2751
+ className="ant-dropdown-menu-item"
2752
+ onClick={rcsDltCardDeleteHandler}
2753
+ >
2754
+ {formatMessage(globalMessages.delete)}
2755
+ </CapMenu.Item>
2756
+ </>
2757
+ </CapMenu>
2758
+ )}
2759
+ >
2760
+ <CapIcon type="more" />
2761
+ </CapDropdown>,
2762
+ ];
2763
+ return {
2764
+ title: channel,
2765
+ content,
2766
+ cardType: channel,
2767
+ extra,
2768
+ };
2769
+ };
2770
+
1544
2771
  // Render character count for description/message
1545
2772
  const renderDescriptionCharacterCount = (className = "rcs-character-count") => {
1546
2773
  const currentLength = getDescriptionCharacterCount();
@@ -1556,11 +2783,31 @@ const onTitleAddVar = () => {
1556
2783
  );
1557
2784
  };
1558
2785
 
2786
+ // Carousel: per-card character counts (same limits as rich card)
2787
+ const getCarouselTitleCharacterCount = (cardIndex) => {
2788
+ const t = carouselData?.[cardIndex]?.title || '';
2789
+ return t ? t.length : 0;
2790
+ };
2791
+
2792
+ const getCarouselDescriptionCharacterCount = (cardIndex) => {
2793
+ const d = carouselData?.[cardIndex]?.description || '';
2794
+ return d ? d.length : 0;
2795
+ };
2796
+
2797
+ const renderCarouselCharacterCount = (currentLength, maxLength, className = "rcs-character-count") => (
2798
+ <CapLabel type="label1" className={className}>
2799
+ {formatMessage(messages.templateMessageLength, {
2800
+ currentLength,
2801
+ maxLength,
2802
+ })}
2803
+ </CapLabel>
2804
+ );
2805
+
1559
2806
  // Check if any RCS variables contain tags (similar to Zalo hasTag logic)
1560
2807
  const hasTag = () => {
1561
2808
  // Check cardVarMapped values for tags
1562
2809
  if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
1563
- const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
2810
+ const hasTagInMapped = Object.values(cardVarMapped).some(value =>
1564
2811
  isTagIncluded(value)
1565
2812
  );
1566
2813
  if (hasTagInMapped) return true;
@@ -1578,6 +2825,14 @@ const onTitleAddVar = () => {
1578
2825
  return false;
1579
2826
  };
1580
2827
 
2828
+ //adding creative dlt fallback sms handlers
2829
+ const addDltMsgHandler = () => {
2830
+ setShowDltContainer(true);
2831
+ setDltMode(RCS_DLT_MODE.TEMPLATES);
2832
+ setDltEditData({});
2833
+ setFallbackMessage('');
2834
+ };
2835
+
1581
2836
  const closeDltContainerHandler = () => {
1582
2837
  setShowDltContainer(false);
1583
2838
  setDltMode('');
@@ -1637,6 +2892,7 @@ const onTitleAddVar = () => {
1637
2892
  isFullMode={isFullMode}
1638
2893
  isDltFromRcs
1639
2894
  onSelectTemplate={rcsDltEditSelectHandler}
2895
+ handlePeviewTemplate={dltFallbackListingPreviewhandler}
1640
2896
  />
1641
2897
  );
1642
2898
  } else if (dltMode === RCS_DLT_MODE.EDIT) {
@@ -1683,7 +2939,8 @@ const onTitleAddVar = () => {
1683
2939
  );
1684
2940
 
1685
2941
  const uploadRcsImage = useCallback((file, type, fileParams, index) => {
1686
- const isRcsThumbnail = index === 1;
2942
+ setImageError(null);
2943
+ const isRcsThumbnail = isThumbnailAssetIndex(index);
1687
2944
  actions.uploadRcsAsset(file, type, {
1688
2945
  isRcsThumbnail,
1689
2946
  ...fileParams,
@@ -1694,6 +2951,7 @@ const onTitleAddVar = () => {
1694
2951
 
1695
2952
  const setUpdateRcsImageSrc = useCallback(
1696
2953
  (val) => {
2954
+ setAssetListImage(val);
1697
2955
  updateRcsImageSrc(val);
1698
2956
  actions.clearRcsMediaAsset(0);
1699
2957
  },
@@ -1720,8 +2978,6 @@ const onTitleAddVar = () => {
1720
2978
  const updateOnRcsImageReUpload = useCallback(() => {
1721
2979
  setUpdateRcsImageSrc('');
1722
2980
  }, []);
1723
-
1724
-
1725
2981
  const uploadRcsVideo = (file, type, fileParams) => {
1726
2982
  actions.uploadRcsAsset(file, type, {
1727
2983
  ...fileParams,
@@ -1753,13 +3009,13 @@ const onTitleAddVar = () => {
1753
3009
  updateRcsThumbnailSrc('');
1754
3010
  };
1755
3011
 
1756
- const renderThumbnailComponent = () => {
3012
+ const renderThumbnailComponent = () => {
1757
3013
  const currentDimension = selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
1758
3014
  return !isEditFlow && (
1759
3015
  <>
1760
3016
  <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
1761
3017
  <CapImageUpload
1762
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
3018
+ style={{ paddingTop: '20px' }}
1763
3019
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1764
3020
  imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
1765
3021
  imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
@@ -1771,11 +3027,13 @@ const onTitleAddVar = () => {
1771
3027
  updateOnReUpload={updateOnRcsThumbnailReUpload}
1772
3028
  minImgSize={RCS_THUMBNAIL_MIN_SIZE}
1773
3029
  index={1}
3030
+ className="cap-custom-image-upload"
1774
3031
  key={`rcs-uploaded-image-${currentDimension}`}
1775
3032
  imageData={thumbnailData}
1776
3033
  channel={RCS}
1777
3034
  channelSpecificStyle={!isFullMode}
1778
3035
  skipDimensionValidation={true}
3036
+ showReUploadButton={!isEditFlow && isFullMode}
1779
3037
  />
1780
3038
  </>
1781
3039
  )
@@ -1815,6 +3073,7 @@ const onTitleAddVar = () => {
1815
3073
  </div>
1816
3074
  ) : (
1817
3075
  <CapImageUpload
3076
+ style={{ paddingTop: '20px' }}
1818
3077
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1819
3078
  imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
1820
3079
  imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
@@ -1836,7 +3095,7 @@ const onTitleAddVar = () => {
1836
3095
 
1837
3096
  </>
1838
3097
  );
1839
- }
3098
+ }
1840
3099
 
1841
3100
  const renderVideoComponent = () => {
1842
3101
  const currentDimension =selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
@@ -1913,6 +3172,36 @@ const onTitleAddVar = () => {
1913
3172
  };
1914
3173
 
1915
3174
  const getRcsPreview = () => {
3175
+
3176
+ if (templateType === contentType.carousel) {
3177
+ const cardsForPreview = buildCarouselCardsForPreview(carouselData);
3178
+ const carouselDimKey = getCarouselDimensionKey();
3179
+ const carouselImgDims =
3180
+ RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
3181
+ const carouselVidDims =
3182
+ RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
3183
+ || RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
3184
+ // Debug log for embedded/library mode preview payload (carousel)
3185
+ // eslint-disable-next-line no-console
3186
+ return (
3187
+ <UnifiedPreview
3188
+ channel={RCS}
3189
+ content={{
3190
+ carouselData: cardsForPreview,
3191
+ carouselPreviewDimensions: {
3192
+ imageWidth: carouselImgDims.width,
3193
+ imageHeight: carouselImgDims.height,
3194
+ videoThumbWidth: carouselVidDims.width,
3195
+ videoThumbHeight: carouselVidDims.height,
3196
+ },
3197
+ }}
3198
+ device={ANDROID}
3199
+ showDeviceToggle={false}
3200
+ showHeader={false}
3201
+ formatMessage={formatMessage}
3202
+ />
3203
+ );
3204
+ }
1916
3205
 
1917
3206
  const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
1918
3207
  const isSlotMappingMode = isEditFlow || !isFullMode;
@@ -2235,7 +3524,7 @@ const onTitleAddVar = () => {
2235
3524
  * with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
2236
3525
  * miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
2237
3526
  */
2238
- const librarySmsFallbackMergedForValidation = useMemo(() => {
3527
+ const librarySmsFallbackMergedForValidation = useMemo(() => {
2239
3528
  if (isFullMode) {
2240
3529
  return smsFallbackData;
2241
3530
  }
@@ -2249,6 +3538,8 @@ const onTitleAddVar = () => {
2249
3538
  };
2250
3539
  }, [isFullMode, templateData, smsFallbackData]);
2251
3540
 
3541
+
3542
+
2252
3543
  const actionCallback = ({ errorMessage, resp }, isEdit) => {
2253
3544
  // eslint-disable-next-line no-undef
2254
3545
  const error = errorMessage?.message || errorMessage;
@@ -2395,7 +3686,7 @@ const onTitleAddVar = () => {
2395
3686
  return true;
2396
3687
  }
2397
3688
 
2398
- if (isMediaTypeVideo && (rcsVideoSrc.videoSrc === '' || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
3689
+ if (isMediaTypeVideo && (!rcsVideoSrc.videoSrc || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
2399
3690
  return true;
2400
3691
  }
2401
3692
  if (buttonType.includes(CTA)) {
@@ -2420,7 +3711,7 @@ const onTitleAddVar = () => {
2420
3711
  };
2421
3712
 
2422
3713
  const isEditDisableDone = () => {
2423
- if (templateStatus !== RCS_STATUSES.approved) {
3714
+ if (!isHostInfoBip && templateStatus !== RCS_STATUSES.approved) {
2424
3715
  return true;
2425
3716
  }
2426
3717
 
@@ -2514,7 +3805,6 @@ const onTitleAddVar = () => {
2514
3805
  // Slideboxes are rendered outside the page-level spinner to avoid
2515
3806
  // stacking/blur issues during initial loads.
2516
3807
  if (showDltContainer) return null;
2517
-
2518
3808
  return (
2519
3809
  <>
2520
3810
  {templateStatus !== '' && (
@@ -2524,7 +3814,7 @@ const onTitleAddVar = () => {
2524
3814
  {formatMessage(messages.templateStatusLabel)}
2525
3815
  </CapLabel>
2526
3816
 
2527
- {templateStatus && (
3817
+ {!isHostInfoBip && templateStatus && (
2528
3818
  <CapAlert
2529
3819
  message={getTemplateStatusMessage()}
2530
3820
  type={getTemplateStatusType(templateStatus)}
@@ -2563,6 +3853,7 @@ const onTitleAddVar = () => {
2563
3853
  )
2564
3854
  )}
2565
3855
  {renderLabel('templateTypeLabel')}
3856
+
2566
3857
  <CapRadioGroup
2567
3858
  id="select-rcs-template-type"
2568
3859
  options={TEMPLATE_TYPE_OPTIONS}
@@ -2571,23 +3862,29 @@ const onTitleAddVar = () => {
2571
3862
  disabled={(isEditFlow || !isFullMode)}
2572
3863
  />
2573
3864
 
2574
- {/* Show media only for rich_card or carousel */}
2575
- {(templateType === contentType.rich_card || templateType === contentType.carousel) && (
3865
+ {templateType === contentType.carousel ? (
3866
+ renderCarouselSection()
3867
+ ) : (
2576
3868
  <>
2577
- {renderLabel('mediaLabel')}
2578
- <CapRadioGroup
2579
- options={mediaRadioOptions || []}
2580
- value={templateMediaType}
2581
- onChange={onTemplateMediaTypeChange}
2582
- disabled={(isEditFlow || !isFullMode)}
2583
- className="rcs-radio"
2584
- />
2585
- <div className="rcs-container-image">
2586
- {getMediaBasedComponent()}
2587
- </div>
3869
+ {/* Show media only for rich_card */}
3870
+ {templateType === contentType.rich_card && (
3871
+ <>
3872
+ {renderLabel('mediaLabel')}
3873
+ <CapRadioGroup
3874
+ options={mediaRadioOptions || []}
3875
+ value={templateMediaType}
3876
+ onChange={onTemplateMediaTypeChange}
3877
+ disabled={(isEditFlow || !isFullMode)}
3878
+ className="rcs-radio"
3879
+ />
3880
+ <div className="rcs-container-image">
3881
+ {getMediaBasedComponent()}
3882
+ </div>
3883
+ </>
3884
+ )}
3885
+ {renderTextComponent()}
2588
3886
  </>
2589
3887
  )}
2590
- {renderTextComponent()}
2591
3888
  <CapDivider className="rcs-fallback-section-divider" />
2592
3889
  {renderFallBackSmsComponent()}
2593
3890
  <div className="rcs-scroll-div" />
@@ -2609,7 +3906,9 @@ const onTitleAddVar = () => {
2609
3906
  disabled={isDisableDone()}
2610
3907
  className="rcs-done-btn"
2611
3908
  >
2612
- <FormattedMessage {...messages.sendForApprovalButtonLabel} />
3909
+ <FormattedMessage
3910
+ {...(isHostInfoBip ? messages.doneButtonLabel : messages.sendForApprovalButtonLabel)}
3911
+ />
2613
3912
  </CapButton>
2614
3913
  </div>
2615
3914
  <CapTooltip
@@ -2705,12 +4004,14 @@ const onTitleAddVar = () => {
2705
4004
  );
2706
4005
  };
2707
4006
 
4007
+
2708
4008
  const mapStateToProps = createStructuredSelector({
2709
4009
  rcsData: makeSelectRcs(),
2710
4010
  accountData: makeSelectAccount(),
2711
4011
  metaEntities: makeSelectMetaEntities(),
2712
4012
  loadingTags: isLoadingMetaEntities(),
2713
4013
  injectedTags: setInjectedTags(),
4014
+ currentOrgDetails: selectCurrentOrgDetails(),
2714
4015
  });
2715
4016
 
2716
4017
  const mapDispatchToProps = (dispatch) => ({