@capillarytech/creatives-library 8.0.330-alpha.0 → 8.0.330-alpha.2
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.
- package/package.json +1 -1
- package/utils/tests/tagValidations.test.js +20 -0
- package/v2Components/CapActionButton/constants.js +7 -0
- package/v2Components/CapActionButton/index.js +167 -109
- package/v2Components/CapActionButton/index.scss +157 -6
- package/v2Components/CapActionButton/messages.js +19 -3
- package/v2Components/CapActionButton/tests/index.test.js +41 -17
- package/v2Components/CapTagList/index.js +28 -23
- package/v2Components/CapTagList/style.scss +29 -0
- package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
- package/v2Components/CapTagListWithInput/index.js +4 -0
- package/v2Components/CapWhatsappCTA/index.js +2 -0
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +1 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +323 -77
- package/v2Components/CommonTestAndPreview/messages.js +8 -0
- package/v2Components/CommonTestAndPreview/reducer.js +3 -1
- package/v2Components/CommonTestAndPreview/sagas.js +2 -1
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/FormBuilder/index.js +1 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +143 -28
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/mockdata.js +1 -0
- package/v2Containers/BeeEditor/index.js +19 -1
- package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -0
- package/v2Containers/Email/index.js +78 -39
- package/v2Containers/Email/reducer.js +2 -2
- package/v2Containers/Email/sagas.js +3 -1
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -2
- package/v2Containers/Email/tests/sagas.test.js +230 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +6 -1
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -1
- package/v2Containers/EmailWrapper/index.js +4 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +2 -0
- package/v2Containers/MobilePush/Edit/index.js +2 -0
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/constants.js +79 -5
- package/v2Containers/Rcs/index.js +1374 -73
- package/v2Containers/Rcs/index.js.rej +1336 -0
- package/v2Containers/Rcs/index.scss +191 -0
- package/v2Containers/Rcs/index.scss.rej +74 -0
- package/v2Containers/Rcs/messages.js +26 -1
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +69173 -118166
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
- package/v2Containers/Rcs/tests/index.test.js +132 -94
- package/v2Containers/Rcs/tests/utils.test.js +220 -38
- package/v2Containers/Rcs/utils.js +77 -1
- package/v2Containers/Sms/Edit/index.js +2 -0
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/TagList/index.js +73 -20
- package/v2Containers/TagList/messages.js +4 -0
- package/v2Containers/TagList/tests/TagList.test.js +124 -20
- package/v2Containers/TagList/tests/mockdata.js +17 -0
- package/v2Containers/Templates/_templates.scss +99 -0
- package/v2Containers/Templates/index.js +29 -14
- package/v2Containers/Viber/index.js +3 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
- package/v2Containers/WebPush/Create/index.js +10 -2
- package/v2Containers/Whatsapp/index.js +5 -0
- 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(
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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={
|
|
2512
|
+
disabled={templateTitleError}
|
|
1345
2513
|
>
|
|
1346
2514
|
{formatMessage(messages.addVar)}
|
|
1347
2515
|
</CapButton>
|
|
1348
|
-
|
|
2516
|
+
)}
|
|
1349
2517
|
</>
|
|
1350
|
-
}
|
|
2518
|
+
}
|
|
1351
2519
|
/>
|
|
1352
|
-
|
|
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
|
-
|
|
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
|
-
</
|
|
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
|
-
{
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
{
|
|
2575
|
-
|
|
3865
|
+
{templateType === contentType.carousel ? (
|
|
3866
|
+
renderCarouselSection()
|
|
3867
|
+
) : (
|
|
2576
3868
|
<>
|
|
2577
|
-
{
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
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
|
|
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) => ({
|