@capillarytech/creatives-library 9.0.15-alpha.3 → 9.0.16-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AppRoot.js +80 -0
- package/app.js +5 -3
- package/constants/unified.js +0 -29
- package/entry.js +67 -1
- package/global-styles.js +1 -1
- package/mfe-exposed-components.js +2 -4
- package/package.json +2 -2
- package/services/api.js +1 -1
- package/services/tests/api.test.js +20 -35
- package/styles/containers/layout/_layoutPage.scss +8 -6
- package/utils/commonUtils.js +1 -19
- package/utils/getDataLayer.js +15 -0
- package/utils/gtmTrackers/gtmEvents/creativeDetails.js +2 -1
- package/utils/mfeDetect.js +1 -0
- package/v2Components/CapActionButton/constants.js +1 -8
- package/v2Components/CapActionButton/index.js +144 -202
- package/v2Components/CapActionButton/index.scss +14 -232
- package/v2Components/CapActionButton/messages.js +3 -19
- package/v2Components/CapActionButton/tests/index.test.js +18 -42
- package/v2Components/CapImageUpload/index.js +2 -2
- package/v2Components/CapTagList/index.js +0 -10
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +48 -70
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -214
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +9 -83
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -60
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +22 -202
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +80 -381
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +8 -155
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
- package/v2Components/CommonTestAndPreview/constants.js +2 -38
- package/v2Components/CommonTestAndPreview/index.js +222 -761
- package/v2Components/CommonTestAndPreview/messages.js +3 -45
- package/v2Components/CommonTestAndPreview/sagas.js +6 -25
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -1
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +267 -284
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
- package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -133
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -31
- package/v2Components/NavigationBar/index.js +9 -7
- package/v2Components/NavigationBar/mfeModuleHeader.config.js +16 -0
- package/v2Components/Pagination/_pagination.scss +2 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +25 -45
- package/v2Components/TemplatePreview/index.js +32 -147
- package/v2Components/TemplatePreview/tests/index.test.js +0 -142
- package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
- package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
- package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
- package/v2Containers/Cap/constants.js +1 -0
- package/v2Containers/Cap/index.js +47 -21
- package/v2Containers/Cap/sagas.js +19 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +1 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +0 -4
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
- package/v2Containers/CreativesContainer/SlideBoxContent.js +7 -37
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -14
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +5 -36
- package/v2Containers/CreativesContainer/constants.js +0 -11
- package/v2Containers/CreativesContainer/index.js +104 -323
- package/v2Containers/CreativesContainer/index.scss +1 -83
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -79
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +90 -333
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
- package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/constants.js +11 -131
- package/v2Containers/Rcs/index.js +829 -2682
- package/v2Containers/Rcs/index.scss +8 -443
- package/v2Containers/Rcs/messages.js +6 -45
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +39409 -74176
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +74 -10
- package/v2Containers/Rcs/tests/index.test.js +40 -151
- package/v2Containers/Rcs/tests/mockData.js +0 -38
- package/v2Containers/Rcs/tests/utils.test.js +30 -643
- package/v2Containers/Rcs/utils.js +12 -466
- package/v2Containers/Sms/Create/index.js +48 -115
- package/v2Containers/SmsTrai/Create/index.js +4 -9
- package/v2Containers/SmsTrai/Edit/constants.js +0 -2
- package/v2Containers/SmsTrai/Edit/index.js +169 -678
- package/v2Containers/SmsTrai/Edit/messages.js +4 -14
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2854 -5455
- package/v2Containers/SmsWrapper/index.js +8 -37
- package/v2Containers/TagList/index.js +0 -6
- package/v2Containers/Templates/_templates.scss +11 -196
- package/v2Containers/Templates/actions.js +0 -11
- package/v2Containers/Templates/constants.js +0 -2
- package/v2Containers/Templates/index.js +60 -131
- package/v2Containers/Templates/sagas.js +13 -57
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1015 -1060
- package/v2Containers/Templates/tests/sagas.test.js +16 -199
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +10 -75
- package/v2Containers/TemplatesV2/index.js +43 -86
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +25 -4
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
- package/utils/rcsPayloadUtils.js +0 -92
- package/utils/templateVarUtils.js +0 -201
- package/utils/tests/rcsPayloadUtils.test.js +0 -226
- package/utils/tests/templateVarUtils.test.js +0 -204
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
- package/v2Components/CommonTestAndPreview/utils.js +0 -84
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
- package/v2Components/SmsFallback/constants.js +0 -94
- package/v2Components/SmsFallback/index.js +0 -958
- package/v2Components/SmsFallback/index.scss +0 -266
- package/v2Components/SmsFallback/messages.js +0 -78
- package/v2Components/SmsFallback/smsFallbackUtils.js +0 -120
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -208
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
- package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
- package/v2Components/TemplatePreview/constants.js +0 -2
- package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
- package/v2Components/VarSegmentMessageEditor/index.js +0 -125
- package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -17
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -227
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
- package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
- package/v2Containers/SmsTrai/Edit/index.scss +0 -126
- package/v2Containers/Templates/TemplatesActionBar.js +0 -101
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/* eslint-disable no-unused-expressions */
|
|
2
|
-
import React, { useState, useEffect, useCallback
|
|
2
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
3
3
|
import { bindActionCreators } from 'redux';
|
|
4
4
|
import { createStructuredSelector } from 'reselect';
|
|
5
|
-
import { FormattedMessage } from 'react-intl';
|
|
5
|
+
import { injectIntl, 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';
|
|
10
11
|
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
11
12
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
12
13
|
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
@@ -34,14 +35,6 @@ import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
|
34
35
|
import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
|
|
35
36
|
import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
|
|
36
37
|
import CapLink from '@capillarytech/cap-ui-library/CapLink';
|
|
37
|
-
import CapTab from '@capillarytech/cap-ui-library/CapTab';
|
|
38
|
-
import { flushSync } from 'react-dom';
|
|
39
|
-
import { isUrl, isValidText } from '../Line/Container/Wrapper/utils';
|
|
40
|
-
import {
|
|
41
|
-
invalidVarRegex,
|
|
42
|
-
RCS_CTA_URL_TYPE,
|
|
43
|
-
URL_MAX_LENGTH,
|
|
44
|
-
} from '../../v2Components/CapActionButton/constants';
|
|
45
38
|
|
|
46
39
|
import {
|
|
47
40
|
CAP_G01,
|
|
@@ -57,30 +50,17 @@ import {
|
|
|
57
50
|
import CapVideoUpload from '../../v2Components/CapVideoUpload';
|
|
58
51
|
import * as globalActions from '../Cap/actions';
|
|
59
52
|
import CapActionButton from '../../v2Components/CapActionButton';
|
|
60
|
-
import TemplatePreview from '../../v2Components/TemplatePreview';
|
|
61
53
|
import { makeSelectRcs, makeSelectAccount } from './selectors';
|
|
62
54
|
import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
|
|
63
55
|
import {
|
|
64
56
|
isLoadingMetaEntities,
|
|
65
57
|
makeSelectMetaEntities,
|
|
66
58
|
setInjectedTags,
|
|
67
|
-
selectCurrentOrgDetails,
|
|
68
59
|
} from '../Cap/selectors';
|
|
69
60
|
import * as RcsActions from './actions';
|
|
70
61
|
import { isAiContentBotDisabled } from '../../utils/common';
|
|
71
62
|
import * as TemplatesActions from '../Templates/actions';
|
|
72
63
|
import './index.scss';
|
|
73
|
-
import {
|
|
74
|
-
normalizeLibraryLoadedTitleDesc,
|
|
75
|
-
mergeRcsSmsFallBackContentFromDetails,
|
|
76
|
-
mergeRcsSmsFallbackVarMapLayers,
|
|
77
|
-
extractRegisteredSenderIdsFromSmsFallbackRecord,
|
|
78
|
-
pickFirstSmsFallbackTemplateString,
|
|
79
|
-
syncCardVarMappedSemanticsFromSlots,
|
|
80
|
-
hasMeaningfulSmsFallbackShape,
|
|
81
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
82
|
-
pickRcsCardVarMappedEntries,
|
|
83
|
-
} from './rcsLibraryHydrationUtils';
|
|
84
64
|
import {
|
|
85
65
|
RCS,
|
|
86
66
|
SMS,
|
|
@@ -97,7 +77,6 @@ import {
|
|
|
97
77
|
RCS_IMG_SIZE,
|
|
98
78
|
RCS_DLT_MODE,
|
|
99
79
|
CTA,
|
|
100
|
-
AI_CONTENT_BOT_DISABLED,
|
|
101
80
|
RCS_STATUSES,
|
|
102
81
|
TITLE_TEXT,
|
|
103
82
|
MESSAGE_TEXT,
|
|
@@ -112,15 +91,9 @@ import {
|
|
|
112
91
|
rcsVarTestRegex,
|
|
113
92
|
RCS_IMAGE_DIMENSIONS,
|
|
114
93
|
RCS_TEXT_MESSAGE_MAX_LENGTH,
|
|
115
|
-
RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP,
|
|
116
94
|
RCS_RICH_CARD_MAX_LENGTH,
|
|
117
95
|
RCS_VIDEO_THUMBNAIL_DIMENSIONS,
|
|
118
|
-
RCS_CAROUSEL_IMAGE_DIMENSIONS,
|
|
119
|
-
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS,
|
|
120
|
-
RCS_CAROUSEL_IMG_SIZE,
|
|
121
|
-
RCS_CAROUSEL_VIDEO_SIZE,
|
|
122
96
|
MAX_BUTTONS,
|
|
123
|
-
INITIAL_SUGGESTIONS,
|
|
124
97
|
INITIAL_SUGGESTIONS_DATA_STOP,
|
|
125
98
|
RCS_BUTTON_TYPES,
|
|
126
99
|
titletype,
|
|
@@ -130,50 +103,25 @@ import {
|
|
|
130
103
|
SMALL,
|
|
131
104
|
MEDIUM,
|
|
132
105
|
RICHCARD,
|
|
133
|
-
HOST_INFOBIP,
|
|
134
|
-
HOST_ICS,
|
|
135
|
-
CAROUSEL_HEIGHT_OPTIONS,
|
|
136
|
-
CAROUSEL_WIDTH_OPTIONS,
|
|
137
|
-
STOP,
|
|
138
|
-
RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
|
|
139
|
-
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
140
|
-
RCS_NUMERIC_VAR_TOKEN_REGEX,
|
|
141
|
-
RCS_STRIP_MUSTACHE_DELIMITERS_REGEX,
|
|
142
|
-
RCS_TAG_AREA_FIELD_TITLE,
|
|
143
|
-
RCS_TAG_AREA_FIELD_DESC,
|
|
144
|
-
REGEX_SPECIAL_CHARS_ESCAPE_PATTERN,
|
|
145
|
-
JS_TYPES,
|
|
146
106
|
} from './constants';
|
|
147
107
|
import globalMessages from '../Cap/messages';
|
|
148
108
|
import messages from './messages';
|
|
149
109
|
import creativesMessages from '../CreativesContainer/messages';
|
|
150
110
|
import withCreatives from '../../hoc/withCreatives';
|
|
151
111
|
import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
152
|
-
import
|
|
153
|
-
import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
112
|
+
import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
|
|
154
113
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
155
|
-
import { splitTemplateVarString } from '../../utils/templateVarUtils';
|
|
156
114
|
import CapImageUpload from '../../v2Components/CapImageUpload';
|
|
115
|
+
import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
|
|
157
116
|
import Templates from '../Templates';
|
|
158
117
|
import SmsTraiEdit from '../SmsTrai/Edit';
|
|
159
|
-
import SmsFallback from '../../v2Components/SmsFallback';
|
|
160
|
-
import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
|
|
161
118
|
import TagList from '../TagList';
|
|
162
119
|
import { validateTags } from '../../utils/tagValidations';
|
|
163
|
-
import {
|
|
120
|
+
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
164
121
|
import { isTagIncluded } from '../../utils/commonUtils';
|
|
165
122
|
import injectReducer from '../../utils/injectReducer';
|
|
166
123
|
import v2RcsReducer from './reducer';
|
|
167
|
-
import {
|
|
168
|
-
areAllRcsSmsFallbackVarSlotsFilled,
|
|
169
|
-
buildRcsNumericMustachePlaceholderRegex,
|
|
170
|
-
getTemplateStatusType,
|
|
171
|
-
normalizeCardVarMapped,
|
|
172
|
-
coalesceCardVarMappedToTemplate,
|
|
173
|
-
getRcsSemanticVarNamesSpanningTitleAndDesc,
|
|
174
|
-
resolveCardVarMappedSlotValue,
|
|
175
|
-
sanitizeCardVarMappedValue,
|
|
176
|
-
} from './utils';
|
|
124
|
+
import { getTemplateStatusType } from './utils';
|
|
177
125
|
|
|
178
126
|
|
|
179
127
|
const { Group: CapCheckboxGroup } = CapCheckbox;
|
|
@@ -190,46 +138,28 @@ export const Rcs = (props) => {
|
|
|
190
138
|
templatesActions,
|
|
191
139
|
globalActions,
|
|
192
140
|
location,
|
|
141
|
+
handleClose,
|
|
193
142
|
getDefaultTags,
|
|
194
143
|
supportedTags,
|
|
195
144
|
metaEntities,
|
|
196
145
|
injectedTags,
|
|
197
146
|
loadingTags,
|
|
198
147
|
getFormData,
|
|
148
|
+
isDltEnabled,
|
|
199
149
|
smsRegister,
|
|
200
|
-
orgUnitId,
|
|
201
150
|
selectedOfferDetails,
|
|
202
151
|
eventContextTags,
|
|
152
|
+
waitEventContextTags,
|
|
203
153
|
accountData = {},
|
|
204
|
-
currentOrgDetails,
|
|
205
154
|
// TestAndPreviewSlidebox props
|
|
206
155
|
showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
|
|
207
156
|
handleTestAndPreview: propsHandleTestAndPreview,
|
|
208
157
|
handleCloseTestAndPreview: propsHandleCloseTestAndPreview,
|
|
209
|
-
handleClose,
|
|
210
158
|
} = props || {};
|
|
211
159
|
const { formatMessage } = intl;
|
|
212
160
|
const { TextArea } = CapInput;
|
|
213
161
|
const { CapCustomCardList } = CapCustomCard;
|
|
214
|
-
|
|
215
|
-
// Defensive: React cannot render plain objects as children (crashes with
|
|
216
|
-
// "Objects are not valid as a React child"). Some campaigns (!isFullMode) flows
|
|
217
|
-
// can accidentally set an error state to an object (e.g. `{}`).
|
|
218
|
-
const normalizeErrorMessage = (err) => {
|
|
219
|
-
if (!err) return '';
|
|
220
|
-
if (React.isValidElement(err)) return err;
|
|
221
|
-
if (typeof err === JS_TYPES.STRING) return err;
|
|
222
|
-
if (typeof err === JS_TYPES.NUMBER) return String(err);
|
|
223
|
-
if (typeof err === JS_TYPES.OBJECT && typeof err.message === JS_TYPES.STRING) return err.message;
|
|
224
|
-
try {
|
|
225
|
-
return JSON.stringify(err);
|
|
226
|
-
} catch (e) {
|
|
227
|
-
return String(err);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
|
|
231
162
|
const [isEditFlow, setEditFlow] = useState(false);
|
|
232
|
-
const isEditLike = isEditFlow || !isFullMode;
|
|
233
163
|
const [tags, updateTags] = useState([]);
|
|
234
164
|
const [spin, setSpin] = useState(false);
|
|
235
165
|
//template
|
|
@@ -238,21 +168,33 @@ export const Rcs = (props) => {
|
|
|
238
168
|
const [templateMediaType, setTemplateMediaType] = useState(
|
|
239
169
|
RCS_MEDIA_TYPES.NONE,
|
|
240
170
|
);
|
|
171
|
+
const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
|
|
241
172
|
const [templateTitle, setTemplateTitle] = useState('');
|
|
242
173
|
const [templateDesc, setTemplateDesc] = useState('');
|
|
243
174
|
const [templateDescError, setTemplateDescError] = useState(false);
|
|
244
175
|
const [templateStatus, setTemplateStatus] = useState('');
|
|
176
|
+
const [templateDate, setTemplateDate] = useState('');
|
|
177
|
+
//fallback
|
|
178
|
+
const [fallbackMessage, setFallbackMessage] = useState('');
|
|
179
|
+
const [fallbackMessageError, setFallbackMessageError] = useState(false);
|
|
245
180
|
//fallback dlt
|
|
246
181
|
const [showDltContainer, setShowDltContainer] = useState(false);
|
|
247
182
|
const [dltMode, setDltMode] = useState('');
|
|
248
183
|
const [dltEditData, setDltEditData] = useState({});
|
|
249
|
-
|
|
250
|
-
const [
|
|
184
|
+
const [showDltCard, setShowDltCard] = useState(false);
|
|
185
|
+
const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
|
|
186
|
+
const [dltPreviewData, setDltPreviewData] = useState('');
|
|
251
187
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
|
|
252
|
-
const buttonType = RCS_BUTTON_TYPES.NONE;
|
|
188
|
+
const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
|
|
253
189
|
const [suggestionError, setSuggestionError] = useState(true);
|
|
254
|
-
const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
|
|
255
190
|
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([]);
|
|
256
198
|
const [titleVarMappedData, setTitleVarMappedData] = useState({});
|
|
257
199
|
const [descVarMappedData, setDescVarMappedData] = useState({});
|
|
258
200
|
const [titleTextAreaId, setTitleTextAreaId] = useState();
|
|
@@ -263,58 +205,75 @@ export const Rcs = (props) => {
|
|
|
263
205
|
const [rcsVideoSrc, setRcsVideoSrc] = useState({});
|
|
264
206
|
const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
|
|
265
207
|
const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
266
|
-
|
|
267
|
-
const [selectedCarousel, setSelectedCarousel] = useState('');
|
|
268
|
-
const [selectedCarouselHeight, setSelectedCarouselHeight] = useState(MEDIUM);
|
|
269
|
-
const [selectedCarouselWidth, setSelectedCarouselWidth] = useState(SMALL);
|
|
270
|
-
const [carouselData, setCarouselData] = useState([]);
|
|
271
|
-
const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
|
|
272
|
-
const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
|
|
273
|
-
const [carouselResetNonce, setCarouselResetNonce] = useState(0);
|
|
274
|
-
const [carouselFocusedVarId, setCarouselFocusedVarId] = useState('');
|
|
275
|
-
const [imageError, setImageError] = useState(null);
|
|
208
|
+
const [imageError, setImageError] = useState(null);
|
|
276
209
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
277
210
|
const [cardVarMapped, setCardVarMapped] = useState({});
|
|
278
|
-
/** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
|
|
279
|
-
const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
|
|
280
|
-
const lastHydratedRcsCardVarSignatureRef = useRef(null);
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
|
|
284
|
-
* without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
|
|
285
|
-
* fallback card / content stopped appearing until re-selected.
|
|
286
|
-
*/
|
|
287
|
-
const rcsHydrationDetails = useMemo(
|
|
288
|
-
() => (isFullMode ? rcsData?.templateDetails : templateData),
|
|
289
|
-
[isFullMode, rcsData?.templateDetails, templateData],
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
/** 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'). */
|
|
293
|
-
const lastTagSchemaQueryKeyRef = useRef(null);
|
|
294
|
-
/**
|
|
295
|
-
* Library: parent often passes a new `templateData` object reference every render. Re-applying the same
|
|
296
|
-
* SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
|
|
297
|
-
*/
|
|
298
|
-
const lastSmsFallbackHydrationKeyRef = useRef(null);
|
|
299
|
-
|
|
300
|
-
const fetchTagSchemaIfNewQuery = useCallback(
|
|
301
|
-
(query) => {
|
|
302
|
-
const key = JSON.stringify(query);
|
|
303
|
-
if (lastTagSchemaQueryKeyRef.current === key) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
lastTagSchemaQueryKeyRef.current = key;
|
|
307
|
-
globalActions.fetchSchemaForEntity(query);
|
|
308
|
-
},
|
|
309
|
-
[globalActions],
|
|
310
|
-
);
|
|
311
211
|
|
|
312
212
|
// TestAndPreviewSlidebox state
|
|
313
213
|
const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
|
|
214
|
+
const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
|
|
215
|
+
|
|
216
|
+
const tempMsg = dltPreviewData === '' ? fallbackMessage : dltPreviewData;
|
|
217
|
+
|
|
218
|
+
// Get template content for TestAndPreviewSlidebox
|
|
219
|
+
// Reference: Based on getRcsPreview() function (lines 2087-2111) which prepares content for TemplatePreview
|
|
220
|
+
// getRcsPreview ALWAYS uses templateTitle and templateDesc for ALL template types (text_message, rich_card, carousel)
|
|
221
|
+
// renderTextComponent (lines 1317-1485) also uses templateTitle and templateDesc
|
|
222
|
+
// Note: templateHeader and templateMessage are defined but NOT used in the component
|
|
223
|
+
const getTemplateContent = useCallback(() => {
|
|
224
|
+
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
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;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Build content object
|
|
247
|
+
// Reference: getRcsPreview (line 2091-2092) uses templateTitle and templateDesc for ALL cases
|
|
248
|
+
// templateTitle is used for rich_card/carousel title, empty for text_message
|
|
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
|
+
};
|
|
260
|
+
|
|
261
|
+
return contentObj;
|
|
262
|
+
}, [
|
|
263
|
+
templateMediaType,
|
|
264
|
+
templateTitle,
|
|
265
|
+
templateDesc,
|
|
266
|
+
rcsImageSrc,
|
|
267
|
+
rcsVideoSrc,
|
|
268
|
+
rcsThumbnailSrc,
|
|
269
|
+
suggestions,
|
|
270
|
+
selectedDimension,
|
|
271
|
+
]);
|
|
314
272
|
|
|
315
273
|
// Handle Test and Preview button click
|
|
316
274
|
const handleTestAndPreview = useCallback(() => {
|
|
317
275
|
setShowTestAndPreviewSlidebox(true);
|
|
276
|
+
setIsTestAndPreviewMode(true);
|
|
318
277
|
if (propsHandleTestAndPreview) {
|
|
319
278
|
propsHandleTestAndPreview();
|
|
320
279
|
}
|
|
@@ -338,758 +297,31 @@ export const Rcs = (props) => {
|
|
|
338
297
|
// For video
|
|
339
298
|
return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
|
|
340
299
|
};
|
|
341
|
-
/** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
|
|
342
|
-
const handleSmsFallbackEditorStateChange = useCallback((patch) => {
|
|
343
|
-
setSmsFallbackData((prev) => {
|
|
344
|
-
if (!patch || typeof patch !== 'object') return prev;
|
|
345
|
-
// Bail out when no template has been selected yet — SmsTraiEdit fires
|
|
346
|
-
// onRcsFallbackEditorStateChange on mount (unicodeValidity, varMapped),
|
|
347
|
-
// which would create a non-null smsFallbackData from nothing and cause
|
|
348
|
-
// the card to appear as "Untitled creative" before any save.
|
|
349
|
-
if (!prev) return prev;
|
|
350
|
-
return { ...prev, ...patch };
|
|
351
|
-
});
|
|
352
|
-
}, []);
|
|
353
300
|
|
|
354
|
-
/** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
|
|
355
301
|
const [accountId, setAccountId] = useState('');
|
|
356
|
-
/** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
|
|
357
|
-
const [wecrmAccountId, setWecrmAccountId] = useState('');
|
|
358
302
|
const [accessToken, setAccessToken] = useState('');
|
|
359
303
|
const [hostName, setHostName] = useState('');
|
|
360
304
|
const [accountName, setAccountName] = useState('');
|
|
361
|
-
const isHostInfoBip = hostName === HOST_INFOBIP;
|
|
362
|
-
const isHostIcs = hostName === HOST_ICS;
|
|
363
|
-
|
|
364
|
-
useEffect(() => {
|
|
365
|
-
setSuggestions(isHostIcs ? INITIAL_SUGGESTIONS_DATA_STOP : []);
|
|
366
|
-
}, [isHostIcs]);
|
|
367
305
|
const [rcsAccount, setRcsAccount] = useState('');
|
|
368
306
|
useEffect(() => {
|
|
369
307
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
370
308
|
if (!isEmpty(accountObj)) {
|
|
371
309
|
const {
|
|
372
|
-
id: wecrmId,
|
|
373
310
|
sourceAccountIdentifier = '',
|
|
374
311
|
configs = {},
|
|
375
312
|
} = accountObj;
|
|
313
|
+
|
|
376
314
|
setAccountId(sourceAccountIdentifier);
|
|
377
|
-
setWecrmAccountId(
|
|
378
|
-
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
379
|
-
);
|
|
380
315
|
setAccessToken(configs.accessToken || '');
|
|
381
316
|
setHostName(accountObj.hostName || '');
|
|
382
317
|
setAccountName(accountObj.name || '');
|
|
383
318
|
setRcsAccount(accountObj.id || '');
|
|
384
|
-
} else {
|
|
385
|
-
setAccountId('');
|
|
386
|
-
setWecrmAccountId('');
|
|
387
|
-
setAccessToken('');
|
|
388
|
-
setHostName('');
|
|
389
|
-
setAccountName('');
|
|
390
319
|
}
|
|
391
320
|
}, [accountData.selectedRcsAccount]);
|
|
392
321
|
|
|
393
322
|
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
394
323
|
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
395
324
|
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
396
|
-
const isCarouselType = templateType === contentType.carousel;
|
|
397
|
-
|
|
398
|
-
const MAX_RCS_CAROUSEL_ALLOWED = 10;
|
|
399
|
-
// Uploads for RCS are stored in redux under dynamic keys `uploadedAssetData${index}`.
|
|
400
|
-
// Carousel needs per-card indices; otherwise all cards "restore" the last uploaded asset
|
|
401
|
-
// and show the same media/thumbnail.
|
|
402
|
-
const RCS_CAROUSEL_ASSET_INDEX_BASE = 10; // keep away from standalone indices 0 (media) and 1 (thumbnail)
|
|
403
|
-
const getCarouselImageAssetIndex = (cardIndex) =>
|
|
404
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
|
|
405
|
-
const getCarouselVideoAssetIndex = (cardIndex) =>
|
|
406
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 1;
|
|
407
|
-
const getCarouselThumbnailAssetIndex = (cardIndex) =>
|
|
408
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 2;
|
|
409
|
-
const isThumbnailAssetIndex = (index) => {
|
|
410
|
-
if (index === 1) return true; // standalone thumbnail
|
|
411
|
-
if (index >= RCS_CAROUSEL_ASSET_INDEX_BASE) {
|
|
412
|
-
return ((index - RCS_CAROUSEL_ASSET_INDEX_BASE) % 3) === 2; // carousel thumbnail slot
|
|
413
|
-
}
|
|
414
|
-
return false;
|
|
415
|
-
};
|
|
416
|
-
|
|
417
|
-
// Carousel dimension key: `${HEIGHT}_${WIDTH}` (e.g., SHORT_SMALL)
|
|
418
|
-
const getCarouselDimensionKey = () => `${selectedCarouselHeight}_${selectedCarouselWidth}`;
|
|
419
|
-
|
|
420
|
-
const clearCarouselCardMedia = (cardIndex, { clearImage = true, clearVideo = true, clearThumb = true } = {}) => {
|
|
421
|
-
setCarouselData((prev = []) => {
|
|
422
|
-
const updated = cloneDeep(prev);
|
|
423
|
-
if (!updated?.[cardIndex]) return updated;
|
|
424
|
-
if (clearImage) updated[cardIndex].imageSrc = '';
|
|
425
|
-
if (clearVideo) updated[cardIndex].videoAsset = {};
|
|
426
|
-
if (clearThumb) updated[cardIndex].thumbnailSrc = '';
|
|
427
|
-
return updated;
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
if (clearImage) actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIndex));
|
|
431
|
-
if (clearVideo) actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIndex));
|
|
432
|
-
if (clearThumb) actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIndex));
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
const resetCarouselMediaForAllCards = () => {
|
|
436
|
-
setCarouselData((prev = []) => {
|
|
437
|
-
const updated = cloneDeep(prev);
|
|
438
|
-
updated.forEach((card) => {
|
|
439
|
-
if (!card) return;
|
|
440
|
-
card.imageSrc = '';
|
|
441
|
-
card.videoAsset = {};
|
|
442
|
-
card.thumbnailSrc = '';
|
|
443
|
-
});
|
|
444
|
-
return updated;
|
|
445
|
-
});
|
|
446
|
-
(carouselData || []).forEach((_, idx) => {
|
|
447
|
-
actions.clearRcsMediaAsset(getCarouselImageAssetIndex(idx));
|
|
448
|
-
actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(idx));
|
|
449
|
-
actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(idx));
|
|
450
|
-
});
|
|
451
|
-
// Force tab panes to remount after global reset (some tab implementations cache inactive panes)
|
|
452
|
-
setCarouselResetNonce((n) => n + 1);
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
const RCS_CAROUSEL_INITIAL_CARD = {
|
|
456
|
-
title: '',
|
|
457
|
-
description: '',
|
|
458
|
-
mediaType: RCS_MEDIA_TYPES.IMAGE, // per-card
|
|
459
|
-
imageSrc: '',
|
|
460
|
-
videoAsset: {}, // CapVideoUpload object shape
|
|
461
|
-
thumbnailSrc: '',
|
|
462
|
-
suggestions: [],
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
const RCS_CAROUSEL_INITIAL_FIRST_CARD = {
|
|
466
|
-
...RCS_CAROUSEL_INITIAL_CARD,
|
|
467
|
-
suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const ensureFirstCardDefaultPhoneSuggestions = (cards) => {
|
|
471
|
-
const next = cloneDeep(cards || []);
|
|
472
|
-
if (next.length === 0) return next;
|
|
473
|
-
const s = next[0].suggestions;
|
|
474
|
-
if (!Array.isArray(s) || s.length === 0) {
|
|
475
|
-
next[0] = {
|
|
476
|
-
...next[0],
|
|
477
|
-
suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
|
|
478
|
-
};
|
|
479
|
-
}
|
|
480
|
-
return next;
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
// Always use functional updates: image upload completes in CapImageUpload's useEffect and calls
|
|
484
|
-
// updateImageSrc → this handler. If we cloned carouselData from render, we'd overwrite title/body/
|
|
485
|
-
// suggestions typed since the last commit (stale closure).
|
|
486
|
-
const handleCarouselValueChange = (carouselIndex, fields) => {
|
|
487
|
-
setCarouselData((prev = []) => {
|
|
488
|
-
const updated = cloneDeep(prev);
|
|
489
|
-
if (!updated[carouselIndex]) return prev;
|
|
490
|
-
fields.forEach(({ fieldName, value }) => {
|
|
491
|
-
updated[carouselIndex][fieldName] = value;
|
|
492
|
-
});
|
|
493
|
-
return updated;
|
|
494
|
-
});
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
const updateCarouselErrors = (carouselIndex, patch) => {
|
|
498
|
-
setCarouselErrors((prev = []) => {
|
|
499
|
-
const next = Array.isArray(prev) ? [...prev] : [];
|
|
500
|
-
next[carouselIndex] = { ...(next[carouselIndex] || {}), ...patch };
|
|
501
|
-
return next;
|
|
502
|
-
});
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
const deleteCarouselCard = (index) => {
|
|
506
|
-
let nextIdx = 0;
|
|
507
|
-
flushSync(() => {
|
|
508
|
-
setCarouselData((prev = []) => {
|
|
509
|
-
const updated = cloneDeep(prev);
|
|
510
|
-
if (index < 0 || index >= updated.length) return updated;
|
|
511
|
-
updated.splice(index, 1);
|
|
512
|
-
nextIdx = Math.max(0, Math.min(index - 1, updated.length - 1));
|
|
513
|
-
return ensureFirstCardDefaultPhoneSuggestions(updated);
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
setCarouselErrors((prev = []) => {
|
|
517
|
-
const next = Array.isArray(prev) ? [...prev] : [];
|
|
518
|
-
next.splice(index, 1);
|
|
519
|
-
return next;
|
|
520
|
-
});
|
|
521
|
-
setActiveCarouselIndex(`${nextIdx}`);
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
const carouselButtonTextHasForbiddenChars = (value) => {
|
|
525
|
-
if (!value) return false;
|
|
526
|
-
if (value.includes('[') || value.includes(']')) return true;
|
|
527
|
-
const withoutValidVariables = value.replace(/\{\{[^}]*\}\}/g, '');
|
|
528
|
-
if (withoutValidVariables.includes('{') || withoutValidVariables.includes('}')) return true;
|
|
529
|
-
return false;
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
const isCompleteSavedCarouselSuggestion = (s) => {
|
|
533
|
-
if (!s || !s.isSaved) return false;
|
|
534
|
-
const text = (s.text || '').trim();
|
|
535
|
-
if (!text || !isValidText(text) || carouselButtonTextHasForbiddenChars(text)) return false;
|
|
536
|
-
if (s.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
|
|
537
|
-
return String(s.phoneNumber || '').length >= 5;
|
|
538
|
-
}
|
|
539
|
-
if (s.type === RCS_BUTTON_TYPES.CTA) {
|
|
540
|
-
const url = String(s.url || '').trim();
|
|
541
|
-
if (!url || url.length > URL_MAX_LENGTH) return false;
|
|
542
|
-
const subtype = s.urlType || RCS_CTA_URL_TYPE.STATIC;
|
|
543
|
-
if (subtype === RCS_CTA_URL_TYPE.DYNAMIC) {
|
|
544
|
-
return true;
|
|
545
|
-
}
|
|
546
|
-
if (!isUrl(url)) return false;
|
|
547
|
-
const varMatches = url.match(invalidVarRegex);
|
|
548
|
-
return !(varMatches && varMatches.length > 0);
|
|
549
|
-
}
|
|
550
|
-
if (s.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
|
|
551
|
-
return true;
|
|
552
|
-
}
|
|
553
|
-
return false;
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
const isCarouselCardValid = (card, cardIndex) => {
|
|
557
|
-
if (!card) return false;
|
|
558
|
-
if (!card.title || !card.title.trim()) return false;
|
|
559
|
-
if ((card.title || '').length > TEMPLATE_TITLE_MAX_LENGTH) return false;
|
|
560
|
-
if (!card.description || !card.description.trim()) return false;
|
|
561
|
-
if ((card.description || '').length > RCS_RICH_CARD_MAX_LENGTH) return false;
|
|
562
|
-
let mediaOk = false;
|
|
563
|
-
if (card.mediaType === RCS_MEDIA_TYPES.IMAGE) {
|
|
564
|
-
mediaOk = !!(card.imageSrc && String(card.imageSrc).trim());
|
|
565
|
-
} else if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
|
|
566
|
-
const hasVideo = !!(card.videoAsset && card.videoAsset.videoSrc && String(card.videoAsset.videoSrc).trim());
|
|
567
|
-
const hasThumb = !!(card.thumbnailSrc && String(card.thumbnailSrc).trim());
|
|
568
|
-
mediaOk = hasVideo && hasThumb;
|
|
569
|
-
} else {
|
|
570
|
-
return false;
|
|
571
|
-
}
|
|
572
|
-
if (!mediaOk) return false;
|
|
573
|
-
if (cardIndex === 0) {
|
|
574
|
-
const sugg = Array.isArray(card.suggestions) ? card.suggestions : [];
|
|
575
|
-
if (!sugg.some(isCompleteSavedCarouselSuggestion)) return false;
|
|
576
|
-
}
|
|
577
|
-
return true;
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
const checkDisableAddCarouselButton = () => {
|
|
581
|
-
const idx = parseInt(activeCarouselIndex, 10);
|
|
582
|
-
const activeCard = carouselData?.[idx];
|
|
583
|
-
return !isCarouselCardValid(activeCard, idx);
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
const addCarouselCard = () => {
|
|
587
|
-
let newIndex = 0;
|
|
588
|
-
flushSync(() => {
|
|
589
|
-
setCarouselData((prev = []) => {
|
|
590
|
-
const updated = cloneDeep(prev);
|
|
591
|
-
updated.push(cloneDeep(RCS_CAROUSEL_INITIAL_CARD));
|
|
592
|
-
newIndex = updated.length - 1;
|
|
593
|
-
return updated;
|
|
594
|
-
});
|
|
595
|
-
});
|
|
596
|
-
setCarouselErrors((prev = []) => ([...(Array.isArray(prev) ? prev : []), {}]));
|
|
597
|
-
setActiveCarouselIndex(`${newIndex}`);
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
// Initialize carousel data when switching to carousel type
|
|
601
|
-
useEffect(() => {
|
|
602
|
-
if (!isCarouselType) return;
|
|
603
|
-
if (!carouselData || carouselData.length === 0) {
|
|
604
|
-
setCarouselData([cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)]);
|
|
605
|
-
setCarouselErrors([{}]);
|
|
606
|
-
setActiveCarouselIndex('0');
|
|
607
|
-
}
|
|
608
|
-
}, [isCarouselType]);
|
|
609
|
-
|
|
610
|
-
// keep derived carousel key in sync
|
|
611
|
-
useEffect(() => {
|
|
612
|
-
if (!isCarouselType) return;
|
|
613
|
-
if (!selectedCarouselHeight || !selectedCarouselWidth) return;
|
|
614
|
-
// Required format: HEIGHT_WIDTH
|
|
615
|
-
setSelectedCarousel(`${selectedCarouselHeight}_${selectedCarouselWidth}`);
|
|
616
|
-
}, [isCarouselType, selectedCarouselHeight, selectedCarouselWidth]);
|
|
617
|
-
|
|
618
|
-
const renderCarouselDimensionSelection = () => {
|
|
619
|
-
if (!isCarouselType) return null;
|
|
620
|
-
return (
|
|
621
|
-
<CapRow className="rcs-carousel-dimension-section">
|
|
622
|
-
<CapRow gutter={16} className="rcs-carousel-dimension-row">
|
|
623
|
-
<CapColumn span={12}>
|
|
624
|
-
<CapHeading type="h4" className="rcs-carousel-dimension-label">Card height</CapHeading>
|
|
625
|
-
{isEditFlow ? (
|
|
626
|
-
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
627
|
-
{CAROUSEL_HEIGHT_OPTIONS.find((option) => option.value === selectedCarouselHeight)?.label || '-'}
|
|
628
|
-
</CapHeading>
|
|
629
|
-
) : (
|
|
630
|
-
<CapSelect
|
|
631
|
-
id="rcs-carousel-height-select"
|
|
632
|
-
value={selectedCarouselHeight}
|
|
633
|
-
onChange={(val) => {
|
|
634
|
-
// Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
|
|
635
|
-
resetCarouselMediaForAllCards();
|
|
636
|
-
setSelectedCarouselHeight(val);
|
|
637
|
-
}}
|
|
638
|
-
options={CAROUSEL_HEIGHT_OPTIONS}
|
|
639
|
-
disabled={!isFullMode}
|
|
640
|
-
/>
|
|
641
|
-
)}
|
|
642
|
-
</CapColumn>
|
|
643
|
-
<CapColumn span={12}>
|
|
644
|
-
<CapHeading type="h4" className="rcs-carousel-dimension-label">Card width</CapHeading>
|
|
645
|
-
{isEditFlow ? (
|
|
646
|
-
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
647
|
-
{CAROUSEL_WIDTH_OPTIONS.find((option) => option.value === selectedCarouselWidth)?.label || '-'}
|
|
648
|
-
</CapHeading>
|
|
649
|
-
) : (
|
|
650
|
-
<CapSelect
|
|
651
|
-
id="rcs-carousel-width-select"
|
|
652
|
-
value={selectedCarouselWidth}
|
|
653
|
-
onChange={(val) => {
|
|
654
|
-
// Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
|
|
655
|
-
resetCarouselMediaForAllCards();
|
|
656
|
-
setSelectedCarouselWidth(val);
|
|
657
|
-
}}
|
|
658
|
-
options={CAROUSEL_WIDTH_OPTIONS}
|
|
659
|
-
disabled={!isFullMode}
|
|
660
|
-
/>
|
|
661
|
-
)}
|
|
662
|
-
</CapColumn>
|
|
663
|
-
</CapRow>
|
|
664
|
-
{!!selectedCarousel && (
|
|
665
|
-
<CapLabel type="label3" className="rcs-carousel-selected-dimension">
|
|
666
|
-
Selected: {selectedCarousel}
|
|
667
|
-
</CapLabel>
|
|
668
|
-
)}
|
|
669
|
-
</CapRow>
|
|
670
|
-
);
|
|
671
|
-
};
|
|
672
|
-
|
|
673
|
-
// Reuse rich-card buttons UI per carousel card
|
|
674
|
-
const renderButtonComponentForCarouselCard = (cardIndex) => {
|
|
675
|
-
const card = carouselData?.[cardIndex] || {};
|
|
676
|
-
const suggestionsForCard = card.suggestions || [];
|
|
677
|
-
return (
|
|
678
|
-
<>
|
|
679
|
-
<CapHeader
|
|
680
|
-
className="rcs-button-cta"
|
|
681
|
-
title={(
|
|
682
|
-
<CapRow type="flex">
|
|
683
|
-
<CapHeading type="h4">
|
|
684
|
-
{formatMessage(messages.btnLabel)}
|
|
685
|
-
</CapHeading>
|
|
686
|
-
</CapRow>
|
|
687
|
-
)}
|
|
688
|
-
description={!isEditFlow && (
|
|
689
|
-
<CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
|
|
690
|
-
)}
|
|
691
|
-
/>
|
|
692
|
-
<CapActionButton
|
|
693
|
-
buttonType={RCS_BUTTON_TYPES.NONE}
|
|
694
|
-
updateButtonChange={(data, btnIndex) => {
|
|
695
|
-
// Match existing behavior: allow CapActionButton to manage save gating.
|
|
696
|
-
const updated = cloneDeep(suggestionsForCard);
|
|
697
|
-
if (btnIndex === MAX_BUTTONS) {
|
|
698
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: data }]);
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
updated[btnIndex] = data;
|
|
702
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
|
|
703
|
-
}}
|
|
704
|
-
deleteButtonHandler={(btnIndex) => {
|
|
705
|
-
if (cardIndex === 0 && btnIndex === 0) {
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
const savedCount = (suggestionsForCard || []).filter((x) => x && x.isSaved).length;
|
|
709
|
-
const target = (suggestionsForCard || []).find((s) => s && s.index === btnIndex);
|
|
710
|
-
if (cardIndex === 0 && target?.isSaved && savedCount <= 1) {
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
const updated = cloneDeep(suggestionsForCard)
|
|
714
|
-
.filter((i) => i.index !== btnIndex)
|
|
715
|
-
.map((i, idx) => ({ ...i, index: idx }));
|
|
716
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
|
|
717
|
-
}}
|
|
718
|
-
suggestions={suggestionsForCard}
|
|
719
|
-
isEditFlow={isEditFlow}
|
|
720
|
-
isFullMode={isFullMode}
|
|
721
|
-
maxButtons={MAX_BUTTONS}
|
|
722
|
-
host={hostName}
|
|
723
|
-
minSavedSuggestions={cardIndex === 0 ? 1 : 0}
|
|
724
|
-
hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
|
|
725
|
-
/>
|
|
726
|
-
</>
|
|
727
|
-
);
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
const renderCarouselCardMedia = (cardIndex) => {
|
|
731
|
-
const card = carouselData?.[cardIndex] || {};
|
|
732
|
-
const dimKey = getCarouselDimensionKey();
|
|
733
|
-
|
|
734
|
-
if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
|
|
735
|
-
return (
|
|
736
|
-
<>
|
|
737
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Video</CapHeading>
|
|
738
|
-
<CapVideoUpload
|
|
739
|
-
index={getCarouselVideoAssetIndex(cardIndex)}
|
|
740
|
-
allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
|
|
741
|
-
videoSize={RCS_CAROUSEL_VIDEO_SIZE}
|
|
742
|
-
isFullMode={isFullMode}
|
|
743
|
-
uploadAsset={uploadRcsVideo}
|
|
744
|
-
uploadedAssetList={card.videoAsset || {}}
|
|
745
|
-
onVideoUploadUpdateAssestList={(_, val) => {
|
|
746
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'videoAsset', value: val }]);
|
|
747
|
-
}}
|
|
748
|
-
videoData={rcsData}
|
|
749
|
-
className="cap-custom-video-upload"
|
|
750
|
-
formClassName={"rcs-video-upload"}
|
|
751
|
-
channel={RCS}
|
|
752
|
-
errorMessage={formatMessage(messages.videoErrorMessage)}
|
|
753
|
-
showVideoNameAndDuration={false}
|
|
754
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
755
|
-
channelSpecificStyle={!isFullMode}
|
|
756
|
-
/>
|
|
757
|
-
|
|
758
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
759
|
-
<CapImageUpload
|
|
760
|
-
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
761
|
-
imgWidth={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.width}
|
|
762
|
-
imgHeight={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.height}
|
|
763
|
-
imgSize={RCS_THUMBNAIL_MAX_SIZE}
|
|
764
|
-
uploadAsset={uploadRcsImage}
|
|
765
|
-
isFullMode={isFullMode}
|
|
766
|
-
imageSrc={card.thumbnailSrc}
|
|
767
|
-
updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: val }])}
|
|
768
|
-
updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: '' }])}
|
|
769
|
-
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
770
|
-
index={getCarouselThumbnailAssetIndex(cardIndex)}
|
|
771
|
-
className="cap-custom-image-upload"
|
|
772
|
-
key={`rcs-carousel-thumb-${cardIndex}-${dimKey}`}
|
|
773
|
-
imageData={rcsData}
|
|
774
|
-
channel={RCS}
|
|
775
|
-
channelSpecificStyle={!isFullMode}
|
|
776
|
-
skipDimensionValidation={true}
|
|
777
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
778
|
-
disabled={isEditFlow || !isFullMode}
|
|
779
|
-
/>
|
|
780
|
-
</>
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// Default: IMAGE
|
|
785
|
-
return (
|
|
786
|
-
<>
|
|
787
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">{formatMessage(messages.mediaImage)}</CapHeading>
|
|
788
|
-
<CapImageUpload
|
|
789
|
-
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
790
|
-
imgWidth={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.width}
|
|
791
|
-
imgHeight={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.height}
|
|
792
|
-
imgSize={RCS_CAROUSEL_IMG_SIZE}
|
|
793
|
-
uploadAsset={uploadRcsImage}
|
|
794
|
-
isFullMode={isFullMode}
|
|
795
|
-
imageSrc={card.imageSrc}
|
|
796
|
-
updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: val }])}
|
|
797
|
-
updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: '' }])}
|
|
798
|
-
index={getCarouselImageAssetIndex(cardIndex)}
|
|
799
|
-
className="cap-custom-image-upload"
|
|
800
|
-
key={`rcs-carousel-image-${cardIndex}-${dimKey}`}
|
|
801
|
-
imageData={rcsData}
|
|
802
|
-
channel={RCS}
|
|
803
|
-
channelSpecificStyle={!isFullMode}
|
|
804
|
-
skipDimensionValidation={true}
|
|
805
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
806
|
-
disabled={isEditFlow || !isFullMode}
|
|
807
|
-
/>
|
|
808
|
-
</>
|
|
809
|
-
);
|
|
810
|
-
};
|
|
811
|
-
|
|
812
|
-
const renderCarouselCardButtons = (cardIndex) => {
|
|
813
|
-
return renderButtonComponentForCarouselCard(cardIndex);
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
const getCarouselTabPanes = () => {
|
|
817
|
-
return (carouselData || []).map((card, index) => {
|
|
818
|
-
return {
|
|
819
|
-
key: String(index),
|
|
820
|
-
tab: index + 1,
|
|
821
|
-
content: (
|
|
822
|
-
<CapCard
|
|
823
|
-
title={`Card ${index + 1}`}
|
|
824
|
-
extra={
|
|
825
|
-
!isEditFlow &&
|
|
826
|
-
(carouselData.length === 1 ? (
|
|
827
|
-
<CapTooltip title={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}>
|
|
828
|
-
<span className="button-disabled-tooltip-wrapper rcs-carousel-delete-tooltip-wrap">
|
|
829
|
-
<CapButton
|
|
830
|
-
className="rcs-carousel-card-delete"
|
|
831
|
-
type="flat"
|
|
832
|
-
onClick={() => deleteCarouselCard(index)}
|
|
833
|
-
disabled
|
|
834
|
-
aria-label={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}
|
|
835
|
-
>
|
|
836
|
-
<CapIcon type="delete" />
|
|
837
|
-
</CapButton>
|
|
838
|
-
</span>
|
|
839
|
-
</CapTooltip>
|
|
840
|
-
) : (
|
|
841
|
-
<CapButton
|
|
842
|
-
className="rcs-carousel-card-delete"
|
|
843
|
-
type="flat"
|
|
844
|
-
onClick={() => deleteCarouselCard(index)}
|
|
845
|
-
aria-label={formatMessage(globalMessages.delete)}
|
|
846
|
-
>
|
|
847
|
-
<CapIcon type="delete" />
|
|
848
|
-
</CapButton>
|
|
849
|
-
))
|
|
850
|
-
}
|
|
851
|
-
className="rcs-carousel-card"
|
|
852
|
-
>
|
|
853
|
-
{/* Media selection should be at top of card */}
|
|
854
|
-
{isEditFlow ? (
|
|
855
|
-
<div className="rcs-creative-name-readonly">
|
|
856
|
-
<CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
|
|
857
|
-
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
858
|
-
{mediaRadioOptions.find((option) => option.value === card.mediaType)?.label || '-'}
|
|
859
|
-
</CapHeading>
|
|
860
|
-
</div>
|
|
861
|
-
) : (
|
|
862
|
-
<CapRow className="rcs-carousel-media-selection">
|
|
863
|
-
<CapColumn className="rcs-carousel-media-selection-heading">
|
|
864
|
-
<CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
|
|
865
|
-
</CapColumn>
|
|
866
|
-
<CapColumn>
|
|
867
|
-
<CapRadioGroup
|
|
868
|
-
id={`rcs-carousel-media-radio-${index}`}
|
|
869
|
-
options={mediaRadioOptions}
|
|
870
|
-
value={card.mediaType}
|
|
871
|
-
onChange={({ target: { value } }) => {
|
|
872
|
-
// Reset media fields when switching type
|
|
873
|
-
if (value === RCS_MEDIA_TYPES.IMAGE) {
|
|
874
|
-
// Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
|
|
875
|
-
clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
|
|
876
|
-
handleCarouselValueChange(index, [
|
|
877
|
-
{ fieldName: 'mediaType', value },
|
|
878
|
-
{ fieldName: 'videoAsset', value: {} },
|
|
879
|
-
{ fieldName: 'thumbnailSrc', value: '' },
|
|
880
|
-
]);
|
|
881
|
-
} else {
|
|
882
|
-
// Switching to VIDEO: clear image upload so it doesn't auto-restore.
|
|
883
|
-
clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
|
|
884
|
-
handleCarouselValueChange(index, [
|
|
885
|
-
{ fieldName: 'mediaType', value },
|
|
886
|
-
{ fieldName: 'imageSrc', value: '' },
|
|
887
|
-
]);
|
|
888
|
-
}
|
|
889
|
-
}}
|
|
890
|
-
disabled={!isFullMode}
|
|
891
|
-
className="rcs-radio"
|
|
892
|
-
/>
|
|
893
|
-
</CapColumn>
|
|
894
|
-
</CapRow>
|
|
895
|
-
)}
|
|
896
|
-
|
|
897
|
-
<CapRow className={`rcs-carousel-media-upload ${isEditFlow ? 'rcs-carousel-media-upload--readonly-above' : ''}`}>
|
|
898
|
-
{renderCarouselCardMedia(index)}
|
|
899
|
-
</CapRow>
|
|
900
|
-
|
|
901
|
-
{/* Title after media */}
|
|
902
|
-
<CapRow className="rcs-carousel-card-row">
|
|
903
|
-
<CapHeader
|
|
904
|
-
className="rcs-template-title-label"
|
|
905
|
-
title={<CapHeading type="h4">Card title</CapHeading>}
|
|
906
|
-
suffix={
|
|
907
|
-
(isEditFlow || !isFullMode) ? (
|
|
908
|
-
<TagList
|
|
909
|
-
label={formatMessage(globalMessages.addLabels)}
|
|
910
|
-
onTagSelect={onCarouselTagSelect}
|
|
911
|
-
location={location}
|
|
912
|
-
tags={getRcsTags()}
|
|
913
|
-
onContextChange={handleOnTagsContextChange}
|
|
914
|
-
injectedTags={injectedTags || {}}
|
|
915
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
916
|
-
/>
|
|
917
|
-
) : (
|
|
918
|
-
<CapButton
|
|
919
|
-
data-testid={`rcs-carousel-title-add-var-${index}`}
|
|
920
|
-
type="flat"
|
|
921
|
-
isAddBtn
|
|
922
|
-
onClick={() => appendVarToCarouselField(index, 'title')}
|
|
923
|
-
disabled={!!carouselErrors?.[index]?.title}
|
|
924
|
-
>
|
|
925
|
-
{formatMessage(messages.addVar)}
|
|
926
|
-
</CapButton>
|
|
927
|
-
)
|
|
928
|
-
}
|
|
929
|
-
/>
|
|
930
|
-
<CapRow className="rcs_text_area_wrapper">
|
|
931
|
-
{(isEditFlow || !isFullMode) ? (
|
|
932
|
-
renderCarouselEditMessage(card.title || '')
|
|
933
|
-
) : (
|
|
934
|
-
<CapInput
|
|
935
|
-
value={card.title || ''}
|
|
936
|
-
placeholder={formatMessage(messages.templateTitlePlaceholder)}
|
|
937
|
-
onChange={({ target: { value } }) => {
|
|
938
|
-
let error = false;
|
|
939
|
-
if (value?.length > TEMPLATE_TITLE_MAX_LENGTH) {
|
|
940
|
-
error = formatMessage(messages.templateHeaderLengthError);
|
|
941
|
-
} else {
|
|
942
|
-
error = variableErrorHandling(value);
|
|
943
|
-
}
|
|
944
|
-
updateCarouselErrors(index, { title: error });
|
|
945
|
-
handleCarouselValueChange(index, [{ fieldName: 'title', value }]);
|
|
946
|
-
}}
|
|
947
|
-
disabled={isEditFlow || !isFullMode}
|
|
948
|
-
errorMessage={carouselErrors?.[index]?.title}
|
|
949
|
-
/>
|
|
950
|
-
)}
|
|
951
|
-
</CapRow>
|
|
952
|
-
</CapRow>
|
|
953
|
-
{!isEditFlow && (
|
|
954
|
-
<CapRow className="rcs-carousel-character-count-row">
|
|
955
|
-
{renderCarouselCharacterCount(
|
|
956
|
-
getCarouselTitleCharacterCount(index),
|
|
957
|
-
getTitleMaxLength(),
|
|
958
|
-
)}
|
|
959
|
-
</CapRow>
|
|
960
|
-
)}
|
|
961
|
-
|
|
962
|
-
{/* Description after title */}
|
|
963
|
-
<CapRow className="rcs-carousel-card-row">
|
|
964
|
-
<CapHeader
|
|
965
|
-
title={<CapHeading type="h4">Card body text</CapHeading>}
|
|
966
|
-
suffix={
|
|
967
|
-
(isEditFlow || !isFullMode) ? (
|
|
968
|
-
<TagList
|
|
969
|
-
label={formatMessage(globalMessages.addLabels)}
|
|
970
|
-
onTagSelect={onCarouselTagSelect}
|
|
971
|
-
location={location}
|
|
972
|
-
tags={getRcsTags()}
|
|
973
|
-
onContextChange={handleOnTagsContextChange}
|
|
974
|
-
injectedTags={injectedTags || {}}
|
|
975
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
976
|
-
/>
|
|
977
|
-
) : (
|
|
978
|
-
<CapButton
|
|
979
|
-
data-testid={`rcs-carousel-desc-add-var-${index}`}
|
|
980
|
-
type="flat"
|
|
981
|
-
isAddBtn
|
|
982
|
-
onClick={() => appendVarToCarouselField(index, 'description')}
|
|
983
|
-
disabled={!!carouselErrors?.[index]?.description}
|
|
984
|
-
>
|
|
985
|
-
{formatMessage(messages.addVar)}
|
|
986
|
-
</CapButton>
|
|
987
|
-
)
|
|
988
|
-
}
|
|
989
|
-
/>
|
|
990
|
-
<CapRow className="rcs_text_area_wrapper">
|
|
991
|
-
{(isEditFlow || !isFullMode) ? (
|
|
992
|
-
renderCarouselEditMessage(card.description || '')
|
|
993
|
-
) : (
|
|
994
|
-
<TextArea
|
|
995
|
-
autosize={{ minRows: 3, maxRows: 5 }}
|
|
996
|
-
value={card.description || ''}
|
|
997
|
-
placeholder={formatMessage(messages.templateDescPlaceholder)}
|
|
998
|
-
onChange={({ target: { value } }) => {
|
|
999
|
-
let error = false;
|
|
1000
|
-
if (value?.length > RCS_RICH_CARD_MAX_LENGTH) {
|
|
1001
|
-
error = formatMessage(messages.templateMessageLengthError);
|
|
1002
|
-
} else {
|
|
1003
|
-
error = variableErrorHandling(value);
|
|
1004
|
-
}
|
|
1005
|
-
updateCarouselErrors(index, { description: error });
|
|
1006
|
-
handleCarouselValueChange(index, [{ fieldName: 'description', value }]);
|
|
1007
|
-
}}
|
|
1008
|
-
disabled={isEditFlow || !isFullMode}
|
|
1009
|
-
errorMessage={
|
|
1010
|
-
carouselErrors?.[index]?.description && (
|
|
1011
|
-
<CapError className="rcs-template-message-error">
|
|
1012
|
-
{carouselErrors[index].description}
|
|
1013
|
-
</CapError>
|
|
1014
|
-
)
|
|
1015
|
-
}
|
|
1016
|
-
/>
|
|
1017
|
-
)}
|
|
1018
|
-
</CapRow>
|
|
1019
|
-
</CapRow>
|
|
1020
|
-
{!isEditFlow && (
|
|
1021
|
-
<CapRow className="rcs-carousel-character-count-row">
|
|
1022
|
-
{renderCarouselCharacterCount(
|
|
1023
|
-
getCarouselDescriptionCharacterCount(index),
|
|
1024
|
-
getDescriptionMaxLength(),
|
|
1025
|
-
)}
|
|
1026
|
-
</CapRow>
|
|
1027
|
-
)}
|
|
1028
|
-
|
|
1029
|
-
<CapDivider className="rcs-carousel-card-divider" />
|
|
1030
|
-
{renderCarouselCardButtons(index)}
|
|
1031
|
-
</CapCard>
|
|
1032
|
-
),
|
|
1033
|
-
};
|
|
1034
|
-
});
|
|
1035
|
-
};
|
|
1036
|
-
|
|
1037
|
-
const renderCarouselSection = () => {
|
|
1038
|
-
if (!isCarouselType) return null;
|
|
1039
|
-
|
|
1040
|
-
const isMaxCarouselCardsReached = MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0);
|
|
1041
|
-
const addCardTooltipTitle = isMaxCarouselCardsReached
|
|
1042
|
-
? formatMessage(messages.rcsCarouselMaxCardTooltip, { max: MAX_RCS_CAROUSEL_ALLOWED })
|
|
1043
|
-
// "Set up the first card" only makes sense while creating a new template —
|
|
1044
|
-
// in edit flow the cards already exist, so don't show it there.
|
|
1045
|
-
: (!isEditFlow && checkDisableAddCarouselButton() ? formatMessage(messages.rcsCarouselAddCardDisabledTooltip) : '');
|
|
1046
|
-
|
|
1047
|
-
// No "+" in edit flow (cards can't be added there) — and with no "+", the
|
|
1048
|
-
// divider before it has nothing to separate, so skip both together instead
|
|
1049
|
-
// of rendering a dangling divider.
|
|
1050
|
-
const operations = isEditFlow ? null : (
|
|
1051
|
-
<>
|
|
1052
|
-
<CapDivider type="vertical" />
|
|
1053
|
-
<CapTooltip title={addCardTooltipTitle}>
|
|
1054
|
-
<span className="button-disabled-tooltip-wrapper">
|
|
1055
|
-
<CapButton
|
|
1056
|
-
onClick={addCarouselCard}
|
|
1057
|
-
type="flat"
|
|
1058
|
-
className="add-carousel-content-button"
|
|
1059
|
-
disabled={
|
|
1060
|
-
isEditFlow ||
|
|
1061
|
-
!isFullMode ||
|
|
1062
|
-
isMaxCarouselCardsReached ||
|
|
1063
|
-
checkDisableAddCarouselButton()
|
|
1064
|
-
}
|
|
1065
|
-
>
|
|
1066
|
-
<CapIcon type="plus" />
|
|
1067
|
-
</CapButton>
|
|
1068
|
-
</span>
|
|
1069
|
-
</CapTooltip>
|
|
1070
|
-
</>
|
|
1071
|
-
);
|
|
1072
|
-
|
|
1073
|
-
return (
|
|
1074
|
-
<CapRow className={`rcs-carousel-section ${isEditFlow ? 'rcs-carousel-section--readonly-above' : ''}`}>
|
|
1075
|
-
{renderCarouselDimensionSelection()}
|
|
1076
|
-
<CapRow className="rcs-carousel-tab">
|
|
1077
|
-
<CapTab
|
|
1078
|
-
key={`rcs-carousel-tab-${carouselResetNonce}`}
|
|
1079
|
-
defaultActiveKey="0"
|
|
1080
|
-
activeKey={activeCarouselIndex}
|
|
1081
|
-
tabBarExtraContent={operations}
|
|
1082
|
-
onChange={(key) => {
|
|
1083
|
-
setActiveCarouselIndex(`${key}`);
|
|
1084
|
-
// reset focused var when switching cards (as per requirement)
|
|
1085
|
-
setCarouselFocusedVarId('');
|
|
1086
|
-
}}
|
|
1087
|
-
panes={getCarouselTabPanes()}
|
|
1088
|
-
/>
|
|
1089
|
-
</CapRow>
|
|
1090
|
-
</CapRow>
|
|
1091
|
-
);
|
|
1092
|
-
};
|
|
1093
325
|
|
|
1094
326
|
const mediaRadioOptions = [
|
|
1095
327
|
{
|
|
@@ -1098,16 +330,11 @@ export const Rcs = (props) => {
|
|
|
1098
330
|
},
|
|
1099
331
|
{
|
|
1100
332
|
value: RCS_MEDIA_TYPES.VIDEO,
|
|
1101
|
-
label: formatMessage(
|
|
1102
|
-
templateType === contentType.carousel
|
|
1103
|
-
? messages.carouselMediaVideoOption
|
|
1104
|
-
: messages.mediaVideo,
|
|
1105
|
-
),
|
|
333
|
+
label: formatMessage(messages.mediaVideo),
|
|
1106
334
|
},
|
|
1107
335
|
];
|
|
1108
336
|
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
1109
337
|
|
|
1110
|
-
|
|
1111
338
|
const updateButtonChange = (data, index) => {
|
|
1112
339
|
if (data && data.text) {
|
|
1113
340
|
const forbiddenError = forbiddenCharactersValidation(data.text);
|
|
@@ -1144,9 +371,7 @@ export const Rcs = (props) => {
|
|
|
1144
371
|
if (isFullMode) return;
|
|
1145
372
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
1146
373
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1147
|
-
const
|
|
1148
|
-
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
1149
|
-
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
374
|
+
const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
|
|
1150
375
|
if (!resolved) {
|
|
1151
376
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
1152
377
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -1156,7 +381,7 @@ export const Rcs = (props) => {
|
|
|
1156
381
|
let contentForValidation = resolved;
|
|
1157
382
|
const placeholderTokens = templateStr.match(rcsVarRegex) || [];
|
|
1158
383
|
placeholderTokens.forEach((t) => {
|
|
1159
|
-
const escaped = t.replace(
|
|
384
|
+
const escaped = t.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1160
385
|
contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
|
|
1161
386
|
});
|
|
1162
387
|
if (!contentForValidation.trim()) {
|
|
@@ -1173,16 +398,10 @@ export const Rcs = (props) => {
|
|
|
1173
398
|
tagModule: getDefaultTags,
|
|
1174
399
|
isFullMode,
|
|
1175
400
|
}) || {};
|
|
1176
|
-
|
|
1177
|
-
validationResponse?.
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1181
|
-
unsupportedTags: validationResponse.unsupportedTags,
|
|
1182
|
-
})) ||
|
|
1183
|
-
(validationResponse.isBraceError &&
|
|
1184
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
1185
|
-
false;
|
|
401
|
+
const errorMsg =
|
|
402
|
+
(validationResponse?.isBraceError &&
|
|
403
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
404
|
+
false;
|
|
1186
405
|
if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
|
|
1187
406
|
if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
|
|
1188
407
|
};
|
|
@@ -1195,98 +414,15 @@ export const Rcs = (props) => {
|
|
|
1195
414
|
validateResolvedTagsForType(MESSAGE_TEXT);
|
|
1196
415
|
}, [cardVarMapped, templateDesc, tags, injectedTags, loadingTags]);
|
|
1197
416
|
|
|
1198
|
-
useEffect(() => {
|
|
1199
|
-
if (isFullMode || !isCarouselType) return;
|
|
1200
|
-
if (loadingTags || !tags || tags.length === 0) return;
|
|
1201
|
-
(carouselData || []).forEach((card, idx) => {
|
|
1202
|
-
['title', 'description'].forEach((field) => {
|
|
1203
|
-
const templateStr = card?.[field] || '';
|
|
1204
|
-
if (!templateStr) return;
|
|
1205
|
-
const resolved = resolveTemplateWithMap(templateStr);
|
|
1206
|
-
if (!resolved) {
|
|
1207
|
-
updateCarouselErrors(idx, { [field]: false });
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
let contentForValidation = resolved;
|
|
1211
|
-
const placeholderTokens = templateStr.match(rcsVarRegex) || [];
|
|
1212
|
-
placeholderTokens.forEach((t) => {
|
|
1213
|
-
const escaped = t.replace(REGEX_SPECIAL_CHARS_ESCAPE_PATTERN, '\\$&');
|
|
1214
|
-
contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
|
|
1215
|
-
});
|
|
1216
|
-
if (!contentForValidation.trim()) {
|
|
1217
|
-
updateCarouselErrors(idx, { [field]: false });
|
|
1218
|
-
return;
|
|
1219
|
-
}
|
|
1220
|
-
const validationResponse = validateTags({
|
|
1221
|
-
content: contentForValidation,
|
|
1222
|
-
tagsParam: tags,
|
|
1223
|
-
injectedTagsParams: injectedTags,
|
|
1224
|
-
location,
|
|
1225
|
-
tagModule: getDefaultTags,
|
|
1226
|
-
eventContextTags,
|
|
1227
|
-
isFullMode,
|
|
1228
|
-
}) || {};
|
|
1229
|
-
const errorMsg =
|
|
1230
|
-
(validationResponse?.unsupportedTags?.length > 0 &&
|
|
1231
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1232
|
-
unsupportedTags: validationResponse.unsupportedTags,
|
|
1233
|
-
})) ||
|
|
1234
|
-
(validationResponse.isBraceError &&
|
|
1235
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
1236
|
-
false;
|
|
1237
|
-
updateCarouselErrors(idx, { [field]: errorMsg });
|
|
1238
|
-
});
|
|
1239
|
-
});
|
|
1240
|
-
}, [cardVarMapped, carouselData, tags, injectedTags, loadingTags, isCarouselType]);
|
|
1241
|
-
|
|
1242
417
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
1243
418
|
|
|
1244
|
-
const
|
|
1245
|
-
|
|
1246
|
-
/** Same `{{tag}}` in both title and description must not share one semantic map entry. */
|
|
1247
|
-
const rcsSpanningSemanticVarNames = useMemo(
|
|
1248
|
-
() => getRcsSemanticVarNamesSpanningTitleAndDesc(templateTitle, templateDesc, rcsVarRegex),
|
|
1249
|
-
[templateTitle, templateDesc],
|
|
1250
|
-
);
|
|
1251
|
-
|
|
1252
|
-
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
1253
|
-
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
1254
|
-
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
1255
|
-
const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
1256
|
-
const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
1257
|
-
let varOrdinal = 0;
|
|
1258
|
-
for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
|
|
1259
|
-
const segmentToken = templateSegments[segmentIndexInField];
|
|
1260
|
-
if (rcsVarTestRegex.test(segmentToken)) {
|
|
1261
|
-
if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
|
|
1262
|
-
return offset + varOrdinal;
|
|
1263
|
-
}
|
|
1264
|
-
varOrdinal += 1;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
return null;
|
|
1268
|
-
};
|
|
1269
|
-
|
|
1270
|
-
/**
|
|
1271
|
-
* Master-branch resolve: uses numeric slot keys + semantic spanning detection for correct
|
|
1272
|
-
* multi-field variable resolution.
|
|
1273
|
-
*/
|
|
1274
|
-
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
419
|
+
const resolveTemplateWithMap = (str = '') => {
|
|
1275
420
|
if (!str) return '';
|
|
1276
|
-
const arr =
|
|
1277
|
-
let varOrdinal = 0;
|
|
421
|
+
const arr = splitTemplateVarString(str);
|
|
1278
422
|
return arr.map((elem) => {
|
|
1279
423
|
if (rcsVarTestRegex.test(elem)) {
|
|
1280
424
|
const key = getVarNameFromToken(elem);
|
|
1281
|
-
const
|
|
1282
|
-
varOrdinal += 1;
|
|
1283
|
-
const v = resolveCardVarMappedSlotValue(
|
|
1284
|
-
cardVarMapped,
|
|
1285
|
-
key,
|
|
1286
|
-
globalSlot,
|
|
1287
|
-
isEditLike,
|
|
1288
|
-
rcsSpanningSemanticVarNames.has(key),
|
|
1289
|
-
);
|
|
425
|
+
const v = cardVarMapped?.[key];
|
|
1290
426
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
1291
427
|
return String(v);
|
|
1292
428
|
}
|
|
@@ -1294,145 +430,39 @@ export const Rcs = (props) => {
|
|
|
1294
430
|
}).join('');
|
|
1295
431
|
};
|
|
1296
432
|
|
|
1297
|
-
const buildCarouselCardsForPreview = (cards = []) => {
|
|
1298
|
-
// Track cumulative variable slot index across all cards so resolveTemplateWithMap
|
|
1299
|
-
// uses the correct globalSlot → slotKey for each token (e.g. card 1's {{3}} needs
|
|
1300
|
-
// slotOffset=2 so slotKey becomes "3", not "1").
|
|
1301
|
-
let slotOffset = 0;
|
|
1302
|
-
return (cards || []).map((card = {}) => {
|
|
1303
|
-
const rawTitle = card.title || '';
|
|
1304
|
-
const rawDesc = card.description || '';
|
|
1305
|
-
const titleVarCount = (rawTitle.match(rcsVarRegex) || []).length;
|
|
1306
|
-
const descVarCount = (rawDesc.match(rcsVarRegex) || []).length;
|
|
1307
|
-
const resolvedTitle = !isFullMode
|
|
1308
|
-
? resolveTemplateWithMap(rawTitle, slotOffset)
|
|
1309
|
-
: rawTitle;
|
|
1310
|
-
const resolvedDesc = !isFullMode
|
|
1311
|
-
? resolveTemplateWithMap(rawDesc, slotOffset + titleVarCount)
|
|
1312
|
-
: rawDesc;
|
|
1313
|
-
if (!isFullMode) {
|
|
1314
|
-
slotOffset += titleVarCount + descVarCount;
|
|
1315
|
-
}
|
|
1316
|
-
const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
|
|
1317
|
-
const videoSrc = card.videoAsset?.videoSrc || '';
|
|
1318
|
-
return {
|
|
1319
|
-
mediaType: (card.mediaType || '').toLowerCase(),
|
|
1320
|
-
imageSrc: card.imageSrc || '',
|
|
1321
|
-
videoSrc,
|
|
1322
|
-
videoPreviewImg: videoThumb,
|
|
1323
|
-
title: resolvedTitle,
|
|
1324
|
-
bodyText: resolvedDesc,
|
|
1325
|
-
suggestions: card.suggestions || [],
|
|
1326
|
-
};
|
|
1327
|
-
});
|
|
1328
|
-
};
|
|
1329
|
-
|
|
1330
|
-
/**
|
|
1331
|
-
* Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
|
|
1332
|
-
* (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
|
|
1333
|
-
* TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
|
|
1334
|
-
*/
|
|
1335
|
-
const getTemplateContent = useCallback(() => {
|
|
1336
|
-
if (templateType === contentType.carousel) {
|
|
1337
|
-
const carouselDimKey = getCarouselDimensionKey();
|
|
1338
|
-
const carouselImgDims =
|
|
1339
|
-
RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
|
|
1340
|
-
const carouselVidDims =
|
|
1341
|
-
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
|
|
1342
|
-
|| RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
|
|
1343
|
-
return {
|
|
1344
|
-
carouselData: buildCarouselCardsForPreview(carouselData),
|
|
1345
|
-
carouselPreviewDimensions: {
|
|
1346
|
-
imageWidth: carouselImgDims.width,
|
|
1347
|
-
imageHeight: carouselImgDims.height,
|
|
1348
|
-
videoThumbWidth: carouselVidDims.width,
|
|
1349
|
-
videoThumbHeight: carouselVidDims.height,
|
|
1350
|
-
},
|
|
1351
|
-
};
|
|
1352
|
-
}
|
|
1353
|
-
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
1354
|
-
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
1355
|
-
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
1356
|
-
|
|
1357
|
-
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1358
|
-
const titleVarCountForResolve = isMediaTypeText
|
|
1359
|
-
? 0
|
|
1360
|
-
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
1361
|
-
const resolvedTitle = isMediaTypeText
|
|
1362
|
-
? ''
|
|
1363
|
-
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
1364
|
-
const resolvedDesc = isSlotMappingMode
|
|
1365
|
-
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
1366
|
-
: templateDesc;
|
|
1367
|
-
|
|
1368
|
-
const mediaPreview = {};
|
|
1369
|
-
if (isMediaTypeImage && rcsImageSrc) {
|
|
1370
|
-
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
1371
|
-
}
|
|
1372
|
-
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
1373
|
-
if (rcsThumbnailSrc) {
|
|
1374
|
-
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
1375
|
-
} else if (rcsVideoSrc?.videoSrc) {
|
|
1376
|
-
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
1377
|
-
}
|
|
1378
|
-
if (rcsThumbnailSrc) {
|
|
1379
|
-
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
const contentObj = {
|
|
1384
|
-
templateHeader: resolvedTitle,
|
|
1385
|
-
templateMessage: resolvedDesc,
|
|
1386
|
-
...mediaPreview,
|
|
1387
|
-
...(suggestions.length > 0 && {
|
|
1388
|
-
suggestions: suggestions,
|
|
1389
|
-
}),
|
|
1390
|
-
};
|
|
1391
433
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
434
|
+
useEffect(() => {
|
|
435
|
+
if (isFullMode || isEditFlow) return;
|
|
436
|
+
const tokens = [
|
|
437
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
438
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
439
|
+
];
|
|
440
|
+
if (!tokens.length) return;
|
|
441
|
+
setCardVarMapped((prev) => {
|
|
442
|
+
const next = { ...(prev || {}) };
|
|
443
|
+
let changed = false;
|
|
444
|
+
tokens.forEach((t) => {
|
|
445
|
+
const name = getVarNameFromToken(t);
|
|
446
|
+
if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
|
|
447
|
+
next[name] = '';
|
|
448
|
+
changed = true;
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
return changed ? next : prev;
|
|
452
|
+
});
|
|
453
|
+
}, [isFullMode, templateTitle, templateDesc]);
|
|
1410
454
|
|
|
1411
|
-
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
1412
455
|
|
|
456
|
+
const RcsLabel = styled.div`
|
|
457
|
+
display: flex;
|
|
458
|
+
margin-top: 20px;
|
|
459
|
+
`;
|
|
1413
460
|
const paramObj = params || {};
|
|
1414
461
|
useEffect(() => {
|
|
1415
462
|
const { id } = paramObj;
|
|
1416
463
|
if (id && isFullMode) {
|
|
1417
464
|
setSpin(true);
|
|
1418
465
|
actions.getTemplateDetails(id, setSpin);
|
|
1419
|
-
} else if (!id && isFullMode) {
|
|
1420
|
-
// Create New: clear standalone media and ALL possible carousel card Redux slots.
|
|
1421
|
-
// Redux persists across mounts so we must clear unconditionally (carouselData may be [] on fresh mount).
|
|
1422
|
-
updateRcsImageSrc('');
|
|
1423
|
-
setRcsVideoSrc({});
|
|
1424
|
-
setRcsThumbnailSrc('');
|
|
1425
|
-
setAssetList({});
|
|
1426
|
-
setEditFlow(false);
|
|
1427
|
-
actions.clearRcsMediaAsset(0);
|
|
1428
|
-
actions.clearRcsMediaAsset(1);
|
|
1429
|
-
for (let cardIdx = 0; cardIdx < MAX_RCS_CAROUSEL_ALLOWED; cardIdx += 1) {
|
|
1430
|
-
actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIdx));
|
|
1431
|
-
actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIdx));
|
|
1432
|
-
actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIdx));
|
|
1433
|
-
}
|
|
1434
|
-
setCarouselData([]);
|
|
1435
|
-
setCarouselErrors([]);
|
|
1436
466
|
}
|
|
1437
467
|
return () => {
|
|
1438
468
|
actions.clearEditResponse();
|
|
@@ -1440,68 +470,59 @@ export const Rcs = (props) => {
|
|
|
1440
470
|
}, [paramObj.id]);
|
|
1441
471
|
|
|
1442
472
|
useEffect(() => {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
|
|
473
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
1446
474
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
const nextVarMap = {};
|
|
1454
|
-
let varOrdinal = 0;
|
|
1455
|
-
arr.forEach((elem, idx) => {
|
|
1456
|
-
// Mustache tokens: {{1}}, {{user_name}}, {{tag.FORMAT_1}}, etc.
|
|
1457
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
1458
|
-
const id = `${elem}_${idx}`;
|
|
1459
|
-
const varName = getVarNameFromToken(elem);
|
|
1460
|
-
const globalSlot = slotOffset + varOrdinal;
|
|
1461
|
-
varOrdinal += 1;
|
|
1462
|
-
const mappedValue = resolveCardVarMappedSlotValue(
|
|
1463
|
-
cardVarMapped,
|
|
1464
|
-
varName,
|
|
1465
|
-
globalSlot,
|
|
1466
|
-
isEditLike,
|
|
1467
|
-
rcsSpanningSemanticVarNames.has(varName),
|
|
1468
|
-
);
|
|
1469
|
-
nextVarMap[id] = mappedValue;
|
|
475
|
+
const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
|
|
476
|
+
const arr = splitTemplateVarString(targetString);
|
|
477
|
+
if (!arr?.length) {
|
|
478
|
+
setVarMap({});
|
|
479
|
+
setUpdated([]);
|
|
480
|
+
return;
|
|
1470
481
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
482
|
+
const nextVarMap = {};
|
|
483
|
+
const nextUpdated = [...arr];
|
|
484
|
+
arr.forEach((elem, idx) => {
|
|
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
|
+
};
|
|
1478
501
|
|
|
1479
|
-
|
|
1480
|
-
|
|
502
|
+
initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
|
|
503
|
+
initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
|
|
504
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
505
|
+
|
|
506
|
+
useEffect(() => {
|
|
507
|
+
if(!isEditFlow && isFullMode){
|
|
1481
508
|
setRcsVideoSrc({});
|
|
1482
509
|
updateRcsImageSrc('');
|
|
1483
|
-
|
|
510
|
+
setUpdateRcsImageSrc('');
|
|
511
|
+
updateRcsThumbnailSrc('');
|
|
1484
512
|
setAssetList({});
|
|
1485
|
-
|
|
1486
|
-
|
|
513
|
+
}
|
|
514
|
+
}, [templateMediaType]);
|
|
1487
515
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
cardContentFirst?.Status
|
|
1492
|
-
?? cardContentFirst?.status
|
|
1493
|
-
?? cardContentFirst?.approvalStatus
|
|
1494
|
-
?? '';
|
|
1495
|
-
const status = typeof raw === 'string' ? raw.trim() : String(raw);
|
|
1496
|
-
const n = status.toLowerCase();
|
|
1497
|
-
switch (n) {
|
|
516
|
+
const templateStatusHelper = (details) => {
|
|
517
|
+
const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
|
|
518
|
+
switch (status) {
|
|
1498
519
|
case RCS_STATUSES.approved:
|
|
1499
520
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
1500
521
|
break;
|
|
1501
522
|
case RCS_STATUSES.pending:
|
|
1502
523
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
1503
524
|
break;
|
|
1504
|
-
case RCS_STATUSES.awaitingApproval
|
|
525
|
+
case RCS_STATUSES.awaitingApproval:
|
|
1505
526
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
1506
527
|
break;
|
|
1507
528
|
case RCS_STATUSES.unavailable:
|
|
@@ -1510,9 +531,6 @@ export const Rcs = (props) => {
|
|
|
1510
531
|
case RCS_STATUSES.rejected:
|
|
1511
532
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
1512
533
|
break;
|
|
1513
|
-
case RCS_STATUSES.created:
|
|
1514
|
-
setTemplateStatus(RCS_STATUSES.created);
|
|
1515
|
-
break;
|
|
1516
534
|
default:
|
|
1517
535
|
setTemplateStatus(status);
|
|
1518
536
|
break;
|
|
@@ -1566,357 +584,48 @@ export const Rcs = (props) => {
|
|
|
1566
584
|
};
|
|
1567
585
|
|
|
1568
586
|
useEffect(() => {
|
|
1569
|
-
const details =
|
|
587
|
+
const details = isFullMode ? rcsData?.templateDetails : templateData;
|
|
1570
588
|
if (details && Object.keys(details).length > 0) {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
'versions.base.content.RCS.rcsContent.cardContent[0]',
|
|
1575
|
-
);
|
|
1576
|
-
const rcsContent = get(details, 'versions.base.content.RCS.rcsContent', {});
|
|
1577
|
-
const cardType = (rcsContent?.cardType || '').toString();
|
|
1578
|
-
|
|
1579
|
-
setEditFlow(true);
|
|
1580
|
-
setTemplateName(details?.name || details?.creativeName || '');
|
|
1581
|
-
|
|
1582
|
-
const cardFromTop = get(details, 'rcsContent.cardContent[0]');
|
|
1583
|
-
const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
|
|
1584
|
-
const cardVarMappedFromCardContent =
|
|
1585
|
-
card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
|
|
1586
|
-
? card0.cardVarMapped
|
|
1587
|
-
: {};
|
|
1588
|
-
const cardVarMappedFromRootMirror =
|
|
1589
|
-
details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
|
|
1590
|
-
? details.rcsCardVarMapped
|
|
1591
|
-
: {};
|
|
1592
|
-
// Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
|
|
1593
|
-
// nested versions.cardContent[0].cardVarMapped is dropped on reload.
|
|
1594
|
-
const mergedCardVarMappedFromPayload = {
|
|
1595
|
-
...cardVarMappedFromRootMirror,
|
|
1596
|
-
...cardVarMappedFromCardContent,
|
|
1597
|
-
};
|
|
1598
|
-
const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
|
|
1599
|
-
const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
|
|
1600
|
-
const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
|
|
1601
|
-
Object.keys(mergedCardVarMappedFromPayload)
|
|
1602
|
-
.sort()
|
|
1603
|
-
.reduce((accumulator, mapKey) => {
|
|
1604
|
-
accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
|
|
1605
|
-
return accumulator;
|
|
1606
|
-
}, {}),
|
|
1607
|
-
)}`;
|
|
1608
|
-
if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
|
|
1609
|
-
lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
|
|
1610
|
-
setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
|
|
1611
|
-
}
|
|
1612
|
-
const tokenListForMap = [
|
|
1613
|
-
...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
|
|
1614
|
-
...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
|
|
1615
|
-
];
|
|
1616
|
-
const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
1617
|
-
// Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
|
|
1618
|
-
// getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
|
|
1619
|
-
const cardVarMappedBeforeCoalesce = isFullMode
|
|
1620
|
-
? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
|
|
1621
|
-
: { ...mergedCardVarMappedFromPayload };
|
|
1622
|
-
const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
1623
|
-
cardVarMappedBeforeCoalesce,
|
|
1624
|
-
loadedTitleForMap,
|
|
1625
|
-
loadedDescForMap,
|
|
1626
|
-
rcsVarRegex,
|
|
1627
|
-
);
|
|
1628
|
-
const cardVarMappedAfterNumericSlotSync = !isFullMode
|
|
1629
|
-
? syncCardVarMappedSemanticsFromSlots(
|
|
1630
|
-
cardVarMappedAfterCoalesce,
|
|
1631
|
-
loadedTitleForMap,
|
|
1632
|
-
loadedDescForMap,
|
|
1633
|
-
rcsVarRegex,
|
|
1634
|
-
)
|
|
1635
|
-
: cardVarMappedAfterCoalesce;
|
|
1636
|
-
const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
|
|
1637
|
-
// Pre-populate variable/tag mappings while opening an existing template in edit flows
|
|
1638
|
-
setCardVarMapped((previousVarMapState) => {
|
|
1639
|
-
const previousVarMap = previousVarMapState ?? {};
|
|
1640
|
-
if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
|
|
1641
|
-
const previousVarMapKeys = Object.keys(previousVarMap);
|
|
1642
|
-
const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
|
|
1643
|
-
if (previousVarMapKeys.length === nextVarMapKeys.length) {
|
|
1644
|
-
const allSlotValuesMatchPrevious = previousVarMapKeys.every(
|
|
1645
|
-
(key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
|
|
1646
|
-
);
|
|
1647
|
-
if (allSlotValuesMatchPrevious) return previousVarMapState;
|
|
1648
|
-
}
|
|
1649
|
-
return hydratedCardVarMappedResult;
|
|
1650
|
-
});
|
|
1651
|
-
|
|
1652
|
-
if (cardType.toUpperCase() === contentType.carousel) {
|
|
1653
|
-
|
|
1654
|
-
setTemplateType(contentType.carousel);
|
|
1655
|
-
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1656
|
-
// Library/campaign payloads can store cardSettings/cardContent at the flat top-level
|
|
1657
|
-
// `rcsContent` instead of nested under versions.base.content.RCS (same fallback already
|
|
1658
|
-
// used for the rich_card media lookup below, line ~1793). Without it, cardWidth/height
|
|
1659
|
-
// silently fall back to SMALL/MEDIUM, mismatching the dimensions the thumbnail was
|
|
1660
|
-
// actually uploaded at and causing object-fit:cover to crop the preview image.
|
|
1661
|
-
const cardSettings = rcsContent?.cardSettings
|
|
1662
|
-
|| get(details, 'rcsContent.cardSettings', {});
|
|
1663
|
-
const cardWidth = cardSettings?.cardWidth || SMALL;
|
|
1664
|
-
setSelectedCarouselWidth(cardWidth);
|
|
1665
|
-
|
|
1666
|
-
const cards = Array.isArray(rcsContent?.cardContent)
|
|
1667
|
-
? rcsContent.cardContent
|
|
1668
|
-
: get(details, 'rcsContent.cardContent', []);
|
|
1669
|
-
const firstHeight = cards?.[0]?.media?.height || MEDIUM;
|
|
1670
|
-
setSelectedCarouselHeight(firstHeight);
|
|
1671
|
-
setSelectedCarousel(`${firstHeight}_${cardWidth}`);
|
|
1672
|
-
setActiveCarouselIndex('0');
|
|
1673
|
-
|
|
1674
|
-
const hydratedCards = cards.map((c = {}, idx) => {
|
|
1675
|
-
const mediaType = c.mediaType;
|
|
1676
|
-
const media = c.media || {};
|
|
1677
|
-
const mediaUrl = media.mediaUrl || '';
|
|
1678
|
-
const thumbUrl = media.thumbnailUrl || '';
|
|
1679
|
-
const rawSuggestions = Array.isArray(c.suggestions) ? c.suggestions : [];
|
|
1680
|
-
const suggestions = idx === 0 && rawSuggestions.length === 0
|
|
1681
|
-
? cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS)
|
|
1682
|
-
: rawSuggestions;
|
|
1683
|
-
return {
|
|
1684
|
-
title: c.title || '',
|
|
1685
|
-
description: c.description || '',
|
|
1686
|
-
mediaType,
|
|
1687
|
-
imageSrc: mediaType === RCS_MEDIA_TYPES.IMAGE ? mediaUrl : '',
|
|
1688
|
-
videoAsset: mediaType === RCS_MEDIA_TYPES.VIDEO ? {
|
|
1689
|
-
videoSrc: mediaUrl,
|
|
1690
|
-
previewUrl: thumbUrl,
|
|
1691
|
-
videoThumbnail: thumbUrl,
|
|
1692
|
-
videoName: c?.media?.videoName || '',
|
|
1693
|
-
} : {},
|
|
1694
|
-
thumbnailSrc: mediaType === RCS_MEDIA_TYPES.VIDEO ? thumbUrl : '',
|
|
1695
|
-
suggestions,
|
|
1696
|
-
};
|
|
1697
|
-
});
|
|
1698
|
-
setCarouselData(
|
|
1699
|
-
hydratedCards.length > 0
|
|
1700
|
-
? ensureFirstCardDefaultPhoneSuggestions(hydratedCards)
|
|
1701
|
-
: [cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)],
|
|
1702
|
-
);
|
|
1703
|
-
setCarouselErrors(new Array(hydratedCards.length > 0 ? hydratedCards.length : 1).fill({}));
|
|
1704
|
-
|
|
1705
|
-
// Status bar uses first card's Status, keep existing behavior.
|
|
1706
|
-
if (isHostInfoBip) {
|
|
1707
|
-
setTemplateStatus('');
|
|
1708
|
-
} else {
|
|
1709
|
-
const firstCard = cards?.[0] || {};
|
|
1710
|
-
const cardForCarouselStatus = {
|
|
1711
|
-
...firstCard,
|
|
1712
|
-
Status:
|
|
1713
|
-
firstCard.Status
|
|
1714
|
-
?? firstCard.status
|
|
1715
|
-
?? firstCard.approvalStatus
|
|
1716
|
-
?? get(details, 'templateStatus')
|
|
1717
|
-
?? get(details, 'approvalStatus')
|
|
1718
|
-
?? get(details, 'creativeStatus')
|
|
1719
|
-
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
1720
|
-
?? '',
|
|
1721
|
-
};
|
|
1722
|
-
templateStatusHelper(cardForCarouselStatus);
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
// Hydrate SMS fallback for carousel — same logic as for non-carousel types below.
|
|
1726
|
-
// Without this the early `return` skips fallback hydration entirely, so reopening
|
|
1727
|
-
// a carousel template never restores the saved SMS fallback data.
|
|
1728
|
-
const carouselSmsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
1729
|
-
const carouselSmsBase = get(carouselSmsFallbackContent, 'versions.base', {});
|
|
1730
|
-
const carouselUpdatedEditor = carouselSmsBase['updated-sms-editor'] ?? carouselSmsBase['sms-editor'];
|
|
1731
|
-
const carouselSmsEditor = carouselSmsBase['sms-editor'];
|
|
1732
|
-
const carouselFromNested = Array.isArray(carouselUpdatedEditor)
|
|
1733
|
-
? carouselUpdatedEditor.join('')
|
|
1734
|
-
: (typeof carouselUpdatedEditor === 'string' ? carouselUpdatedEditor : (carouselSmsEditor || ''));
|
|
1735
|
-
const carouselFallbackMessage = carouselSmsFallbackContent.smsContent
|
|
1736
|
-
|| carouselSmsFallbackContent.smsTemplateContent
|
|
1737
|
-
|| carouselSmsFallbackContent.message
|
|
1738
|
-
|| carouselFromNested
|
|
1739
|
-
|| '';
|
|
1740
|
-
const carouselVarMappedFromPayload = carouselSmsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
1741
|
-
const carouselHasVarMapped = Object.keys(carouselVarMappedFromPayload).length > 0;
|
|
1742
|
-
const carouselHasFallbackPayload =
|
|
1743
|
-
carouselSmsFallbackContent
|
|
1744
|
-
&& Object.keys(carouselSmsFallbackContent).length > 0
|
|
1745
|
-
&& (
|
|
1746
|
-
!!carouselSmsFallbackContent.smsTemplateName
|
|
1747
|
-
|| !!carouselFallbackMessage
|
|
1748
|
-
|| carouselHasVarMapped
|
|
1749
|
-
);
|
|
1750
|
-
if (carouselHasFallbackPayload) {
|
|
1751
|
-
const carouselUnicodeFromApi =
|
|
1752
|
-
typeof carouselSmsFallbackContent.unicodeValidity === 'boolean'
|
|
1753
|
-
? carouselSmsFallbackContent.unicodeValidity
|
|
1754
|
-
: (typeof carouselSmsBase['unicode-validity'] === 'boolean' ? carouselSmsBase['unicode-validity'] : true);
|
|
1755
|
-
const carouselRegisteredSenderIds =
|
|
1756
|
-
extractRegisteredSenderIdsFromSmsFallbackRecord(carouselSmsFallbackContent);
|
|
1757
|
-
const carouselNextSmsState = {
|
|
1758
|
-
templateName: carouselSmsFallbackContent.smsTemplateName || '',
|
|
1759
|
-
content: carouselFallbackMessage,
|
|
1760
|
-
templateContent: carouselFallbackMessage,
|
|
1761
|
-
unicodeValidity: carouselUnicodeFromApi,
|
|
1762
|
-
...(carouselHasVarMapped && { rcsSmsFallbackVarMapped: carouselVarMappedFromPayload }),
|
|
1763
|
-
...(Array.isArray(carouselRegisteredSenderIds) && carouselRegisteredSenderIds.length > 0
|
|
1764
|
-
? { registeredSenderIds: carouselRegisteredSenderIds }
|
|
1765
|
-
: {}),
|
|
1766
|
-
};
|
|
1767
|
-
const carouselHydrationKey = JSON.stringify({
|
|
1768
|
-
creativeKey: details._id || details.name || details.creativeName || '',
|
|
1769
|
-
templateName: carouselNextSmsState.templateName,
|
|
1770
|
-
content: carouselNextSmsState.content,
|
|
1771
|
-
unicodeValidity: carouselNextSmsState.unicodeValidity,
|
|
1772
|
-
varMapped: carouselNextSmsState.rcsSmsFallbackVarMapped || {},
|
|
1773
|
-
senderIds: Array.isArray(carouselRegisteredSenderIds)
|
|
1774
|
-
? carouselRegisteredSenderIds.join('')
|
|
1775
|
-
: '',
|
|
1776
|
-
});
|
|
1777
|
-
if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== carouselHydrationKey) {
|
|
1778
|
-
lastSmsFallbackHydrationKeyRef.current = carouselHydrationKey;
|
|
1779
|
-
setSmsFallbackData(carouselNextSmsState);
|
|
1780
|
-
}
|
|
1781
|
-
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
1782
|
-
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
1783
|
-
setSmsFallbackData(null);
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
return;
|
|
589
|
+
if (!isFullMode) {
|
|
590
|
+
const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
|
|
591
|
+
setCardVarMapped(tempCardVarMapped);
|
|
1787
592
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
card0.mediaType
|
|
1791
|
-
|| get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
1792
|
-
if (cardType.toUpperCase() !== contentType.carousel && mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
593
|
+
const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
594
|
+
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
1793
595
|
setTemplateType(contentType.text_message);
|
|
1794
|
-
} else
|
|
596
|
+
} else {
|
|
1795
597
|
setTemplateType(contentType.rich_card);
|
|
1796
598
|
}
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
const
|
|
1800
|
-
const
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
1805
|
-
rcsVarRegex,
|
|
1806
|
-
});
|
|
599
|
+
setEditFlow(true);
|
|
600
|
+
setTemplateName(details.name || '');
|
|
601
|
+
const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
|
|
602
|
+
const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
|
|
603
|
+
const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
|
|
604
|
+
const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
|
|
605
|
+
const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
|
|
1807
606
|
setTemplateTitle(normalizedTitle);
|
|
1808
607
|
setTemplateDesc(normalizedDesc);
|
|
1809
|
-
setSuggestions(
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
);
|
|
1814
|
-
const cardForStatus = {
|
|
1815
|
-
...card0,
|
|
1816
|
-
Status:
|
|
1817
|
-
card0.Status
|
|
1818
|
-
?? card0.status
|
|
1819
|
-
?? card0.approvalStatus
|
|
1820
|
-
?? get(details, 'templateStatus')
|
|
1821
|
-
?? get(details, 'approvalStatus')
|
|
1822
|
-
?? get(details, 'creativeStatus')
|
|
1823
|
-
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
1824
|
-
?? '',
|
|
1825
|
-
};
|
|
1826
|
-
if (isHostInfoBip) {
|
|
1827
|
-
setTemplateStatus('');
|
|
1828
|
-
} else {
|
|
1829
|
-
templateStatusHelper(cardForStatus);
|
|
1830
|
-
}
|
|
1831
|
-
const mediaData =
|
|
1832
|
-
card0.media != null && card0.media !== ''
|
|
1833
|
-
? card0.media
|
|
1834
|
-
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
1835
|
-
const cardSettings =
|
|
1836
|
-
get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
|
|
1837
|
-
|| get(details, 'rcsContent.cardSettings', '');
|
|
608
|
+
setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
|
|
609
|
+
templateStatusHelper(details);
|
|
610
|
+
const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
611
|
+
const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
|
|
1838
612
|
setMediaData(mediaData, mediaType, cardSettings);
|
|
1839
613
|
if (details?.edit) {
|
|
1840
614
|
const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
|
|
1841
615
|
setRcsAccount(rcsAccountId);
|
|
1842
616
|
}
|
|
1843
|
-
|
|
1844
|
-
const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
1845
|
-
const base = get(smsFallbackContent, 'versions.base', {});
|
|
1846
|
-
const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
|
|
1847
|
-
const smsEditor = base['sms-editor'];
|
|
1848
|
-
const fromNested = Array.isArray(updatedEditor)
|
|
1849
|
-
? updatedEditor.join('')
|
|
1850
|
-
: (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
|
|
1851
|
-
const fallbackMessage = smsFallbackContent.smsContent
|
|
1852
|
-
|| smsFallbackContent.smsTemplateContent
|
|
1853
|
-
|| smsFallbackContent.message
|
|
1854
|
-
|| fromNested
|
|
1855
|
-
|| '';
|
|
1856
|
-
const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
1857
|
-
const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
|
|
1858
|
-
const hasFallbackPayload =
|
|
1859
|
-
smsFallbackContent
|
|
1860
|
-
&& Object.keys(smsFallbackContent).length > 0
|
|
1861
|
-
&& (
|
|
1862
|
-
!!smsFallbackContent.smsTemplateName
|
|
1863
|
-
|| !!fallbackMessage
|
|
1864
|
-
|| hasVarMapped
|
|
1865
|
-
);
|
|
1866
|
-
if (hasFallbackPayload) {
|
|
1867
|
-
if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
|
|
1868
|
-
console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
|
|
1869
|
-
}
|
|
1870
|
-
const unicodeFromApi =
|
|
1871
|
-
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
1872
|
-
? smsFallbackContent.unicodeValidity
|
|
1873
|
-
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
1874
|
-
const registeredSenderIdsFromApi =
|
|
1875
|
-
extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackContent);
|
|
1876
|
-
const nextSmsState = {
|
|
1877
|
-
templateName: smsFallbackContent.smsTemplateName || '',
|
|
1878
|
-
content: fallbackMessage,
|
|
1879
|
-
templateContent: fallbackMessage,
|
|
1880
|
-
unicodeValidity: unicodeFromApi,
|
|
1881
|
-
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
1882
|
-
...(Array.isArray(registeredSenderIdsFromApi) && registeredSenderIdsFromApi.length > 0
|
|
1883
|
-
? { registeredSenderIds: registeredSenderIdsFromApi }
|
|
1884
|
-
: {}),
|
|
1885
|
-
};
|
|
1886
|
-
const hydrationKey = JSON.stringify({
|
|
1887
|
-
creativeKey: details._id || details.name || details.creativeName || '',
|
|
1888
|
-
templateName: nextSmsState.templateName,
|
|
1889
|
-
content: nextSmsState.content,
|
|
1890
|
-
unicodeValidity: nextSmsState.unicodeValidity,
|
|
1891
|
-
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
1892
|
-
senderIds:
|
|
1893
|
-
Array.isArray(registeredSenderIdsFromApi)
|
|
1894
|
-
? registeredSenderIdsFromApi.join('\u001f')
|
|
1895
|
-
: '',
|
|
1896
|
-
});
|
|
1897
|
-
if (
|
|
1898
|
-
isFullMode
|
|
1899
|
-
|| lastSmsFallbackHydrationKeyRef.current !== hydrationKey
|
|
1900
|
-
) {
|
|
1901
|
-
lastSmsFallbackHydrationKeyRef.current = hydrationKey;
|
|
1902
|
-
setSmsFallbackData(nextSmsState);
|
|
1903
|
-
}
|
|
1904
|
-
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
1905
|
-
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
1906
|
-
setSmsFallbackData(null);
|
|
1907
|
-
}
|
|
1908
617
|
}
|
|
1909
|
-
}, [
|
|
618
|
+
}, [rcsData, templateData, isFullMode, isEditFlow]);
|
|
619
|
+
|
|
1910
620
|
|
|
1911
621
|
useEffect(() => {
|
|
1912
622
|
if (templateType === contentType.text_message) {
|
|
1913
623
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1914
|
-
|
|
1915
|
-
|
|
624
|
+
setTemplateTitle('');
|
|
625
|
+
setTemplateTitleError('');
|
|
1916
626
|
if (!isEditFlow && isFullMode) {
|
|
1917
|
-
setTemplateTitle('');
|
|
1918
|
-
setTemplateTitleError('');
|
|
1919
627
|
setUpdateRcsImageSrc('');
|
|
628
|
+
setUpdateRcsVideoSrc({});
|
|
1920
629
|
setRcsVideoSrc({});
|
|
1921
630
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
1922
631
|
}
|
|
@@ -1943,8 +652,7 @@ export const Rcs = (props) => {
|
|
|
1943
652
|
if (!showDltContainer) {
|
|
1944
653
|
const { type, module } = location.query || {};
|
|
1945
654
|
const isEmbedded = type === EMBEDDED;
|
|
1946
|
-
|
|
1947
|
-
const context = isEmbedded ? module : 'outbound';
|
|
655
|
+
const context = isEmbedded ? module : DEFAULT;
|
|
1948
656
|
const embedded = isEmbedded ? type : FULL;
|
|
1949
657
|
const query = {
|
|
1950
658
|
layout: SMS,
|
|
@@ -1955,9 +663,9 @@ export const Rcs = (props) => {
|
|
|
1955
663
|
if (getDefaultTags) {
|
|
1956
664
|
query.context = getDefaultTags;
|
|
1957
665
|
}
|
|
1958
|
-
|
|
666
|
+
globalActions.fetchSchemaForEntity(query);
|
|
1959
667
|
}
|
|
1960
|
-
}, [showDltContainer
|
|
668
|
+
}, [showDltContainer]);
|
|
1961
669
|
|
|
1962
670
|
useEffect(() => {
|
|
1963
671
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -1980,128 +688,16 @@ export const Rcs = (props) => {
|
|
|
1980
688
|
context,
|
|
1981
689
|
embedded,
|
|
1982
690
|
};
|
|
1983
|
-
if (getDefaultTags) {
|
|
1984
|
-
query.context = getDefaultTags;
|
|
1985
|
-
}
|
|
1986
|
-
fetchTagSchemaIfNewQuery(query);
|
|
1987
691
|
globalActions.fetchSchemaForEntity(query);
|
|
1988
692
|
};
|
|
1989
693
|
|
|
1990
|
-
const
|
|
1991
|
-
if (!
|
|
1992
|
-
const
|
|
1993
|
-
return templateStr.replace(re, `{{${tagName}}}`);
|
|
1994
|
-
};
|
|
1995
|
-
|
|
1996
|
-
const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
|
|
1997
|
-
if (!varSegmentCompositeDomId) return;
|
|
1998
|
-
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
1999
|
-
if (underscoreIndexInCompositeId === -1) return;
|
|
2000
|
-
const segmentIndexSuffix = varSegmentCompositeDomId.slice(underscoreIndexInCompositeId + 1);
|
|
2001
|
-
if (segmentIndexSuffix === '' || isNaN(Number(segmentIndexSuffix))) return;
|
|
2002
|
-
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
2003
|
-
const semanticOrNumericVarName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
2004
|
-
if (!semanticOrNumericVarName) return;
|
|
2005
|
-
const isNumericPlaceholderSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(semanticOrNumericVarName));
|
|
2006
|
-
const templateStringForField =
|
|
2007
|
-
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
2008
|
-
const titleOrMessageFieldType =
|
|
2009
|
-
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
2010
|
-
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
2011
|
-
varSegmentCompositeDomId,
|
|
2012
|
-
templateStringForField,
|
|
2013
|
-
titleOrMessageFieldType,
|
|
2014
|
-
);
|
|
2015
|
-
const cardVarMappedNumericSlotKey =
|
|
2016
|
-
globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined
|
|
2017
|
-
? String(globalVarSlotIndexZeroBased + 1)
|
|
2018
|
-
: null;
|
|
2019
|
-
|
|
2020
|
-
setCardVarMapped((previousCardVarMapped) => {
|
|
2021
|
-
const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
|
|
2022
|
-
if (isNumericPlaceholderSlot) {
|
|
2023
|
-
const existingValueBeforeAppend = (
|
|
2024
|
-
previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
|
|
2025
|
-
).toString();
|
|
2026
|
-
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
2027
|
-
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
2028
|
-
updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
|
|
2029
|
-
} else {
|
|
2030
|
-
// Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
|
|
2031
|
-
// "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
|
|
2032
|
-
// read/write the global numeric slot only and drop the shared semantic key.
|
|
2033
|
-
const existingValueBeforeAppend = cardVarMappedNumericSlotKey
|
|
2034
|
-
? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
|
|
2035
|
-
: String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
|
|
2036
|
-
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
2037
|
-
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
2038
|
-
if (cardVarMappedNumericSlotKey) {
|
|
2039
|
-
updatedCardVarMapped[cardVarMappedNumericSlotKey] = mappedValueAfterAppendingTag;
|
|
2040
|
-
// Sync all other intra-field occurrences of the same variable (cross-field slots stay independent)
|
|
2041
|
-
const fieldOffset = titleOrMessageFieldType === TITLE_TEXT ? 0 : (templateTitle?.match(rcsVarRegex) ?? []).length;
|
|
2042
|
-
const fieldSegments = splitTemplateVarStringRcs(templateStringForField ?? '');
|
|
2043
|
-
let varOrdinal = 0;
|
|
2044
|
-
for (let i = 0; i < fieldSegments.length; i++) {
|
|
2045
|
-
if (rcsVarTestRegex.test(fieldSegments[i])) {
|
|
2046
|
-
const slotVarName = getVarNameFromToken(fieldSegments[i]);
|
|
2047
|
-
const globalSlot = fieldOffset + varOrdinal;
|
|
2048
|
-
varOrdinal += 1;
|
|
2049
|
-
if (slotVarName === semanticOrNumericVarName && globalSlot !== globalVarSlotIndexZeroBased) {
|
|
2050
|
-
updatedCardVarMapped[String(globalSlot + 1)] = mappedValueAfterAppendingTag;
|
|
2051
|
-
}
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
} else {
|
|
2055
|
-
updatedCardVarMapped[semanticOrNumericVarName] = mappedValueAfterAppendingTag;
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
return updatedCardVarMapped;
|
|
2059
|
-
});
|
|
2060
|
-
|
|
2061
|
-
if (
|
|
2062
|
-
isNumericPlaceholderSlot
|
|
2063
|
-
&& (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
|
|
2064
|
-
) {
|
|
2065
|
-
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
|
|
2066
|
-
setTemplateTitle((previousTitle) => {
|
|
2067
|
-
const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
2068
|
-
previousTitle || '',
|
|
2069
|
-
semanticOrNumericVarName,
|
|
2070
|
-
selectedTagNameFromPicker,
|
|
2071
|
-
);
|
|
2072
|
-
if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
|
|
2073
|
-
setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
|
|
2074
|
-
// 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
|
|
2075
|
-
setRcsVarSegmentEditorRemountKey((k) => k + 1);
|
|
2076
|
-
return titleAfterReplacingNumericPlaceholder;
|
|
2077
|
-
});
|
|
2078
|
-
} else {
|
|
2079
|
-
setTemplateDesc((previousDescription) => {
|
|
2080
|
-
const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
2081
|
-
previousDescription || '',
|
|
2082
|
-
semanticOrNumericVarName,
|
|
2083
|
-
selectedTagNameFromPicker,
|
|
2084
|
-
);
|
|
2085
|
-
if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
|
|
2086
|
-
return previousDescription;
|
|
2087
|
-
}
|
|
2088
|
-
setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
|
|
2089
|
-
setRcsVarSegmentEditorRemountKey((k) => k + 1);
|
|
2090
|
-
return descriptionAfterReplacingNumericPlaceholder;
|
|
2091
|
-
});
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
};
|
|
2095
|
-
|
|
2096
|
-
const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
|
|
2097
|
-
|
|
2098
|
-
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
2099
|
-
|
|
2100
|
-
const onCarouselTagSelect = (data) => {
|
|
2101
|
-
if (!carouselFocusedVarId) return;
|
|
2102
|
-
const sep = carouselFocusedVarId.lastIndexOf('_');
|
|
694
|
+
const onTagSelect = (data, areaId) => {
|
|
695
|
+
if (!areaId) return;
|
|
696
|
+
const sep = areaId.lastIndexOf('_');
|
|
2103
697
|
if (sep === -1) return;
|
|
2104
|
-
const
|
|
698
|
+
const numId = Number(areaId.slice(sep + 1));
|
|
699
|
+
if (isNaN(numId)) return;
|
|
700
|
+
const token = areaId.slice(0, sep);
|
|
2105
701
|
const variableName = getVarNameFromToken(token);
|
|
2106
702
|
if (!variableName) return;
|
|
2107
703
|
setCardVarMapped((prev) => {
|
|
@@ -2114,6 +710,10 @@ export const Rcs = (props) => {
|
|
|
2114
710
|
});
|
|
2115
711
|
};
|
|
2116
712
|
|
|
713
|
+
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
714
|
+
|
|
715
|
+
const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
|
|
716
|
+
|
|
2117
717
|
const onTagSelectFallback = (data) => {
|
|
2118
718
|
const tempMsg = `${fallbackMessage}{{${data}}}`;
|
|
2119
719
|
const error = fallbackMessageErrorHandler(tempMsg);
|
|
@@ -2133,14 +733,15 @@ export const Rcs = (props) => {
|
|
|
2133
733
|
};
|
|
2134
734
|
// tag Code end
|
|
2135
735
|
|
|
2136
|
-
const renderLabel = (value, desc) => {
|
|
736
|
+
const renderLabel = (value, showLabel, desc) => {
|
|
737
|
+
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
2137
738
|
return (
|
|
2138
739
|
<>
|
|
2139
|
-
<
|
|
740
|
+
<RcsLabel>
|
|
2140
741
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
2141
|
-
</
|
|
742
|
+
</RcsLabel>
|
|
2142
743
|
{desc && (
|
|
2143
|
-
<CapLabel type="label3"
|
|
744
|
+
<CapLabel type="label3" style={{ marginBottom: '17px' }}>
|
|
2144
745
|
{formatMessage(messages[desc])}
|
|
2145
746
|
</CapLabel>
|
|
2146
747
|
)}
|
|
@@ -2159,8 +760,13 @@ export const Rcs = (props) => {
|
|
|
2159
760
|
},
|
|
2160
761
|
{
|
|
2161
762
|
value: contentType.carousel,
|
|
2162
|
-
label:
|
|
2163
|
-
|
|
763
|
+
label: (
|
|
764
|
+
<CapTooltip title={formatMessage(messages.disabledCarouselTooltip)}>
|
|
765
|
+
{formatMessage(messages.carousel)}
|
|
766
|
+
</CapTooltip>
|
|
767
|
+
),
|
|
768
|
+
disabled: true,
|
|
769
|
+
},
|
|
2164
770
|
];
|
|
2165
771
|
|
|
2166
772
|
const onTemplateNameChange = ({ target: { value } }) => {
|
|
@@ -2171,10 +777,6 @@ export const Rcs = (props) => {
|
|
|
2171
777
|
|
|
2172
778
|
const onTemplateTypeChange = ({ target: { value } }) => {
|
|
2173
779
|
setTemplateType(value);
|
|
2174
|
-
// Carousel has per-card media; keep template-level media type neutral.
|
|
2175
|
-
if (value === contentType.carousel) {
|
|
2176
|
-
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
2177
|
-
}
|
|
2178
780
|
};
|
|
2179
781
|
|
|
2180
782
|
|
|
@@ -2195,39 +797,8 @@ export const Rcs = (props) => {
|
|
|
2195
797
|
const onTemplateMediaTypeChange = ({ target: { value } }) => {
|
|
2196
798
|
setTemplateMediaType(value);
|
|
2197
799
|
};
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
if (descArray?.length) {
|
|
2201
|
-
descArray.forEach((elem, index) => {
|
|
2202
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
2203
|
-
// Variable input
|
|
2204
|
-
renderArray.push(
|
|
2205
|
-
<TextArea
|
|
2206
|
-
id={`${elem}_${index}`}
|
|
2207
|
-
key={`${elem}_${index}`}
|
|
2208
|
-
placeholder={`enter the value for ${elem}`}
|
|
2209
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2210
|
-
onChange={e => textAreaValueChange(e, type)}
|
|
2211
|
-
value={textAreaValue(index, type)}
|
|
2212
|
-
onFocus={(e) => setTextAreaId(e, type)}
|
|
2213
|
-
/>
|
|
2214
|
-
);
|
|
2215
|
-
} else if (elem) {
|
|
2216
|
-
// Static text
|
|
2217
|
-
renderArray.push(
|
|
2218
|
-
<TextArea
|
|
2219
|
-
key={`static_${index}`}
|
|
2220
|
-
value={elem}
|
|
2221
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2222
|
-
disabled
|
|
2223
|
-
className="rcs-edit-template-message-static-textarea"
|
|
2224
|
-
/>
|
|
2225
|
-
);
|
|
2226
|
-
}
|
|
2227
|
-
});
|
|
2228
|
-
}
|
|
2229
|
-
return renderArray;
|
|
2230
|
-
};
|
|
800
|
+
|
|
801
|
+
|
|
2231
802
|
const onTemplateTitleChange = ({ target: { value } }) => {
|
|
2232
803
|
let errorMessage = false;
|
|
2233
804
|
if (templateType === contentType.rich_card && !value.trim()) {
|
|
@@ -2243,7 +814,7 @@ export const Rcs = (props) => {
|
|
|
2243
814
|
|
|
2244
815
|
const onTemplateDescChange = ({ target: { value } }) => {
|
|
2245
816
|
let errorMessage = false;
|
|
2246
|
-
if(templateType === contentType.text_message && value?.length >
|
|
817
|
+
if(templateType === contentType.text_message && value?.length > RCS_TEXT_MESSAGE_MAX_LENGTH){
|
|
2247
818
|
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
2248
819
|
} else if(templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH){
|
|
2249
820
|
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
@@ -2259,16 +830,16 @@ export const Rcs = (props) => {
|
|
|
2259
830
|
|
|
2260
831
|
const templateDescErrorHandler = (value) => {
|
|
2261
832
|
let errorMessage = false;
|
|
2262
|
-
const {
|
|
833
|
+
const { isBraceError } = validateTags({
|
|
2263
834
|
content: value,
|
|
2264
835
|
tagsParam: tags,
|
|
2265
|
-
injectedTagsParams: injectedTags,
|
|
2266
836
|
location,
|
|
2267
837
|
tagModule: getDefaultTags,
|
|
838
|
+
isFullMode,
|
|
2268
839
|
}) || {};
|
|
2269
840
|
|
|
2270
841
|
const maxLength = templateType === contentType.text_message
|
|
2271
|
-
?
|
|
842
|
+
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
2272
843
|
: RCS_RICH_CARD_MAX_LENGTH;
|
|
2273
844
|
|
|
2274
845
|
if (value === '' && isMediaTypeText) {
|
|
@@ -2291,25 +862,10 @@ export const Rcs = (props) => {
|
|
|
2291
862
|
};
|
|
2292
863
|
|
|
2293
864
|
const fallbackMessageErrorHandler = (value) => {
|
|
2294
|
-
let errorMessage = false;
|
|
2295
|
-
const { unsupportedTags } = validateTags({
|
|
2296
|
-
content: value,
|
|
2297
|
-
tagsParam: tags,
|
|
2298
|
-
injectedTagsParams: injectedTags,
|
|
2299
|
-
location,
|
|
2300
|
-
tagModule: getDefaultTags,
|
|
2301
|
-
}) || {};
|
|
2302
865
|
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
2303
|
-
|
|
2304
|
-
} else if (unsupportedTags?.length > 0) {
|
|
2305
|
-
errorMessage = formatMessage(
|
|
2306
|
-
globalMessages.unsupportedTagsValidationError,
|
|
2307
|
-
{
|
|
2308
|
-
unsupportedTags,
|
|
2309
|
-
},
|
|
2310
|
-
);
|
|
866
|
+
return formatMessage(messages.fallbackMsgLenError);
|
|
2311
867
|
}
|
|
2312
|
-
return
|
|
868
|
+
return false;
|
|
2313
869
|
};
|
|
2314
870
|
|
|
2315
871
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
@@ -2356,43 +912,53 @@ export const Rcs = (props) => {
|
|
|
2356
912
|
if(!isFullMode){
|
|
2357
913
|
return false;
|
|
2358
914
|
}
|
|
2359
|
-
|
|
2360
|
-
if (!/^[\w.]+$/.test(paramName)) {
|
|
915
|
+
if (!/^\w+$/.test(paramName)) {
|
|
2361
916
|
return formatMessage(messages.unknownCharactersError);
|
|
2362
917
|
}
|
|
2363
918
|
}
|
|
2364
919
|
return false;
|
|
2365
920
|
};
|
|
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
|
+
|
|
2366
948
|
|
|
2367
949
|
const onMessageAddVar = () => {
|
|
2368
|
-
onAddVar(templateDesc);
|
|
950
|
+
onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
|
|
2369
951
|
};
|
|
2370
952
|
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
* (duplicate numbers would share a cardVarMapped key and bleed values across fields).
|
|
2376
|
-
*/
|
|
2377
|
-
const getNextRcsNumericVarNumber = (titleStr, descStr) => {
|
|
2378
|
-
const allExistingVars = [
|
|
2379
|
-
...(titleStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
|
|
2380
|
-
...(descStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
|
|
2381
|
-
];
|
|
2382
|
-
const existingNumbers = allExistingVars.flatMap(v => {
|
|
2383
|
-
const m = v.match(/\d+/);
|
|
2384
|
-
return m ? [parseInt(m[0], 10)] : [];
|
|
2385
|
-
});
|
|
953
|
+
const onAddVar = (type, messageContent, regex) => {
|
|
954
|
+
// Always append the next variable at the end, like WhatsApp
|
|
955
|
+
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
956
|
+
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
2386
957
|
let nextNumber = 1;
|
|
2387
958
|
while (existingNumbers.includes(nextNumber)) {
|
|
2388
959
|
nextNumber++;
|
|
2389
960
|
}
|
|
2390
|
-
|
|
2391
|
-
};
|
|
2392
|
-
|
|
2393
|
-
const onAddVar = (messageContent) => {
|
|
2394
|
-
const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
|
|
2395
|
-
if (nextNumber === null) {
|
|
961
|
+
if (nextNumber > 19) {
|
|
2396
962
|
return;
|
|
2397
963
|
}
|
|
2398
964
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -2404,13 +970,15 @@ const onAddVar = (messageContent) => {
|
|
|
2404
970
|
};
|
|
2405
971
|
|
|
2406
972
|
const onTitleAddVar = () => {
|
|
2407
|
-
//
|
|
2408
|
-
// duplicate a number already used in the description. Duplicate numeric
|
|
2409
|
-
// names would share the same cardVarMapped semantic key, causing the
|
|
2410
|
-
// description slot to reflect the title slot value and vice-versa.
|
|
973
|
+
// Always append the next variable at the end, like WhatsApp
|
|
2411
974
|
const messageContent = templateTitle;
|
|
2412
|
-
const
|
|
2413
|
-
|
|
975
|
+
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
976
|
+
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
977
|
+
let nextNumber = 1;
|
|
978
|
+
while (existingNumbers.includes(nextNumber)) {
|
|
979
|
+
nextNumber++;
|
|
980
|
+
}
|
|
981
|
+
if (nextNumber > 19) {
|
|
2414
982
|
return;
|
|
2415
983
|
}
|
|
2416
984
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -2422,35 +990,26 @@ const onTitleAddVar = () => {
|
|
|
2422
990
|
setTemplateTitleError(error);
|
|
2423
991
|
};
|
|
2424
992
|
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
if (!token) return;
|
|
2446
|
-
setCarouselData((prev = []) => {
|
|
2447
|
-
const updated = cloneDeep(prev);
|
|
2448
|
-
if (!updated[cardIndex]) return prev;
|
|
2449
|
-
const current = (updated[cardIndex][fieldName] || '').toString();
|
|
2450
|
-
updated[cardIndex][fieldName] = `${current}${token}`;
|
|
2451
|
-
return updated;
|
|
2452
|
-
});
|
|
2453
|
-
};
|
|
993
|
+
|
|
994
|
+
const splitTemplateVarString = (str) => {
|
|
995
|
+
if (!str) return [];
|
|
996
|
+
const validVarArr = str.match(rcsVarRegex) || [];
|
|
997
|
+
const templateVarArray = [];
|
|
998
|
+
let content = str;
|
|
999
|
+
while (content?.length !== 0) {
|
|
1000
|
+
const index = content.indexOf(validVarArr?.[0]);
|
|
1001
|
+
if (index !== -1) {
|
|
1002
|
+
templateVarArray.push(content.substring(0, index));
|
|
1003
|
+
templateVarArray.push(validVarArr?.[0]);
|
|
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
|
+
};
|
|
2454
1013
|
|
|
2455
1014
|
const textAreaValue = (idValue, type) => {
|
|
2456
1015
|
if (idValue >= 0) {
|
|
@@ -2466,46 +1025,6 @@ const onTitleAddVar = () => {
|
|
|
2466
1025
|
return "";
|
|
2467
1026
|
};
|
|
2468
1027
|
|
|
2469
|
-
// Carousel: render variable-value editor for a given template string (title/description).
|
|
2470
|
-
// This matches rich-card/text edit behavior: static pieces are read-only, variable tokens are editable.
|
|
2471
|
-
const renderCarouselEditMessage = (templateStr) => {
|
|
2472
|
-
const renderArray = [];
|
|
2473
|
-
const templateArr = splitTemplateVarString(templateStr);
|
|
2474
|
-
if (templateArr?.length) {
|
|
2475
|
-
templateArr.forEach((elem, index) => {
|
|
2476
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
2477
|
-
const varName = getVarNameFromToken(elem);
|
|
2478
|
-
renderArray.push(
|
|
2479
|
-
<div key={`${elem}_${index}`} className="var-segment-message-editor__var-slot">
|
|
2480
|
-
<TextArea
|
|
2481
|
-
id={`${elem}_${index}`}
|
|
2482
|
-
placeholder={`enter the value for ${elem}`}
|
|
2483
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2484
|
-
onChange={(e) => textAreaValueChange(e, TITLE_TEXT)}
|
|
2485
|
-
value={varName ? ((cardVarMapped?.[varName] ?? '').toString()) : ''}
|
|
2486
|
-
onFocus={(e) => {
|
|
2487
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
2488
|
-
setCarouselFocusedVarId(id);
|
|
2489
|
-
}}
|
|
2490
|
-
/>
|
|
2491
|
-
</div>
|
|
2492
|
-
);
|
|
2493
|
-
} else if (elem) {
|
|
2494
|
-
renderArray.push(
|
|
2495
|
-
<CapHeading
|
|
2496
|
-
key={`static_${index}`}
|
|
2497
|
-
type="h4"
|
|
2498
|
-
className="rcs-edit-template-message-split"
|
|
2499
|
-
>
|
|
2500
|
-
{elem}
|
|
2501
|
-
</CapHeading>
|
|
2502
|
-
);
|
|
2503
|
-
}
|
|
2504
|
-
});
|
|
2505
|
-
}
|
|
2506
|
-
return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
|
|
2507
|
-
};
|
|
2508
|
-
|
|
2509
1028
|
const textAreaValueChange = (e, type) => {
|
|
2510
1029
|
const value = e?.target?.value ?? '';
|
|
2511
1030
|
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
@@ -2525,9 +1044,7 @@ const onTitleAddVar = () => {
|
|
|
2525
1044
|
};
|
|
2526
1045
|
|
|
2527
1046
|
const setTextAreaId = (e, type) => {
|
|
2528
|
-
|
|
2529
|
-
// have an `.target.id` shape. Support both.
|
|
2530
|
-
const id = typeof e === 'string' ? e : (e?.target?.id || e?.currentTarget?.id || '');
|
|
1047
|
+
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
2531
1048
|
if (!id) return;
|
|
2532
1049
|
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
2533
1050
|
else setDescTextAreaId(id);
|
|
@@ -2546,12 +1063,10 @@ const onTitleAddVar = () => {
|
|
|
2546
1063
|
</CapRow>
|
|
2547
1064
|
}
|
|
2548
1065
|
description={
|
|
2549
|
-
|
|
2550
|
-
<CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
|
|
2551
|
-
)
|
|
1066
|
+
<CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
|
|
2552
1067
|
}
|
|
2553
1068
|
/>
|
|
2554
|
-
|
|
1069
|
+
|
|
2555
1070
|
<CapActionButton
|
|
2556
1071
|
buttonType={buttonType}
|
|
2557
1072
|
updateButtonChange={updateButtonChange}
|
|
@@ -2560,85 +1075,44 @@ const onTitleAddVar = () => {
|
|
|
2560
1075
|
isEditFlow={isEditFlow}
|
|
2561
1076
|
isFullMode={isFullMode}
|
|
2562
1077
|
maxButtons={MAX_BUTTONS}
|
|
2563
|
-
|
|
2564
|
-
/>
|
|
1078
|
+
/>
|
|
2565
1079
|
</>
|
|
2566
1080
|
);
|
|
2567
1081
|
};
|
|
2568
1082
|
|
|
2569
|
-
const
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
const descriptionVarSegmentValueMapById = useMemo(
|
|
2599
|
-
() => getRcsValueMap(templateDesc, MESSAGE_TEXT),
|
|
2600
|
-
[templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
|
|
2601
|
-
);
|
|
2602
|
-
|
|
2603
|
-
const handleRcsVarChange = (varSegmentCompositeDomId, value, type) => {
|
|
2604
|
-
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
2605
|
-
if (underscoreIndexInCompositeId === -1) return;
|
|
2606
|
-
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
2607
|
-
const variableName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
2608
|
-
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
2609
|
-
const isInvalidValue = value?.trim() === '';
|
|
2610
|
-
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
2611
|
-
const templateStringForField = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
2612
|
-
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
2613
|
-
varSegmentCompositeDomId,
|
|
2614
|
-
templateStringForField,
|
|
2615
|
-
type,
|
|
2616
|
-
);
|
|
2617
|
-
const fieldOffset = type === TITLE_TEXT ? 0 : (templateTitle?.match(rcsVarRegex) ?? []).length;
|
|
2618
|
-
setCardVarMapped((previousCardVarMapped) => {
|
|
2619
|
-
const updatedCardVarMapped = { ...previousCardVarMapped };
|
|
2620
|
-
// Remove stale semantic key: keeping it causes every other slot sharing the same
|
|
2621
|
-
// variable name (e.g. {{adv}} in both title and description) to read the same value
|
|
2622
|
-
// via the semantic-key fallback in resolveCardVarMappedSlotValue.
|
|
2623
|
-
delete updatedCardVarMapped[variableName];
|
|
2624
|
-
if (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined) {
|
|
2625
|
-
updatedCardVarMapped[String(globalVarSlotIndexZeroBased + 1)] = coercedSlotValue;
|
|
2626
|
-
// Sync all other intra-field occurrences of the same variable (cross-field slots stay independent)
|
|
2627
|
-
const fieldSegments = splitTemplateVarStringRcs(templateStringForField ?? '');
|
|
2628
|
-
let varOrdinal = 0;
|
|
2629
|
-
for (let i = 0; i < fieldSegments.length; i++) {
|
|
2630
|
-
if (rcsVarTestRegex.test(fieldSegments[i])) {
|
|
2631
|
-
const slotVarName = getVarNameFromToken(fieldSegments[i]);
|
|
2632
|
-
const globalSlot = fieldOffset + varOrdinal;
|
|
2633
|
-
varOrdinal += 1;
|
|
2634
|
-
if (slotVarName === variableName && globalSlot !== globalVarSlotIndexZeroBased) {
|
|
2635
|
-
updatedCardVarMapped[String(globalSlot + 1)] = coercedSlotValue;
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
1083
|
+
const renderedRCSEditMessage = (descArray, type) => {
|
|
1084
|
+
const renderArray = [];
|
|
1085
|
+
if (descArray?.length) {
|
|
1086
|
+
descArray.forEach((elem, index) => {
|
|
1087
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
1088
|
+
// Variable input
|
|
1089
|
+
renderArray.push(
|
|
1090
|
+
<TextArea
|
|
1091
|
+
id={`${elem}_${index}`}
|
|
1092
|
+
key={`${elem}_${index}`}
|
|
1093
|
+
placeholder={`enter the value for ${elem}`}
|
|
1094
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1095
|
+
onChange={e => textAreaValueChange(e, type)}
|
|
1096
|
+
value={textAreaValue(index, type)}
|
|
1097
|
+
onFocus={(e) => setTextAreaId(e, type)}
|
|
1098
|
+
/>
|
|
1099
|
+
);
|
|
1100
|
+
} else if (elem) {
|
|
1101
|
+
// Static text
|
|
1102
|
+
renderArray.push(
|
|
1103
|
+
<TextArea
|
|
1104
|
+
key={`static_${index}`}
|
|
1105
|
+
value={elem}
|
|
1106
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1107
|
+
disabled
|
|
1108
|
+
className="rcs-edit-template-message-static-textarea"
|
|
1109
|
+
style={{ background: '#fafafa', color: '#888' }}
|
|
1110
|
+
/>
|
|
1111
|
+
);
|
|
2638
1112
|
}
|
|
2639
|
-
}
|
|
2640
|
-
|
|
2641
|
-
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
return renderArray;
|
|
2642
1116
|
};
|
|
2643
1117
|
|
|
2644
1118
|
const renderTextComponent = () => {
|
|
@@ -2658,7 +1132,6 @@ const onTitleAddVar = () => {
|
|
|
2658
1132
|
}
|
|
2659
1133
|
suffix={
|
|
2660
1134
|
<>
|
|
2661
|
-
|
|
2662
1135
|
{(isEditFlow || !isFullMode) ? (
|
|
2663
1136
|
<TagList
|
|
2664
1137
|
label={formatMessage(globalMessages.addLabels)}
|
|
@@ -2675,28 +1148,18 @@ const onTitleAddVar = () => {
|
|
|
2675
1148
|
type="flat"
|
|
2676
1149
|
isAddBtn
|
|
2677
1150
|
onClick={onTitleAddVar}
|
|
2678
|
-
disabled={templateTitleError}
|
|
1151
|
+
disabled={!!templateTitleError}
|
|
2679
1152
|
>
|
|
2680
1153
|
{formatMessage(messages.addVar)}
|
|
2681
1154
|
</CapButton>
|
|
2682
|
-
|
|
1155
|
+
)}
|
|
2683
1156
|
</>
|
|
2684
|
-
}
|
|
2685
|
-
/>
|
|
2686
|
-
|
|
2687
|
-
{(isEditFlow || !isFullMode) ? (
|
|
2688
|
-
<VarSegmentMessageEditor
|
|
2689
|
-
key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
2690
|
-
templateString={templateTitle}
|
|
2691
|
-
valueMap={titleVarSegmentValueMapById}
|
|
2692
|
-
onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
|
|
2693
|
-
onFocus={(id) => setTitleTextAreaId(id)}
|
|
2694
|
-
varRegex={rcsVarRegex}
|
|
2695
|
-
placeholderPrefix=""
|
|
2696
|
-
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1157
|
+
}
|
|
2697
1158
|
/>
|
|
1159
|
+
<div className="rcs_text_area_wrapper">
|
|
1160
|
+
{(isEditFlow || !isFullMode) ? (
|
|
1161
|
+
renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
|
|
2698
1162
|
) : (
|
|
2699
|
-
<div className="rcs_text_area_wrapper">
|
|
2700
1163
|
<CapInput
|
|
2701
1164
|
className={`rcs-template-title-input ${
|
|
2702
1165
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -2709,8 +1172,8 @@ const onTitleAddVar = () => {
|
|
|
2709
1172
|
errorMessage={templateTitleError}
|
|
2710
1173
|
disabled={isEditFlow || !isFullMode}
|
|
2711
1174
|
/>
|
|
2712
|
-
</div>
|
|
2713
1175
|
)}
|
|
1176
|
+
</div>
|
|
2714
1177
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
2715
1178
|
<CapError className="rcs-template-title-error">
|
|
2716
1179
|
{templateTitleError}
|
|
@@ -2718,7 +1181,7 @@ const onTitleAddVar = () => {
|
|
|
2718
1181
|
)}
|
|
2719
1182
|
{!isEditFlow && isFullMode && renderTitleCharacterCount()}
|
|
2720
1183
|
</>
|
|
2721
|
-
|
|
1184
|
+
)}
|
|
2722
1185
|
|
|
2723
1186
|
{/* Template Message */}
|
|
2724
1187
|
<CapRow useLegacy id="rcs-template-message-label">
|
|
@@ -2756,21 +1219,9 @@ const onTitleAddVar = () => {
|
|
|
2756
1219
|
/>
|
|
2757
1220
|
</CapRow>
|
|
2758
1221
|
<CapRow useLegacy className="rcs-create-template-message-input">
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
(
|
|
2763
|
-
<VarSegmentMessageEditor
|
|
2764
|
-
key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
2765
|
-
templateString={templateDesc}
|
|
2766
|
-
valueMap={descriptionVarSegmentValueMapById}
|
|
2767
|
-
onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
|
|
2768
|
-
onFocus={(id) => setDescTextAreaId(id)}
|
|
2769
|
-
varRegex={rcsVarRegex}
|
|
2770
|
-
placeholderPrefix=""
|
|
2771
|
-
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
2772
|
-
/>
|
|
2773
|
-
)
|
|
1222
|
+
<div className="rcs_text_area_wrapper">
|
|
1223
|
+
{(isEditFlow || !isFullMode)
|
|
1224
|
+
? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
|
|
2774
1225
|
: (
|
|
2775
1226
|
<>
|
|
2776
1227
|
<TextArea
|
|
@@ -2808,15 +1259,13 @@ const onTitleAddVar = () => {
|
|
|
2808
1259
|
</>
|
|
2809
1260
|
)
|
|
2810
1261
|
}
|
|
2811
|
-
</
|
|
1262
|
+
</div>
|
|
2812
1263
|
{(isEditFlow || !isFullMode) && templateDescError && (
|
|
2813
1264
|
<CapError className="rcs-template-message-error">
|
|
2814
1265
|
{templateDescError}
|
|
2815
1266
|
</CapError>
|
|
2816
1267
|
)}
|
|
2817
|
-
{
|
|
2818
|
-
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
2819
|
-
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1268
|
+
{!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
|
|
2820
1269
|
{!isFullMode && hasTag() && (
|
|
2821
1270
|
<CapAlert
|
|
2822
1271
|
message={
|
|
@@ -2830,13 +1279,24 @@ const onTitleAddVar = () => {
|
|
|
2830
1279
|
/>
|
|
2831
1280
|
)}
|
|
2832
1281
|
</CapRow>
|
|
2833
|
-
{(
|
|
2834
|
-
renderButtonComponent()}
|
|
1282
|
+
{renderButtonComponent()}
|
|
2835
1283
|
</>
|
|
2836
1284
|
|
|
2837
1285
|
);
|
|
2838
1286
|
};
|
|
2839
1287
|
|
|
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
|
+
|
|
2840
1300
|
// Get character count for title (rich card only)
|
|
2841
1301
|
const getTitleCharacterCount = () => {
|
|
2842
1302
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -2851,7 +1311,7 @@ const onTitleAddVar = () => {
|
|
|
2851
1311
|
// Get max length for description based on template type
|
|
2852
1312
|
const getDescriptionMaxLength = () => {
|
|
2853
1313
|
return templateType === contentType.text_message
|
|
2854
|
-
?
|
|
1314
|
+
? RCS_TEXT_MESSAGE_MAX_LENGTH // 160 for text message
|
|
2855
1315
|
: RCS_RICH_CARD_MAX_LENGTH; // 2000 for rich card description
|
|
2856
1316
|
};
|
|
2857
1317
|
|
|
@@ -2877,58 +1337,6 @@ const onTitleAddVar = () => {
|
|
|
2877
1337
|
);
|
|
2878
1338
|
};
|
|
2879
1339
|
|
|
2880
|
-
const rcsDltCardDeleteHandler = () => {
|
|
2881
|
-
closeDltContainerHandler();
|
|
2882
|
-
setDltEditData({});
|
|
2883
|
-
setFallbackMessage('');
|
|
2884
|
-
setFallbackMessageError(false);
|
|
2885
|
-
};
|
|
2886
|
-
|
|
2887
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
2888
|
-
const {
|
|
2889
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
2890
|
-
'sms-editor': smsEditor = '',
|
|
2891
|
-
} = data.versions.base || {};
|
|
2892
|
-
};
|
|
2893
|
-
|
|
2894
|
-
const getDltContentCardList = (content, channel) => {
|
|
2895
|
-
const extra = [
|
|
2896
|
-
<CapIcon
|
|
2897
|
-
type="edit"
|
|
2898
|
-
className="rcs-dlt-edit-icon"
|
|
2899
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
2900
|
-
/>,
|
|
2901
|
-
<CapDropdown
|
|
2902
|
-
overlay={(
|
|
2903
|
-
<CapMenu>
|
|
2904
|
-
<>
|
|
2905
|
-
<CapMenu.Item
|
|
2906
|
-
className="ant-dropdown-menu-item"
|
|
2907
|
-
onClick={() => {}}
|
|
2908
|
-
>
|
|
2909
|
-
{formatMessage(globalMessages.preview)}
|
|
2910
|
-
</CapMenu.Item>
|
|
2911
|
-
<CapMenu.Item
|
|
2912
|
-
className="ant-dropdown-menu-item"
|
|
2913
|
-
onClick={rcsDltCardDeleteHandler}
|
|
2914
|
-
>
|
|
2915
|
-
{formatMessage(globalMessages.delete)}
|
|
2916
|
-
</CapMenu.Item>
|
|
2917
|
-
</>
|
|
2918
|
-
</CapMenu>
|
|
2919
|
-
)}
|
|
2920
|
-
>
|
|
2921
|
-
<CapIcon type="more" />
|
|
2922
|
-
</CapDropdown>,
|
|
2923
|
-
];
|
|
2924
|
-
return {
|
|
2925
|
-
title: channel,
|
|
2926
|
-
content,
|
|
2927
|
-
cardType: channel,
|
|
2928
|
-
extra,
|
|
2929
|
-
};
|
|
2930
|
-
};
|
|
2931
|
-
|
|
2932
1340
|
// Render character count for description/message
|
|
2933
1341
|
const renderDescriptionCharacterCount = (className = "rcs-character-count") => {
|
|
2934
1342
|
const currentLength = getDescriptionCharacterCount();
|
|
@@ -2944,26 +1352,6 @@ const onTitleAddVar = () => {
|
|
|
2944
1352
|
);
|
|
2945
1353
|
};
|
|
2946
1354
|
|
|
2947
|
-
// Carousel: per-card character counts (same limits as rich card)
|
|
2948
|
-
const getCarouselTitleCharacterCount = (cardIndex) => {
|
|
2949
|
-
const titleText = carouselData?.[cardIndex]?.title || '';
|
|
2950
|
-
return titleText ? titleText.length : 0;
|
|
2951
|
-
};
|
|
2952
|
-
|
|
2953
|
-
const getCarouselDescriptionCharacterCount = (cardIndex) => {
|
|
2954
|
-
const descriptionText = carouselData?.[cardIndex]?.description || '';
|
|
2955
|
-
return descriptionText ? descriptionText.length : 0;
|
|
2956
|
-
};
|
|
2957
|
-
|
|
2958
|
-
const renderCarouselCharacterCount = (currentLength, maxLength, className = "rcs-character-count") => (
|
|
2959
|
-
<CapLabel type="label1" className={className}>
|
|
2960
|
-
{formatMessage(messages.templateMessageLength, {
|
|
2961
|
-
currentLength,
|
|
2962
|
-
maxLength,
|
|
2963
|
-
})}
|
|
2964
|
-
</CapLabel>
|
|
2965
|
-
);
|
|
2966
|
-
|
|
2967
1355
|
// Check if any RCS variables contain tags (similar to Zalo hasTag logic)
|
|
2968
1356
|
const hasTag = () => {
|
|
2969
1357
|
// Check cardVarMapped values for tags
|
|
@@ -3006,27 +1394,78 @@ const onTitleAddVar = () => {
|
|
|
3006
1394
|
setDltEditData(data);
|
|
3007
1395
|
};
|
|
3008
1396
|
|
|
3009
|
-
const rcsDltEditCompletionHandler = (data) => {
|
|
3010
|
-
const tempData = {
|
|
3011
|
-
versions: {
|
|
3012
|
-
// eslint-disable-next-line no-undef
|
|
3013
|
-
base: data?.value?.base || {},
|
|
3014
|
-
},
|
|
1397
|
+
const rcsDltEditCompletionHandler = (data) => {
|
|
1398
|
+
const tempData = {
|
|
1399
|
+
versions: {
|
|
1400
|
+
// eslint-disable-next-line no-undef
|
|
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,
|
|
3015
1468
|
};
|
|
3016
|
-
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
3017
|
-
'',
|
|
3018
|
-
);
|
|
3019
|
-
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
3020
|
-
|| get(tempData, 'versions.base.name', '')
|
|
3021
|
-
|| '';
|
|
3022
|
-
closeDltContainerHandler();
|
|
3023
|
-
setDltEditData(tempData);
|
|
3024
|
-
const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
|
|
3025
|
-
setSmsFallbackData({
|
|
3026
|
-
templateName: templateNameFromDlt,
|
|
3027
|
-
content: fallMsg,
|
|
3028
|
-
...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
|
|
3029
|
-
});
|
|
3030
1469
|
};
|
|
3031
1470
|
|
|
3032
1471
|
const getDltSlideBoxContent = () => {
|
|
@@ -3074,34 +1513,148 @@ const onTitleAddVar = () => {
|
|
|
3074
1513
|
return { dltHeader, dltContent };
|
|
3075
1514
|
};
|
|
3076
1515
|
|
|
3077
|
-
const renderFallBackSmsComponent = () =>
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
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
|
+
};
|
|
3101
1654
|
|
|
3102
1655
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
3103
1656
|
setImageError(null);
|
|
3104
|
-
const isRcsThumbnail =
|
|
1657
|
+
const isRcsThumbnail = index === 1;
|
|
3105
1658
|
actions.uploadRcsAsset(file, type, {
|
|
3106
1659
|
isRcsThumbnail,
|
|
3107
1660
|
...fileParams,
|
|
@@ -3139,7 +1692,10 @@ const onTitleAddVar = () => {
|
|
|
3139
1692
|
const updateOnRcsImageReUpload = useCallback(() => {
|
|
3140
1693
|
setUpdateRcsImageSrc('');
|
|
3141
1694
|
}, []);
|
|
1695
|
+
|
|
1696
|
+
|
|
3142
1697
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1698
|
+
setImageError(null);
|
|
3143
1699
|
actions.uploadRcsAsset(file, type, {
|
|
3144
1700
|
...fileParams,
|
|
3145
1701
|
type: 'video',
|
|
@@ -3148,6 +1704,9 @@ const onTitleAddVar = () => {
|
|
|
3148
1704
|
});
|
|
3149
1705
|
};
|
|
3150
1706
|
|
|
1707
|
+
const updateRcsVideoSrc = (val) => {
|
|
1708
|
+
setRcsVideoSrc(val);
|
|
1709
|
+
};
|
|
3151
1710
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
3152
1711
|
setRcsVideoSrc(val);
|
|
3153
1712
|
setAssetList(val);
|
|
@@ -3170,13 +1729,13 @@ const onTitleAddVar = () => {
|
|
|
3170
1729
|
updateRcsThumbnailSrc('');
|
|
3171
1730
|
};
|
|
3172
1731
|
|
|
3173
|
-
|
|
1732
|
+
const renderThumbnailComponent = () => {
|
|
3174
1733
|
const currentDimension = selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
3175
1734
|
return !isEditFlow && (
|
|
3176
1735
|
<>
|
|
3177
1736
|
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
3178
1737
|
<CapImageUpload
|
|
3179
|
-
|
|
1738
|
+
style={{ paddingTop: '20px' }}
|
|
3180
1739
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
3181
1740
|
imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
|
|
3182
1741
|
imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
|
|
@@ -3194,7 +1753,6 @@ const onTitleAddVar = () => {
|
|
|
3194
1753
|
channel={RCS}
|
|
3195
1754
|
channelSpecificStyle={!isFullMode}
|
|
3196
1755
|
skipDimensionValidation={true}
|
|
3197
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
3198
1756
|
/>
|
|
3199
1757
|
</>
|
|
3200
1758
|
)
|
|
@@ -3220,7 +1778,7 @@ const onTitleAddVar = () => {
|
|
|
3220
1778
|
value: dim.type,
|
|
3221
1779
|
label: `${dim.label}`
|
|
3222
1780
|
}))}
|
|
3223
|
-
|
|
1781
|
+
style={{ marginBottom: '20px' }}
|
|
3224
1782
|
/>
|
|
3225
1783
|
</>
|
|
3226
1784
|
)}
|
|
@@ -3234,7 +1792,7 @@ const onTitleAddVar = () => {
|
|
|
3234
1792
|
</div>
|
|
3235
1793
|
) : (
|
|
3236
1794
|
<CapImageUpload
|
|
3237
|
-
|
|
1795
|
+
style={{ paddingTop: '20px' }}
|
|
3238
1796
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
3239
1797
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
3240
1798
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -3245,7 +1803,7 @@ const onTitleAddVar = () => {
|
|
|
3245
1803
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
3246
1804
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
3247
1805
|
index={0}
|
|
3248
|
-
className="cap-custom-image-upload
|
|
1806
|
+
className="cap-custom-image-upload"
|
|
3249
1807
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
3250
1808
|
imageData={rcsData}
|
|
3251
1809
|
channel={RCS}
|
|
@@ -3256,7 +1814,7 @@ const onTitleAddVar = () => {
|
|
|
3256
1814
|
|
|
3257
1815
|
</>
|
|
3258
1816
|
);
|
|
3259
|
-
|
|
1817
|
+
}
|
|
3260
1818
|
|
|
3261
1819
|
const renderVideoComponent = () => {
|
|
3262
1820
|
const currentDimension =selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
@@ -3275,7 +1833,7 @@ const onTitleAddVar = () => {
|
|
|
3275
1833
|
value: dim.type,
|
|
3276
1834
|
label: `${dim.label}`
|
|
3277
1835
|
}))}
|
|
3278
|
-
|
|
1836
|
+
style={{ marginBottom: '20px' }}
|
|
3279
1837
|
/>
|
|
3280
1838
|
)}
|
|
3281
1839
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -3333,48 +1891,10 @@ const onTitleAddVar = () => {
|
|
|
3333
1891
|
};
|
|
3334
1892
|
|
|
3335
1893
|
const getRcsPreview = () => {
|
|
3336
|
-
|
|
3337
|
-
if (templateType === contentType.carousel) {
|
|
3338
|
-
const cardsForPreview = buildCarouselCardsForPreview(carouselData);
|
|
3339
|
-
const carouselDimKey = getCarouselDimensionKey();
|
|
3340
|
-
const carouselImgDims =
|
|
3341
|
-
RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
|
|
3342
|
-
const carouselVidDims =
|
|
3343
|
-
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
|
|
3344
|
-
|| RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
|
|
3345
|
-
// Debug log for embedded/library mode preview payload (carousel)
|
|
3346
|
-
// eslint-disable-next-line no-console
|
|
3347
|
-
return (
|
|
3348
|
-
<UnifiedPreview
|
|
3349
|
-
channel={RCS}
|
|
3350
|
-
content={{
|
|
3351
|
-
carouselData: cardsForPreview,
|
|
3352
|
-
carouselPreviewDimensions: {
|
|
3353
|
-
imageWidth: carouselImgDims.width,
|
|
3354
|
-
imageHeight: carouselImgDims.height,
|
|
3355
|
-
videoThumbWidth: carouselVidDims.width,
|
|
3356
|
-
videoThumbHeight: carouselVidDims.height,
|
|
3357
|
-
},
|
|
3358
|
-
}}
|
|
3359
|
-
device={ANDROID}
|
|
3360
|
-
showDeviceToggle={false}
|
|
3361
|
-
showHeader={false}
|
|
3362
|
-
formatMessage={formatMessage}
|
|
3363
|
-
/>
|
|
3364
|
-
);
|
|
3365
|
-
}
|
|
3366
1894
|
|
|
3367
1895
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
3368
|
-
const
|
|
3369
|
-
const
|
|
3370
|
-
? 0
|
|
3371
|
-
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
3372
|
-
const resolvedTitle = isMediaTypeText
|
|
3373
|
-
? ''
|
|
3374
|
-
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
3375
|
-
const resolvedDesc = isSlotMappingMode
|
|
3376
|
-
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
3377
|
-
: templateDesc;
|
|
1896
|
+
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1897
|
+
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
3378
1898
|
return (
|
|
3379
1899
|
<UnifiedPreview
|
|
3380
1900
|
channel={RCS}
|
|
@@ -3397,108 +1917,51 @@ const onTitleAddVar = () => {
|
|
|
3397
1917
|
);
|
|
3398
1918
|
};
|
|
3399
1919
|
|
|
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
|
+
|
|
3400
1951
|
const createPayload = () => {
|
|
3401
|
-
const
|
|
1952
|
+
const base = get(dltEditData, `versions.base`, {});
|
|
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;
|
|
3402
1961
|
const alignment = isMediaTypeImage
|
|
3403
1962
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
3404
1963
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
3405
1964
|
|
|
3406
|
-
const heightTypeForCardWidth = isMediaTypeText
|
|
3407
|
-
? undefined
|
|
3408
|
-
: isMediaTypeImage
|
|
3409
|
-
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
|
|
3410
|
-
: isMediaTypeVideo
|
|
3411
|
-
? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
|
|
3412
|
-
: undefined;
|
|
3413
|
-
const cardWidthFromSelection =
|
|
3414
|
-
heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
|
|
3415
|
-
|
|
3416
|
-
/** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
|
|
3417
|
-
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
3418
|
-
const smsFallbackMerged = !isFullMode
|
|
3419
|
-
? (() => {
|
|
3420
|
-
const local =
|
|
3421
|
-
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
3422
|
-
return {
|
|
3423
|
-
...smsFromApiShape,
|
|
3424
|
-
...local,
|
|
3425
|
-
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
3426
|
-
};
|
|
3427
|
-
})()
|
|
3428
|
-
: (smsFallbackData || {});
|
|
3429
|
-
const smsFallbackForPayload = (() => {
|
|
3430
|
-
if (isFullMode) {
|
|
3431
|
-
return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
|
|
3432
|
-
}
|
|
3433
|
-
const mapped = {
|
|
3434
|
-
templateName:
|
|
3435
|
-
smsFallbackMerged.templateName
|
|
3436
|
-
|| smsFallbackMerged.smsTemplateName
|
|
3437
|
-
|| '',
|
|
3438
|
-
// Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
|
|
3439
|
-
content:
|
|
3440
|
-
smsFallbackMerged.content
|
|
3441
|
-
|| smsFallbackMerged.smsContent
|
|
3442
|
-
|| smsFallbackMerged.smsTemplateContent
|
|
3443
|
-
|| smsFallbackMerged.message
|
|
3444
|
-
|| '',
|
|
3445
|
-
templateContent:
|
|
3446
|
-
pickFirstSmsFallbackTemplateString(smsFallbackMerged)
|
|
3447
|
-
|| '',
|
|
3448
|
-
...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
|
|
3449
|
-
&& { unicodeValidity: smsFallbackMerged.unicodeValidity }),
|
|
3450
|
-
...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
3451
|
-
&& Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
3452
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
3453
|
-
smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
3454
|
-
}),
|
|
3455
|
-
};
|
|
3456
|
-
return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
|
|
3457
|
-
})();
|
|
3458
|
-
|
|
3459
|
-
const carouselCardContent = isCarouselType
|
|
3460
|
-
? (carouselData || []).map((card = {}) => {
|
|
3461
|
-
const cardMediaType = card.mediaType;
|
|
3462
|
-
const isCardVideo = cardMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
3463
|
-
const isCardImage = cardMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
3464
|
-
const mediaUrl = isCardVideo
|
|
3465
|
-
? (card.videoAsset?.videoSrc || '')
|
|
3466
|
-
: (card.imageSrc || '');
|
|
3467
|
-
const thumbnailUrl = isCardVideo
|
|
3468
|
-
? (card.thumbnailSrc || card.videoAsset?.videoThumbnail || '')
|
|
3469
|
-
: '';
|
|
3470
|
-
const cardVarTokens = [
|
|
3471
|
-
...((card.title || '').match(rcsVarRegex) ?? []),
|
|
3472
|
-
...((card.description || '').match(rcsVarRegex) ?? []),
|
|
3473
|
-
];
|
|
3474
|
-
const cardVarMappedForCard = {};
|
|
3475
|
-
if (isSlotMappingMode && cardVarTokens.length > 0) {
|
|
3476
|
-
cardVarTokens.forEach((token) => {
|
|
3477
|
-
const varName = getVarNameFromToken(token);
|
|
3478
|
-
if (!varName) return;
|
|
3479
|
-
cardVarMappedForCard[varName] = sanitizeCardVarMappedValue(cardVarMapped?.[varName] ?? '');
|
|
3480
|
-
});
|
|
3481
|
-
}
|
|
3482
|
-
return {
|
|
3483
|
-
title: card.title || '',
|
|
3484
|
-
description: card.description || '',
|
|
3485
|
-
mediaType: cardMediaType,
|
|
3486
|
-
...((isCardImage || isCardVideo) && {
|
|
3487
|
-
media: {
|
|
3488
|
-
mediaUrl,
|
|
3489
|
-
thumbnailUrl,
|
|
3490
|
-
height: selectedCarouselHeight || MEDIUM,
|
|
3491
|
-
...(isCardVideo && card.videoAsset?.videoName && { videoName: card.videoAsset.videoName }),
|
|
3492
|
-
},
|
|
3493
|
-
}),
|
|
3494
|
-
...(Array.isArray(card.suggestions) && card.suggestions.length > 0 && {
|
|
3495
|
-
suggestions: card.suggestions,
|
|
3496
|
-
}),
|
|
3497
|
-
...(Object.keys(cardVarMappedForCard).length > 0 && { cardVarMapped: cardVarMappedForCard }),
|
|
3498
|
-
};
|
|
3499
|
-
})
|
|
3500
|
-
: null;
|
|
3501
|
-
|
|
3502
1965
|
const payload = {
|
|
3503
1966
|
name: templateName,
|
|
3504
1967
|
versions: {
|
|
@@ -3507,22 +1970,16 @@ const onTitleAddVar = () => {
|
|
|
3507
1970
|
RCS: {
|
|
3508
1971
|
rcsContent: {
|
|
3509
1972
|
...(rcsAccount && !isFullMode && { accountId: rcsAccount }),
|
|
3510
|
-
cardType:
|
|
3511
|
-
cardSettings:
|
|
3512
|
-
?
|
|
3513
|
-
:
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
},
|
|
3518
|
-
cardContent: isCarouselType
|
|
3519
|
-
? carouselCardContent
|
|
3520
|
-
: [
|
|
1973
|
+
cardType: STANDALONE,
|
|
1974
|
+
cardSettings: {
|
|
1975
|
+
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
1976
|
+
...(alignment && { mediaAlignment: alignment }),
|
|
1977
|
+
cardWidth: SMALL,
|
|
1978
|
+
},
|
|
1979
|
+
cardContent: [
|
|
3521
1980
|
{
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
title: templateTitle,
|
|
3525
|
-
description: templateDesc,
|
|
1981
|
+
title: resolvedTitle,
|
|
1982
|
+
description: resolvedDesc,
|
|
3526
1983
|
mediaType: templateMediaType,
|
|
3527
1984
|
...(!isMediaTypeText && {media: {
|
|
3528
1985
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -3531,32 +1988,23 @@ const onTitleAddVar = () => {
|
|
|
3531
1988
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
3532
1989
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
3533
1990
|
}}),
|
|
3534
|
-
...(
|
|
3535
|
-
const
|
|
3536
|
-
...(templateTitle
|
|
3537
|
-
...(templateDesc
|
|
1991
|
+
...(!isFullMode && (() => {
|
|
1992
|
+
const tokens = [
|
|
1993
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
1994
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
3538
1995
|
];
|
|
3539
|
-
const
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
3550
|
-
cardVarMappedForRcsCardOnly,
|
|
3551
|
-
varName,
|
|
3552
|
-
slotIndexZeroBased,
|
|
3553
|
-
isSlotMappingMode,
|
|
3554
|
-
rcsSpanningSemanticVarNames.has(varName),
|
|
3555
|
-
);
|
|
3556
|
-
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
3557
|
-
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
1996
|
+
const allowedKeys = tokens
|
|
1997
|
+
.map((t) => getVarNameFromToken(t))
|
|
1998
|
+
.filter(Boolean);
|
|
1999
|
+
const nextMap = {};
|
|
2000
|
+
allowedKeys.forEach((k) => {
|
|
2001
|
+
if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
|
|
2002
|
+
nextMap[k] = cardVarMapped[k];
|
|
2003
|
+
} else {
|
|
2004
|
+
nextMap[k] = '';
|
|
2005
|
+
}
|
|
3558
2006
|
});
|
|
3559
|
-
return { cardVarMapped:
|
|
2007
|
+
return { cardVarMapped: nextMap };
|
|
3560
2008
|
})()),
|
|
3561
2009
|
...(suggestions.length > 0 && { suggestions }),
|
|
3562
2010
|
}
|
|
@@ -3564,109 +2012,17 @@ const onTitleAddVar = () => {
|
|
|
3564
2012
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
3565
2013
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
3566
2014
|
},
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
const m = smsFallbackMerged || {};
|
|
3579
|
-
const tcSibling = m.templateConfigs && typeof m.templateConfigs === 'object'
|
|
3580
|
-
? m.templateConfigs
|
|
3581
|
-
: {};
|
|
3582
|
-
const smsFallbackTemplateId =
|
|
3583
|
-
(m.smsTemplateId != null && String(m.smsTemplateId).trim() !== ''
|
|
3584
|
-
? String(m.smsTemplateId)
|
|
3585
|
-
: '')
|
|
3586
|
-
|| (tcSibling.templateId != null && String(tcSibling.templateId).trim() !== ''
|
|
3587
|
-
? String(tcSibling.templateId)
|
|
3588
|
-
: '');
|
|
3589
|
-
const smsFallbackTemplateStr =
|
|
3590
|
-
pickFirstSmsFallbackTemplateString(m)
|
|
3591
|
-
|| (typeof m.templateContent === 'string' ? m.templateContent : '')
|
|
3592
|
-
|| (typeof tcSibling.template === 'string' ? tcSibling.template : '')
|
|
3593
|
-
|| '';
|
|
3594
|
-
const smsFallbackTemplateName =
|
|
3595
|
-
m.templateName
|
|
3596
|
-
|| m.smsTemplateName
|
|
3597
|
-
|| tcSibling.templateName
|
|
3598
|
-
|| tcSibling.name
|
|
3599
|
-
|| '';
|
|
3600
|
-
const registeredSenderIdsForPayload = Array.isArray(m.registeredSenderIds)
|
|
3601
|
-
? m.registeredSenderIds
|
|
3602
|
-
: Array.isArray(tcSibling.registeredSenderIds)
|
|
3603
|
-
? tcSibling.registeredSenderIds
|
|
3604
|
-
: Array.isArray(tcSibling.header)
|
|
3605
|
-
? tcSibling.header
|
|
3606
|
-
: null;
|
|
3607
|
-
const hasRegisteredSenderIds = Array.isArray(registeredSenderIdsForPayload);
|
|
3608
|
-
const smsFallbackTemplateConfigs =
|
|
3609
|
-
smsFallbackTemplateId || hasRegisteredSenderIds
|
|
3610
|
-
? {
|
|
3611
|
-
...(smsFallbackTemplateId && { templateId: smsFallbackTemplateId }),
|
|
3612
|
-
...(smsFallbackTemplateStr && { template: smsFallbackTemplateStr }),
|
|
3613
|
-
...(smsFallbackTemplateName && {
|
|
3614
|
-
templateName: smsFallbackTemplateName,
|
|
3615
|
-
}),
|
|
3616
|
-
...(hasRegisteredSenderIds && {
|
|
3617
|
-
registeredSenderIds: registeredSenderIdsForPayload,
|
|
3618
|
-
}),
|
|
3619
|
-
}
|
|
3620
|
-
: null;
|
|
3621
|
-
const isDltCampaign = !isFullMode && isTraiDLTEnable(isFullMode, smsRegister);
|
|
3622
|
-
return {
|
|
3623
|
-
smsFallBackContent: isFullMode
|
|
3624
|
-
? {
|
|
3625
|
-
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
3626
|
-
smsContent: smsBodyText,
|
|
3627
|
-
// cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
|
|
3628
|
-
message: smsBodyText,
|
|
3629
|
-
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
3630
|
-
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
3631
|
-
}),
|
|
3632
|
-
...(smsFallbackForPayload.rcsSmsFallbackVarMapped
|
|
3633
|
-
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
3634
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
3635
|
-
}),
|
|
3636
|
-
...(smsFallbackTemplateConfigs && {
|
|
3637
|
-
templateConfigs: smsFallbackTemplateConfigs,
|
|
3638
|
-
}),
|
|
3639
|
-
}
|
|
3640
|
-
: {
|
|
3641
|
-
// Round-trip storage: full shape so reopening the editor restores template
|
|
3642
|
-
// name, sender IDs, and var mappings. normalizeRcsMessageContentForApi
|
|
3643
|
-
// (called in CreativesContainer getCreativesData) strips this to
|
|
3644
|
-
// { message, templateConfigs } before the API call — the extra fields here
|
|
3645
|
-
// are only used for re-hydration, never sent to the API.
|
|
3646
|
-
message: smsBodyText,
|
|
3647
|
-
...(smsFallbackForPayload.templateName && {
|
|
3648
|
-
smsTemplateName: smsFallbackForPayload.templateName,
|
|
3649
|
-
}),
|
|
3650
|
-
...(smsFallbackForPayload.templateContent && {
|
|
3651
|
-
templateContent: smsFallbackForPayload.templateContent,
|
|
3652
|
-
}),
|
|
3653
|
-
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
3654
|
-
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
3655
|
-
}),
|
|
3656
|
-
...(Array.isArray(registeredSenderIdsForPayload) && {
|
|
3657
|
-
registeredSenderIds: registeredSenderIdsForPayload,
|
|
3658
|
-
}),
|
|
3659
|
-
...(smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
3660
|
-
&& Object.keys(smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
3661
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
3662
|
-
smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
3663
|
-
}),
|
|
3664
|
-
...(isDltCampaign && smsFallbackTemplateConfigs && {
|
|
3665
|
-
templateConfigs: smsFallbackTemplateConfigs,
|
|
3666
|
-
}),
|
|
3667
|
-
},
|
|
3668
|
-
};
|
|
3669
|
-
})()),
|
|
2015
|
+
smsFallBackContent: {
|
|
2016
|
+
message: fallbackMessage,
|
|
2017
|
+
...(isDltEnabled && {
|
|
2018
|
+
templateConfigs: {
|
|
2019
|
+
templateId,
|
|
2020
|
+
templateName: template_name,
|
|
2021
|
+
template,
|
|
2022
|
+
registeredSenderIds,
|
|
2023
|
+
},
|
|
2024
|
+
}),
|
|
2025
|
+
},
|
|
3670
2026
|
},
|
|
3671
2027
|
},
|
|
3672
2028
|
},
|
|
@@ -3676,141 +2032,6 @@ const onTitleAddVar = () => {
|
|
|
3676
2032
|
return payload;
|
|
3677
2033
|
};
|
|
3678
2034
|
|
|
3679
|
-
/** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
|
|
3680
|
-
const testPreviewFormData = useMemo(() => {
|
|
3681
|
-
const payload = createPayload();
|
|
3682
|
-
const rcs = payload?.versions?.base?.content?.RCS;
|
|
3683
|
-
if (!rcs) return null;
|
|
3684
|
-
// createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
|
|
3685
|
-
const accountIdForCreateMessageMeta =
|
|
3686
|
-
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
3687
|
-
? String(wecrmAccountId)
|
|
3688
|
-
: accountId;
|
|
3689
|
-
const isSlotMappingModeForPreview = isEditFlow || !isFullMode;
|
|
3690
|
-
let rcsForTest = {
|
|
3691
|
-
...rcs,
|
|
3692
|
-
rcsContent: {
|
|
3693
|
-
...rcs.rcsContent,
|
|
3694
|
-
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
3695
|
-
},
|
|
3696
|
-
};
|
|
3697
|
-
/** Approval payload keeps numeric-only `cardVarMapped`; preview APIs still need semantic keys. */
|
|
3698
|
-
const cardContent = rcsForTest.rcsContent?.cardContent;
|
|
3699
|
-
if (Array.isArray(cardContent) && cardContent[0]) {
|
|
3700
|
-
if (isCarouselType) {
|
|
3701
|
-
// Carousel: resolve {{N}} slot tokens to the actual tag expressions / static values the
|
|
3702
|
-
// user mapped via cardVarMapped. Run this for ALL modes (create, edit, consumer) so that:
|
|
3703
|
-
// - buildRcsTestMessagePayload sends real Capillary tag names to the test API, and
|
|
3704
|
-
// - prepareTagExtractionPayload can extract tag metadata from the card content.
|
|
3705
|
-
// Track cumulative slotOffset across cards: each card's title/description vars occupy
|
|
3706
|
-
// sequential global slot indices ({{1}},{{2}} in card 0; {{3}},{{4}} in card 1, …).
|
|
3707
|
-
// Without the offset, every card restarts at slotKey="1" and resolves against card 0's
|
|
3708
|
-
// mappings, causing tags from card 1+ to be missing or wrong.
|
|
3709
|
-
let carouselSlotOffset = 0;
|
|
3710
|
-
rcsForTest = {
|
|
3711
|
-
...rcsForTest,
|
|
3712
|
-
rcsContent: {
|
|
3713
|
-
...rcsForTest.rcsContent,
|
|
3714
|
-
cardContent: cardContent.map((card) => {
|
|
3715
|
-
const rawTitle = card.title || '';
|
|
3716
|
-
const rawDesc = card.description || '';
|
|
3717
|
-
const titleVarCount = (rawTitle.match(rcsVarRegex) || []).length;
|
|
3718
|
-
const descVarCount = (rawDesc.match(rcsVarRegex) || []).length;
|
|
3719
|
-
const resolvedTitle = resolveTemplateWithMap(rawTitle, carouselSlotOffset);
|
|
3720
|
-
const resolvedDesc = resolveTemplateWithMap(rawDesc, carouselSlotOffset + titleVarCount);
|
|
3721
|
-
carouselSlotOffset += titleVarCount + descVarCount;
|
|
3722
|
-
return { ...card, title: resolvedTitle, description: resolvedDesc };
|
|
3723
|
-
}),
|
|
3724
|
-
},
|
|
3725
|
-
};
|
|
3726
|
-
} else if (isSlotMappingModeForPreview) {
|
|
3727
|
-
// Standalone card: coalesce cardVarMapped with the template's slot names so the preview
|
|
3728
|
-
// API receives a correctly-keyed var map for non-carousel templates.
|
|
3729
|
-
const fullCardVarMapped = coalesceCardVarMappedToTemplate(
|
|
3730
|
-
pickRcsCardVarMappedEntries(cardVarMapped),
|
|
3731
|
-
templateTitle,
|
|
3732
|
-
templateDesc,
|
|
3733
|
-
rcsVarRegex,
|
|
3734
|
-
);
|
|
3735
|
-
rcsForTest = {
|
|
3736
|
-
...rcsForTest,
|
|
3737
|
-
rcsContent: {
|
|
3738
|
-
...rcsForTest.rcsContent,
|
|
3739
|
-
cardContent: [
|
|
3740
|
-
{ ...cardContent[0], cardVarMapped: fullCardVarMapped },
|
|
3741
|
-
...cardContent.slice(1),
|
|
3742
|
-
],
|
|
3743
|
-
},
|
|
3744
|
-
};
|
|
3745
|
-
}
|
|
3746
|
-
}
|
|
3747
|
-
const out = {
|
|
3748
|
-
versions: {
|
|
3749
|
-
base: {
|
|
3750
|
-
content: {
|
|
3751
|
-
RCS: rcsForTest,
|
|
3752
|
-
},
|
|
3753
|
-
},
|
|
3754
|
-
},
|
|
3755
|
-
};
|
|
3756
|
-
// Use the smsFallbackTemplateConfigs already built by createPayload() — it uses smsFallbackMerged
|
|
3757
|
-
// which merges smsFromApiShape (API baseline with templateId, registeredSenderIds, templateName, etc.)
|
|
3758
|
-
// with local smsFallbackData state. Reading raw smsFallbackData here would miss the API-sourced
|
|
3759
|
-
// smsTemplateId in campaign mode (smsFallbackMerged = { ...smsFromApiShape, ...local } there).
|
|
3760
|
-
// This also keeps templateName (and all other fields) in sync with the approval payload automatically.
|
|
3761
|
-
const smsFallbackTcFromPayload = rcs?.rcsContent?.smsFallBackContent?.templateConfigs;
|
|
3762
|
-
if (smsFallbackTcFromPayload && typeof smsFallbackTcFromPayload === 'object') {
|
|
3763
|
-
out.templateConfigs = {
|
|
3764
|
-
...smsFallbackTcFromPayload,
|
|
3765
|
-
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
3766
|
-
};
|
|
3767
|
-
}
|
|
3768
|
-
return out;
|
|
3769
|
-
}, [
|
|
3770
|
-
templateName,
|
|
3771
|
-
templateTitle,
|
|
3772
|
-
templateDesc,
|
|
3773
|
-
templateMediaType,
|
|
3774
|
-
cardVarMapped,
|
|
3775
|
-
carouselData,
|
|
3776
|
-
suggestions,
|
|
3777
|
-
rcsImageSrc,
|
|
3778
|
-
rcsVideoSrc,
|
|
3779
|
-
rcsThumbnailSrc,
|
|
3780
|
-
selectedDimension,
|
|
3781
|
-
smsFallbackData,
|
|
3782
|
-
isFullMode,
|
|
3783
|
-
isEditFlow,
|
|
3784
|
-
templateType,
|
|
3785
|
-
accountId,
|
|
3786
|
-
wecrmAccountId,
|
|
3787
|
-
accessToken,
|
|
3788
|
-
accountName,
|
|
3789
|
-
hostName,
|
|
3790
|
-
smsRegister,
|
|
3791
|
-
]);
|
|
3792
|
-
|
|
3793
|
-
/**
|
|
3794
|
-
* Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
|
|
3795
|
-
* with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
|
|
3796
|
-
* miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
|
|
3797
|
-
*/
|
|
3798
|
-
const librarySmsFallbackMergedForValidation = useMemo(() => {
|
|
3799
|
-
if (isFullMode) {
|
|
3800
|
-
return smsFallbackData;
|
|
3801
|
-
}
|
|
3802
|
-
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
3803
|
-
const local =
|
|
3804
|
-
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
3805
|
-
return {
|
|
3806
|
-
...smsFromApiShape,
|
|
3807
|
-
...local,
|
|
3808
|
-
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
3809
|
-
};
|
|
3810
|
-
}, [isFullMode, templateData, smsFallbackData]);
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
2035
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
3815
2036
|
// eslint-disable-next-line no-undef
|
|
3816
2037
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -3840,9 +2061,6 @@ const onTitleAddVar = () => {
|
|
|
3840
2061
|
_id: params?.id,
|
|
3841
2062
|
validity: true,
|
|
3842
2063
|
type: RCS,
|
|
3843
|
-
// CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
|
|
3844
|
-
// the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
|
|
3845
|
-
closeSlideBoxAfterSubmit: !isFullMode,
|
|
3846
2064
|
};
|
|
3847
2065
|
getFormData(formDataParams);
|
|
3848
2066
|
};
|
|
@@ -3856,7 +2074,6 @@ const onTitleAddVar = () => {
|
|
|
3856
2074
|
actionCallback({ resp, errorMessage });
|
|
3857
2075
|
setSpin(false); // Always turn off spinner
|
|
3858
2076
|
if (!errorMessage) {
|
|
3859
|
-
setTemplateStatus(RCS_STATUSES.pending);
|
|
3860
2077
|
onCreateComplete();
|
|
3861
2078
|
}
|
|
3862
2079
|
});
|
|
@@ -3868,91 +2085,6 @@ const onTitleAddVar = () => {
|
|
|
3868
2085
|
}
|
|
3869
2086
|
};
|
|
3870
2087
|
|
|
3871
|
-
/** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
|
|
3872
|
-
const smsFallbackBlocksDone = () => {
|
|
3873
|
-
// Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
|
|
3874
|
-
if (
|
|
3875
|
-
!isFullMode
|
|
3876
|
-
&& !isTraiDLTEnable(isFullMode, smsRegister)
|
|
3877
|
-
&& smsFallbackData == null
|
|
3878
|
-
&& hasMeaningfulSmsFallbackShape(
|
|
3879
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
3880
|
-
)
|
|
3881
|
-
) {
|
|
3882
|
-
return true;
|
|
3883
|
-
}
|
|
3884
|
-
if (!smsFallbackData) return false;
|
|
3885
|
-
// Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
|
|
3886
|
-
// concern, not a structural requirement for approval — the registered SMS template body stands on
|
|
3887
|
-
// its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
|
|
3888
|
-
if (isFullMode) return false;
|
|
3889
|
-
const merged = librarySmsFallbackMergedForValidation;
|
|
3890
|
-
const templateText = pickFirstSmsFallbackTemplateString(merged);
|
|
3891
|
-
if (!templateText) {
|
|
3892
|
-
return true;
|
|
3893
|
-
}
|
|
3894
|
-
const rawVarMap =
|
|
3895
|
-
merged.rcsSmsFallbackVarMapped
|
|
3896
|
-
|| merged['rcs-sms-fallback-var-mapped'];
|
|
3897
|
-
const varMap =
|
|
3898
|
-
rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
|
|
3899
|
-
return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
|
|
3900
|
-
};
|
|
3901
|
-
|
|
3902
|
-
/**
|
|
3903
|
-
* Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
|
|
3904
|
-
* semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
|
|
3905
|
-
* / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
|
|
3906
|
-
*/
|
|
3907
|
-
const isLibraryCampaignCardVarMappingIncomplete = () => {
|
|
3908
|
-
if (isFullMode) return false;
|
|
3909
|
-
const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
|
|
3910
|
-
rcsVarTestRegex.test(elem),
|
|
3911
|
-
);
|
|
3912
|
-
const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
|
|
3913
|
-
rcsVarTestRegex.test(elem),
|
|
3914
|
-
);
|
|
3915
|
-
const orderedVarNames = [
|
|
3916
|
-
...titleTokens.map((t) => t.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '')),
|
|
3917
|
-
...descTokens.map((t) => t.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '')),
|
|
3918
|
-
];
|
|
3919
|
-
if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
|
|
3920
|
-
return true;
|
|
3921
|
-
}
|
|
3922
|
-
return orderedVarNames.some((name, globalIdx) => {
|
|
3923
|
-
const v = resolveCardVarMappedSlotValue(
|
|
3924
|
-
cardVarMapped,
|
|
3925
|
-
name,
|
|
3926
|
-
globalIdx,
|
|
3927
|
-
true,
|
|
3928
|
-
rcsSpanningSemanticVarNames.has(name),
|
|
3929
|
-
);
|
|
3930
|
-
const s = v == null ? '' : String(v);
|
|
3931
|
-
return s.trim() === '';
|
|
3932
|
-
});
|
|
3933
|
-
};
|
|
3934
|
-
|
|
3935
|
-
const isCarouselLibraryIncomplete = () => {
|
|
3936
|
-
if (!isCarouselType || isFullMode) return false;
|
|
3937
|
-
if ((carouselErrors || []).some((err) => err?.title || err?.description)) return true;
|
|
3938
|
-
// Block if any card's title or description has no content at all (no static text and no variable tokens).
|
|
3939
|
-
const hasEmptyField = (carouselData || []).some((card) =>
|
|
3940
|
-
['title', 'description'].some((field) => !(card?.[field] || '').trim())
|
|
3941
|
-
);
|
|
3942
|
-
if (hasEmptyField) return true;
|
|
3943
|
-
const unfilledVar = (carouselData || []).some((card) =>
|
|
3944
|
-
['title', 'description'].some((field) => {
|
|
3945
|
-
const tokens = splitTemplateVarStringRcs(card?.[field] || '').filter((t) => rcsVarTestRegex.test(t));
|
|
3946
|
-
return tokens.some((t) => {
|
|
3947
|
-
const name = t.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
|
|
3948
|
-
const v = cardVarMapped?.[name];
|
|
3949
|
-
return v == null || String(v).trim() === '';
|
|
3950
|
-
});
|
|
3951
|
-
})
|
|
3952
|
-
);
|
|
3953
|
-
return unfilledVar;
|
|
3954
|
-
};
|
|
3955
|
-
|
|
3956
2088
|
const isDisableDone = () => {
|
|
3957
2089
|
if(isEditFlow){
|
|
3958
2090
|
return false;
|
|
@@ -3963,26 +2095,46 @@ const onTitleAddVar = () => {
|
|
|
3963
2095
|
}
|
|
3964
2096
|
}
|
|
3965
2097
|
|
|
3966
|
-
if
|
|
3967
|
-
|
|
3968
|
-
|
|
2098
|
+
if(!isFullMode){
|
|
2099
|
+
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2100
|
+
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2101
|
+
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
3969
2102
|
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
2103
|
+
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2104
|
+
return true;
|
|
2105
|
+
}
|
|
3973
2106
|
|
|
3974
|
-
|
|
3975
|
-
|
|
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
|
+
}
|
|
2118
|
+
|
|
2119
|
+
const anyMissing = allVars.some(name => {
|
|
2120
|
+
const v = cardVarMapped?.[name];
|
|
2121
|
+
if (typeof v !== 'string') return !v;
|
|
2122
|
+
return v.trim() === '';
|
|
2123
|
+
});
|
|
2124
|
+
if (anyMissing) {
|
|
2125
|
+
return true;
|
|
2126
|
+
}
|
|
3976
2127
|
}
|
|
3977
2128
|
|
|
3978
|
-
|
|
2129
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
3979
2130
|
return true;
|
|
2131
|
+
|
|
3980
2132
|
}
|
|
3981
|
-
if (
|
|
2133
|
+
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
3982
2134
|
return true;
|
|
3983
2135
|
}
|
|
3984
2136
|
|
|
3985
|
-
if (
|
|
2137
|
+
if (isMediaTypeVideo && (rcsVideoSrc.videoSrc === '' || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
3986
2138
|
return true;
|
|
3987
2139
|
}
|
|
3988
2140
|
if (buttonType.includes(CTA)) {
|
|
@@ -3994,60 +2146,72 @@ const onTitleAddVar = () => {
|
|
|
3994
2146
|
return true;
|
|
3995
2147
|
}
|
|
3996
2148
|
}
|
|
3997
|
-
if (templateDescError || templateTitleError) {
|
|
3998
|
-
return true;
|
|
3999
|
-
}
|
|
4000
|
-
if (
|
|
4001
|
-
smsFallbackData?.content
|
|
4002
|
-
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
4003
|
-
) {
|
|
2149
|
+
if (templateDescError || templateTitleError || fallbackMessageError) {
|
|
4004
2150
|
return true;
|
|
4005
2151
|
}
|
|
4006
2152
|
return false;
|
|
4007
2153
|
};
|
|
4008
2154
|
|
|
4009
2155
|
const isEditDisableDone = () => {
|
|
4010
|
-
|
|
2156
|
+
|
|
2157
|
+
if (templateStatus !== RCS_STATUSES.approved) {
|
|
4011
2158
|
return true;
|
|
4012
2159
|
}
|
|
4013
2160
|
|
|
4014
|
-
if (
|
|
4015
|
-
|
|
2161
|
+
if (!isFullMode) {
|
|
2162
|
+
if (templateName.trim() === '' || templateNameError) {
|
|
2163
|
+
return true;
|
|
2164
|
+
}
|
|
4016
2165
|
}
|
|
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 ]));
|
|
4017
2170
|
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
2171
|
+
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
4021
2174
|
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
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
|
+
}
|
|
4025
2186
|
|
|
4026
|
-
|
|
2187
|
+
const anyMissing = allVars.some(name => {
|
|
2188
|
+
const v = cardVarMapped?.[name];
|
|
2189
|
+
if (typeof v !== 'string') return !v;
|
|
2190
|
+
return v.trim() === '';
|
|
2191
|
+
});
|
|
2192
|
+
if (anyMissing) {
|
|
2193
|
+
return true;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
4027
2197
|
return true;
|
|
4028
2198
|
}
|
|
4029
|
-
if (
|
|
2199
|
+
if (isMediaTypeImage && rcsImageSrc === '') {
|
|
4030
2200
|
return true;
|
|
4031
2201
|
}
|
|
4032
|
-
if
|
|
2202
|
+
if(isMediaTypeVideo && (rcsThumbnailSrc === '' || rcsVideoSrc.videoSrc === '')) {
|
|
4033
2203
|
return true;
|
|
4034
2204
|
}
|
|
4035
2205
|
|
|
4036
2206
|
if (buttonType.includes(CTA)) {
|
|
4037
|
-
const hasValidButtons = suggestions.every(suggestion =>
|
|
2207
|
+
const hasValidButtons = suggestions.every(suggestion =>
|
|
4038
2208
|
suggestion.text && suggestion.url && !suggestionError && !forbiddenCharactersValidation(suggestion.text)
|
|
4039
2209
|
);
|
|
4040
2210
|
if (!hasValidButtons) {
|
|
4041
2211
|
return true;
|
|
4042
2212
|
}
|
|
4043
2213
|
}
|
|
4044
|
-
if (templateTitleError || templateDescError) {
|
|
4045
|
-
return true;
|
|
4046
|
-
}
|
|
4047
|
-
if (
|
|
4048
|
-
smsFallbackData?.content
|
|
4049
|
-
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
4050
|
-
) {
|
|
2214
|
+
if (templateTitleError || templateDescError || fallbackMessageError) {
|
|
4051
2215
|
return true;
|
|
4052
2216
|
}
|
|
4053
2217
|
return false;
|
|
@@ -4097,111 +2261,85 @@ const onTitleAddVar = () => {
|
|
|
4097
2261
|
};
|
|
4098
2262
|
|
|
4099
2263
|
const getMainContent = () => {
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
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
|
+
|
|
4103
2278
|
return (
|
|
4104
2279
|
<>
|
|
4105
|
-
{templateStatus !== '' && (
|
|
4106
|
-
<
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
{
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
)}
|
|
4118
|
-
</CapColumn>
|
|
4119
|
-
</CapRow>
|
|
2280
|
+
{templateStatus !== '' && (<CapRow useLegacy className="template-status-container">
|
|
2281
|
+
<CapLabel type="label2">
|
|
2282
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
2283
|
+
</CapLabel>
|
|
2284
|
+
|
|
2285
|
+
{templateStatus && (
|
|
2286
|
+
<CapAlert
|
|
2287
|
+
message={getTemplateStatusMessage()}
|
|
2288
|
+
type={getTemplateStatusType(templateStatus)}
|
|
2289
|
+
/>
|
|
2290
|
+
)}
|
|
2291
|
+
</CapRow>
|
|
4120
2292
|
)}
|
|
4121
|
-
<CapRow className=
|
|
2293
|
+
<CapRow className="cap-rcs-creatives">
|
|
4122
2294
|
<CapColumn span={14}>
|
|
4123
2295
|
{/* template name */}
|
|
4124
2296
|
{isFullMode && (
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
onChange={onTemplateNameChange}
|
|
4139
|
-
errorMessage={templateNameError}
|
|
4140
|
-
placeholder={formatMessage(
|
|
4141
|
-
globalMessages.templateNamePlaceholder,
|
|
4142
|
-
)}
|
|
4143
|
-
value={templateName || ''}
|
|
4144
|
-
size="default"
|
|
4145
|
-
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
4146
|
-
disabled={(isEditFlow || !isFullMode)}
|
|
4147
|
-
/>
|
|
4148
|
-
)
|
|
4149
|
-
)}
|
|
4150
|
-
{isEditFlow ? (
|
|
4151
|
-
<div className="rcs-creative-name-readonly">
|
|
4152
|
-
<CapHeading type="h4">
|
|
4153
|
-
{formatMessage(messages.templateTypeLabel)}
|
|
4154
|
-
</CapHeading>
|
|
4155
|
-
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
4156
|
-
{TEMPLATE_TYPE_OPTIONS.find((option) => option.value === templateType)?.label || '-'}
|
|
4157
|
-
</CapHeading>
|
|
4158
|
-
</div>
|
|
4159
|
-
) : (
|
|
4160
|
-
<>
|
|
4161
|
-
{renderLabel('templateTypeLabel')}
|
|
4162
|
-
<ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
|
|
4163
|
-
<CapRadioGroup
|
|
4164
|
-
id="select-rcs-template-type"
|
|
4165
|
-
options={TEMPLATE_TYPE_OPTIONS}
|
|
4166
|
-
onChange={onTemplateTypeChange}
|
|
4167
|
-
value={templateType}
|
|
4168
|
-
disabled={!isFullMode}
|
|
4169
|
-
/>
|
|
4170
|
-
</ConfigProvider>
|
|
4171
|
-
</>
|
|
2297
|
+
<CapInput
|
|
2298
|
+
id="rcs_template_name_input"
|
|
2299
|
+
data-testid="template_name"
|
|
2300
|
+
onChange={onTemplateNameChange}
|
|
2301
|
+
errorMessage={templateNameError}
|
|
2302
|
+
placeholder={formatMessage(
|
|
2303
|
+
globalMessages.templateNamePlaceholder,
|
|
2304
|
+
)}
|
|
2305
|
+
value={templateName || ''}
|
|
2306
|
+
size="default"
|
|
2307
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
2308
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2309
|
+
/>
|
|
4172
2310
|
)}
|
|
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>
|
|
4173
2321
|
|
|
4174
|
-
{
|
|
4175
|
-
|
|
4176
|
-
) : (
|
|
2322
|
+
{/* Show media only for rich_card or carousel */}
|
|
2323
|
+
{(templateType === contentType.rich_card || templateType === contentType.carousel) && (
|
|
4177
2324
|
<>
|
|
4178
|
-
{
|
|
4179
|
-
|
|
4180
|
-
<>
|
|
4181
|
-
{renderLabel('mediaLabel')}
|
|
4182
|
-
<ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
|
|
2325
|
+
{renderLabel('mediaLabel')}
|
|
2326
|
+
<ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
|
|
4183
2327
|
<CapRadioGroup
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
2328
|
+
options={mediaRadioOptions || []}
|
|
2329
|
+
value={templateMediaType}
|
|
2330
|
+
onChange={onTemplateMediaTypeChange}
|
|
2331
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2332
|
+
className="rcs-radio"
|
|
2333
|
+
/>
|
|
2334
|
+
</ConfigProvider>
|
|
4191
2335
|
<div className="rcs-container-image">
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
</>
|
|
4195
|
-
)}
|
|
4196
|
-
{renderTextComponent()}
|
|
4197
|
-
</>
|
|
4198
|
-
)}
|
|
4199
|
-
{(!isEditFlow || hasMeaningfulSmsFallbackShape(smsFallbackData)) && (
|
|
4200
|
-
<>
|
|
4201
|
-
<CapDivider className="rcs-fallback-section-divider" />
|
|
4202
|
-
{renderFallBackSmsComponent()}
|
|
2336
|
+
{getMediaBasedComponent()}
|
|
2337
|
+
</div>
|
|
4203
2338
|
</>
|
|
4204
2339
|
)}
|
|
2340
|
+
{renderTextComponent()}
|
|
2341
|
+
<CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
|
|
2342
|
+
{renderFallBackSmsComponent()}
|
|
4205
2343
|
<div className="rcs-scroll-div" />
|
|
4206
2344
|
</CapColumn>
|
|
4207
2345
|
<CapColumn span={8} offset={2}>
|
|
@@ -4212,8 +2350,7 @@ const onTitleAddVar = () => {
|
|
|
4212
2350
|
|
|
4213
2351
|
|
|
4214
2352
|
<div className="rcs-footer">
|
|
4215
|
-
{
|
|
4216
|
-
{!isEditFlow && isFullMode && (
|
|
2353
|
+
{!isEditFlow && (
|
|
4217
2354
|
<>
|
|
4218
2355
|
<div className="button-disabled-tooltip-wrapper">
|
|
4219
2356
|
<CapButton
|
|
@@ -4221,9 +2358,7 @@ const onTitleAddVar = () => {
|
|
|
4221
2358
|
disabled={isDisableDone()}
|
|
4222
2359
|
className="rcs-done-btn"
|
|
4223
2360
|
>
|
|
4224
|
-
<FormattedMessage
|
|
4225
|
-
{...(isHostInfoBip ? messages.doneButtonLabel : messages.sendForApprovalButtonLabel)}
|
|
4226
|
-
/>
|
|
2361
|
+
<FormattedMessage {...messages.sendForApprovalButtonLabel} />
|
|
4227
2362
|
</CapButton>
|
|
4228
2363
|
</div>
|
|
4229
2364
|
<CapTooltip
|
|
@@ -4236,6 +2371,7 @@ const onTitleAddVar = () => {
|
|
|
4236
2371
|
className="rcs-test-preview-btn"
|
|
4237
2372
|
type="secondary"
|
|
4238
2373
|
disabled={true}
|
|
2374
|
+
style={{ marginLeft: "8px" }}
|
|
4239
2375
|
>
|
|
4240
2376
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
4241
2377
|
</CapButton>
|
|
@@ -4256,7 +2392,7 @@ const onTitleAddVar = () => {
|
|
|
4256
2392
|
</div>
|
|
4257
2393
|
</>
|
|
4258
2394
|
)}
|
|
4259
|
-
{isEditFlow &&
|
|
2395
|
+
{isEditFlow && templateStatus === RCS_STATUSES.approved && (
|
|
4260
2396
|
<>
|
|
4261
2397
|
<CapButton
|
|
4262
2398
|
onClick={handleTestAndPreview}
|
|
@@ -4268,6 +2404,51 @@ const onTitleAddVar = () => {
|
|
|
4268
2404
|
</>
|
|
4269
2405
|
)}
|
|
4270
2406
|
</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
|
+
)}
|
|
4271
2452
|
</>
|
|
4272
2453
|
);
|
|
4273
2454
|
};
|
|
@@ -4276,57 +2457,23 @@ const onTitleAddVar = () => {
|
|
|
4276
2457
|
<CapSpin spinning={loadingTags || spin}>
|
|
4277
2458
|
{getMainContent()}
|
|
4278
2459
|
</CapSpin>
|
|
4279
|
-
|
|
4280
|
-
{showDltContainer && (() => {
|
|
4281
|
-
const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
|
|
4282
|
-
return (
|
|
4283
|
-
<CapSlideBox
|
|
4284
|
-
show={showDltContainer}
|
|
4285
|
-
header={dltHeader}
|
|
4286
|
-
content={dltContent}
|
|
4287
|
-
handleClose={closeDltContainerHandler}
|
|
4288
|
-
size="size-xl"
|
|
4289
|
-
/>
|
|
4290
|
-
);
|
|
4291
|
-
})()}
|
|
4292
|
-
|
|
4293
2460
|
<TestAndPreviewSlidebox
|
|
4294
2461
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
4295
2462
|
onClose={handleCloseTestAndPreview}
|
|
4296
|
-
formData={
|
|
4297
|
-
content={
|
|
2463
|
+
formData={null} // RCS doesn't use formData structure like SMS
|
|
2464
|
+
content={getTemplateContent()}
|
|
4298
2465
|
currentChannel={RCS}
|
|
4299
|
-
orgUnitId={orgUnitId}
|
|
4300
|
-
rcsTestPreviewOptions={{ isLibraryMode: !isFullMode }}
|
|
4301
|
-
smsFallbackContent={
|
|
4302
|
-
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
4303
|
-
? {
|
|
4304
|
-
templateContent:
|
|
4305
|
-
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
4306
|
-
templateName: smsFallbackData.templateName || '',
|
|
4307
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: !isFullMode
|
|
4308
|
-
? mergeRcsSmsFallbackVarMapLayers(
|
|
4309
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
4310
|
-
smsFallbackData,
|
|
4311
|
-
)
|
|
4312
|
-
: mergeRcsSmsFallbackVarMapLayers({}, smsFallbackData),
|
|
4313
|
-
}
|
|
4314
|
-
: null
|
|
4315
|
-
}
|
|
4316
|
-
smsRegister={smsRegister}
|
|
4317
2466
|
/>
|
|
4318
2467
|
</>
|
|
4319
2468
|
);
|
|
4320
2469
|
};
|
|
4321
2470
|
|
|
4322
|
-
|
|
4323
2471
|
const mapStateToProps = createStructuredSelector({
|
|
4324
2472
|
rcsData: makeSelectRcs(),
|
|
4325
2473
|
accountData: makeSelectAccount(),
|
|
4326
2474
|
metaEntities: makeSelectMetaEntities(),
|
|
4327
2475
|
loadingTags: isLoadingMetaEntities(),
|
|
4328
2476
|
injectedTags: setInjectedTags(),
|
|
4329
|
-
currentOrgDetails: selectCurrentOrgDetails(),
|
|
4330
2477
|
});
|
|
4331
2478
|
|
|
4332
2479
|
const mapDispatchToProps = (dispatch) => ({
|