@capillarytech/creatives-library 8.0.319 → 8.0.320
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 +14 -0
- package/package.json +1 -1
- package/utils/templateVarUtils.js +172 -0
- package/utils/tests/templateVarUtils.test.js +160 -0
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -0
- package/v2Components/CommonTestAndPreview/index.js +693 -155
- package/v2Components/CommonTestAndPreview/messages.js +41 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +15 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/FormBuilder/index.js +7 -1
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +956 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/v2Containers/CreativesContainer/constants.js +9 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +289 -99
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/Rcs/constants.js +32 -1
- package/v2Containers/Rcs/index.js +950 -873
- package/v2Containers/Rcs/index.scss +85 -6
- package/v2Containers/Rcs/messages.js +10 -1
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/index.test.js +41 -38
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
- package/v2Containers/Rcs/tests/utils.test.js +379 -1
- package/v2Containers/Rcs/utils.js +358 -10
- package/v2Containers/Sms/Create/index.js +81 -36
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +609 -128
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +9 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +61 -2
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +90 -40
- package/v2Containers/Templates/sagas.js +57 -12
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/v2Containers/Templates/tests/sagas.test.js +193 -12
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
/* eslint-disable no-unused-expressions */
|
|
2
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
3
3
|
import { bindActionCreators } from 'redux';
|
|
4
4
|
import { createStructuredSelector } from 'reselect';
|
|
5
|
-
import {
|
|
5
|
+
import { FormattedMessage } from 'react-intl';
|
|
6
6
|
import get from 'lodash/get';
|
|
7
7
|
import isEmpty from 'lodash/isEmpty';
|
|
8
8
|
import cloneDeep from 'lodash/cloneDeep';
|
|
9
9
|
import isNil from 'lodash/isNil';
|
|
10
|
-
import styled from 'styled-components';
|
|
11
10
|
import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
|
|
12
11
|
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
13
12
|
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
@@ -22,35 +21,16 @@ import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
|
|
|
22
21
|
import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
|
|
23
22
|
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
24
23
|
import CapImage from '@capillarytech/cap-ui-library/CapImage';
|
|
25
|
-
import CapCard from '@capillarytech/cap-ui-library/CapCard';
|
|
26
24
|
import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
|
|
27
25
|
import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
|
|
28
|
-
import CapCustomCard from '@capillarytech/cap-ui-library/CapCustomCard';
|
|
29
|
-
import CapDropdown from '@capillarytech/cap-ui-library/CapDropdown';
|
|
30
|
-
import CapMenu from '@capillarytech/cap-ui-library/CapMenu';
|
|
31
26
|
import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
|
|
32
|
-
import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
|
|
33
27
|
import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
34
|
-
import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
|
|
35
28
|
import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
|
|
36
|
-
import CapLink from '@capillarytech/cap-ui-library/CapLink';
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
CAP_G01,
|
|
40
|
-
CAP_SPACE_04,
|
|
41
|
-
CAP_SPACE_16,
|
|
42
|
-
CAP_SPACE_24,
|
|
43
|
-
CAP_SPACE_28,
|
|
44
|
-
CAP_SPACE_32,
|
|
45
|
-
CAP_WHITE,
|
|
46
|
-
CAP_SECONDARY,
|
|
47
|
-
} from '@capillarytech/cap-ui-library/styled/variables';
|
|
48
29
|
|
|
49
30
|
import CapVideoUpload from '../../v2Components/CapVideoUpload';
|
|
50
31
|
import * as globalActions from '../Cap/actions';
|
|
51
32
|
import CapActionButton from '../../v2Components/CapActionButton';
|
|
52
33
|
import { makeSelectRcs, makeSelectAccount } from './selectors';
|
|
53
|
-
import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
|
|
54
34
|
import {
|
|
55
35
|
isLoadingMetaEntities,
|
|
56
36
|
makeSelectMetaEntities,
|
|
@@ -60,6 +40,15 @@ import * as RcsActions from './actions';
|
|
|
60
40
|
import { isAiContentBotDisabled } from '../../utils/common';
|
|
61
41
|
import * as TemplatesActions from '../Templates/actions';
|
|
62
42
|
import './index.scss';
|
|
43
|
+
import {
|
|
44
|
+
normalizeLibraryLoadedTitleDesc,
|
|
45
|
+
mergeRcsSmsFallBackContentFromDetails,
|
|
46
|
+
mergeRcsSmsFallbackVarMapLayers,
|
|
47
|
+
pickFirstSmsFallbackTemplateString,
|
|
48
|
+
syncCardVarMappedSemanticsFromSlots,
|
|
49
|
+
hasMeaningfulSmsFallbackShape,
|
|
50
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
51
|
+
} from './rcsLibraryHydrationUtils';
|
|
63
52
|
import {
|
|
64
53
|
RCS,
|
|
65
54
|
SMS,
|
|
@@ -81,8 +70,6 @@ import {
|
|
|
81
70
|
MESSAGE_TEXT,
|
|
82
71
|
ALLOWED_EXTENSIONS_VIDEO_REGEX,
|
|
83
72
|
RCS_VIDEO_SIZE,
|
|
84
|
-
TEMPLATE_HEADER_MAX_LENGTH,
|
|
85
|
-
TEMPLATE_MESSAGE_MAX_LENGTH,
|
|
86
73
|
RCS_THUMBNAIL_MIN_SIZE,
|
|
87
74
|
RCS_THUMBNAIL_MAX_SIZE,
|
|
88
75
|
contentType,
|
|
@@ -95,35 +82,45 @@ import {
|
|
|
95
82
|
MAX_BUTTONS,
|
|
96
83
|
INITIAL_SUGGESTIONS_DATA_STOP,
|
|
97
84
|
RCS_BUTTON_TYPES,
|
|
98
|
-
titletype,
|
|
99
|
-
descType,
|
|
100
85
|
STANDALONE,
|
|
101
86
|
VERTICAL,
|
|
102
87
|
SMALL,
|
|
103
88
|
MEDIUM,
|
|
104
89
|
RICHCARD,
|
|
90
|
+
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
91
|
+
RCS_TAG_AREA_FIELD_TITLE,
|
|
92
|
+
RCS_TAG_AREA_FIELD_DESC,
|
|
105
93
|
} from './constants';
|
|
106
94
|
import globalMessages from '../Cap/messages';
|
|
107
95
|
import messages from './messages';
|
|
108
96
|
import creativesMessages from '../CreativesContainer/messages';
|
|
109
97
|
import withCreatives from '../../hoc/withCreatives';
|
|
110
98
|
import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
111
|
-
import
|
|
99
|
+
import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
|
|
100
|
+
import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
112
101
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
102
|
+
import { splitTemplateVarString } from '../../utils/templateVarUtils';
|
|
113
103
|
import CapImageUpload from '../../v2Components/CapImageUpload';
|
|
114
|
-
import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
|
|
115
104
|
import Templates from '../Templates';
|
|
116
105
|
import SmsTraiEdit from '../SmsTrai/Edit';
|
|
106
|
+
import SmsFallback from '../../v2Components/SmsFallback';
|
|
107
|
+
import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
|
|
117
108
|
import TagList from '../TagList';
|
|
118
109
|
import { validateTags } from '../../utils/tagValidations';
|
|
119
|
-
import {
|
|
110
|
+
import { isTraiDLTEnable } from '../../utils/common';
|
|
120
111
|
import { isTagIncluded } from '../../utils/commonUtils';
|
|
121
112
|
import injectReducer from '../../utils/injectReducer';
|
|
122
113
|
import v2RcsReducer from './reducer';
|
|
123
|
-
import {
|
|
124
|
-
|
|
114
|
+
import {
|
|
115
|
+
areAllRcsSmsFallbackVarSlotsFilled,
|
|
116
|
+
buildRcsNumericMustachePlaceholderRegex,
|
|
117
|
+
getTemplateStatusType,
|
|
118
|
+
normalizeCardVarMapped,
|
|
119
|
+
coalesceCardVarMappedToTemplate,
|
|
120
|
+
resolveCardVarMappedSlotValue,
|
|
121
|
+
sanitizeCardVarMappedValue,
|
|
122
|
+
} from './utils';
|
|
125
123
|
|
|
126
|
-
const { Group: CapCheckboxGroup } = CapCheckbox;
|
|
127
124
|
export const Rcs = (props) => {
|
|
128
125
|
const {
|
|
129
126
|
intl,
|
|
@@ -137,15 +134,14 @@ export const Rcs = (props) => {
|
|
|
137
134
|
templatesActions,
|
|
138
135
|
globalActions,
|
|
139
136
|
location,
|
|
140
|
-
handleClose,
|
|
141
137
|
getDefaultTags,
|
|
142
138
|
supportedTags,
|
|
143
139
|
metaEntities,
|
|
144
140
|
injectedTags,
|
|
145
141
|
loadingTags,
|
|
146
142
|
getFormData,
|
|
147
|
-
isDltEnabled,
|
|
148
143
|
smsRegister,
|
|
144
|
+
orgUnitId,
|
|
149
145
|
selectedOfferDetails,
|
|
150
146
|
eventContextTags,
|
|
151
147
|
accountData = {},
|
|
@@ -156,8 +152,8 @@ export const Rcs = (props) => {
|
|
|
156
152
|
} = props || {};
|
|
157
153
|
const { formatMessage } = intl;
|
|
158
154
|
const { TextArea } = CapInput;
|
|
159
|
-
const { CapCustomCardList } = CapCustomCard;
|
|
160
155
|
const [isEditFlow, setEditFlow] = useState(false);
|
|
156
|
+
const isEditLike = isEditFlow || !isFullMode;
|
|
161
157
|
const [tags, updateTags] = useState([]);
|
|
162
158
|
const [spin, setSpin] = useState(false);
|
|
163
159
|
//template
|
|
@@ -166,112 +162,71 @@ export const Rcs = (props) => {
|
|
|
166
162
|
const [templateMediaType, setTemplateMediaType] = useState(
|
|
167
163
|
RCS_MEDIA_TYPES.NONE,
|
|
168
164
|
);
|
|
169
|
-
const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
|
|
170
165
|
const [templateTitle, setTemplateTitle] = useState('');
|
|
171
166
|
const [templateDesc, setTemplateDesc] = useState('');
|
|
172
167
|
const [templateDescError, setTemplateDescError] = useState(false);
|
|
173
168
|
const [templateStatus, setTemplateStatus] = useState('');
|
|
174
|
-
const [templateDate, setTemplateDate] = useState('');
|
|
175
|
-
//fallback
|
|
176
|
-
const [fallbackMessage, setFallbackMessage] = useState('');
|
|
177
|
-
const [fallbackMessageError, setFallbackMessageError] = useState(false);
|
|
178
169
|
//fallback dlt
|
|
179
170
|
const [showDltContainer, setShowDltContainer] = useState(false);
|
|
180
171
|
const [dltMode, setDltMode] = useState('');
|
|
181
172
|
const [dltEditData, setDltEditData] = useState({});
|
|
182
|
-
|
|
183
|
-
const [
|
|
184
|
-
const [dltPreviewData, setDltPreviewData] = useState('');
|
|
173
|
+
/** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
|
|
174
|
+
const [smsFallbackData, setSmsFallbackData] = useState(undefined);
|
|
185
175
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
|
|
186
|
-
const
|
|
176
|
+
const buttonType = RCS_BUTTON_TYPES.NONE;
|
|
187
177
|
const [suggestionError, setSuggestionError] = useState(true);
|
|
188
178
|
const [templateType, setTemplateType] = useState('text_message');
|
|
189
|
-
const [templateHeader, setTemplateHeader] = useState('');
|
|
190
|
-
const [templateMessage, setTemplateMessage] = useState('');
|
|
191
|
-
const [templateHeaderError, setTemplateHeaderError] = useState('');
|
|
192
|
-
const [templateMessageError, setTemplateMessageError] = useState('');
|
|
193
|
-
const validVarRegex = /\{\{(\d+)\}\}/g;
|
|
194
|
-
const [updatedTitleData, setUpdatedTitleData] = useState([]);
|
|
195
|
-
const [updatedDescData, setUpdatedDescData] = useState([]);
|
|
196
179
|
const [titleVarMappedData, setTitleVarMappedData] = useState({});
|
|
197
180
|
const [descVarMappedData, setDescVarMappedData] = useState({});
|
|
198
181
|
const [titleTextAreaId, setTitleTextAreaId] = useState();
|
|
199
182
|
const [descTextAreaId, setDescTextAreaId] = useState();
|
|
200
183
|
const [assetList, setAssetList] = useState({});
|
|
201
|
-
const [assetListImage, setAssetListImage] = useState('');
|
|
202
184
|
const [rcsImageSrc, updateRcsImageSrc] = useState('');
|
|
203
185
|
const [rcsVideoSrc, setRcsVideoSrc] = useState({});
|
|
204
186
|
const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
|
|
205
187
|
const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
206
|
-
const [imageError, setImageError] = useState(null);
|
|
207
188
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
208
189
|
const [cardVarMapped, setCardVarMapped] = useState({});
|
|
190
|
+
/** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
|
|
191
|
+
const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
|
|
192
|
+
const lastHydratedRcsCardVarSignatureRef = useRef(null);
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
|
|
196
|
+
* without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
|
|
197
|
+
* fallback card / content stopped appearing until re-selected.
|
|
198
|
+
*/
|
|
199
|
+
const rcsHydrationDetails = useMemo(
|
|
200
|
+
() => (isFullMode ? rcsData?.templateDetails : templateData),
|
|
201
|
+
[isFullMode, rcsData?.templateDetails, templateData],
|
|
202
|
+
);
|
|
209
203
|
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
224
|
-
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
225
|
-
|
|
226
|
-
// Build media preview object (same pattern as getRcsPreview)
|
|
227
|
-
const mediaPreview = {};
|
|
228
|
-
if (isMediaTypeImage && rcsImageSrc) {
|
|
229
|
-
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
230
|
-
}
|
|
231
|
-
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
232
|
-
// For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
|
|
233
|
-
if (rcsThumbnailSrc) {
|
|
234
|
-
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
235
|
-
} else if (rcsVideoSrc?.videoSrc) {
|
|
236
|
-
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
237
|
-
}
|
|
238
|
-
// Also include thumbnailSrc separately if available
|
|
239
|
-
if (rcsThumbnailSrc) {
|
|
240
|
-
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
204
|
+
/** 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'). */
|
|
205
|
+
const lastTagSchemaQueryKeyRef = useRef(null);
|
|
206
|
+
/**
|
|
207
|
+
* Library: parent often passes a new `templateData` object reference every render. Re-applying the same
|
|
208
|
+
* SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
|
|
209
|
+
*/
|
|
210
|
+
const lastSmsFallbackHydrationKeyRef = useRef(null);
|
|
211
|
+
|
|
212
|
+
const fetchTagSchemaIfNewQuery = useCallback(
|
|
213
|
+
(query) => {
|
|
214
|
+
const key = JSON.stringify(query);
|
|
215
|
+
if (lastTagSchemaQueryKeyRef.current === key) {
|
|
216
|
+
return;
|
|
241
217
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// templateDesc is used for ALL types (text message body or rich card description)
|
|
248
|
-
// For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
|
|
249
|
-
const contentObj = {
|
|
250
|
-
// Map templateTitle to templateHeader and templateDesc to templateMessage
|
|
251
|
-
templateHeader: templateTitle,
|
|
252
|
-
templateMessage: templateDesc,
|
|
253
|
-
...mediaPreview,
|
|
254
|
-
...(suggestions.length > 0 && {
|
|
255
|
-
suggestions: suggestions,
|
|
256
|
-
}),
|
|
257
|
-
};
|
|
218
|
+
lastTagSchemaQueryKeyRef.current = key;
|
|
219
|
+
globalActions.fetchSchemaForEntity(query);
|
|
220
|
+
},
|
|
221
|
+
[globalActions],
|
|
222
|
+
);
|
|
258
223
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
templateMediaType,
|
|
262
|
-
templateTitle,
|
|
263
|
-
templateDesc,
|
|
264
|
-
rcsImageSrc,
|
|
265
|
-
rcsVideoSrc,
|
|
266
|
-
rcsThumbnailSrc,
|
|
267
|
-
suggestions,
|
|
268
|
-
selectedDimension,
|
|
269
|
-
]);
|
|
224
|
+
// TestAndPreviewSlidebox state
|
|
225
|
+
const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
|
|
270
226
|
|
|
271
227
|
// Handle Test and Preview button click
|
|
272
228
|
const handleTestAndPreview = useCallback(() => {
|
|
273
229
|
setShowTestAndPreviewSlidebox(true);
|
|
274
|
-
setIsTestAndPreviewMode(true);
|
|
275
230
|
if (propsHandleTestAndPreview) {
|
|
276
231
|
propsHandleTestAndPreview();
|
|
277
232
|
}
|
|
@@ -280,23 +235,24 @@ export const Rcs = (props) => {
|
|
|
280
235
|
// Handle close Test and Preview slidebox
|
|
281
236
|
const handleCloseTestAndPreview = useCallback(() => {
|
|
282
237
|
setShowTestAndPreviewSlidebox(false);
|
|
283
|
-
setIsTestAndPreviewMode(false);
|
|
284
238
|
if (propsHandleCloseTestAndPreview) {
|
|
285
239
|
propsHandleCloseTestAndPreview();
|
|
286
240
|
}
|
|
287
241
|
}, [propsHandleCloseTestAndPreview]);
|
|
288
242
|
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
};
|
|
243
|
+
/** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
|
|
244
|
+
const handleSmsFallbackEditorStateChange = useCallback((patch) => {
|
|
245
|
+
setSmsFallbackData((prev) => {
|
|
246
|
+
if (!patch || typeof patch !== 'object') return prev;
|
|
247
|
+
// Merge even when `prev` is null so tag/slot updates apply on top of `smsFromApiShape` in createPayload.
|
|
248
|
+
return { ...(prev || {}), ...patch };
|
|
249
|
+
});
|
|
250
|
+
}, []);
|
|
298
251
|
|
|
252
|
+
/** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
|
|
299
253
|
const [accountId, setAccountId] = useState('');
|
|
254
|
+
/** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
|
|
255
|
+
const [wecrmAccountId, setWecrmAccountId] = useState('');
|
|
300
256
|
const [accessToken, setAccessToken] = useState('');
|
|
301
257
|
const [hostName, setHostName] = useState('');
|
|
302
258
|
const [accountName, setAccountName] = useState('');
|
|
@@ -305,15 +261,25 @@ export const Rcs = (props) => {
|
|
|
305
261
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
306
262
|
if (!isEmpty(accountObj)) {
|
|
307
263
|
const {
|
|
264
|
+
id: wecrmId,
|
|
308
265
|
sourceAccountIdentifier = '',
|
|
309
266
|
configs = {},
|
|
310
267
|
} = accountObj;
|
|
311
|
-
|
|
312
268
|
setAccountId(sourceAccountIdentifier);
|
|
269
|
+
setWecrmAccountId(
|
|
270
|
+
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
271
|
+
);
|
|
313
272
|
setAccessToken(configs.accessToken || '');
|
|
314
273
|
setHostName(accountObj.hostName || '');
|
|
315
274
|
setAccountName(accountObj.name || '');
|
|
316
275
|
setRcsAccount(accountObj.id || '');
|
|
276
|
+
} else {
|
|
277
|
+
setAccountId('');
|
|
278
|
+
setWecrmAccountId('');
|
|
279
|
+
setAccessToken('');
|
|
280
|
+
setHostName('');
|
|
281
|
+
setAccountName('');
|
|
282
|
+
setRcsAccount('');
|
|
317
283
|
}
|
|
318
284
|
}, [accountData.selectedRcsAccount]);
|
|
319
285
|
|
|
@@ -369,7 +335,9 @@ export const Rcs = (props) => {
|
|
|
369
335
|
if (isFullMode) return;
|
|
370
336
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
371
337
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
372
|
-
const
|
|
338
|
+
const slotOffset =
|
|
339
|
+
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
340
|
+
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
373
341
|
if (!resolved) {
|
|
374
342
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
375
343
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -414,13 +382,41 @@ export const Rcs = (props) => {
|
|
|
414
382
|
|
|
415
383
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
416
384
|
|
|
417
|
-
const
|
|
385
|
+
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
386
|
+
|
|
387
|
+
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
388
|
+
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
389
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
390
|
+
const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
391
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
392
|
+
let varOrdinal = 0;
|
|
393
|
+
for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
|
|
394
|
+
const segmentToken = templateSegments[segmentIndexInField];
|
|
395
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
396
|
+
if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
|
|
397
|
+
return offset + varOrdinal;
|
|
398
|
+
}
|
|
399
|
+
varOrdinal += 1;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
418
406
|
if (!str) return '';
|
|
419
|
-
const arr =
|
|
407
|
+
const arr = splitTemplateVarStringRcs(str);
|
|
408
|
+
let varOrdinal = 0;
|
|
420
409
|
return arr.map((elem) => {
|
|
421
410
|
if (rcsVarTestRegex.test(elem)) {
|
|
422
411
|
const key = getVarNameFromToken(elem);
|
|
423
|
-
const
|
|
412
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
413
|
+
varOrdinal += 1;
|
|
414
|
+
const v = resolveCardVarMappedSlotValue(
|
|
415
|
+
cardVarMapped,
|
|
416
|
+
key,
|
|
417
|
+
globalSlot,
|
|
418
|
+
isEditLike,
|
|
419
|
+
);
|
|
424
420
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
425
421
|
return String(v);
|
|
426
422
|
}
|
|
@@ -428,107 +424,154 @@ export const Rcs = (props) => {
|
|
|
428
424
|
}).join('');
|
|
429
425
|
};
|
|
430
426
|
|
|
427
|
+
/**
|
|
428
|
+
* Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
|
|
429
|
+
* (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
|
|
430
|
+
* TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
|
|
431
|
+
*/
|
|
432
|
+
const getTemplateContent = useCallback(() => {
|
|
433
|
+
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
434
|
+
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
435
|
+
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
431
436
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
tokens.forEach((t) => {
|
|
443
|
-
const name = getVarNameFromToken(t);
|
|
444
|
-
if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
|
|
445
|
-
next[name] = '';
|
|
446
|
-
changed = true;
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
return changed ? next : prev;
|
|
450
|
-
});
|
|
451
|
-
}, [isFullMode, templateTitle, templateDesc]);
|
|
437
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
438
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
439
|
+
? 0
|
|
440
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
441
|
+
const resolvedTitle = isMediaTypeText
|
|
442
|
+
? ''
|
|
443
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
444
|
+
const resolvedDesc = isSlotMappingMode
|
|
445
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
446
|
+
: templateDesc;
|
|
452
447
|
|
|
448
|
+
const mediaPreview = {};
|
|
449
|
+
if (isMediaTypeImage && rcsImageSrc) {
|
|
450
|
+
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
451
|
+
}
|
|
452
|
+
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
453
|
+
if (rcsThumbnailSrc) {
|
|
454
|
+
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
455
|
+
} else if (rcsVideoSrc?.videoSrc) {
|
|
456
|
+
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
457
|
+
}
|
|
458
|
+
if (rcsThumbnailSrc) {
|
|
459
|
+
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const contentObj = {
|
|
464
|
+
templateHeader: resolvedTitle,
|
|
465
|
+
templateMessage: resolvedDesc,
|
|
466
|
+
...mediaPreview,
|
|
467
|
+
...(suggestions.length > 0 && {
|
|
468
|
+
suggestions: suggestions,
|
|
469
|
+
}),
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
return contentObj;
|
|
473
|
+
}, [
|
|
474
|
+
templateMediaType,
|
|
475
|
+
templateTitle,
|
|
476
|
+
templateDesc,
|
|
477
|
+
rcsImageSrc,
|
|
478
|
+
rcsVideoSrc,
|
|
479
|
+
rcsThumbnailSrc,
|
|
480
|
+
suggestions,
|
|
481
|
+
selectedDimension,
|
|
482
|
+
isFullMode,
|
|
483
|
+
isEditFlow,
|
|
484
|
+
cardVarMapped,
|
|
485
|
+
]);
|
|
486
|
+
|
|
487
|
+
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
453
488
|
|
|
454
|
-
const RcsLabel = styled.div`
|
|
455
|
-
display: flex;
|
|
456
|
-
margin-top: 20px;
|
|
457
|
-
`;
|
|
458
489
|
const paramObj = params || {};
|
|
459
490
|
useEffect(() => {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}, [paramObj.id]);
|
|
491
|
+
const { id } = paramObj;
|
|
492
|
+
if (id && isFullMode) {
|
|
493
|
+
setSpin(true);
|
|
494
|
+
actions.getTemplateDetails(id, setSpin);
|
|
495
|
+
}
|
|
496
|
+
return () => {
|
|
497
|
+
actions.clearEditResponse();
|
|
498
|
+
};
|
|
499
|
+
}, [paramObj.id, isFullMode]);
|
|
469
500
|
|
|
470
501
|
useEffect(() => {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
502
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
503
|
+
|
|
504
|
+
const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
|
|
505
|
+
|
|
506
|
+
const initField = (targetString, setVarMap, slotOffset) => {
|
|
507
|
+
const arr = splitTemplateVarStringRcs(targetString);
|
|
508
|
+
if (!arr?.length) {
|
|
509
|
+
setVarMap({});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const nextVarMap = {};
|
|
513
|
+
let varOrdinal = 0;
|
|
514
|
+
arr.forEach((elem, idx) => {
|
|
515
|
+
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
516
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
517
|
+
const id = `${elem}_${idx}`;
|
|
518
|
+
const varName = getVarNameFromToken(elem);
|
|
519
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
520
|
+
varOrdinal += 1;
|
|
521
|
+
const mappedValue = resolveCardVarMappedSlotValue(
|
|
522
|
+
cardVarMapped,
|
|
523
|
+
varName,
|
|
524
|
+
globalSlot,
|
|
525
|
+
isEditLike,
|
|
526
|
+
);
|
|
527
|
+
nextVarMap[id] = mappedValue;
|
|
479
528
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
484
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
485
|
-
const id = `${elem}_${idx}`;
|
|
486
|
-
const varName = getVarNameFromToken(elem);
|
|
487
|
-
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
488
|
-
nextVarMap[id] = mappedValue;
|
|
489
|
-
if (mappedValue !== '') {
|
|
490
|
-
nextUpdated[idx] = mappedValue;
|
|
491
|
-
} else {
|
|
492
|
-
nextUpdated[idx] = elem;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
setVarMap(nextVarMap);
|
|
497
|
-
setUpdated(nextUpdated);
|
|
498
|
-
};
|
|
529
|
+
});
|
|
530
|
+
setVarMap(nextVarMap);
|
|
531
|
+
};
|
|
499
532
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
533
|
+
initField(templateTitle, setTitleVarMappedData, 0);
|
|
534
|
+
initField(templateDesc, setDescVarMappedData, titleTokenCount);
|
|
535
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
536
|
+
|
|
537
|
+
useEffect(() => {
|
|
538
|
+
if (!isEditFlow && isFullMode) {
|
|
506
539
|
setRcsVideoSrc({});
|
|
507
540
|
updateRcsImageSrc('');
|
|
508
541
|
setUpdateRcsImageSrc('');
|
|
509
542
|
updateRcsThumbnailSrc('');
|
|
510
543
|
setAssetList({});
|
|
511
|
-
|
|
512
|
-
|
|
544
|
+
}
|
|
545
|
+
}, [templateMediaType]);
|
|
513
546
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
547
|
+
/** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
|
|
548
|
+
const templateStatusHelper = (cardContentFirst) => {
|
|
549
|
+
const raw =
|
|
550
|
+
cardContentFirst?.Status
|
|
551
|
+
?? cardContentFirst?.status
|
|
552
|
+
?? cardContentFirst?.approvalStatus
|
|
553
|
+
?? '';
|
|
554
|
+
const status = typeof raw === 'string' ? raw.trim() : String(raw);
|
|
555
|
+
const n = status.toLowerCase();
|
|
556
|
+
switch (n) {
|
|
557
|
+
case 'approved':
|
|
518
558
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
519
559
|
break;
|
|
520
|
-
case
|
|
560
|
+
case 'pending':
|
|
521
561
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
522
562
|
break;
|
|
523
|
-
case
|
|
563
|
+
case 'awaitingapproval':
|
|
524
564
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
525
565
|
break;
|
|
526
|
-
case
|
|
566
|
+
case 'unavailable':
|
|
527
567
|
setTemplateStatus(RCS_STATUSES.unavailable);
|
|
528
568
|
break;
|
|
529
|
-
case
|
|
569
|
+
case 'rejected':
|
|
530
570
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
531
571
|
break;
|
|
572
|
+
case 'created':
|
|
573
|
+
setTemplateStatus(RCS_STATUSES.created);
|
|
574
|
+
break;
|
|
532
575
|
default:
|
|
533
576
|
setTemplateStatus(status);
|
|
534
577
|
break;
|
|
@@ -539,7 +582,6 @@ export const Rcs = (props) => {
|
|
|
539
582
|
if (mediaType) {
|
|
540
583
|
setTemplateMediaType(mediaType);
|
|
541
584
|
}
|
|
542
|
-
const tempOrientation = cardSettings.cardOrientation;
|
|
543
585
|
const tempAlignment = cardSettings.mediaAlignment;
|
|
544
586
|
const tempHeight = mediaData.height;
|
|
545
587
|
|
|
@@ -582,48 +624,200 @@ export const Rcs = (props) => {
|
|
|
582
624
|
};
|
|
583
625
|
|
|
584
626
|
useEffect(() => {
|
|
585
|
-
const details =
|
|
627
|
+
const details = rcsHydrationDetails;
|
|
586
628
|
if (details && Object.keys(details).length > 0) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
629
|
+
// Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
|
|
630
|
+
const cardFromVersions = get(
|
|
631
|
+
details,
|
|
632
|
+
'versions.base.content.RCS.rcsContent.cardContent[0]',
|
|
633
|
+
);
|
|
634
|
+
const cardFromTop = get(details, 'rcsContent.cardContent[0]');
|
|
635
|
+
const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
|
|
636
|
+
const cardVarMappedFromCardContent =
|
|
637
|
+
card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
|
|
638
|
+
? card0.cardVarMapped
|
|
639
|
+
: {};
|
|
640
|
+
const cardVarMappedFromRootMirror =
|
|
641
|
+
details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
|
|
642
|
+
? details.rcsCardVarMapped
|
|
643
|
+
: {};
|
|
644
|
+
// Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
|
|
645
|
+
// nested versions.cardContent[0].cardVarMapped is dropped on reload.
|
|
646
|
+
const mergedCardVarMappedFromPayload = {
|
|
647
|
+
...cardVarMappedFromRootMirror,
|
|
648
|
+
...cardVarMappedFromCardContent,
|
|
649
|
+
};
|
|
650
|
+
const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
|
|
651
|
+
const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
|
|
652
|
+
const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
|
|
653
|
+
Object.keys(mergedCardVarMappedFromPayload)
|
|
654
|
+
.sort()
|
|
655
|
+
.reduce((accumulator, mapKey) => {
|
|
656
|
+
accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
|
|
657
|
+
return accumulator;
|
|
658
|
+
}, {}),
|
|
659
|
+
)}`;
|
|
660
|
+
if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
|
|
661
|
+
lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
|
|
662
|
+
setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
|
|
590
663
|
}
|
|
591
|
-
const
|
|
664
|
+
const tokenListForMap = [
|
|
665
|
+
...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
|
|
666
|
+
...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
|
|
667
|
+
];
|
|
668
|
+
const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
669
|
+
// Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
|
|
670
|
+
// getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
|
|
671
|
+
const cardVarMappedBeforeCoalesce = isFullMode
|
|
672
|
+
? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
|
|
673
|
+
: { ...mergedCardVarMappedFromPayload };
|
|
674
|
+
const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
675
|
+
cardVarMappedBeforeCoalesce,
|
|
676
|
+
loadedTitleForMap,
|
|
677
|
+
loadedDescForMap,
|
|
678
|
+
rcsVarRegex,
|
|
679
|
+
);
|
|
680
|
+
const cardVarMappedAfterNumericSlotSync = !isFullMode
|
|
681
|
+
? syncCardVarMappedSemanticsFromSlots(
|
|
682
|
+
cardVarMappedAfterCoalesce,
|
|
683
|
+
loadedTitleForMap,
|
|
684
|
+
loadedDescForMap,
|
|
685
|
+
rcsVarRegex,
|
|
686
|
+
)
|
|
687
|
+
: cardVarMappedAfterCoalesce;
|
|
688
|
+
const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
|
|
689
|
+
// Pre-populate variable/tag mappings while opening an existing template in edit flows
|
|
690
|
+
setCardVarMapped((previousVarMapState) => {
|
|
691
|
+
const previousVarMap = previousVarMapState ?? {};
|
|
692
|
+
if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
|
|
693
|
+
const previousVarMapKeys = Object.keys(previousVarMap);
|
|
694
|
+
const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
|
|
695
|
+
if (previousVarMapKeys.length === nextVarMapKeys.length) {
|
|
696
|
+
const allSlotValuesMatchPrevious = previousVarMapKeys.every(
|
|
697
|
+
(key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
|
|
698
|
+
);
|
|
699
|
+
if (allSlotValuesMatchPrevious) return previousVarMapState;
|
|
700
|
+
}
|
|
701
|
+
return hydratedCardVarMappedResult;
|
|
702
|
+
});
|
|
703
|
+
const mediaType =
|
|
704
|
+
card0.mediaType
|
|
705
|
+
|| get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
592
706
|
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
593
707
|
setTemplateType(contentType.text_message);
|
|
594
708
|
} else {
|
|
595
709
|
setTemplateType(contentType.rich_card);
|
|
596
710
|
}
|
|
597
711
|
setEditFlow(true);
|
|
598
|
-
setTemplateName(details
|
|
599
|
-
const loadedTitle =
|
|
600
|
-
const loadedDesc =
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
712
|
+
setTemplateName(details?.name || details?.creativeName || '');
|
|
713
|
+
const loadedTitle = loadedTitleForMap;
|
|
714
|
+
const loadedDesc = loadedDescForMap;
|
|
715
|
+
const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
|
|
716
|
+
loadedTitle,
|
|
717
|
+
loadedDesc,
|
|
718
|
+
isFullMode,
|
|
719
|
+
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
720
|
+
rcsVarRegex,
|
|
721
|
+
});
|
|
604
722
|
setTemplateTitle(normalizedTitle);
|
|
605
723
|
setTemplateDesc(normalizedDesc);
|
|
606
|
-
setSuggestions(
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
724
|
+
setSuggestions(
|
|
725
|
+
Array.isArray(card0.suggestions)
|
|
726
|
+
? card0.suggestions
|
|
727
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
|
|
728
|
+
);
|
|
729
|
+
const cardForStatus = {
|
|
730
|
+
...card0,
|
|
731
|
+
Status:
|
|
732
|
+
card0.Status
|
|
733
|
+
?? card0.status
|
|
734
|
+
?? card0.approvalStatus
|
|
735
|
+
?? get(details, 'templateStatus')
|
|
736
|
+
?? get(details, 'approvalStatus')
|
|
737
|
+
?? get(details, 'creativeStatus')
|
|
738
|
+
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
739
|
+
?? '',
|
|
740
|
+
};
|
|
741
|
+
templateStatusHelper(cardForStatus);
|
|
742
|
+
const mediaData =
|
|
743
|
+
card0.media != null && card0.media !== ''
|
|
744
|
+
? card0.media
|
|
745
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
746
|
+
const cardSettings =
|
|
747
|
+
get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
|
|
748
|
+
|| get(details, 'rcsContent.cardSettings', '');
|
|
610
749
|
setMediaData(mediaData, mediaType, cardSettings);
|
|
611
750
|
if (details?.edit) {
|
|
612
751
|
const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
|
|
613
752
|
setRcsAccount(rcsAccountId);
|
|
614
753
|
}
|
|
754
|
+
const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
755
|
+
const base = get(smsFallbackContent, 'versions.base', {});
|
|
756
|
+
const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
|
|
757
|
+
const smsEditor = base['sms-editor'];
|
|
758
|
+
const fromNested = Array.isArray(updatedEditor)
|
|
759
|
+
? updatedEditor.join('')
|
|
760
|
+
: (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
|
|
761
|
+
const fallbackMessage = smsFallbackContent.smsContent
|
|
762
|
+
|| smsFallbackContent.smsTemplateContent
|
|
763
|
+
|| smsFallbackContent.message
|
|
764
|
+
|| fromNested
|
|
765
|
+
|| '';
|
|
766
|
+
const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
767
|
+
const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
|
|
768
|
+
const hasFallbackPayload =
|
|
769
|
+
smsFallbackContent
|
|
770
|
+
&& Object.keys(smsFallbackContent).length > 0
|
|
771
|
+
&& (
|
|
772
|
+
!!smsFallbackContent.smsTemplateName
|
|
773
|
+
|| !!fallbackMessage
|
|
774
|
+
|| hasVarMapped
|
|
775
|
+
);
|
|
776
|
+
if (hasFallbackPayload) {
|
|
777
|
+
if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
|
|
778
|
+
console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
|
|
779
|
+
}
|
|
780
|
+
const unicodeFromApi =
|
|
781
|
+
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
782
|
+
? smsFallbackContent.unicodeValidity
|
|
783
|
+
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
784
|
+
const nextSmsState = {
|
|
785
|
+
templateName: smsFallbackContent.smsTemplateName || '',
|
|
786
|
+
content: fallbackMessage,
|
|
787
|
+
templateContent: fallbackMessage,
|
|
788
|
+
unicodeValidity: unicodeFromApi,
|
|
789
|
+
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
790
|
+
};
|
|
791
|
+
const hydrationKey = JSON.stringify({
|
|
792
|
+
creativeKey: details._id || details.name || details.creativeName || '',
|
|
793
|
+
templateName: nextSmsState.templateName,
|
|
794
|
+
content: nextSmsState.content,
|
|
795
|
+
unicodeValidity: nextSmsState.unicodeValidity,
|
|
796
|
+
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
797
|
+
});
|
|
798
|
+
if (
|
|
799
|
+
isFullMode
|
|
800
|
+
|| lastSmsFallbackHydrationKeyRef.current !== hydrationKey
|
|
801
|
+
) {
|
|
802
|
+
lastSmsFallbackHydrationKeyRef.current = hydrationKey;
|
|
803
|
+
setSmsFallbackData(nextSmsState);
|
|
804
|
+
}
|
|
805
|
+
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
806
|
+
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
807
|
+
setSmsFallbackData(null);
|
|
808
|
+
}
|
|
615
809
|
}
|
|
616
|
-
}, [
|
|
617
|
-
|
|
810
|
+
}, [rcsHydrationDetails, isFullMode]);
|
|
618
811
|
|
|
619
812
|
useEffect(() => {
|
|
620
813
|
if (templateType === contentType.text_message) {
|
|
621
814
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
622
|
-
|
|
623
|
-
|
|
815
|
+
// Full-mode create only: switching to plain text clears draft title/media. Never clear when
|
|
816
|
+
// hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
|
|
624
817
|
if (!isEditFlow && isFullMode) {
|
|
818
|
+
setTemplateTitle('');
|
|
819
|
+
setTemplateTitleError('');
|
|
625
820
|
setUpdateRcsImageSrc('');
|
|
626
|
-
setUpdateRcsVideoSrc({});
|
|
627
821
|
setRcsVideoSrc({});
|
|
628
822
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
629
823
|
}
|
|
@@ -650,7 +844,8 @@ export const Rcs = (props) => {
|
|
|
650
844
|
if (!showDltContainer) {
|
|
651
845
|
const { type, module } = location.query || {};
|
|
652
846
|
const isEmbedded = type === EMBEDDED;
|
|
653
|
-
|
|
847
|
+
// Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
|
|
848
|
+
const context = isEmbedded ? module : 'outbound';
|
|
654
849
|
const embedded = isEmbedded ? type : FULL;
|
|
655
850
|
const query = {
|
|
656
851
|
layout: SMS,
|
|
@@ -661,9 +856,9 @@ export const Rcs = (props) => {
|
|
|
661
856
|
if (getDefaultTags) {
|
|
662
857
|
query.context = getDefaultTags;
|
|
663
858
|
}
|
|
664
|
-
|
|
859
|
+
fetchTagSchemaIfNewQuery(query);
|
|
665
860
|
}
|
|
666
|
-
}, [showDltContainer]);
|
|
861
|
+
}, [showDltContainer, fetchTagSchemaIfNewQuery]);
|
|
667
862
|
|
|
668
863
|
useEffect(() => {
|
|
669
864
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -686,39 +881,72 @@ export const Rcs = (props) => {
|
|
|
686
881
|
context,
|
|
687
882
|
embedded,
|
|
688
883
|
};
|
|
689
|
-
|
|
884
|
+
if (getDefaultTags) {
|
|
885
|
+
query.context = getDefaultTags;
|
|
886
|
+
}
|
|
887
|
+
fetchTagSchemaIfNewQuery(query);
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
|
|
891
|
+
if (!templateStr || !numericVarName || !tagName) return templateStr;
|
|
892
|
+
const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
|
|
893
|
+
return templateStr.replace(re, `{{${tagName}}}`);
|
|
690
894
|
};
|
|
691
895
|
|
|
692
|
-
const onTagSelect = (data, areaId) => {
|
|
896
|
+
const onTagSelect = (data, areaId, field) => {
|
|
693
897
|
if (!areaId) return;
|
|
694
898
|
const sep = areaId.lastIndexOf('_');
|
|
695
899
|
if (sep === -1) return;
|
|
696
|
-
const
|
|
697
|
-
if (isNaN(
|
|
900
|
+
const slotSuffix = areaId.slice(sep + 1);
|
|
901
|
+
if (slotSuffix === '' || isNaN(Number(slotSuffix))) return;
|
|
698
902
|
const token = areaId.slice(0, sep);
|
|
699
903
|
const variableName = getVarNameFromToken(token);
|
|
700
904
|
if (!variableName) return;
|
|
905
|
+
const isNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(variableName));
|
|
906
|
+
const fieldStr = field === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
907
|
+
const fieldType = field === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
908
|
+
const globalSlotForArea = getGlobalSlotIndexForRcsFieldId(areaId, fieldStr, fieldType);
|
|
909
|
+
|
|
701
910
|
setCardVarMapped((prev) => {
|
|
702
911
|
const base = (prev?.[variableName] ?? '').toString();
|
|
703
912
|
const nextVal = `${base}{{${data}}}`;
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
[variableName]
|
|
707
|
-
|
|
913
|
+
const next = { ...(prev || {}) };
|
|
914
|
+
if (isNumericSlot) {
|
|
915
|
+
delete next[variableName];
|
|
916
|
+
next[data] = nextVal;
|
|
917
|
+
} else {
|
|
918
|
+
next[variableName] = nextVal;
|
|
919
|
+
// resolveCardVarMappedSlotValue prefers numeric slot keys ("1","2",…) over semantic names;
|
|
920
|
+
// hydration may set "1": ''. Use global slot index — suffix is segment index (includes static text), not var ordinal.
|
|
921
|
+
if (globalSlotForArea !== null && globalSlotForArea !== undefined) {
|
|
922
|
+
next[String(globalSlotForArea + 1)] = nextVal;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return next;
|
|
708
926
|
});
|
|
709
|
-
};
|
|
710
|
-
|
|
711
|
-
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
712
|
-
|
|
713
|
-
const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
|
|
714
927
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
928
|
+
if (isNumericSlot && (field === RCS_TAG_AREA_FIELD_TITLE || field === RCS_TAG_AREA_FIELD_DESC)) {
|
|
929
|
+
if (field === RCS_TAG_AREA_FIELD_TITLE) {
|
|
930
|
+
setTemplateTitle((prev) => {
|
|
931
|
+
const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
|
|
932
|
+
if (nextStr === prev) return prev;
|
|
933
|
+
setTemplateTitleError(variableErrorHandling(nextStr));
|
|
934
|
+
return nextStr;
|
|
935
|
+
});
|
|
936
|
+
} else {
|
|
937
|
+
setTemplateDesc((prev) => {
|
|
938
|
+
const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
|
|
939
|
+
if (nextStr === prev) return prev;
|
|
940
|
+
setTemplateDescError(variableErrorHandling(nextStr));
|
|
941
|
+
return nextStr;
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
}
|
|
720
945
|
};
|
|
721
946
|
|
|
947
|
+
const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
|
|
948
|
+
|
|
949
|
+
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
722
950
|
|
|
723
951
|
//removing optout tag for rcs
|
|
724
952
|
const getRcsTags = () => {
|
|
@@ -731,15 +959,14 @@ export const Rcs = (props) => {
|
|
|
731
959
|
};
|
|
732
960
|
// tag Code end
|
|
733
961
|
|
|
734
|
-
const renderLabel = (value,
|
|
735
|
-
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
962
|
+
const renderLabel = (value, desc) => {
|
|
736
963
|
return (
|
|
737
964
|
<>
|
|
738
|
-
<
|
|
965
|
+
<div className="rcs-form-section-heading">
|
|
739
966
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
740
|
-
</
|
|
967
|
+
</div>
|
|
741
968
|
{desc && (
|
|
742
|
-
<CapLabel type="label3"
|
|
969
|
+
<CapLabel type="label3" className="rcs-form-field-caption">
|
|
743
970
|
{formatMessage(messages[desc])}
|
|
744
971
|
</CapLabel>
|
|
745
972
|
)}
|
|
@@ -825,47 +1052,6 @@ export const Rcs = (props) => {
|
|
|
825
1052
|
setTemplateDescError(error);
|
|
826
1053
|
};
|
|
827
1054
|
|
|
828
|
-
|
|
829
|
-
const templateDescErrorHandler = (value) => {
|
|
830
|
-
let errorMessage = false;
|
|
831
|
-
const { isBraceError } = validateTags({
|
|
832
|
-
content: value,
|
|
833
|
-
tagsParam: tags,
|
|
834
|
-
location,
|
|
835
|
-
tagModule: getDefaultTags,
|
|
836
|
-
isFullMode,
|
|
837
|
-
}) || {};
|
|
838
|
-
|
|
839
|
-
const maxLength = templateType === contentType.text_message
|
|
840
|
-
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
841
|
-
: RCS_RICH_CARD_MAX_LENGTH;
|
|
842
|
-
|
|
843
|
-
if (value === '' && isMediaTypeText) {
|
|
844
|
-
errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
845
|
-
} else if (value?.length > maxLength) {
|
|
846
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
if (isBraceError) {
|
|
850
|
-
errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
851
|
-
}
|
|
852
|
-
return errorMessage;
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
const onFallbackMessageChange = ({ target: { value } }) => {
|
|
857
|
-
const error = fallbackMessageErrorHandler(value);
|
|
858
|
-
setFallbackMessage(value);
|
|
859
|
-
setFallbackMessageError(error);
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
const fallbackMessageErrorHandler = (value) => {
|
|
863
|
-
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
864
|
-
return formatMessage(messages.fallbackMsgLenError);
|
|
865
|
-
}
|
|
866
|
-
return false;
|
|
867
|
-
};
|
|
868
|
-
|
|
869
1055
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
870
1056
|
const forbiddenCharactersValidation = (value) => {
|
|
871
1057
|
if (!value) return false;
|
|
@@ -916,39 +1102,12 @@ export const Rcs = (props) => {
|
|
|
916
1102
|
}
|
|
917
1103
|
return false;
|
|
918
1104
|
};
|
|
919
|
-
|
|
920
|
-
const templateHeaderErrorHandler = (value) => {
|
|
921
|
-
let errorMessage = false;
|
|
922
|
-
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
923
|
-
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
924
|
-
} else {
|
|
925
|
-
errorMessage = variableErrorHandling(value);
|
|
926
|
-
}
|
|
927
|
-
return errorMessage;
|
|
928
|
-
};
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
const templateMessageErrorHandler = (value) => {
|
|
932
|
-
let errorMessage = false;
|
|
933
|
-
if (value === '') {
|
|
934
|
-
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
935
|
-
} else if (
|
|
936
|
-
value?.length
|
|
937
|
-
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
938
|
-
) {
|
|
939
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
940
|
-
} else {
|
|
941
|
-
errorMessage = variableErrorHandling(value);
|
|
942
|
-
}
|
|
943
|
-
return errorMessage;
|
|
944
|
-
};
|
|
945
|
-
|
|
946
1105
|
|
|
947
1106
|
const onMessageAddVar = () => {
|
|
948
|
-
onAddVar(
|
|
1107
|
+
onAddVar(templateDesc);
|
|
949
1108
|
};
|
|
950
1109
|
|
|
951
|
-
const onAddVar = (
|
|
1110
|
+
const onAddVar = (messageContent) => {
|
|
952
1111
|
// Always append the next variable at the end, like WhatsApp
|
|
953
1112
|
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
954
1113
|
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
@@ -988,66 +1147,6 @@ const onTitleAddVar = () => {
|
|
|
988
1147
|
setTemplateTitleError(error);
|
|
989
1148
|
};
|
|
990
1149
|
|
|
991
|
-
|
|
992
|
-
const splitTemplateVarString = (str) => {
|
|
993
|
-
if (!str) return [];
|
|
994
|
-
const validVarArr = str.match(rcsVarRegex) || [];
|
|
995
|
-
const templateVarArray = [];
|
|
996
|
-
let content = str;
|
|
997
|
-
while (content?.length !== 0) {
|
|
998
|
-
const index = content.indexOf(validVarArr?.[0]);
|
|
999
|
-
if (index !== -1) {
|
|
1000
|
-
templateVarArray.push(content.substring(0, index));
|
|
1001
|
-
templateVarArray.push(validVarArr?.[0]);
|
|
1002
|
-
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
1003
|
-
validVarArr?.shift();
|
|
1004
|
-
} else {
|
|
1005
|
-
templateVarArray.push(content);
|
|
1006
|
-
break;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
return templateVarArray.filter(Boolean);
|
|
1010
|
-
};
|
|
1011
|
-
|
|
1012
|
-
const textAreaValue = (idValue, type) => {
|
|
1013
|
-
if (idValue >= 0) {
|
|
1014
|
-
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1015
|
-
const templateArr = splitTemplateVarString(templateStr);
|
|
1016
|
-
const token = templateArr?.[idValue] || "";
|
|
1017
|
-
if (token && rcsVarTestRegex.test(token)) {
|
|
1018
|
-
const varName = getVarNameFromToken(token);
|
|
1019
|
-
return (cardVarMapped?.[varName] ?? '').toString();
|
|
1020
|
-
}
|
|
1021
|
-
return "";
|
|
1022
|
-
}
|
|
1023
|
-
return "";
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
|
-
const textAreaValueChange = (e, type) => {
|
|
1027
|
-
const value = e?.target?.value ?? '';
|
|
1028
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1029
|
-
if (!id) return;
|
|
1030
|
-
const sep = id.lastIndexOf('_');
|
|
1031
|
-
if (sep === -1) return;
|
|
1032
|
-
const isInvalidValue = value?.trim() === "";
|
|
1033
|
-
const token = id.slice(0, sep);
|
|
1034
|
-
const variableName = getVarNameFromToken(token);
|
|
1035
|
-
|
|
1036
|
-
if (variableName) {
|
|
1037
|
-
setCardVarMapped((prev) => ({
|
|
1038
|
-
...prev,
|
|
1039
|
-
[variableName]: isInvalidValue ? "" : value,
|
|
1040
|
-
}));
|
|
1041
|
-
}
|
|
1042
|
-
};
|
|
1043
|
-
|
|
1044
|
-
const setTextAreaId = (e, type) => {
|
|
1045
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1046
|
-
if (!id) return;
|
|
1047
|
-
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
1048
|
-
else setDescTextAreaId(id);
|
|
1049
|
-
};
|
|
1050
|
-
|
|
1051
1150
|
const renderButtonComponent = () => {
|
|
1052
1151
|
return (
|
|
1053
1152
|
<>
|
|
@@ -1078,39 +1177,56 @@ const splitTemplateVarString = (str) => {
|
|
|
1078
1177
|
);
|
|
1079
1178
|
};
|
|
1080
1179
|
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1180
|
+
const getRcsValueMap = (fieldTemplateString, fieldType) => {
|
|
1181
|
+
if (!fieldTemplateString) return {};
|
|
1182
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
1183
|
+
const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
1184
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
|
|
1185
|
+
const segmentIdToResolvedValue = {};
|
|
1186
|
+
let varOrdinal = 0;
|
|
1187
|
+
templateSegments.forEach((segmentToken, segmentIndexInField) => {
|
|
1188
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
1189
|
+
const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
|
|
1190
|
+
const varName = getVarNameFromToken(segmentToken);
|
|
1191
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
1192
|
+
varOrdinal += 1;
|
|
1193
|
+
segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
|
|
1194
|
+
cardVarMapped,
|
|
1195
|
+
varName,
|
|
1196
|
+
globalSlot,
|
|
1197
|
+
isEditLike,
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
return segmentIdToResolvedValue;
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
const titleVarSegmentValueMapById = useMemo(
|
|
1205
|
+
() => getRcsValueMap(templateTitle, TITLE_TEXT),
|
|
1206
|
+
[templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1207
|
+
);
|
|
1208
|
+
const descriptionVarSegmentValueMapById = useMemo(
|
|
1209
|
+
() => getRcsValueMap(templateDesc, MESSAGE_TEXT),
|
|
1210
|
+
[templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
const handleRcsVarChange = (id, value, type) => {
|
|
1214
|
+
const sep = id.lastIndexOf('_');
|
|
1215
|
+
if (sep === -1) return;
|
|
1216
|
+
const token = id.slice(0, sep);
|
|
1217
|
+
const variableName = getVarNameFromToken(token);
|
|
1218
|
+
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
1219
|
+
const isInvalidValue = value?.trim() === '';
|
|
1220
|
+
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
1221
|
+
const fieldStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1222
|
+
const globalSlot = getGlobalSlotIndexForRcsFieldId(id, fieldStr, type);
|
|
1223
|
+
setCardVarMapped((previousVarMap) => {
|
|
1224
|
+
const nextVarMap = { ...previousVarMap, [variableName]: coercedSlotValue };
|
|
1225
|
+
if (globalSlot !== null && globalSlot !== undefined) {
|
|
1226
|
+
nextVarMap[String(globalSlot + 1)] = coercedSlotValue;
|
|
1227
|
+
}
|
|
1228
|
+
return nextVarMap;
|
|
1229
|
+
});
|
|
1114
1230
|
};
|
|
1115
1231
|
|
|
1116
1232
|
const renderTextComponent = () => {
|
|
@@ -1154,10 +1270,19 @@ const splitTemplateVarString = (str) => {
|
|
|
1154
1270
|
</>
|
|
1155
1271
|
}
|
|
1156
1272
|
/>
|
|
1157
|
-
<div className="rcs_text_area_wrapper">
|
|
1158
1273
|
{(isEditFlow || !isFullMode) ? (
|
|
1159
|
-
|
|
1274
|
+
<VarSegmentMessageEditor
|
|
1275
|
+
key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
1276
|
+
templateString={templateTitle}
|
|
1277
|
+
valueMap={titleVarSegmentValueMapById}
|
|
1278
|
+
onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
|
|
1279
|
+
onFocus={(id) => setTitleTextAreaId(id)}
|
|
1280
|
+
varRegex={rcsVarRegex}
|
|
1281
|
+
placeholderPrefix=""
|
|
1282
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1283
|
+
/>
|
|
1160
1284
|
) : (
|
|
1285
|
+
<div className="rcs_text_area_wrapper">
|
|
1161
1286
|
<CapInput
|
|
1162
1287
|
className={`rcs-template-title-input ${
|
|
1163
1288
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -1170,8 +1295,8 @@ const splitTemplateVarString = (str) => {
|
|
|
1170
1295
|
errorMessage={templateTitleError}
|
|
1171
1296
|
disabled={isEditFlow || !isFullMode}
|
|
1172
1297
|
/>
|
|
1298
|
+
</div>
|
|
1173
1299
|
)}
|
|
1174
|
-
</div>
|
|
1175
1300
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
1176
1301
|
<CapError className="rcs-template-title-error">
|
|
1177
1302
|
{templateTitleError}
|
|
@@ -1219,7 +1344,18 @@ const splitTemplateVarString = (str) => {
|
|
|
1219
1344
|
<CapRow className="rcs-create-template-message-input">
|
|
1220
1345
|
<div className="rcs_text_area_wrapper">
|
|
1221
1346
|
{(isEditFlow || !isFullMode)
|
|
1222
|
-
?
|
|
1347
|
+
? (
|
|
1348
|
+
<VarSegmentMessageEditor
|
|
1349
|
+
key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
1350
|
+
templateString={templateDesc}
|
|
1351
|
+
valueMap={descriptionVarSegmentValueMapById}
|
|
1352
|
+
onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
|
|
1353
|
+
onFocus={(id) => setDescTextAreaId(id)}
|
|
1354
|
+
varRegex={rcsVarRegex}
|
|
1355
|
+
placeholderPrefix=""
|
|
1356
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1357
|
+
/>
|
|
1358
|
+
)
|
|
1223
1359
|
: (
|
|
1224
1360
|
<>
|
|
1225
1361
|
<TextArea
|
|
@@ -1263,7 +1399,9 @@ const splitTemplateVarString = (str) => {
|
|
|
1263
1399
|
{templateDescError}
|
|
1264
1400
|
</CapError>
|
|
1265
1401
|
)}
|
|
1266
|
-
{
|
|
1402
|
+
{(isEditFlow || !isFullMode)
|
|
1403
|
+
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
1404
|
+
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1267
1405
|
{!isFullMode && hasTag() && (
|
|
1268
1406
|
<CapAlert
|
|
1269
1407
|
message={
|
|
@@ -1283,18 +1421,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1283
1421
|
);
|
|
1284
1422
|
};
|
|
1285
1423
|
|
|
1286
|
-
|
|
1287
|
-
const fallbackSmsLength = () => (
|
|
1288
|
-
<CapLabel type="label1" className="fallback-sms-length">
|
|
1289
|
-
{formatMessage(messages.totalCharacters, {
|
|
1290
|
-
smsCount: Math.ceil(
|
|
1291
|
-
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1292
|
-
),
|
|
1293
|
-
number: fallbackMessage?.length,
|
|
1294
|
-
})}
|
|
1295
|
-
</CapLabel>
|
|
1296
|
-
);
|
|
1297
|
-
|
|
1298
1424
|
// Get character count for title (rich card only)
|
|
1299
1425
|
const getTitleCharacterCount = () => {
|
|
1300
1426
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -1354,7 +1480,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1354
1480
|
const hasTag = () => {
|
|
1355
1481
|
// Check cardVarMapped values for tags
|
|
1356
1482
|
if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
|
|
1357
|
-
const hasTagInMapped = Object.values(cardVarMapped).some(value =>
|
|
1483
|
+
const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
|
|
1358
1484
|
isTagIncluded(value)
|
|
1359
1485
|
);
|
|
1360
1486
|
if (hasTagInMapped) return true;
|
|
@@ -1372,14 +1498,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1372
1498
|
return false;
|
|
1373
1499
|
};
|
|
1374
1500
|
|
|
1375
|
-
//adding creative dlt fallback sms handlers
|
|
1376
|
-
const addDltMsgHandler = () => {
|
|
1377
|
-
setShowDltContainer(true);
|
|
1378
|
-
setDltMode(RCS_DLT_MODE.TEMPLATES);
|
|
1379
|
-
setDltEditData({});
|
|
1380
|
-
setFallbackMessage('');
|
|
1381
|
-
};
|
|
1382
|
-
|
|
1383
1501
|
const closeDltContainerHandler = () => {
|
|
1384
1502
|
setShowDltContainer(false);
|
|
1385
1503
|
setDltMode('');
|
|
@@ -1402,68 +1520,17 @@ const splitTemplateVarString = (str) => {
|
|
|
1402
1520
|
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
1403
1521
|
'',
|
|
1404
1522
|
);
|
|
1523
|
+
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
1524
|
+
|| get(tempData, 'versions.base.name', '')
|
|
1525
|
+
|| '';
|
|
1405
1526
|
closeDltContainerHandler();
|
|
1406
1527
|
setDltEditData(tempData);
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
closeDltContainerHandler();
|
|
1414
|
-
setDltEditData({});
|
|
1415
|
-
setFallbackMessage('');
|
|
1416
|
-
setFallbackMessageError(false);
|
|
1417
|
-
setShowDltCard(false);
|
|
1418
|
-
};
|
|
1419
|
-
|
|
1420
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
1421
|
-
const {
|
|
1422
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
1423
|
-
'sms-editor': smsEditor = '',
|
|
1424
|
-
} = data.versions.base || {};
|
|
1425
|
-
setFallbackPreviewmode(true);
|
|
1426
|
-
setDltPreviewData(
|
|
1427
|
-
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1428
|
-
);
|
|
1429
|
-
};
|
|
1430
|
-
|
|
1431
|
-
const getDltContentCardList = (content, channel) => {
|
|
1432
|
-
const extra = [
|
|
1433
|
-
<CapIcon
|
|
1434
|
-
type="edit"
|
|
1435
|
-
style={{ marginRight: '8px' }}
|
|
1436
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1437
|
-
/>,
|
|
1438
|
-
<CapDropdown
|
|
1439
|
-
overlay={(
|
|
1440
|
-
<CapMenu>
|
|
1441
|
-
<>
|
|
1442
|
-
<CapMenu.Item
|
|
1443
|
-
className="ant-dropdown-menu-item"
|
|
1444
|
-
onClick={() => setFallbackPreviewmode(true)}
|
|
1445
|
-
>
|
|
1446
|
-
{formatMessage(globalMessages.preview)}
|
|
1447
|
-
</CapMenu.Item>
|
|
1448
|
-
<CapMenu.Item
|
|
1449
|
-
className="ant-dropdown-menu-item"
|
|
1450
|
-
onClick={rcsDltCardDeleteHandler}
|
|
1451
|
-
>
|
|
1452
|
-
{formatMessage(globalMessages.delete)}
|
|
1453
|
-
</CapMenu.Item>
|
|
1454
|
-
</>
|
|
1455
|
-
</CapMenu>
|
|
1456
|
-
)}
|
|
1457
|
-
>
|
|
1458
|
-
<CapIcon type="more" />
|
|
1459
|
-
</CapDropdown>,
|
|
1460
|
-
];
|
|
1461
|
-
return {
|
|
1462
|
-
title: channel,
|
|
1463
|
-
content,
|
|
1464
|
-
cardType: channel,
|
|
1465
|
-
extra,
|
|
1466
|
-
};
|
|
1528
|
+
const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
|
|
1529
|
+
setSmsFallbackData({
|
|
1530
|
+
templateName: templateNameFromDlt,
|
|
1531
|
+
content: fallMsg,
|
|
1532
|
+
...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
|
|
1533
|
+
});
|
|
1467
1534
|
};
|
|
1468
1535
|
|
|
1469
1536
|
const getDltSlideBoxContent = () => {
|
|
@@ -1490,7 +1557,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1490
1557
|
isFullMode={isFullMode}
|
|
1491
1558
|
isDltFromRcs
|
|
1492
1559
|
onSelectTemplate={rcsDltEditSelectHandler}
|
|
1493
|
-
handlePeviewTemplate={dltFallbackListingPreviewhandler}
|
|
1494
1560
|
/>
|
|
1495
1561
|
);
|
|
1496
1562
|
} else if (dltMode === RCS_DLT_MODE.EDIT) {
|
|
@@ -1511,147 +1577,32 @@ const splitTemplateVarString = (str) => {
|
|
|
1511
1577
|
return { dltHeader, dltContent };
|
|
1512
1578
|
};
|
|
1513
1579
|
|
|
1514
|
-
const renderFallBackSmsComponent = () =>
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1539
|
-
// }}
|
|
1540
|
-
// title={formatMessage(messages.fallbackToolTip)}
|
|
1541
|
-
// />
|
|
1542
|
-
// </CapRow>
|
|
1543
|
-
// )}
|
|
1544
|
-
// description={formatMessage(messages.fallbackDesc)}
|
|
1545
|
-
// suffix={
|
|
1546
|
-
// isDltEnabled ? null : (
|
|
1547
|
-
// <CapButton
|
|
1548
|
-
// type="flat"
|
|
1549
|
-
// className="fallback-preview-btn"
|
|
1550
|
-
// prefix={<CapIcon type="eye" />}
|
|
1551
|
-
// style={{ color: CAP_SECONDARY.base }}
|
|
1552
|
-
// onClick={() => setFallbackPreviewmode(true)}
|
|
1553
|
-
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1554
|
-
// >
|
|
1555
|
-
// {formatMessage(globalMessages.preview)}
|
|
1556
|
-
// </CapButton>
|
|
1557
|
-
// )
|
|
1558
|
-
// }
|
|
1559
|
-
// />
|
|
1560
|
-
// </CapRow>,
|
|
1561
|
-
// );
|
|
1562
|
-
|
|
1563
|
-
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1564
|
-
// showAddCreativeBtnForDlt
|
|
1565
|
-
// && contentArr.push(
|
|
1566
|
-
// <CapCard className="rcs-dlt-fallback-card">
|
|
1567
|
-
// <CapRow type="flex" justify="center" align="middle">
|
|
1568
|
-
// <CapColumn span={10}>
|
|
1569
|
-
// <CapImage src={addCreativesIcon} />
|
|
1570
|
-
// </CapColumn>
|
|
1571
|
-
// <CapColumn span={14}>
|
|
1572
|
-
// <CapButton
|
|
1573
|
-
// className="add-dlt-btn"
|
|
1574
|
-
// type="secondary"
|
|
1575
|
-
// onClick={addDltMsgHandler}
|
|
1576
|
-
// >
|
|
1577
|
-
// {formatMessage(messages.addSmsCreative)}
|
|
1578
|
-
// </CapButton>
|
|
1579
|
-
// </CapColumn>
|
|
1580
|
-
// </CapRow>
|
|
1581
|
-
// </CapCard>,
|
|
1582
|
-
// );
|
|
1583
|
-
|
|
1584
|
-
// //dlt is enabled and dlt content is added, show it in a card
|
|
1585
|
-
// showCardForDlt
|
|
1586
|
-
// && contentArr.push(
|
|
1587
|
-
// <CapCustomCardList
|
|
1588
|
-
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1589
|
-
// className="rcs-dlt-card"
|
|
1590
|
-
// />,
|
|
1591
|
-
// fallbackMessageError && (
|
|
1592
|
-
// <CapError className="rcs-fallback-len-error">
|
|
1593
|
-
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1594
|
-
// </CapError>
|
|
1595
|
-
// ),
|
|
1596
|
-
// );
|
|
1597
|
-
|
|
1598
|
-
// //dlt is not enabled, show non dlt text area
|
|
1599
|
-
// showNonDltFallbackComp
|
|
1600
|
-
// && contentArr.push(
|
|
1601
|
-
// <>
|
|
1602
|
-
// <CapRow>
|
|
1603
|
-
// <CapHeader
|
|
1604
|
-
// title={(
|
|
1605
|
-
// <CapHeading type="h4">
|
|
1606
|
-
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1607
|
-
// </CapHeading>
|
|
1608
|
-
// )}
|
|
1609
|
-
// suffix={(
|
|
1610
|
-
// <TagList
|
|
1611
|
-
// label={formatMessage(globalMessages.addLabels)}
|
|
1612
|
-
// onTagSelect={onTagSelectFallback}
|
|
1613
|
-
// location={location}
|
|
1614
|
-
// tags={tags || []}
|
|
1615
|
-
// onContextChange={handleOnTagsContextChange}
|
|
1616
|
-
// injectedTags={injectedTags || {}}
|
|
1617
|
-
// selectedOfferDetails={selectedOfferDetails}
|
|
1618
|
-
// />
|
|
1619
|
-
// )}
|
|
1620
|
-
// />
|
|
1621
|
-
// </CapRow>
|
|
1622
|
-
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1623
|
-
// <TextArea
|
|
1624
|
-
// id="rcs_fallback_message_textarea"
|
|
1625
|
-
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1626
|
-
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1627
|
-
// onChange={onFallbackMessageChange}
|
|
1628
|
-
// errorMessage={fallbackMessageError}
|
|
1629
|
-
// value={fallbackMessage || ""}
|
|
1630
|
-
// />
|
|
1631
|
-
// {!aiContentBotDisabled && (
|
|
1632
|
-
// <CapAskAira.ContentGenerationBot
|
|
1633
|
-
// text={fallbackMessage || ""}
|
|
1634
|
-
// setText={(text) => {
|
|
1635
|
-
// onFallbackMessageChange({ target: { value: text } });
|
|
1636
|
-
// }}
|
|
1637
|
-
// iconPlacement="float-br"
|
|
1638
|
-
// rootStyle={{
|
|
1639
|
-
// bottom: "0.5rem",
|
|
1640
|
-
// right: "0.5rem",
|
|
1641
|
-
// position: "absolute",
|
|
1642
|
-
// }}
|
|
1643
|
-
// />
|
|
1644
|
-
// )}
|
|
1645
|
-
// </div>
|
|
1646
|
-
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1647
|
-
// </>
|
|
1648
|
-
// );
|
|
1649
|
-
|
|
1650
|
-
// return <>{contentArr}</>;
|
|
1651
|
-
};
|
|
1580
|
+
const renderFallBackSmsComponent = () => (
|
|
1581
|
+
<SmsFallback
|
|
1582
|
+
value={smsFallbackData}
|
|
1583
|
+
onChange={setSmsFallbackData}
|
|
1584
|
+
parentLocation={location}
|
|
1585
|
+
smsRegister={smsRegister}
|
|
1586
|
+
isFullMode={isFullMode}
|
|
1587
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1588
|
+
channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
|
|
1589
|
+
sectionTitle={
|
|
1590
|
+
smsFallbackData
|
|
1591
|
+
? formatMessage(messages.fallbackLabel)
|
|
1592
|
+
: formatMessage(messages.smsFallbackOptional)
|
|
1593
|
+
}
|
|
1594
|
+
templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
|
|
1595
|
+
templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
|
|
1596
|
+
/* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
|
|
1597
|
+
showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
|
|
1598
|
+
disableSelectTemplate={isEditFlow}
|
|
1599
|
+
eventContextTags={eventContextTags}
|
|
1600
|
+
onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
|
|
1601
|
+
isRcsEditFlow={isEditFlow}
|
|
1602
|
+
/>
|
|
1603
|
+
);
|
|
1652
1604
|
|
|
1653
1605
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
1654
|
-
setImageError(null);
|
|
1655
1606
|
const isRcsThumbnail = index === 1;
|
|
1656
1607
|
actions.uploadRcsAsset(file, type, {
|
|
1657
1608
|
isRcsThumbnail,
|
|
@@ -1663,7 +1614,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1663
1614
|
|
|
1664
1615
|
const setUpdateRcsImageSrc = useCallback(
|
|
1665
1616
|
(val) => {
|
|
1666
|
-
setAssetListImage(val);
|
|
1667
1617
|
updateRcsImageSrc(val);
|
|
1668
1618
|
actions.clearRcsMediaAsset(0);
|
|
1669
1619
|
},
|
|
@@ -1693,7 +1643,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1693
1643
|
|
|
1694
1644
|
|
|
1695
1645
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1696
|
-
setImageError(null);
|
|
1697
1646
|
actions.uploadRcsAsset(file, type, {
|
|
1698
1647
|
...fileParams,
|
|
1699
1648
|
type: 'video',
|
|
@@ -1702,9 +1651,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1702
1651
|
});
|
|
1703
1652
|
};
|
|
1704
1653
|
|
|
1705
|
-
const updateRcsVideoSrc = (val) => {
|
|
1706
|
-
setRcsVideoSrc(val);
|
|
1707
|
-
};
|
|
1708
1654
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
1709
1655
|
setRcsVideoSrc(val);
|
|
1710
1656
|
setAssetList(val);
|
|
@@ -1733,7 +1679,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1733
1679
|
<>
|
|
1734
1680
|
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
1735
1681
|
<CapImageUpload
|
|
1736
|
-
|
|
1682
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1737
1683
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1738
1684
|
imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
|
|
1739
1685
|
imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
|
|
@@ -1745,7 +1691,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1745
1691
|
updateOnReUpload={updateOnRcsThumbnailReUpload}
|
|
1746
1692
|
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
1747
1693
|
index={1}
|
|
1748
|
-
className="cap-custom-image-upload"
|
|
1749
1694
|
key={`rcs-uploaded-image-${currentDimension}`}
|
|
1750
1695
|
imageData={thumbnailData}
|
|
1751
1696
|
channel={RCS}
|
|
@@ -1776,7 +1721,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1776
1721
|
value: dim.type,
|
|
1777
1722
|
label: `${dim.label}`
|
|
1778
1723
|
}))}
|
|
1779
|
-
|
|
1724
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1780
1725
|
/>
|
|
1781
1726
|
</>
|
|
1782
1727
|
)}
|
|
@@ -1790,7 +1735,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1790
1735
|
</div>
|
|
1791
1736
|
) : (
|
|
1792
1737
|
<CapImageUpload
|
|
1793
|
-
style={{ paddingTop: '20px' }}
|
|
1794
1738
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1795
1739
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
1796
1740
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -1801,7 +1745,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1801
1745
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
1802
1746
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
1803
1747
|
index={0}
|
|
1804
|
-
className="cap-custom-image-upload"
|
|
1748
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1805
1749
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
1806
1750
|
imageData={rcsData}
|
|
1807
1751
|
channel={RCS}
|
|
@@ -1831,7 +1775,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1831
1775
|
value: dim.type,
|
|
1832
1776
|
label: `${dim.label}`
|
|
1833
1777
|
}))}
|
|
1834
|
-
|
|
1778
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1835
1779
|
/>
|
|
1836
1780
|
)}
|
|
1837
1781
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -1891,8 +1835,16 @@ const splitTemplateVarString = (str) => {
|
|
|
1891
1835
|
const getRcsPreview = () => {
|
|
1892
1836
|
|
|
1893
1837
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
1894
|
-
const
|
|
1895
|
-
const
|
|
1838
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1839
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
1840
|
+
? 0
|
|
1841
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
1842
|
+
const resolvedTitle = isMediaTypeText
|
|
1843
|
+
? ''
|
|
1844
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
1845
|
+
const resolvedDesc = isSlotMappingMode
|
|
1846
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
1847
|
+
: templateDesc;
|
|
1896
1848
|
return (
|
|
1897
1849
|
<UnifiedPreview
|
|
1898
1850
|
channel={RCS}
|
|
@@ -1915,51 +1867,65 @@ const splitTemplateVarString = (str) => {
|
|
|
1915
1867
|
);
|
|
1916
1868
|
};
|
|
1917
1869
|
|
|
1918
|
-
const getUnmappedDesc = (str, mapping) => {
|
|
1919
|
-
if (!str) return '';
|
|
1920
|
-
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1921
|
-
let result = str;
|
|
1922
|
-
const replacements = [];
|
|
1923
|
-
Object.entries(mapping).forEach(([key, value]) => {
|
|
1924
|
-
const raw = (value ?? '').toString();
|
|
1925
|
-
if (!raw || raw?.trim?.() === '') return;
|
|
1926
|
-
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1927
|
-
replacements.push({ key, needle: raw });
|
|
1928
|
-
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1929
|
-
});
|
|
1930
|
-
const seen = new Set();
|
|
1931
|
-
const uniq = replacements
|
|
1932
|
-
.filter(({ key, needle }) => {
|
|
1933
|
-
const id = `${key}::${needle}`;
|
|
1934
|
-
if (seen.has(id)) return false;
|
|
1935
|
-
seen.add(id);
|
|
1936
|
-
return true;
|
|
1937
|
-
})
|
|
1938
|
-
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1939
|
-
|
|
1940
|
-
uniq.forEach(({ key, needle }) => {
|
|
1941
|
-
if (!needle) return;
|
|
1942
|
-
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1943
|
-
const regex = new RegExp(escaped, 'g');
|
|
1944
|
-
result = result.replace(regex, `{{${key}}}`);
|
|
1945
|
-
});
|
|
1946
|
-
return result;
|
|
1947
|
-
};
|
|
1948
|
-
|
|
1949
1870
|
const createPayload = () => {
|
|
1950
|
-
const
|
|
1951
|
-
const {
|
|
1952
|
-
template_id: templateId = '',
|
|
1953
|
-
template_name = '',
|
|
1954
|
-
'sms-editor': template = '',
|
|
1955
|
-
header: registeredSenderIds = [],
|
|
1956
|
-
} = base;
|
|
1957
|
-
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1958
|
-
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
1871
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1959
1872
|
const alignment = isMediaTypeImage
|
|
1960
1873
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
1961
1874
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
1962
1875
|
|
|
1876
|
+
const heightTypeForCardWidth = isMediaTypeText
|
|
1877
|
+
? undefined
|
|
1878
|
+
: isMediaTypeImage
|
|
1879
|
+
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
|
|
1880
|
+
: isMediaTypeVideo
|
|
1881
|
+
? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
|
|
1882
|
+
: undefined;
|
|
1883
|
+
const cardWidthFromSelection =
|
|
1884
|
+
heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
|
|
1885
|
+
|
|
1886
|
+
/** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
|
|
1887
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
1888
|
+
const smsFallbackMerged = !isFullMode
|
|
1889
|
+
? (() => {
|
|
1890
|
+
const local =
|
|
1891
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
1892
|
+
return {
|
|
1893
|
+
...smsFromApiShape,
|
|
1894
|
+
...local,
|
|
1895
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
1896
|
+
};
|
|
1897
|
+
})()
|
|
1898
|
+
: (smsFallbackData || {});
|
|
1899
|
+
const smsFallbackForPayload = (() => {
|
|
1900
|
+
if (isFullMode) {
|
|
1901
|
+
return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
|
|
1902
|
+
}
|
|
1903
|
+
const mapped = {
|
|
1904
|
+
templateName:
|
|
1905
|
+
smsFallbackMerged.templateName
|
|
1906
|
+
|| smsFallbackMerged.smsTemplateName
|
|
1907
|
+
|| '',
|
|
1908
|
+
// Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
|
|
1909
|
+
content:
|
|
1910
|
+
smsFallbackMerged.content
|
|
1911
|
+
|| smsFallbackMerged.smsContent
|
|
1912
|
+
|| smsFallbackMerged.smsTemplateContent
|
|
1913
|
+
|| smsFallbackMerged.message
|
|
1914
|
+
|| '',
|
|
1915
|
+
templateContent:
|
|
1916
|
+
pickFirstSmsFallbackTemplateString(smsFallbackMerged)
|
|
1917
|
+
|| '',
|
|
1918
|
+
...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
|
|
1919
|
+
&& { unicodeValidity: smsFallbackMerged.unicodeValidity }),
|
|
1920
|
+
...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
1921
|
+
&& Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
1922
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
1923
|
+
smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
1924
|
+
}),
|
|
1925
|
+
};
|
|
1926
|
+
return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
|
|
1927
|
+
})();
|
|
1928
|
+
|
|
1963
1929
|
const payload = {
|
|
1964
1930
|
name: templateName,
|
|
1965
1931
|
versions: {
|
|
@@ -1972,12 +1938,14 @@ const splitTemplateVarString = (str) => {
|
|
|
1972
1938
|
cardSettings: {
|
|
1973
1939
|
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
1974
1940
|
...(alignment && { mediaAlignment: alignment }),
|
|
1975
|
-
cardWidth:
|
|
1941
|
+
cardWidth: cardWidthFromSelection,
|
|
1976
1942
|
},
|
|
1977
1943
|
cardContent: [
|
|
1978
1944
|
{
|
|
1979
|
-
|
|
1980
|
-
|
|
1945
|
+
// Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
|
|
1946
|
+
// / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
|
|
1947
|
+
title: templateTitle,
|
|
1948
|
+
description: templateDesc,
|
|
1981
1949
|
mediaType: templateMediaType,
|
|
1982
1950
|
...(!isMediaTypeText && {media: {
|
|
1983
1951
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -1986,23 +1954,30 @@ const splitTemplateVarString = (str) => {
|
|
|
1986
1954
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
1987
1955
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
1988
1956
|
}}),
|
|
1989
|
-
...(
|
|
1990
|
-
const
|
|
1991
|
-
...(templateTitle
|
|
1992
|
-
...(templateDesc
|
|
1957
|
+
...(isSlotMappingMode && (() => {
|
|
1958
|
+
const templateVarTokens = [
|
|
1959
|
+
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
1960
|
+
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
1993
1961
|
];
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
1962
|
+
const persistedSlotVarMap = {};
|
|
1963
|
+
const seenSemanticVarNames = new Set();
|
|
1964
|
+
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
1965
|
+
const varName = getVarNameFromToken(token);
|
|
1966
|
+
if (!varName) return;
|
|
1967
|
+
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
1968
|
+
cardVarMapped,
|
|
1969
|
+
varName,
|
|
1970
|
+
slotIndexZeroBased,
|
|
1971
|
+
isSlotMappingMode,
|
|
1972
|
+
);
|
|
1973
|
+
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
1974
|
+
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
1975
|
+
if (!seenSemanticVarNames.has(varName)) {
|
|
1976
|
+
seenSemanticVarNames.add(varName);
|
|
1977
|
+
persistedSlotVarMap[varName] = sanitizedSlotValue;
|
|
2003
1978
|
}
|
|
2004
1979
|
});
|
|
2005
|
-
return { cardVarMapped:
|
|
1980
|
+
return { cardVarMapped: persistedSlotVarMap };
|
|
2006
1981
|
})()),
|
|
2007
1982
|
...(suggestions.length > 0 && { suggestions }),
|
|
2008
1983
|
}
|
|
@@ -2010,17 +1985,29 @@ const splitTemplateVarString = (str) => {
|
|
|
2010
1985
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
2011
1986
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
2012
1987
|
},
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
1988
|
+
...(smsFallbackForPayload && (() => {
|
|
1989
|
+
const smsBodyText =
|
|
1990
|
+
smsFallbackForPayload.content
|
|
1991
|
+
|| smsFallbackForPayload.templateContent
|
|
1992
|
+
|| smsFallbackForPayload.message
|
|
1993
|
+
|| smsFallbackForPayload.smsContent
|
|
1994
|
+
|| '';
|
|
1995
|
+
return {
|
|
1996
|
+
smsFallBackContent: {
|
|
1997
|
+
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
1998
|
+
smsContent: smsBodyText,
|
|
1999
|
+
// cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
|
|
2000
|
+
message: smsBodyText,
|
|
2001
|
+
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
2002
|
+
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
2003
|
+
}),
|
|
2004
|
+
...(smsFallbackForPayload.rcsSmsFallbackVarMapped
|
|
2005
|
+
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
2006
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
2007
|
+
}),
|
|
2021
2008
|
},
|
|
2022
|
-
}
|
|
2023
|
-
},
|
|
2009
|
+
};
|
|
2010
|
+
})()),
|
|
2024
2011
|
},
|
|
2025
2012
|
},
|
|
2026
2013
|
},
|
|
@@ -2030,6 +2017,84 @@ const splitTemplateVarString = (str) => {
|
|
|
2030
2017
|
return payload;
|
|
2031
2018
|
};
|
|
2032
2019
|
|
|
2020
|
+
/** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
|
|
2021
|
+
const testPreviewFormData = useMemo(() => {
|
|
2022
|
+
const payload = createPayload();
|
|
2023
|
+
const rcs = payload?.versions?.base?.content?.RCS;
|
|
2024
|
+
if (!rcs) return null;
|
|
2025
|
+
// createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
|
|
2026
|
+
const accountIdForCreateMessageMeta =
|
|
2027
|
+
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
2028
|
+
? String(wecrmAccountId)
|
|
2029
|
+
: accountId;
|
|
2030
|
+
const rcsForTest = {
|
|
2031
|
+
...rcs,
|
|
2032
|
+
rcsContent: {
|
|
2033
|
+
...rcs.rcsContent,
|
|
2034
|
+
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
2035
|
+
},
|
|
2036
|
+
};
|
|
2037
|
+
const out = {
|
|
2038
|
+
versions: {
|
|
2039
|
+
base: {
|
|
2040
|
+
content: {
|
|
2041
|
+
RCS: rcsForTest,
|
|
2042
|
+
},
|
|
2043
|
+
},
|
|
2044
|
+
},
|
|
2045
|
+
};
|
|
2046
|
+
const fb = smsFallbackData;
|
|
2047
|
+
if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
|
|
2048
|
+
out.templateConfigs = {
|
|
2049
|
+
templateId: fb.smsTemplateId || '',
|
|
2050
|
+
template: fb.templateContent || fb.content || '',
|
|
2051
|
+
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
2052
|
+
registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
return out;
|
|
2056
|
+
}, [
|
|
2057
|
+
templateName,
|
|
2058
|
+
templateTitle,
|
|
2059
|
+
templateDesc,
|
|
2060
|
+
templateMediaType,
|
|
2061
|
+
cardVarMapped,
|
|
2062
|
+
suggestions,
|
|
2063
|
+
rcsImageSrc,
|
|
2064
|
+
rcsVideoSrc,
|
|
2065
|
+
rcsThumbnailSrc,
|
|
2066
|
+
selectedDimension,
|
|
2067
|
+
smsFallbackData,
|
|
2068
|
+
isFullMode,
|
|
2069
|
+
isEditFlow,
|
|
2070
|
+
templateType,
|
|
2071
|
+
accountId,
|
|
2072
|
+
wecrmAccountId,
|
|
2073
|
+
accessToken,
|
|
2074
|
+
accountName,
|
|
2075
|
+
hostName,
|
|
2076
|
+
smsRegister,
|
|
2077
|
+
]);
|
|
2078
|
+
|
|
2079
|
+
/**
|
|
2080
|
+
* Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
|
|
2081
|
+
* with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
|
|
2082
|
+
* miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
|
|
2083
|
+
*/
|
|
2084
|
+
const librarySmsFallbackMergedForValidation = useMemo(() => {
|
|
2085
|
+
if (isFullMode) {
|
|
2086
|
+
return smsFallbackData;
|
|
2087
|
+
}
|
|
2088
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
2089
|
+
const local =
|
|
2090
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
2091
|
+
return {
|
|
2092
|
+
...smsFromApiShape,
|
|
2093
|
+
...local,
|
|
2094
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
2095
|
+
};
|
|
2096
|
+
}, [isFullMode, templateData, smsFallbackData]);
|
|
2097
|
+
|
|
2033
2098
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
2034
2099
|
// eslint-disable-next-line no-undef
|
|
2035
2100
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -2059,6 +2124,9 @@ const splitTemplateVarString = (str) => {
|
|
|
2059
2124
|
_id: params?.id,
|
|
2060
2125
|
validity: true,
|
|
2061
2126
|
type: RCS,
|
|
2127
|
+
// CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
|
|
2128
|
+
// the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
|
|
2129
|
+
closeSlideBoxAfterSubmit: !isFullMode,
|
|
2062
2130
|
};
|
|
2063
2131
|
getFormData(formDataParams);
|
|
2064
2132
|
};
|
|
@@ -2072,6 +2140,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2072
2140
|
actionCallback({ resp, errorMessage });
|
|
2073
2141
|
setSpin(false); // Always turn off spinner
|
|
2074
2142
|
if (!errorMessage) {
|
|
2143
|
+
setTemplateStatus(RCS_STATUSES.pending);
|
|
2075
2144
|
onCreateComplete();
|
|
2076
2145
|
}
|
|
2077
2146
|
});
|
|
@@ -2083,6 +2152,64 @@ const splitTemplateVarString = (str) => {
|
|
|
2083
2152
|
}
|
|
2084
2153
|
};
|
|
2085
2154
|
|
|
2155
|
+
/** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
|
|
2156
|
+
const smsFallbackBlocksDone = () => {
|
|
2157
|
+
// Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
|
|
2158
|
+
if (
|
|
2159
|
+
!isFullMode
|
|
2160
|
+
&& !isTraiDLTEnable(isFullMode, smsRegister)
|
|
2161
|
+
&& smsFallbackData == null
|
|
2162
|
+
&& hasMeaningfulSmsFallbackShape(
|
|
2163
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
2164
|
+
)
|
|
2165
|
+
) {
|
|
2166
|
+
return true;
|
|
2167
|
+
}
|
|
2168
|
+
if (!smsFallbackData) return false;
|
|
2169
|
+
// Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
|
|
2170
|
+
// concern, not a structural requirement for approval — the registered SMS template body stands on
|
|
2171
|
+
// its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
|
|
2172
|
+
if (isFullMode) return false;
|
|
2173
|
+
const merged = librarySmsFallbackMergedForValidation;
|
|
2174
|
+
const templateText = pickFirstSmsFallbackTemplateString(merged);
|
|
2175
|
+
if (!templateText) {
|
|
2176
|
+
return true;
|
|
2177
|
+
}
|
|
2178
|
+
const rawVarMap =
|
|
2179
|
+
merged.rcsSmsFallbackVarMapped
|
|
2180
|
+
|| merged['rcs-sms-fallback-var-mapped'];
|
|
2181
|
+
const varMap =
|
|
2182
|
+
rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
|
|
2183
|
+
return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
|
|
2184
|
+
};
|
|
2185
|
+
|
|
2186
|
+
/**
|
|
2187
|
+
* Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
|
|
2188
|
+
* semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
|
|
2189
|
+
* / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
|
|
2190
|
+
*/
|
|
2191
|
+
const isLibraryCampaignCardVarMappingIncomplete = () => {
|
|
2192
|
+
if (isFullMode) return false;
|
|
2193
|
+
const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
|
|
2194
|
+
rcsVarTestRegex.test(elem),
|
|
2195
|
+
);
|
|
2196
|
+
const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
|
|
2197
|
+
rcsVarTestRegex.test(elem),
|
|
2198
|
+
);
|
|
2199
|
+
const orderedVarNames = [
|
|
2200
|
+
...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
2201
|
+
...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
2202
|
+
];
|
|
2203
|
+
if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
|
|
2204
|
+
return true;
|
|
2205
|
+
}
|
|
2206
|
+
return orderedVarNames.some((name, globalIdx) => {
|
|
2207
|
+
const v = resolveCardVarMappedSlotValue(cardVarMapped, name, globalIdx, true);
|
|
2208
|
+
const s = v == null ? '' : String(v);
|
|
2209
|
+
return s.trim() === '';
|
|
2210
|
+
});
|
|
2211
|
+
};
|
|
2212
|
+
|
|
2086
2213
|
const isDisableDone = () => {
|
|
2087
2214
|
if(isEditFlow){
|
|
2088
2215
|
return false;
|
|
@@ -2093,40 +2220,16 @@ const splitTemplateVarString = (str) => {
|
|
|
2093
2220
|
}
|
|
2094
2221
|
}
|
|
2095
2222
|
|
|
2096
|
-
if(
|
|
2097
|
-
|
|
2098
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2099
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2100
|
-
|
|
2101
|
-
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2102
|
-
return true;
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
const hasEmptyMapping =
|
|
2106
|
-
cardVarMapped &&
|
|
2107
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2108
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2109
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2110
|
-
return v.trim() === ''; // empty string
|
|
2111
|
-
});
|
|
2112
|
-
|
|
2113
|
-
if (hasEmptyMapping) {
|
|
2114
|
-
return true;
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2117
|
-
const anyMissing = allVars.some(name => {
|
|
2118
|
-
const v = cardVarMapped?.[name];
|
|
2119
|
-
if (typeof v !== 'string') return !v;
|
|
2120
|
-
return v.trim() === '';
|
|
2121
|
-
});
|
|
2122
|
-
if (anyMissing) {
|
|
2123
|
-
return true;
|
|
2124
|
-
}
|
|
2223
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2224
|
+
return true;
|
|
2125
2225
|
}
|
|
2126
2226
|
|
|
2127
|
-
|
|
2227
|
+
if (smsFallbackBlocksDone()) {
|
|
2128
2228
|
return true;
|
|
2229
|
+
}
|
|
2129
2230
|
|
|
2231
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2232
|
+
return true;
|
|
2130
2233
|
}
|
|
2131
2234
|
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
2132
2235
|
return true;
|
|
@@ -2144,53 +2247,36 @@ const splitTemplateVarString = (str) => {
|
|
|
2144
2247
|
return true;
|
|
2145
2248
|
}
|
|
2146
2249
|
}
|
|
2147
|
-
if (templateDescError || templateTitleError
|
|
2250
|
+
if (templateDescError || templateTitleError) {
|
|
2251
|
+
return true;
|
|
2252
|
+
}
|
|
2253
|
+
if (
|
|
2254
|
+
smsFallbackData?.content
|
|
2255
|
+
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2256
|
+
) {
|
|
2148
2257
|
return true;
|
|
2149
2258
|
}
|
|
2150
2259
|
return false;
|
|
2151
2260
|
};
|
|
2152
2261
|
|
|
2153
2262
|
const isEditDisableDone = () => {
|
|
2154
|
-
|
|
2155
2263
|
if (templateStatus !== RCS_STATUSES.approved) {
|
|
2156
2264
|
return true;
|
|
2157
2265
|
}
|
|
2158
2266
|
|
|
2159
|
-
if (!isFullMode) {
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2267
|
+
// if (!isFullMode) {
|
|
2268
|
+
// if (templateName.trim() === '' || templateNameError) {
|
|
2269
|
+
// return true;
|
|
2270
|
+
// }
|
|
2271
|
+
// }
|
|
2272
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2273
|
+
return true;
|
|
2163
2274
|
}
|
|
2164
|
-
if(!isFullMode){
|
|
2165
|
-
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2166
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2167
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2168
2275
|
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
const hasEmptyMapping =
|
|
2174
|
-
cardVarMapped &&
|
|
2175
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2176
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2177
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2178
|
-
return v.trim() === ''; // empty string
|
|
2179
|
-
});
|
|
2180
|
-
|
|
2181
|
-
if (hasEmptyMapping) {
|
|
2182
|
-
return true;
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
const anyMissing = allVars.some(name => {
|
|
2186
|
-
const v = cardVarMapped?.[name];
|
|
2187
|
-
if (typeof v !== 'string') return !v;
|
|
2188
|
-
return v.trim() === '';
|
|
2189
|
-
});
|
|
2190
|
-
if (anyMissing) {
|
|
2191
|
-
return true;
|
|
2192
|
-
}
|
|
2276
|
+
if (smsFallbackBlocksDone()) {
|
|
2277
|
+
return true;
|
|
2193
2278
|
}
|
|
2279
|
+
|
|
2194
2280
|
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2195
2281
|
return true;
|
|
2196
2282
|
}
|
|
@@ -2209,7 +2295,13 @@ const splitTemplateVarString = (str) => {
|
|
|
2209
2295
|
return true;
|
|
2210
2296
|
}
|
|
2211
2297
|
}
|
|
2212
|
-
if (templateTitleError || templateDescError
|
|
2298
|
+
if (templateTitleError || templateDescError) {
|
|
2299
|
+
return true;
|
|
2300
|
+
}
|
|
2301
|
+
if (
|
|
2302
|
+
smsFallbackData?.content
|
|
2303
|
+
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2304
|
+
) {
|
|
2213
2305
|
return true;
|
|
2214
2306
|
}
|
|
2215
2307
|
return false;
|
|
@@ -2259,52 +2351,56 @@ const splitTemplateVarString = (str) => {
|
|
|
2259
2351
|
};
|
|
2260
2352
|
|
|
2261
2353
|
const getMainContent = () => {
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
return (
|
|
2266
|
-
<CapSlideBox
|
|
2267
|
-
show={showDltContainer}
|
|
2268
|
-
header={dltHeader}
|
|
2269
|
-
content={dltContent}
|
|
2270
|
-
handleClose={closeDltContainerHandler}
|
|
2271
|
-
size="size-xl"
|
|
2272
|
-
/>
|
|
2273
|
-
);
|
|
2274
|
-
}
|
|
2354
|
+
// Slideboxes are rendered outside the page-level spinner to avoid
|
|
2355
|
+
// stacking/blur issues during initial loads.
|
|
2356
|
+
if (showDltContainer) return null;
|
|
2275
2357
|
|
|
2276
2358
|
return (
|
|
2277
2359
|
<>
|
|
2278
|
-
{templateStatus !== '' && (
|
|
2279
|
-
<
|
|
2280
|
-
{
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2360
|
+
{templateStatus !== '' && (
|
|
2361
|
+
<CapRow className="template-status-container">
|
|
2362
|
+
<CapColumn span={14}>
|
|
2363
|
+
<CapLabel type="label2">
|
|
2364
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
2365
|
+
</CapLabel>
|
|
2366
|
+
|
|
2367
|
+
{templateStatus && (
|
|
2368
|
+
<CapAlert
|
|
2369
|
+
message={getTemplateStatusMessage()}
|
|
2370
|
+
type={getTemplateStatusType(templateStatus)}
|
|
2371
|
+
/>
|
|
2372
|
+
)}
|
|
2373
|
+
</CapColumn>
|
|
2374
|
+
</CapRow>
|
|
2290
2375
|
)}
|
|
2291
|
-
<CapRow className=
|
|
2376
|
+
<CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
|
|
2292
2377
|
<CapColumn span={14}>
|
|
2293
2378
|
{/* template name */}
|
|
2294
2379
|
{isFullMode && (
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2380
|
+
isEditFlow ? (
|
|
2381
|
+
<div className="rcs-creative-name-readonly">
|
|
2382
|
+
<CapHeading type="h4">
|
|
2383
|
+
{formatMessage(globalMessages.creativeNameLabel)}
|
|
2384
|
+
</CapHeading>
|
|
2385
|
+
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
2386
|
+
{templateName || '-'}
|
|
2387
|
+
</CapHeading>
|
|
2388
|
+
</div>
|
|
2389
|
+
) : (
|
|
2390
|
+
<CapInput
|
|
2391
|
+
id="rcs_template_name_input"
|
|
2392
|
+
data-testid="template_name"
|
|
2393
|
+
onChange={onTemplateNameChange}
|
|
2394
|
+
errorMessage={templateNameError}
|
|
2395
|
+
placeholder={formatMessage(
|
|
2396
|
+
globalMessages.templateNamePlaceholder,
|
|
2397
|
+
)}
|
|
2398
|
+
value={templateName || ''}
|
|
2399
|
+
size="default"
|
|
2400
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
2401
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2402
|
+
/>
|
|
2403
|
+
)
|
|
2308
2404
|
)}
|
|
2309
2405
|
{renderLabel('templateTypeLabel')}
|
|
2310
2406
|
<CapRadioGroup
|
|
@@ -2332,7 +2428,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2332
2428
|
</>
|
|
2333
2429
|
)}
|
|
2334
2430
|
{renderTextComponent()}
|
|
2335
|
-
<CapDivider
|
|
2431
|
+
<CapDivider className="rcs-fallback-section-divider" />
|
|
2336
2432
|
{renderFallBackSmsComponent()}
|
|
2337
2433
|
<div className="rcs-scroll-div" />
|
|
2338
2434
|
</CapColumn>
|
|
@@ -2344,7 +2440,8 @@ const splitTemplateVarString = (str) => {
|
|
|
2344
2440
|
|
|
2345
2441
|
|
|
2346
2442
|
<div className="rcs-footer">
|
|
2347
|
-
{!
|
|
2443
|
+
{/* Full-mode create only: send-for-approval + disabled test/preview. Library mode uses Done below — onDoneCallback() is undefined when !isFullMode, so do not render this row (avoids a no-op primary button). */}
|
|
2444
|
+
{!isEditFlow && isFullMode && (
|
|
2348
2445
|
<>
|
|
2349
2446
|
<div className="button-disabled-tooltip-wrapper">
|
|
2350
2447
|
<CapButton
|
|
@@ -2365,7 +2462,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2365
2462
|
className="rcs-test-preview-btn"
|
|
2366
2463
|
type="secondary"
|
|
2367
2464
|
disabled={true}
|
|
2368
|
-
style={{ marginLeft: "8px" }}
|
|
2369
2465
|
>
|
|
2370
2466
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
2371
2467
|
</CapButton>
|
|
@@ -2398,51 +2494,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2398
2494
|
</>
|
|
2399
2495
|
)}
|
|
2400
2496
|
</div>
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
{fallbackPreviewmode && (
|
|
2404
|
-
<CapSlideBox
|
|
2405
|
-
className="rcs-fallback-preview"
|
|
2406
|
-
show={fallbackPreviewmode}
|
|
2407
|
-
header={(
|
|
2408
|
-
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2409
|
-
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2410
|
-
</CapHeading>
|
|
2411
|
-
)}
|
|
2412
|
-
content={(
|
|
2413
|
-
<>
|
|
2414
|
-
<UnifiedPreview
|
|
2415
|
-
channel={RCS}
|
|
2416
|
-
content={{
|
|
2417
|
-
rcsPreviewContent: {
|
|
2418
|
-
rcsDesc: tempMsg,
|
|
2419
|
-
},
|
|
2420
|
-
}}
|
|
2421
|
-
device={ANDROID}
|
|
2422
|
-
showDeviceToggle={false}
|
|
2423
|
-
showHeader={false}
|
|
2424
|
-
formatMessage={formatMessage}
|
|
2425
|
-
/>
|
|
2426
|
-
<CapHeading
|
|
2427
|
-
type="h3"
|
|
2428
|
-
style={{ textAlign: 'center' }}
|
|
2429
|
-
className="margin-t-16"
|
|
2430
|
-
>
|
|
2431
|
-
{formatMessage(messages.totalCharacters, {
|
|
2432
|
-
smsCount: Math.ceil(
|
|
2433
|
-
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2434
|
-
),
|
|
2435
|
-
number: tempMsg.length,
|
|
2436
|
-
})}
|
|
2437
|
-
</CapHeading>
|
|
2438
|
-
</>
|
|
2439
|
-
)}
|
|
2440
|
-
handleClose={() => {
|
|
2441
|
-
setFallbackPreviewmode(false);
|
|
2442
|
-
setDltPreviewData('');
|
|
2443
|
-
}}
|
|
2444
|
-
/>
|
|
2445
|
-
)}
|
|
2446
2497
|
</>
|
|
2447
2498
|
);
|
|
2448
2499
|
};
|
|
@@ -2451,12 +2502,38 @@ const splitTemplateVarString = (str) => {
|
|
|
2451
2502
|
<CapSpin spinning={loadingTags || spin}>
|
|
2452
2503
|
{getMainContent()}
|
|
2453
2504
|
</CapSpin>
|
|
2505
|
+
|
|
2506
|
+
{showDltContainer && (() => {
|
|
2507
|
+
const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
|
|
2508
|
+
return (
|
|
2509
|
+
<CapSlideBox
|
|
2510
|
+
show={showDltContainer}
|
|
2511
|
+
header={dltHeader}
|
|
2512
|
+
content={dltContent}
|
|
2513
|
+
handleClose={closeDltContainerHandler}
|
|
2514
|
+
size="size-xl"
|
|
2515
|
+
/>
|
|
2516
|
+
);
|
|
2517
|
+
})()}
|
|
2518
|
+
|
|
2454
2519
|
<TestAndPreviewSlidebox
|
|
2455
2520
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
2456
2521
|
onClose={handleCloseTestAndPreview}
|
|
2457
|
-
formData={
|
|
2458
|
-
content={
|
|
2522
|
+
formData={testPreviewFormData}
|
|
2523
|
+
content={testAndPreviewContent}
|
|
2459
2524
|
currentChannel={RCS}
|
|
2525
|
+
orgUnitId={orgUnitId}
|
|
2526
|
+
smsFallbackContent={
|
|
2527
|
+
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
2528
|
+
? {
|
|
2529
|
+
templateContent:
|
|
2530
|
+
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
2531
|
+
templateName: smsFallbackData.templateName || '',
|
|
2532
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackData?.rcsSmsFallbackVarMapped ?? {},
|
|
2533
|
+
}
|
|
2534
|
+
: null
|
|
2535
|
+
}
|
|
2536
|
+
smsRegister={smsRegister}
|
|
2460
2537
|
/>
|
|
2461
2538
|
</>
|
|
2462
2539
|
);
|