@capillarytech/creatives-library 9.0.29 → 9.0.30
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/constants/unified.js +29 -0
- package/global-styles.js +1 -1
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/commonUtils.js +22 -1
- package/utils/rcsPayloadUtils.js +102 -0
- package/utils/templateVarUtils.js +198 -0
- package/utils/tests/rcsPayloadUtils.test.js +295 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +21 -1
- package/v2Components/CapActionButton/index.js +261 -144
- package/v2Components/CapActionButton/index.scss +245 -14
- package/v2Components/CapActionButton/messages.js +32 -3
- package/v2Components/CapActionButton/tests/index.test.js +74 -19
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +9 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +58 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +186 -22
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +35 -2
- package/v2Components/CommonTestAndPreview/index.js +770 -231
- package/v2Components/CommonTestAndPreview/messages.js +45 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +25 -6
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +412 -257
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +337 -63
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +108 -15
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
- package/v2Components/CommonTestAndPreview/tests/utils.test.js +151 -0
- package/v2Components/CommonTestAndPreview/utils.js +84 -0
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
- package/v2Components/SmsFallback/constants.js +94 -0
- package/v2Components/SmsFallback/index.js +958 -0
- package/v2Components/SmsFallback/index.scss +266 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +471 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +147 -32
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
- package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +27 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
- package/v2Containers/CreativesContainer/constants.js +11 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +77 -0
- package/v2Containers/CreativesContainer/index.js +322 -106
- package/v2Containers/CreativesContainer/index.scss +102 -1
- package/v2Containers/CreativesContainer/tests/CreativesSlideBoxWrapper.test.js +58 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.smsDltPreview.test.js +73 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +37 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +103 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +193 -15
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +88 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +549 -57
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/carouselUtils.js +224 -0
- package/v2Containers/Rcs/components/CarouselCard.js +317 -0
- package/v2Containers/Rcs/components/CarouselCardButtons.js +113 -0
- package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
- package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
- package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
- package/v2Containers/Rcs/constants.js +132 -16
- package/v2Containers/Rcs/index.js +1822 -946
- package/v2Containers/Rcs/index.scss +443 -8
- package/v2Containers/Rcs/messages.js +45 -22
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +494 -0
- package/v2Containers/Rcs/tests/CarouselCard.test.js +464 -0
- package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
- package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
- package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
- package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74440 -39387
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
- package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
- package/v2Containers/Rcs/tests/index.test.js +275 -40
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +912 -0
- package/v2Containers/Rcs/tests/utils.test.js +682 -30
- package/v2Containers/Rcs/utils.js +514 -12
- package/v2Containers/Sms/Create/index.js +115 -48
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +678 -169
- package/v2Containers/SmsTrai/Edit/index.scss +126 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +5 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +196 -12
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +131 -59
- package/v2Containers/Templates/sagas.js +57 -13
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
- package/v2Containers/Templates/tests/sagas.test.js +199 -16
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +88 -25
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/* eslint-disable no-unused-expressions */
|
|
2
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
3
3
|
import { bindActionCreators } from 'redux';
|
|
4
4
|
import { createStructuredSelector } from 'reselect';
|
|
5
|
-
import {
|
|
5
|
+
import { FormattedMessage } from 'react-intl';
|
|
6
6
|
import get from 'lodash/get';
|
|
7
7
|
import isEmpty from 'lodash/isEmpty';
|
|
8
8
|
import cloneDeep from 'lodash/cloneDeep';
|
|
9
9
|
import isNil from 'lodash/isNil';
|
|
10
|
-
import styled from 'styled-components';
|
|
11
10
|
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
12
11
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
13
12
|
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
@@ -23,8 +22,6 @@ import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
|
23
22
|
import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
24
23
|
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
25
24
|
import CapImage from '@capillarytech/cap-ui-library/CapImage';
|
|
26
|
-
import CapCard from '@capillarytech/cap-ui-library/CapCard';
|
|
27
|
-
import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
|
|
28
25
|
import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
|
|
29
26
|
import CapCustomCard from '@capillarytech/cap-ui-library/CapCustomCard';
|
|
30
27
|
import CapDropdown from '@capillarytech/cap-ui-library/CapDropdown';
|
|
@@ -35,6 +32,8 @@ import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
|
35
32
|
import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
|
|
36
33
|
import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
|
|
37
34
|
import CapLink from '@capillarytech/cap-ui-library/CapLink';
|
|
35
|
+
import CapTab from '@capillarytech/cap-ui-library/CapTab';
|
|
36
|
+
import { flushSync } from 'react-dom';
|
|
38
37
|
|
|
39
38
|
import {
|
|
40
39
|
CAP_G01,
|
|
@@ -50,17 +49,29 @@ import {
|
|
|
50
49
|
import CapVideoUpload from '../../v2Components/CapVideoUpload';
|
|
51
50
|
import * as globalActions from '../Cap/actions';
|
|
52
51
|
import CapActionButton from '../../v2Components/CapActionButton';
|
|
52
|
+
import TemplatePreview from '../../v2Components/TemplatePreview';
|
|
53
53
|
import { makeSelectRcs, makeSelectAccount } from './selectors';
|
|
54
54
|
import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
|
|
55
55
|
import {
|
|
56
56
|
isLoadingMetaEntities,
|
|
57
57
|
makeSelectMetaEntities,
|
|
58
58
|
setInjectedTags,
|
|
59
|
+
selectCurrentOrgDetails,
|
|
59
60
|
} from '../Cap/selectors';
|
|
60
61
|
import * as RcsActions from './actions';
|
|
61
62
|
import { isAiContentBotDisabled } from '../../utils/common';
|
|
62
|
-
import * as TemplatesActions from '../Templates/actions';
|
|
63
63
|
import './index.scss';
|
|
64
|
+
import {
|
|
65
|
+
normalizeLibraryLoadedTitleDesc,
|
|
66
|
+
mergeRcsSmsFallbackVarMapLayers,
|
|
67
|
+
applySmsFallbackHydration,
|
|
68
|
+
syncCardVarMappedSemanticsFromSlots,
|
|
69
|
+
hasMeaningfulSmsFallbackShape,
|
|
70
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
71
|
+
mergeSmsFallbackForLibrary,
|
|
72
|
+
buildSmsFallBackContentForPayload,
|
|
73
|
+
pickRcsCardVarMappedEntries,
|
|
74
|
+
} from './rcsLibraryHydrationUtils';
|
|
64
75
|
import {
|
|
65
76
|
RCS,
|
|
66
77
|
SMS,
|
|
@@ -75,8 +86,8 @@ import {
|
|
|
75
86
|
LIBRARY,
|
|
76
87
|
ALLOWED_IMAGE_EXTENSIONS_REGEX,
|
|
77
88
|
RCS_IMG_SIZE,
|
|
78
|
-
RCS_DLT_MODE,
|
|
79
89
|
CTA,
|
|
90
|
+
AI_CONTENT_BOT_DISABLED,
|
|
80
91
|
RCS_STATUSES,
|
|
81
92
|
TITLE_TEXT,
|
|
82
93
|
MESSAGE_TEXT,
|
|
@@ -91,9 +102,13 @@ import {
|
|
|
91
102
|
rcsVarTestRegex,
|
|
92
103
|
RCS_IMAGE_DIMENSIONS,
|
|
93
104
|
RCS_TEXT_MESSAGE_MAX_LENGTH,
|
|
105
|
+
RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP,
|
|
94
106
|
RCS_RICH_CARD_MAX_LENGTH,
|
|
95
107
|
RCS_VIDEO_THUMBNAIL_DIMENSIONS,
|
|
108
|
+
RCS_CAROUSEL_IMAGE_DIMENSIONS,
|
|
109
|
+
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS,
|
|
96
110
|
MAX_BUTTONS,
|
|
111
|
+
INITIAL_SUGGESTIONS,
|
|
97
112
|
INITIAL_SUGGESTIONS_DATA_STOP,
|
|
98
113
|
RCS_BUTTON_TYPES,
|
|
99
114
|
titletype,
|
|
@@ -103,25 +118,66 @@ import {
|
|
|
103
118
|
SMALL,
|
|
104
119
|
MEDIUM,
|
|
105
120
|
RICHCARD,
|
|
121
|
+
HOST_INFOBIP,
|
|
122
|
+
HOST_ICS,
|
|
123
|
+
STOP,
|
|
124
|
+
RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
|
|
125
|
+
MAX_RCS_CAROUSEL_ALLOWED,
|
|
126
|
+
RCS_CAROUSEL_INITIAL_CARD,
|
|
127
|
+
RCS_CAROUSEL_INITIAL_FIRST_CARD,
|
|
128
|
+
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
129
|
+
RCS_NUMERIC_VAR_TOKEN_REGEX,
|
|
130
|
+
RCS_STRIP_MUSTACHE_DELIMITERS_REGEX,
|
|
131
|
+
RCS_TAG_AREA_FIELD_TITLE,
|
|
132
|
+
RCS_TAG_AREA_FIELD_DESC,
|
|
133
|
+
REGEX_SPECIAL_CHARS_ESCAPE_PATTERN,
|
|
134
|
+
JS_TYPES,
|
|
106
135
|
} from './constants';
|
|
107
136
|
import globalMessages from '../Cap/messages';
|
|
108
137
|
import messages from './messages';
|
|
109
138
|
import creativesMessages from '../CreativesContainer/messages';
|
|
110
139
|
import withCreatives from '../../hoc/withCreatives';
|
|
111
140
|
import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
112
|
-
import
|
|
141
|
+
import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
|
|
142
|
+
import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
113
143
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
144
|
+
import { splitTemplateVarString } from '../../utils/templateVarUtils';
|
|
114
145
|
import CapImageUpload from '../../v2Components/CapImageUpload';
|
|
115
|
-
import
|
|
116
|
-
import
|
|
117
|
-
import SmsTraiEdit from '../SmsTrai/Edit';
|
|
146
|
+
import SmsFallback from '../../v2Components/SmsFallback';
|
|
147
|
+
import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
|
|
118
148
|
import TagList from '../TagList';
|
|
119
149
|
import { validateTags } from '../../utils/tagValidations';
|
|
120
|
-
import {
|
|
150
|
+
import { isTraiDLTEnable } from '../../utils/common';
|
|
121
151
|
import { isTagIncluded } from '../../utils/commonUtils';
|
|
122
152
|
import injectReducer from '../../utils/injectReducer';
|
|
123
153
|
import v2RcsReducer from './reducer';
|
|
124
|
-
import {
|
|
154
|
+
import {
|
|
155
|
+
buildRcsNumericMustachePlaceholderRegex,
|
|
156
|
+
getTemplateStatusType,
|
|
157
|
+
normalizeCardVarMapped,
|
|
158
|
+
coalesceCardVarMappedToTemplate,
|
|
159
|
+
getRcsSemanticVarNamesSpanningTitleAndDesc,
|
|
160
|
+
resolveCardVarMappedSlotValue,
|
|
161
|
+
sanitizeCardVarMappedValue,
|
|
162
|
+
checkSmsFallbackBlocksDone,
|
|
163
|
+
isSmsFallbackContentTooLong,
|
|
164
|
+
} from './utils';
|
|
165
|
+
import {
|
|
166
|
+
getCarouselImageAssetIndex,
|
|
167
|
+
getCarouselVideoAssetIndex,
|
|
168
|
+
getCarouselThumbnailAssetIndex,
|
|
169
|
+
isThumbnailAssetIndex,
|
|
170
|
+
getCarouselDimensionKey,
|
|
171
|
+
ensureFirstCardDefaultPhoneSuggestions,
|
|
172
|
+
checkDisableAddCarouselButton,
|
|
173
|
+
getNextCarouselVarToken,
|
|
174
|
+
getCarouselTitleCharacterCount,
|
|
175
|
+
getCarouselDescriptionCharacterCount,
|
|
176
|
+
buildCarouselCardsForPreview as buildCarouselCardsForPreviewUtil,
|
|
177
|
+
buildCarouselCardContentForPayload,
|
|
178
|
+
} from './carouselUtils';
|
|
179
|
+
import CarouselDimensionSelection from './components/CarouselDimensionSelection';
|
|
180
|
+
import CarouselCard from './components/CarouselCard';
|
|
125
181
|
|
|
126
182
|
|
|
127
183
|
const { Group: CapCheckboxGroup } = CapCheckbox;
|
|
@@ -135,31 +191,48 @@ export const Rcs = (props) => {
|
|
|
135
191
|
templateData = {},
|
|
136
192
|
rcsData = {},
|
|
137
193
|
thumbnailData = {}, // Add separate thumbnail data
|
|
138
|
-
templatesActions,
|
|
139
194
|
globalActions,
|
|
140
195
|
location,
|
|
141
|
-
handleClose,
|
|
142
196
|
getDefaultTags,
|
|
143
197
|
supportedTags,
|
|
144
198
|
metaEntities,
|
|
145
199
|
injectedTags,
|
|
146
200
|
loadingTags,
|
|
147
201
|
getFormData,
|
|
148
|
-
isDltEnabled,
|
|
149
202
|
smsRegister,
|
|
203
|
+
orgUnitId,
|
|
150
204
|
selectedOfferDetails,
|
|
151
205
|
eventContextTags,
|
|
152
|
-
waitEventContextTags,
|
|
153
206
|
accountData = {},
|
|
207
|
+
currentOrgDetails,
|
|
154
208
|
// TestAndPreviewSlidebox props
|
|
155
209
|
showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
|
|
156
210
|
handleTestAndPreview: propsHandleTestAndPreview,
|
|
157
211
|
handleCloseTestAndPreview: propsHandleCloseTestAndPreview,
|
|
212
|
+
handleClose,
|
|
158
213
|
} = props || {};
|
|
159
214
|
const { formatMessage } = intl;
|
|
160
215
|
const { TextArea } = CapInput;
|
|
161
216
|
const { CapCustomCardList } = CapCustomCard;
|
|
217
|
+
|
|
218
|
+
// Defensive: React cannot render plain objects as children (crashes with
|
|
219
|
+
// "Objects are not valid as a React child"). Some campaigns (!isFullMode) flows
|
|
220
|
+
// can accidentally set an error state to an object (e.g. `{}`).
|
|
221
|
+
const normalizeErrorMessage = (err) => {
|
|
222
|
+
if (!err) return '';
|
|
223
|
+
if (React.isValidElement(err)) return err;
|
|
224
|
+
if (typeof err === JS_TYPES.STRING) return err;
|
|
225
|
+
if (typeof err === JS_TYPES.NUMBER) return String(err);
|
|
226
|
+
if (typeof err === JS_TYPES.OBJECT && typeof err.message === JS_TYPES.STRING) return err.message;
|
|
227
|
+
try {
|
|
228
|
+
return JSON.stringify(err);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
return String(err);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
162
234
|
const [isEditFlow, setEditFlow] = useState(false);
|
|
235
|
+
const isEditLike = isEditFlow || !isFullMode;
|
|
163
236
|
const [tags, updateTags] = useState([]);
|
|
164
237
|
const [spin, setSpin] = useState(false);
|
|
165
238
|
//template
|
|
@@ -168,33 +241,17 @@ export const Rcs = (props) => {
|
|
|
168
241
|
const [templateMediaType, setTemplateMediaType] = useState(
|
|
169
242
|
RCS_MEDIA_TYPES.NONE,
|
|
170
243
|
);
|
|
171
|
-
const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
|
|
172
244
|
const [templateTitle, setTemplateTitle] = useState('');
|
|
173
245
|
const [templateDesc, setTemplateDesc] = useState('');
|
|
174
246
|
const [templateDescError, setTemplateDescError] = useState(false);
|
|
175
247
|
const [templateStatus, setTemplateStatus] = useState('');
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const [fallbackMessage, setFallbackMessage] = useState('');
|
|
179
|
-
const [fallbackMessageError, setFallbackMessageError] = useState(false);
|
|
180
|
-
//fallback dlt
|
|
181
|
-
const [showDltContainer, setShowDltContainer] = useState(false);
|
|
182
|
-
const [dltMode, setDltMode] = useState('');
|
|
183
|
-
const [dltEditData, setDltEditData] = useState({});
|
|
184
|
-
const [showDltCard, setShowDltCard] = useState(false);
|
|
185
|
-
const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
|
|
186
|
-
const [dltPreviewData, setDltPreviewData] = useState('');
|
|
248
|
+
/** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
|
|
249
|
+
const [smsFallbackData, setSmsFallbackData] = useState(undefined);
|
|
187
250
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
|
|
188
|
-
const
|
|
251
|
+
const buttonType = RCS_BUTTON_TYPES.NONE;
|
|
189
252
|
const [suggestionError, setSuggestionError] = useState(true);
|
|
253
|
+
const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
|
|
190
254
|
const [templateType, setTemplateType] = useState('text_message');
|
|
191
|
-
const [templateHeader, setTemplateHeader] = useState('');
|
|
192
|
-
const [templateMessage, setTemplateMessage] = useState('');
|
|
193
|
-
const [templateHeaderError, setTemplateHeaderError] = useState('');
|
|
194
|
-
const [templateMessageError, setTemplateMessageError] = useState('');
|
|
195
|
-
const validVarRegex = /\{\{(\d+)\}\}/g;
|
|
196
|
-
const [updatedTitleData, setUpdatedTitleData] = useState([]);
|
|
197
|
-
const [updatedDescData, setUpdatedDescData] = useState([]);
|
|
198
255
|
const [titleVarMappedData, setTitleVarMappedData] = useState({});
|
|
199
256
|
const [descVarMappedData, setDescVarMappedData] = useState({});
|
|
200
257
|
const [titleTextAreaId, setTitleTextAreaId] = useState();
|
|
@@ -205,75 +262,58 @@ export const Rcs = (props) => {
|
|
|
205
262
|
const [rcsVideoSrc, setRcsVideoSrc] = useState({});
|
|
206
263
|
const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
|
|
207
264
|
const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
208
|
-
|
|
265
|
+
// Carousel (UI-only) state
|
|
266
|
+
const [selectedCarousel, setSelectedCarousel] = useState('');
|
|
267
|
+
const [selectedCarouselHeight, setSelectedCarouselHeight] = useState(MEDIUM);
|
|
268
|
+
const [selectedCarouselWidth, setSelectedCarouselWidth] = useState(SMALL);
|
|
269
|
+
const [carouselData, setCarouselData] = useState([]);
|
|
270
|
+
const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
|
|
271
|
+
const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
|
|
272
|
+
const [carouselResetNonce, setCarouselResetNonce] = useState(0);
|
|
273
|
+
const [carouselFocusedVarId, setCarouselFocusedVarId] = useState('');
|
|
274
|
+
const [imageError, setImageError] = useState(null);
|
|
209
275
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
210
276
|
const [cardVarMapped, setCardVarMapped] = useState({});
|
|
277
|
+
/** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
|
|
278
|
+
const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
|
|
279
|
+
const lastHydratedRcsCardVarSignatureRef = useRef(null);
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
|
|
283
|
+
* without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
|
|
284
|
+
* fallback card / content stopped appearing until re-selected.
|
|
285
|
+
*/
|
|
286
|
+
const rcsHydrationDetails = useMemo(
|
|
287
|
+
() => (isFullMode ? rcsData?.templateDetails : templateData),
|
|
288
|
+
[isFullMode, rcsData?.templateDetails, templateData],
|
|
289
|
+
);
|
|
211
290
|
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
226
|
-
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
227
|
-
|
|
228
|
-
// Build media preview object (same pattern as getRcsPreview)
|
|
229
|
-
const mediaPreview = {};
|
|
230
|
-
if (isMediaTypeImage && rcsImageSrc) {
|
|
231
|
-
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
232
|
-
}
|
|
233
|
-
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
234
|
-
// For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
|
|
235
|
-
if (rcsThumbnailSrc) {
|
|
236
|
-
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
237
|
-
} else if (rcsVideoSrc?.videoSrc) {
|
|
238
|
-
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
239
|
-
}
|
|
240
|
-
// Also include thumbnailSrc separately if available
|
|
241
|
-
if (rcsThumbnailSrc) {
|
|
242
|
-
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
291
|
+
/** Skip duplicate /meta/TAG fetches: same query is triggered from (1) useEffect below, (2) title TagList mount, (3) description TagList mount — each calls getTagsforContext('Outbound'). */
|
|
292
|
+
const lastTagSchemaQueryKeyRef = useRef(null);
|
|
293
|
+
/**
|
|
294
|
+
* Library: parent often passes a new `templateData` object reference every render. Re-applying the same
|
|
295
|
+
* SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
|
|
296
|
+
*/
|
|
297
|
+
const lastSmsFallbackHydrationKeyRef = useRef(null);
|
|
298
|
+
|
|
299
|
+
const fetchTagSchemaIfNewQuery = useCallback(
|
|
300
|
+
(query) => {
|
|
301
|
+
const key = JSON.stringify(query);
|
|
302
|
+
if (lastTagSchemaQueryKeyRef.current === key) {
|
|
303
|
+
return;
|
|
243
304
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// templateDesc is used for ALL types (text message body or rich card description)
|
|
250
|
-
// For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
|
|
251
|
-
const contentObj = {
|
|
252
|
-
// Map templateTitle to templateHeader and templateDesc to templateMessage
|
|
253
|
-
templateHeader: templateTitle,
|
|
254
|
-
templateMessage: templateDesc,
|
|
255
|
-
...mediaPreview,
|
|
256
|
-
...(suggestions.length > 0 && {
|
|
257
|
-
suggestions: suggestions,
|
|
258
|
-
}),
|
|
259
|
-
};
|
|
305
|
+
lastTagSchemaQueryKeyRef.current = key;
|
|
306
|
+
globalActions.fetchSchemaForEntity(query);
|
|
307
|
+
},
|
|
308
|
+
[globalActions],
|
|
309
|
+
);
|
|
260
310
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
templateMediaType,
|
|
264
|
-
templateTitle,
|
|
265
|
-
templateDesc,
|
|
266
|
-
rcsImageSrc,
|
|
267
|
-
rcsVideoSrc,
|
|
268
|
-
rcsThumbnailSrc,
|
|
269
|
-
suggestions,
|
|
270
|
-
selectedDimension,
|
|
271
|
-
]);
|
|
311
|
+
// TestAndPreviewSlidebox state
|
|
312
|
+
const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
|
|
272
313
|
|
|
273
314
|
// Handle Test and Preview button click
|
|
274
315
|
const handleTestAndPreview = useCallback(() => {
|
|
275
316
|
setShowTestAndPreviewSlidebox(true);
|
|
276
|
-
setIsTestAndPreviewMode(true);
|
|
277
317
|
if (propsHandleTestAndPreview) {
|
|
278
318
|
propsHandleTestAndPreview();
|
|
279
319
|
}
|
|
@@ -297,31 +337,332 @@ export const Rcs = (props) => {
|
|
|
297
337
|
// For video
|
|
298
338
|
return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
|
|
299
339
|
};
|
|
340
|
+
/** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
|
|
341
|
+
const handleSmsFallbackEditorStateChange = useCallback((patch) => {
|
|
342
|
+
setSmsFallbackData((prev) => {
|
|
343
|
+
if (!patch || typeof patch !== 'object') return prev;
|
|
344
|
+
// Bail out when no template has been selected yet — SmsTraiEdit fires
|
|
345
|
+
// onRcsFallbackEditorStateChange on mount (unicodeValidity, varMapped),
|
|
346
|
+
// which would create a non-null smsFallbackData from nothing and cause
|
|
347
|
+
// the card to appear as "Untitled creative" before any save.
|
|
348
|
+
if (!prev) return prev;
|
|
349
|
+
return { ...prev, ...patch };
|
|
350
|
+
});
|
|
351
|
+
}, []);
|
|
300
352
|
|
|
353
|
+
/** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
|
|
301
354
|
const [accountId, setAccountId] = useState('');
|
|
355
|
+
/** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
|
|
356
|
+
const [wecrmAccountId, setWecrmAccountId] = useState('');
|
|
302
357
|
const [accessToken, setAccessToken] = useState('');
|
|
303
358
|
const [hostName, setHostName] = useState('');
|
|
304
359
|
const [accountName, setAccountName] = useState('');
|
|
360
|
+
const isHostInfoBip = hostName === HOST_INFOBIP;
|
|
361
|
+
const isHostIcs = hostName === HOST_ICS;
|
|
362
|
+
|
|
363
|
+
useEffect(() => {
|
|
364
|
+
setSuggestions(isHostIcs ? INITIAL_SUGGESTIONS_DATA_STOP : []);
|
|
365
|
+
}, [isHostIcs]);
|
|
305
366
|
const [rcsAccount, setRcsAccount] = useState('');
|
|
306
367
|
useEffect(() => {
|
|
307
368
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
308
369
|
if (!isEmpty(accountObj)) {
|
|
309
370
|
const {
|
|
371
|
+
id: wecrmId,
|
|
310
372
|
sourceAccountIdentifier = '',
|
|
311
373
|
configs = {},
|
|
312
374
|
} = accountObj;
|
|
313
|
-
|
|
314
375
|
setAccountId(sourceAccountIdentifier);
|
|
376
|
+
setWecrmAccountId(
|
|
377
|
+
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
378
|
+
);
|
|
315
379
|
setAccessToken(configs.accessToken || '');
|
|
316
380
|
setHostName(accountObj.hostName || '');
|
|
317
381
|
setAccountName(accountObj.name || '');
|
|
318
382
|
setRcsAccount(accountObj.id || '');
|
|
383
|
+
} else {
|
|
384
|
+
setAccountId('');
|
|
385
|
+
setWecrmAccountId('');
|
|
386
|
+
setAccessToken('');
|
|
387
|
+
setHostName('');
|
|
388
|
+
setAccountName('');
|
|
319
389
|
}
|
|
320
390
|
}, [accountData.selectedRcsAccount]);
|
|
321
391
|
|
|
322
392
|
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
323
393
|
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
324
394
|
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
395
|
+
const isCarouselType = templateType === contentType.carousel;
|
|
396
|
+
|
|
397
|
+
const clearCarouselCardMedia = (cardIndex, { clearImage = true, clearVideo = true, clearThumb = true } = {}) => {
|
|
398
|
+
setCarouselData((prev = []) => {
|
|
399
|
+
const updated = cloneDeep(prev);
|
|
400
|
+
if (!updated?.[cardIndex]) return updated;
|
|
401
|
+
if (clearImage) updated[cardIndex].imageSrc = '';
|
|
402
|
+
if (clearVideo) updated[cardIndex].videoAsset = {};
|
|
403
|
+
if (clearThumb) updated[cardIndex].thumbnailSrc = '';
|
|
404
|
+
return updated;
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
if (clearImage) actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIndex));
|
|
408
|
+
if (clearVideo) actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIndex));
|
|
409
|
+
if (clearThumb) actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIndex));
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const resetCarouselMediaForAllCards = () => {
|
|
413
|
+
setCarouselData((prev = []) => {
|
|
414
|
+
const updated = cloneDeep(prev);
|
|
415
|
+
updated.forEach((card) => {
|
|
416
|
+
if (!card) return;
|
|
417
|
+
card.imageSrc = '';
|
|
418
|
+
card.videoAsset = {};
|
|
419
|
+
card.thumbnailSrc = '';
|
|
420
|
+
});
|
|
421
|
+
return updated;
|
|
422
|
+
});
|
|
423
|
+
(carouselData || []).forEach((_, idx) => {
|
|
424
|
+
actions.clearRcsMediaAsset(getCarouselImageAssetIndex(idx));
|
|
425
|
+
actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(idx));
|
|
426
|
+
actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(idx));
|
|
427
|
+
});
|
|
428
|
+
// Force tab panes to remount after global reset (some tab implementations cache inactive panes)
|
|
429
|
+
setCarouselResetNonce((count) => count + 1);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// Always use functional updates: image upload completes in CapImageUpload's useEffect and calls
|
|
433
|
+
// updateImageSrc → this handler. If we cloned carouselData from render, we'd overwrite title/body/
|
|
434
|
+
// suggestions typed since the last commit (stale closure).
|
|
435
|
+
const handleCarouselValueChange = (carouselIndex, fields) => {
|
|
436
|
+
setCarouselData((prev = []) => {
|
|
437
|
+
const updated = cloneDeep(prev);
|
|
438
|
+
if (!updated[carouselIndex]) return prev;
|
|
439
|
+
fields.forEach(({ fieldName, value }) => {
|
|
440
|
+
updated[carouselIndex][fieldName] = value;
|
|
441
|
+
});
|
|
442
|
+
return updated;
|
|
443
|
+
});
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const updateCarouselErrors = (carouselIndex, patch) => {
|
|
447
|
+
setCarouselErrors((prev = []) => {
|
|
448
|
+
const next = Array.isArray(prev) ? [...prev] : [];
|
|
449
|
+
next[carouselIndex] = { ...(next[carouselIndex] || {}), ...patch };
|
|
450
|
+
return next;
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const deleteCarouselCard = (index) => {
|
|
455
|
+
let nextIdx = 0;
|
|
456
|
+
flushSync(() => {
|
|
457
|
+
setCarouselData((prev = []) => {
|
|
458
|
+
const updated = cloneDeep(prev);
|
|
459
|
+
if (index < 0 || index >= updated.length) return updated;
|
|
460
|
+
updated.splice(index, 1);
|
|
461
|
+
nextIdx = Math.max(0, Math.min(index - 1, updated.length - 1));
|
|
462
|
+
return ensureFirstCardDefaultPhoneSuggestions(updated);
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
setCarouselErrors((prev = []) => {
|
|
466
|
+
const next = Array.isArray(prev) ? [...prev] : [];
|
|
467
|
+
next.splice(index, 1);
|
|
468
|
+
return next;
|
|
469
|
+
});
|
|
470
|
+
setActiveCarouselIndex(`${nextIdx}`);
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
const addCarouselCard = () => {
|
|
475
|
+
let newIndex = 0;
|
|
476
|
+
flushSync(() => {
|
|
477
|
+
setCarouselData((prev = []) => {
|
|
478
|
+
const updated = cloneDeep(prev);
|
|
479
|
+
updated.push(cloneDeep(RCS_CAROUSEL_INITIAL_CARD));
|
|
480
|
+
newIndex = updated.length - 1;
|
|
481
|
+
return updated;
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
setCarouselErrors((prev = []) => ([...(Array.isArray(prev) ? prev : []), {}]));
|
|
485
|
+
setActiveCarouselIndex(`${newIndex}`);
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// Initialize carousel data when switching to carousel type
|
|
489
|
+
useEffect(() => {
|
|
490
|
+
if (!isCarouselType) return;
|
|
491
|
+
if (!carouselData || carouselData.length === 0) {
|
|
492
|
+
setCarouselData([cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)]);
|
|
493
|
+
setCarouselErrors([{}]);
|
|
494
|
+
setActiveCarouselIndex('0');
|
|
495
|
+
}
|
|
496
|
+
}, [isCarouselType]);
|
|
497
|
+
|
|
498
|
+
// keep derived carousel key in sync
|
|
499
|
+
useEffect(() => {
|
|
500
|
+
if (!isCarouselType) return;
|
|
501
|
+
if (!selectedCarouselHeight || !selectedCarouselWidth) return;
|
|
502
|
+
// Required format: HEIGHT_WIDTH
|
|
503
|
+
setSelectedCarousel(`${selectedCarouselHeight}_${selectedCarouselWidth}`);
|
|
504
|
+
}, [isCarouselType, selectedCarouselHeight, selectedCarouselWidth]);
|
|
505
|
+
|
|
506
|
+
const renderCarouselDimensionSelection = () => {
|
|
507
|
+
if (!isCarouselType) return null;
|
|
508
|
+
return (
|
|
509
|
+
<CarouselDimensionSelection
|
|
510
|
+
isEditFlow={isEditFlow}
|
|
511
|
+
isFullMode={isFullMode}
|
|
512
|
+
selectedCarouselHeight={selectedCarouselHeight}
|
|
513
|
+
selectedCarouselWidth={selectedCarouselWidth}
|
|
514
|
+
selectedCarousel={selectedCarousel}
|
|
515
|
+
onHeightChange={(val) => {
|
|
516
|
+
// Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
|
|
517
|
+
resetCarouselMediaForAllCards();
|
|
518
|
+
setSelectedCarouselHeight(val);
|
|
519
|
+
}}
|
|
520
|
+
onWidthChange={(val) => {
|
|
521
|
+
// Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
|
|
522
|
+
resetCarouselMediaForAllCards();
|
|
523
|
+
setSelectedCarouselWidth(val);
|
|
524
|
+
}}
|
|
525
|
+
/>
|
|
526
|
+
);
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const getCarouselTabPanes = () => {
|
|
530
|
+
const tagsForPicker = getRcsTags();
|
|
531
|
+
return (carouselData || []).map((card, index) => ({
|
|
532
|
+
key: String(index),
|
|
533
|
+
tab: index + 1,
|
|
534
|
+
content: (
|
|
535
|
+
<CarouselCard
|
|
536
|
+
cardIndex={index}
|
|
537
|
+
card={card}
|
|
538
|
+
carouselLength={carouselData.length}
|
|
539
|
+
isEditFlow={isEditFlow}
|
|
540
|
+
isFullMode={isFullMode}
|
|
541
|
+
mediaRadioOptions={mediaRadioOptions}
|
|
542
|
+
cardError={carouselErrors?.[index]}
|
|
543
|
+
location={location}
|
|
544
|
+
tagsForPicker={tagsForPicker}
|
|
545
|
+
injectedTags={injectedTags}
|
|
546
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
547
|
+
formatMessage={formatMessage}
|
|
548
|
+
titleMaxLength={getTitleMaxLength()}
|
|
549
|
+
descriptionMaxLength={getDescriptionMaxLength()}
|
|
550
|
+
titleCharCount={getCarouselTitleCharacterCount(carouselData, index)}
|
|
551
|
+
descriptionCharCount={getCarouselDescriptionCharacterCount(carouselData, index)}
|
|
552
|
+
dimKey={getCarouselDimensionKey(selectedCarouselHeight, selectedCarouselWidth)}
|
|
553
|
+
rcsData={rcsData}
|
|
554
|
+
uploadRcsImage={uploadRcsImage}
|
|
555
|
+
uploadRcsVideo={uploadRcsVideo}
|
|
556
|
+
hostName={hostName}
|
|
557
|
+
onDelete={() => deleteCarouselCard(index)}
|
|
558
|
+
onMediaTypeChange={(value) => {
|
|
559
|
+
// Reset media fields when switching type
|
|
560
|
+
if (value === RCS_MEDIA_TYPES.IMAGE) {
|
|
561
|
+
// Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
|
|
562
|
+
clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
|
|
563
|
+
handleCarouselValueChange(index, [
|
|
564
|
+
{ fieldName: 'mediaType', value },
|
|
565
|
+
{ fieldName: 'videoAsset', value: {} },
|
|
566
|
+
{ fieldName: 'thumbnailSrc', value: '' },
|
|
567
|
+
]);
|
|
568
|
+
} else {
|
|
569
|
+
// Switching to VIDEO: clear image upload so it doesn't auto-restore.
|
|
570
|
+
clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
|
|
571
|
+
handleCarouselValueChange(index, [
|
|
572
|
+
{ fieldName: 'mediaType', value },
|
|
573
|
+
{ fieldName: 'imageSrc', value: '' },
|
|
574
|
+
]);
|
|
575
|
+
}
|
|
576
|
+
}}
|
|
577
|
+
onTitleChange={({ target: { value } }) => {
|
|
578
|
+
let error = false;
|
|
579
|
+
if (value?.length > TEMPLATE_TITLE_MAX_LENGTH) {
|
|
580
|
+
error = formatMessage(messages.templateHeaderLengthError);
|
|
581
|
+
} else {
|
|
582
|
+
error = variableErrorHandling(value);
|
|
583
|
+
}
|
|
584
|
+
updateCarouselErrors(index, { title: error });
|
|
585
|
+
handleCarouselValueChange(index, [{ fieldName: 'title', value }]);
|
|
586
|
+
}}
|
|
587
|
+
onDescriptionChange={({ target: { value } }) => {
|
|
588
|
+
let error = false;
|
|
589
|
+
if (value?.length > RCS_RICH_CARD_MAX_LENGTH) {
|
|
590
|
+
error = formatMessage(messages.templateMessageLengthError);
|
|
591
|
+
} else {
|
|
592
|
+
error = variableErrorHandling(value);
|
|
593
|
+
}
|
|
594
|
+
updateCarouselErrors(index, { description: error });
|
|
595
|
+
handleCarouselValueChange(index, [{ fieldName: 'description', value }]);
|
|
596
|
+
}}
|
|
597
|
+
onTitleAddVar={() => appendVarToCarouselField(index, 'title')}
|
|
598
|
+
onDescriptionAddVar={() => appendVarToCarouselField(index, 'description')}
|
|
599
|
+
onTagSelect={onCarouselTagSelect}
|
|
600
|
+
onContextChange={handleOnTagsContextChange}
|
|
601
|
+
renderEditMessage={renderCarouselEditMessage}
|
|
602
|
+
onFieldChange={(fieldName, value) => handleCarouselValueChange(index, [{ fieldName, value }])}
|
|
603
|
+
onSuggestionsChange={(nextSuggestions) =>
|
|
604
|
+
handleCarouselValueChange(index, [{ fieldName: 'suggestions', value: nextSuggestions }])}
|
|
605
|
+
/>
|
|
606
|
+
),
|
|
607
|
+
}));
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const renderCarouselSection = () => {
|
|
611
|
+
if (!isCarouselType) return null;
|
|
612
|
+
|
|
613
|
+
const isMaxCarouselCardsReached = MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0);
|
|
614
|
+
const addCardTooltipTitle = isMaxCarouselCardsReached
|
|
615
|
+
? formatMessage(messages.rcsCarouselMaxCardTooltip, { max: MAX_RCS_CAROUSEL_ALLOWED })
|
|
616
|
+
// "Set up the first card" only makes sense while creating a new template —
|
|
617
|
+
// in edit flow the cards already exist, so don't show it there.
|
|
618
|
+
: (!isEditFlow && checkDisableAddCarouselButton(carouselData, activeCarouselIndex) ? formatMessage(messages.rcsCarouselAddCardDisabledTooltip) : '');
|
|
619
|
+
|
|
620
|
+
// No "+" in edit flow (cards can't be added there) — and with no "+", the
|
|
621
|
+
// divider before it has nothing to separate, so skip both together instead
|
|
622
|
+
// of rendering a dangling divider.
|
|
623
|
+
const operations = isEditFlow ? null : (
|
|
624
|
+
<>
|
|
625
|
+
<CapDivider type="vertical" />
|
|
626
|
+
<CapTooltip title={addCardTooltipTitle}>
|
|
627
|
+
<span className="button-disabled-tooltip-wrapper">
|
|
628
|
+
<CapButton
|
|
629
|
+
onClick={addCarouselCard}
|
|
630
|
+
type="flat"
|
|
631
|
+
className="add-carousel-content-button"
|
|
632
|
+
disabled={
|
|
633
|
+
isEditFlow ||
|
|
634
|
+
!isFullMode ||
|
|
635
|
+
isMaxCarouselCardsReached ||
|
|
636
|
+
checkDisableAddCarouselButton(carouselData, activeCarouselIndex)
|
|
637
|
+
}
|
|
638
|
+
>
|
|
639
|
+
<CapIcon type="plus" />
|
|
640
|
+
</CapButton>
|
|
641
|
+
</span>
|
|
642
|
+
</CapTooltip>
|
|
643
|
+
</>
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
return (
|
|
647
|
+
<CapRow className={`rcs-carousel-section ${isEditFlow ? 'rcs-carousel-section--readonly-above' : ''}`}>
|
|
648
|
+
{renderCarouselDimensionSelection()}
|
|
649
|
+
<CapRow className="rcs-carousel-tab">
|
|
650
|
+
<CapTab
|
|
651
|
+
key={`rcs-carousel-tab-${carouselResetNonce}`}
|
|
652
|
+
defaultActiveKey="0"
|
|
653
|
+
activeKey={activeCarouselIndex}
|
|
654
|
+
tabBarExtraContent={operations}
|
|
655
|
+
onChange={(key) => {
|
|
656
|
+
setActiveCarouselIndex(`${key}`);
|
|
657
|
+
// reset focused var when switching cards (as per requirement)
|
|
658
|
+
setCarouselFocusedVarId('');
|
|
659
|
+
}}
|
|
660
|
+
panes={getCarouselTabPanes()}
|
|
661
|
+
/>
|
|
662
|
+
</CapRow>
|
|
663
|
+
</CapRow>
|
|
664
|
+
);
|
|
665
|
+
};
|
|
325
666
|
|
|
326
667
|
const mediaRadioOptions = [
|
|
327
668
|
{
|
|
@@ -330,11 +671,16 @@ export const Rcs = (props) => {
|
|
|
330
671
|
},
|
|
331
672
|
{
|
|
332
673
|
value: RCS_MEDIA_TYPES.VIDEO,
|
|
333
|
-
label: formatMessage(
|
|
674
|
+
label: formatMessage(
|
|
675
|
+
templateType === contentType.carousel
|
|
676
|
+
? messages.carouselMediaVideoOption
|
|
677
|
+
: messages.mediaVideo,
|
|
678
|
+
),
|
|
334
679
|
},
|
|
335
680
|
];
|
|
336
681
|
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
337
682
|
|
|
683
|
+
|
|
338
684
|
const updateButtonChange = (data, index) => {
|
|
339
685
|
if (data && data.text) {
|
|
340
686
|
const forbiddenError = forbiddenCharactersValidation(data.text);
|
|
@@ -371,7 +717,9 @@ export const Rcs = (props) => {
|
|
|
371
717
|
if (isFullMode) return;
|
|
372
718
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
373
719
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
374
|
-
const
|
|
720
|
+
const slotOffset =
|
|
721
|
+
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
722
|
+
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
375
723
|
if (!resolved) {
|
|
376
724
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
377
725
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -380,8 +728,8 @@ export const Rcs = (props) => {
|
|
|
380
728
|
|
|
381
729
|
let contentForValidation = resolved;
|
|
382
730
|
const placeholderTokens = templateStr.match(rcsVarRegex) || [];
|
|
383
|
-
placeholderTokens.forEach((
|
|
384
|
-
const escaped =
|
|
731
|
+
placeholderTokens.forEach((token) => {
|
|
732
|
+
const escaped = token.replace(REGEX_SPECIAL_CHARS_ESCAPE_PATTERN, '\\$&');
|
|
385
733
|
contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
|
|
386
734
|
});
|
|
387
735
|
if (!contentForValidation.trim()) {
|
|
@@ -398,10 +746,16 @@ export const Rcs = (props) => {
|
|
|
398
746
|
tagModule: getDefaultTags,
|
|
399
747
|
isFullMode,
|
|
400
748
|
}) || {};
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
749
|
+
const unsupportedTagsLengthCheck =
|
|
750
|
+
validationResponse?.unsupportedTags?.length > 0;
|
|
751
|
+
const errorMsg =
|
|
752
|
+
(unsupportedTagsLengthCheck &&
|
|
753
|
+
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
754
|
+
unsupportedTags: validationResponse.unsupportedTags,
|
|
755
|
+
})) ||
|
|
756
|
+
(validationResponse.isBraceError &&
|
|
757
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
758
|
+
false;
|
|
405
759
|
if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
|
|
406
760
|
if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
|
|
407
761
|
};
|
|
@@ -414,55 +768,214 @@ export const Rcs = (props) => {
|
|
|
414
768
|
validateResolvedTagsForType(MESSAGE_TEXT);
|
|
415
769
|
}, [cardVarMapped, templateDesc, tags, injectedTags, loadingTags]);
|
|
416
770
|
|
|
771
|
+
useEffect(() => {
|
|
772
|
+
if (isFullMode || !isCarouselType) return;
|
|
773
|
+
if (loadingTags || !tags || tags.length === 0) return;
|
|
774
|
+
(carouselData || []).forEach((card, idx) => {
|
|
775
|
+
['title', 'description'].forEach((field) => {
|
|
776
|
+
const templateStr = card?.[field] || '';
|
|
777
|
+
if (!templateStr) return;
|
|
778
|
+
const resolved = resolveTemplateWithMap(templateStr);
|
|
779
|
+
if (!resolved) {
|
|
780
|
+
updateCarouselErrors(idx, { [field]: false });
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
let contentForValidation = resolved;
|
|
784
|
+
const placeholderTokens = templateStr.match(rcsVarRegex) || [];
|
|
785
|
+
placeholderTokens.forEach((token) => {
|
|
786
|
+
const escaped = token.replace(REGEX_SPECIAL_CHARS_ESCAPE_PATTERN, '\\$&');
|
|
787
|
+
contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
|
|
788
|
+
});
|
|
789
|
+
if (!contentForValidation.trim()) {
|
|
790
|
+
updateCarouselErrors(idx, { [field]: false });
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const validationResponse = validateTags({
|
|
794
|
+
content: contentForValidation,
|
|
795
|
+
tagsParam: tags,
|
|
796
|
+
injectedTagsParams: injectedTags,
|
|
797
|
+
location,
|
|
798
|
+
tagModule: getDefaultTags,
|
|
799
|
+
eventContextTags,
|
|
800
|
+
isFullMode,
|
|
801
|
+
}) || {};
|
|
802
|
+
const errorMsg =
|
|
803
|
+
(validationResponse?.unsupportedTags?.length > 0 &&
|
|
804
|
+
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
805
|
+
unsupportedTags: validationResponse.unsupportedTags,
|
|
806
|
+
})) ||
|
|
807
|
+
(validationResponse.isBraceError &&
|
|
808
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
809
|
+
false;
|
|
810
|
+
updateCarouselErrors(idx, { [field]: errorMsg });
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
}, [cardVarMapped, carouselData, tags, injectedTags, loadingTags, isCarouselType]);
|
|
814
|
+
|
|
417
815
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
418
816
|
|
|
419
|
-
const
|
|
817
|
+
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
818
|
+
|
|
819
|
+
/** Same `{{tag}}` in both title and description must not share one semantic map entry. */
|
|
820
|
+
const rcsSpanningSemanticVarNames = useMemo(
|
|
821
|
+
() => getRcsSemanticVarNamesSpanningTitleAndDesc(templateTitle, templateDesc, rcsVarRegex),
|
|
822
|
+
[templateTitle, templateDesc],
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
826
|
+
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
827
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
828
|
+
const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
829
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
830
|
+
let varOrdinal = 0;
|
|
831
|
+
for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
|
|
832
|
+
const segmentToken = templateSegments[segmentIndexInField];
|
|
833
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
834
|
+
if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
|
|
835
|
+
return offset + varOrdinal;
|
|
836
|
+
}
|
|
837
|
+
varOrdinal += 1;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return null;
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Master-branch resolve: uses numeric slot keys + semantic spanning detection for correct
|
|
845
|
+
* multi-field variable resolution.
|
|
846
|
+
*/
|
|
847
|
+
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
420
848
|
if (!str) return '';
|
|
421
|
-
const arr =
|
|
849
|
+
const arr = splitTemplateVarStringRcs(str);
|
|
850
|
+
let varOrdinal = 0;
|
|
422
851
|
return arr.map((elem) => {
|
|
423
852
|
if (rcsVarTestRegex.test(elem)) {
|
|
424
853
|
const key = getVarNameFromToken(elem);
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
854
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
855
|
+
varOrdinal += 1;
|
|
856
|
+
const slotValue = resolveCardVarMappedSlotValue(
|
|
857
|
+
cardVarMapped,
|
|
858
|
+
key,
|
|
859
|
+
globalSlot,
|
|
860
|
+
isEditLike,
|
|
861
|
+
rcsSpanningSemanticVarNames.has(key),
|
|
862
|
+
);
|
|
863
|
+
if (isNil(slotValue) || String(slotValue)?.trim?.() === '') return elem;
|
|
864
|
+
return String(slotValue);
|
|
428
865
|
}
|
|
429
866
|
return elem;
|
|
430
867
|
}).join('');
|
|
431
868
|
};
|
|
432
869
|
|
|
870
|
+
const buildCarouselCardsForPreview = (cards = []) =>
|
|
871
|
+
buildCarouselCardsForPreviewUtil(cards, { isFullMode, rcsVarRegex, resolveTemplateWithMap });
|
|
433
872
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (
|
|
441
|
-
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
873
|
+
/**
|
|
874
|
+
* Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
|
|
875
|
+
* (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
|
|
876
|
+
* TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
|
|
877
|
+
*/
|
|
878
|
+
const getTemplateContent = useCallback(() => {
|
|
879
|
+
if (templateType === contentType.carousel) {
|
|
880
|
+
const carouselDimKey = getCarouselDimensionKey(selectedCarouselHeight, selectedCarouselWidth);
|
|
881
|
+
const carouselImgDims =
|
|
882
|
+
RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
|
|
883
|
+
const carouselVidDims =
|
|
884
|
+
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
|
|
885
|
+
|| RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
|
|
886
|
+
return {
|
|
887
|
+
carouselData: buildCarouselCardsForPreview(carouselData),
|
|
888
|
+
carouselPreviewDimensions: {
|
|
889
|
+
imageWidth: carouselImgDims.width,
|
|
890
|
+
imageHeight: carouselImgDims.height,
|
|
891
|
+
videoThumbWidth: carouselVidDims.width,
|
|
892
|
+
videoThumbHeight: carouselVidDims.height,
|
|
893
|
+
},
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
897
|
+
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
898
|
+
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
454
899
|
|
|
900
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
901
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
902
|
+
? 0
|
|
903
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
904
|
+
const resolvedTitle = isMediaTypeText
|
|
905
|
+
? ''
|
|
906
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
907
|
+
const resolvedDesc = isSlotMappingMode
|
|
908
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
909
|
+
: templateDesc;
|
|
910
|
+
|
|
911
|
+
const mediaPreview = {};
|
|
912
|
+
if (isMediaTypeImage && rcsImageSrc) {
|
|
913
|
+
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
914
|
+
}
|
|
915
|
+
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
916
|
+
if (rcsThumbnailSrc) {
|
|
917
|
+
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
918
|
+
} else if (rcsVideoSrc?.videoSrc) {
|
|
919
|
+
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
920
|
+
}
|
|
921
|
+
if (rcsThumbnailSrc) {
|
|
922
|
+
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const contentObj = {
|
|
927
|
+
templateHeader: resolvedTitle,
|
|
928
|
+
templateMessage: resolvedDesc,
|
|
929
|
+
...mediaPreview,
|
|
930
|
+
...(suggestions.length > 0 && {
|
|
931
|
+
suggestions: suggestions,
|
|
932
|
+
}),
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
return contentObj;
|
|
936
|
+
}, [
|
|
937
|
+
templateMediaType,
|
|
938
|
+
templateTitle,
|
|
939
|
+
templateDesc,
|
|
940
|
+
rcsImageSrc,
|
|
941
|
+
rcsVideoSrc,
|
|
942
|
+
rcsThumbnailSrc,
|
|
943
|
+
suggestions,
|
|
944
|
+
selectedDimension,
|
|
945
|
+
isFullMode,
|
|
946
|
+
isEditFlow,
|
|
947
|
+
cardVarMapped,
|
|
948
|
+
rcsSpanningSemanticVarNames,
|
|
949
|
+
carouselData,
|
|
950
|
+
selectedCarouselHeight,
|
|
951
|
+
selectedCarouselWidth,
|
|
952
|
+
]);
|
|
953
|
+
|
|
954
|
+
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
455
955
|
|
|
456
|
-
const RcsLabel = styled.div`
|
|
457
|
-
display: flex;
|
|
458
|
-
margin-top: 20px;
|
|
459
|
-
`;
|
|
460
956
|
const paramObj = params || {};
|
|
461
957
|
useEffect(() => {
|
|
462
958
|
const { id } = paramObj;
|
|
463
959
|
if (id && isFullMode) {
|
|
464
960
|
setSpin(true);
|
|
465
961
|
actions.getTemplateDetails(id, setSpin);
|
|
962
|
+
} else if (!id && isFullMode) {
|
|
963
|
+
// Create New: clear standalone media and ALL possible carousel card Redux slots.
|
|
964
|
+
// Redux persists across mounts so we must clear unconditionally (carouselData may be [] on fresh mount).
|
|
965
|
+
updateRcsImageSrc('');
|
|
966
|
+
setRcsVideoSrc({});
|
|
967
|
+
setRcsThumbnailSrc('');
|
|
968
|
+
setAssetList({});
|
|
969
|
+
setEditFlow(false);
|
|
970
|
+
actions.clearRcsMediaAsset(0);
|
|
971
|
+
actions.clearRcsMediaAsset(1);
|
|
972
|
+
for (let cardIdx = 0; cardIdx < MAX_RCS_CAROUSEL_ALLOWED; cardIdx += 1) {
|
|
973
|
+
actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIdx));
|
|
974
|
+
actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIdx));
|
|
975
|
+
actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIdx));
|
|
976
|
+
}
|
|
977
|
+
setCarouselData([]);
|
|
978
|
+
setCarouselErrors([]);
|
|
466
979
|
}
|
|
467
980
|
return () => {
|
|
468
981
|
actions.clearEditResponse();
|
|
@@ -470,59 +983,68 @@ export const Rcs = (props) => {
|
|
|
470
983
|
}, [paramObj.id]);
|
|
471
984
|
|
|
472
985
|
useEffect(() => {
|
|
473
|
-
|
|
986
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
474
987
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
988
|
+
const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
|
|
989
|
+
|
|
990
|
+
const initField = (targetString, setVarMap, slotOffset) => {
|
|
991
|
+
const arr = splitTemplateVarStringRcs(targetString);
|
|
992
|
+
if (!arr?.length) {
|
|
993
|
+
setVarMap({});
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
const nextVarMap = {};
|
|
997
|
+
let varOrdinal = 0;
|
|
998
|
+
arr.forEach((elem, idx) => {
|
|
999
|
+
// Mustache tokens: {{1}}, {{user_name}}, {{tag.FORMAT_1}}, etc.
|
|
1000
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
1001
|
+
const id = `${elem}_${idx}`;
|
|
1002
|
+
const varName = getVarNameFromToken(elem);
|
|
1003
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
1004
|
+
varOrdinal += 1;
|
|
1005
|
+
const mappedValue = resolveCardVarMappedSlotValue(
|
|
1006
|
+
cardVarMapped,
|
|
1007
|
+
varName,
|
|
1008
|
+
globalSlot,
|
|
1009
|
+
isEditLike,
|
|
1010
|
+
rcsSpanningSemanticVarNames.has(varName),
|
|
1011
|
+
);
|
|
1012
|
+
nextVarMap[id] = mappedValue;
|
|
481
1013
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
486
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
487
|
-
const id = `${elem}_${idx}`;
|
|
488
|
-
const varName = getVarNameFromToken(elem);
|
|
489
|
-
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
490
|
-
nextVarMap[id] = mappedValue;
|
|
491
|
-
if (mappedValue !== '') {
|
|
492
|
-
nextUpdated[idx] = mappedValue;
|
|
493
|
-
} else {
|
|
494
|
-
nextUpdated[idx] = elem;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
setVarMap(nextVarMap);
|
|
499
|
-
setUpdated(nextUpdated);
|
|
500
|
-
};
|
|
1014
|
+
});
|
|
1015
|
+
setVarMap(nextVarMap);
|
|
1016
|
+
};
|
|
501
1017
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
1018
|
+
initField(templateTitle, setTitleVarMappedData, 0);
|
|
1019
|
+
initField(templateDesc, setDescVarMappedData, titleTokenCount);
|
|
1020
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames]);
|
|
1021
|
+
|
|
1022
|
+
useEffect(() => {
|
|
1023
|
+
if (!isEditFlow && isFullMode) {
|
|
508
1024
|
setRcsVideoSrc({});
|
|
509
1025
|
updateRcsImageSrc('');
|
|
510
|
-
|
|
511
|
-
updateRcsThumbnailSrc('');
|
|
1026
|
+
setRcsThumbnailSrc('');
|
|
512
1027
|
setAssetList({});
|
|
513
|
-
|
|
514
|
-
|
|
1028
|
+
}
|
|
1029
|
+
}, [templateMediaType]);
|
|
515
1030
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
1031
|
+
/** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
|
|
1032
|
+
const templateStatusHelper = (cardContentFirst) => {
|
|
1033
|
+
const raw =
|
|
1034
|
+
cardContentFirst?.Status
|
|
1035
|
+
?? cardContentFirst?.status
|
|
1036
|
+
?? cardContentFirst?.approvalStatus
|
|
1037
|
+
?? '';
|
|
1038
|
+
const status = typeof raw === 'string' ? raw.trim() : String(raw);
|
|
1039
|
+
const normalizedStatus = status.toLowerCase();
|
|
1040
|
+
switch (normalizedStatus) {
|
|
519
1041
|
case RCS_STATUSES.approved:
|
|
520
1042
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
521
1043
|
break;
|
|
522
1044
|
case RCS_STATUSES.pending:
|
|
523
1045
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
524
1046
|
break;
|
|
525
|
-
case RCS_STATUSES.awaitingApproval:
|
|
1047
|
+
case RCS_STATUSES.awaitingApproval.toLowerCase():
|
|
526
1048
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
527
1049
|
break;
|
|
528
1050
|
case RCS_STATUSES.unavailable:
|
|
@@ -531,6 +1053,9 @@ export const Rcs = (props) => {
|
|
|
531
1053
|
case RCS_STATUSES.rejected:
|
|
532
1054
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
533
1055
|
break;
|
|
1056
|
+
case RCS_STATUSES.created:
|
|
1057
|
+
setTemplateStatus(RCS_STATUSES.created);
|
|
1058
|
+
break;
|
|
534
1059
|
default:
|
|
535
1060
|
setTemplateStatus(status);
|
|
536
1061
|
break;
|
|
@@ -584,48 +1109,255 @@ export const Rcs = (props) => {
|
|
|
584
1109
|
};
|
|
585
1110
|
|
|
586
1111
|
useEffect(() => {
|
|
587
|
-
const details =
|
|
1112
|
+
const details = rcsHydrationDetails;
|
|
588
1113
|
if (details && Object.keys(details).length > 0) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
1114
|
+
// Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
|
|
1115
|
+
const cardFromVersions = get(
|
|
1116
|
+
details,
|
|
1117
|
+
'versions.base.content.RCS.rcsContent.cardContent[0]',
|
|
1118
|
+
);
|
|
1119
|
+
const rcsContent = get(details, 'versions.base.content.RCS.rcsContent', {});
|
|
1120
|
+
const cardType = (rcsContent?.cardType || '').toString();
|
|
1121
|
+
|
|
1122
|
+
setEditFlow(true);
|
|
1123
|
+
setTemplateName(details?.name || details?.creativeName || '');
|
|
1124
|
+
|
|
1125
|
+
const cardFromTop = get(details, 'rcsContent.cardContent[0]');
|
|
1126
|
+
const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
|
|
1127
|
+
const cardVarMappedFromCardContent =
|
|
1128
|
+
card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
|
|
1129
|
+
? card0.cardVarMapped
|
|
1130
|
+
: {};
|
|
1131
|
+
const cardVarMappedFromRootMirror =
|
|
1132
|
+
details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
|
|
1133
|
+
? details.rcsCardVarMapped
|
|
1134
|
+
: {};
|
|
1135
|
+
// Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
|
|
1136
|
+
// nested versions.cardContent[0].cardVarMapped is dropped on reload.
|
|
1137
|
+
const mergedCardVarMappedFromPayload = {
|
|
1138
|
+
...cardVarMappedFromRootMirror,
|
|
1139
|
+
...cardVarMappedFromCardContent,
|
|
1140
|
+
};
|
|
1141
|
+
const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
|
|
1142
|
+
const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
|
|
1143
|
+
const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
|
|
1144
|
+
Object.keys(mergedCardVarMappedFromPayload)
|
|
1145
|
+
.sort()
|
|
1146
|
+
.reduce((accumulator, mapKey) => {
|
|
1147
|
+
accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
|
|
1148
|
+
return accumulator;
|
|
1149
|
+
}, {}),
|
|
1150
|
+
)}`;
|
|
1151
|
+
if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
|
|
1152
|
+
lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
|
|
1153
|
+
setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
|
|
1154
|
+
}
|
|
1155
|
+
const tokenListForMap = [
|
|
1156
|
+
...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
|
|
1157
|
+
...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
|
|
1158
|
+
];
|
|
1159
|
+
const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
1160
|
+
// Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
|
|
1161
|
+
// getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
|
|
1162
|
+
const cardVarMappedBeforeCoalesce = isFullMode
|
|
1163
|
+
? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
|
|
1164
|
+
: { ...mergedCardVarMappedFromPayload };
|
|
1165
|
+
const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
1166
|
+
cardVarMappedBeforeCoalesce,
|
|
1167
|
+
loadedTitleForMap,
|
|
1168
|
+
loadedDescForMap,
|
|
1169
|
+
rcsVarRegex,
|
|
1170
|
+
);
|
|
1171
|
+
const cardVarMappedAfterNumericSlotSync = !isFullMode
|
|
1172
|
+
? syncCardVarMappedSemanticsFromSlots(
|
|
1173
|
+
cardVarMappedAfterCoalesce,
|
|
1174
|
+
loadedTitleForMap,
|
|
1175
|
+
loadedDescForMap,
|
|
1176
|
+
rcsVarRegex,
|
|
1177
|
+
)
|
|
1178
|
+
: cardVarMappedAfterCoalesce;
|
|
1179
|
+
const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
|
|
1180
|
+
// Pre-populate variable/tag mappings while opening an existing template in edit flows
|
|
1181
|
+
setCardVarMapped((previousVarMapState) => {
|
|
1182
|
+
const previousVarMap = previousVarMapState ?? {};
|
|
1183
|
+
if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
|
|
1184
|
+
const previousVarMapKeys = Object.keys(previousVarMap);
|
|
1185
|
+
const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
|
|
1186
|
+
if (previousVarMapKeys.length === nextVarMapKeys.length) {
|
|
1187
|
+
const allSlotValuesMatchPrevious = previousVarMapKeys.every(
|
|
1188
|
+
(key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
|
|
1189
|
+
);
|
|
1190
|
+
if (allSlotValuesMatchPrevious) return previousVarMapState;
|
|
1191
|
+
}
|
|
1192
|
+
return hydratedCardVarMappedResult;
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
if (cardType.toUpperCase() === contentType.carousel) {
|
|
1196
|
+
|
|
1197
|
+
setTemplateType(contentType.carousel);
|
|
1198
|
+
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1199
|
+
// Library/campaign payloads can store cardSettings/cardContent at the flat top-level
|
|
1200
|
+
// `rcsContent` instead of nested under versions.base.content.RCS (same fallback already
|
|
1201
|
+
// used for the rich_card media lookup below, line ~1793). Without it, cardWidth/height
|
|
1202
|
+
// silently fall back to SMALL/MEDIUM, mismatching the dimensions the thumbnail was
|
|
1203
|
+
// actually uploaded at and causing object-fit:cover to crop the preview image.
|
|
1204
|
+
const cardSettings = rcsContent?.cardSettings
|
|
1205
|
+
|| get(details, 'rcsContent.cardSettings', {});
|
|
1206
|
+
const cardWidth = cardSettings?.cardWidth || SMALL;
|
|
1207
|
+
setSelectedCarouselWidth(cardWidth);
|
|
1208
|
+
|
|
1209
|
+
const cards = Array.isArray(rcsContent?.cardContent)
|
|
1210
|
+
? rcsContent.cardContent
|
|
1211
|
+
: get(details, 'rcsContent.cardContent', []);
|
|
1212
|
+
const firstHeight = cards?.[0]?.media?.height || MEDIUM;
|
|
1213
|
+
setSelectedCarouselHeight(firstHeight);
|
|
1214
|
+
setSelectedCarousel(`${firstHeight}_${cardWidth}`);
|
|
1215
|
+
setActiveCarouselIndex('0');
|
|
1216
|
+
|
|
1217
|
+
if (cards.length > 1) {
|
|
1218
|
+
setCardVarMapped((previousVarMap) => cards.reduce((merged, c) => {
|
|
1219
|
+
const cardMap = c?.cardVarMapped;
|
|
1220
|
+
return cardMap != null && typeof cardMap === 'object' ? { ...merged, ...cardMap } : merged;
|
|
1221
|
+
}, { ...(previousVarMap || {}) }));
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const hydratedCards = cards.map((c = {}, idx) => {
|
|
1225
|
+
const mediaType = c.mediaType;
|
|
1226
|
+
const media = c.media || {};
|
|
1227
|
+
const mediaUrl = media.mediaUrl || '';
|
|
1228
|
+
const thumbUrl = media.thumbnailUrl || '';
|
|
1229
|
+
const rawSuggestions = Array.isArray(c.suggestions) ? c.suggestions : [];
|
|
1230
|
+
const suggestions = idx === 0 && rawSuggestions.length === 0
|
|
1231
|
+
? cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS)
|
|
1232
|
+
: rawSuggestions;
|
|
1233
|
+
return {
|
|
1234
|
+
title: c.title || '',
|
|
1235
|
+
description: c.description || '',
|
|
1236
|
+
mediaType,
|
|
1237
|
+
imageSrc: mediaType === RCS_MEDIA_TYPES.IMAGE ? mediaUrl : '',
|
|
1238
|
+
videoAsset: mediaType === RCS_MEDIA_TYPES.VIDEO ? {
|
|
1239
|
+
videoSrc: mediaUrl,
|
|
1240
|
+
previewUrl: thumbUrl,
|
|
1241
|
+
videoThumbnail: thumbUrl,
|
|
1242
|
+
videoName: c?.media?.videoName || '',
|
|
1243
|
+
} : {},
|
|
1244
|
+
thumbnailSrc: mediaType === RCS_MEDIA_TYPES.VIDEO ? thumbUrl : '',
|
|
1245
|
+
suggestions,
|
|
1246
|
+
};
|
|
1247
|
+
});
|
|
1248
|
+
setCarouselData(
|
|
1249
|
+
hydratedCards.length > 0
|
|
1250
|
+
? ensureFirstCardDefaultPhoneSuggestions(hydratedCards)
|
|
1251
|
+
: [cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)],
|
|
1252
|
+
);
|
|
1253
|
+
setCarouselErrors(new Array(hydratedCards.length > 0 ? hydratedCards.length : 1).fill({}));
|
|
1254
|
+
|
|
1255
|
+
// Status bar uses first card's Status, keep existing behavior.
|
|
1256
|
+
if (isHostInfoBip) {
|
|
1257
|
+
setTemplateStatus('');
|
|
1258
|
+
} else {
|
|
1259
|
+
const firstCard = cards?.[0] || {};
|
|
1260
|
+
const cardForCarouselStatus = {
|
|
1261
|
+
...firstCard,
|
|
1262
|
+
Status:
|
|
1263
|
+
firstCard.Status
|
|
1264
|
+
?? firstCard.status
|
|
1265
|
+
?? firstCard.approvalStatus
|
|
1266
|
+
?? get(details, 'templateStatus')
|
|
1267
|
+
?? get(details, 'approvalStatus')
|
|
1268
|
+
?? get(details, 'creativeStatus')
|
|
1269
|
+
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
1270
|
+
?? '',
|
|
1271
|
+
};
|
|
1272
|
+
templateStatusHelper(cardForCarouselStatus);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// Hydrate SMS fallback for carousel — same logic as for non-carousel types below.
|
|
1276
|
+
// Without this the early `return` skips fallback hydration entirely, so reopening
|
|
1277
|
+
// a carousel template never restores the saved SMS fallback data.
|
|
1278
|
+
applySmsFallbackHydration({
|
|
1279
|
+
details,
|
|
1280
|
+
isFullMode,
|
|
1281
|
+
lastSmsFallbackHydrationKeyRef,
|
|
1282
|
+
setSmsFallbackData,
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
return;
|
|
592
1286
|
}
|
|
593
|
-
|
|
594
|
-
|
|
1287
|
+
|
|
1288
|
+
const mediaType =
|
|
1289
|
+
card0.mediaType
|
|
1290
|
+
|| get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
1291
|
+
if (cardType.toUpperCase() !== contentType.carousel && mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
595
1292
|
setTemplateType(contentType.text_message);
|
|
596
|
-
} else {
|
|
1293
|
+
} else if (cardType.toUpperCase() !== contentType.carousel && mediaType !== RCS_MEDIA_TYPES.NONE) {
|
|
597
1294
|
setTemplateType(contentType.rich_card);
|
|
598
1295
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
1296
|
+
|
|
1297
|
+
const loadedTitle = loadedTitleForMap;
|
|
1298
|
+
const loadedDesc = loadedDescForMap;
|
|
1299
|
+
const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
|
|
1300
|
+
loadedTitle,
|
|
1301
|
+
loadedDesc,
|
|
1302
|
+
isFullMode,
|
|
1303
|
+
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
1304
|
+
rcsVarRegex,
|
|
1305
|
+
});
|
|
606
1306
|
setTemplateTitle(normalizedTitle);
|
|
607
1307
|
setTemplateDesc(normalizedDesc);
|
|
608
|
-
setSuggestions(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1308
|
+
setSuggestions(
|
|
1309
|
+
Array.isArray(card0.suggestions)
|
|
1310
|
+
? card0.suggestions
|
|
1311
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
|
|
1312
|
+
);
|
|
1313
|
+
const cardForStatus = {
|
|
1314
|
+
...card0,
|
|
1315
|
+
Status:
|
|
1316
|
+
card0.Status
|
|
1317
|
+
?? card0.status
|
|
1318
|
+
?? card0.approvalStatus
|
|
1319
|
+
?? get(details, 'templateStatus')
|
|
1320
|
+
?? get(details, 'approvalStatus')
|
|
1321
|
+
?? get(details, 'creativeStatus')
|
|
1322
|
+
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
1323
|
+
?? '',
|
|
1324
|
+
};
|
|
1325
|
+
if (isHostInfoBip) {
|
|
1326
|
+
setTemplateStatus('');
|
|
1327
|
+
} else {
|
|
1328
|
+
templateStatusHelper(cardForStatus);
|
|
1329
|
+
}
|
|
1330
|
+
const mediaData =
|
|
1331
|
+
card0.media != null && card0.media !== ''
|
|
1332
|
+
? card0.media
|
|
1333
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
1334
|
+
const cardSettings =
|
|
1335
|
+
get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
|
|
1336
|
+
|| get(details, 'rcsContent.cardSettings', '');
|
|
612
1337
|
setMediaData(mediaData, mediaType, cardSettings);
|
|
613
1338
|
if (details?.edit) {
|
|
614
1339
|
const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
|
|
615
1340
|
setRcsAccount(rcsAccountId);
|
|
616
1341
|
}
|
|
1342
|
+
|
|
1343
|
+
applySmsFallbackHydration({
|
|
1344
|
+
details,
|
|
1345
|
+
isFullMode,
|
|
1346
|
+
lastSmsFallbackHydrationKeyRef,
|
|
1347
|
+
setSmsFallbackData,
|
|
1348
|
+
});
|
|
617
1349
|
}
|
|
618
|
-
}, [
|
|
619
|
-
|
|
1350
|
+
}, [rcsHydrationDetails, isFullMode, isHostInfoBip]);
|
|
620
1351
|
|
|
621
1352
|
useEffect(() => {
|
|
622
1353
|
if (templateType === contentType.text_message) {
|
|
623
1354
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
624
|
-
|
|
625
|
-
|
|
1355
|
+
// Full-mode create only: switching to plain text clears draft title/media. Never clear when
|
|
1356
|
+
// hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
|
|
626
1357
|
if (!isEditFlow && isFullMode) {
|
|
1358
|
+
setTemplateTitle('');
|
|
1359
|
+
setTemplateTitleError('');
|
|
627
1360
|
setUpdateRcsImageSrc('');
|
|
628
|
-
setUpdateRcsVideoSrc({});
|
|
629
1361
|
setRcsVideoSrc({});
|
|
630
1362
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
631
1363
|
}
|
|
@@ -649,23 +1381,22 @@ export const Rcs = (props) => {
|
|
|
649
1381
|
}, [templateType, isEditFlow, isFullMode]);
|
|
650
1382
|
|
|
651
1383
|
useEffect(() => {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
globalActions.fetchSchemaForEntity(query);
|
|
1384
|
+
const { type, module } = location.query || {};
|
|
1385
|
+
const isEmbedded = type === EMBEDDED;
|
|
1386
|
+
// Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
|
|
1387
|
+
const context = isEmbedded ? module : 'outbound';
|
|
1388
|
+
const embedded = isEmbedded ? type : FULL;
|
|
1389
|
+
const query = {
|
|
1390
|
+
layout: SMS,
|
|
1391
|
+
type: TAG,
|
|
1392
|
+
context,
|
|
1393
|
+
embedded,
|
|
1394
|
+
};
|
|
1395
|
+
if (getDefaultTags) {
|
|
1396
|
+
query.context = getDefaultTags;
|
|
667
1397
|
}
|
|
668
|
-
|
|
1398
|
+
fetchTagSchemaIfNewQuery(query);
|
|
1399
|
+
}, [fetchTagSchemaIfNewQuery]);
|
|
669
1400
|
|
|
670
1401
|
useEffect(() => {
|
|
671
1402
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -688,16 +1419,128 @@ export const Rcs = (props) => {
|
|
|
688
1419
|
context,
|
|
689
1420
|
embedded,
|
|
690
1421
|
};
|
|
1422
|
+
if (getDefaultTags) {
|
|
1423
|
+
query.context = getDefaultTags;
|
|
1424
|
+
}
|
|
1425
|
+
fetchTagSchemaIfNewQuery(query);
|
|
691
1426
|
globalActions.fetchSchemaForEntity(query);
|
|
692
1427
|
};
|
|
693
1428
|
|
|
694
|
-
const
|
|
695
|
-
if (!
|
|
696
|
-
const
|
|
1429
|
+
const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
|
|
1430
|
+
if (!templateStr || !numericVarName || !tagName) return templateStr;
|
|
1431
|
+
const placeholderRegex = buildRcsNumericMustachePlaceholderRegex(numericVarName);
|
|
1432
|
+
return templateStr.replace(placeholderRegex, `{{${tagName}}}`);
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
|
|
1436
|
+
if (!varSegmentCompositeDomId) return;
|
|
1437
|
+
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
1438
|
+
if (underscoreIndexInCompositeId === -1) return;
|
|
1439
|
+
const segmentIndexSuffix = varSegmentCompositeDomId.slice(underscoreIndexInCompositeId + 1);
|
|
1440
|
+
if (segmentIndexSuffix === '' || isNaN(Number(segmentIndexSuffix))) return;
|
|
1441
|
+
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
1442
|
+
const semanticOrNumericVarName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
1443
|
+
if (!semanticOrNumericVarName) return;
|
|
1444
|
+
const isNumericPlaceholderSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(semanticOrNumericVarName));
|
|
1445
|
+
const templateStringForField =
|
|
1446
|
+
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
1447
|
+
const titleOrMessageFieldType =
|
|
1448
|
+
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
1449
|
+
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
1450
|
+
varSegmentCompositeDomId,
|
|
1451
|
+
templateStringForField,
|
|
1452
|
+
titleOrMessageFieldType,
|
|
1453
|
+
);
|
|
1454
|
+
const cardVarMappedNumericSlotKey =
|
|
1455
|
+
globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined
|
|
1456
|
+
? String(globalVarSlotIndexZeroBased + 1)
|
|
1457
|
+
: null;
|
|
1458
|
+
|
|
1459
|
+
setCardVarMapped((previousCardVarMapped) => {
|
|
1460
|
+
const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
|
|
1461
|
+
if (isNumericPlaceholderSlot) {
|
|
1462
|
+
const existingValueBeforeAppend = (
|
|
1463
|
+
previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
|
|
1464
|
+
).toString();
|
|
1465
|
+
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
1466
|
+
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
1467
|
+
updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
|
|
1468
|
+
} else {
|
|
1469
|
+
// Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
|
|
1470
|
+
// "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
|
|
1471
|
+
// read/write the global numeric slot only and drop the shared semantic key.
|
|
1472
|
+
const existingValueBeforeAppend = cardVarMappedNumericSlotKey
|
|
1473
|
+
? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
|
|
1474
|
+
: String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
|
|
1475
|
+
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
1476
|
+
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
1477
|
+
if (cardVarMappedNumericSlotKey) {
|
|
1478
|
+
updatedCardVarMapped[cardVarMappedNumericSlotKey] = mappedValueAfterAppendingTag;
|
|
1479
|
+
// Sync all other intra-field occurrences of the same variable (cross-field slots stay independent)
|
|
1480
|
+
const fieldOffset = titleOrMessageFieldType === TITLE_TEXT ? 0 : (templateTitle?.match(rcsVarRegex) ?? []).length;
|
|
1481
|
+
const fieldSegments = splitTemplateVarStringRcs(templateStringForField ?? '');
|
|
1482
|
+
let varOrdinal = 0;
|
|
1483
|
+
for (let i = 0; i < fieldSegments.length; i++) {
|
|
1484
|
+
if (rcsVarTestRegex.test(fieldSegments[i])) {
|
|
1485
|
+
const slotVarName = getVarNameFromToken(fieldSegments[i]);
|
|
1486
|
+
const globalSlot = fieldOffset + varOrdinal;
|
|
1487
|
+
varOrdinal += 1;
|
|
1488
|
+
if (slotVarName === semanticOrNumericVarName && globalSlot !== globalVarSlotIndexZeroBased) {
|
|
1489
|
+
updatedCardVarMapped[String(globalSlot + 1)] = mappedValueAfterAppendingTag;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
} else {
|
|
1494
|
+
updatedCardVarMapped[semanticOrNumericVarName] = mappedValueAfterAppendingTag;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return updatedCardVarMapped;
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
if (
|
|
1501
|
+
isNumericPlaceholderSlot
|
|
1502
|
+
&& (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
|
|
1503
|
+
) {
|
|
1504
|
+
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
|
|
1505
|
+
setTemplateTitle((previousTitle) => {
|
|
1506
|
+
const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
1507
|
+
previousTitle || '',
|
|
1508
|
+
semanticOrNumericVarName,
|
|
1509
|
+
selectedTagNameFromPicker,
|
|
1510
|
+
);
|
|
1511
|
+
if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
|
|
1512
|
+
setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
|
|
1513
|
+
// Remount segment editor: tag insert replaces {{n}} with e.g. {{tag.FORMAT_1}} — slot ids change; avoids stale UI vs manual typing in full-mode TextArea
|
|
1514
|
+
setRcsVarSegmentEditorRemountKey((remountKey) => remountKey + 1);
|
|
1515
|
+
return titleAfterReplacingNumericPlaceholder;
|
|
1516
|
+
});
|
|
1517
|
+
} else {
|
|
1518
|
+
setTemplateDesc((previousDescription) => {
|
|
1519
|
+
const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
1520
|
+
previousDescription || '',
|
|
1521
|
+
semanticOrNumericVarName,
|
|
1522
|
+
selectedTagNameFromPicker,
|
|
1523
|
+
);
|
|
1524
|
+
if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
|
|
1525
|
+
return previousDescription;
|
|
1526
|
+
}
|
|
1527
|
+
setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
|
|
1528
|
+
setRcsVarSegmentEditorRemountKey((remountKey) => remountKey + 1);
|
|
1529
|
+
return descriptionAfterReplacingNumericPlaceholder;
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
|
|
1536
|
+
|
|
1537
|
+
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
1538
|
+
|
|
1539
|
+
const onCarouselTagSelect = (data) => {
|
|
1540
|
+
if (!carouselFocusedVarId) return;
|
|
1541
|
+
const sep = carouselFocusedVarId.lastIndexOf('_');
|
|
697
1542
|
if (sep === -1) return;
|
|
698
|
-
const
|
|
699
|
-
if (isNaN(numId)) return;
|
|
700
|
-
const token = areaId.slice(0, sep);
|
|
1543
|
+
const token = carouselFocusedVarId.slice(0, sep);
|
|
701
1544
|
const variableName = getVarNameFromToken(token);
|
|
702
1545
|
if (!variableName) return;
|
|
703
1546
|
setCardVarMapped((prev) => {
|
|
@@ -710,18 +1553,6 @@ export const Rcs = (props) => {
|
|
|
710
1553
|
});
|
|
711
1554
|
};
|
|
712
1555
|
|
|
713
|
-
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
714
|
-
|
|
715
|
-
const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
|
|
716
|
-
|
|
717
|
-
const onTagSelectFallback = (data) => {
|
|
718
|
-
const tempMsg = `${fallbackMessage}{{${data}}}`;
|
|
719
|
-
const error = fallbackMessageErrorHandler(tempMsg);
|
|
720
|
-
setFallbackMessage(tempMsg);
|
|
721
|
-
setFallbackMessageError(error);
|
|
722
|
-
};
|
|
723
|
-
|
|
724
|
-
|
|
725
1556
|
//removing optout tag for rcs
|
|
726
1557
|
const getRcsTags = () => {
|
|
727
1558
|
const tempTags = cloneDeep(tags);
|
|
@@ -733,15 +1564,14 @@ export const Rcs = (props) => {
|
|
|
733
1564
|
};
|
|
734
1565
|
// tag Code end
|
|
735
1566
|
|
|
736
|
-
const renderLabel = (value,
|
|
737
|
-
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
1567
|
+
const renderLabel = (value, desc) => {
|
|
738
1568
|
return (
|
|
739
1569
|
<>
|
|
740
|
-
<
|
|
1570
|
+
<div className="rcs-form-section-heading">
|
|
741
1571
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
742
|
-
</
|
|
1572
|
+
</div>
|
|
743
1573
|
{desc && (
|
|
744
|
-
<CapLabel type="label3"
|
|
1574
|
+
<CapLabel type="label3" className="rcs-form-field-caption">
|
|
745
1575
|
{formatMessage(messages[desc])}
|
|
746
1576
|
</CapLabel>
|
|
747
1577
|
)}
|
|
@@ -760,13 +1590,8 @@ export const Rcs = (props) => {
|
|
|
760
1590
|
},
|
|
761
1591
|
{
|
|
762
1592
|
value: contentType.carousel,
|
|
763
|
-
label: (
|
|
764
|
-
|
|
765
|
-
{formatMessage(messages.carousel)}
|
|
766
|
-
</CapTooltip>
|
|
767
|
-
),
|
|
768
|
-
disabled: true,
|
|
769
|
-
},
|
|
1593
|
+
label: formatMessage(messages.carousel),
|
|
1594
|
+
}
|
|
770
1595
|
];
|
|
771
1596
|
|
|
772
1597
|
const onTemplateNameChange = ({ target: { value } }) => {
|
|
@@ -777,6 +1602,10 @@ export const Rcs = (props) => {
|
|
|
777
1602
|
|
|
778
1603
|
const onTemplateTypeChange = ({ target: { value } }) => {
|
|
779
1604
|
setTemplateType(value);
|
|
1605
|
+
// Carousel has per-card media; keep template-level media type neutral.
|
|
1606
|
+
if (value === contentType.carousel) {
|
|
1607
|
+
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1608
|
+
}
|
|
780
1609
|
};
|
|
781
1610
|
|
|
782
1611
|
|
|
@@ -797,8 +1626,39 @@ export const Rcs = (props) => {
|
|
|
797
1626
|
const onTemplateMediaTypeChange = ({ target: { value } }) => {
|
|
798
1627
|
setTemplateMediaType(value);
|
|
799
1628
|
};
|
|
800
|
-
|
|
801
|
-
|
|
1629
|
+
const renderedRCSEditMessage = (descArray, type) => {
|
|
1630
|
+
const renderArray = [];
|
|
1631
|
+
if (descArray?.length) {
|
|
1632
|
+
descArray.forEach((elem, index) => {
|
|
1633
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
1634
|
+
// Variable input
|
|
1635
|
+
renderArray.push(
|
|
1636
|
+
<TextArea
|
|
1637
|
+
id={`${elem}_${index}`}
|
|
1638
|
+
key={`${elem}_${index}`}
|
|
1639
|
+
placeholder={`enter the value for ${elem}`}
|
|
1640
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1641
|
+
onChange={e => textAreaValueChange(e, type)}
|
|
1642
|
+
value={textAreaValue(index, type)}
|
|
1643
|
+
onFocus={(e) => setTextAreaId(e, type)}
|
|
1644
|
+
/>
|
|
1645
|
+
);
|
|
1646
|
+
} else if (elem) {
|
|
1647
|
+
// Static text
|
|
1648
|
+
renderArray.push(
|
|
1649
|
+
<TextArea
|
|
1650
|
+
key={`static_${index}`}
|
|
1651
|
+
value={elem}
|
|
1652
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1653
|
+
disabled
|
|
1654
|
+
className="rcs-edit-template-message-static-textarea"
|
|
1655
|
+
/>
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
return renderArray;
|
|
1661
|
+
};
|
|
802
1662
|
const onTemplateTitleChange = ({ target: { value } }) => {
|
|
803
1663
|
let errorMessage = false;
|
|
804
1664
|
if (templateType === contentType.rich_card && !value.trim()) {
|
|
@@ -814,7 +1674,7 @@ export const Rcs = (props) => {
|
|
|
814
1674
|
|
|
815
1675
|
const onTemplateDescChange = ({ target: { value } }) => {
|
|
816
1676
|
let errorMessage = false;
|
|
817
|
-
if(templateType === contentType.text_message && value?.length > RCS_TEXT_MESSAGE_MAX_LENGTH){
|
|
1677
|
+
if(templateType === contentType.text_message && value?.length > (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)){
|
|
818
1678
|
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
819
1679
|
} else if(templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH){
|
|
820
1680
|
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
@@ -830,16 +1690,16 @@ export const Rcs = (props) => {
|
|
|
830
1690
|
|
|
831
1691
|
const templateDescErrorHandler = (value) => {
|
|
832
1692
|
let errorMessage = false;
|
|
833
|
-
const { isBraceError } = validateTags({
|
|
1693
|
+
const { unsupportedTags, isBraceError } = validateTags({
|
|
834
1694
|
content: value,
|
|
835
1695
|
tagsParam: tags,
|
|
1696
|
+
injectedTagsParams: injectedTags,
|
|
836
1697
|
location,
|
|
837
1698
|
tagModule: getDefaultTags,
|
|
838
|
-
isFullMode,
|
|
839
1699
|
}) || {};
|
|
840
1700
|
|
|
841
1701
|
const maxLength = templateType === contentType.text_message
|
|
842
|
-
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
1702
|
+
? (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)
|
|
843
1703
|
: RCS_RICH_CARD_MAX_LENGTH;
|
|
844
1704
|
|
|
845
1705
|
if (value === '' && isMediaTypeText) {
|
|
@@ -855,19 +1715,6 @@ export const Rcs = (props) => {
|
|
|
855
1715
|
};
|
|
856
1716
|
|
|
857
1717
|
|
|
858
|
-
const onFallbackMessageChange = ({ target: { value } }) => {
|
|
859
|
-
const error = fallbackMessageErrorHandler(value);
|
|
860
|
-
setFallbackMessage(value);
|
|
861
|
-
setFallbackMessageError(error);
|
|
862
|
-
};
|
|
863
|
-
|
|
864
|
-
const fallbackMessageErrorHandler = (value) => {
|
|
865
|
-
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
866
|
-
return formatMessage(messages.fallbackMsgLenError);
|
|
867
|
-
}
|
|
868
|
-
return false;
|
|
869
|
-
};
|
|
870
|
-
|
|
871
1718
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
872
1719
|
const forbiddenCharactersValidation = (value) => {
|
|
873
1720
|
if (!value) return false;
|
|
@@ -912,53 +1759,43 @@ export const Rcs = (props) => {
|
|
|
912
1759
|
if(!isFullMode){
|
|
913
1760
|
return false;
|
|
914
1761
|
}
|
|
915
|
-
|
|
1762
|
+
// Allow Liquid-style param names: letters, digits, underscore, dots (e.g. dynamic_expiry_date_after_3_days.FORMAT_1)
|
|
1763
|
+
if (!/^[\w.]+$/.test(paramName)) {
|
|
916
1764
|
return formatMessage(messages.unknownCharactersError);
|
|
917
1765
|
}
|
|
918
1766
|
}
|
|
919
1767
|
return false;
|
|
920
1768
|
};
|
|
921
|
-
|
|
922
|
-
const templateHeaderErrorHandler = (value) => {
|
|
923
|
-
let errorMessage = false;
|
|
924
|
-
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
925
|
-
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
926
|
-
} else {
|
|
927
|
-
errorMessage = variableErrorHandling(value);
|
|
928
|
-
}
|
|
929
|
-
return errorMessage;
|
|
930
|
-
};
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const templateMessageErrorHandler = (value) => {
|
|
934
|
-
let errorMessage = false;
|
|
935
|
-
if (value === '') {
|
|
936
|
-
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
937
|
-
} else if (
|
|
938
|
-
value?.length
|
|
939
|
-
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
940
|
-
) {
|
|
941
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
942
|
-
} else {
|
|
943
|
-
errorMessage = variableErrorHandling(value);
|
|
944
|
-
}
|
|
945
|
-
return errorMessage;
|
|
946
|
-
};
|
|
947
|
-
|
|
948
1769
|
|
|
949
1770
|
const onMessageAddVar = () => {
|
|
950
|
-
onAddVar(
|
|
1771
|
+
onAddVar(templateDesc);
|
|
951
1772
|
};
|
|
952
1773
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1774
|
+
/**
|
|
1775
|
+
* Returns the smallest positive integer not already used as a `{{N}}` variable
|
|
1776
|
+
* in either the title or description fields, or null if the limit (19) is reached.
|
|
1777
|
+
* Scans both fields so title and description vars never share the same number
|
|
1778
|
+
* (duplicate numbers would share a cardVarMapped key and bleed values across fields).
|
|
1779
|
+
*/
|
|
1780
|
+
const getNextRcsNumericVarNumber = (titleStr, descStr) => {
|
|
1781
|
+
const allExistingVars = [
|
|
1782
|
+
...(titleStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
|
|
1783
|
+
...(descStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
|
|
1784
|
+
];
|
|
1785
|
+
const existingNumbers = allExistingVars.flatMap(varToken => {
|
|
1786
|
+
const numericMatch = varToken.match(/\d+/);
|
|
1787
|
+
return numericMatch ? [parseInt(numericMatch[0], 10)] : [];
|
|
1788
|
+
});
|
|
957
1789
|
let nextNumber = 1;
|
|
958
1790
|
while (existingNumbers.includes(nextNumber)) {
|
|
959
1791
|
nextNumber++;
|
|
960
1792
|
}
|
|
961
|
-
|
|
1793
|
+
return nextNumber > 19 ? null : nextNumber;
|
|
1794
|
+
};
|
|
1795
|
+
|
|
1796
|
+
const onAddVar = (messageContent) => {
|
|
1797
|
+
const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
|
|
1798
|
+
if (nextNumber === null) {
|
|
962
1799
|
return;
|
|
963
1800
|
}
|
|
964
1801
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -970,15 +1807,13 @@ const onAddVar = (type, messageContent, regex) => {
|
|
|
970
1807
|
};
|
|
971
1808
|
|
|
972
1809
|
const onTitleAddVar = () => {
|
|
973
|
-
//
|
|
1810
|
+
// Scan both title AND description so the new title var number doesn't
|
|
1811
|
+
// duplicate a number already used in the description. Duplicate numeric
|
|
1812
|
+
// names would share the same cardVarMapped semantic key, causing the
|
|
1813
|
+
// description slot to reflect the title slot value and vice-versa.
|
|
974
1814
|
const messageContent = templateTitle;
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
let nextNumber = 1;
|
|
978
|
-
while (existingNumbers.includes(nextNumber)) {
|
|
979
|
-
nextNumber++;
|
|
980
|
-
}
|
|
981
|
-
if (nextNumber > 19) {
|
|
1815
|
+
const nextNumber = getNextRcsNumericVarNumber(templateTitle, templateDesc);
|
|
1816
|
+
if (nextNumber === null) {
|
|
982
1817
|
return;
|
|
983
1818
|
}
|
|
984
1819
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -990,26 +1825,17 @@ const onTitleAddVar = () => {
|
|
|
990
1825
|
setTemplateTitleError(error);
|
|
991
1826
|
};
|
|
992
1827
|
|
|
993
|
-
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
1005
|
-
validVarArr?.shift();
|
|
1006
|
-
} else {
|
|
1007
|
-
templateVarArray.push(content);
|
|
1008
|
-
break;
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
return templateVarArray.filter(Boolean);
|
|
1012
|
-
};
|
|
1828
|
+
const appendVarToCarouselField = (cardIndex, fieldName) => {
|
|
1829
|
+
const token = getNextCarouselVarToken(carouselData);
|
|
1830
|
+
if (!token) return;
|
|
1831
|
+
setCarouselData((prev = []) => {
|
|
1832
|
+
const updated = cloneDeep(prev);
|
|
1833
|
+
if (!updated[cardIndex]) return prev;
|
|
1834
|
+
const current = (updated[cardIndex][fieldName] || '').toString();
|
|
1835
|
+
updated[cardIndex][fieldName] = `${current}${token}`;
|
|
1836
|
+
return updated;
|
|
1837
|
+
});
|
|
1838
|
+
};
|
|
1013
1839
|
|
|
1014
1840
|
const textAreaValue = (idValue, type) => {
|
|
1015
1841
|
if (idValue >= 0) {
|
|
@@ -1025,6 +1851,46 @@ const splitTemplateVarString = (str) => {
|
|
|
1025
1851
|
return "";
|
|
1026
1852
|
};
|
|
1027
1853
|
|
|
1854
|
+
// Carousel: render variable-value editor for a given template string (title/description).
|
|
1855
|
+
// This matches rich-card/text edit behavior: static pieces are read-only, variable tokens are editable.
|
|
1856
|
+
const renderCarouselEditMessage = (templateStr) => {
|
|
1857
|
+
const renderArray = [];
|
|
1858
|
+
const templateArr = splitTemplateVarString(templateStr);
|
|
1859
|
+
if (templateArr?.length) {
|
|
1860
|
+
templateArr.forEach((elem, index) => {
|
|
1861
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
1862
|
+
const varName = getVarNameFromToken(elem);
|
|
1863
|
+
renderArray.push(
|
|
1864
|
+
<div key={`${elem}_${index}`} className="var-segment-message-editor__var-slot">
|
|
1865
|
+
<TextArea
|
|
1866
|
+
id={`${elem}_${index}`}
|
|
1867
|
+
placeholder={`enter the value for ${elem}`}
|
|
1868
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1869
|
+
onChange={(e) => textAreaValueChange(e, TITLE_TEXT)}
|
|
1870
|
+
value={varName ? ((cardVarMapped?.[varName] ?? '').toString()) : ''}
|
|
1871
|
+
onFocus={(e) => {
|
|
1872
|
+
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1873
|
+
setCarouselFocusedVarId(id);
|
|
1874
|
+
}}
|
|
1875
|
+
/>
|
|
1876
|
+
</div>
|
|
1877
|
+
);
|
|
1878
|
+
} else if (elem) {
|
|
1879
|
+
renderArray.push(
|
|
1880
|
+
<CapHeading
|
|
1881
|
+
key={`static_${index}`}
|
|
1882
|
+
type="h4"
|
|
1883
|
+
className="rcs-edit-template-message-split"
|
|
1884
|
+
>
|
|
1885
|
+
{elem}
|
|
1886
|
+
</CapHeading>
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1028
1894
|
const textAreaValueChange = (e, type) => {
|
|
1029
1895
|
const value = e?.target?.value ?? '';
|
|
1030
1896
|
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
@@ -1044,7 +1910,9 @@ const splitTemplateVarString = (str) => {
|
|
|
1044
1910
|
};
|
|
1045
1911
|
|
|
1046
1912
|
const setTextAreaId = (e, type) => {
|
|
1047
|
-
|
|
1913
|
+
// VarSegmentMessageEditor calls onFocus(id) with a plain string; DOM events
|
|
1914
|
+
// have an `.target.id` shape. Support both.
|
|
1915
|
+
const id = typeof e === 'string' ? e : (e?.target?.id || e?.currentTarget?.id || '');
|
|
1048
1916
|
if (!id) return;
|
|
1049
1917
|
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
1050
1918
|
else setDescTextAreaId(id);
|
|
@@ -1063,10 +1931,12 @@ const splitTemplateVarString = (str) => {
|
|
|
1063
1931
|
</CapRow>
|
|
1064
1932
|
}
|
|
1065
1933
|
description={
|
|
1066
|
-
|
|
1934
|
+
!isEditFlow && (
|
|
1935
|
+
<CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
|
|
1936
|
+
)
|
|
1067
1937
|
}
|
|
1068
1938
|
/>
|
|
1069
|
-
|
|
1939
|
+
|
|
1070
1940
|
<CapActionButton
|
|
1071
1941
|
buttonType={buttonType}
|
|
1072
1942
|
updateButtonChange={updateButtonChange}
|
|
@@ -1075,44 +1945,90 @@ const splitTemplateVarString = (str) => {
|
|
|
1075
1945
|
isEditFlow={isEditFlow}
|
|
1076
1946
|
isFullMode={isFullMode}
|
|
1077
1947
|
maxButtons={MAX_BUTTONS}
|
|
1078
|
-
|
|
1948
|
+
host={hostName}
|
|
1949
|
+
location={location}
|
|
1950
|
+
tags={getRcsTags()}
|
|
1951
|
+
injectedTags={injectedTags || {}}
|
|
1952
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1953
|
+
onContextChange={handleOnTagsContextChange}
|
|
1954
|
+
/>
|
|
1079
1955
|
</>
|
|
1080
1956
|
);
|
|
1081
1957
|
};
|
|
1082
1958
|
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1959
|
+
const getRcsValueMap = (fieldTemplateString, fieldType) => {
|
|
1960
|
+
if (!fieldTemplateString) return {};
|
|
1961
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
1962
|
+
const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
1963
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
|
|
1964
|
+
const segmentIdToResolvedValue = {};
|
|
1965
|
+
let varOrdinal = 0;
|
|
1966
|
+
templateSegments.forEach((segmentToken, segmentIndexInField) => {
|
|
1967
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
1968
|
+
const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
|
|
1969
|
+
const varName = getVarNameFromToken(segmentToken);
|
|
1970
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
1971
|
+
varOrdinal += 1;
|
|
1972
|
+
segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
|
|
1973
|
+
cardVarMapped,
|
|
1974
|
+
varName,
|
|
1975
|
+
globalSlot,
|
|
1976
|
+
isEditLike,
|
|
1977
|
+
rcsSpanningSemanticVarNames.has(varName),
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
});
|
|
1981
|
+
return segmentIdToResolvedValue;
|
|
1982
|
+
};
|
|
1983
|
+
|
|
1984
|
+
const titleVarSegmentValueMapById = useMemo(
|
|
1985
|
+
() => getRcsValueMap(templateTitle, TITLE_TEXT),
|
|
1986
|
+
[templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
|
|
1987
|
+
);
|
|
1988
|
+
const descriptionVarSegmentValueMapById = useMemo(
|
|
1989
|
+
() => getRcsValueMap(templateDesc, MESSAGE_TEXT),
|
|
1990
|
+
[templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
|
|
1991
|
+
);
|
|
1992
|
+
|
|
1993
|
+
const handleRcsVarChange = (varSegmentCompositeDomId, value, type) => {
|
|
1994
|
+
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
1995
|
+
if (underscoreIndexInCompositeId === -1) return;
|
|
1996
|
+
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
1997
|
+
const variableName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
1998
|
+
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
1999
|
+
const isInvalidValue = value?.trim() === '';
|
|
2000
|
+
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
2001
|
+
const templateStringForField = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
2002
|
+
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
2003
|
+
varSegmentCompositeDomId,
|
|
2004
|
+
templateStringForField,
|
|
2005
|
+
type,
|
|
2006
|
+
);
|
|
2007
|
+
const fieldOffset = type === TITLE_TEXT ? 0 : (templateTitle?.match(rcsVarRegex) ?? []).length;
|
|
2008
|
+
setCardVarMapped((previousCardVarMapped) => {
|
|
2009
|
+
const updatedCardVarMapped = { ...previousCardVarMapped };
|
|
2010
|
+
// Remove stale semantic key: keeping it causes every other slot sharing the same
|
|
2011
|
+
// variable name (e.g. {{adv}} in both title and description) to read the same value
|
|
2012
|
+
// via the semantic-key fallback in resolveCardVarMappedSlotValue.
|
|
2013
|
+
delete updatedCardVarMapped[variableName];
|
|
2014
|
+
if (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined) {
|
|
2015
|
+
updatedCardVarMapped[String(globalVarSlotIndexZeroBased + 1)] = coercedSlotValue;
|
|
2016
|
+
// Sync all other intra-field occurrences of the same variable (cross-field slots stay independent)
|
|
2017
|
+
const fieldSegments = splitTemplateVarStringRcs(templateStringForField ?? '');
|
|
2018
|
+
let varOrdinal = 0;
|
|
2019
|
+
for (let i = 0; i < fieldSegments.length; i++) {
|
|
2020
|
+
if (rcsVarTestRegex.test(fieldSegments[i])) {
|
|
2021
|
+
const slotVarName = getVarNameFromToken(fieldSegments[i]);
|
|
2022
|
+
const globalSlot = fieldOffset + varOrdinal;
|
|
2023
|
+
varOrdinal += 1;
|
|
2024
|
+
if (slotVarName === variableName && globalSlot !== globalVarSlotIndexZeroBased) {
|
|
2025
|
+
updatedCardVarMapped[String(globalSlot + 1)] = coercedSlotValue;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
1112
2028
|
}
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
|
|
2029
|
+
}
|
|
2030
|
+
return updatedCardVarMapped;
|
|
2031
|
+
});
|
|
1116
2032
|
};
|
|
1117
2033
|
|
|
1118
2034
|
const renderTextComponent = () => {
|
|
@@ -1132,6 +2048,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1132
2048
|
}
|
|
1133
2049
|
suffix={
|
|
1134
2050
|
<>
|
|
2051
|
+
|
|
1135
2052
|
{(isEditFlow || !isFullMode) ? (
|
|
1136
2053
|
<TagList
|
|
1137
2054
|
label={formatMessage(globalMessages.addLabels)}
|
|
@@ -1148,18 +2065,28 @@ const splitTemplateVarString = (str) => {
|
|
|
1148
2065
|
type="flat"
|
|
1149
2066
|
isAddBtn
|
|
1150
2067
|
onClick={onTitleAddVar}
|
|
1151
|
-
disabled={
|
|
2068
|
+
disabled={templateTitleError}
|
|
1152
2069
|
>
|
|
1153
2070
|
{formatMessage(messages.addVar)}
|
|
1154
2071
|
</CapButton>
|
|
1155
|
-
|
|
2072
|
+
)}
|
|
1156
2073
|
</>
|
|
1157
|
-
}
|
|
2074
|
+
}
|
|
2075
|
+
/>
|
|
2076
|
+
|
|
2077
|
+
{(isEditFlow || !isFullMode) ? (
|
|
2078
|
+
<VarSegmentMessageEditor
|
|
2079
|
+
key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
2080
|
+
templateString={templateTitle}
|
|
2081
|
+
valueMap={titleVarSegmentValueMapById}
|
|
2082
|
+
onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
|
|
2083
|
+
onFocus={(id) => setTitleTextAreaId(id)}
|
|
2084
|
+
varRegex={rcsVarRegex}
|
|
2085
|
+
placeholderPrefix=""
|
|
2086
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1158
2087
|
/>
|
|
1159
|
-
<div className="rcs_text_area_wrapper">
|
|
1160
|
-
{(isEditFlow || !isFullMode) ? (
|
|
1161
|
-
renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
|
|
1162
2088
|
) : (
|
|
2089
|
+
<div className="rcs_text_area_wrapper">
|
|
1163
2090
|
<CapInput
|
|
1164
2091
|
className={`rcs-template-title-input ${
|
|
1165
2092
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -1172,8 +2099,8 @@ const splitTemplateVarString = (str) => {
|
|
|
1172
2099
|
errorMessage={templateTitleError}
|
|
1173
2100
|
disabled={isEditFlow || !isFullMode}
|
|
1174
2101
|
/>
|
|
2102
|
+
</div>
|
|
1175
2103
|
)}
|
|
1176
|
-
</div>
|
|
1177
2104
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
1178
2105
|
<CapError className="rcs-template-title-error">
|
|
1179
2106
|
{templateTitleError}
|
|
@@ -1181,7 +2108,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1181
2108
|
)}
|
|
1182
2109
|
{!isEditFlow && isFullMode && renderTitleCharacterCount()}
|
|
1183
2110
|
</>
|
|
1184
|
-
|
|
2111
|
+
)}
|
|
1185
2112
|
|
|
1186
2113
|
{/* Template Message */}
|
|
1187
2114
|
<CapRow useLegacy id="rcs-template-message-label">
|
|
@@ -1219,9 +2146,21 @@ const splitTemplateVarString = (str) => {
|
|
|
1219
2146
|
/>
|
|
1220
2147
|
</CapRow>
|
|
1221
2148
|
<CapRow useLegacy className="rcs-create-template-message-input">
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
2149
|
+
{/* 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. */}
|
|
2150
|
+
<CapRow className="rcs_text_area_wrapper">
|
|
2151
|
+
{(isEditFlow || !isFullMode)?
|
|
2152
|
+
(
|
|
2153
|
+
<VarSegmentMessageEditor
|
|
2154
|
+
key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
2155
|
+
templateString={templateDesc}
|
|
2156
|
+
valueMap={descriptionVarSegmentValueMapById}
|
|
2157
|
+
onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
|
|
2158
|
+
onFocus={(id) => setDescTextAreaId(id)}
|
|
2159
|
+
varRegex={rcsVarRegex}
|
|
2160
|
+
placeholderPrefix=""
|
|
2161
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
2162
|
+
/>
|
|
2163
|
+
)
|
|
1225
2164
|
: (
|
|
1226
2165
|
<>
|
|
1227
2166
|
<TextArea
|
|
@@ -1259,13 +2198,15 @@ const splitTemplateVarString = (str) => {
|
|
|
1259
2198
|
</>
|
|
1260
2199
|
)
|
|
1261
2200
|
}
|
|
1262
|
-
</
|
|
2201
|
+
</CapRow>
|
|
1263
2202
|
{(isEditFlow || !isFullMode) && templateDescError && (
|
|
1264
2203
|
<CapError className="rcs-template-message-error">
|
|
1265
2204
|
{templateDescError}
|
|
1266
2205
|
</CapError>
|
|
1267
2206
|
)}
|
|
1268
|
-
{
|
|
2207
|
+
{(isEditFlow || !isFullMode)
|
|
2208
|
+
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
2209
|
+
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1269
2210
|
{!isFullMode && hasTag() && (
|
|
1270
2211
|
<CapAlert
|
|
1271
2212
|
message={
|
|
@@ -1279,24 +2220,13 @@ const splitTemplateVarString = (str) => {
|
|
|
1279
2220
|
/>
|
|
1280
2221
|
)}
|
|
1281
2222
|
</CapRow>
|
|
1282
|
-
{
|
|
2223
|
+
{((!isEditFlow && isFullMode) || (isEditFlow && (suggestions?.length ?? 0) > 0)) &&
|
|
2224
|
+
renderButtonComponent()}
|
|
1283
2225
|
</>
|
|
1284
2226
|
|
|
1285
2227
|
);
|
|
1286
2228
|
};
|
|
1287
2229
|
|
|
1288
|
-
|
|
1289
|
-
const fallbackSmsLength = () => (
|
|
1290
|
-
<CapLabel type="label1" className="fallback-sms-length">
|
|
1291
|
-
{formatMessage(messages.totalCharacters, {
|
|
1292
|
-
smsCount: Math.ceil(
|
|
1293
|
-
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1294
|
-
),
|
|
1295
|
-
number: fallbackMessage?.length,
|
|
1296
|
-
})}
|
|
1297
|
-
</CapLabel>
|
|
1298
|
-
);
|
|
1299
|
-
|
|
1300
2230
|
// Get character count for title (rich card only)
|
|
1301
2231
|
const getTitleCharacterCount = () => {
|
|
1302
2232
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -1311,7 +2241,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1311
2241
|
// Get max length for description based on template type
|
|
1312
2242
|
const getDescriptionMaxLength = () => {
|
|
1313
2243
|
return templateType === contentType.text_message
|
|
1314
|
-
? RCS_TEXT_MESSAGE_MAX_LENGTH // 160 for text message
|
|
2244
|
+
? (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH) // 160 for text message
|
|
1315
2245
|
: RCS_RICH_CARD_MAX_LENGTH; // 2000 for rich card description
|
|
1316
2246
|
};
|
|
1317
2247
|
|
|
@@ -1374,287 +2304,34 @@ const splitTemplateVarString = (str) => {
|
|
|
1374
2304
|
return false;
|
|
1375
2305
|
};
|
|
1376
2306
|
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
base: data?.value?.base || {},
|
|
1402
|
-
},
|
|
1403
|
-
};
|
|
1404
|
-
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
1405
|
-
'',
|
|
1406
|
-
);
|
|
1407
|
-
closeDltContainerHandler();
|
|
1408
|
-
setDltEditData(tempData);
|
|
1409
|
-
setFallbackMessage(fallMsg);
|
|
1410
|
-
setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
|
|
1411
|
-
setShowDltCard(true);
|
|
1412
|
-
};
|
|
1413
|
-
|
|
1414
|
-
const rcsDltCardDeleteHandler = () => {
|
|
1415
|
-
closeDltContainerHandler();
|
|
1416
|
-
setDltEditData({});
|
|
1417
|
-
setFallbackMessage('');
|
|
1418
|
-
setFallbackMessageError(false);
|
|
1419
|
-
setShowDltCard(false);
|
|
1420
|
-
};
|
|
1421
|
-
|
|
1422
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
1423
|
-
const {
|
|
1424
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
1425
|
-
'sms-editor': smsEditor = '',
|
|
1426
|
-
} = data.versions.base || {};
|
|
1427
|
-
setFallbackPreviewmode(true);
|
|
1428
|
-
setDltPreviewData(
|
|
1429
|
-
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1430
|
-
);
|
|
1431
|
-
};
|
|
1432
|
-
|
|
1433
|
-
const getDltContentCardList = (content, channel) => {
|
|
1434
|
-
const extra = [
|
|
1435
|
-
<CapIcon
|
|
1436
|
-
type="edit"
|
|
1437
|
-
style={{ marginRight: '8px' }}
|
|
1438
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1439
|
-
/>,
|
|
1440
|
-
<CapDropdown
|
|
1441
|
-
overlay={(
|
|
1442
|
-
<CapMenu>
|
|
1443
|
-
<>
|
|
1444
|
-
<CapMenu.Item
|
|
1445
|
-
className="ant-dropdown-menu-item"
|
|
1446
|
-
onClick={() => setFallbackPreviewmode(true)}
|
|
1447
|
-
>
|
|
1448
|
-
{formatMessage(globalMessages.preview)}
|
|
1449
|
-
</CapMenu.Item>
|
|
1450
|
-
<CapMenu.Item
|
|
1451
|
-
className="ant-dropdown-menu-item"
|
|
1452
|
-
onClick={rcsDltCardDeleteHandler}
|
|
1453
|
-
>
|
|
1454
|
-
{formatMessage(globalMessages.delete)}
|
|
1455
|
-
</CapMenu.Item>
|
|
1456
|
-
</>
|
|
1457
|
-
</CapMenu>
|
|
1458
|
-
)}
|
|
1459
|
-
>
|
|
1460
|
-
<CapIcon type="more" />
|
|
1461
|
-
</CapDropdown>,
|
|
1462
|
-
];
|
|
1463
|
-
return {
|
|
1464
|
-
title: channel,
|
|
1465
|
-
content,
|
|
1466
|
-
cardType: channel,
|
|
1467
|
-
extra,
|
|
1468
|
-
};
|
|
1469
|
-
};
|
|
1470
|
-
|
|
1471
|
-
const getDltSlideBoxContent = () => {
|
|
1472
|
-
const loc = {
|
|
1473
|
-
pathname: `/sms/edit`,
|
|
1474
|
-
query: { type: EMBEDDED, module: LIBRARY },
|
|
1475
|
-
search: '',
|
|
1476
|
-
};
|
|
1477
|
-
const route = { name: SMS.toLowerCase() };
|
|
1478
|
-
let dltHeader = '';
|
|
1479
|
-
let dltContent = '';
|
|
1480
|
-
if (dltMode === RCS_DLT_MODE.TEMPLATES) {
|
|
1481
|
-
dltHeader = (
|
|
1482
|
-
<CapHeader
|
|
1483
|
-
title={formatMessage(messages.rcsDltTitle)}
|
|
1484
|
-
description={formatMessage(messages.rcsDltDesc)}
|
|
1485
|
-
/>
|
|
1486
|
-
);
|
|
1487
|
-
dltContent = (
|
|
1488
|
-
<Templates
|
|
1489
|
-
location={loc}
|
|
1490
|
-
route={route}
|
|
1491
|
-
smsRegister={smsRegister}
|
|
1492
|
-
isFullMode={isFullMode}
|
|
1493
|
-
isDltFromRcs
|
|
1494
|
-
onSelectTemplate={rcsDltEditSelectHandler}
|
|
1495
|
-
handlePeviewTemplate={dltFallbackListingPreviewhandler}
|
|
1496
|
-
/>
|
|
1497
|
-
);
|
|
1498
|
-
} else if (dltMode === RCS_DLT_MODE.EDIT) {
|
|
1499
|
-
dltHeader = formatMessage(messages.dltEditHeader);
|
|
1500
|
-
dltContent = (
|
|
1501
|
-
<SmsTraiEdit
|
|
1502
|
-
location={loc}
|
|
1503
|
-
route={route}
|
|
1504
|
-
getDefaultTags="outbound"
|
|
1505
|
-
handleClose={closeDltContainerHandler}
|
|
1506
|
-
templateData={dltEditData}
|
|
1507
|
-
getFormSubscriptionData={rcsDltEditCompletionHandler}
|
|
1508
|
-
isFullMode={false}
|
|
1509
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
1510
|
-
/>
|
|
1511
|
-
);
|
|
1512
|
-
}
|
|
1513
|
-
return { dltHeader, dltContent };
|
|
1514
|
-
};
|
|
1515
|
-
|
|
1516
|
-
const renderFallBackSmsComponent = () => {
|
|
1517
|
-
// Completely disable fallback functionality when DLT is disabled
|
|
1518
|
-
return null;
|
|
1519
|
-
|
|
1520
|
-
// const contentArr = [];
|
|
1521
|
-
// const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
|
|
1522
|
-
// const showCardForDlt = isDltEnabled && showDltCard;
|
|
1523
|
-
// const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
|
|
1524
|
-
// //pushing common fallback sms headings
|
|
1525
|
-
// contentArr.push(
|
|
1526
|
-
// <CapRow
|
|
1527
|
-
// style={{
|
|
1528
|
-
// marginBottom: isDltEnabled ? '20px' : '10px',
|
|
1529
|
-
// }}
|
|
1530
|
-
// >
|
|
1531
|
-
// <CapHeader
|
|
1532
|
-
// title={(
|
|
1533
|
-
// <CapRow type="flex">
|
|
1534
|
-
// <CapHeading type="h4">
|
|
1535
|
-
// {formatMessage(messages.fallbackLabel)}
|
|
1536
|
-
// </CapHeading>
|
|
1537
|
-
// <CapTooltipWithInfo
|
|
1538
|
-
// placement="right"
|
|
1539
|
-
// infoIconProps={{
|
|
1540
|
-
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1541
|
-
// }}
|
|
1542
|
-
// title={formatMessage(messages.fallbackToolTip)}
|
|
1543
|
-
// />
|
|
1544
|
-
// </CapRow>
|
|
1545
|
-
// )}
|
|
1546
|
-
// description={formatMessage(messages.fallbackDesc)}
|
|
1547
|
-
// suffix={
|
|
1548
|
-
// isDltEnabled ? null : (
|
|
1549
|
-
// <CapButton
|
|
1550
|
-
// type="flat"
|
|
1551
|
-
// className="fallback-preview-btn"
|
|
1552
|
-
// prefix={<CapIcon type="eye" />}
|
|
1553
|
-
// style={{ color: CAP_SECONDARY.base }}
|
|
1554
|
-
// onClick={() => setFallbackPreviewmode(true)}
|
|
1555
|
-
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1556
|
-
// >
|
|
1557
|
-
// {formatMessage(globalMessages.preview)}
|
|
1558
|
-
// </CapButton>
|
|
1559
|
-
// )
|
|
1560
|
-
// }
|
|
1561
|
-
// />
|
|
1562
|
-
// </CapRow>,
|
|
1563
|
-
// );
|
|
1564
|
-
|
|
1565
|
-
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1566
|
-
// showAddCreativeBtnForDlt
|
|
1567
|
-
// && contentArr.push(
|
|
1568
|
-
// <CapCard className="rcs-dlt-fallback-card">
|
|
1569
|
-
// <CapRow type="flex" justify="center" align="middle">
|
|
1570
|
-
// <CapColumn span={10}>
|
|
1571
|
-
// <CapImage src={addCreativesIcon} />
|
|
1572
|
-
// </CapColumn>
|
|
1573
|
-
// <CapColumn span={14}>
|
|
1574
|
-
// <CapButton
|
|
1575
|
-
// className="add-dlt-btn"
|
|
1576
|
-
// type="secondary"
|
|
1577
|
-
// onClick={addDltMsgHandler}
|
|
1578
|
-
// >
|
|
1579
|
-
// {formatMessage(messages.addSmsCreative)}
|
|
1580
|
-
// </CapButton>
|
|
1581
|
-
// </CapColumn>
|
|
1582
|
-
// </CapRow>
|
|
1583
|
-
// </CapCard>,
|
|
1584
|
-
// );
|
|
1585
|
-
|
|
1586
|
-
// //dlt is enabled and dlt content is added, show it in a card
|
|
1587
|
-
// showCardForDlt
|
|
1588
|
-
// && contentArr.push(
|
|
1589
|
-
// <CapCustomCardList
|
|
1590
|
-
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1591
|
-
// className="rcs-dlt-card"
|
|
1592
|
-
// />,
|
|
1593
|
-
// fallbackMessageError && (
|
|
1594
|
-
// <CapError className="rcs-fallback-len-error">
|
|
1595
|
-
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1596
|
-
// </CapError>
|
|
1597
|
-
// ),
|
|
1598
|
-
// );
|
|
1599
|
-
|
|
1600
|
-
// //dlt is not enabled, show non dlt text area
|
|
1601
|
-
// showNonDltFallbackComp
|
|
1602
|
-
// && contentArr.push(
|
|
1603
|
-
// <>
|
|
1604
|
-
// <CapRow>
|
|
1605
|
-
// <CapHeader
|
|
1606
|
-
// title={(
|
|
1607
|
-
// <CapHeading type="h4">
|
|
1608
|
-
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1609
|
-
// </CapHeading>
|
|
1610
|
-
// )}
|
|
1611
|
-
// suffix={(
|
|
1612
|
-
// <TagList
|
|
1613
|
-
// label={formatMessage(globalMessages.addLabels)}
|
|
1614
|
-
// onTagSelect={onTagSelectFallback}
|
|
1615
|
-
// location={location}
|
|
1616
|
-
// tags={tags || []}
|
|
1617
|
-
// onContextChange={handleOnTagsContextChange}
|
|
1618
|
-
// injectedTags={injectedTags || {}}
|
|
1619
|
-
// selectedOfferDetails={selectedOfferDetails}
|
|
1620
|
-
// />
|
|
1621
|
-
// )}
|
|
1622
|
-
// />
|
|
1623
|
-
// </CapRow>
|
|
1624
|
-
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1625
|
-
// <TextArea
|
|
1626
|
-
// id="rcs_fallback_message_textarea"
|
|
1627
|
-
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1628
|
-
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1629
|
-
// onChange={onFallbackMessageChange}
|
|
1630
|
-
// errorMessage={fallbackMessageError}
|
|
1631
|
-
// value={fallbackMessage || ""}
|
|
1632
|
-
// />
|
|
1633
|
-
// {!aiContentBotDisabled && (
|
|
1634
|
-
// <CapAskAira.ContentGenerationBot
|
|
1635
|
-
// text={fallbackMessage || ""}
|
|
1636
|
-
// setText={(text) => {
|
|
1637
|
-
// onFallbackMessageChange({ target: { value: text } });
|
|
1638
|
-
// }}
|
|
1639
|
-
// iconPlacement="float-br"
|
|
1640
|
-
// rootStyle={{
|
|
1641
|
-
// bottom: "0.5rem",
|
|
1642
|
-
// right: "0.5rem",
|
|
1643
|
-
// position: "absolute",
|
|
1644
|
-
// }}
|
|
1645
|
-
// />
|
|
1646
|
-
// )}
|
|
1647
|
-
// </div>
|
|
1648
|
-
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1649
|
-
// </>
|
|
1650
|
-
// );
|
|
1651
|
-
|
|
1652
|
-
// return <>{contentArr}</>;
|
|
1653
|
-
};
|
|
2307
|
+
const renderFallBackSmsComponent = () => (
|
|
2308
|
+
<SmsFallback
|
|
2309
|
+
value={smsFallbackData}
|
|
2310
|
+
onChange={setSmsFallbackData}
|
|
2311
|
+
parentLocation={location}
|
|
2312
|
+
smsRegister={smsRegister}
|
|
2313
|
+
isFullMode={isFullMode}
|
|
2314
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
2315
|
+
channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
|
|
2316
|
+
sectionTitle={
|
|
2317
|
+
smsFallbackData
|
|
2318
|
+
? formatMessage(messages.fallbackLabel)
|
|
2319
|
+
: formatMessage(messages.smsFallbackOptional)
|
|
2320
|
+
}
|
|
2321
|
+
templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
|
|
2322
|
+
templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
|
|
2323
|
+
/* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
|
|
2324
|
+
showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
|
|
2325
|
+
disableSelectTemplate={isEditFlow}
|
|
2326
|
+
eventContextTags={eventContextTags}
|
|
2327
|
+
onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
|
|
2328
|
+
isRcsEditFlow={isEditFlow}
|
|
2329
|
+
/>
|
|
2330
|
+
);
|
|
1654
2331
|
|
|
1655
2332
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
1656
2333
|
setImageError(null);
|
|
1657
|
-
const isRcsThumbnail = index
|
|
2334
|
+
const isRcsThumbnail = isThumbnailAssetIndex(index);
|
|
1658
2335
|
actions.uploadRcsAsset(file, type, {
|
|
1659
2336
|
isRcsThumbnail,
|
|
1660
2337
|
...fileParams,
|
|
@@ -1692,21 +2369,15 @@ const splitTemplateVarString = (str) => {
|
|
|
1692
2369
|
const updateOnRcsImageReUpload = useCallback(() => {
|
|
1693
2370
|
setUpdateRcsImageSrc('');
|
|
1694
2371
|
}, []);
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1698
|
-
setImageError(null);
|
|
2372
|
+
const uploadRcsVideo = (file, type, fileParams, index) => {
|
|
1699
2373
|
actions.uploadRcsAsset(file, type, {
|
|
1700
2374
|
...fileParams,
|
|
1701
2375
|
type: 'video',
|
|
1702
2376
|
channel: RCS,
|
|
1703
2377
|
isGenerateVideoThumbnail: false,
|
|
1704
|
-
});
|
|
2378
|
+
}, index);
|
|
1705
2379
|
};
|
|
1706
2380
|
|
|
1707
|
-
const updateRcsVideoSrc = (val) => {
|
|
1708
|
-
setRcsVideoSrc(val);
|
|
1709
|
-
};
|
|
1710
2381
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
1711
2382
|
setRcsVideoSrc(val);
|
|
1712
2383
|
setAssetList(val);
|
|
@@ -1729,13 +2400,13 @@ const splitTemplateVarString = (str) => {
|
|
|
1729
2400
|
updateRcsThumbnailSrc('');
|
|
1730
2401
|
};
|
|
1731
2402
|
|
|
1732
|
-
|
|
2403
|
+
const renderThumbnailComponent = () => {
|
|
1733
2404
|
const currentDimension = selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
1734
2405
|
return !isEditFlow && (
|
|
1735
2406
|
<>
|
|
1736
2407
|
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
1737
2408
|
<CapImageUpload
|
|
1738
|
-
|
|
2409
|
+
className="rcs-image-upload-with-top-padding"
|
|
1739
2410
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1740
2411
|
imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
|
|
1741
2412
|
imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
|
|
@@ -1753,6 +2424,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1753
2424
|
channel={RCS}
|
|
1754
2425
|
channelSpecificStyle={!isFullMode}
|
|
1755
2426
|
skipDimensionValidation={true}
|
|
2427
|
+
showReUploadButton={!isEditFlow && isFullMode}
|
|
1756
2428
|
/>
|
|
1757
2429
|
</>
|
|
1758
2430
|
)
|
|
@@ -1778,7 +2450,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1778
2450
|
value: dim.type,
|
|
1779
2451
|
label: `${dim.label}`
|
|
1780
2452
|
}))}
|
|
1781
|
-
|
|
2453
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1782
2454
|
/>
|
|
1783
2455
|
</>
|
|
1784
2456
|
)}
|
|
@@ -1792,7 +2464,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1792
2464
|
</div>
|
|
1793
2465
|
) : (
|
|
1794
2466
|
<CapImageUpload
|
|
1795
|
-
|
|
2467
|
+
className="rcs-image-upload-with-top-padding"
|
|
1796
2468
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1797
2469
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
1798
2470
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -1803,7 +2475,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1803
2475
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
1804
2476
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
1805
2477
|
index={0}
|
|
1806
|
-
className="cap-custom-image-upload"
|
|
2478
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1807
2479
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
1808
2480
|
imageData={rcsData}
|
|
1809
2481
|
channel={RCS}
|
|
@@ -1814,7 +2486,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1814
2486
|
|
|
1815
2487
|
</>
|
|
1816
2488
|
);
|
|
1817
|
-
|
|
2489
|
+
}
|
|
1818
2490
|
|
|
1819
2491
|
const renderVideoComponent = () => {
|
|
1820
2492
|
const currentDimension =selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
@@ -1833,7 +2505,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1833
2505
|
value: dim.type,
|
|
1834
2506
|
label: `${dim.label}`
|
|
1835
2507
|
}))}
|
|
1836
|
-
|
|
2508
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1837
2509
|
/>
|
|
1838
2510
|
)}
|
|
1839
2511
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -1891,10 +2563,48 @@ const splitTemplateVarString = (str) => {
|
|
|
1891
2563
|
};
|
|
1892
2564
|
|
|
1893
2565
|
const getRcsPreview = () => {
|
|
2566
|
+
|
|
2567
|
+
if (templateType === contentType.carousel) {
|
|
2568
|
+
const cardsForPreview = buildCarouselCardsForPreview(carouselData);
|
|
2569
|
+
const carouselDimKey = getCarouselDimensionKey(selectedCarouselHeight, selectedCarouselWidth);
|
|
2570
|
+
const carouselImgDims =
|
|
2571
|
+
RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
|
|
2572
|
+
const carouselVidDims =
|
|
2573
|
+
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
|
|
2574
|
+
|| RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
|
|
2575
|
+
// Debug log for embedded/library mode preview payload (carousel)
|
|
2576
|
+
// eslint-disable-next-line no-console
|
|
2577
|
+
return (
|
|
2578
|
+
<UnifiedPreview
|
|
2579
|
+
channel={RCS}
|
|
2580
|
+
content={{
|
|
2581
|
+
carouselData: cardsForPreview,
|
|
2582
|
+
carouselPreviewDimensions: {
|
|
2583
|
+
imageWidth: carouselImgDims.width,
|
|
2584
|
+
imageHeight: carouselImgDims.height,
|
|
2585
|
+
videoThumbWidth: carouselVidDims.width,
|
|
2586
|
+
videoThumbHeight: carouselVidDims.height,
|
|
2587
|
+
},
|
|
2588
|
+
}}
|
|
2589
|
+
device={ANDROID}
|
|
2590
|
+
showDeviceToggle={false}
|
|
2591
|
+
showHeader={false}
|
|
2592
|
+
formatMessage={formatMessage}
|
|
2593
|
+
/>
|
|
2594
|
+
);
|
|
2595
|
+
}
|
|
1894
2596
|
|
|
1895
2597
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
1896
|
-
const
|
|
1897
|
-
const
|
|
2598
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
2599
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
2600
|
+
? 0
|
|
2601
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
2602
|
+
const resolvedTitle = isMediaTypeText
|
|
2603
|
+
? ''
|
|
2604
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
2605
|
+
const resolvedDesc = isSlotMappingMode
|
|
2606
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
2607
|
+
: templateDesc;
|
|
1898
2608
|
return (
|
|
1899
2609
|
<UnifiedPreview
|
|
1900
2610
|
channel={RCS}
|
|
@@ -1917,51 +2627,39 @@ const splitTemplateVarString = (str) => {
|
|
|
1917
2627
|
);
|
|
1918
2628
|
};
|
|
1919
2629
|
|
|
1920
|
-
const getUnmappedDesc = (str, mapping) => {
|
|
1921
|
-
if (!str) return '';
|
|
1922
|
-
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1923
|
-
let result = str;
|
|
1924
|
-
const replacements = [];
|
|
1925
|
-
Object.entries(mapping).forEach(([key, value]) => {
|
|
1926
|
-
const raw = (value ?? '').toString();
|
|
1927
|
-
if (!raw || raw?.trim?.() === '') return;
|
|
1928
|
-
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1929
|
-
replacements.push({ key, needle: raw });
|
|
1930
|
-
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1931
|
-
});
|
|
1932
|
-
const seen = new Set();
|
|
1933
|
-
const uniq = replacements
|
|
1934
|
-
.filter(({ key, needle }) => {
|
|
1935
|
-
const id = `${key}::${needle}`;
|
|
1936
|
-
if (seen.has(id)) return false;
|
|
1937
|
-
seen.add(id);
|
|
1938
|
-
return true;
|
|
1939
|
-
})
|
|
1940
|
-
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1941
|
-
|
|
1942
|
-
uniq.forEach(({ key, needle }) => {
|
|
1943
|
-
if (!needle) return;
|
|
1944
|
-
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1945
|
-
const regex = new RegExp(escaped, 'g');
|
|
1946
|
-
result = result.replace(regex, `{{${key}}}`);
|
|
1947
|
-
});
|
|
1948
|
-
return result;
|
|
1949
|
-
};
|
|
1950
|
-
|
|
1951
2630
|
const createPayload = () => {
|
|
1952
|
-
const
|
|
1953
|
-
const {
|
|
1954
|
-
template_id: templateId = '',
|
|
1955
|
-
template_name = '',
|
|
1956
|
-
'sms-editor': template = '',
|
|
1957
|
-
header: registeredSenderIds = [],
|
|
1958
|
-
} = base;
|
|
1959
|
-
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1960
|
-
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
2631
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1961
2632
|
const alignment = isMediaTypeImage
|
|
1962
2633
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
1963
2634
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
1964
2635
|
|
|
2636
|
+
const heightTypeForCardWidth = isMediaTypeText
|
|
2637
|
+
? undefined
|
|
2638
|
+
: isMediaTypeImage
|
|
2639
|
+
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
|
|
2640
|
+
: isMediaTypeVideo
|
|
2641
|
+
? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
|
|
2642
|
+
: undefined;
|
|
2643
|
+
const cardWidthFromSelection =
|
|
2644
|
+
heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
|
|
2645
|
+
|
|
2646
|
+
/** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
|
|
2647
|
+
const smsFallbackPayloadContent = buildSmsFallBackContentForPayload({
|
|
2648
|
+
smsFallbackData,
|
|
2649
|
+
templateData,
|
|
2650
|
+
isFullMode,
|
|
2651
|
+
smsRegister,
|
|
2652
|
+
});
|
|
2653
|
+
|
|
2654
|
+
const carouselCardContent = isCarouselType
|
|
2655
|
+
? buildCarouselCardContentForPayload(carouselData, {
|
|
2656
|
+
isSlotMappingMode,
|
|
2657
|
+
cardVarMapped,
|
|
2658
|
+
selectedCarouselHeight,
|
|
2659
|
+
rcsVarRegex,
|
|
2660
|
+
})
|
|
2661
|
+
: null;
|
|
2662
|
+
|
|
1965
2663
|
const payload = {
|
|
1966
2664
|
name: templateName,
|
|
1967
2665
|
versions: {
|
|
@@ -1970,16 +2668,22 @@ const splitTemplateVarString = (str) => {
|
|
|
1970
2668
|
RCS: {
|
|
1971
2669
|
rcsContent: {
|
|
1972
2670
|
...(rcsAccount && !isFullMode && { accountId: rcsAccount }),
|
|
1973
|
-
cardType: STANDALONE,
|
|
1974
|
-
cardSettings:
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2671
|
+
cardType: isCarouselType ? contentType.carousel : STANDALONE,
|
|
2672
|
+
cardSettings: isCarouselType
|
|
2673
|
+
? { cardOrientation: VERTICAL, cardWidth: selectedCarouselWidth || SMALL }
|
|
2674
|
+
: {
|
|
2675
|
+
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
2676
|
+
...(alignment && { mediaAlignment: alignment }),
|
|
2677
|
+
cardWidth: SMALL,
|
|
2678
|
+
},
|
|
2679
|
+
cardContent: isCarouselType
|
|
2680
|
+
? carouselCardContent
|
|
2681
|
+
: [
|
|
1980
2682
|
{
|
|
1981
|
-
|
|
1982
|
-
|
|
2683
|
+
// Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
|
|
2684
|
+
// / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
|
|
2685
|
+
title: templateTitle,
|
|
2686
|
+
description: templateDesc,
|
|
1983
2687
|
mediaType: templateMediaType,
|
|
1984
2688
|
...(!isMediaTypeText && {media: {
|
|
1985
2689
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -1988,23 +2692,32 @@ const splitTemplateVarString = (str) => {
|
|
|
1988
2692
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
1989
2693
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
1990
2694
|
}}),
|
|
1991
|
-
...(
|
|
1992
|
-
const
|
|
1993
|
-
...(templateTitle
|
|
1994
|
-
...(templateDesc
|
|
2695
|
+
...(isSlotMappingMode && (() => {
|
|
2696
|
+
const templateVarTokens = [
|
|
2697
|
+
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
2698
|
+
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
1995
2699
|
];
|
|
1996
|
-
const
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2700
|
+
const cardVarMappedForRcsCardOnly = pickRcsCardVarMappedEntries(
|
|
2701
|
+
cardVarMapped,
|
|
2702
|
+
);
|
|
2703
|
+
// Persist numeric slot keys only ("1","2",…) — avoids duplicating the same value under
|
|
2704
|
+
// semantic names (e.g. both "1" and dynamic_expiry_date_after_3_days.FORMAT_1). Hydration
|
|
2705
|
+
// and coalesceCardVarMappedToTemplate still resolve from numeric keys + template tokens.
|
|
2706
|
+
const persistedSlotVarMap = {};
|
|
2707
|
+
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
2708
|
+
const varName = getVarNameFromToken(token);
|
|
2709
|
+
if (!varName) return;
|
|
2710
|
+
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
2711
|
+
cardVarMappedForRcsCardOnly,
|
|
2712
|
+
varName,
|
|
2713
|
+
slotIndexZeroBased,
|
|
2714
|
+
isSlotMappingMode,
|
|
2715
|
+
rcsSpanningSemanticVarNames.has(varName),
|
|
2716
|
+
);
|
|
2717
|
+
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
2718
|
+
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
2006
2719
|
});
|
|
2007
|
-
return { cardVarMapped:
|
|
2720
|
+
return { cardVarMapped: persistedSlotVarMap };
|
|
2008
2721
|
})()),
|
|
2009
2722
|
...(suggestions.length > 0 && { suggestions }),
|
|
2010
2723
|
}
|
|
@@ -2012,17 +2725,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2012
2725
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
2013
2726
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
2014
2727
|
},
|
|
2015
|
-
|
|
2016
|
-
message: fallbackMessage,
|
|
2017
|
-
...(isDltEnabled && {
|
|
2018
|
-
templateConfigs: {
|
|
2019
|
-
templateId,
|
|
2020
|
-
templateName: template_name,
|
|
2021
|
-
template,
|
|
2022
|
-
registeredSenderIds,
|
|
2023
|
-
},
|
|
2024
|
-
}),
|
|
2025
|
-
},
|
|
2728
|
+
...smsFallbackPayloadContent,
|
|
2026
2729
|
},
|
|
2027
2730
|
},
|
|
2028
2731
|
},
|
|
@@ -2032,6 +2735,132 @@ const splitTemplateVarString = (str) => {
|
|
|
2032
2735
|
return payload;
|
|
2033
2736
|
};
|
|
2034
2737
|
|
|
2738
|
+
/** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
|
|
2739
|
+
const testPreviewFormData = useMemo(() => {
|
|
2740
|
+
const payload = createPayload();
|
|
2741
|
+
const rcs = payload?.versions?.base?.content?.RCS;
|
|
2742
|
+
if (!rcs) return null;
|
|
2743
|
+
// createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
|
|
2744
|
+
const accountIdForCreateMessageMeta =
|
|
2745
|
+
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
2746
|
+
? String(wecrmAccountId)
|
|
2747
|
+
: accountId;
|
|
2748
|
+
const isSlotMappingModeForPreview = isEditFlow || !isFullMode;
|
|
2749
|
+
let rcsForTest = {
|
|
2750
|
+
...rcs,
|
|
2751
|
+
rcsContent: {
|
|
2752
|
+
...rcs.rcsContent,
|
|
2753
|
+
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
2754
|
+
},
|
|
2755
|
+
};
|
|
2756
|
+
/** Approval payload keeps numeric-only `cardVarMapped`; preview APIs still need semantic keys. */
|
|
2757
|
+
const cardContent = rcsForTest.rcsContent?.cardContent;
|
|
2758
|
+
if (Array.isArray(cardContent) && cardContent[0]) {
|
|
2759
|
+
if (isCarouselType) {
|
|
2760
|
+
// Carousel: resolve {{N}} slot tokens to the actual tag expressions / static values the
|
|
2761
|
+
// user mapped via cardVarMapped. Run this for ALL modes (create, edit, consumer) so that:
|
|
2762
|
+
// - buildRcsTestMessagePayload sends real Capillary tag names to the test API, and
|
|
2763
|
+
// - prepareTagExtractionPayload can extract tag metadata from the card content.
|
|
2764
|
+
// Track cumulative slotOffset across cards: each card's title/description vars occupy
|
|
2765
|
+
// sequential global slot indices ({{1}},{{2}} in card 0; {{3}},{{4}} in card 1, …).
|
|
2766
|
+
// Without the offset, every card restarts at slotKey="1" and resolves against card 0's
|
|
2767
|
+
// mappings, causing tags from card 1+ to be missing or wrong.
|
|
2768
|
+
let carouselSlotOffset = 0;
|
|
2769
|
+
rcsForTest = {
|
|
2770
|
+
...rcsForTest,
|
|
2771
|
+
rcsContent: {
|
|
2772
|
+
...rcsForTest.rcsContent,
|
|
2773
|
+
cardContent: cardContent.map((card) => {
|
|
2774
|
+
const rawTitle = card.title || '';
|
|
2775
|
+
const rawDesc = card.description || '';
|
|
2776
|
+
const titleVarCount = (rawTitle.match(rcsVarRegex) || []).length;
|
|
2777
|
+
const descVarCount = (rawDesc.match(rcsVarRegex) || []).length;
|
|
2778
|
+
const resolvedTitle = resolveTemplateWithMap(rawTitle, carouselSlotOffset);
|
|
2779
|
+
const resolvedDesc = resolveTemplateWithMap(rawDesc, carouselSlotOffset + titleVarCount);
|
|
2780
|
+
carouselSlotOffset += titleVarCount + descVarCount;
|
|
2781
|
+
return { ...card, title: resolvedTitle, description: resolvedDesc };
|
|
2782
|
+
}),
|
|
2783
|
+
},
|
|
2784
|
+
};
|
|
2785
|
+
} else if (isSlotMappingModeForPreview) {
|
|
2786
|
+
// Standalone card: coalesce cardVarMapped with the template's slot names so the preview
|
|
2787
|
+
// API receives a correctly-keyed var map for non-carousel templates.
|
|
2788
|
+
const fullCardVarMapped = coalesceCardVarMappedToTemplate(
|
|
2789
|
+
pickRcsCardVarMappedEntries(cardVarMapped),
|
|
2790
|
+
templateTitle,
|
|
2791
|
+
templateDesc,
|
|
2792
|
+
rcsVarRegex,
|
|
2793
|
+
);
|
|
2794
|
+
rcsForTest = {
|
|
2795
|
+
...rcsForTest,
|
|
2796
|
+
rcsContent: {
|
|
2797
|
+
...rcsForTest.rcsContent,
|
|
2798
|
+
cardContent: [
|
|
2799
|
+
{ ...cardContent[0], cardVarMapped: fullCardVarMapped },
|
|
2800
|
+
...cardContent.slice(1),
|
|
2801
|
+
],
|
|
2802
|
+
},
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
const out = {
|
|
2807
|
+
versions: {
|
|
2808
|
+
base: {
|
|
2809
|
+
content: {
|
|
2810
|
+
RCS: rcsForTest,
|
|
2811
|
+
},
|
|
2812
|
+
},
|
|
2813
|
+
},
|
|
2814
|
+
};
|
|
2815
|
+
// Use the smsFallbackTemplateConfigs already built by createPayload() / buildSmsFallBackContentForPayload —
|
|
2816
|
+
// it merges the API baseline (templateId, registeredSenderIds, templateName, etc.) with local
|
|
2817
|
+
// smsFallbackData state. Reading raw smsFallbackData here would miss the API-sourced smsTemplateId
|
|
2818
|
+
// in campaign mode. This also keeps templateName (and all other fields) in sync with the approval
|
|
2819
|
+
// payload automatically.
|
|
2820
|
+
const smsFallbackTcFromPayload = rcs?.rcsContent?.smsFallBackContent?.templateConfigs;
|
|
2821
|
+
if (smsFallbackTcFromPayload && typeof smsFallbackTcFromPayload === 'object') {
|
|
2822
|
+
out.templateConfigs = {
|
|
2823
|
+
...smsFallbackTcFromPayload,
|
|
2824
|
+
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2827
|
+
return out;
|
|
2828
|
+
}, [
|
|
2829
|
+
templateName,
|
|
2830
|
+
templateTitle,
|
|
2831
|
+
templateDesc,
|
|
2832
|
+
templateMediaType,
|
|
2833
|
+
cardVarMapped,
|
|
2834
|
+
carouselData,
|
|
2835
|
+
suggestions,
|
|
2836
|
+
rcsImageSrc,
|
|
2837
|
+
rcsVideoSrc,
|
|
2838
|
+
rcsThumbnailSrc,
|
|
2839
|
+
selectedDimension,
|
|
2840
|
+
smsFallbackData,
|
|
2841
|
+
isFullMode,
|
|
2842
|
+
isEditFlow,
|
|
2843
|
+
templateType,
|
|
2844
|
+
accountId,
|
|
2845
|
+
wecrmAccountId,
|
|
2846
|
+
accessToken,
|
|
2847
|
+
accountName,
|
|
2848
|
+
hostName,
|
|
2849
|
+
smsRegister,
|
|
2850
|
+
]);
|
|
2851
|
+
|
|
2852
|
+
/**
|
|
2853
|
+
* Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
|
|
2854
|
+
* with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
|
|
2855
|
+
* miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
|
|
2856
|
+
*/
|
|
2857
|
+
const librarySmsFallbackMergedForValidation = useMemo(
|
|
2858
|
+
() => mergeSmsFallbackForLibrary(templateData, smsFallbackData, isFullMode),
|
|
2859
|
+
[isFullMode, templateData, smsFallbackData],
|
|
2860
|
+
);
|
|
2861
|
+
|
|
2862
|
+
|
|
2863
|
+
|
|
2035
2864
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
2036
2865
|
// eslint-disable-next-line no-undef
|
|
2037
2866
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -2061,6 +2890,9 @@ const splitTemplateVarString = (str) => {
|
|
|
2061
2890
|
_id: params?.id,
|
|
2062
2891
|
validity: true,
|
|
2063
2892
|
type: RCS,
|
|
2893
|
+
// CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
|
|
2894
|
+
// the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
|
|
2895
|
+
closeSlideBoxAfterSubmit: !isFullMode,
|
|
2064
2896
|
};
|
|
2065
2897
|
getFormData(formDataParams);
|
|
2066
2898
|
};
|
|
@@ -2074,6 +2906,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2074
2906
|
actionCallback({ resp, errorMessage });
|
|
2075
2907
|
setSpin(false); // Always turn off spinner
|
|
2076
2908
|
if (!errorMessage) {
|
|
2909
|
+
setTemplateStatus(RCS_STATUSES.pending);
|
|
2077
2910
|
onCreateComplete();
|
|
2078
2911
|
}
|
|
2079
2912
|
});
|
|
@@ -2085,6 +2918,81 @@ const splitTemplateVarString = (str) => {
|
|
|
2085
2918
|
}
|
|
2086
2919
|
};
|
|
2087
2920
|
|
|
2921
|
+
const smsFallbackBlocksDone = () => checkSmsFallbackBlocksDone({
|
|
2922
|
+
isFullMode,
|
|
2923
|
+
smsRegister,
|
|
2924
|
+
smsFallbackData,
|
|
2925
|
+
templateData,
|
|
2926
|
+
smsFallbackMergedForValidation: librarySmsFallbackMergedForValidation,
|
|
2927
|
+
});
|
|
2928
|
+
|
|
2929
|
+
/**
|
|
2930
|
+
* Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
|
|
2931
|
+
* semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
|
|
2932
|
+
* / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
|
|
2933
|
+
*/
|
|
2934
|
+
const isLibraryCampaignCardVarMappingIncomplete = () => {
|
|
2935
|
+
if (isFullMode) return false;
|
|
2936
|
+
const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
|
|
2937
|
+
rcsVarTestRegex.test(elem),
|
|
2938
|
+
);
|
|
2939
|
+
const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
|
|
2940
|
+
rcsVarTestRegex.test(elem),
|
|
2941
|
+
);
|
|
2942
|
+
const orderedVarNames = [
|
|
2943
|
+
...titleTokens.map((token) => token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '')),
|
|
2944
|
+
...descTokens.map((token) => token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '')),
|
|
2945
|
+
];
|
|
2946
|
+
if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
|
|
2947
|
+
return true;
|
|
2948
|
+
}
|
|
2949
|
+
return orderedVarNames.some((name, globalIdx) => {
|
|
2950
|
+
const slotValue = resolveCardVarMappedSlotValue(
|
|
2951
|
+
cardVarMapped,
|
|
2952
|
+
name,
|
|
2953
|
+
globalIdx,
|
|
2954
|
+
true,
|
|
2955
|
+
rcsSpanningSemanticVarNames.has(name),
|
|
2956
|
+
);
|
|
2957
|
+
const stringValue = slotValue == null ? '' : String(slotValue);
|
|
2958
|
+
return stringValue.trim() === '';
|
|
2959
|
+
});
|
|
2960
|
+
};
|
|
2961
|
+
|
|
2962
|
+
const isCarouselLibraryIncomplete = () => {
|
|
2963
|
+
if (!isCarouselType || isFullMode) return false;
|
|
2964
|
+
if ((carouselErrors || []).some((err) => err?.title || err?.description)) return true;
|
|
2965
|
+
// Block if any card's title or description has no content at all (no static text and no variable tokens).
|
|
2966
|
+
const hasEmptyField = (carouselData || []).some((card) =>
|
|
2967
|
+
['title', 'description'].some((field) => !(card?.[field] || '').trim())
|
|
2968
|
+
);
|
|
2969
|
+
if (hasEmptyField) return true;
|
|
2970
|
+
const unfilledVar = (carouselData || []).some((card) =>
|
|
2971
|
+
['title', 'description'].some((field) => {
|
|
2972
|
+
const tokens = splitTemplateVarStringRcs(card?.[field] || '').filter((token) => rcsVarTestRegex.test(token));
|
|
2973
|
+
return tokens.some((token) => {
|
|
2974
|
+
const name = token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
|
|
2975
|
+
const slotValue = cardVarMapped?.[name];
|
|
2976
|
+
return slotValue == null || String(slotValue).trim() === '';
|
|
2977
|
+
});
|
|
2978
|
+
})
|
|
2979
|
+
);
|
|
2980
|
+
return unfilledVar;
|
|
2981
|
+
};
|
|
2982
|
+
|
|
2983
|
+
// Mirror WhatsApp's isEditDoneDisabled: block Done while a CTA button's dynamic URL still
|
|
2984
|
+
// holds the unresolved {{1}} placeholder (no real personalization tag assigned yet).
|
|
2985
|
+
const hasUnresolvedDynamicCtaUrl = () =>
|
|
2986
|
+
isCarouselType
|
|
2987
|
+
? (carouselData || []).some((card) =>
|
|
2988
|
+
(card?.suggestions || []).some((suggestion) =>
|
|
2989
|
+
suggestion?.type === CTA && (suggestion?.url || '').includes('{{1}}')
|
|
2990
|
+
)
|
|
2991
|
+
)
|
|
2992
|
+
: suggestions.some((suggestion) =>
|
|
2993
|
+
suggestion?.type === CTA && (suggestion?.url || '').includes('{{1}}')
|
|
2994
|
+
);
|
|
2995
|
+
|
|
2088
2996
|
const isDisableDone = () => {
|
|
2089
2997
|
if(isEditFlow){
|
|
2090
2998
|
return false;
|
|
@@ -2095,46 +3003,26 @@ const splitTemplateVarString = (str) => {
|
|
|
2095
3003
|
}
|
|
2096
3004
|
}
|
|
2097
3005
|
|
|
2098
|
-
if(
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2102
|
-
|
|
2103
|
-
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2104
|
-
return true;
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
const hasEmptyMapping =
|
|
2108
|
-
cardVarMapped &&
|
|
2109
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2110
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2111
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2112
|
-
return v.trim() === ''; // empty string
|
|
2113
|
-
});
|
|
2114
|
-
|
|
2115
|
-
if (hasEmptyMapping) {
|
|
2116
|
-
return true;
|
|
2117
|
-
}
|
|
3006
|
+
if (isCarouselLibraryIncomplete()) {
|
|
3007
|
+
return true;
|
|
3008
|
+
}
|
|
2118
3009
|
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
if (typeof v !== 'string') return !v;
|
|
2122
|
-
return v.trim() === '';
|
|
2123
|
-
});
|
|
2124
|
-
if (anyMissing) {
|
|
2125
|
-
return true;
|
|
2126
|
-
}
|
|
3010
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
3011
|
+
return true;
|
|
2127
3012
|
}
|
|
2128
3013
|
|
|
2129
|
-
|
|
3014
|
+
if (smsFallbackBlocksDone()) {
|
|
2130
3015
|
return true;
|
|
3016
|
+
}
|
|
2131
3017
|
|
|
3018
|
+
if (!isCarouselType && isMediaTypeText && templateDesc.trim() === '') {
|
|
3019
|
+
return true;
|
|
2132
3020
|
}
|
|
2133
|
-
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
3021
|
+
if (!isCarouselType && isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
2134
3022
|
return true;
|
|
2135
3023
|
}
|
|
2136
3024
|
|
|
2137
|
-
if (isMediaTypeVideo && (rcsVideoSrc.videoSrc
|
|
3025
|
+
if (!isCarouselType && isMediaTypeVideo && (!rcsVideoSrc.videoSrc || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
2138
3026
|
return true;
|
|
2139
3027
|
}
|
|
2140
3028
|
if (buttonType.includes(CTA)) {
|
|
@@ -2146,72 +3034,60 @@ const splitTemplateVarString = (str) => {
|
|
|
2146
3034
|
return true;
|
|
2147
3035
|
}
|
|
2148
3036
|
}
|
|
2149
|
-
if (
|
|
3037
|
+
if (hasUnresolvedDynamicCtaUrl()) {
|
|
3038
|
+
return true;
|
|
3039
|
+
}
|
|
3040
|
+
if (templateDescError || templateTitleError) {
|
|
3041
|
+
return true;
|
|
3042
|
+
}
|
|
3043
|
+
if (isSmsFallbackContentTooLong(smsFallbackData, FALLBACK_MESSAGE_MAX_LENGTH)) {
|
|
2150
3044
|
return true;
|
|
2151
3045
|
}
|
|
2152
3046
|
return false;
|
|
2153
3047
|
};
|
|
2154
3048
|
|
|
2155
3049
|
const isEditDisableDone = () => {
|
|
2156
|
-
|
|
2157
|
-
if (templateStatus !== RCS_STATUSES.approved) {
|
|
3050
|
+
if (isFullMode && !isHostInfoBip && templateStatus !== RCS_STATUSES.approved) {
|
|
2158
3051
|
return true;
|
|
2159
3052
|
}
|
|
2160
3053
|
|
|
2161
|
-
if (
|
|
2162
|
-
|
|
2163
|
-
return true;
|
|
2164
|
-
}
|
|
3054
|
+
if (isCarouselLibraryIncomplete()) {
|
|
3055
|
+
return true;
|
|
2165
3056
|
}
|
|
2166
|
-
if(!isFullMode){
|
|
2167
|
-
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2168
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2169
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2170
3057
|
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
const hasEmptyMapping =
|
|
2176
|
-
cardVarMapped &&
|
|
2177
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2178
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2179
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2180
|
-
return v.trim() === ''; // empty string
|
|
2181
|
-
});
|
|
2182
|
-
|
|
2183
|
-
if (hasEmptyMapping) {
|
|
2184
|
-
return true;
|
|
2185
|
-
}
|
|
3058
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
3059
|
+
return true;
|
|
3060
|
+
}
|
|
2186
3061
|
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
if (typeof v !== 'string') return !v;
|
|
2190
|
-
return v.trim() === '';
|
|
2191
|
-
});
|
|
2192
|
-
if (anyMissing) {
|
|
2193
|
-
return true;
|
|
2194
|
-
}
|
|
3062
|
+
if (smsFallbackBlocksDone()) {
|
|
3063
|
+
return true;
|
|
2195
3064
|
}
|
|
2196
|
-
|
|
3065
|
+
|
|
3066
|
+
if (!isCarouselType && isMediaTypeText && templateDesc.trim() === '') {
|
|
2197
3067
|
return true;
|
|
2198
3068
|
}
|
|
2199
|
-
if (isMediaTypeImage && rcsImageSrc === '') {
|
|
3069
|
+
if (!isCarouselType && isMediaTypeImage && rcsImageSrc === '') {
|
|
2200
3070
|
return true;
|
|
2201
3071
|
}
|
|
2202
|
-
if(isMediaTypeVideo && (rcsThumbnailSrc === '' || rcsVideoSrc.videoSrc === '')) {
|
|
3072
|
+
if (!isCarouselType && isMediaTypeVideo && (rcsThumbnailSrc === '' || rcsVideoSrc.videoSrc === '')) {
|
|
2203
3073
|
return true;
|
|
2204
3074
|
}
|
|
2205
3075
|
|
|
2206
3076
|
if (buttonType.includes(CTA)) {
|
|
2207
|
-
const hasValidButtons = suggestions.every(suggestion =>
|
|
3077
|
+
const hasValidButtons = suggestions.every(suggestion =>
|
|
2208
3078
|
suggestion.text && suggestion.url && !suggestionError && !forbiddenCharactersValidation(suggestion.text)
|
|
2209
3079
|
);
|
|
2210
3080
|
if (!hasValidButtons) {
|
|
2211
3081
|
return true;
|
|
2212
3082
|
}
|
|
2213
3083
|
}
|
|
2214
|
-
if (
|
|
3084
|
+
if (hasUnresolvedDynamicCtaUrl()) {
|
|
3085
|
+
return true;
|
|
3086
|
+
}
|
|
3087
|
+
if (templateTitleError || templateDescError) {
|
|
3088
|
+
return true;
|
|
3089
|
+
}
|
|
3090
|
+
if (isSmsFallbackContentTooLong(smsFallbackData, FALLBACK_MESSAGE_MAX_LENGTH)) {
|
|
2215
3091
|
return true;
|
|
2216
3092
|
}
|
|
2217
3093
|
return false;
|
|
@@ -2261,85 +3137,108 @@ const splitTemplateVarString = (str) => {
|
|
|
2261
3137
|
};
|
|
2262
3138
|
|
|
2263
3139
|
const getMainContent = () => {
|
|
2264
|
-
if (showDltContainer && !fallbackPreviewmode) {
|
|
2265
|
-
const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
|
|
2266
|
-
const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
|
|
2267
|
-
return (
|
|
2268
|
-
<CapSlideBox
|
|
2269
|
-
show={showDltContainer}
|
|
2270
|
-
header={dltHeader}
|
|
2271
|
-
content={dltContent}
|
|
2272
|
-
handleClose={closeDltContainerHandler}
|
|
2273
|
-
size="size-xl"
|
|
2274
|
-
/>
|
|
2275
|
-
);
|
|
2276
|
-
}
|
|
2277
|
-
|
|
2278
3140
|
return (
|
|
2279
3141
|
<>
|
|
2280
|
-
{templateStatus !== '' && (
|
|
2281
|
-
<
|
|
2282
|
-
{
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
3142
|
+
{templateStatus !== '' && (
|
|
3143
|
+
<CapRow useLegacy className="template-status-container">
|
|
3144
|
+
<CapColumn span={14}>
|
|
3145
|
+
<CapLabel type="label2">
|
|
3146
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
3147
|
+
</CapLabel>
|
|
3148
|
+
|
|
3149
|
+
{!isHostInfoBip && templateStatus && (
|
|
3150
|
+
<CapAlert
|
|
3151
|
+
message={getTemplateStatusMessage()}
|
|
3152
|
+
type={getTemplateStatusType(templateStatus)}
|
|
3153
|
+
/>
|
|
3154
|
+
)}
|
|
3155
|
+
</CapColumn>
|
|
3156
|
+
</CapRow>
|
|
2292
3157
|
)}
|
|
2293
|
-
<CapRow className=
|
|
3158
|
+
<CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
|
|
2294
3159
|
<CapColumn span={14}>
|
|
2295
3160
|
{/* template name */}
|
|
2296
3161
|
{isFullMode && (
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
3162
|
+
isEditFlow ? (
|
|
3163
|
+
<div className="rcs-creative-name-readonly">
|
|
3164
|
+
<CapHeading type="h4">
|
|
3165
|
+
{formatMessage(globalMessages.creativeNameLabel)}
|
|
3166
|
+
</CapHeading>
|
|
3167
|
+
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
3168
|
+
{templateName || '-'}
|
|
3169
|
+
</CapHeading>
|
|
3170
|
+
</div>
|
|
3171
|
+
) : (
|
|
3172
|
+
<CapInput
|
|
3173
|
+
id="rcs_template_name_input"
|
|
3174
|
+
data-testid="template_name"
|
|
3175
|
+
onChange={onTemplateNameChange}
|
|
3176
|
+
errorMessage={templateNameError}
|
|
3177
|
+
placeholder={formatMessage(
|
|
3178
|
+
globalMessages.templateNamePlaceholder,
|
|
3179
|
+
)}
|
|
3180
|
+
value={templateName || ''}
|
|
3181
|
+
size="default"
|
|
3182
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
3183
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
3184
|
+
/>
|
|
3185
|
+
)
|
|
3186
|
+
)}
|
|
3187
|
+
{isEditFlow ? (
|
|
3188
|
+
<div className="rcs-creative-name-readonly">
|
|
3189
|
+
<CapHeading type="h4">
|
|
3190
|
+
{formatMessage(messages.templateTypeLabel)}
|
|
3191
|
+
</CapHeading>
|
|
3192
|
+
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
3193
|
+
{TEMPLATE_TYPE_OPTIONS.find((option) => option.value === templateType)?.label || '-'}
|
|
3194
|
+
</CapHeading>
|
|
3195
|
+
</div>
|
|
3196
|
+
) : (
|
|
3197
|
+
<>
|
|
3198
|
+
{renderLabel('templateTypeLabel')}
|
|
3199
|
+
<ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
|
|
3200
|
+
<CapRadioGroup
|
|
3201
|
+
id="select-rcs-template-type"
|
|
3202
|
+
options={TEMPLATE_TYPE_OPTIONS}
|
|
3203
|
+
onChange={onTemplateTypeChange}
|
|
3204
|
+
value={templateType}
|
|
3205
|
+
disabled={!isFullMode}
|
|
3206
|
+
/>
|
|
3207
|
+
</ConfigProvider>
|
|
3208
|
+
</>
|
|
2310
3209
|
)}
|
|
2311
|
-
{renderLabel('templateTypeLabel')}
|
|
2312
|
-
<ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
|
|
2313
|
-
<CapRadioGroup
|
|
2314
|
-
id="select-rcs-template-type"
|
|
2315
|
-
options={TEMPLATE_TYPE_OPTIONS}
|
|
2316
|
-
onChange={onTemplateTypeChange}
|
|
2317
|
-
value={templateType}
|
|
2318
|
-
disabled={(isEditFlow || !isFullMode)}
|
|
2319
|
-
/>
|
|
2320
|
-
</ConfigProvider>
|
|
2321
3210
|
|
|
2322
|
-
{
|
|
2323
|
-
|
|
3211
|
+
{templateType === contentType.carousel ? (
|
|
3212
|
+
renderCarouselSection()
|
|
3213
|
+
) : (
|
|
2324
3214
|
<>
|
|
2325
|
-
{
|
|
2326
|
-
|
|
3215
|
+
{/* Show media only for rich_card */}
|
|
3216
|
+
{templateType === contentType.rich_card && (
|
|
3217
|
+
<>
|
|
3218
|
+
{renderLabel('mediaLabel')}
|
|
3219
|
+
<ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
|
|
2327
3220
|
<CapRadioGroup
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
3221
|
+
options={mediaRadioOptions || []}
|
|
3222
|
+
value={templateMediaType}
|
|
3223
|
+
onChange={onTemplateMediaTypeChange}
|
|
3224
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
3225
|
+
className="rcs-radio"
|
|
3226
|
+
/>
|
|
3227
|
+
</ConfigProvider>
|
|
2335
3228
|
<div className="rcs-container-image">
|
|
2336
|
-
|
|
2337
|
-
|
|
3229
|
+
{getMediaBasedComponent()}
|
|
3230
|
+
</div>
|
|
3231
|
+
</>
|
|
3232
|
+
)}
|
|
3233
|
+
{renderTextComponent()}
|
|
3234
|
+
</>
|
|
3235
|
+
)}
|
|
3236
|
+
{(!isEditFlow || hasMeaningfulSmsFallbackShape(smsFallbackData)) && (
|
|
3237
|
+
<>
|
|
3238
|
+
<CapDivider className="rcs-fallback-section-divider" />
|
|
3239
|
+
{renderFallBackSmsComponent()}
|
|
2338
3240
|
</>
|
|
2339
3241
|
)}
|
|
2340
|
-
{renderTextComponent()}
|
|
2341
|
-
<CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
|
|
2342
|
-
{renderFallBackSmsComponent()}
|
|
2343
3242
|
<div className="rcs-scroll-div" />
|
|
2344
3243
|
</CapColumn>
|
|
2345
3244
|
<CapColumn span={8} offset={2}>
|
|
@@ -2350,7 +3249,8 @@ const splitTemplateVarString = (str) => {
|
|
|
2350
3249
|
|
|
2351
3250
|
|
|
2352
3251
|
<div className="rcs-footer">
|
|
2353
|
-
{!
|
|
3252
|
+
{/* Full-mode create only: send-for-approval + disabled test/preview. Library mode uses Done below — onDoneCallback() is undefined when !isFullMode, so do not render this row (avoids a no-op primary button). */}
|
|
3253
|
+
{!isEditFlow && isFullMode && (
|
|
2354
3254
|
<>
|
|
2355
3255
|
<div className="button-disabled-tooltip-wrapper">
|
|
2356
3256
|
<CapButton
|
|
@@ -2358,7 +3258,9 @@ const splitTemplateVarString = (str) => {
|
|
|
2358
3258
|
disabled={isDisableDone()}
|
|
2359
3259
|
className="rcs-done-btn"
|
|
2360
3260
|
>
|
|
2361
|
-
<FormattedMessage
|
|
3261
|
+
<FormattedMessage
|
|
3262
|
+
{...(isHostInfoBip ? messages.doneButtonLabel : messages.sendForApprovalButtonLabel)}
|
|
3263
|
+
/>
|
|
2362
3264
|
</CapButton>
|
|
2363
3265
|
</div>
|
|
2364
3266
|
<CapTooltip
|
|
@@ -2371,7 +3273,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2371
3273
|
className="rcs-test-preview-btn"
|
|
2372
3274
|
type="secondary"
|
|
2373
3275
|
disabled={true}
|
|
2374
|
-
style={{ marginLeft: "8px" }}
|
|
2375
3276
|
>
|
|
2376
3277
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
2377
3278
|
</CapButton>
|
|
@@ -2392,7 +3293,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2392
3293
|
</div>
|
|
2393
3294
|
</>
|
|
2394
3295
|
)}
|
|
2395
|
-
{isEditFlow && templateStatus === RCS_STATUSES.approved && (
|
|
3296
|
+
{isEditFlow && (isHostInfoBip || templateStatus === RCS_STATUSES.approved) && (
|
|
2396
3297
|
<>
|
|
2397
3298
|
<CapButton
|
|
2398
3299
|
onClick={handleTestAndPreview}
|
|
@@ -2404,51 +3305,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2404
3305
|
</>
|
|
2405
3306
|
)}
|
|
2406
3307
|
</div>
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
{fallbackPreviewmode && (
|
|
2410
|
-
<CapSlideBox
|
|
2411
|
-
className="rcs-fallback-preview"
|
|
2412
|
-
show={fallbackPreviewmode}
|
|
2413
|
-
header={(
|
|
2414
|
-
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2415
|
-
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2416
|
-
</CapHeading>
|
|
2417
|
-
)}
|
|
2418
|
-
content={(
|
|
2419
|
-
<>
|
|
2420
|
-
<UnifiedPreview
|
|
2421
|
-
channel={RCS}
|
|
2422
|
-
content={{
|
|
2423
|
-
rcsPreviewContent: {
|
|
2424
|
-
rcsDesc: tempMsg,
|
|
2425
|
-
},
|
|
2426
|
-
}}
|
|
2427
|
-
device={ANDROID}
|
|
2428
|
-
showDeviceToggle={false}
|
|
2429
|
-
showHeader={false}
|
|
2430
|
-
formatMessage={formatMessage}
|
|
2431
|
-
/>
|
|
2432
|
-
<CapHeading
|
|
2433
|
-
type="h3"
|
|
2434
|
-
style={{ textAlign: 'center', width: '100%' }}
|
|
2435
|
-
className="margin-t-16"
|
|
2436
|
-
>
|
|
2437
|
-
{formatMessage(messages.totalCharacters, {
|
|
2438
|
-
smsCount: Math.ceil(
|
|
2439
|
-
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2440
|
-
),
|
|
2441
|
-
number: tempMsg.length,
|
|
2442
|
-
})}
|
|
2443
|
-
</CapHeading>
|
|
2444
|
-
</>
|
|
2445
|
-
)}
|
|
2446
|
-
handleClose={() => {
|
|
2447
|
-
setFallbackPreviewmode(false);
|
|
2448
|
-
setDltPreviewData('');
|
|
2449
|
-
}}
|
|
2450
|
-
/>
|
|
2451
|
-
)}
|
|
2452
3308
|
</>
|
|
2453
3309
|
);
|
|
2454
3310
|
};
|
|
@@ -2457,29 +3313,49 @@ const splitTemplateVarString = (str) => {
|
|
|
2457
3313
|
<CapSpin spinning={loadingTags || spin}>
|
|
2458
3314
|
{getMainContent()}
|
|
2459
3315
|
</CapSpin>
|
|
3316
|
+
|
|
2460
3317
|
<TestAndPreviewSlidebox
|
|
2461
3318
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
2462
3319
|
onClose={handleCloseTestAndPreview}
|
|
2463
|
-
formData={
|
|
2464
|
-
content={
|
|
3320
|
+
formData={testPreviewFormData}
|
|
3321
|
+
content={testAndPreviewContent}
|
|
2465
3322
|
currentChannel={RCS}
|
|
3323
|
+
orgUnitId={orgUnitId}
|
|
3324
|
+
rcsTestPreviewOptions={{ isLibraryMode: !isFullMode }}
|
|
3325
|
+
smsFallbackContent={
|
|
3326
|
+
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
3327
|
+
? {
|
|
3328
|
+
templateContent:
|
|
3329
|
+
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
3330
|
+
templateName: smsFallbackData.templateName || '',
|
|
3331
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: !isFullMode
|
|
3332
|
+
? mergeRcsSmsFallbackVarMapLayers(
|
|
3333
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
3334
|
+
smsFallbackData,
|
|
3335
|
+
)
|
|
3336
|
+
: mergeRcsSmsFallbackVarMapLayers({}, smsFallbackData),
|
|
3337
|
+
}
|
|
3338
|
+
: null
|
|
3339
|
+
}
|
|
3340
|
+
smsRegister={smsRegister}
|
|
2466
3341
|
/>
|
|
2467
3342
|
</>
|
|
2468
3343
|
);
|
|
2469
3344
|
};
|
|
2470
3345
|
|
|
3346
|
+
|
|
2471
3347
|
const mapStateToProps = createStructuredSelector({
|
|
2472
3348
|
rcsData: makeSelectRcs(),
|
|
2473
3349
|
accountData: makeSelectAccount(),
|
|
2474
3350
|
metaEntities: makeSelectMetaEntities(),
|
|
2475
3351
|
loadingTags: isLoadingMetaEntities(),
|
|
2476
3352
|
injectedTags: setInjectedTags(),
|
|
3353
|
+
currentOrgDetails: selectCurrentOrgDetails(),
|
|
2477
3354
|
});
|
|
2478
3355
|
|
|
2479
3356
|
const mapDispatchToProps = (dispatch) => ({
|
|
2480
3357
|
actions: bindActionCreators(RcsActions, dispatch),
|
|
2481
3358
|
globalActions: bindActionCreators(globalActions, dispatch),
|
|
2482
|
-
templatesActions: bindActionCreators(TemplatesActions, dispatch),
|
|
2483
3359
|
});
|
|
2484
3360
|
|
|
2485
3361
|
const withReducer = injectReducer({ key: 'rcs', reducer: v2RcsReducer });
|