@capillarytech/creatives-library 8.0.353-alpha.6 → 8.0.354
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +0 -29
- package/index.html +1 -0
- package/package.json +1 -1
- package/services/tests/api.test.js +20 -35
- package/utils/cdnTransformation.js +63 -3
- package/utils/commonUtils.js +1 -19
- package/utils/tests/cdnTransformation.test.js +111 -0
- package/v2Components/CapActionButton/constants.js +0 -7
- package/v2Components/CapActionButton/index.js +108 -166
- package/v2Components/CapActionButton/index.scss +6 -157
- package/v2Components/CapActionButton/messages.js +3 -19
- package/v2Components/CapActionButton/tests/index.test.js +17 -41
- package/v2Components/CapTagList/index.js +0 -10
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -72
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -213
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -157
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -346
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
- package/v2Components/CommonTestAndPreview/constants.js +2 -38
- package/v2Components/CommonTestAndPreview/index.js +186 -691
- package/v2Components/CommonTestAndPreview/messages.js +3 -45
- package/v2Components/CommonTestAndPreview/sagas.js +6 -25
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
- 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 -132
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +26 -36
- package/v2Components/FormBuilder/index.js +6 -11
- package/v2Components/TemplatePreview/_templatePreview.scss +23 -38
- package/v2Components/TemplatePreview/index.js +31 -143
- package/v2Components/TemplatePreview/tests/index.test.js +0 -142
- 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/CreativesContainer/SlideBoxContent.js +4 -36
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
- package/v2Containers/CreativesContainer/constants.js +0 -9
- package/v2Containers/CreativesContainer/index.js +103 -322
- package/v2Containers/CreativesContainer/index.scss +1 -51
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
- 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 +98 -357
- 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 +10 -119
- package/v2Containers/Rcs/index.js +818 -2450
- package/v2Containers/Rcs/index.scss +8 -280
- package/v2Containers/Rcs/messages.js +3 -34
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
- package/v2Containers/Rcs/tests/index.test.js +121 -152
- package/v2Containers/Rcs/tests/mockData.js +0 -38
- package/v2Containers/Rcs/tests/utils.test.js +30 -646
- package/v2Containers/Rcs/utils.js +11 -478
- package/v2Containers/Sms/Create/index.js +40 -106
- package/v2Containers/SmsTrai/Create/index.js +4 -9
- package/v2Containers/SmsTrai/Edit/constants.js +0 -2
- package/v2Containers/SmsTrai/Edit/index.js +130 -640
- package/v2Containers/SmsTrai/Edit/messages.js +4 -14
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
- package/v2Containers/SmsWrapper/index.js +8 -37
- package/v2Containers/TagList/index.js +0 -6
- package/v2Containers/Templates/_templates.scss +9 -166
- package/v2Containers/Templates/actions.js +0 -11
- package/v2Containers/Templates/constants.js +0 -2
- package/v2Containers/Templates/index.js +52 -120
- package/v2Containers/Templates/sagas.js +18 -57
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1017 -1062
- package/v2Containers/Templates/tests/sagas.test.js +39 -205
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
- package/v2Containers/TemplatesV2/index.js +23 -86
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +20 -3
- 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/SmsFallback/SmsFallbackLocalSelector.js +0 -91
- package/v2Components/SmsFallback/constants.js +0 -73
- package/v2Components/SmsFallback/index.js +0 -956
- package/v2Components/SmsFallback/index.scss +0 -265
- package/v2Components/SmsFallback/messages.js +0 -78
- package/v2Components/SmsFallback/smsFallbackUtils.js +0 -119
- 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 -223
- 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 -43
- 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 -225
- 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 -121
- 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';
|
|
@@ -33,14 +34,6 @@ import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
|
33
34
|
import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
|
|
34
35
|
import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
|
|
35
36
|
import CapLink from '@capillarytech/cap-ui-library/CapLink';
|
|
36
|
-
import CapTab from '@capillarytech/cap-ui-library/CapTab';
|
|
37
|
-
import { flushSync } from 'react-dom';
|
|
38
|
-
import { isUrl, isValidText } from '../Line/Container/Wrapper/utils';
|
|
39
|
-
import {
|
|
40
|
-
invalidVarRegex,
|
|
41
|
-
RCS_CTA_URL_TYPE,
|
|
42
|
-
URL_MAX_LENGTH,
|
|
43
|
-
} from '../../v2Components/CapActionButton/constants';
|
|
44
37
|
|
|
45
38
|
import {
|
|
46
39
|
CAP_G01,
|
|
@@ -56,30 +49,17 @@ import {
|
|
|
56
49
|
import CapVideoUpload from '../../v2Components/CapVideoUpload';
|
|
57
50
|
import * as globalActions from '../Cap/actions';
|
|
58
51
|
import CapActionButton from '../../v2Components/CapActionButton';
|
|
59
|
-
import TemplatePreview from '../../v2Components/TemplatePreview';
|
|
60
52
|
import { makeSelectRcs, makeSelectAccount } from './selectors';
|
|
61
53
|
import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
|
|
62
54
|
import {
|
|
63
55
|
isLoadingMetaEntities,
|
|
64
56
|
makeSelectMetaEntities,
|
|
65
57
|
setInjectedTags,
|
|
66
|
-
selectCurrentOrgDetails,
|
|
67
58
|
} from '../Cap/selectors';
|
|
68
59
|
import * as RcsActions from './actions';
|
|
69
60
|
import { isAiContentBotDisabled } from '../../utils/common';
|
|
70
61
|
import * as TemplatesActions from '../Templates/actions';
|
|
71
62
|
import './index.scss';
|
|
72
|
-
import {
|
|
73
|
-
normalizeLibraryLoadedTitleDesc,
|
|
74
|
-
mergeRcsSmsFallBackContentFromDetails,
|
|
75
|
-
mergeRcsSmsFallbackVarMapLayers,
|
|
76
|
-
extractRegisteredSenderIdsFromSmsFallbackRecord,
|
|
77
|
-
pickFirstSmsFallbackTemplateString,
|
|
78
|
-
syncCardVarMappedSemanticsFromSlots,
|
|
79
|
-
hasMeaningfulSmsFallbackShape,
|
|
80
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
81
|
-
pickRcsCardVarMappedEntries,
|
|
82
|
-
} from './rcsLibraryHydrationUtils';
|
|
83
63
|
import {
|
|
84
64
|
RCS,
|
|
85
65
|
SMS,
|
|
@@ -96,7 +76,6 @@ import {
|
|
|
96
76
|
RCS_IMG_SIZE,
|
|
97
77
|
RCS_DLT_MODE,
|
|
98
78
|
CTA,
|
|
99
|
-
AI_CONTENT_BOT_DISABLED,
|
|
100
79
|
RCS_STATUSES,
|
|
101
80
|
TITLE_TEXT,
|
|
102
81
|
MESSAGE_TEXT,
|
|
@@ -111,15 +90,9 @@ import {
|
|
|
111
90
|
rcsVarTestRegex,
|
|
112
91
|
RCS_IMAGE_DIMENSIONS,
|
|
113
92
|
RCS_TEXT_MESSAGE_MAX_LENGTH,
|
|
114
|
-
RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP,
|
|
115
93
|
RCS_RICH_CARD_MAX_LENGTH,
|
|
116
94
|
RCS_VIDEO_THUMBNAIL_DIMENSIONS,
|
|
117
|
-
RCS_CAROUSEL_IMAGE_DIMENSIONS,
|
|
118
|
-
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS,
|
|
119
|
-
RCS_CAROUSEL_IMG_SIZE,
|
|
120
|
-
RCS_CAROUSEL_VIDEO_SIZE,
|
|
121
95
|
MAX_BUTTONS,
|
|
122
|
-
INITIAL_SUGGESTIONS,
|
|
123
96
|
INITIAL_SUGGESTIONS_DATA_STOP,
|
|
124
97
|
RCS_BUTTON_TYPES,
|
|
125
98
|
titletype,
|
|
@@ -129,47 +102,25 @@ import {
|
|
|
129
102
|
SMALL,
|
|
130
103
|
MEDIUM,
|
|
131
104
|
RICHCARD,
|
|
132
|
-
HOST_INFOBIP,
|
|
133
|
-
HOST_ICS,
|
|
134
|
-
CAROUSEL_HEIGHT_OPTIONS,
|
|
135
|
-
CAROUSEL_WIDTH_OPTIONS,
|
|
136
|
-
STOP,
|
|
137
|
-
RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
|
|
138
|
-
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
139
|
-
RCS_NUMERIC_VAR_TOKEN_REGEX,
|
|
140
|
-
RCS_TAG_AREA_FIELD_TITLE,
|
|
141
|
-
RCS_TAG_AREA_FIELD_DESC,
|
|
142
105
|
} from './constants';
|
|
143
106
|
import globalMessages from '../Cap/messages';
|
|
144
107
|
import messages from './messages';
|
|
145
108
|
import creativesMessages from '../CreativesContainer/messages';
|
|
146
109
|
import withCreatives from '../../hoc/withCreatives';
|
|
147
110
|
import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
148
|
-
import
|
|
149
|
-
import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
111
|
+
import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
|
|
150
112
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
151
|
-
import { splitTemplateVarString } from '../../utils/templateVarUtils';
|
|
152
113
|
import CapImageUpload from '../../v2Components/CapImageUpload';
|
|
114
|
+
import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
|
|
153
115
|
import Templates from '../Templates';
|
|
154
116
|
import SmsTraiEdit from '../SmsTrai/Edit';
|
|
155
|
-
import SmsFallback from '../../v2Components/SmsFallback';
|
|
156
|
-
import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
|
|
157
117
|
import TagList from '../TagList';
|
|
158
118
|
import { validateTags } from '../../utils/tagValidations';
|
|
159
|
-
import {
|
|
119
|
+
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
160
120
|
import { isTagIncluded } from '../../utils/commonUtils';
|
|
161
121
|
import injectReducer from '../../utils/injectReducer';
|
|
162
122
|
import v2RcsReducer from './reducer';
|
|
163
|
-
import {
|
|
164
|
-
areAllRcsSmsFallbackVarSlotsFilled,
|
|
165
|
-
buildRcsNumericMustachePlaceholderRegex,
|
|
166
|
-
getTemplateStatusType,
|
|
167
|
-
normalizeCardVarMapped,
|
|
168
|
-
coalesceCardVarMappedToTemplate,
|
|
169
|
-
getRcsSemanticVarNamesSpanningTitleAndDesc,
|
|
170
|
-
resolveCardVarMappedSlotValue,
|
|
171
|
-
sanitizeCardVarMappedValue,
|
|
172
|
-
} from './utils';
|
|
123
|
+
import { getTemplateStatusType } from './utils';
|
|
173
124
|
|
|
174
125
|
|
|
175
126
|
const { Group: CapCheckboxGroup } = CapCheckbox;
|
|
@@ -186,18 +137,19 @@ export const Rcs = (props) => {
|
|
|
186
137
|
templatesActions,
|
|
187
138
|
globalActions,
|
|
188
139
|
location,
|
|
140
|
+
handleClose,
|
|
189
141
|
getDefaultTags,
|
|
190
142
|
supportedTags,
|
|
191
143
|
metaEntities,
|
|
192
144
|
injectedTags,
|
|
193
145
|
loadingTags,
|
|
194
146
|
getFormData,
|
|
147
|
+
isDltEnabled,
|
|
195
148
|
smsRegister,
|
|
196
|
-
orgUnitId,
|
|
197
149
|
selectedOfferDetails,
|
|
198
150
|
eventContextTags,
|
|
151
|
+
waitEventContextTags,
|
|
199
152
|
accountData = {},
|
|
200
|
-
currentOrgDetails,
|
|
201
153
|
// TestAndPreviewSlidebox props
|
|
202
154
|
showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
|
|
203
155
|
handleTestAndPreview: propsHandleTestAndPreview,
|
|
@@ -206,25 +158,7 @@ export const Rcs = (props) => {
|
|
|
206
158
|
const { formatMessage } = intl;
|
|
207
159
|
const { TextArea } = CapInput;
|
|
208
160
|
const { CapCustomCardList } = CapCustomCard;
|
|
209
|
-
|
|
210
|
-
// Defensive: React cannot render plain objects as children (crashes with
|
|
211
|
-
// "Objects are not valid as a React child"). Some campaigns (!isFullMode) flows
|
|
212
|
-
// can accidentally set an error state to an object (e.g. `{}`).
|
|
213
|
-
const normalizeErrorMessage = (err) => {
|
|
214
|
-
if (!err) return '';
|
|
215
|
-
if (React.isValidElement(err)) return err;
|
|
216
|
-
if (typeof err === 'string') return err;
|
|
217
|
-
if (typeof err === 'number') return String(err);
|
|
218
|
-
if (typeof err === 'object' && typeof err.message === 'string') return err.message;
|
|
219
|
-
try {
|
|
220
|
-
return JSON.stringify(err);
|
|
221
|
-
} catch (e) {
|
|
222
|
-
return String(err);
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
|
|
226
161
|
const [isEditFlow, setEditFlow] = useState(false);
|
|
227
|
-
const isEditLike = isEditFlow || !isFullMode;
|
|
228
162
|
const [tags, updateTags] = useState([]);
|
|
229
163
|
const [spin, setSpin] = useState(false);
|
|
230
164
|
//template
|
|
@@ -233,21 +167,33 @@ export const Rcs = (props) => {
|
|
|
233
167
|
const [templateMediaType, setTemplateMediaType] = useState(
|
|
234
168
|
RCS_MEDIA_TYPES.NONE,
|
|
235
169
|
);
|
|
170
|
+
const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
|
|
236
171
|
const [templateTitle, setTemplateTitle] = useState('');
|
|
237
172
|
const [templateDesc, setTemplateDesc] = useState('');
|
|
238
173
|
const [templateDescError, setTemplateDescError] = useState(false);
|
|
239
174
|
const [templateStatus, setTemplateStatus] = useState('');
|
|
175
|
+
const [templateDate, setTemplateDate] = useState('');
|
|
176
|
+
//fallback
|
|
177
|
+
const [fallbackMessage, setFallbackMessage] = useState('');
|
|
178
|
+
const [fallbackMessageError, setFallbackMessageError] = useState(false);
|
|
240
179
|
//fallback dlt
|
|
241
180
|
const [showDltContainer, setShowDltContainer] = useState(false);
|
|
242
181
|
const [dltMode, setDltMode] = useState('');
|
|
243
182
|
const [dltEditData, setDltEditData] = useState({});
|
|
244
|
-
|
|
245
|
-
const [
|
|
183
|
+
const [showDltCard, setShowDltCard] = useState(false);
|
|
184
|
+
const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
|
|
185
|
+
const [dltPreviewData, setDltPreviewData] = useState('');
|
|
246
186
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
|
|
247
|
-
const buttonType = RCS_BUTTON_TYPES.NONE;
|
|
187
|
+
const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
|
|
248
188
|
const [suggestionError, setSuggestionError] = useState(true);
|
|
249
|
-
const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
|
|
250
189
|
const [templateType, setTemplateType] = useState('text_message');
|
|
190
|
+
const [templateHeader, setTemplateHeader] = useState('');
|
|
191
|
+
const [templateMessage, setTemplateMessage] = useState('');
|
|
192
|
+
const [templateHeaderError, setTemplateHeaderError] = useState('');
|
|
193
|
+
const [templateMessageError, setTemplateMessageError] = useState('');
|
|
194
|
+
const validVarRegex = /\{\{(\d+)\}\}/g;
|
|
195
|
+
const [updatedTitleData, setUpdatedTitleData] = useState([]);
|
|
196
|
+
const [updatedDescData, setUpdatedDescData] = useState([]);
|
|
251
197
|
const [titleVarMappedData, setTitleVarMappedData] = useState({});
|
|
252
198
|
const [descVarMappedData, setDescVarMappedData] = useState({});
|
|
253
199
|
const [titleTextAreaId, setTitleTextAreaId] = useState();
|
|
@@ -258,58 +204,75 @@ export const Rcs = (props) => {
|
|
|
258
204
|
const [rcsVideoSrc, setRcsVideoSrc] = useState({});
|
|
259
205
|
const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
|
|
260
206
|
const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
261
|
-
|
|
262
|
-
const [selectedCarousel, setSelectedCarousel] = useState('');
|
|
263
|
-
const [selectedCarouselHeight, setSelectedCarouselHeight] = useState(MEDIUM);
|
|
264
|
-
const [selectedCarouselWidth, setSelectedCarouselWidth] = useState(SMALL);
|
|
265
|
-
const [carouselData, setCarouselData] = useState([]);
|
|
266
|
-
const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
|
|
267
|
-
const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
|
|
268
|
-
const [carouselResetNonce, setCarouselResetNonce] = useState(0);
|
|
269
|
-
const [carouselFocusedVarId, setCarouselFocusedVarId] = useState('');
|
|
270
|
-
const [imageError, setImageError] = useState(null);
|
|
207
|
+
const [imageError, setImageError] = useState(null);
|
|
271
208
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
272
209
|
const [cardVarMapped, setCardVarMapped] = useState({});
|
|
273
|
-
/** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
|
|
274
|
-
const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
|
|
275
|
-
const lastHydratedRcsCardVarSignatureRef = useRef(null);
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
|
|
279
|
-
* without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
|
|
280
|
-
* fallback card / content stopped appearing until re-selected.
|
|
281
|
-
*/
|
|
282
|
-
const rcsHydrationDetails = useMemo(
|
|
283
|
-
() => (isFullMode ? rcsData?.templateDetails : templateData),
|
|
284
|
-
[isFullMode, rcsData?.templateDetails, templateData],
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
/** 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'). */
|
|
288
|
-
const lastTagSchemaQueryKeyRef = useRef(null);
|
|
289
|
-
/**
|
|
290
|
-
* Library: parent often passes a new `templateData` object reference every render. Re-applying the same
|
|
291
|
-
* SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
|
|
292
|
-
*/
|
|
293
|
-
const lastSmsFallbackHydrationKeyRef = useRef(null);
|
|
294
|
-
|
|
295
|
-
const fetchTagSchemaIfNewQuery = useCallback(
|
|
296
|
-
(query) => {
|
|
297
|
-
const key = JSON.stringify(query);
|
|
298
|
-
if (lastTagSchemaQueryKeyRef.current === key) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
lastTagSchemaQueryKeyRef.current = key;
|
|
302
|
-
globalActions.fetchSchemaForEntity(query);
|
|
303
|
-
},
|
|
304
|
-
[globalActions],
|
|
305
|
-
);
|
|
306
210
|
|
|
307
211
|
// TestAndPreviewSlidebox state
|
|
308
212
|
const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
|
|
213
|
+
const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
|
|
214
|
+
|
|
215
|
+
const tempMsg = dltPreviewData === '' ? fallbackMessage : dltPreviewData;
|
|
216
|
+
|
|
217
|
+
// Get template content for TestAndPreviewSlidebox
|
|
218
|
+
// Reference: Based on getRcsPreview() function (lines 2087-2111) which prepares content for TemplatePreview
|
|
219
|
+
// getRcsPreview ALWAYS uses templateTitle and templateDesc for ALL template types (text_message, rich_card, carousel)
|
|
220
|
+
// renderTextComponent (lines 1317-1485) also uses templateTitle and templateDesc
|
|
221
|
+
// Note: templateHeader and templateMessage are defined but NOT used in the component
|
|
222
|
+
const getTemplateContent = useCallback(() => {
|
|
223
|
+
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
224
|
+
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
225
|
+
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
226
|
+
|
|
227
|
+
// Build media preview object (same pattern as getRcsPreview)
|
|
228
|
+
const mediaPreview = {};
|
|
229
|
+
if (isMediaTypeImage && rcsImageSrc) {
|
|
230
|
+
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
231
|
+
}
|
|
232
|
+
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
233
|
+
// For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
|
|
234
|
+
if (rcsThumbnailSrc) {
|
|
235
|
+
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
236
|
+
} else if (rcsVideoSrc?.videoSrc) {
|
|
237
|
+
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
238
|
+
}
|
|
239
|
+
// Also include thumbnailSrc separately if available
|
|
240
|
+
if (rcsThumbnailSrc) {
|
|
241
|
+
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Build content object
|
|
246
|
+
// Reference: getRcsPreview (line 2091-2092) uses templateTitle and templateDesc for ALL cases
|
|
247
|
+
// templateTitle is used for rich_card/carousel title, empty for text_message
|
|
248
|
+
// templateDesc is used for ALL types (text message body or rich card description)
|
|
249
|
+
// For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
|
|
250
|
+
const contentObj = {
|
|
251
|
+
// Map templateTitle to templateHeader and templateDesc to templateMessage
|
|
252
|
+
templateHeader: templateTitle,
|
|
253
|
+
templateMessage: templateDesc,
|
|
254
|
+
...mediaPreview,
|
|
255
|
+
...(suggestions.length > 0 && {
|
|
256
|
+
suggestions: suggestions,
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return contentObj;
|
|
261
|
+
}, [
|
|
262
|
+
templateMediaType,
|
|
263
|
+
templateTitle,
|
|
264
|
+
templateDesc,
|
|
265
|
+
rcsImageSrc,
|
|
266
|
+
rcsVideoSrc,
|
|
267
|
+
rcsThumbnailSrc,
|
|
268
|
+
suggestions,
|
|
269
|
+
selectedDimension,
|
|
270
|
+
]);
|
|
309
271
|
|
|
310
272
|
// Handle Test and Preview button click
|
|
311
273
|
const handleTestAndPreview = useCallback(() => {
|
|
312
274
|
setShowTestAndPreviewSlidebox(true);
|
|
275
|
+
setIsTestAndPreviewMode(true);
|
|
313
276
|
if (propsHandleTestAndPreview) {
|
|
314
277
|
propsHandleTestAndPreview();
|
|
315
278
|
}
|
|
@@ -333,723 +296,31 @@ export const Rcs = (props) => {
|
|
|
333
296
|
// For video
|
|
334
297
|
return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
|
|
335
298
|
};
|
|
336
|
-
/** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
|
|
337
|
-
const handleSmsFallbackEditorStateChange = useCallback((patch) => {
|
|
338
|
-
setSmsFallbackData((prev) => {
|
|
339
|
-
if (!patch || typeof patch !== 'object') return prev;
|
|
340
|
-
// Bail out when no template has been selected yet — SmsTraiEdit fires
|
|
341
|
-
// onRcsFallbackEditorStateChange on mount (unicodeValidity, varMapped),
|
|
342
|
-
// which would create a non-null smsFallbackData from nothing and cause
|
|
343
|
-
// the card to appear as "Untitled creative" before any save.
|
|
344
|
-
if (!prev) return prev;
|
|
345
|
-
return { ...prev, ...patch };
|
|
346
|
-
});
|
|
347
|
-
}, []);
|
|
348
299
|
|
|
349
|
-
/** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
|
|
350
300
|
const [accountId, setAccountId] = useState('');
|
|
351
|
-
/** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
|
|
352
|
-
const [wecrmAccountId, setWecrmAccountId] = useState('');
|
|
353
301
|
const [accessToken, setAccessToken] = useState('');
|
|
354
302
|
const [hostName, setHostName] = useState('');
|
|
355
303
|
const [accountName, setAccountName] = useState('');
|
|
356
|
-
const isHostInfoBip = hostName === HOST_INFOBIP;
|
|
357
|
-
const isHostIcs = hostName === HOST_ICS;
|
|
358
|
-
|
|
359
|
-
useEffect(() => {
|
|
360
|
-
setSuggestions(isHostIcs ? INITIAL_SUGGESTIONS_DATA_STOP : []);
|
|
361
|
-
}, [isHostIcs]);
|
|
362
304
|
const [rcsAccount, setRcsAccount] = useState('');
|
|
363
305
|
useEffect(() => {
|
|
364
306
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
365
307
|
if (!isEmpty(accountObj)) {
|
|
366
308
|
const {
|
|
367
|
-
id: wecrmId,
|
|
368
309
|
sourceAccountIdentifier = '',
|
|
369
310
|
configs = {},
|
|
370
311
|
} = accountObj;
|
|
312
|
+
|
|
371
313
|
setAccountId(sourceAccountIdentifier);
|
|
372
|
-
setWecrmAccountId(
|
|
373
|
-
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
374
|
-
);
|
|
375
314
|
setAccessToken(configs.accessToken || '');
|
|
376
315
|
setHostName(accountObj.hostName || '');
|
|
377
316
|
setAccountName(accountObj.name || '');
|
|
378
317
|
setRcsAccount(accountObj.id || '');
|
|
379
|
-
} else {
|
|
380
|
-
setAccountId('');
|
|
381
|
-
setWecrmAccountId('');
|
|
382
|
-
setAccessToken('');
|
|
383
|
-
setHostName('');
|
|
384
|
-
setAccountName('');
|
|
385
318
|
}
|
|
386
319
|
}, [accountData.selectedRcsAccount]);
|
|
387
320
|
|
|
388
321
|
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
389
322
|
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
390
323
|
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
391
|
-
const isCarouselType = templateType === contentType.carousel;
|
|
392
|
-
|
|
393
|
-
const MAX_RCS_CAROUSEL_ALLOWED = 10;
|
|
394
|
-
// Uploads for RCS are stored in redux under dynamic keys `uploadedAssetData${index}`.
|
|
395
|
-
// Carousel needs per-card indices; otherwise all cards "restore" the last uploaded asset
|
|
396
|
-
// and show the same media/thumbnail.
|
|
397
|
-
const RCS_CAROUSEL_ASSET_INDEX_BASE = 10; // keep away from standalone indices 0 (media) and 1 (thumbnail)
|
|
398
|
-
const getCarouselImageAssetIndex = (cardIndex) =>
|
|
399
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
|
|
400
|
-
const getCarouselVideoAssetIndex = (cardIndex) =>
|
|
401
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 1;
|
|
402
|
-
const getCarouselThumbnailAssetIndex = (cardIndex) =>
|
|
403
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 2;
|
|
404
|
-
const isThumbnailAssetIndex = (index) => {
|
|
405
|
-
if (index === 1) return true; // standalone thumbnail
|
|
406
|
-
if (index >= RCS_CAROUSEL_ASSET_INDEX_BASE) {
|
|
407
|
-
return ((index - RCS_CAROUSEL_ASSET_INDEX_BASE) % 3) === 2; // carousel thumbnail slot
|
|
408
|
-
}
|
|
409
|
-
return false;
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
// Carousel dimension key: `${HEIGHT}_${WIDTH}` (e.g., SHORT_SMALL)
|
|
413
|
-
const getCarouselDimensionKey = () => `${selectedCarouselHeight}_${selectedCarouselWidth}`;
|
|
414
|
-
|
|
415
|
-
const clearCarouselCardMedia = (cardIndex, { clearImage = true, clearVideo = true, clearThumb = true } = {}) => {
|
|
416
|
-
setCarouselData((prev = []) => {
|
|
417
|
-
const updated = cloneDeep(prev);
|
|
418
|
-
if (!updated?.[cardIndex]) return updated;
|
|
419
|
-
if (clearImage) updated[cardIndex].imageSrc = '';
|
|
420
|
-
if (clearVideo) updated[cardIndex].videoAsset = {};
|
|
421
|
-
if (clearThumb) updated[cardIndex].thumbnailSrc = '';
|
|
422
|
-
return updated;
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
if (clearImage) actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIndex));
|
|
426
|
-
if (clearVideo) actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIndex));
|
|
427
|
-
if (clearThumb) actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIndex));
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
const resetCarouselMediaForAllCards = () => {
|
|
431
|
-
setCarouselData((prev = []) => {
|
|
432
|
-
const updated = cloneDeep(prev);
|
|
433
|
-
updated.forEach((card) => {
|
|
434
|
-
if (!card) return;
|
|
435
|
-
card.imageSrc = '';
|
|
436
|
-
card.videoAsset = {};
|
|
437
|
-
card.thumbnailSrc = '';
|
|
438
|
-
});
|
|
439
|
-
return updated;
|
|
440
|
-
});
|
|
441
|
-
(carouselData || []).forEach((_, idx) => {
|
|
442
|
-
actions.clearRcsMediaAsset(getCarouselImageAssetIndex(idx));
|
|
443
|
-
actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(idx));
|
|
444
|
-
actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(idx));
|
|
445
|
-
});
|
|
446
|
-
// Force tab panes to remount after global reset (some tab implementations cache inactive panes)
|
|
447
|
-
setCarouselResetNonce((n) => n + 1);
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
const RCS_CAROUSEL_INITIAL_CARD = {
|
|
451
|
-
title: '',
|
|
452
|
-
description: '',
|
|
453
|
-
mediaType: RCS_MEDIA_TYPES.IMAGE, // per-card
|
|
454
|
-
imageSrc: '',
|
|
455
|
-
videoAsset: {}, // CapVideoUpload object shape
|
|
456
|
-
thumbnailSrc: '',
|
|
457
|
-
suggestions: [],
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
const RCS_CAROUSEL_INITIAL_FIRST_CARD = {
|
|
461
|
-
...RCS_CAROUSEL_INITIAL_CARD,
|
|
462
|
-
suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
const ensureFirstCardDefaultPhoneSuggestions = (cards) => {
|
|
466
|
-
const next = cloneDeep(cards || []);
|
|
467
|
-
if (next.length === 0) return next;
|
|
468
|
-
const s = next[0].suggestions;
|
|
469
|
-
if (!Array.isArray(s) || s.length === 0) {
|
|
470
|
-
next[0] = {
|
|
471
|
-
...next[0],
|
|
472
|
-
suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
return next;
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
// Always use functional updates: image upload completes in CapImageUpload's useEffect and calls
|
|
479
|
-
// updateImageSrc → this handler. If we cloned carouselData from render, we'd overwrite title/body/
|
|
480
|
-
// suggestions typed since the last commit (stale closure).
|
|
481
|
-
const handleCarouselValueChange = (carouselIndex, fields) => {
|
|
482
|
-
setCarouselData((prev = []) => {
|
|
483
|
-
const updated = cloneDeep(prev);
|
|
484
|
-
if (!updated[carouselIndex]) return prev;
|
|
485
|
-
fields.forEach(({ fieldName, value }) => {
|
|
486
|
-
updated[carouselIndex][fieldName] = value;
|
|
487
|
-
});
|
|
488
|
-
return updated;
|
|
489
|
-
});
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
const updateCarouselErrors = (carouselIndex, patch) => {
|
|
493
|
-
setCarouselErrors((prev = []) => {
|
|
494
|
-
const next = Array.isArray(prev) ? [...prev] : [];
|
|
495
|
-
next[carouselIndex] = { ...(next[carouselIndex] || {}), ...patch };
|
|
496
|
-
return next;
|
|
497
|
-
});
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const deleteCarouselCard = (index) => {
|
|
501
|
-
let nextIdx = 0;
|
|
502
|
-
flushSync(() => {
|
|
503
|
-
setCarouselData((prev = []) => {
|
|
504
|
-
const updated = cloneDeep(prev);
|
|
505
|
-
if (index < 0 || index >= updated.length) return updated;
|
|
506
|
-
updated.splice(index, 1);
|
|
507
|
-
nextIdx = Math.max(0, Math.min(index - 1, updated.length - 1));
|
|
508
|
-
return ensureFirstCardDefaultPhoneSuggestions(updated);
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
setCarouselErrors((prev = []) => {
|
|
512
|
-
const next = Array.isArray(prev) ? [...prev] : [];
|
|
513
|
-
next.splice(index, 1);
|
|
514
|
-
return next;
|
|
515
|
-
});
|
|
516
|
-
setActiveCarouselIndex(`${nextIdx}`);
|
|
517
|
-
};
|
|
518
|
-
|
|
519
|
-
const carouselButtonTextHasForbiddenChars = (value) => {
|
|
520
|
-
if (!value) return false;
|
|
521
|
-
if (value.includes('[') || value.includes(']')) return true;
|
|
522
|
-
const withoutValidVariables = value.replace(/\{\{[^}]*\}\}/g, '');
|
|
523
|
-
if (withoutValidVariables.includes('{') || withoutValidVariables.includes('}')) return true;
|
|
524
|
-
return false;
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
const isCompleteSavedCarouselSuggestion = (s) => {
|
|
528
|
-
if (!s || !s.isSaved) return false;
|
|
529
|
-
const text = (s.text || '').trim();
|
|
530
|
-
if (!text || !isValidText(text) || carouselButtonTextHasForbiddenChars(text)) return false;
|
|
531
|
-
if (s.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
|
|
532
|
-
return String(s.phoneNumber || '').length >= 5;
|
|
533
|
-
}
|
|
534
|
-
if (s.type === RCS_BUTTON_TYPES.CTA) {
|
|
535
|
-
const url = String(s.url || '').trim();
|
|
536
|
-
if (!url || url.length > URL_MAX_LENGTH) return false;
|
|
537
|
-
const subtype = s.urlType || RCS_CTA_URL_TYPE.STATIC;
|
|
538
|
-
if (subtype === RCS_CTA_URL_TYPE.DYNAMIC) {
|
|
539
|
-
return true;
|
|
540
|
-
}
|
|
541
|
-
if (!isUrl(url)) return false;
|
|
542
|
-
const varMatches = url.match(invalidVarRegex);
|
|
543
|
-
return !(varMatches && varMatches.length > 0);
|
|
544
|
-
}
|
|
545
|
-
if (s.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
|
|
546
|
-
return true;
|
|
547
|
-
}
|
|
548
|
-
return false;
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
const isCarouselCardValid = (card, cardIndex) => {
|
|
552
|
-
if (!card) return false;
|
|
553
|
-
if (!card.title || !card.title.trim()) return false;
|
|
554
|
-
if ((card.title || '').length > TEMPLATE_TITLE_MAX_LENGTH) return false;
|
|
555
|
-
if (!card.description || !card.description.trim()) return false;
|
|
556
|
-
if ((card.description || '').length > RCS_RICH_CARD_MAX_LENGTH) return false;
|
|
557
|
-
let mediaOk = false;
|
|
558
|
-
if (card.mediaType === RCS_MEDIA_TYPES.IMAGE) {
|
|
559
|
-
mediaOk = !!(card.imageSrc && String(card.imageSrc).trim());
|
|
560
|
-
} else if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
|
|
561
|
-
const hasVideo = !!(card.videoAsset && card.videoAsset.videoSrc && String(card.videoAsset.videoSrc).trim());
|
|
562
|
-
const hasThumb = !!(card.thumbnailSrc && String(card.thumbnailSrc).trim());
|
|
563
|
-
mediaOk = hasVideo && hasThumb;
|
|
564
|
-
} else {
|
|
565
|
-
return false;
|
|
566
|
-
}
|
|
567
|
-
if (!mediaOk) return false;
|
|
568
|
-
if (cardIndex === 0) {
|
|
569
|
-
const sugg = Array.isArray(card.suggestions) ? card.suggestions : [];
|
|
570
|
-
if (!sugg.some(isCompleteSavedCarouselSuggestion)) return false;
|
|
571
|
-
}
|
|
572
|
-
return true;
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
const checkDisableAddCarouselButton = () => {
|
|
576
|
-
const idx = parseInt(activeCarouselIndex, 10);
|
|
577
|
-
const activeCard = carouselData?.[idx];
|
|
578
|
-
return !isCarouselCardValid(activeCard, idx);
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
const addCarouselCard = () => {
|
|
582
|
-
let newIndex = 0;
|
|
583
|
-
flushSync(() => {
|
|
584
|
-
setCarouselData((prev = []) => {
|
|
585
|
-
const updated = cloneDeep(prev);
|
|
586
|
-
updated.push(cloneDeep(RCS_CAROUSEL_INITIAL_CARD));
|
|
587
|
-
newIndex = updated.length - 1;
|
|
588
|
-
return updated;
|
|
589
|
-
});
|
|
590
|
-
});
|
|
591
|
-
setCarouselErrors((prev = []) => ([...(Array.isArray(prev) ? prev : []), {}]));
|
|
592
|
-
setActiveCarouselIndex(`${newIndex}`);
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
// Initialize carousel data when switching to carousel type
|
|
596
|
-
useEffect(() => {
|
|
597
|
-
if (!isCarouselType) return;
|
|
598
|
-
if (!carouselData || carouselData.length === 0) {
|
|
599
|
-
setCarouselData([cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)]);
|
|
600
|
-
setCarouselErrors([{}]);
|
|
601
|
-
setActiveCarouselIndex('0');
|
|
602
|
-
}
|
|
603
|
-
}, [isCarouselType]);
|
|
604
|
-
|
|
605
|
-
// keep derived carousel key in sync
|
|
606
|
-
useEffect(() => {
|
|
607
|
-
if (!isCarouselType) return;
|
|
608
|
-
if (!selectedCarouselHeight || !selectedCarouselWidth) return;
|
|
609
|
-
// Required format: HEIGHT_WIDTH
|
|
610
|
-
setSelectedCarousel(`${selectedCarouselHeight}_${selectedCarouselWidth}`);
|
|
611
|
-
}, [isCarouselType, selectedCarouselHeight, selectedCarouselWidth]);
|
|
612
|
-
|
|
613
|
-
const renderCarouselDimensionSelection = () => {
|
|
614
|
-
if (!isCarouselType) return null;
|
|
615
|
-
return (
|
|
616
|
-
<CapRow className="rcs-carousel-dimension-section">
|
|
617
|
-
<CapRow gutter={16} className="rcs-carousel-dimension-row">
|
|
618
|
-
<CapColumn span={12}>
|
|
619
|
-
<CapHeading type="h4" className="rcs-carousel-dimension-label">Card height</CapHeading>
|
|
620
|
-
<CapSelect
|
|
621
|
-
id="rcs-carousel-height-select"
|
|
622
|
-
value={selectedCarouselHeight}
|
|
623
|
-
onChange={(val) => {
|
|
624
|
-
// Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
|
|
625
|
-
resetCarouselMediaForAllCards();
|
|
626
|
-
setSelectedCarouselHeight(val);
|
|
627
|
-
}}
|
|
628
|
-
options={CAROUSEL_HEIGHT_OPTIONS}
|
|
629
|
-
disabled={isEditFlow || !isFullMode}
|
|
630
|
-
/>
|
|
631
|
-
</CapColumn>
|
|
632
|
-
<CapColumn span={12}>
|
|
633
|
-
<CapHeading type="h4" className="rcs-carousel-dimension-label">Card width</CapHeading>
|
|
634
|
-
<CapSelect
|
|
635
|
-
id="rcs-carousel-width-select"
|
|
636
|
-
value={selectedCarouselWidth}
|
|
637
|
-
onChange={(val) => {
|
|
638
|
-
// Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
|
|
639
|
-
resetCarouselMediaForAllCards();
|
|
640
|
-
setSelectedCarouselWidth(val);
|
|
641
|
-
}}
|
|
642
|
-
options={CAROUSEL_WIDTH_OPTIONS}
|
|
643
|
-
disabled={isEditFlow || !isFullMode}
|
|
644
|
-
/>
|
|
645
|
-
</CapColumn>
|
|
646
|
-
</CapRow>
|
|
647
|
-
{!!selectedCarousel && (
|
|
648
|
-
<CapLabel type="label3" className="rcs-carousel-selected-dimension">
|
|
649
|
-
Selected: {selectedCarousel}
|
|
650
|
-
</CapLabel>
|
|
651
|
-
)}
|
|
652
|
-
</CapRow>
|
|
653
|
-
);
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
// Reuse rich-card buttons UI per carousel card
|
|
657
|
-
const renderButtonComponentForCarouselCard = (cardIndex) => {
|
|
658
|
-
const card = carouselData?.[cardIndex] || {};
|
|
659
|
-
const suggestionsForCard = card.suggestions || [];
|
|
660
|
-
return (
|
|
661
|
-
<>
|
|
662
|
-
<CapHeader
|
|
663
|
-
className="rcs-button-cta"
|
|
664
|
-
title={(
|
|
665
|
-
<CapRow type="flex">
|
|
666
|
-
<CapHeading type="h4">
|
|
667
|
-
{formatMessage(messages.btnLabel)}
|
|
668
|
-
</CapHeading>
|
|
669
|
-
</CapRow>
|
|
670
|
-
)}
|
|
671
|
-
description={(
|
|
672
|
-
<CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
|
|
673
|
-
)}
|
|
674
|
-
/>
|
|
675
|
-
<CapActionButton
|
|
676
|
-
buttonType={RCS_BUTTON_TYPES.NONE}
|
|
677
|
-
updateButtonChange={(data, btnIndex) => {
|
|
678
|
-
// Match existing behavior: allow CapActionButton to manage save gating.
|
|
679
|
-
const updated = cloneDeep(suggestionsForCard);
|
|
680
|
-
if (btnIndex === MAX_BUTTONS) {
|
|
681
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: data }]);
|
|
682
|
-
return;
|
|
683
|
-
}
|
|
684
|
-
updated[btnIndex] = data;
|
|
685
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
|
|
686
|
-
}}
|
|
687
|
-
deleteButtonHandler={(btnIndex) => {
|
|
688
|
-
if (cardIndex === 0 && btnIndex === 0) {
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
const savedCount = (suggestionsForCard || []).filter((x) => x && x.isSaved).length;
|
|
692
|
-
const target = (suggestionsForCard || []).find((s) => s && s.index === btnIndex);
|
|
693
|
-
if (cardIndex === 0 && target?.isSaved && savedCount <= 1) {
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
const updated = cloneDeep(suggestionsForCard)
|
|
697
|
-
.filter((i) => i.index !== btnIndex)
|
|
698
|
-
.map((i, idx) => ({ ...i, index: idx }));
|
|
699
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
|
|
700
|
-
}}
|
|
701
|
-
suggestions={suggestionsForCard}
|
|
702
|
-
isEditFlow={isEditFlow}
|
|
703
|
-
isFullMode={isFullMode}
|
|
704
|
-
maxButtons={MAX_BUTTONS}
|
|
705
|
-
host={hostName}
|
|
706
|
-
minSavedSuggestions={cardIndex === 0 ? 1 : 0}
|
|
707
|
-
hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
|
|
708
|
-
/>
|
|
709
|
-
</>
|
|
710
|
-
);
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
const renderCarouselCardMedia = (cardIndex) => {
|
|
714
|
-
const card = carouselData?.[cardIndex] || {};
|
|
715
|
-
const dimKey = getCarouselDimensionKey();
|
|
716
|
-
|
|
717
|
-
if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
|
|
718
|
-
return (
|
|
719
|
-
<>
|
|
720
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Video</CapHeading>
|
|
721
|
-
<CapVideoUpload
|
|
722
|
-
index={getCarouselVideoAssetIndex(cardIndex)}
|
|
723
|
-
allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
|
|
724
|
-
videoSize={RCS_CAROUSEL_VIDEO_SIZE}
|
|
725
|
-
isFullMode={isFullMode}
|
|
726
|
-
uploadAsset={uploadRcsVideo}
|
|
727
|
-
uploadedAssetList={card.videoAsset || {}}
|
|
728
|
-
onVideoUploadUpdateAssestList={(_, val) => {
|
|
729
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'videoAsset', value: val }]);
|
|
730
|
-
}}
|
|
731
|
-
videoData={rcsData}
|
|
732
|
-
className="cap-custom-video-upload"
|
|
733
|
-
formClassName={"rcs-video-upload"}
|
|
734
|
-
channel={RCS}
|
|
735
|
-
errorMessage={formatMessage(messages.videoErrorMessage)}
|
|
736
|
-
showVideoNameAndDuration={false}
|
|
737
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
738
|
-
channelSpecificStyle={!isFullMode}
|
|
739
|
-
/>
|
|
740
|
-
|
|
741
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
742
|
-
<CapImageUpload
|
|
743
|
-
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
744
|
-
imgWidth={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.width}
|
|
745
|
-
imgHeight={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.height}
|
|
746
|
-
imgSize={RCS_THUMBNAIL_MAX_SIZE}
|
|
747
|
-
uploadAsset={uploadRcsImage}
|
|
748
|
-
isFullMode={isFullMode}
|
|
749
|
-
imageSrc={card.thumbnailSrc}
|
|
750
|
-
updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: val }])}
|
|
751
|
-
updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: '' }])}
|
|
752
|
-
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
753
|
-
index={getCarouselThumbnailAssetIndex(cardIndex)}
|
|
754
|
-
className="cap-custom-image-upload"
|
|
755
|
-
key={`rcs-carousel-thumb-${cardIndex}-${dimKey}`}
|
|
756
|
-
imageData={rcsData}
|
|
757
|
-
channel={RCS}
|
|
758
|
-
channelSpecificStyle={!isFullMode}
|
|
759
|
-
skipDimensionValidation={true}
|
|
760
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
761
|
-
disabled={isEditFlow || !isFullMode}
|
|
762
|
-
/>
|
|
763
|
-
</>
|
|
764
|
-
);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Default: IMAGE
|
|
768
|
-
return (
|
|
769
|
-
<>
|
|
770
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Image</CapHeading>
|
|
771
|
-
<CapImageUpload
|
|
772
|
-
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
773
|
-
imgWidth={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.width}
|
|
774
|
-
imgHeight={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.height}
|
|
775
|
-
imgSize={RCS_CAROUSEL_IMG_SIZE}
|
|
776
|
-
uploadAsset={uploadRcsImage}
|
|
777
|
-
isFullMode={isFullMode}
|
|
778
|
-
imageSrc={card.imageSrc}
|
|
779
|
-
updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: val }])}
|
|
780
|
-
updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: '' }])}
|
|
781
|
-
index={getCarouselImageAssetIndex(cardIndex)}
|
|
782
|
-
className="cap-custom-image-upload"
|
|
783
|
-
key={`rcs-carousel-image-${cardIndex}-${dimKey}`}
|
|
784
|
-
imageData={rcsData}
|
|
785
|
-
channel={RCS}
|
|
786
|
-
channelSpecificStyle={!isFullMode}
|
|
787
|
-
skipDimensionValidation={true}
|
|
788
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
789
|
-
disabled={isEditFlow || !isFullMode}
|
|
790
|
-
/>
|
|
791
|
-
</>
|
|
792
|
-
);
|
|
793
|
-
};
|
|
794
|
-
|
|
795
|
-
const renderCarouselCardButtons = (cardIndex) => {
|
|
796
|
-
return renderButtonComponentForCarouselCard(cardIndex);
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
const getCarouselTabPanes = () => {
|
|
800
|
-
return (carouselData || []).map((card, index) => {
|
|
801
|
-
return {
|
|
802
|
-
key: index,
|
|
803
|
-
tab: index + 1,
|
|
804
|
-
content: (
|
|
805
|
-
<CapCard
|
|
806
|
-
title={`Card ${index + 1}`}
|
|
807
|
-
extra={
|
|
808
|
-
!isEditFlow &&
|
|
809
|
-
(carouselData.length === 1 ? (
|
|
810
|
-
<CapTooltip title={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}>
|
|
811
|
-
<span className="button-disabled-tooltip-wrapper rcs-carousel-delete-tooltip-wrap">
|
|
812
|
-
<CapButton
|
|
813
|
-
className="rcs-carousel-card-delete"
|
|
814
|
-
type="flat"
|
|
815
|
-
onClick={() => deleteCarouselCard(index)}
|
|
816
|
-
disabled
|
|
817
|
-
aria-label={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}
|
|
818
|
-
>
|
|
819
|
-
<CapIcon type="delete" />
|
|
820
|
-
</CapButton>
|
|
821
|
-
</span>
|
|
822
|
-
</CapTooltip>
|
|
823
|
-
) : (
|
|
824
|
-
<CapButton
|
|
825
|
-
className="rcs-carousel-card-delete"
|
|
826
|
-
type="flat"
|
|
827
|
-
onClick={() => deleteCarouselCard(index)}
|
|
828
|
-
aria-label={formatMessage(globalMessages.delete)}
|
|
829
|
-
>
|
|
830
|
-
<CapIcon type="delete" />
|
|
831
|
-
</CapButton>
|
|
832
|
-
))
|
|
833
|
-
}
|
|
834
|
-
className="rcs-carousel-card"
|
|
835
|
-
>
|
|
836
|
-
{/* Media selection should be at top of card */}
|
|
837
|
-
<CapRow className="rcs-carousel-media-selection">
|
|
838
|
-
<CapColumn className="rcs-carousel-media-selection-heading">
|
|
839
|
-
<CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
|
|
840
|
-
</CapColumn>
|
|
841
|
-
<CapColumn>
|
|
842
|
-
<CapRadioGroup
|
|
843
|
-
id={`rcs-carousel-media-radio-${index}`}
|
|
844
|
-
options={mediaRadioOptions}
|
|
845
|
-
value={card.mediaType}
|
|
846
|
-
onChange={({ target: { value } }) => {
|
|
847
|
-
// Reset media fields when switching type
|
|
848
|
-
if (value === RCS_MEDIA_TYPES.IMAGE) {
|
|
849
|
-
// Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
|
|
850
|
-
clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
|
|
851
|
-
handleCarouselValueChange(index, [
|
|
852
|
-
{ fieldName: 'mediaType', value },
|
|
853
|
-
{ fieldName: 'videoAsset', value: {} },
|
|
854
|
-
{ fieldName: 'thumbnailSrc', value: '' },
|
|
855
|
-
]);
|
|
856
|
-
} else {
|
|
857
|
-
// Switching to VIDEO: clear image upload so it doesn't auto-restore.
|
|
858
|
-
clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
|
|
859
|
-
handleCarouselValueChange(index, [
|
|
860
|
-
{ fieldName: 'mediaType', value },
|
|
861
|
-
{ fieldName: 'imageSrc', value: '' },
|
|
862
|
-
]);
|
|
863
|
-
}
|
|
864
|
-
}}
|
|
865
|
-
disabled={isEditFlow || !isFullMode}
|
|
866
|
-
className="rcs-radio"
|
|
867
|
-
/>
|
|
868
|
-
</CapColumn>
|
|
869
|
-
</CapRow>
|
|
870
|
-
|
|
871
|
-
<CapRow className="rcs-carousel-media-upload">
|
|
872
|
-
{renderCarouselCardMedia(index)}
|
|
873
|
-
</CapRow>
|
|
874
|
-
|
|
875
|
-
{/* Title after media */}
|
|
876
|
-
<CapRow className="rcs-carousel-card-row">
|
|
877
|
-
<CapHeader
|
|
878
|
-
className="rcs-template-title-label"
|
|
879
|
-
title={<CapHeading type="h4">Card title</CapHeading>}
|
|
880
|
-
suffix={
|
|
881
|
-
(isEditFlow || !isFullMode) ? (
|
|
882
|
-
<TagList
|
|
883
|
-
label={formatMessage(globalMessages.addLabels)}
|
|
884
|
-
onTagSelect={onCarouselTagSelect}
|
|
885
|
-
location={location}
|
|
886
|
-
tags={getRcsTags()}
|
|
887
|
-
onContextChange={handleOnTagsContextChange}
|
|
888
|
-
injectedTags={injectedTags || {}}
|
|
889
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
890
|
-
/>
|
|
891
|
-
) : (
|
|
892
|
-
<CapButton
|
|
893
|
-
data-testid={`rcs-carousel-title-add-var-${index}`}
|
|
894
|
-
type="flat"
|
|
895
|
-
isAddBtn
|
|
896
|
-
onClick={() => appendVarToCarouselField(index, 'title')}
|
|
897
|
-
disabled={!!carouselErrors?.[index]?.title}
|
|
898
|
-
>
|
|
899
|
-
{formatMessage(messages.addVar)}
|
|
900
|
-
</CapButton>
|
|
901
|
-
)
|
|
902
|
-
}
|
|
903
|
-
/>
|
|
904
|
-
<CapRow className="rcs_text_area_wrapper">
|
|
905
|
-
{(isEditFlow || !isFullMode) ? (
|
|
906
|
-
renderCarouselEditMessage(card.title || '')
|
|
907
|
-
) : (
|
|
908
|
-
<CapInput
|
|
909
|
-
value={card.title || ''}
|
|
910
|
-
placeholder={formatMessage(messages.templateTitlePlaceholder)}
|
|
911
|
-
onChange={({ target: { value } }) => {
|
|
912
|
-
let error = false;
|
|
913
|
-
if (value?.length > TEMPLATE_TITLE_MAX_LENGTH) {
|
|
914
|
-
error = formatMessage(messages.templateHeaderLengthError);
|
|
915
|
-
} else {
|
|
916
|
-
error = variableErrorHandling(value);
|
|
917
|
-
}
|
|
918
|
-
updateCarouselErrors(index, { title: error });
|
|
919
|
-
handleCarouselValueChange(index, [{ fieldName: 'title', value }]);
|
|
920
|
-
}}
|
|
921
|
-
disabled={isEditFlow || !isFullMode}
|
|
922
|
-
errorMessage={carouselErrors?.[index]?.title}
|
|
923
|
-
/>
|
|
924
|
-
)}
|
|
925
|
-
</CapRow>
|
|
926
|
-
</CapRow>
|
|
927
|
-
{!isEditFlow && (
|
|
928
|
-
<CapRow className="rcs-carousel-character-count-row">
|
|
929
|
-
{renderCarouselCharacterCount(
|
|
930
|
-
getCarouselTitleCharacterCount(index),
|
|
931
|
-
getTitleMaxLength(),
|
|
932
|
-
)}
|
|
933
|
-
</CapRow>
|
|
934
|
-
)}
|
|
935
|
-
|
|
936
|
-
{/* Description after title */}
|
|
937
|
-
<CapRow className="rcs-carousel-card-row">
|
|
938
|
-
<CapHeader
|
|
939
|
-
title={<CapHeading type="h4">Card body text</CapHeading>}
|
|
940
|
-
suffix={
|
|
941
|
-
(isEditFlow || !isFullMode) ? (
|
|
942
|
-
<TagList
|
|
943
|
-
label={formatMessage(globalMessages.addLabels)}
|
|
944
|
-
onTagSelect={onCarouselTagSelect}
|
|
945
|
-
location={location}
|
|
946
|
-
tags={getRcsTags()}
|
|
947
|
-
onContextChange={handleOnTagsContextChange}
|
|
948
|
-
injectedTags={injectedTags || {}}
|
|
949
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
950
|
-
/>
|
|
951
|
-
) : (
|
|
952
|
-
<CapButton
|
|
953
|
-
data-testid={`rcs-carousel-desc-add-var-${index}`}
|
|
954
|
-
type="flat"
|
|
955
|
-
isAddBtn
|
|
956
|
-
onClick={() => appendVarToCarouselField(index, 'description')}
|
|
957
|
-
disabled={!!carouselErrors?.[index]?.description}
|
|
958
|
-
>
|
|
959
|
-
{formatMessage(messages.addVar)}
|
|
960
|
-
</CapButton>
|
|
961
|
-
)
|
|
962
|
-
}
|
|
963
|
-
/>
|
|
964
|
-
<CapRow className="rcs_text_area_wrapper">
|
|
965
|
-
{(isEditFlow || !isFullMode) ? (
|
|
966
|
-
renderCarouselEditMessage(card.description || '')
|
|
967
|
-
) : (
|
|
968
|
-
<TextArea
|
|
969
|
-
autosize={{ minRows: 3, maxRows: 5 }}
|
|
970
|
-
value={card.description || ''}
|
|
971
|
-
placeholder={formatMessage(messages.templateDescPlaceholder)}
|
|
972
|
-
onChange={({ target: { value } }) => {
|
|
973
|
-
let error = false;
|
|
974
|
-
if (value?.length > RCS_RICH_CARD_MAX_LENGTH) {
|
|
975
|
-
error = formatMessage(messages.templateMessageLengthError);
|
|
976
|
-
} else {
|
|
977
|
-
error = variableErrorHandling(value);
|
|
978
|
-
}
|
|
979
|
-
updateCarouselErrors(index, { description: error });
|
|
980
|
-
handleCarouselValueChange(index, [{ fieldName: 'description', value }]);
|
|
981
|
-
}}
|
|
982
|
-
disabled={isEditFlow || !isFullMode}
|
|
983
|
-
errorMessage={
|
|
984
|
-
carouselErrors?.[index]?.description && (
|
|
985
|
-
<CapError className="rcs-template-message-error">
|
|
986
|
-
{carouselErrors[index].description}
|
|
987
|
-
</CapError>
|
|
988
|
-
)
|
|
989
|
-
}
|
|
990
|
-
/>
|
|
991
|
-
)}
|
|
992
|
-
</CapRow>
|
|
993
|
-
</CapRow>
|
|
994
|
-
{!isEditFlow && (
|
|
995
|
-
<CapRow className="rcs-carousel-character-count-row">
|
|
996
|
-
{renderCarouselCharacterCount(
|
|
997
|
-
getCarouselDescriptionCharacterCount(index),
|
|
998
|
-
getDescriptionMaxLength(),
|
|
999
|
-
)}
|
|
1000
|
-
</CapRow>
|
|
1001
|
-
)}
|
|
1002
|
-
|
|
1003
|
-
<CapDivider className="rcs-carousel-card-divider" />
|
|
1004
|
-
{renderCarouselCardButtons(index)}
|
|
1005
|
-
</CapCard>
|
|
1006
|
-
),
|
|
1007
|
-
};
|
|
1008
|
-
});
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
const renderCarouselSection = () => {
|
|
1012
|
-
if (!isCarouselType) return null;
|
|
1013
|
-
|
|
1014
|
-
const operations = (
|
|
1015
|
-
<>
|
|
1016
|
-
<CapDivider type="vertical" />
|
|
1017
|
-
<CapButton
|
|
1018
|
-
onClick={addCarouselCard}
|
|
1019
|
-
type="flat"
|
|
1020
|
-
className="add-carousel-content-button"
|
|
1021
|
-
disabled={
|
|
1022
|
-
isEditFlow ||
|
|
1023
|
-
!isFullMode ||
|
|
1024
|
-
MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0) ||
|
|
1025
|
-
checkDisableAddCarouselButton()
|
|
1026
|
-
}
|
|
1027
|
-
>
|
|
1028
|
-
<CapIcon type="plus" />
|
|
1029
|
-
</CapButton>
|
|
1030
|
-
</>
|
|
1031
|
-
);
|
|
1032
|
-
|
|
1033
|
-
return (
|
|
1034
|
-
<CapRow className="rcs-carousel-section">
|
|
1035
|
-
{renderCarouselDimensionSelection()}
|
|
1036
|
-
<CapRow className="rcs-carousel-tab">
|
|
1037
|
-
<CapTab
|
|
1038
|
-
key={`rcs-carousel-tab-${carouselResetNonce}`}
|
|
1039
|
-
defaultActiveKey="0"
|
|
1040
|
-
activeKey={activeCarouselIndex}
|
|
1041
|
-
tabBarExtraContent={operations}
|
|
1042
|
-
onChange={(key) => {
|
|
1043
|
-
setActiveCarouselIndex(`${key}`);
|
|
1044
|
-
// reset focused var when switching cards (as per requirement)
|
|
1045
|
-
setCarouselFocusedVarId('');
|
|
1046
|
-
}}
|
|
1047
|
-
panes={getCarouselTabPanes()}
|
|
1048
|
-
/>
|
|
1049
|
-
</CapRow>
|
|
1050
|
-
</CapRow>
|
|
1051
|
-
);
|
|
1052
|
-
};
|
|
1053
324
|
|
|
1054
325
|
const mediaRadioOptions = [
|
|
1055
326
|
{
|
|
@@ -1058,16 +329,11 @@ export const Rcs = (props) => {
|
|
|
1058
329
|
},
|
|
1059
330
|
{
|
|
1060
331
|
value: RCS_MEDIA_TYPES.VIDEO,
|
|
1061
|
-
label: formatMessage(
|
|
1062
|
-
templateType === contentType.carousel
|
|
1063
|
-
? messages.carouselMediaVideoOption
|
|
1064
|
-
: messages.mediaVideo,
|
|
1065
|
-
),
|
|
332
|
+
label: formatMessage(messages.mediaVideo),
|
|
1066
333
|
},
|
|
1067
334
|
];
|
|
1068
335
|
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
1069
336
|
|
|
1070
|
-
|
|
1071
337
|
const updateButtonChange = (data, index) => {
|
|
1072
338
|
if (data && data.text) {
|
|
1073
339
|
const forbiddenError = forbiddenCharactersValidation(data.text);
|
|
@@ -1104,9 +370,7 @@ export const Rcs = (props) => {
|
|
|
1104
370
|
if (isFullMode) return;
|
|
1105
371
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
1106
372
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1107
|
-
const
|
|
1108
|
-
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
1109
|
-
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
373
|
+
const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
|
|
1110
374
|
if (!resolved) {
|
|
1111
375
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
1112
376
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -1133,16 +397,10 @@ export const Rcs = (props) => {
|
|
|
1133
397
|
tagModule: getDefaultTags,
|
|
1134
398
|
isFullMode,
|
|
1135
399
|
}) || {};
|
|
1136
|
-
|
|
1137
|
-
validationResponse?.
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1141
|
-
unsupportedTags: validationResponse.unsupportedTags,
|
|
1142
|
-
})) ||
|
|
1143
|
-
(validationResponse.isBraceError &&
|
|
1144
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
1145
|
-
false;
|
|
400
|
+
const errorMsg =
|
|
401
|
+
(validationResponse?.isBraceError &&
|
|
402
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
403
|
+
false;
|
|
1146
404
|
if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
|
|
1147
405
|
if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
|
|
1148
406
|
};
|
|
@@ -1155,98 +413,15 @@ export const Rcs = (props) => {
|
|
|
1155
413
|
validateResolvedTagsForType(MESSAGE_TEXT);
|
|
1156
414
|
}, [cardVarMapped, templateDesc, tags, injectedTags, loadingTags]);
|
|
1157
415
|
|
|
1158
|
-
useEffect(() => {
|
|
1159
|
-
if (isFullMode || !isCarouselType) return;
|
|
1160
|
-
if (loadingTags || !tags || tags.length === 0) return;
|
|
1161
|
-
(carouselData || []).forEach((card, idx) => {
|
|
1162
|
-
['title', 'description'].forEach((field) => {
|
|
1163
|
-
const templateStr = card?.[field] || '';
|
|
1164
|
-
if (!templateStr) return;
|
|
1165
|
-
const resolved = resolveTemplateWithMap(templateStr);
|
|
1166
|
-
if (!resolved) {
|
|
1167
|
-
updateCarouselErrors(idx, { [field]: false });
|
|
1168
|
-
return;
|
|
1169
|
-
}
|
|
1170
|
-
let contentForValidation = resolved;
|
|
1171
|
-
const placeholderTokens = templateStr.match(rcsVarRegex) || [];
|
|
1172
|
-
placeholderTokens.forEach((t) => {
|
|
1173
|
-
const escaped = t.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1174
|
-
contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
|
|
1175
|
-
});
|
|
1176
|
-
if (!contentForValidation.trim()) {
|
|
1177
|
-
updateCarouselErrors(idx, { [field]: false });
|
|
1178
|
-
return;
|
|
1179
|
-
}
|
|
1180
|
-
const validationResponse = validateTags({
|
|
1181
|
-
content: contentForValidation,
|
|
1182
|
-
tagsParam: tags,
|
|
1183
|
-
injectedTagsParams: injectedTags,
|
|
1184
|
-
location,
|
|
1185
|
-
tagModule: getDefaultTags,
|
|
1186
|
-
eventContextTags,
|
|
1187
|
-
isFullMode,
|
|
1188
|
-
}) || {};
|
|
1189
|
-
const errorMsg =
|
|
1190
|
-
(validationResponse?.unsupportedTags?.length > 0 &&
|
|
1191
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1192
|
-
unsupportedTags: validationResponse.unsupportedTags,
|
|
1193
|
-
})) ||
|
|
1194
|
-
(validationResponse.isBraceError &&
|
|
1195
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
1196
|
-
false;
|
|
1197
|
-
updateCarouselErrors(idx, { [field]: errorMsg });
|
|
1198
|
-
});
|
|
1199
|
-
});
|
|
1200
|
-
}, [cardVarMapped, carouselData, tags, injectedTags, loadingTags, isCarouselType]);
|
|
1201
|
-
|
|
1202
416
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
1203
417
|
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
/** Same `{{tag}}` in both title and description must not share one semantic map entry. */
|
|
1207
|
-
const rcsSpanningSemanticVarNames = useMemo(
|
|
1208
|
-
() => getRcsSemanticVarNamesSpanningTitleAndDesc(templateTitle, templateDesc, rcsVarRegex),
|
|
1209
|
-
[templateTitle, templateDesc],
|
|
1210
|
-
);
|
|
1211
|
-
|
|
1212
|
-
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
1213
|
-
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
1214
|
-
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
1215
|
-
const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
1216
|
-
const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
1217
|
-
let varOrdinal = 0;
|
|
1218
|
-
for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
|
|
1219
|
-
const segmentToken = templateSegments[segmentIndexInField];
|
|
1220
|
-
if (rcsVarTestRegex.test(segmentToken)) {
|
|
1221
|
-
if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
|
|
1222
|
-
return offset + varOrdinal;
|
|
1223
|
-
}
|
|
1224
|
-
varOrdinal += 1;
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
return null;
|
|
1228
|
-
};
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* Master-branch resolve: uses numeric slot keys + semantic spanning detection for correct
|
|
1232
|
-
* multi-field variable resolution.
|
|
1233
|
-
*/
|
|
1234
|
-
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
418
|
+
const resolveTemplateWithMap = (str = '') => {
|
|
1235
419
|
if (!str) return '';
|
|
1236
|
-
const arr =
|
|
1237
|
-
let varOrdinal = 0;
|
|
420
|
+
const arr = splitTemplateVarString(str);
|
|
1238
421
|
return arr.map((elem) => {
|
|
1239
422
|
if (rcsVarTestRegex.test(elem)) {
|
|
1240
423
|
const key = getVarNameFromToken(elem);
|
|
1241
|
-
const
|
|
1242
|
-
varOrdinal += 1;
|
|
1243
|
-
const v = resolveCardVarMappedSlotValue(
|
|
1244
|
-
cardVarMapped,
|
|
1245
|
-
key,
|
|
1246
|
-
globalSlot,
|
|
1247
|
-
isEditLike,
|
|
1248
|
-
rcsSpanningSemanticVarNames.has(key),
|
|
1249
|
-
);
|
|
424
|
+
const v = cardVarMapped?.[key];
|
|
1250
425
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
1251
426
|
return String(v);
|
|
1252
427
|
}
|
|
@@ -1254,128 +429,39 @@ export const Rcs = (props) => {
|
|
|
1254
429
|
}).join('');
|
|
1255
430
|
};
|
|
1256
431
|
|
|
1257
|
-
const buildCarouselCardsForPreview = (cards = []) => (cards || []).map((card = {}) => {
|
|
1258
|
-
const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
|
|
1259
|
-
const videoSrc = card.videoAsset?.videoSrc || '';
|
|
1260
|
-
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(card.title || '') : (card.title || '');
|
|
1261
|
-
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(card.description || '') : (card.description || '');
|
|
1262
|
-
return {
|
|
1263
|
-
mediaType: (card.mediaType || '').toLowerCase(),
|
|
1264
|
-
imageSrc: card.imageSrc || '',
|
|
1265
|
-
videoSrc,
|
|
1266
|
-
videoPreviewImg: videoThumb,
|
|
1267
|
-
title: resolvedTitle,
|
|
1268
|
-
bodyText: resolvedDesc,
|
|
1269
|
-
suggestions: card.suggestions || [],
|
|
1270
|
-
};
|
|
1271
|
-
});
|
|
1272
|
-
|
|
1273
|
-
/**
|
|
1274
|
-
* Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
|
|
1275
|
-
* (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
|
|
1276
|
-
* TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
|
|
1277
|
-
*/
|
|
1278
|
-
const getTemplateContent = useCallback(() => {
|
|
1279
|
-
if (templateType === contentType.carousel) {
|
|
1280
|
-
const carouselDimKey = getCarouselDimensionKey();
|
|
1281
|
-
const carouselImgDims =
|
|
1282
|
-
RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
|
|
1283
|
-
const carouselVidDims =
|
|
1284
|
-
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
|
|
1285
|
-
|| RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
|
|
1286
|
-
return {
|
|
1287
|
-
carouselData: buildCarouselCardsForPreview(carouselData),
|
|
1288
|
-
carouselPreviewDimensions: {
|
|
1289
|
-
imageWidth: carouselImgDims.width,
|
|
1290
|
-
imageHeight: carouselImgDims.height,
|
|
1291
|
-
videoThumbWidth: carouselVidDims.width,
|
|
1292
|
-
videoThumbHeight: carouselVidDims.height,
|
|
1293
|
-
},
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
1297
|
-
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
1298
|
-
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
1299
|
-
|
|
1300
|
-
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1301
|
-
const titleVarCountForResolve = isMediaTypeText
|
|
1302
|
-
? 0
|
|
1303
|
-
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
1304
|
-
const resolvedTitle = isMediaTypeText
|
|
1305
|
-
? ''
|
|
1306
|
-
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
1307
|
-
const resolvedDesc = isSlotMappingMode
|
|
1308
|
-
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
1309
|
-
: templateDesc;
|
|
1310
|
-
|
|
1311
|
-
const mediaPreview = {};
|
|
1312
|
-
if (isMediaTypeImage && rcsImageSrc) {
|
|
1313
|
-
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
1314
|
-
}
|
|
1315
|
-
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
1316
|
-
if (rcsThumbnailSrc) {
|
|
1317
|
-
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
1318
|
-
} else if (rcsVideoSrc?.videoSrc) {
|
|
1319
|
-
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
1320
|
-
}
|
|
1321
|
-
if (rcsThumbnailSrc) {
|
|
1322
|
-
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
const contentObj = {
|
|
1327
|
-
templateHeader: resolvedTitle,
|
|
1328
|
-
templateMessage: resolvedDesc,
|
|
1329
|
-
...mediaPreview,
|
|
1330
|
-
...(suggestions.length > 0 && {
|
|
1331
|
-
suggestions: suggestions,
|
|
1332
|
-
}),
|
|
1333
|
-
};
|
|
1334
432
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
if (isFullMode || isEditFlow) return;
|
|
435
|
+
const tokens = [
|
|
436
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
437
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
438
|
+
];
|
|
439
|
+
if (!tokens.length) return;
|
|
440
|
+
setCardVarMapped((prev) => {
|
|
441
|
+
const next = { ...(prev || {}) };
|
|
442
|
+
let changed = false;
|
|
443
|
+
tokens.forEach((t) => {
|
|
444
|
+
const name = getVarNameFromToken(t);
|
|
445
|
+
if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
|
|
446
|
+
next[name] = '';
|
|
447
|
+
changed = true;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
return changed ? next : prev;
|
|
451
|
+
});
|
|
452
|
+
}, [isFullMode, templateTitle, templateDesc]);
|
|
1353
453
|
|
|
1354
|
-
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
1355
454
|
|
|
455
|
+
const RcsLabel = styled.div`
|
|
456
|
+
display: flex;
|
|
457
|
+
margin-top: 20px;
|
|
458
|
+
`;
|
|
1356
459
|
const paramObj = params || {};
|
|
1357
460
|
useEffect(() => {
|
|
1358
461
|
const { id } = paramObj;
|
|
1359
462
|
if (id && isFullMode) {
|
|
1360
463
|
setSpin(true);
|
|
1361
464
|
actions.getTemplateDetails(id, setSpin);
|
|
1362
|
-
} else if (!id && isFullMode) {
|
|
1363
|
-
// Create New: clear standalone media and ALL possible carousel card Redux slots.
|
|
1364
|
-
// Redux persists across mounts so we must clear unconditionally (carouselData may be [] on fresh mount).
|
|
1365
|
-
updateRcsImageSrc('');
|
|
1366
|
-
setRcsVideoSrc({});
|
|
1367
|
-
setRcsThumbnailSrc('');
|
|
1368
|
-
setAssetList({});
|
|
1369
|
-
setEditFlow(false);
|
|
1370
|
-
actions.clearRcsMediaAsset(0);
|
|
1371
|
-
actions.clearRcsMediaAsset(1);
|
|
1372
|
-
for (let cardIdx = 0; cardIdx < MAX_RCS_CAROUSEL_ALLOWED; cardIdx += 1) {
|
|
1373
|
-
actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIdx));
|
|
1374
|
-
actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIdx));
|
|
1375
|
-
actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIdx));
|
|
1376
|
-
}
|
|
1377
|
-
setCarouselData([]);
|
|
1378
|
-
setCarouselErrors([]);
|
|
1379
465
|
}
|
|
1380
466
|
return () => {
|
|
1381
467
|
actions.clearEditResponse();
|
|
@@ -1383,79 +469,67 @@ export const Rcs = (props) => {
|
|
|
1383
469
|
}, [paramObj.id]);
|
|
1384
470
|
|
|
1385
471
|
useEffect(() => {
|
|
1386
|
-
|
|
472
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
1387
473
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
return;
|
|
1395
|
-
}
|
|
1396
|
-
const nextVarMap = {};
|
|
1397
|
-
let varOrdinal = 0;
|
|
1398
|
-
arr.forEach((elem, idx) => {
|
|
1399
|
-
// Mustache tokens: {{1}}, {{user_name}}, {{tag.FORMAT_1}}, etc.
|
|
1400
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
1401
|
-
const id = `${elem}_${idx}`;
|
|
1402
|
-
const varName = getVarNameFromToken(elem);
|
|
1403
|
-
const globalSlot = slotOffset + varOrdinal;
|
|
1404
|
-
varOrdinal += 1;
|
|
1405
|
-
const mappedValue = resolveCardVarMappedSlotValue(
|
|
1406
|
-
cardVarMapped,
|
|
1407
|
-
varName,
|
|
1408
|
-
globalSlot,
|
|
1409
|
-
isEditLike,
|
|
1410
|
-
rcsSpanningSemanticVarNames.has(varName),
|
|
1411
|
-
);
|
|
1412
|
-
nextVarMap[id] = mappedValue;
|
|
474
|
+
const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
|
|
475
|
+
const arr = splitTemplateVarString(targetString);
|
|
476
|
+
if (!arr?.length) {
|
|
477
|
+
setVarMap({});
|
|
478
|
+
setUpdated([]);
|
|
479
|
+
return;
|
|
1413
480
|
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
481
|
+
const nextVarMap = {};
|
|
482
|
+
const nextUpdated = [...arr];
|
|
483
|
+
arr.forEach((elem, idx) => {
|
|
484
|
+
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
485
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
486
|
+
const id = `${elem}_${idx}`;
|
|
487
|
+
const varName = getVarNameFromToken(elem);
|
|
488
|
+
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
489
|
+
nextVarMap[id] = mappedValue;
|
|
490
|
+
if (mappedValue !== '') {
|
|
491
|
+
nextUpdated[idx] = mappedValue;
|
|
492
|
+
} else {
|
|
493
|
+
nextUpdated[idx] = elem;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
setVarMap(nextVarMap);
|
|
498
|
+
setUpdated(nextUpdated);
|
|
499
|
+
};
|
|
1421
500
|
|
|
1422
|
-
|
|
1423
|
-
|
|
501
|
+
initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
|
|
502
|
+
initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
|
|
503
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
504
|
+
|
|
505
|
+
useEffect(() => {
|
|
506
|
+
if(!isEditFlow && isFullMode){
|
|
1424
507
|
setRcsVideoSrc({});
|
|
1425
508
|
updateRcsImageSrc('');
|
|
1426
|
-
|
|
509
|
+
setUpdateRcsImageSrc('');
|
|
510
|
+
updateRcsThumbnailSrc('');
|
|
1427
511
|
setAssetList({});
|
|
1428
|
-
|
|
1429
|
-
|
|
512
|
+
}
|
|
513
|
+
}, [templateMediaType]);
|
|
1430
514
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
?? cardContentFirst?.status
|
|
1436
|
-
?? cardContentFirst?.approvalStatus
|
|
1437
|
-
?? '';
|
|
1438
|
-
const status = typeof raw === 'string' ? raw.trim() : String(raw);
|
|
1439
|
-
const n = status.toLowerCase();
|
|
1440
|
-
switch (n) {
|
|
1441
|
-
case 'approved':
|
|
515
|
+
const templateStatusHelper = (details) => {
|
|
516
|
+
const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
|
|
517
|
+
switch (status) {
|
|
518
|
+
case RCS_STATUSES.approved:
|
|
1442
519
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
1443
520
|
break;
|
|
1444
|
-
case
|
|
521
|
+
case RCS_STATUSES.pending:
|
|
1445
522
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
1446
523
|
break;
|
|
1447
|
-
case
|
|
524
|
+
case RCS_STATUSES.awaitingApproval:
|
|
1448
525
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
1449
526
|
break;
|
|
1450
|
-
case
|
|
527
|
+
case RCS_STATUSES.unavailable:
|
|
1451
528
|
setTemplateStatus(RCS_STATUSES.unavailable);
|
|
1452
529
|
break;
|
|
1453
|
-
case
|
|
530
|
+
case RCS_STATUSES.rejected:
|
|
1454
531
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
1455
532
|
break;
|
|
1456
|
-
case 'created':
|
|
1457
|
-
setTemplateStatus(RCS_STATUSES.created);
|
|
1458
|
-
break;
|
|
1459
533
|
default:
|
|
1460
534
|
setTemplateStatus(status);
|
|
1461
535
|
break;
|
|
@@ -1509,287 +583,48 @@ export const Rcs = (props) => {
|
|
|
1509
583
|
};
|
|
1510
584
|
|
|
1511
585
|
useEffect(() => {
|
|
1512
|
-
const details =
|
|
586
|
+
const details = isFullMode ? rcsData?.templateDetails : templateData;
|
|
1513
587
|
if (details && Object.keys(details).length > 0) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
'versions.base.content.RCS.rcsContent.cardContent[0]',
|
|
1518
|
-
);
|
|
1519
|
-
const rcsContent = get(details, 'versions.base.content.RCS.rcsContent', {});
|
|
1520
|
-
const cardType = (rcsContent?.cardType || '').toString().toLowerCase();
|
|
1521
|
-
|
|
1522
|
-
setEditFlow(true);
|
|
1523
|
-
setTemplateName(details?.name || details?.creativeName || '');
|
|
1524
|
-
|
|
1525
|
-
const cardFromTop = get(details, 'rcsContent.cardContent[0]');
|
|
1526
|
-
const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
|
|
1527
|
-
const cardVarMappedFromCardContent =
|
|
1528
|
-
card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
|
|
1529
|
-
? card0.cardVarMapped
|
|
1530
|
-
: {};
|
|
1531
|
-
const cardVarMappedFromRootMirror =
|
|
1532
|
-
details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
|
|
1533
|
-
? details.rcsCardVarMapped
|
|
1534
|
-
: {};
|
|
1535
|
-
// Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
|
|
1536
|
-
// nested versions.cardContent[0].cardVarMapped is dropped on reload.
|
|
1537
|
-
const mergedCardVarMappedFromPayload = {
|
|
1538
|
-
...cardVarMappedFromRootMirror,
|
|
1539
|
-
...cardVarMappedFromCardContent,
|
|
1540
|
-
};
|
|
1541
|
-
const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
|
|
1542
|
-
const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
|
|
1543
|
-
const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
|
|
1544
|
-
Object.keys(mergedCardVarMappedFromPayload)
|
|
1545
|
-
.sort()
|
|
1546
|
-
.reduce((accumulator, mapKey) => {
|
|
1547
|
-
accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
|
|
1548
|
-
return accumulator;
|
|
1549
|
-
}, {}),
|
|
1550
|
-
)}`;
|
|
1551
|
-
if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
|
|
1552
|
-
lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
|
|
1553
|
-
setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
|
|
588
|
+
if (!isFullMode) {
|
|
589
|
+
const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
|
|
590
|
+
setCardVarMapped(tempCardVarMapped);
|
|
1554
591
|
}
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
|
|
1558
|
-
];
|
|
1559
|
-
const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
1560
|
-
// Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
|
|
1561
|
-
// getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
|
|
1562
|
-
const cardVarMappedBeforeCoalesce = isFullMode
|
|
1563
|
-
? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
|
|
1564
|
-
: { ...mergedCardVarMappedFromPayload };
|
|
1565
|
-
const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
1566
|
-
cardVarMappedBeforeCoalesce,
|
|
1567
|
-
loadedTitleForMap,
|
|
1568
|
-
loadedDescForMap,
|
|
1569
|
-
rcsVarRegex,
|
|
1570
|
-
);
|
|
1571
|
-
const cardVarMappedAfterNumericSlotSync = !isFullMode
|
|
1572
|
-
? syncCardVarMappedSemanticsFromSlots(
|
|
1573
|
-
cardVarMappedAfterCoalesce,
|
|
1574
|
-
loadedTitleForMap,
|
|
1575
|
-
loadedDescForMap,
|
|
1576
|
-
rcsVarRegex,
|
|
1577
|
-
)
|
|
1578
|
-
: cardVarMappedAfterCoalesce;
|
|
1579
|
-
const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
|
|
1580
|
-
// Pre-populate variable/tag mappings while opening an existing template in edit flows
|
|
1581
|
-
setCardVarMapped((previousVarMapState) => {
|
|
1582
|
-
const previousVarMap = previousVarMapState ?? {};
|
|
1583
|
-
if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
|
|
1584
|
-
const previousVarMapKeys = Object.keys(previousVarMap);
|
|
1585
|
-
const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
|
|
1586
|
-
if (previousVarMapKeys.length === nextVarMapKeys.length) {
|
|
1587
|
-
const allSlotValuesMatchPrevious = previousVarMapKeys.every(
|
|
1588
|
-
(key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
|
|
1589
|
-
);
|
|
1590
|
-
if (allSlotValuesMatchPrevious) return previousVarMapState;
|
|
1591
|
-
}
|
|
1592
|
-
return hydratedCardVarMappedResult;
|
|
1593
|
-
});
|
|
1594
|
-
|
|
1595
|
-
if (cardType === contentType.carousel) {
|
|
1596
|
-
|
|
1597
|
-
setTemplateType(contentType.carousel);
|
|
1598
|
-
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1599
|
-
const cardSettings = rcsContent?.cardSettings || {};
|
|
1600
|
-
const cardWidth = cardSettings?.cardWidth || SMALL;
|
|
1601
|
-
setSelectedCarouselWidth(cardWidth);
|
|
1602
|
-
|
|
1603
|
-
const cards = Array.isArray(rcsContent?.cardContent) ? rcsContent.cardContent : [];
|
|
1604
|
-
const firstHeight = cards?.[0]?.media?.height || MEDIUM;
|
|
1605
|
-
setSelectedCarouselHeight(firstHeight);
|
|
1606
|
-
setSelectedCarousel(`${firstHeight}_${cardWidth}`);
|
|
1607
|
-
setActiveCarouselIndex('0');
|
|
1608
|
-
|
|
1609
|
-
const hydratedCards = cards.map((c = {}, idx) => {
|
|
1610
|
-
const mediaType = c.mediaType;
|
|
1611
|
-
const media = c.media || {};
|
|
1612
|
-
const mediaUrl = media.mediaUrl || '';
|
|
1613
|
-
const thumbUrl = media.thumbnailUrl || '';
|
|
1614
|
-
const rawSuggestions = Array.isArray(c.suggestions) ? c.suggestions : [];
|
|
1615
|
-
const suggestions = idx === 0 && rawSuggestions.length === 0
|
|
1616
|
-
? cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS)
|
|
1617
|
-
: rawSuggestions;
|
|
1618
|
-
return {
|
|
1619
|
-
title: c.title || '',
|
|
1620
|
-
description: c.description || '',
|
|
1621
|
-
mediaType,
|
|
1622
|
-
imageSrc: mediaType === RCS_MEDIA_TYPES.IMAGE ? mediaUrl : '',
|
|
1623
|
-
videoAsset: mediaType === RCS_MEDIA_TYPES.VIDEO ? {
|
|
1624
|
-
videoSrc: mediaUrl,
|
|
1625
|
-
previewUrl: thumbUrl,
|
|
1626
|
-
videoThumbnail: thumbUrl,
|
|
1627
|
-
videoName: c?.media?.videoName || '',
|
|
1628
|
-
} : {},
|
|
1629
|
-
thumbnailSrc: mediaType === RCS_MEDIA_TYPES.VIDEO ? thumbUrl : '',
|
|
1630
|
-
suggestions,
|
|
1631
|
-
};
|
|
1632
|
-
});
|
|
1633
|
-
setCarouselData(
|
|
1634
|
-
hydratedCards.length > 0
|
|
1635
|
-
? ensureFirstCardDefaultPhoneSuggestions(hydratedCards)
|
|
1636
|
-
: [cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)],
|
|
1637
|
-
);
|
|
1638
|
-
setCarouselErrors(new Array(hydratedCards.length > 0 ? hydratedCards.length : 1).fill({}));
|
|
1639
|
-
|
|
1640
|
-
// Status bar uses first card's Status, keep existing behavior.
|
|
1641
|
-
if (isHostInfoBip) {
|
|
1642
|
-
setTemplateStatus('');
|
|
1643
|
-
} else {
|
|
1644
|
-
const firstCard = cards?.[0] || {};
|
|
1645
|
-
const cardForCarouselStatus = {
|
|
1646
|
-
...firstCard,
|
|
1647
|
-
Status:
|
|
1648
|
-
firstCard.Status
|
|
1649
|
-
?? firstCard.status
|
|
1650
|
-
?? firstCard.approvalStatus
|
|
1651
|
-
?? get(details, 'templateStatus')
|
|
1652
|
-
?? get(details, 'approvalStatus')
|
|
1653
|
-
?? get(details, 'creativeStatus')
|
|
1654
|
-
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
1655
|
-
?? '',
|
|
1656
|
-
};
|
|
1657
|
-
templateStatusHelper(cardForCarouselStatus);
|
|
1658
|
-
}
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
const mediaType =
|
|
1663
|
-
card0.mediaType
|
|
1664
|
-
|| get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
1665
|
-
if (cardType !== contentType.carousel && mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
592
|
+
const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
593
|
+
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
1666
594
|
setTemplateType(contentType.text_message);
|
|
1667
|
-
} else if (cardType !== contentType.carousel && mediaType !== RCS_MEDIA_TYPES.NONE) {
|
|
1668
|
-
setTemplateType(contentType.rich_card);
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
const loadedTitle = loadedTitleForMap;
|
|
1672
|
-
const loadedDesc = loadedDescForMap;
|
|
1673
|
-
const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
|
|
1674
|
-
loadedTitle,
|
|
1675
|
-
loadedDesc,
|
|
1676
|
-
isFullMode,
|
|
1677
|
-
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
1678
|
-
rcsVarRegex,
|
|
1679
|
-
});
|
|
1680
|
-
setTemplateTitle(normalizedTitle);
|
|
1681
|
-
setTemplateDesc(normalizedDesc);
|
|
1682
|
-
setSuggestions(
|
|
1683
|
-
Array.isArray(card0.suggestions)
|
|
1684
|
-
? card0.suggestions
|
|
1685
|
-
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
|
|
1686
|
-
);
|
|
1687
|
-
const cardForStatus = {
|
|
1688
|
-
...card0,
|
|
1689
|
-
Status:
|
|
1690
|
-
card0.Status
|
|
1691
|
-
?? card0.status
|
|
1692
|
-
?? card0.approvalStatus
|
|
1693
|
-
?? get(details, 'templateStatus')
|
|
1694
|
-
?? get(details, 'approvalStatus')
|
|
1695
|
-
?? get(details, 'creativeStatus')
|
|
1696
|
-
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
1697
|
-
?? '',
|
|
1698
|
-
};
|
|
1699
|
-
if (isHostInfoBip) {
|
|
1700
|
-
setTemplateStatus('');
|
|
1701
595
|
} else {
|
|
1702
|
-
|
|
596
|
+
setTemplateType(contentType.rich_card);
|
|
1703
597
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
const
|
|
1709
|
-
|
|
1710
|
-
|
|
598
|
+
setEditFlow(true);
|
|
599
|
+
setTemplateName(details.name || '');
|
|
600
|
+
const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
|
|
601
|
+
const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
|
|
602
|
+
const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
|
|
603
|
+
const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
|
|
604
|
+
const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
|
|
605
|
+
setTemplateTitle(normalizedTitle);
|
|
606
|
+
setTemplateDesc(normalizedDesc);
|
|
607
|
+
setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
|
|
608
|
+
templateStatusHelper(details);
|
|
609
|
+
const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
610
|
+
const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
|
|
1711
611
|
setMediaData(mediaData, mediaType, cardSettings);
|
|
1712
612
|
if (details?.edit) {
|
|
1713
613
|
const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
|
|
1714
614
|
setRcsAccount(rcsAccountId);
|
|
1715
615
|
}
|
|
1716
|
-
|
|
1717
|
-
const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
1718
|
-
const base = get(smsFallbackContent, 'versions.base', {});
|
|
1719
|
-
const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
|
|
1720
|
-
const smsEditor = base['sms-editor'];
|
|
1721
|
-
const fromNested = Array.isArray(updatedEditor)
|
|
1722
|
-
? updatedEditor.join('')
|
|
1723
|
-
: (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
|
|
1724
|
-
const fallbackMessage = smsFallbackContent.smsContent
|
|
1725
|
-
|| smsFallbackContent.smsTemplateContent
|
|
1726
|
-
|| smsFallbackContent.message
|
|
1727
|
-
|| fromNested
|
|
1728
|
-
|| '';
|
|
1729
|
-
const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
1730
|
-
const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
|
|
1731
|
-
const hasFallbackPayload =
|
|
1732
|
-
smsFallbackContent
|
|
1733
|
-
&& Object.keys(smsFallbackContent).length > 0
|
|
1734
|
-
&& (
|
|
1735
|
-
!!smsFallbackContent.smsTemplateName
|
|
1736
|
-
|| !!fallbackMessage
|
|
1737
|
-
|| hasVarMapped
|
|
1738
|
-
);
|
|
1739
|
-
if (hasFallbackPayload) {
|
|
1740
|
-
if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
|
|
1741
|
-
console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
|
|
1742
|
-
}
|
|
1743
|
-
const unicodeFromApi =
|
|
1744
|
-
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
1745
|
-
? smsFallbackContent.unicodeValidity
|
|
1746
|
-
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
1747
|
-
const registeredSenderIdsFromApi =
|
|
1748
|
-
extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackContent);
|
|
1749
|
-
const nextSmsState = {
|
|
1750
|
-
templateName: smsFallbackContent.smsTemplateName || '',
|
|
1751
|
-
content: fallbackMessage,
|
|
1752
|
-
templateContent: fallbackMessage,
|
|
1753
|
-
unicodeValidity: unicodeFromApi,
|
|
1754
|
-
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
1755
|
-
...(Array.isArray(registeredSenderIdsFromApi) && registeredSenderIdsFromApi.length > 0
|
|
1756
|
-
? { registeredSenderIds: registeredSenderIdsFromApi }
|
|
1757
|
-
: {}),
|
|
1758
|
-
};
|
|
1759
|
-
const hydrationKey = JSON.stringify({
|
|
1760
|
-
creativeKey: details._id || details.name || details.creativeName || '',
|
|
1761
|
-
templateName: nextSmsState.templateName,
|
|
1762
|
-
content: nextSmsState.content,
|
|
1763
|
-
unicodeValidity: nextSmsState.unicodeValidity,
|
|
1764
|
-
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
1765
|
-
senderIds:
|
|
1766
|
-
Array.isArray(registeredSenderIdsFromApi)
|
|
1767
|
-
? registeredSenderIdsFromApi.join('\u001f')
|
|
1768
|
-
: '',
|
|
1769
|
-
});
|
|
1770
|
-
if (
|
|
1771
|
-
isFullMode
|
|
1772
|
-
|| lastSmsFallbackHydrationKeyRef.current !== hydrationKey
|
|
1773
|
-
) {
|
|
1774
|
-
lastSmsFallbackHydrationKeyRef.current = hydrationKey;
|
|
1775
|
-
setSmsFallbackData(nextSmsState);
|
|
1776
|
-
}
|
|
1777
|
-
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
1778
|
-
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
1779
|
-
setSmsFallbackData(null);
|
|
1780
|
-
}
|
|
1781
616
|
}
|
|
1782
|
-
}, [
|
|
617
|
+
}, [rcsData, templateData, isFullMode, isEditFlow]);
|
|
618
|
+
|
|
1783
619
|
|
|
1784
620
|
useEffect(() => {
|
|
1785
621
|
if (templateType === contentType.text_message) {
|
|
1786
622
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1787
|
-
|
|
1788
|
-
|
|
623
|
+
setTemplateTitle('');
|
|
624
|
+
setTemplateTitleError('');
|
|
1789
625
|
if (!isEditFlow && isFullMode) {
|
|
1790
|
-
setTemplateTitle('');
|
|
1791
|
-
setTemplateTitleError('');
|
|
1792
626
|
setUpdateRcsImageSrc('');
|
|
627
|
+
setUpdateRcsVideoSrc({});
|
|
1793
628
|
setRcsVideoSrc({});
|
|
1794
629
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
1795
630
|
}
|
|
@@ -1816,8 +651,7 @@ export const Rcs = (props) => {
|
|
|
1816
651
|
if (!showDltContainer) {
|
|
1817
652
|
const { type, module } = location.query || {};
|
|
1818
653
|
const isEmbedded = type === EMBEDDED;
|
|
1819
|
-
|
|
1820
|
-
const context = isEmbedded ? module : 'outbound';
|
|
654
|
+
const context = isEmbedded ? module : DEFAULT;
|
|
1821
655
|
const embedded = isEmbedded ? type : FULL;
|
|
1822
656
|
const query = {
|
|
1823
657
|
layout: SMS,
|
|
@@ -1828,9 +662,9 @@ export const Rcs = (props) => {
|
|
|
1828
662
|
if (getDefaultTags) {
|
|
1829
663
|
query.context = getDefaultTags;
|
|
1830
664
|
}
|
|
1831
|
-
|
|
665
|
+
globalActions.fetchSchemaForEntity(query);
|
|
1832
666
|
}
|
|
1833
|
-
}, [showDltContainer
|
|
667
|
+
}, [showDltContainer]);
|
|
1834
668
|
|
|
1835
669
|
useEffect(() => {
|
|
1836
670
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -1853,114 +687,16 @@ export const Rcs = (props) => {
|
|
|
1853
687
|
context,
|
|
1854
688
|
embedded,
|
|
1855
689
|
};
|
|
1856
|
-
if (getDefaultTags) {
|
|
1857
|
-
query.context = getDefaultTags;
|
|
1858
|
-
}
|
|
1859
|
-
fetchTagSchemaIfNewQuery(query);
|
|
1860
690
|
globalActions.fetchSchemaForEntity(query);
|
|
1861
691
|
};
|
|
1862
692
|
|
|
1863
|
-
const
|
|
1864
|
-
if (!
|
|
1865
|
-
const
|
|
1866
|
-
return templateStr.replace(re, `{{${tagName}}}`);
|
|
1867
|
-
};
|
|
1868
|
-
|
|
1869
|
-
const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
|
|
1870
|
-
if (!varSegmentCompositeDomId) return;
|
|
1871
|
-
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
1872
|
-
if (underscoreIndexInCompositeId === -1) return;
|
|
1873
|
-
const segmentIndexSuffix = varSegmentCompositeDomId.slice(underscoreIndexInCompositeId + 1);
|
|
1874
|
-
if (segmentIndexSuffix === '' || isNaN(Number(segmentIndexSuffix))) return;
|
|
1875
|
-
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
1876
|
-
const semanticOrNumericVarName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
1877
|
-
if (!semanticOrNumericVarName) return;
|
|
1878
|
-
const isNumericPlaceholderSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(semanticOrNumericVarName));
|
|
1879
|
-
const templateStringForField =
|
|
1880
|
-
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
1881
|
-
const titleOrMessageFieldType =
|
|
1882
|
-
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
1883
|
-
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
1884
|
-
varSegmentCompositeDomId,
|
|
1885
|
-
templateStringForField,
|
|
1886
|
-
titleOrMessageFieldType,
|
|
1887
|
-
);
|
|
1888
|
-
const cardVarMappedNumericSlotKey =
|
|
1889
|
-
globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined
|
|
1890
|
-
? String(globalVarSlotIndexZeroBased + 1)
|
|
1891
|
-
: null;
|
|
1892
|
-
|
|
1893
|
-
setCardVarMapped((previousCardVarMapped) => {
|
|
1894
|
-
const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
|
|
1895
|
-
if (isNumericPlaceholderSlot) {
|
|
1896
|
-
const existingValueBeforeAppend = (
|
|
1897
|
-
previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
|
|
1898
|
-
).toString();
|
|
1899
|
-
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
1900
|
-
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
1901
|
-
updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
|
|
1902
|
-
} else {
|
|
1903
|
-
// Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
|
|
1904
|
-
// "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
|
|
1905
|
-
// read/write the global numeric slot only and drop the shared semantic key.
|
|
1906
|
-
const existingValueBeforeAppend = cardVarMappedNumericSlotKey
|
|
1907
|
-
? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
|
|
1908
|
-
: String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
|
|
1909
|
-
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
1910
|
-
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
1911
|
-
if (cardVarMappedNumericSlotKey) {
|
|
1912
|
-
updatedCardVarMapped[cardVarMappedNumericSlotKey] = mappedValueAfterAppendingTag;
|
|
1913
|
-
} else {
|
|
1914
|
-
updatedCardVarMapped[semanticOrNumericVarName] = mappedValueAfterAppendingTag;
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
return updatedCardVarMapped;
|
|
1918
|
-
});
|
|
1919
|
-
|
|
1920
|
-
if (
|
|
1921
|
-
isNumericPlaceholderSlot
|
|
1922
|
-
&& (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
|
|
1923
|
-
) {
|
|
1924
|
-
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
|
|
1925
|
-
setTemplateTitle((previousTitle) => {
|
|
1926
|
-
const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
1927
|
-
previousTitle || '',
|
|
1928
|
-
semanticOrNumericVarName,
|
|
1929
|
-
selectedTagNameFromPicker,
|
|
1930
|
-
);
|
|
1931
|
-
if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
|
|
1932
|
-
setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
|
|
1933
|
-
// 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
|
|
1934
|
-
setRcsVarSegmentEditorRemountKey((k) => k + 1);
|
|
1935
|
-
return titleAfterReplacingNumericPlaceholder;
|
|
1936
|
-
});
|
|
1937
|
-
} else {
|
|
1938
|
-
setTemplateDesc((previousDescription) => {
|
|
1939
|
-
const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
1940
|
-
previousDescription || '',
|
|
1941
|
-
semanticOrNumericVarName,
|
|
1942
|
-
selectedTagNameFromPicker,
|
|
1943
|
-
);
|
|
1944
|
-
if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
|
|
1945
|
-
return previousDescription;
|
|
1946
|
-
}
|
|
1947
|
-
setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
|
|
1948
|
-
setRcsVarSegmentEditorRemountKey((k) => k + 1);
|
|
1949
|
-
return descriptionAfterReplacingNumericPlaceholder;
|
|
1950
|
-
});
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
};
|
|
1954
|
-
|
|
1955
|
-
const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
|
|
1956
|
-
|
|
1957
|
-
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
1958
|
-
|
|
1959
|
-
const onCarouselTagSelect = (data) => {
|
|
1960
|
-
if (!carouselFocusedVarId) return;
|
|
1961
|
-
const sep = carouselFocusedVarId.lastIndexOf('_');
|
|
693
|
+
const onTagSelect = (data, areaId) => {
|
|
694
|
+
if (!areaId) return;
|
|
695
|
+
const sep = areaId.lastIndexOf('_');
|
|
1962
696
|
if (sep === -1) return;
|
|
1963
|
-
const
|
|
697
|
+
const numId = Number(areaId.slice(sep + 1));
|
|
698
|
+
if (isNaN(numId)) return;
|
|
699
|
+
const token = areaId.slice(0, sep);
|
|
1964
700
|
const variableName = getVarNameFromToken(token);
|
|
1965
701
|
if (!variableName) return;
|
|
1966
702
|
setCardVarMapped((prev) => {
|
|
@@ -1973,11 +709,15 @@ export const Rcs = (props) => {
|
|
|
1973
709
|
});
|
|
1974
710
|
};
|
|
1975
711
|
|
|
712
|
+
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
713
|
+
|
|
714
|
+
const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
|
|
715
|
+
|
|
1976
716
|
const onTagSelectFallback = (data) => {
|
|
1977
717
|
const tempMsg = `${fallbackMessage}{{${data}}}`;
|
|
1978
718
|
const error = fallbackMessageErrorHandler(tempMsg);
|
|
1979
|
-
|
|
1980
|
-
|
|
719
|
+
setFallbackMessage(tempMsg);
|
|
720
|
+
setFallbackMessageError(error);
|
|
1981
721
|
};
|
|
1982
722
|
|
|
1983
723
|
|
|
@@ -1992,14 +732,15 @@ export const Rcs = (props) => {
|
|
|
1992
732
|
};
|
|
1993
733
|
// tag Code end
|
|
1994
734
|
|
|
1995
|
-
const renderLabel = (value, desc) => {
|
|
735
|
+
const renderLabel = (value, showLabel, desc) => {
|
|
736
|
+
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
1996
737
|
return (
|
|
1997
738
|
<>
|
|
1998
|
-
<
|
|
739
|
+
<RcsLabel>
|
|
1999
740
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
2000
|
-
</
|
|
741
|
+
</RcsLabel>
|
|
2001
742
|
{desc && (
|
|
2002
|
-
<CapLabel type="label3"
|
|
743
|
+
<CapLabel type="label3" style={{ marginBottom: '17px' }}>
|
|
2003
744
|
{formatMessage(messages[desc])}
|
|
2004
745
|
</CapLabel>
|
|
2005
746
|
)}
|
|
@@ -2018,8 +759,13 @@ export const Rcs = (props) => {
|
|
|
2018
759
|
},
|
|
2019
760
|
{
|
|
2020
761
|
value: contentType.carousel,
|
|
2021
|
-
label:
|
|
2022
|
-
|
|
762
|
+
label: (
|
|
763
|
+
<CapTooltip title={formatMessage(messages.disabledCarouselTooltip)}>
|
|
764
|
+
{formatMessage(messages.carousel)}
|
|
765
|
+
</CapTooltip>
|
|
766
|
+
),
|
|
767
|
+
disabled: true,
|
|
768
|
+
},
|
|
2023
769
|
];
|
|
2024
770
|
|
|
2025
771
|
const onTemplateNameChange = ({ target: { value } }) => {
|
|
@@ -2030,10 +776,6 @@ export const Rcs = (props) => {
|
|
|
2030
776
|
|
|
2031
777
|
const onTemplateTypeChange = ({ target: { value } }) => {
|
|
2032
778
|
setTemplateType(value);
|
|
2033
|
-
// Carousel has per-card media; keep template-level media type neutral.
|
|
2034
|
-
if (value === contentType.carousel) {
|
|
2035
|
-
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
2036
|
-
}
|
|
2037
779
|
};
|
|
2038
780
|
|
|
2039
781
|
|
|
@@ -2054,39 +796,8 @@ export const Rcs = (props) => {
|
|
|
2054
796
|
const onTemplateMediaTypeChange = ({ target: { value } }) => {
|
|
2055
797
|
setTemplateMediaType(value);
|
|
2056
798
|
};
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
if (descArray?.length) {
|
|
2060
|
-
descArray.forEach((elem, index) => {
|
|
2061
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
2062
|
-
// Variable input
|
|
2063
|
-
renderArray.push(
|
|
2064
|
-
<TextArea
|
|
2065
|
-
id={`${elem}_${index}`}
|
|
2066
|
-
key={`${elem}_${index}`}
|
|
2067
|
-
placeholder={`enter the value for ${elem}`}
|
|
2068
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2069
|
-
onChange={e => textAreaValueChange(e, type)}
|
|
2070
|
-
value={textAreaValue(index, type)}
|
|
2071
|
-
onFocus={(e) => setTextAreaId(e, type)}
|
|
2072
|
-
/>
|
|
2073
|
-
);
|
|
2074
|
-
} else if (elem) {
|
|
2075
|
-
// Static text
|
|
2076
|
-
renderArray.push(
|
|
2077
|
-
<TextArea
|
|
2078
|
-
key={`static_${index}`}
|
|
2079
|
-
value={elem}
|
|
2080
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2081
|
-
disabled
|
|
2082
|
-
className="rcs-edit-template-message-static-textarea"
|
|
2083
|
-
/>
|
|
2084
|
-
);
|
|
2085
|
-
}
|
|
2086
|
-
});
|
|
2087
|
-
}
|
|
2088
|
-
return renderArray;
|
|
2089
|
-
};
|
|
799
|
+
|
|
800
|
+
|
|
2090
801
|
const onTemplateTitleChange = ({ target: { value } }) => {
|
|
2091
802
|
let errorMessage = false;
|
|
2092
803
|
if (templateType === contentType.rich_card && !value.trim()) {
|
|
@@ -2102,7 +813,7 @@ export const Rcs = (props) => {
|
|
|
2102
813
|
|
|
2103
814
|
const onTemplateDescChange = ({ target: { value } }) => {
|
|
2104
815
|
let errorMessage = false;
|
|
2105
|
-
if(templateType === contentType.text_message && value?.length >
|
|
816
|
+
if(templateType === contentType.text_message && value?.length > RCS_TEXT_MESSAGE_MAX_LENGTH){
|
|
2106
817
|
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
2107
818
|
} else if(templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH){
|
|
2108
819
|
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
@@ -2118,16 +829,16 @@ export const Rcs = (props) => {
|
|
|
2118
829
|
|
|
2119
830
|
const templateDescErrorHandler = (value) => {
|
|
2120
831
|
let errorMessage = false;
|
|
2121
|
-
const {
|
|
832
|
+
const { isBraceError } = validateTags({
|
|
2122
833
|
content: value,
|
|
2123
834
|
tagsParam: tags,
|
|
2124
|
-
injectedTagsParams: injectedTags,
|
|
2125
835
|
location,
|
|
2126
836
|
tagModule: getDefaultTags,
|
|
837
|
+
isFullMode,
|
|
2127
838
|
}) || {};
|
|
2128
839
|
|
|
2129
840
|
const maxLength = templateType === contentType.text_message
|
|
2130
|
-
?
|
|
841
|
+
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
2131
842
|
: RCS_RICH_CARD_MAX_LENGTH;
|
|
2132
843
|
|
|
2133
844
|
if (value === '' && isMediaTypeText) {
|
|
@@ -2145,30 +856,15 @@ export const Rcs = (props) => {
|
|
|
2145
856
|
|
|
2146
857
|
const onFallbackMessageChange = ({ target: { value } }) => {
|
|
2147
858
|
const error = fallbackMessageErrorHandler(value);
|
|
2148
|
-
|
|
2149
|
-
|
|
859
|
+
setFallbackMessage(value);
|
|
860
|
+
setFallbackMessageError(error);
|
|
2150
861
|
};
|
|
2151
862
|
|
|
2152
863
|
const fallbackMessageErrorHandler = (value) => {
|
|
2153
|
-
let errorMessage = false;
|
|
2154
|
-
const { unsupportedTags } = validateTags({
|
|
2155
|
-
content: value,
|
|
2156
|
-
tagsParam: tags,
|
|
2157
|
-
injectedTagsParams: injectedTags,
|
|
2158
|
-
location,
|
|
2159
|
-
tagModule: getDefaultTags,
|
|
2160
|
-
}) || {};
|
|
2161
864
|
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
2162
|
-
|
|
2163
|
-
} else if (unsupportedTags?.length > 0) {
|
|
2164
|
-
errorMessage = formatMessage(
|
|
2165
|
-
globalMessages.unsupportedTagsValidationError,
|
|
2166
|
-
{
|
|
2167
|
-
unsupportedTags,
|
|
2168
|
-
},
|
|
2169
|
-
);
|
|
865
|
+
return formatMessage(messages.fallbackMsgLenError);
|
|
2170
866
|
}
|
|
2171
|
-
return
|
|
867
|
+
return false;
|
|
2172
868
|
};
|
|
2173
869
|
|
|
2174
870
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
@@ -2215,43 +911,53 @@ export const Rcs = (props) => {
|
|
|
2215
911
|
if(!isFullMode){
|
|
2216
912
|
return false;
|
|
2217
913
|
}
|
|
2218
|
-
|
|
2219
|
-
if (!/^[\w.]+$/.test(paramName)) {
|
|
914
|
+
if (!/^\w+$/.test(paramName)) {
|
|
2220
915
|
return formatMessage(messages.unknownCharactersError);
|
|
2221
916
|
}
|
|
2222
917
|
}
|
|
2223
918
|
return false;
|
|
2224
919
|
};
|
|
920
|
+
|
|
921
|
+
const templateHeaderErrorHandler = (value) => {
|
|
922
|
+
let errorMessage = false;
|
|
923
|
+
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
924
|
+
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
925
|
+
} else {
|
|
926
|
+
errorMessage = variableErrorHandling(value);
|
|
927
|
+
}
|
|
928
|
+
return errorMessage;
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
const templateMessageErrorHandler = (value) => {
|
|
933
|
+
let errorMessage = false;
|
|
934
|
+
if (value === '') {
|
|
935
|
+
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
936
|
+
} else if (
|
|
937
|
+
value?.length
|
|
938
|
+
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
939
|
+
) {
|
|
940
|
+
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
941
|
+
} else {
|
|
942
|
+
errorMessage = variableErrorHandling(value);
|
|
943
|
+
}
|
|
944
|
+
return errorMessage;
|
|
945
|
+
};
|
|
946
|
+
|
|
2225
947
|
|
|
2226
948
|
const onMessageAddVar = () => {
|
|
2227
|
-
onAddVar(templateDesc);
|
|
949
|
+
onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
|
|
2228
950
|
};
|
|
2229
951
|
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
* (duplicate numbers would share a cardVarMapped key and bleed values across fields).
|
|
2235
|
-
*/
|
|
2236
|
-
const getNextRcsNumericVarNumber = (titleStr, descStr) => {
|
|
2237
|
-
const allExistingVars = [
|
|
2238
|
-
...(titleStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
|
|
2239
|
-
...(descStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
|
|
2240
|
-
];
|
|
2241
|
-
const existingNumbers = allExistingVars.flatMap(v => {
|
|
2242
|
-
const m = v.match(/\d+/);
|
|
2243
|
-
return m ? [parseInt(m[0], 10)] : [];
|
|
2244
|
-
});
|
|
952
|
+
const onAddVar = (type, messageContent, regex) => {
|
|
953
|
+
// Always append the next variable at the end, like WhatsApp
|
|
954
|
+
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
955
|
+
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
2245
956
|
let nextNumber = 1;
|
|
2246
957
|
while (existingNumbers.includes(nextNumber)) {
|
|
2247
958
|
nextNumber++;
|
|
2248
959
|
}
|
|
2249
|
-
|
|
2250
|
-
};
|
|
2251
|
-
|
|
2252
|
-
const onAddVar = (messageContent) => {
|
|
2253
|
-
const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
|
|
2254
|
-
if (nextNumber === null) {
|
|
960
|
+
if (nextNumber > 19) {
|
|
2255
961
|
return;
|
|
2256
962
|
}
|
|
2257
963
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -2263,13 +969,15 @@ const onAddVar = (messageContent) => {
|
|
|
2263
969
|
};
|
|
2264
970
|
|
|
2265
971
|
const onTitleAddVar = () => {
|
|
2266
|
-
//
|
|
2267
|
-
// duplicate a number already used in the description. Duplicate numeric
|
|
2268
|
-
// names would share the same cardVarMapped semantic key, causing the
|
|
2269
|
-
// description slot to reflect the title slot value and vice-versa.
|
|
972
|
+
// Always append the next variable at the end, like WhatsApp
|
|
2270
973
|
const messageContent = templateTitle;
|
|
2271
|
-
const
|
|
2272
|
-
|
|
974
|
+
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
975
|
+
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
976
|
+
let nextNumber = 1;
|
|
977
|
+
while (existingNumbers.includes(nextNumber)) {
|
|
978
|
+
nextNumber++;
|
|
979
|
+
}
|
|
980
|
+
if (nextNumber > 19) {
|
|
2273
981
|
return;
|
|
2274
982
|
}
|
|
2275
983
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -2281,35 +989,26 @@ const onTitleAddVar = () => {
|
|
|
2281
989
|
setTemplateTitleError(error);
|
|
2282
990
|
};
|
|
2283
991
|
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
if (!token) return;
|
|
2305
|
-
setCarouselData((prev = []) => {
|
|
2306
|
-
const updated = cloneDeep(prev);
|
|
2307
|
-
if (!updated[cardIndex]) return prev;
|
|
2308
|
-
const current = (updated[cardIndex][fieldName] || '').toString();
|
|
2309
|
-
updated[cardIndex][fieldName] = `${current}${token}`;
|
|
2310
|
-
return updated;
|
|
2311
|
-
});
|
|
2312
|
-
};
|
|
992
|
+
|
|
993
|
+
const splitTemplateVarString = (str) => {
|
|
994
|
+
if (!str) return [];
|
|
995
|
+
const validVarArr = str.match(rcsVarRegex) || [];
|
|
996
|
+
const templateVarArray = [];
|
|
997
|
+
let content = str;
|
|
998
|
+
while (content?.length !== 0) {
|
|
999
|
+
const index = content.indexOf(validVarArr?.[0]);
|
|
1000
|
+
if (index !== -1) {
|
|
1001
|
+
templateVarArray.push(content.substring(0, index));
|
|
1002
|
+
templateVarArray.push(validVarArr?.[0]);
|
|
1003
|
+
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
1004
|
+
validVarArr?.shift();
|
|
1005
|
+
} else {
|
|
1006
|
+
templateVarArray.push(content);
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return templateVarArray.filter(Boolean);
|
|
1011
|
+
};
|
|
2313
1012
|
|
|
2314
1013
|
const textAreaValue = (idValue, type) => {
|
|
2315
1014
|
if (idValue >= 0) {
|
|
@@ -2325,46 +1024,6 @@ const onTitleAddVar = () => {
|
|
|
2325
1024
|
return "";
|
|
2326
1025
|
};
|
|
2327
1026
|
|
|
2328
|
-
// Carousel: render variable-value editor for a given template string (title/description).
|
|
2329
|
-
// This matches rich-card/text edit behavior: static pieces are read-only, variable tokens are editable.
|
|
2330
|
-
const renderCarouselEditMessage = (templateStr) => {
|
|
2331
|
-
const renderArray = [];
|
|
2332
|
-
const templateArr = splitTemplateVarString(templateStr);
|
|
2333
|
-
if (templateArr?.length) {
|
|
2334
|
-
templateArr.forEach((elem, index) => {
|
|
2335
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
2336
|
-
const varName = getVarNameFromToken(elem);
|
|
2337
|
-
renderArray.push(
|
|
2338
|
-
<div key={`${elem}_${index}`} className="var-segment-message-editor__var-slot">
|
|
2339
|
-
<TextArea
|
|
2340
|
-
id={`${elem}_${index}`}
|
|
2341
|
-
placeholder={`enter the value for ${elem}`}
|
|
2342
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2343
|
-
onChange={(e) => textAreaValueChange(e, TITLE_TEXT)}
|
|
2344
|
-
value={varName ? ((cardVarMapped?.[varName] ?? '').toString()) : ''}
|
|
2345
|
-
onFocus={(e) => {
|
|
2346
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
2347
|
-
setCarouselFocusedVarId(id);
|
|
2348
|
-
}}
|
|
2349
|
-
/>
|
|
2350
|
-
</div>
|
|
2351
|
-
);
|
|
2352
|
-
} else if (elem) {
|
|
2353
|
-
renderArray.push(
|
|
2354
|
-
<CapHeading
|
|
2355
|
-
key={`static_${index}`}
|
|
2356
|
-
type="h4"
|
|
2357
|
-
className="rcs-edit-template-message-split"
|
|
2358
|
-
>
|
|
2359
|
-
{elem}
|
|
2360
|
-
</CapHeading>
|
|
2361
|
-
);
|
|
2362
|
-
}
|
|
2363
|
-
});
|
|
2364
|
-
}
|
|
2365
|
-
return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
|
|
2366
|
-
};
|
|
2367
|
-
|
|
2368
1027
|
const textAreaValueChange = (e, type) => {
|
|
2369
1028
|
const value = e?.target?.value ?? '';
|
|
2370
1029
|
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
@@ -2384,9 +1043,7 @@ const onTitleAddVar = () => {
|
|
|
2384
1043
|
};
|
|
2385
1044
|
|
|
2386
1045
|
const setTextAreaId = (e, type) => {
|
|
2387
|
-
|
|
2388
|
-
// have an `.target.id` shape. Support both.
|
|
2389
|
-
const id = typeof e === 'string' ? e : (e?.target?.id || e?.currentTarget?.id || '');
|
|
1046
|
+
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
2390
1047
|
if (!id) return;
|
|
2391
1048
|
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
2392
1049
|
else setDescTextAreaId(id);
|
|
@@ -2417,71 +1074,44 @@ const onTitleAddVar = () => {
|
|
|
2417
1074
|
isEditFlow={isEditFlow}
|
|
2418
1075
|
isFullMode={isFullMode}
|
|
2419
1076
|
maxButtons={MAX_BUTTONS}
|
|
2420
|
-
|
|
2421
|
-
/>
|
|
1077
|
+
/>
|
|
2422
1078
|
</>
|
|
2423
1079
|
);
|
|
2424
1080
|
};
|
|
2425
1081
|
|
|
2426
|
-
const
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
const handleRcsVarChange = (varSegmentCompositeDomId, value, type) => {
|
|
2461
|
-
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
2462
|
-
if (underscoreIndexInCompositeId === -1) return;
|
|
2463
|
-
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
2464
|
-
const variableName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
2465
|
-
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
2466
|
-
const isInvalidValue = value?.trim() === '';
|
|
2467
|
-
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
2468
|
-
const templateStringForField = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
2469
|
-
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
2470
|
-
varSegmentCompositeDomId,
|
|
2471
|
-
templateStringForField,
|
|
2472
|
-
type,
|
|
2473
|
-
);
|
|
2474
|
-
setCardVarMapped((previousCardVarMapped) => {
|
|
2475
|
-
const updatedCardVarMapped = { ...previousCardVarMapped };
|
|
2476
|
-
// Remove stale semantic key: keeping it causes every other slot sharing the same
|
|
2477
|
-
// variable name (e.g. {{adv}} in both title and description) to read the same value
|
|
2478
|
-
// via the semantic-key fallback in resolveCardVarMappedSlotValue.
|
|
2479
|
-
delete updatedCardVarMapped[variableName];
|
|
2480
|
-
if (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined) {
|
|
2481
|
-
updatedCardVarMapped[String(globalVarSlotIndexZeroBased + 1)] = coercedSlotValue;
|
|
2482
|
-
}
|
|
2483
|
-
return updatedCardVarMapped;
|
|
2484
|
-
});
|
|
1082
|
+
const renderedRCSEditMessage = (descArray, type) => {
|
|
1083
|
+
const renderArray = [];
|
|
1084
|
+
if (descArray?.length) {
|
|
1085
|
+
descArray.forEach((elem, index) => {
|
|
1086
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
1087
|
+
// Variable input
|
|
1088
|
+
renderArray.push(
|
|
1089
|
+
<TextArea
|
|
1090
|
+
id={`${elem}_${index}`}
|
|
1091
|
+
key={`${elem}_${index}`}
|
|
1092
|
+
placeholder={`enter the value for ${elem}`}
|
|
1093
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1094
|
+
onChange={e => textAreaValueChange(e, type)}
|
|
1095
|
+
value={textAreaValue(index, type)}
|
|
1096
|
+
onFocus={(e) => setTextAreaId(e, type)}
|
|
1097
|
+
/>
|
|
1098
|
+
);
|
|
1099
|
+
} else if (elem) {
|
|
1100
|
+
// Static text
|
|
1101
|
+
renderArray.push(
|
|
1102
|
+
<TextArea
|
|
1103
|
+
key={`static_${index}`}
|
|
1104
|
+
value={elem}
|
|
1105
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1106
|
+
disabled
|
|
1107
|
+
className="rcs-edit-template-message-static-textarea"
|
|
1108
|
+
style={{ background: '#fafafa', color: '#888' }}
|
|
1109
|
+
/>
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
return renderArray;
|
|
2485
1115
|
};
|
|
2486
1116
|
|
|
2487
1117
|
const renderTextComponent = () => {
|
|
@@ -2501,7 +1131,6 @@ const onTitleAddVar = () => {
|
|
|
2501
1131
|
}
|
|
2502
1132
|
suffix={
|
|
2503
1133
|
<>
|
|
2504
|
-
|
|
2505
1134
|
{(isEditFlow || !isFullMode) ? (
|
|
2506
1135
|
<TagList
|
|
2507
1136
|
label={formatMessage(globalMessages.addLabels)}
|
|
@@ -2518,28 +1147,18 @@ const onTitleAddVar = () => {
|
|
|
2518
1147
|
type="flat"
|
|
2519
1148
|
isAddBtn
|
|
2520
1149
|
onClick={onTitleAddVar}
|
|
2521
|
-
disabled={templateTitleError}
|
|
1150
|
+
disabled={!!templateTitleError}
|
|
2522
1151
|
>
|
|
2523
1152
|
{formatMessage(messages.addVar)}
|
|
2524
1153
|
</CapButton>
|
|
2525
|
-
|
|
1154
|
+
)}
|
|
2526
1155
|
</>
|
|
2527
|
-
}
|
|
2528
|
-
/>
|
|
2529
|
-
|
|
2530
|
-
{(isEditFlow || !isFullMode) ? (
|
|
2531
|
-
<VarSegmentMessageEditor
|
|
2532
|
-
key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
2533
|
-
templateString={templateTitle}
|
|
2534
|
-
valueMap={titleVarSegmentValueMapById}
|
|
2535
|
-
onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
|
|
2536
|
-
onFocus={(id) => setTitleTextAreaId(id)}
|
|
2537
|
-
varRegex={rcsVarRegex}
|
|
2538
|
-
placeholderPrefix=""
|
|
2539
|
-
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1156
|
+
}
|
|
2540
1157
|
/>
|
|
1158
|
+
<div className="rcs_text_area_wrapper">
|
|
1159
|
+
{(isEditFlow || !isFullMode) ? (
|
|
1160
|
+
renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
|
|
2541
1161
|
) : (
|
|
2542
|
-
<div className="rcs_text_area_wrapper">
|
|
2543
1162
|
<CapInput
|
|
2544
1163
|
className={`rcs-template-title-input ${
|
|
2545
1164
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -2552,8 +1171,8 @@ const onTitleAddVar = () => {
|
|
|
2552
1171
|
errorMessage={templateTitleError}
|
|
2553
1172
|
disabled={isEditFlow || !isFullMode}
|
|
2554
1173
|
/>
|
|
2555
|
-
</div>
|
|
2556
1174
|
)}
|
|
1175
|
+
</div>
|
|
2557
1176
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
2558
1177
|
<CapError className="rcs-template-title-error">
|
|
2559
1178
|
{templateTitleError}
|
|
@@ -2561,7 +1180,7 @@ const onTitleAddVar = () => {
|
|
|
2561
1180
|
)}
|
|
2562
1181
|
{!isEditFlow && isFullMode && renderTitleCharacterCount()}
|
|
2563
1182
|
</>
|
|
2564
|
-
|
|
1183
|
+
)}
|
|
2565
1184
|
|
|
2566
1185
|
{/* Template Message */}
|
|
2567
1186
|
<CapRow id="rcs-template-message-label">
|
|
@@ -2599,21 +1218,9 @@ const onTitleAddVar = () => {
|
|
|
2599
1218
|
/>
|
|
2600
1219
|
</CapRow>
|
|
2601
1220
|
<CapRow className="rcs-create-template-message-input">
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
(
|
|
2606
|
-
<VarSegmentMessageEditor
|
|
2607
|
-
key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
2608
|
-
templateString={templateDesc}
|
|
2609
|
-
valueMap={descriptionVarSegmentValueMapById}
|
|
2610
|
-
onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
|
|
2611
|
-
onFocus={(id) => setDescTextAreaId(id)}
|
|
2612
|
-
varRegex={rcsVarRegex}
|
|
2613
|
-
placeholderPrefix=""
|
|
2614
|
-
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
2615
|
-
/>
|
|
2616
|
-
)
|
|
1221
|
+
<div className="rcs_text_area_wrapper">
|
|
1222
|
+
{(isEditFlow || !isFullMode)
|
|
1223
|
+
? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
|
|
2617
1224
|
: (
|
|
2618
1225
|
<>
|
|
2619
1226
|
<TextArea
|
|
@@ -2651,15 +1258,13 @@ const onTitleAddVar = () => {
|
|
|
2651
1258
|
</>
|
|
2652
1259
|
)
|
|
2653
1260
|
}
|
|
2654
|
-
</
|
|
1261
|
+
</div>
|
|
2655
1262
|
{(isEditFlow || !isFullMode) && templateDescError && (
|
|
2656
1263
|
<CapError className="rcs-template-message-error">
|
|
2657
1264
|
{templateDescError}
|
|
2658
1265
|
</CapError>
|
|
2659
1266
|
)}
|
|
2660
|
-
{
|
|
2661
|
-
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
2662
|
-
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1267
|
+
{!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
|
|
2663
1268
|
{!isFullMode && hasTag() && (
|
|
2664
1269
|
<CapAlert
|
|
2665
1270
|
message={
|
|
@@ -2673,13 +1278,24 @@ const onTitleAddVar = () => {
|
|
|
2673
1278
|
/>
|
|
2674
1279
|
)}
|
|
2675
1280
|
</CapRow>
|
|
2676
|
-
{(
|
|
2677
|
-
renderButtonComponent()}
|
|
1281
|
+
{renderButtonComponent()}
|
|
2678
1282
|
</>
|
|
2679
1283
|
|
|
2680
1284
|
);
|
|
2681
1285
|
};
|
|
2682
1286
|
|
|
1287
|
+
|
|
1288
|
+
const fallbackSmsLength = () => (
|
|
1289
|
+
<CapLabel type="label1" className="fallback-sms-length">
|
|
1290
|
+
{formatMessage(messages.totalCharacters, {
|
|
1291
|
+
smsCount: Math.ceil(
|
|
1292
|
+
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1293
|
+
),
|
|
1294
|
+
number: fallbackMessage?.length,
|
|
1295
|
+
})}
|
|
1296
|
+
</CapLabel>
|
|
1297
|
+
);
|
|
1298
|
+
|
|
2683
1299
|
// Get character count for title (rich card only)
|
|
2684
1300
|
const getTitleCharacterCount = () => {
|
|
2685
1301
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -2694,7 +1310,7 @@ const onTitleAddVar = () => {
|
|
|
2694
1310
|
// Get max length for description based on template type
|
|
2695
1311
|
const getDescriptionMaxLength = () => {
|
|
2696
1312
|
return templateType === contentType.text_message
|
|
2697
|
-
?
|
|
1313
|
+
? RCS_TEXT_MESSAGE_MAX_LENGTH // 160 for text message
|
|
2698
1314
|
: RCS_RICH_CARD_MAX_LENGTH; // 2000 for rich card description
|
|
2699
1315
|
};
|
|
2700
1316
|
|
|
@@ -2715,61 +1331,9 @@ const onTitleAddVar = () => {
|
|
|
2715
1331
|
{formatMessage(messages.templateMessageLength, {
|
|
2716
1332
|
currentLength,
|
|
2717
1333
|
maxLength,
|
|
2718
|
-
})}
|
|
2719
|
-
</CapLabel>
|
|
2720
|
-
);
|
|
2721
|
-
};
|
|
2722
|
-
|
|
2723
|
-
const rcsDltCardDeleteHandler = () => {
|
|
2724
|
-
closeDltContainerHandler();
|
|
2725
|
-
setDltEditData({});
|
|
2726
|
-
// setFallbackMessage('');
|
|
2727
|
-
// setFallbackMessageError(false);
|
|
2728
|
-
};
|
|
2729
|
-
|
|
2730
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
2731
|
-
const {
|
|
2732
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
2733
|
-
'sms-editor': smsEditor = '',
|
|
2734
|
-
} = data.versions.base || {};
|
|
2735
|
-
};
|
|
2736
|
-
|
|
2737
|
-
const getDltContentCardList = (content, channel) => {
|
|
2738
|
-
const extra = [
|
|
2739
|
-
<CapIcon
|
|
2740
|
-
type="edit"
|
|
2741
|
-
style={{ marginRight: '8px' }}
|
|
2742
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
2743
|
-
/>,
|
|
2744
|
-
<CapDropdown
|
|
2745
|
-
overlay={(
|
|
2746
|
-
<CapMenu>
|
|
2747
|
-
<>
|
|
2748
|
-
<CapMenu.Item
|
|
2749
|
-
className="ant-dropdown-menu-item"
|
|
2750
|
-
onClick={() => {}}
|
|
2751
|
-
>
|
|
2752
|
-
{formatMessage(globalMessages.preview)}
|
|
2753
|
-
</CapMenu.Item>
|
|
2754
|
-
<CapMenu.Item
|
|
2755
|
-
className="ant-dropdown-menu-item"
|
|
2756
|
-
onClick={rcsDltCardDeleteHandler}
|
|
2757
|
-
>
|
|
2758
|
-
{formatMessage(globalMessages.delete)}
|
|
2759
|
-
</CapMenu.Item>
|
|
2760
|
-
</>
|
|
2761
|
-
</CapMenu>
|
|
2762
|
-
)}
|
|
2763
|
-
>
|
|
2764
|
-
<CapIcon type="more" />
|
|
2765
|
-
</CapDropdown>,
|
|
2766
|
-
];
|
|
2767
|
-
return {
|
|
2768
|
-
title: channel,
|
|
2769
|
-
content,
|
|
2770
|
-
cardType: channel,
|
|
2771
|
-
extra,
|
|
2772
|
-
};
|
|
1334
|
+
})}
|
|
1335
|
+
</CapLabel>
|
|
1336
|
+
);
|
|
2773
1337
|
};
|
|
2774
1338
|
|
|
2775
1339
|
// Render character count for description/message
|
|
@@ -2787,26 +1351,6 @@ const onTitleAddVar = () => {
|
|
|
2787
1351
|
);
|
|
2788
1352
|
};
|
|
2789
1353
|
|
|
2790
|
-
// Carousel: per-card character counts (same limits as rich card)
|
|
2791
|
-
const getCarouselTitleCharacterCount = (cardIndex) => {
|
|
2792
|
-
const t = carouselData?.[cardIndex]?.title || '';
|
|
2793
|
-
return t ? t.length : 0;
|
|
2794
|
-
};
|
|
2795
|
-
|
|
2796
|
-
const getCarouselDescriptionCharacterCount = (cardIndex) => {
|
|
2797
|
-
const d = carouselData?.[cardIndex]?.description || '';
|
|
2798
|
-
return d ? d.length : 0;
|
|
2799
|
-
};
|
|
2800
|
-
|
|
2801
|
-
const renderCarouselCharacterCount = (currentLength, maxLength, className = "rcs-character-count") => (
|
|
2802
|
-
<CapLabel type="label1" className={className}>
|
|
2803
|
-
{formatMessage(messages.templateMessageLength, {
|
|
2804
|
-
currentLength,
|
|
2805
|
-
maxLength,
|
|
2806
|
-
})}
|
|
2807
|
-
</CapLabel>
|
|
2808
|
-
);
|
|
2809
|
-
|
|
2810
1354
|
// Check if any RCS variables contain tags (similar to Zalo hasTag logic)
|
|
2811
1355
|
const hasTag = () => {
|
|
2812
1356
|
// Check cardVarMapped values for tags
|
|
@@ -2859,17 +1403,68 @@ const onTitleAddVar = () => {
|
|
|
2859
1403
|
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
2860
1404
|
'',
|
|
2861
1405
|
);
|
|
2862
|
-
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
2863
|
-
|| get(tempData, 'versions.base.name', '')
|
|
2864
|
-
|| '';
|
|
2865
1406
|
closeDltContainerHandler();
|
|
2866
1407
|
setDltEditData(tempData);
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
1408
|
+
setFallbackMessage(fallMsg);
|
|
1409
|
+
setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
|
|
1410
|
+
setShowDltCard(true);
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
const rcsDltCardDeleteHandler = () => {
|
|
1414
|
+
closeDltContainerHandler();
|
|
1415
|
+
setDltEditData({});
|
|
1416
|
+
setFallbackMessage('');
|
|
1417
|
+
setFallbackMessageError(false);
|
|
1418
|
+
setShowDltCard(false);
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
const dltFallbackListingPreviewhandler = (data) => {
|
|
1422
|
+
const {
|
|
1423
|
+
'updated-sms-editor': updatedSmsEditor = [],
|
|
1424
|
+
'sms-editor': smsEditor = '',
|
|
1425
|
+
} = data.versions.base || {};
|
|
1426
|
+
setFallbackPreviewmode(true);
|
|
1427
|
+
setDltPreviewData(
|
|
1428
|
+
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1429
|
+
);
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
const getDltContentCardList = (content, channel) => {
|
|
1433
|
+
const extra = [
|
|
1434
|
+
<CapIcon
|
|
1435
|
+
type="edit"
|
|
1436
|
+
style={{ marginRight: '8px' }}
|
|
1437
|
+
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1438
|
+
/>,
|
|
1439
|
+
<CapDropdown
|
|
1440
|
+
overlay={(
|
|
1441
|
+
<CapMenu>
|
|
1442
|
+
<>
|
|
1443
|
+
<CapMenu.Item
|
|
1444
|
+
className="ant-dropdown-menu-item"
|
|
1445
|
+
onClick={() => setFallbackPreviewmode(true)}
|
|
1446
|
+
>
|
|
1447
|
+
{formatMessage(globalMessages.preview)}
|
|
1448
|
+
</CapMenu.Item>
|
|
1449
|
+
<CapMenu.Item
|
|
1450
|
+
className="ant-dropdown-menu-item"
|
|
1451
|
+
onClick={rcsDltCardDeleteHandler}
|
|
1452
|
+
>
|
|
1453
|
+
{formatMessage(globalMessages.delete)}
|
|
1454
|
+
</CapMenu.Item>
|
|
1455
|
+
</>
|
|
1456
|
+
</CapMenu>
|
|
1457
|
+
)}
|
|
1458
|
+
>
|
|
1459
|
+
<CapIcon type="more" />
|
|
1460
|
+
</CapDropdown>,
|
|
1461
|
+
];
|
|
1462
|
+
return {
|
|
1463
|
+
title: channel,
|
|
1464
|
+
content,
|
|
1465
|
+
cardType: channel,
|
|
1466
|
+
extra,
|
|
1467
|
+
};
|
|
2873
1468
|
};
|
|
2874
1469
|
|
|
2875
1470
|
const getDltSlideBoxContent = () => {
|
|
@@ -2917,34 +1512,148 @@ const onTitleAddVar = () => {
|
|
|
2917
1512
|
return { dltHeader, dltContent };
|
|
2918
1513
|
};
|
|
2919
1514
|
|
|
2920
|
-
const renderFallBackSmsComponent = () =>
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
1515
|
+
const renderFallBackSmsComponent = () => {
|
|
1516
|
+
// Completely disable fallback functionality when DLT is disabled
|
|
1517
|
+
return null;
|
|
1518
|
+
|
|
1519
|
+
// const contentArr = [];
|
|
1520
|
+
// const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
|
|
1521
|
+
// const showCardForDlt = isDltEnabled && showDltCard;
|
|
1522
|
+
// const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
|
|
1523
|
+
// //pushing common fallback sms headings
|
|
1524
|
+
// contentArr.push(
|
|
1525
|
+
// <CapRow
|
|
1526
|
+
// style={{
|
|
1527
|
+
// marginBottom: isDltEnabled ? '20px' : '10px',
|
|
1528
|
+
// }}
|
|
1529
|
+
// >
|
|
1530
|
+
// <CapHeader
|
|
1531
|
+
// title={(
|
|
1532
|
+
// <CapRow type="flex">
|
|
1533
|
+
// <CapHeading type="h4">
|
|
1534
|
+
// {formatMessage(messages.fallbackLabel)}
|
|
1535
|
+
// </CapHeading>
|
|
1536
|
+
// <CapTooltipWithInfo
|
|
1537
|
+
// placement="right"
|
|
1538
|
+
// infoIconProps={{
|
|
1539
|
+
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1540
|
+
// }}
|
|
1541
|
+
// title={formatMessage(messages.fallbackToolTip)}
|
|
1542
|
+
// />
|
|
1543
|
+
// </CapRow>
|
|
1544
|
+
// )}
|
|
1545
|
+
// description={formatMessage(messages.fallbackDesc)}
|
|
1546
|
+
// suffix={
|
|
1547
|
+
// isDltEnabled ? null : (
|
|
1548
|
+
// <CapButton
|
|
1549
|
+
// type="flat"
|
|
1550
|
+
// className="fallback-preview-btn"
|
|
1551
|
+
// prefix={<CapIcon type="eye" />}
|
|
1552
|
+
// style={{ color: CAP_SECONDARY.base }}
|
|
1553
|
+
// onClick={() => setFallbackPreviewmode(true)}
|
|
1554
|
+
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1555
|
+
// >
|
|
1556
|
+
// {formatMessage(globalMessages.preview)}
|
|
1557
|
+
// </CapButton>
|
|
1558
|
+
// )
|
|
1559
|
+
// }
|
|
1560
|
+
// />
|
|
1561
|
+
// </CapRow>,
|
|
1562
|
+
// );
|
|
1563
|
+
|
|
1564
|
+
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1565
|
+
// showAddCreativeBtnForDlt
|
|
1566
|
+
// && contentArr.push(
|
|
1567
|
+
// <CapCard className="rcs-dlt-fallback-card">
|
|
1568
|
+
// <CapRow type="flex" justify="center" align="middle">
|
|
1569
|
+
// <CapColumn span={10}>
|
|
1570
|
+
// <CapImage src={addCreativesIcon} />
|
|
1571
|
+
// </CapColumn>
|
|
1572
|
+
// <CapColumn span={14}>
|
|
1573
|
+
// <CapButton
|
|
1574
|
+
// className="add-dlt-btn"
|
|
1575
|
+
// type="secondary"
|
|
1576
|
+
// onClick={addDltMsgHandler}
|
|
1577
|
+
// >
|
|
1578
|
+
// {formatMessage(messages.addSmsCreative)}
|
|
1579
|
+
// </CapButton>
|
|
1580
|
+
// </CapColumn>
|
|
1581
|
+
// </CapRow>
|
|
1582
|
+
// </CapCard>,
|
|
1583
|
+
// );
|
|
1584
|
+
|
|
1585
|
+
// //dlt is enabled and dlt content is added, show it in a card
|
|
1586
|
+
// showCardForDlt
|
|
1587
|
+
// && contentArr.push(
|
|
1588
|
+
// <CapCustomCardList
|
|
1589
|
+
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1590
|
+
// className="rcs-dlt-card"
|
|
1591
|
+
// />,
|
|
1592
|
+
// fallbackMessageError && (
|
|
1593
|
+
// <CapError className="rcs-fallback-len-error">
|
|
1594
|
+
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1595
|
+
// </CapError>
|
|
1596
|
+
// ),
|
|
1597
|
+
// );
|
|
1598
|
+
|
|
1599
|
+
// //dlt is not enabled, show non dlt text area
|
|
1600
|
+
// showNonDltFallbackComp
|
|
1601
|
+
// && contentArr.push(
|
|
1602
|
+
// <>
|
|
1603
|
+
// <CapRow>
|
|
1604
|
+
// <CapHeader
|
|
1605
|
+
// title={(
|
|
1606
|
+
// <CapHeading type="h4">
|
|
1607
|
+
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1608
|
+
// </CapHeading>
|
|
1609
|
+
// )}
|
|
1610
|
+
// suffix={(
|
|
1611
|
+
// <TagList
|
|
1612
|
+
// label={formatMessage(globalMessages.addLabels)}
|
|
1613
|
+
// onTagSelect={onTagSelectFallback}
|
|
1614
|
+
// location={location}
|
|
1615
|
+
// tags={tags || []}
|
|
1616
|
+
// onContextChange={handleOnTagsContextChange}
|
|
1617
|
+
// injectedTags={injectedTags || {}}
|
|
1618
|
+
// selectedOfferDetails={selectedOfferDetails}
|
|
1619
|
+
// />
|
|
1620
|
+
// )}
|
|
1621
|
+
// />
|
|
1622
|
+
// </CapRow>
|
|
1623
|
+
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1624
|
+
// <TextArea
|
|
1625
|
+
// id="rcs_fallback_message_textarea"
|
|
1626
|
+
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1627
|
+
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1628
|
+
// onChange={onFallbackMessageChange}
|
|
1629
|
+
// errorMessage={fallbackMessageError}
|
|
1630
|
+
// value={fallbackMessage || ""}
|
|
1631
|
+
// />
|
|
1632
|
+
// {!aiContentBotDisabled && (
|
|
1633
|
+
// <CapAskAira.ContentGenerationBot
|
|
1634
|
+
// text={fallbackMessage || ""}
|
|
1635
|
+
// setText={(text) => {
|
|
1636
|
+
// onFallbackMessageChange({ target: { value: text } });
|
|
1637
|
+
// }}
|
|
1638
|
+
// iconPlacement="float-br"
|
|
1639
|
+
// rootStyle={{
|
|
1640
|
+
// bottom: "0.5rem",
|
|
1641
|
+
// right: "0.5rem",
|
|
1642
|
+
// position: "absolute",
|
|
1643
|
+
// }}
|
|
1644
|
+
// />
|
|
1645
|
+
// )}
|
|
1646
|
+
// </div>
|
|
1647
|
+
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1648
|
+
// </>
|
|
1649
|
+
// );
|
|
1650
|
+
|
|
1651
|
+
// return <>{contentArr}</>;
|
|
1652
|
+
};
|
|
2944
1653
|
|
|
2945
1654
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
2946
1655
|
setImageError(null);
|
|
2947
|
-
const isRcsThumbnail =
|
|
1656
|
+
const isRcsThumbnail = index === 1;
|
|
2948
1657
|
actions.uploadRcsAsset(file, type, {
|
|
2949
1658
|
isRcsThumbnail,
|
|
2950
1659
|
...fileParams,
|
|
@@ -2982,7 +1691,10 @@ const onTitleAddVar = () => {
|
|
|
2982
1691
|
const updateOnRcsImageReUpload = useCallback(() => {
|
|
2983
1692
|
setUpdateRcsImageSrc('');
|
|
2984
1693
|
}, []);
|
|
1694
|
+
|
|
1695
|
+
|
|
2985
1696
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1697
|
+
setImageError(null);
|
|
2986
1698
|
actions.uploadRcsAsset(file, type, {
|
|
2987
1699
|
...fileParams,
|
|
2988
1700
|
type: 'video',
|
|
@@ -2991,6 +1703,9 @@ const onTitleAddVar = () => {
|
|
|
2991
1703
|
});
|
|
2992
1704
|
};
|
|
2993
1705
|
|
|
1706
|
+
const updateRcsVideoSrc = (val) => {
|
|
1707
|
+
setRcsVideoSrc(val);
|
|
1708
|
+
};
|
|
2994
1709
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
2995
1710
|
setRcsVideoSrc(val);
|
|
2996
1711
|
setAssetList(val);
|
|
@@ -3013,7 +1728,7 @@ const onTitleAddVar = () => {
|
|
|
3013
1728
|
updateRcsThumbnailSrc('');
|
|
3014
1729
|
};
|
|
3015
1730
|
|
|
3016
|
-
|
|
1731
|
+
const renderThumbnailComponent = () => {
|
|
3017
1732
|
const currentDimension = selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
3018
1733
|
return !isEditFlow && (
|
|
3019
1734
|
<>
|
|
@@ -3037,7 +1752,6 @@ const onTitleAddVar = () => {
|
|
|
3037
1752
|
channel={RCS}
|
|
3038
1753
|
channelSpecificStyle={!isFullMode}
|
|
3039
1754
|
skipDimensionValidation={true}
|
|
3040
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
3041
1755
|
/>
|
|
3042
1756
|
</>
|
|
3043
1757
|
)
|
|
@@ -3063,7 +1777,7 @@ const onTitleAddVar = () => {
|
|
|
3063
1777
|
value: dim.type,
|
|
3064
1778
|
label: `${dim.label}`
|
|
3065
1779
|
}))}
|
|
3066
|
-
|
|
1780
|
+
style={{ marginBottom: '20px' }}
|
|
3067
1781
|
/>
|
|
3068
1782
|
</>
|
|
3069
1783
|
)}
|
|
@@ -3077,7 +1791,7 @@ const onTitleAddVar = () => {
|
|
|
3077
1791
|
</div>
|
|
3078
1792
|
) : (
|
|
3079
1793
|
<CapImageUpload
|
|
3080
|
-
|
|
1794
|
+
style={{ paddingTop: '20px' }}
|
|
3081
1795
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
3082
1796
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
3083
1797
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -3088,7 +1802,7 @@ const onTitleAddVar = () => {
|
|
|
3088
1802
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
3089
1803
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
3090
1804
|
index={0}
|
|
3091
|
-
className="cap-custom-image-upload
|
|
1805
|
+
className="cap-custom-image-upload"
|
|
3092
1806
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
3093
1807
|
imageData={rcsData}
|
|
3094
1808
|
channel={RCS}
|
|
@@ -3099,7 +1813,7 @@ const onTitleAddVar = () => {
|
|
|
3099
1813
|
|
|
3100
1814
|
</>
|
|
3101
1815
|
);
|
|
3102
|
-
|
|
1816
|
+
}
|
|
3103
1817
|
|
|
3104
1818
|
const renderVideoComponent = () => {
|
|
3105
1819
|
const currentDimension =selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
@@ -3118,7 +1832,7 @@ const onTitleAddVar = () => {
|
|
|
3118
1832
|
value: dim.type,
|
|
3119
1833
|
label: `${dim.label}`
|
|
3120
1834
|
}))}
|
|
3121
|
-
|
|
1835
|
+
style={{ marginBottom: '20px' }}
|
|
3122
1836
|
/>
|
|
3123
1837
|
)}
|
|
3124
1838
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -3176,48 +1890,10 @@ const onTitleAddVar = () => {
|
|
|
3176
1890
|
};
|
|
3177
1891
|
|
|
3178
1892
|
const getRcsPreview = () => {
|
|
3179
|
-
|
|
3180
|
-
if (templateType === contentType.carousel) {
|
|
3181
|
-
const cardsForPreview = buildCarouselCardsForPreview(carouselData);
|
|
3182
|
-
const carouselDimKey = getCarouselDimensionKey();
|
|
3183
|
-
const carouselImgDims =
|
|
3184
|
-
RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
|
|
3185
|
-
const carouselVidDims =
|
|
3186
|
-
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
|
|
3187
|
-
|| RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
|
|
3188
|
-
// Debug log for embedded/library mode preview payload (carousel)
|
|
3189
|
-
// eslint-disable-next-line no-console
|
|
3190
|
-
return (
|
|
3191
|
-
<UnifiedPreview
|
|
3192
|
-
channel={RCS}
|
|
3193
|
-
content={{
|
|
3194
|
-
carouselData: cardsForPreview,
|
|
3195
|
-
carouselPreviewDimensions: {
|
|
3196
|
-
imageWidth: carouselImgDims.width,
|
|
3197
|
-
imageHeight: carouselImgDims.height,
|
|
3198
|
-
videoThumbWidth: carouselVidDims.width,
|
|
3199
|
-
videoThumbHeight: carouselVidDims.height,
|
|
3200
|
-
},
|
|
3201
|
-
}}
|
|
3202
|
-
device={ANDROID}
|
|
3203
|
-
showDeviceToggle={false}
|
|
3204
|
-
showHeader={false}
|
|
3205
|
-
formatMessage={formatMessage}
|
|
3206
|
-
/>
|
|
3207
|
-
);
|
|
3208
|
-
}
|
|
3209
1893
|
|
|
3210
1894
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
3211
|
-
const
|
|
3212
|
-
const
|
|
3213
|
-
? 0
|
|
3214
|
-
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
3215
|
-
const resolvedTitle = isMediaTypeText
|
|
3216
|
-
? ''
|
|
3217
|
-
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
3218
|
-
const resolvedDesc = isSlotMappingMode
|
|
3219
|
-
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
3220
|
-
: templateDesc;
|
|
1895
|
+
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1896
|
+
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
3221
1897
|
return (
|
|
3222
1898
|
<UnifiedPreview
|
|
3223
1899
|
channel={RCS}
|
|
@@ -3240,95 +1916,51 @@ const onTitleAddVar = () => {
|
|
|
3240
1916
|
);
|
|
3241
1917
|
};
|
|
3242
1918
|
|
|
1919
|
+
const getUnmappedDesc = (str, mapping) => {
|
|
1920
|
+
if (!str) return '';
|
|
1921
|
+
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1922
|
+
let result = str;
|
|
1923
|
+
const replacements = [];
|
|
1924
|
+
Object.entries(mapping).forEach(([key, value]) => {
|
|
1925
|
+
const raw = (value ?? '').toString();
|
|
1926
|
+
if (!raw || raw?.trim?.() === '') return;
|
|
1927
|
+
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1928
|
+
replacements.push({ key, needle: raw });
|
|
1929
|
+
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1930
|
+
});
|
|
1931
|
+
const seen = new Set();
|
|
1932
|
+
const uniq = replacements
|
|
1933
|
+
.filter(({ key, needle }) => {
|
|
1934
|
+
const id = `${key}::${needle}`;
|
|
1935
|
+
if (seen.has(id)) return false;
|
|
1936
|
+
seen.add(id);
|
|
1937
|
+
return true;
|
|
1938
|
+
})
|
|
1939
|
+
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1940
|
+
|
|
1941
|
+
uniq.forEach(({ key, needle }) => {
|
|
1942
|
+
if (!needle) return;
|
|
1943
|
+
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1944
|
+
const regex = new RegExp(escaped, 'g');
|
|
1945
|
+
result = result.replace(regex, `{{${key}}}`);
|
|
1946
|
+
});
|
|
1947
|
+
return result;
|
|
1948
|
+
};
|
|
1949
|
+
|
|
3243
1950
|
const createPayload = () => {
|
|
3244
|
-
const
|
|
1951
|
+
const base = get(dltEditData, `versions.base`, {});
|
|
1952
|
+
const {
|
|
1953
|
+
template_id: templateId = '',
|
|
1954
|
+
template_name = '',
|
|
1955
|
+
'sms-editor': template = '',
|
|
1956
|
+
header: registeredSenderIds = [],
|
|
1957
|
+
} = base;
|
|
1958
|
+
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1959
|
+
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
3245
1960
|
const alignment = isMediaTypeImage
|
|
3246
1961
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
3247
1962
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
3248
1963
|
|
|
3249
|
-
const heightTypeForCardWidth = isMediaTypeText
|
|
3250
|
-
? undefined
|
|
3251
|
-
: isMediaTypeImage
|
|
3252
|
-
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
|
|
3253
|
-
: isMediaTypeVideo
|
|
3254
|
-
? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
|
|
3255
|
-
: undefined;
|
|
3256
|
-
const cardWidthFromSelection =
|
|
3257
|
-
heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
|
|
3258
|
-
|
|
3259
|
-
/** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
|
|
3260
|
-
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
3261
|
-
const smsFallbackMerged = !isFullMode
|
|
3262
|
-
? (() => {
|
|
3263
|
-
const local =
|
|
3264
|
-
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
3265
|
-
return {
|
|
3266
|
-
...smsFromApiShape,
|
|
3267
|
-
...local,
|
|
3268
|
-
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
3269
|
-
};
|
|
3270
|
-
})()
|
|
3271
|
-
: (smsFallbackData || {});
|
|
3272
|
-
const smsFallbackForPayload = (() => {
|
|
3273
|
-
if (isFullMode) {
|
|
3274
|
-
return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
|
|
3275
|
-
}
|
|
3276
|
-
const mapped = {
|
|
3277
|
-
templateName:
|
|
3278
|
-
smsFallbackMerged.templateName
|
|
3279
|
-
|| smsFallbackMerged.smsTemplateName
|
|
3280
|
-
|| '',
|
|
3281
|
-
// Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
|
|
3282
|
-
content:
|
|
3283
|
-
smsFallbackMerged.content
|
|
3284
|
-
|| smsFallbackMerged.smsContent
|
|
3285
|
-
|| smsFallbackMerged.smsTemplateContent
|
|
3286
|
-
|| smsFallbackMerged.message
|
|
3287
|
-
|| '',
|
|
3288
|
-
templateContent:
|
|
3289
|
-
pickFirstSmsFallbackTemplateString(smsFallbackMerged)
|
|
3290
|
-
|| '',
|
|
3291
|
-
...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
|
|
3292
|
-
&& { unicodeValidity: smsFallbackMerged.unicodeValidity }),
|
|
3293
|
-
...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
3294
|
-
&& Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
3295
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
3296
|
-
smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
3297
|
-
}),
|
|
3298
|
-
};
|
|
3299
|
-
return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
|
|
3300
|
-
})();
|
|
3301
|
-
|
|
3302
|
-
const carouselCardContent = isCarouselType
|
|
3303
|
-
? (carouselData || []).map((card = {}) => {
|
|
3304
|
-
const cardMediaType = card.mediaType;
|
|
3305
|
-
const isCardVideo = cardMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
3306
|
-
const isCardImage = cardMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
3307
|
-
const mediaUrl = isCardVideo
|
|
3308
|
-
? (card.videoAsset?.videoSrc || '')
|
|
3309
|
-
: (card.imageSrc || '');
|
|
3310
|
-
const thumbnailUrl = isCardVideo
|
|
3311
|
-
? (card.thumbnailSrc || card.videoAsset?.videoThumbnail || '')
|
|
3312
|
-
: '';
|
|
3313
|
-
return {
|
|
3314
|
-
title: card.title || '',
|
|
3315
|
-
description: card.description || '',
|
|
3316
|
-
mediaType: cardMediaType,
|
|
3317
|
-
...((isCardImage || isCardVideo) && {
|
|
3318
|
-
media: {
|
|
3319
|
-
mediaUrl,
|
|
3320
|
-
thumbnailUrl,
|
|
3321
|
-
height: selectedCarouselHeight || MEDIUM,
|
|
3322
|
-
...(isCardVideo && card.videoAsset?.videoName && { videoName: card.videoAsset.videoName }),
|
|
3323
|
-
},
|
|
3324
|
-
}),
|
|
3325
|
-
...(Array.isArray(card.suggestions) && card.suggestions.length > 0 && {
|
|
3326
|
-
suggestions: card.suggestions,
|
|
3327
|
-
}),
|
|
3328
|
-
};
|
|
3329
|
-
})
|
|
3330
|
-
: null;
|
|
3331
|
-
|
|
3332
1964
|
const payload = {
|
|
3333
1965
|
name: templateName,
|
|
3334
1966
|
versions: {
|
|
@@ -3337,22 +1969,16 @@ const onTitleAddVar = () => {
|
|
|
3337
1969
|
RCS: {
|
|
3338
1970
|
rcsContent: {
|
|
3339
1971
|
...(rcsAccount && !isFullMode && { accountId: rcsAccount }),
|
|
3340
|
-
cardType:
|
|
3341
|
-
cardSettings:
|
|
3342
|
-
?
|
|
3343
|
-
:
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
},
|
|
3348
|
-
cardContent: isCarouselType
|
|
3349
|
-
? carouselCardContent
|
|
3350
|
-
: [
|
|
1972
|
+
cardType: STANDALONE,
|
|
1973
|
+
cardSettings: {
|
|
1974
|
+
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
1975
|
+
...(alignment && { mediaAlignment: alignment }),
|
|
1976
|
+
cardWidth: SMALL,
|
|
1977
|
+
},
|
|
1978
|
+
cardContent: [
|
|
3351
1979
|
{
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
title: templateTitle,
|
|
3355
|
-
description: templateDesc,
|
|
1980
|
+
title: resolvedTitle,
|
|
1981
|
+
description: resolvedDesc,
|
|
3356
1982
|
mediaType: templateMediaType,
|
|
3357
1983
|
...(!isMediaTypeText && {media: {
|
|
3358
1984
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -3361,32 +1987,23 @@ const onTitleAddVar = () => {
|
|
|
3361
1987
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
3362
1988
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
3363
1989
|
}}),
|
|
3364
|
-
...(
|
|
3365
|
-
const
|
|
3366
|
-
...(templateTitle
|
|
3367
|
-
...(templateDesc
|
|
1990
|
+
...(!isFullMode && (() => {
|
|
1991
|
+
const tokens = [
|
|
1992
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
1993
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
3368
1994
|
];
|
|
3369
|
-
const
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
3380
|
-
cardVarMappedForRcsCardOnly,
|
|
3381
|
-
varName,
|
|
3382
|
-
slotIndexZeroBased,
|
|
3383
|
-
isSlotMappingMode,
|
|
3384
|
-
rcsSpanningSemanticVarNames.has(varName),
|
|
3385
|
-
);
|
|
3386
|
-
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
3387
|
-
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
1995
|
+
const allowedKeys = tokens
|
|
1996
|
+
.map((t) => getVarNameFromToken(t))
|
|
1997
|
+
.filter(Boolean);
|
|
1998
|
+
const nextMap = {};
|
|
1999
|
+
allowedKeys.forEach((k) => {
|
|
2000
|
+
if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
|
|
2001
|
+
nextMap[k] = cardVarMapped[k];
|
|
2002
|
+
} else {
|
|
2003
|
+
nextMap[k] = '';
|
|
2004
|
+
}
|
|
3388
2005
|
});
|
|
3389
|
-
return { cardVarMapped:
|
|
2006
|
+
return { cardVarMapped: nextMap };
|
|
3390
2007
|
})()),
|
|
3391
2008
|
...(suggestions.length > 0 && { suggestions }),
|
|
3392
2009
|
}
|
|
@@ -3394,109 +2011,17 @@ const onTitleAddVar = () => {
|
|
|
3394
2011
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
3395
2012
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
3396
2013
|
},
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
const m = smsFallbackMerged || {};
|
|
3409
|
-
const tcSibling = m.templateConfigs && typeof m.templateConfigs === 'object'
|
|
3410
|
-
? m.templateConfigs
|
|
3411
|
-
: {};
|
|
3412
|
-
const smsFallbackTemplateId =
|
|
3413
|
-
(m.smsTemplateId != null && String(m.smsTemplateId).trim() !== ''
|
|
3414
|
-
? String(m.smsTemplateId)
|
|
3415
|
-
: '')
|
|
3416
|
-
|| (tcSibling.templateId != null && String(tcSibling.templateId).trim() !== ''
|
|
3417
|
-
? String(tcSibling.templateId)
|
|
3418
|
-
: '');
|
|
3419
|
-
const smsFallbackTemplateStr =
|
|
3420
|
-
pickFirstSmsFallbackTemplateString(m)
|
|
3421
|
-
|| (typeof m.templateContent === 'string' ? m.templateContent : '')
|
|
3422
|
-
|| (typeof tcSibling.template === 'string' ? tcSibling.template : '')
|
|
3423
|
-
|| '';
|
|
3424
|
-
const smsFallbackTemplateName =
|
|
3425
|
-
m.templateName
|
|
3426
|
-
|| m.smsTemplateName
|
|
3427
|
-
|| tcSibling.templateName
|
|
3428
|
-
|| tcSibling.name
|
|
3429
|
-
|| '';
|
|
3430
|
-
const registeredSenderIdsForPayload = Array.isArray(m.registeredSenderIds)
|
|
3431
|
-
? m.registeredSenderIds
|
|
3432
|
-
: Array.isArray(tcSibling.registeredSenderIds)
|
|
3433
|
-
? tcSibling.registeredSenderIds
|
|
3434
|
-
: Array.isArray(tcSibling.header)
|
|
3435
|
-
? tcSibling.header
|
|
3436
|
-
: null;
|
|
3437
|
-
const hasRegisteredSenderIds = Array.isArray(registeredSenderIdsForPayload);
|
|
3438
|
-
const smsFallbackTemplateConfigs =
|
|
3439
|
-
smsFallbackTemplateId || hasRegisteredSenderIds
|
|
3440
|
-
? {
|
|
3441
|
-
...(smsFallbackTemplateId && { templateId: smsFallbackTemplateId }),
|
|
3442
|
-
...(smsFallbackTemplateStr && { template: smsFallbackTemplateStr }),
|
|
3443
|
-
...(smsFallbackTemplateName && {
|
|
3444
|
-
templateName: smsFallbackTemplateName,
|
|
3445
|
-
}),
|
|
3446
|
-
...(hasRegisteredSenderIds && {
|
|
3447
|
-
registeredSenderIds: registeredSenderIdsForPayload,
|
|
3448
|
-
}),
|
|
3449
|
-
}
|
|
3450
|
-
: null;
|
|
3451
|
-
const isDltCampaign = !isFullMode && isTraiDLTEnable(isFullMode, smsRegister);
|
|
3452
|
-
return {
|
|
3453
|
-
smsFallBackContent: isFullMode
|
|
3454
|
-
? {
|
|
3455
|
-
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
3456
|
-
smsContent: smsBodyText,
|
|
3457
|
-
// cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
|
|
3458
|
-
message: smsBodyText,
|
|
3459
|
-
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
3460
|
-
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
3461
|
-
}),
|
|
3462
|
-
...(smsFallbackForPayload.rcsSmsFallbackVarMapped
|
|
3463
|
-
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
3464
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
3465
|
-
}),
|
|
3466
|
-
...(smsFallbackTemplateConfigs && {
|
|
3467
|
-
templateConfigs: smsFallbackTemplateConfigs,
|
|
3468
|
-
}),
|
|
3469
|
-
}
|
|
3470
|
-
: {
|
|
3471
|
-
// Round-trip storage: full shape so reopening the editor restores template
|
|
3472
|
-
// name, sender IDs, and var mappings. normalizeRcsMessageContentForApi
|
|
3473
|
-
// (called in CreativesContainer getCreativesData) strips this to
|
|
3474
|
-
// { message, templateConfigs } before the API call — the extra fields here
|
|
3475
|
-
// are only used for re-hydration, never sent to the API.
|
|
3476
|
-
message: smsBodyText,
|
|
3477
|
-
...(smsFallbackForPayload.templateName && {
|
|
3478
|
-
smsTemplateName: smsFallbackForPayload.templateName,
|
|
3479
|
-
}),
|
|
3480
|
-
...(smsFallbackForPayload.templateContent && {
|
|
3481
|
-
templateContent: smsFallbackForPayload.templateContent,
|
|
3482
|
-
}),
|
|
3483
|
-
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
3484
|
-
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
3485
|
-
}),
|
|
3486
|
-
...(Array.isArray(registeredSenderIdsForPayload) && {
|
|
3487
|
-
registeredSenderIds: registeredSenderIdsForPayload,
|
|
3488
|
-
}),
|
|
3489
|
-
...(smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
3490
|
-
&& Object.keys(smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
3491
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
3492
|
-
smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
3493
|
-
}),
|
|
3494
|
-
...(isDltCampaign && smsFallbackTemplateConfigs && {
|
|
3495
|
-
templateConfigs: smsFallbackTemplateConfigs,
|
|
3496
|
-
}),
|
|
3497
|
-
},
|
|
3498
|
-
};
|
|
3499
|
-
})()),
|
|
2014
|
+
smsFallBackContent: {
|
|
2015
|
+
message: fallbackMessage,
|
|
2016
|
+
...(isDltEnabled && {
|
|
2017
|
+
templateConfigs: {
|
|
2018
|
+
templateId,
|
|
2019
|
+
templateName: template_name,
|
|
2020
|
+
template,
|
|
2021
|
+
registeredSenderIds,
|
|
2022
|
+
},
|
|
2023
|
+
}),
|
|
2024
|
+
},
|
|
3500
2025
|
},
|
|
3501
2026
|
},
|
|
3502
2027
|
},
|
|
@@ -3506,109 +2031,6 @@ const onTitleAddVar = () => {
|
|
|
3506
2031
|
return payload;
|
|
3507
2032
|
};
|
|
3508
2033
|
|
|
3509
|
-
/** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
|
|
3510
|
-
const testPreviewFormData = useMemo(() => {
|
|
3511
|
-
const payload = createPayload();
|
|
3512
|
-
const rcs = payload?.versions?.base?.content?.RCS;
|
|
3513
|
-
if (!rcs) return null;
|
|
3514
|
-
// createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
|
|
3515
|
-
const accountIdForCreateMessageMeta =
|
|
3516
|
-
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
3517
|
-
? String(wecrmAccountId)
|
|
3518
|
-
: accountId;
|
|
3519
|
-
const isSlotMappingModeForPreview = isEditFlow || !isFullMode;
|
|
3520
|
-
let rcsForTest = {
|
|
3521
|
-
...rcs,
|
|
3522
|
-
rcsContent: {
|
|
3523
|
-
...rcs.rcsContent,
|
|
3524
|
-
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
3525
|
-
},
|
|
3526
|
-
};
|
|
3527
|
-
/** Approval payload keeps numeric-only `cardVarMapped`; preview APIs still need semantic keys. */
|
|
3528
|
-
if (isSlotMappingModeForPreview) {
|
|
3529
|
-
const cardContent = rcsForTest.rcsContent?.cardContent;
|
|
3530
|
-
if (Array.isArray(cardContent) && cardContent[0]) {
|
|
3531
|
-
const fullCardVarMapped = coalesceCardVarMappedToTemplate(
|
|
3532
|
-
pickRcsCardVarMappedEntries(cardVarMapped),
|
|
3533
|
-
templateTitle,
|
|
3534
|
-
templateDesc,
|
|
3535
|
-
rcsVarRegex,
|
|
3536
|
-
);
|
|
3537
|
-
rcsForTest = {
|
|
3538
|
-
...rcsForTest,
|
|
3539
|
-
rcsContent: {
|
|
3540
|
-
...rcsForTest.rcsContent,
|
|
3541
|
-
cardContent: [
|
|
3542
|
-
{ ...cardContent[0], cardVarMapped: fullCardVarMapped },
|
|
3543
|
-
...cardContent.slice(1),
|
|
3544
|
-
],
|
|
3545
|
-
},
|
|
3546
|
-
};
|
|
3547
|
-
}
|
|
3548
|
-
}
|
|
3549
|
-
const out = {
|
|
3550
|
-
versions: {
|
|
3551
|
-
base: {
|
|
3552
|
-
content: {
|
|
3553
|
-
RCS: rcsForTest,
|
|
3554
|
-
},
|
|
3555
|
-
},
|
|
3556
|
-
},
|
|
3557
|
-
};
|
|
3558
|
-
const fb = smsFallbackData;
|
|
3559
|
-
if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
|
|
3560
|
-
out.templateConfigs = {
|
|
3561
|
-
templateId: fb.smsTemplateId || '',
|
|
3562
|
-
template: fb.templateContent || fb.content || '',
|
|
3563
|
-
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
3564
|
-
registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
|
|
3565
|
-
};
|
|
3566
|
-
}
|
|
3567
|
-
return out;
|
|
3568
|
-
}, [
|
|
3569
|
-
templateName,
|
|
3570
|
-
templateTitle,
|
|
3571
|
-
templateDesc,
|
|
3572
|
-
templateMediaType,
|
|
3573
|
-
cardVarMapped,
|
|
3574
|
-
suggestions,
|
|
3575
|
-
rcsImageSrc,
|
|
3576
|
-
rcsVideoSrc,
|
|
3577
|
-
rcsThumbnailSrc,
|
|
3578
|
-
selectedDimension,
|
|
3579
|
-
smsFallbackData,
|
|
3580
|
-
isFullMode,
|
|
3581
|
-
isEditFlow,
|
|
3582
|
-
templateType,
|
|
3583
|
-
accountId,
|
|
3584
|
-
wecrmAccountId,
|
|
3585
|
-
accessToken,
|
|
3586
|
-
accountName,
|
|
3587
|
-
hostName,
|
|
3588
|
-
smsRegister,
|
|
3589
|
-
]);
|
|
3590
|
-
|
|
3591
|
-
/**
|
|
3592
|
-
* Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
|
|
3593
|
-
* with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
|
|
3594
|
-
* miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
|
|
3595
|
-
*/
|
|
3596
|
-
const librarySmsFallbackMergedForValidation = useMemo(() => {
|
|
3597
|
-
if (isFullMode) {
|
|
3598
|
-
return smsFallbackData;
|
|
3599
|
-
}
|
|
3600
|
-
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
3601
|
-
const local =
|
|
3602
|
-
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
3603
|
-
return {
|
|
3604
|
-
...smsFromApiShape,
|
|
3605
|
-
...local,
|
|
3606
|
-
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
3607
|
-
};
|
|
3608
|
-
}, [isFullMode, templateData, smsFallbackData]);
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
2034
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
3613
2035
|
// eslint-disable-next-line no-undef
|
|
3614
2036
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -3638,9 +2060,6 @@ const onTitleAddVar = () => {
|
|
|
3638
2060
|
_id: params?.id,
|
|
3639
2061
|
validity: true,
|
|
3640
2062
|
type: RCS,
|
|
3641
|
-
// CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
|
|
3642
|
-
// the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
|
|
3643
|
-
closeSlideBoxAfterSubmit: !isFullMode,
|
|
3644
2063
|
};
|
|
3645
2064
|
getFormData(formDataParams);
|
|
3646
2065
|
};
|
|
@@ -3654,7 +2073,6 @@ const onTitleAddVar = () => {
|
|
|
3654
2073
|
actionCallback({ resp, errorMessage });
|
|
3655
2074
|
setSpin(false); // Always turn off spinner
|
|
3656
2075
|
if (!errorMessage) {
|
|
3657
|
-
setTemplateStatus(RCS_STATUSES.pending);
|
|
3658
2076
|
onCreateComplete();
|
|
3659
2077
|
}
|
|
3660
2078
|
});
|
|
@@ -3666,86 +2084,6 @@ const onTitleAddVar = () => {
|
|
|
3666
2084
|
}
|
|
3667
2085
|
};
|
|
3668
2086
|
|
|
3669
|
-
/** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
|
|
3670
|
-
const smsFallbackBlocksDone = () => {
|
|
3671
|
-
// Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
|
|
3672
|
-
if (
|
|
3673
|
-
!isFullMode
|
|
3674
|
-
&& !isTraiDLTEnable(isFullMode, smsRegister)
|
|
3675
|
-
&& smsFallbackData == null
|
|
3676
|
-
&& hasMeaningfulSmsFallbackShape(
|
|
3677
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
3678
|
-
)
|
|
3679
|
-
) {
|
|
3680
|
-
return true;
|
|
3681
|
-
}
|
|
3682
|
-
if (!smsFallbackData) return false;
|
|
3683
|
-
// Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
|
|
3684
|
-
// concern, not a structural requirement for approval — the registered SMS template body stands on
|
|
3685
|
-
// its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
|
|
3686
|
-
if (isFullMode) return false;
|
|
3687
|
-
const merged = librarySmsFallbackMergedForValidation;
|
|
3688
|
-
const templateText = pickFirstSmsFallbackTemplateString(merged);
|
|
3689
|
-
if (!templateText) {
|
|
3690
|
-
return true;
|
|
3691
|
-
}
|
|
3692
|
-
const rawVarMap =
|
|
3693
|
-
merged.rcsSmsFallbackVarMapped
|
|
3694
|
-
|| merged['rcs-sms-fallback-var-mapped'];
|
|
3695
|
-
const varMap =
|
|
3696
|
-
rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
|
|
3697
|
-
return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
|
|
3698
|
-
};
|
|
3699
|
-
|
|
3700
|
-
/**
|
|
3701
|
-
* Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
|
|
3702
|
-
* semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
|
|
3703
|
-
* / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
|
|
3704
|
-
*/
|
|
3705
|
-
const isLibraryCampaignCardVarMappingIncomplete = () => {
|
|
3706
|
-
if (isFullMode) return false;
|
|
3707
|
-
const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
|
|
3708
|
-
rcsVarTestRegex.test(elem),
|
|
3709
|
-
);
|
|
3710
|
-
const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
|
|
3711
|
-
rcsVarTestRegex.test(elem),
|
|
3712
|
-
);
|
|
3713
|
-
const orderedVarNames = [
|
|
3714
|
-
...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
3715
|
-
...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
3716
|
-
];
|
|
3717
|
-
if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
|
|
3718
|
-
return true;
|
|
3719
|
-
}
|
|
3720
|
-
return orderedVarNames.some((name, globalIdx) => {
|
|
3721
|
-
const v = resolveCardVarMappedSlotValue(
|
|
3722
|
-
cardVarMapped,
|
|
3723
|
-
name,
|
|
3724
|
-
globalIdx,
|
|
3725
|
-
true,
|
|
3726
|
-
rcsSpanningSemanticVarNames.has(name),
|
|
3727
|
-
);
|
|
3728
|
-
const s = v == null ? '' : String(v);
|
|
3729
|
-
return s.trim() === '';
|
|
3730
|
-
});
|
|
3731
|
-
};
|
|
3732
|
-
|
|
3733
|
-
const isCarouselLibraryIncomplete = () => {
|
|
3734
|
-
if (!isCarouselType || isFullMode) return false;
|
|
3735
|
-
if ((carouselErrors || []).some((err) => err?.title || err?.description)) return true;
|
|
3736
|
-
const unfilledVar = (carouselData || []).some((card) =>
|
|
3737
|
-
['title', 'description'].some((field) => {
|
|
3738
|
-
const tokens = splitTemplateVarStringRcs(card?.[field] || '').filter((t) => rcsVarTestRegex.test(t));
|
|
3739
|
-
return tokens.some((t) => {
|
|
3740
|
-
const name = t.replace(/^\{\{|\}\}$/g, '');
|
|
3741
|
-
const v = cardVarMapped?.[name];
|
|
3742
|
-
return v == null || String(v).trim() === '';
|
|
3743
|
-
});
|
|
3744
|
-
})
|
|
3745
|
-
);
|
|
3746
|
-
return unfilledVar;
|
|
3747
|
-
};
|
|
3748
|
-
|
|
3749
2087
|
const isDisableDone = () => {
|
|
3750
2088
|
if(isEditFlow){
|
|
3751
2089
|
return false;
|
|
@@ -3756,26 +2094,46 @@ const onTitleAddVar = () => {
|
|
|
3756
2094
|
}
|
|
3757
2095
|
}
|
|
3758
2096
|
|
|
3759
|
-
if
|
|
3760
|
-
|
|
3761
|
-
|
|
2097
|
+
if(!isFullMode){
|
|
2098
|
+
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2099
|
+
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2100
|
+
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
3762
2101
|
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
2102
|
+
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2103
|
+
return true;
|
|
2104
|
+
}
|
|
3766
2105
|
|
|
3767
|
-
|
|
3768
|
-
|
|
2106
|
+
const hasEmptyMapping =
|
|
2107
|
+
cardVarMapped &&
|
|
2108
|
+
Object.keys(cardVarMapped).length > 0 &&
|
|
2109
|
+
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2110
|
+
if (typeof v !== 'string') return !v; // null/undefined
|
|
2111
|
+
return v.trim() === ''; // empty string
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
if (hasEmptyMapping) {
|
|
2115
|
+
return true;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
const anyMissing = allVars.some(name => {
|
|
2119
|
+
const v = cardVarMapped?.[name];
|
|
2120
|
+
if (typeof v !== 'string') return !v;
|
|
2121
|
+
return v.trim() === '';
|
|
2122
|
+
});
|
|
2123
|
+
if (anyMissing) {
|
|
2124
|
+
return true;
|
|
2125
|
+
}
|
|
3769
2126
|
}
|
|
3770
2127
|
|
|
3771
|
-
|
|
2128
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
3772
2129
|
return true;
|
|
2130
|
+
|
|
3773
2131
|
}
|
|
3774
|
-
if (
|
|
2132
|
+
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
3775
2133
|
return true;
|
|
3776
2134
|
}
|
|
3777
2135
|
|
|
3778
|
-
if (
|
|
2136
|
+
if (isMediaTypeVideo && (rcsVideoSrc.videoSrc === '' || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
3779
2137
|
return true;
|
|
3780
2138
|
}
|
|
3781
2139
|
if (buttonType.includes(CTA)) {
|
|
@@ -3787,61 +2145,72 @@ const onTitleAddVar = () => {
|
|
|
3787
2145
|
return true;
|
|
3788
2146
|
}
|
|
3789
2147
|
}
|
|
3790
|
-
if (templateDescError || templateTitleError) {
|
|
3791
|
-
return true;
|
|
3792
|
-
}
|
|
3793
|
-
if (
|
|
3794
|
-
smsFallbackData?.content
|
|
3795
|
-
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
3796
|
-
) {
|
|
2148
|
+
if (templateDescError || templateTitleError || fallbackMessageError) {
|
|
3797
2149
|
return true;
|
|
3798
2150
|
}
|
|
3799
2151
|
return false;
|
|
3800
2152
|
};
|
|
3801
2153
|
|
|
3802
2154
|
const isEditDisableDone = () => {
|
|
3803
|
-
|
|
2155
|
+
|
|
2156
|
+
if (templateStatus !== RCS_STATUSES.approved) {
|
|
3804
2157
|
return true;
|
|
3805
2158
|
}
|
|
3806
2159
|
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
// }
|
|
3812
|
-
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
3813
|
-
return true;
|
|
2160
|
+
if (!isFullMode) {
|
|
2161
|
+
if (templateName.trim() === '' || templateNameError) {
|
|
2162
|
+
return true;
|
|
2163
|
+
}
|
|
3814
2164
|
}
|
|
2165
|
+
if(!isFullMode){
|
|
2166
|
+
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2167
|
+
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2168
|
+
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
3815
2169
|
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
2170
|
+
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2171
|
+
return true;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
const hasEmptyMapping =
|
|
2175
|
+
cardVarMapped &&
|
|
2176
|
+
Object.keys(cardVarMapped).length > 0 &&
|
|
2177
|
+
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2178
|
+
if (typeof v !== 'string') return !v; // null/undefined
|
|
2179
|
+
return v.trim() === ''; // empty string
|
|
2180
|
+
});
|
|
3819
2181
|
|
|
3820
|
-
|
|
2182
|
+
if (hasEmptyMapping) {
|
|
2183
|
+
return true;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
const anyMissing = allVars.some(name => {
|
|
2187
|
+
const v = cardVarMapped?.[name];
|
|
2188
|
+
if (typeof v !== 'string') return !v;
|
|
2189
|
+
return v.trim() === '';
|
|
2190
|
+
});
|
|
2191
|
+
if (anyMissing) {
|
|
2192
|
+
return true;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
3821
2196
|
return true;
|
|
3822
2197
|
}
|
|
3823
|
-
if (
|
|
2198
|
+
if (isMediaTypeImage && rcsImageSrc === '') {
|
|
3824
2199
|
return true;
|
|
3825
2200
|
}
|
|
3826
|
-
if
|
|
2201
|
+
if(isMediaTypeVideo && (rcsThumbnailSrc === '' || rcsVideoSrc.videoSrc === '')) {
|
|
3827
2202
|
return true;
|
|
3828
2203
|
}
|
|
3829
2204
|
|
|
3830
2205
|
if (buttonType.includes(CTA)) {
|
|
3831
|
-
const hasValidButtons = suggestions.every(suggestion =>
|
|
2206
|
+
const hasValidButtons = suggestions.every(suggestion =>
|
|
3832
2207
|
suggestion.text && suggestion.url && !suggestionError && !forbiddenCharactersValidation(suggestion.text)
|
|
3833
2208
|
);
|
|
3834
2209
|
if (!hasValidButtons) {
|
|
3835
2210
|
return true;
|
|
3836
2211
|
}
|
|
3837
2212
|
}
|
|
3838
|
-
if (templateTitleError || templateDescError) {
|
|
3839
|
-
return true;
|
|
3840
|
-
}
|
|
3841
|
-
if (
|
|
3842
|
-
smsFallbackData?.content
|
|
3843
|
-
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
3844
|
-
) {
|
|
2213
|
+
if (templateTitleError || templateDescError || fallbackMessageError) {
|
|
3845
2214
|
return true;
|
|
3846
2215
|
}
|
|
3847
2216
|
return false;
|
|
@@ -3891,58 +2260,54 @@ const onTitleAddVar = () => {
|
|
|
3891
2260
|
};
|
|
3892
2261
|
|
|
3893
2262
|
const getMainContent = () => {
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
2263
|
+
if (showDltContainer && !fallbackPreviewmode) {
|
|
2264
|
+
const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
|
|
2265
|
+
const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
|
|
2266
|
+
return (
|
|
2267
|
+
<CapSlideBox
|
|
2268
|
+
show={showDltContainer}
|
|
2269
|
+
header={dltHeader}
|
|
2270
|
+
content={dltContent}
|
|
2271
|
+
handleClose={closeDltContainerHandler}
|
|
2272
|
+
size="size-xl"
|
|
2273
|
+
/>
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
3897
2277
|
return (
|
|
3898
2278
|
<>
|
|
3899
|
-
{templateStatus !== '' && (
|
|
3900
|
-
<
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
{
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
)}
|
|
3912
|
-
</CapColumn>
|
|
3913
|
-
</CapRow>
|
|
2279
|
+
{templateStatus !== '' && (<CapRow className="template-status-container">
|
|
2280
|
+
<CapLabel type="label2">
|
|
2281
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
2282
|
+
</CapLabel>
|
|
2283
|
+
|
|
2284
|
+
{templateStatus && (
|
|
2285
|
+
<CapAlert
|
|
2286
|
+
message={getTemplateStatusMessage()}
|
|
2287
|
+
type={getTemplateStatusType(templateStatus)}
|
|
2288
|
+
/>
|
|
2289
|
+
)}
|
|
2290
|
+
</CapRow>
|
|
3914
2291
|
)}
|
|
3915
|
-
<CapRow className=
|
|
2292
|
+
<CapRow className="cap-rcs-creatives">
|
|
3916
2293
|
<CapColumn span={14}>
|
|
3917
2294
|
{/* template name */}
|
|
3918
2295
|
{isFullMode && (
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
onChange={onTemplateNameChange}
|
|
3933
|
-
errorMessage={templateNameError}
|
|
3934
|
-
placeholder={formatMessage(
|
|
3935
|
-
globalMessages.templateNamePlaceholder,
|
|
3936
|
-
)}
|
|
3937
|
-
value={templateName || ''}
|
|
3938
|
-
size="default"
|
|
3939
|
-
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
3940
|
-
disabled={(isEditFlow || !isFullMode)}
|
|
3941
|
-
/>
|
|
3942
|
-
)
|
|
2296
|
+
<CapInput
|
|
2297
|
+
id="rcs_template_name_input"
|
|
2298
|
+
data-testid="template_name"
|
|
2299
|
+
onChange={onTemplateNameChange}
|
|
2300
|
+
errorMessage={templateNameError}
|
|
2301
|
+
placeholder={formatMessage(
|
|
2302
|
+
globalMessages.templateNamePlaceholder,
|
|
2303
|
+
)}
|
|
2304
|
+
value={templateName || ''}
|
|
2305
|
+
size="default"
|
|
2306
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
2307
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2308
|
+
/>
|
|
3943
2309
|
)}
|
|
3944
2310
|
{renderLabel('templateTypeLabel')}
|
|
3945
|
-
|
|
3946
2311
|
<CapRadioGroup
|
|
3947
2312
|
id="select-rcs-template-type"
|
|
3948
2313
|
options={TEMPLATE_TYPE_OPTIONS}
|
|
@@ -3951,30 +2316,24 @@ const onTitleAddVar = () => {
|
|
|
3951
2316
|
disabled={(isEditFlow || !isFullMode)}
|
|
3952
2317
|
/>
|
|
3953
2318
|
|
|
3954
|
-
{
|
|
3955
|
-
|
|
3956
|
-
) : (
|
|
2319
|
+
{/* Show media only for rich_card or carousel */}
|
|
2320
|
+
{(templateType === contentType.rich_card || templateType === contentType.carousel) && (
|
|
3957
2321
|
<>
|
|
3958
|
-
{
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
{
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
<div className="rcs-container-image">
|
|
3970
|
-
{getMediaBasedComponent()}
|
|
3971
|
-
</div>
|
|
3972
|
-
</>
|
|
3973
|
-
)}
|
|
3974
|
-
{renderTextComponent()}
|
|
2322
|
+
{renderLabel('mediaLabel')}
|
|
2323
|
+
<CapRadioGroup
|
|
2324
|
+
options={mediaRadioOptions || []}
|
|
2325
|
+
value={templateMediaType}
|
|
2326
|
+
onChange={onTemplateMediaTypeChange}
|
|
2327
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2328
|
+
className="rcs-radio"
|
|
2329
|
+
/>
|
|
2330
|
+
<div className="rcs-container-image">
|
|
2331
|
+
{getMediaBasedComponent()}
|
|
2332
|
+
</div>
|
|
3975
2333
|
</>
|
|
3976
2334
|
)}
|
|
3977
|
-
|
|
2335
|
+
{renderTextComponent()}
|
|
2336
|
+
<CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
|
|
3978
2337
|
{renderFallBackSmsComponent()}
|
|
3979
2338
|
<div className="rcs-scroll-div" />
|
|
3980
2339
|
</CapColumn>
|
|
@@ -3986,8 +2345,7 @@ const onTitleAddVar = () => {
|
|
|
3986
2345
|
|
|
3987
2346
|
|
|
3988
2347
|
<div className="rcs-footer">
|
|
3989
|
-
{
|
|
3990
|
-
{!isEditFlow && isFullMode && (
|
|
2348
|
+
{!isEditFlow && (
|
|
3991
2349
|
<>
|
|
3992
2350
|
<div className="button-disabled-tooltip-wrapper">
|
|
3993
2351
|
<CapButton
|
|
@@ -3995,9 +2353,7 @@ const onTitleAddVar = () => {
|
|
|
3995
2353
|
disabled={isDisableDone()}
|
|
3996
2354
|
className="rcs-done-btn"
|
|
3997
2355
|
>
|
|
3998
|
-
<FormattedMessage
|
|
3999
|
-
{...(isHostInfoBip ? messages.doneButtonLabel : messages.sendForApprovalButtonLabel)}
|
|
4000
|
-
/>
|
|
2356
|
+
<FormattedMessage {...messages.sendForApprovalButtonLabel} />
|
|
4001
2357
|
</CapButton>
|
|
4002
2358
|
</div>
|
|
4003
2359
|
<CapTooltip
|
|
@@ -4010,6 +2366,7 @@ const onTitleAddVar = () => {
|
|
|
4010
2366
|
className="rcs-test-preview-btn"
|
|
4011
2367
|
type="secondary"
|
|
4012
2368
|
disabled={true}
|
|
2369
|
+
style={{ marginLeft: "8px" }}
|
|
4013
2370
|
>
|
|
4014
2371
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
4015
2372
|
</CapButton>
|
|
@@ -4030,7 +2387,7 @@ const onTitleAddVar = () => {
|
|
|
4030
2387
|
</div>
|
|
4031
2388
|
</>
|
|
4032
2389
|
)}
|
|
4033
|
-
{isEditFlow &&
|
|
2390
|
+
{isEditFlow && templateStatus === RCS_STATUSES.approved && (
|
|
4034
2391
|
<>
|
|
4035
2392
|
<CapButton
|
|
4036
2393
|
onClick={handleTestAndPreview}
|
|
@@ -4042,6 +2399,51 @@ const onTitleAddVar = () => {
|
|
|
4042
2399
|
</>
|
|
4043
2400
|
)}
|
|
4044
2401
|
</div>
|
|
2402
|
+
|
|
2403
|
+
|
|
2404
|
+
{fallbackPreviewmode && (
|
|
2405
|
+
<CapSlideBox
|
|
2406
|
+
className="rcs-fallback-preview"
|
|
2407
|
+
show={fallbackPreviewmode}
|
|
2408
|
+
header={(
|
|
2409
|
+
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2410
|
+
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2411
|
+
</CapHeading>
|
|
2412
|
+
)}
|
|
2413
|
+
content={(
|
|
2414
|
+
<>
|
|
2415
|
+
<UnifiedPreview
|
|
2416
|
+
channel={RCS}
|
|
2417
|
+
content={{
|
|
2418
|
+
rcsPreviewContent: {
|
|
2419
|
+
rcsDesc: tempMsg,
|
|
2420
|
+
},
|
|
2421
|
+
}}
|
|
2422
|
+
device={ANDROID}
|
|
2423
|
+
showDeviceToggle={false}
|
|
2424
|
+
showHeader={false}
|
|
2425
|
+
formatMessage={formatMessage}
|
|
2426
|
+
/>
|
|
2427
|
+
<CapHeading
|
|
2428
|
+
type="h3"
|
|
2429
|
+
style={{ textAlign: 'center' }}
|
|
2430
|
+
className="margin-t-16"
|
|
2431
|
+
>
|
|
2432
|
+
{formatMessage(messages.totalCharacters, {
|
|
2433
|
+
smsCount: Math.ceil(
|
|
2434
|
+
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2435
|
+
),
|
|
2436
|
+
number: tempMsg.length,
|
|
2437
|
+
})}
|
|
2438
|
+
</CapHeading>
|
|
2439
|
+
</>
|
|
2440
|
+
)}
|
|
2441
|
+
handleClose={() => {
|
|
2442
|
+
setFallbackPreviewmode(false);
|
|
2443
|
+
setDltPreviewData('');
|
|
2444
|
+
}}
|
|
2445
|
+
/>
|
|
2446
|
+
)}
|
|
4045
2447
|
</>
|
|
4046
2448
|
);
|
|
4047
2449
|
};
|
|
@@ -4050,57 +2452,23 @@ const onTitleAddVar = () => {
|
|
|
4050
2452
|
<CapSpin spinning={loadingTags || spin}>
|
|
4051
2453
|
{getMainContent()}
|
|
4052
2454
|
</CapSpin>
|
|
4053
|
-
|
|
4054
|
-
{showDltContainer && (() => {
|
|
4055
|
-
const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
|
|
4056
|
-
return (
|
|
4057
|
-
<CapSlideBox
|
|
4058
|
-
show={showDltContainer}
|
|
4059
|
-
header={dltHeader}
|
|
4060
|
-
content={dltContent}
|
|
4061
|
-
handleClose={closeDltContainerHandler}
|
|
4062
|
-
size="size-xl"
|
|
4063
|
-
/>
|
|
4064
|
-
);
|
|
4065
|
-
})()}
|
|
4066
|
-
|
|
4067
2455
|
<TestAndPreviewSlidebox
|
|
4068
2456
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
4069
2457
|
onClose={handleCloseTestAndPreview}
|
|
4070
|
-
formData={
|
|
4071
|
-
content={
|
|
2458
|
+
formData={null} // RCS doesn't use formData structure like SMS
|
|
2459
|
+
content={getTemplateContent()}
|
|
4072
2460
|
currentChannel={RCS}
|
|
4073
|
-
orgUnitId={orgUnitId}
|
|
4074
|
-
rcsTestPreviewOptions={{ isLibraryMode: !isFullMode }}
|
|
4075
|
-
smsFallbackContent={
|
|
4076
|
-
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
4077
|
-
? {
|
|
4078
|
-
templateContent:
|
|
4079
|
-
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
4080
|
-
templateName: smsFallbackData.templateName || '',
|
|
4081
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: !isFullMode
|
|
4082
|
-
? mergeRcsSmsFallbackVarMapLayers(
|
|
4083
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
4084
|
-
smsFallbackData,
|
|
4085
|
-
)
|
|
4086
|
-
: mergeRcsSmsFallbackVarMapLayers({}, smsFallbackData),
|
|
4087
|
-
}
|
|
4088
|
-
: null
|
|
4089
|
-
}
|
|
4090
|
-
smsRegister={smsRegister}
|
|
4091
2461
|
/>
|
|
4092
2462
|
</>
|
|
4093
2463
|
);
|
|
4094
2464
|
};
|
|
4095
2465
|
|
|
4096
|
-
|
|
4097
2466
|
const mapStateToProps = createStructuredSelector({
|
|
4098
2467
|
rcsData: makeSelectRcs(),
|
|
4099
2468
|
accountData: makeSelectAccount(),
|
|
4100
2469
|
metaEntities: makeSelectMetaEntities(),
|
|
4101
2470
|
loadingTags: isLoadingMetaEntities(),
|
|
4102
2471
|
injectedTags: setInjectedTags(),
|
|
4103
|
-
currentOrgDetails: selectCurrentOrgDetails(),
|
|
4104
2472
|
});
|
|
4105
2473
|
|
|
4106
2474
|
const mapDispatchToProps = (dispatch) => ({
|