@capillarytech/creatives-library 8.0.316-alpha.3 → 8.0.316-alpha.4
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 +172 -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 +245 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +100 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +19 -1
- 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 +327 -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 +286 -93
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
- 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/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 +110 -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/WebPush/Create/components/MessageSection.js +54 -18
- package/v2Containers/WebPush/Create/components/MessageSection.test.js +28 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +7 -3
- package/v2Containers/Whatsapp/index.js +7 -23
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
- package/v2Containers/Whatsapp/tests/index.test.js +172 -0
|
@@ -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('');
|
|
@@ -304,14 +260,23 @@ export const Rcs = (props) => {
|
|
|
304
260
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
305
261
|
if (!isEmpty(accountObj)) {
|
|
306
262
|
const {
|
|
263
|
+
id: wecrmId,
|
|
307
264
|
sourceAccountIdentifier = '',
|
|
308
265
|
configs = {},
|
|
309
266
|
} = accountObj;
|
|
310
|
-
|
|
311
267
|
setAccountId(sourceAccountIdentifier);
|
|
268
|
+
setWecrmAccountId(
|
|
269
|
+
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
270
|
+
);
|
|
312
271
|
setAccessToken(configs.accessToken || '');
|
|
313
272
|
setHostName(accountObj.hostName || '');
|
|
314
273
|
setAccountName(accountObj.name || '');
|
|
274
|
+
} else {
|
|
275
|
+
setAccountId('');
|
|
276
|
+
setWecrmAccountId('');
|
|
277
|
+
setAccessToken('');
|
|
278
|
+
setHostName('');
|
|
279
|
+
setAccountName('');
|
|
315
280
|
}
|
|
316
281
|
}, [accountData.selectedRcsAccount]);
|
|
317
282
|
|
|
@@ -367,7 +332,9 @@ export const Rcs = (props) => {
|
|
|
367
332
|
if (isFullMode) return;
|
|
368
333
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
369
334
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
370
|
-
const
|
|
335
|
+
const slotOffset =
|
|
336
|
+
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
337
|
+
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
371
338
|
if (!resolved) {
|
|
372
339
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
373
340
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -412,13 +379,41 @@ export const Rcs = (props) => {
|
|
|
412
379
|
|
|
413
380
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
414
381
|
|
|
415
|
-
const
|
|
382
|
+
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
383
|
+
|
|
384
|
+
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
385
|
+
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
386
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
387
|
+
const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
388
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
389
|
+
let varOrdinal = 0;
|
|
390
|
+
for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
|
|
391
|
+
const segmentToken = templateSegments[segmentIndexInField];
|
|
392
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
393
|
+
if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
|
|
394
|
+
return offset + varOrdinal;
|
|
395
|
+
}
|
|
396
|
+
varOrdinal += 1;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
416
403
|
if (!str) return '';
|
|
417
|
-
const arr =
|
|
404
|
+
const arr = splitTemplateVarStringRcs(str);
|
|
405
|
+
let varOrdinal = 0;
|
|
418
406
|
return arr.map((elem) => {
|
|
419
407
|
if (rcsVarTestRegex.test(elem)) {
|
|
420
408
|
const key = getVarNameFromToken(elem);
|
|
421
|
-
const
|
|
409
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
410
|
+
varOrdinal += 1;
|
|
411
|
+
const v = resolveCardVarMappedSlotValue(
|
|
412
|
+
cardVarMapped,
|
|
413
|
+
key,
|
|
414
|
+
globalSlot,
|
|
415
|
+
isEditLike,
|
|
416
|
+
);
|
|
422
417
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
423
418
|
return String(v);
|
|
424
419
|
}
|
|
@@ -426,107 +421,154 @@ export const Rcs = (props) => {
|
|
|
426
421
|
}).join('');
|
|
427
422
|
};
|
|
428
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
|
|
426
|
+
* (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
|
|
427
|
+
* TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
|
|
428
|
+
*/
|
|
429
|
+
const getTemplateContent = useCallback(() => {
|
|
430
|
+
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
431
|
+
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
432
|
+
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
429
433
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
434
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
435
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
436
|
+
? 0
|
|
437
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
438
|
+
const resolvedTitle = isMediaTypeText
|
|
439
|
+
? ''
|
|
440
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
441
|
+
const resolvedDesc = isSlotMappingMode
|
|
442
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
443
|
+
: templateDesc;
|
|
444
|
+
|
|
445
|
+
const mediaPreview = {};
|
|
446
|
+
if (isMediaTypeImage && rcsImageSrc) {
|
|
447
|
+
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
448
|
+
}
|
|
449
|
+
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
450
|
+
if (rcsThumbnailSrc) {
|
|
451
|
+
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
452
|
+
} else if (rcsVideoSrc?.videoSrc) {
|
|
453
|
+
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
454
|
+
}
|
|
455
|
+
if (rcsThumbnailSrc) {
|
|
456
|
+
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const contentObj = {
|
|
461
|
+
templateHeader: resolvedTitle,
|
|
462
|
+
templateMessage: resolvedDesc,
|
|
463
|
+
...mediaPreview,
|
|
464
|
+
...(suggestions.length > 0 && {
|
|
465
|
+
suggestions: suggestions,
|
|
466
|
+
}),
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
return contentObj;
|
|
470
|
+
}, [
|
|
471
|
+
templateMediaType,
|
|
472
|
+
templateTitle,
|
|
473
|
+
templateDesc,
|
|
474
|
+
rcsImageSrc,
|
|
475
|
+
rcsVideoSrc,
|
|
476
|
+
rcsThumbnailSrc,
|
|
477
|
+
suggestions,
|
|
478
|
+
selectedDimension,
|
|
479
|
+
isFullMode,
|
|
480
|
+
isEditFlow,
|
|
481
|
+
cardVarMapped,
|
|
482
|
+
]);
|
|
450
483
|
|
|
484
|
+
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
451
485
|
|
|
452
|
-
const RcsLabel = styled.div`
|
|
453
|
-
display: flex;
|
|
454
|
-
margin-top: 20px;
|
|
455
|
-
`;
|
|
456
486
|
const paramObj = params || {};
|
|
457
487
|
useEffect(() => {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}, [paramObj.id]);
|
|
488
|
+
const { id } = paramObj;
|
|
489
|
+
if (id && isFullMode) {
|
|
490
|
+
setSpin(true);
|
|
491
|
+
actions.getTemplateDetails(id, setSpin);
|
|
492
|
+
}
|
|
493
|
+
return () => {
|
|
494
|
+
actions.clearEditResponse();
|
|
495
|
+
};
|
|
496
|
+
}, [paramObj.id, isFullMode]);
|
|
467
497
|
|
|
468
498
|
useEffect(() => {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
499
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
500
|
+
|
|
501
|
+
const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
|
|
502
|
+
|
|
503
|
+
const initField = (targetString, setVarMap, slotOffset) => {
|
|
504
|
+
const arr = splitTemplateVarStringRcs(targetString);
|
|
505
|
+
if (!arr?.length) {
|
|
506
|
+
setVarMap({});
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const nextVarMap = {};
|
|
510
|
+
let varOrdinal = 0;
|
|
511
|
+
arr.forEach((elem, idx) => {
|
|
512
|
+
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
513
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
514
|
+
const id = `${elem}_${idx}`;
|
|
515
|
+
const varName = getVarNameFromToken(elem);
|
|
516
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
517
|
+
varOrdinal += 1;
|
|
518
|
+
const mappedValue = resolveCardVarMappedSlotValue(
|
|
519
|
+
cardVarMapped,
|
|
520
|
+
varName,
|
|
521
|
+
globalSlot,
|
|
522
|
+
isEditLike,
|
|
523
|
+
);
|
|
524
|
+
nextVarMap[id] = mappedValue;
|
|
477
525
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
482
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
483
|
-
const id = `${elem}_${idx}`;
|
|
484
|
-
const varName = getVarNameFromToken(elem);
|
|
485
|
-
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
486
|
-
nextVarMap[id] = mappedValue;
|
|
487
|
-
if (mappedValue !== '') {
|
|
488
|
-
nextUpdated[idx] = mappedValue;
|
|
489
|
-
} else {
|
|
490
|
-
nextUpdated[idx] = elem;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
setVarMap(nextVarMap);
|
|
495
|
-
setUpdated(nextUpdated);
|
|
496
|
-
};
|
|
526
|
+
});
|
|
527
|
+
setVarMap(nextVarMap);
|
|
528
|
+
};
|
|
497
529
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
530
|
+
initField(templateTitle, setTitleVarMappedData, 0);
|
|
531
|
+
initField(templateDesc, setDescVarMappedData, titleTokenCount);
|
|
532
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
533
|
+
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
if (!isEditFlow && isFullMode) {
|
|
504
536
|
setRcsVideoSrc({});
|
|
505
537
|
updateRcsImageSrc('');
|
|
506
538
|
setUpdateRcsImageSrc('');
|
|
507
539
|
updateRcsThumbnailSrc('');
|
|
508
540
|
setAssetList({});
|
|
509
|
-
|
|
510
|
-
|
|
541
|
+
}
|
|
542
|
+
}, [templateMediaType]);
|
|
511
543
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
544
|
+
/** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
|
|
545
|
+
const templateStatusHelper = (cardContentFirst) => {
|
|
546
|
+
const raw =
|
|
547
|
+
cardContentFirst?.Status
|
|
548
|
+
?? cardContentFirst?.status
|
|
549
|
+
?? cardContentFirst?.approvalStatus
|
|
550
|
+
?? '';
|
|
551
|
+
const status = typeof raw === 'string' ? raw.trim() : String(raw);
|
|
552
|
+
const n = status.toLowerCase();
|
|
553
|
+
switch (n) {
|
|
554
|
+
case 'approved':
|
|
516
555
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
517
556
|
break;
|
|
518
|
-
case
|
|
557
|
+
case 'pending':
|
|
519
558
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
520
559
|
break;
|
|
521
|
-
case
|
|
560
|
+
case 'awaitingapproval':
|
|
522
561
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
523
562
|
break;
|
|
524
|
-
case
|
|
563
|
+
case 'unavailable':
|
|
525
564
|
setTemplateStatus(RCS_STATUSES.unavailable);
|
|
526
565
|
break;
|
|
527
|
-
case
|
|
566
|
+
case 'rejected':
|
|
528
567
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
529
568
|
break;
|
|
569
|
+
case 'created':
|
|
570
|
+
setTemplateStatus(RCS_STATUSES.created);
|
|
571
|
+
break;
|
|
530
572
|
default:
|
|
531
573
|
setTemplateStatus(status);
|
|
532
574
|
break;
|
|
@@ -537,7 +579,6 @@ export const Rcs = (props) => {
|
|
|
537
579
|
if (mediaType) {
|
|
538
580
|
setTemplateMediaType(mediaType);
|
|
539
581
|
}
|
|
540
|
-
const tempOrientation = cardSettings.cardOrientation;
|
|
541
582
|
const tempAlignment = cardSettings.mediaAlignment;
|
|
542
583
|
const tempHeight = mediaData.height;
|
|
543
584
|
|
|
@@ -580,44 +621,197 @@ export const Rcs = (props) => {
|
|
|
580
621
|
};
|
|
581
622
|
|
|
582
623
|
useEffect(() => {
|
|
583
|
-
const details =
|
|
624
|
+
const details = rcsHydrationDetails;
|
|
584
625
|
if (details && Object.keys(details).length > 0) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
626
|
+
// Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
|
|
627
|
+
const cardFromVersions = get(
|
|
628
|
+
details,
|
|
629
|
+
'versions.base.content.RCS.rcsContent.cardContent[0]',
|
|
630
|
+
);
|
|
631
|
+
const cardFromTop = get(details, 'rcsContent.cardContent[0]');
|
|
632
|
+
const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
|
|
633
|
+
const cardVarMappedFromCardContent =
|
|
634
|
+
card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
|
|
635
|
+
? card0.cardVarMapped
|
|
636
|
+
: {};
|
|
637
|
+
const cardVarMappedFromRootMirror =
|
|
638
|
+
details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
|
|
639
|
+
? details.rcsCardVarMapped
|
|
640
|
+
: {};
|
|
641
|
+
// Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
|
|
642
|
+
// nested versions.cardContent[0].cardVarMapped is dropped on reload.
|
|
643
|
+
const mergedCardVarMappedFromPayload = {
|
|
644
|
+
...cardVarMappedFromRootMirror,
|
|
645
|
+
...cardVarMappedFromCardContent,
|
|
646
|
+
};
|
|
647
|
+
const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
|
|
648
|
+
const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
|
|
649
|
+
const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
|
|
650
|
+
Object.keys(mergedCardVarMappedFromPayload)
|
|
651
|
+
.sort()
|
|
652
|
+
.reduce((accumulator, mapKey) => {
|
|
653
|
+
accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
|
|
654
|
+
return accumulator;
|
|
655
|
+
}, {}),
|
|
656
|
+
)}`;
|
|
657
|
+
if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
|
|
658
|
+
lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
|
|
659
|
+
setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
|
|
588
660
|
}
|
|
589
|
-
const
|
|
661
|
+
const tokenListForMap = [
|
|
662
|
+
...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
|
|
663
|
+
...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
|
|
664
|
+
];
|
|
665
|
+
const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
666
|
+
// Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
|
|
667
|
+
// getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
|
|
668
|
+
const cardVarMappedBeforeCoalesce = isFullMode
|
|
669
|
+
? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
|
|
670
|
+
: { ...mergedCardVarMappedFromPayload };
|
|
671
|
+
const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
672
|
+
cardVarMappedBeforeCoalesce,
|
|
673
|
+
loadedTitleForMap,
|
|
674
|
+
loadedDescForMap,
|
|
675
|
+
rcsVarRegex,
|
|
676
|
+
);
|
|
677
|
+
const cardVarMappedAfterNumericSlotSync = !isFullMode
|
|
678
|
+
? syncCardVarMappedSemanticsFromSlots(
|
|
679
|
+
cardVarMappedAfterCoalesce,
|
|
680
|
+
loadedTitleForMap,
|
|
681
|
+
loadedDescForMap,
|
|
682
|
+
rcsVarRegex,
|
|
683
|
+
)
|
|
684
|
+
: cardVarMappedAfterCoalesce;
|
|
685
|
+
const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
|
|
686
|
+
// Pre-populate variable/tag mappings while opening an existing template in edit flows
|
|
687
|
+
setCardVarMapped((previousVarMapState) => {
|
|
688
|
+
const previousVarMap = previousVarMapState ?? {};
|
|
689
|
+
if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
|
|
690
|
+
const previousVarMapKeys = Object.keys(previousVarMap);
|
|
691
|
+
const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
|
|
692
|
+
if (previousVarMapKeys.length === nextVarMapKeys.length) {
|
|
693
|
+
const allSlotValuesMatchPrevious = previousVarMapKeys.every(
|
|
694
|
+
(key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
|
|
695
|
+
);
|
|
696
|
+
if (allSlotValuesMatchPrevious) return previousVarMapState;
|
|
697
|
+
}
|
|
698
|
+
return hydratedCardVarMappedResult;
|
|
699
|
+
});
|
|
700
|
+
const mediaType =
|
|
701
|
+
card0.mediaType
|
|
702
|
+
|| get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
590
703
|
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
591
704
|
setTemplateType(contentType.text_message);
|
|
592
705
|
} else {
|
|
593
706
|
setTemplateType(contentType.rich_card);
|
|
594
707
|
}
|
|
595
708
|
setEditFlow(true);
|
|
596
|
-
setTemplateName(details
|
|
597
|
-
const loadedTitle =
|
|
598
|
-
const loadedDesc =
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
709
|
+
setTemplateName(details?.name || details?.creativeName || '');
|
|
710
|
+
const loadedTitle = loadedTitleForMap;
|
|
711
|
+
const loadedDesc = loadedDescForMap;
|
|
712
|
+
const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
|
|
713
|
+
loadedTitle,
|
|
714
|
+
loadedDesc,
|
|
715
|
+
isFullMode,
|
|
716
|
+
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
717
|
+
rcsVarRegex,
|
|
718
|
+
});
|
|
602
719
|
setTemplateTitle(normalizedTitle);
|
|
603
720
|
setTemplateDesc(normalizedDesc);
|
|
604
|
-
setSuggestions(
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
721
|
+
setSuggestions(
|
|
722
|
+
Array.isArray(card0.suggestions)
|
|
723
|
+
? card0.suggestions
|
|
724
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
|
|
725
|
+
);
|
|
726
|
+
const cardForStatus = {
|
|
727
|
+
...card0,
|
|
728
|
+
Status:
|
|
729
|
+
card0.Status
|
|
730
|
+
?? card0.status
|
|
731
|
+
?? card0.approvalStatus
|
|
732
|
+
?? get(details, 'templateStatus')
|
|
733
|
+
?? get(details, 'approvalStatus')
|
|
734
|
+
?? get(details, 'creativeStatus')
|
|
735
|
+
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
736
|
+
?? '',
|
|
737
|
+
};
|
|
738
|
+
templateStatusHelper(cardForStatus);
|
|
739
|
+
const mediaData =
|
|
740
|
+
card0.media != null && card0.media !== ''
|
|
741
|
+
? card0.media
|
|
742
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
743
|
+
const cardSettings =
|
|
744
|
+
get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
|
|
745
|
+
|| get(details, 'rcsContent.cardSettings', '');
|
|
608
746
|
setMediaData(mediaData, mediaType, cardSettings);
|
|
747
|
+
|
|
748
|
+
const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
749
|
+
const base = get(smsFallbackContent, 'versions.base', {});
|
|
750
|
+
const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
|
|
751
|
+
const smsEditor = base['sms-editor'];
|
|
752
|
+
const fromNested = Array.isArray(updatedEditor)
|
|
753
|
+
? updatedEditor.join('')
|
|
754
|
+
: (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
|
|
755
|
+
const fallbackMessage = smsFallbackContent.smsContent
|
|
756
|
+
|| smsFallbackContent.smsTemplateContent
|
|
757
|
+
|| smsFallbackContent.message
|
|
758
|
+
|| fromNested
|
|
759
|
+
|| '';
|
|
760
|
+
const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
761
|
+
const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
|
|
762
|
+
const hasFallbackPayload =
|
|
763
|
+
smsFallbackContent
|
|
764
|
+
&& Object.keys(smsFallbackContent).length > 0
|
|
765
|
+
&& (
|
|
766
|
+
!!smsFallbackContent.smsTemplateName
|
|
767
|
+
|| !!fallbackMessage
|
|
768
|
+
|| hasVarMapped
|
|
769
|
+
);
|
|
770
|
+
if (hasFallbackPayload) {
|
|
771
|
+
if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
|
|
772
|
+
console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
|
|
773
|
+
}
|
|
774
|
+
const unicodeFromApi =
|
|
775
|
+
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
776
|
+
? smsFallbackContent.unicodeValidity
|
|
777
|
+
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
778
|
+
const nextSmsState = {
|
|
779
|
+
templateName: smsFallbackContent.smsTemplateName || '',
|
|
780
|
+
content: fallbackMessage,
|
|
781
|
+
templateContent: fallbackMessage,
|
|
782
|
+
unicodeValidity: unicodeFromApi,
|
|
783
|
+
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
784
|
+
};
|
|
785
|
+
const hydrationKey = JSON.stringify({
|
|
786
|
+
creativeKey: details._id || details.name || details.creativeName || '',
|
|
787
|
+
templateName: nextSmsState.templateName,
|
|
788
|
+
content: nextSmsState.content,
|
|
789
|
+
unicodeValidity: nextSmsState.unicodeValidity,
|
|
790
|
+
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
791
|
+
});
|
|
792
|
+
if (
|
|
793
|
+
isFullMode
|
|
794
|
+
|| lastSmsFallbackHydrationKeyRef.current !== hydrationKey
|
|
795
|
+
) {
|
|
796
|
+
lastSmsFallbackHydrationKeyRef.current = hydrationKey;
|
|
797
|
+
setSmsFallbackData(nextSmsState);
|
|
798
|
+
}
|
|
799
|
+
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
800
|
+
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
801
|
+
setSmsFallbackData(null);
|
|
802
|
+
}
|
|
609
803
|
}
|
|
610
|
-
}, [
|
|
611
|
-
|
|
804
|
+
}, [rcsHydrationDetails, isFullMode]);
|
|
612
805
|
|
|
613
806
|
useEffect(() => {
|
|
614
807
|
if (templateType === contentType.text_message) {
|
|
615
808
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
616
|
-
|
|
617
|
-
|
|
809
|
+
// Full-mode create only: switching to plain text clears draft title/media. Never clear when
|
|
810
|
+
// hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
|
|
618
811
|
if (!isEditFlow && isFullMode) {
|
|
812
|
+
setTemplateTitle('');
|
|
813
|
+
setTemplateTitleError('');
|
|
619
814
|
setUpdateRcsImageSrc('');
|
|
620
|
-
setUpdateRcsVideoSrc({});
|
|
621
815
|
setRcsVideoSrc({});
|
|
622
816
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
623
817
|
}
|
|
@@ -644,7 +838,8 @@ export const Rcs = (props) => {
|
|
|
644
838
|
if (!showDltContainer) {
|
|
645
839
|
const { type, module } = location.query || {};
|
|
646
840
|
const isEmbedded = type === EMBEDDED;
|
|
647
|
-
|
|
841
|
+
// Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
|
|
842
|
+
const context = isEmbedded ? module : 'outbound';
|
|
648
843
|
const embedded = isEmbedded ? type : FULL;
|
|
649
844
|
const query = {
|
|
650
845
|
layout: SMS,
|
|
@@ -655,9 +850,9 @@ export const Rcs = (props) => {
|
|
|
655
850
|
if (getDefaultTags) {
|
|
656
851
|
query.context = getDefaultTags;
|
|
657
852
|
}
|
|
658
|
-
|
|
853
|
+
fetchTagSchemaIfNewQuery(query);
|
|
659
854
|
}
|
|
660
|
-
}, [showDltContainer]);
|
|
855
|
+
}, [showDltContainer, fetchTagSchemaIfNewQuery]);
|
|
661
856
|
|
|
662
857
|
useEffect(() => {
|
|
663
858
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -680,39 +875,72 @@ export const Rcs = (props) => {
|
|
|
680
875
|
context,
|
|
681
876
|
embedded,
|
|
682
877
|
};
|
|
683
|
-
|
|
878
|
+
if (getDefaultTags) {
|
|
879
|
+
query.context = getDefaultTags;
|
|
880
|
+
}
|
|
881
|
+
fetchTagSchemaIfNewQuery(query);
|
|
684
882
|
};
|
|
685
883
|
|
|
686
|
-
const
|
|
884
|
+
const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
|
|
885
|
+
if (!templateStr || !numericVarName || !tagName) return templateStr;
|
|
886
|
+
const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
|
|
887
|
+
return templateStr.replace(re, `{{${tagName}}}`);
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
const onTagSelect = (data, areaId, field) => {
|
|
687
891
|
if (!areaId) return;
|
|
688
892
|
const sep = areaId.lastIndexOf('_');
|
|
689
893
|
if (sep === -1) return;
|
|
690
|
-
const
|
|
691
|
-
if (isNaN(
|
|
894
|
+
const slotSuffix = areaId.slice(sep + 1);
|
|
895
|
+
if (slotSuffix === '' || isNaN(Number(slotSuffix))) return;
|
|
692
896
|
const token = areaId.slice(0, sep);
|
|
693
897
|
const variableName = getVarNameFromToken(token);
|
|
694
898
|
if (!variableName) return;
|
|
899
|
+
const isNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(variableName));
|
|
900
|
+
const fieldStr = field === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
901
|
+
const fieldType = field === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
902
|
+
const globalSlotForArea = getGlobalSlotIndexForRcsFieldId(areaId, fieldStr, fieldType);
|
|
903
|
+
|
|
695
904
|
setCardVarMapped((prev) => {
|
|
696
905
|
const base = (prev?.[variableName] ?? '').toString();
|
|
697
906
|
const nextVal = `${base}{{${data}}}`;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
[variableName]
|
|
701
|
-
|
|
907
|
+
const next = { ...(prev || {}) };
|
|
908
|
+
if (isNumericSlot) {
|
|
909
|
+
delete next[variableName];
|
|
910
|
+
next[data] = nextVal;
|
|
911
|
+
} else {
|
|
912
|
+
next[variableName] = nextVal;
|
|
913
|
+
// resolveCardVarMappedSlotValue prefers numeric slot keys ("1","2",…) over semantic names;
|
|
914
|
+
// hydration may set "1": ''. Use global slot index — suffix is segment index (includes static text), not var ordinal.
|
|
915
|
+
if (globalSlotForArea !== null && globalSlotForArea !== undefined) {
|
|
916
|
+
next[String(globalSlotForArea + 1)] = nextVal;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return next;
|
|
702
920
|
});
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
706
921
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
922
|
+
if (isNumericSlot && (field === RCS_TAG_AREA_FIELD_TITLE || field === RCS_TAG_AREA_FIELD_DESC)) {
|
|
923
|
+
if (field === RCS_TAG_AREA_FIELD_TITLE) {
|
|
924
|
+
setTemplateTitle((prev) => {
|
|
925
|
+
const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
|
|
926
|
+
if (nextStr === prev) return prev;
|
|
927
|
+
setTemplateTitleError(variableErrorHandling(nextStr));
|
|
928
|
+
return nextStr;
|
|
929
|
+
});
|
|
930
|
+
} else {
|
|
931
|
+
setTemplateDesc((prev) => {
|
|
932
|
+
const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
|
|
933
|
+
if (nextStr === prev) return prev;
|
|
934
|
+
setTemplateDescError(variableErrorHandling(nextStr));
|
|
935
|
+
return nextStr;
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
}
|
|
714
939
|
};
|
|
715
940
|
|
|
941
|
+
const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
|
|
942
|
+
|
|
943
|
+
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
716
944
|
|
|
717
945
|
//removing optout tag for rcs
|
|
718
946
|
const getRcsTags = () => {
|
|
@@ -725,15 +953,14 @@ export const Rcs = (props) => {
|
|
|
725
953
|
};
|
|
726
954
|
// tag Code end
|
|
727
955
|
|
|
728
|
-
const renderLabel = (value,
|
|
729
|
-
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
956
|
+
const renderLabel = (value, desc) => {
|
|
730
957
|
return (
|
|
731
958
|
<>
|
|
732
|
-
<
|
|
959
|
+
<div className="rcs-form-section-heading">
|
|
733
960
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
734
|
-
</
|
|
961
|
+
</div>
|
|
735
962
|
{desc && (
|
|
736
|
-
<CapLabel type="label3"
|
|
963
|
+
<CapLabel type="label3" className="rcs-form-field-caption">
|
|
737
964
|
{formatMessage(messages[desc])}
|
|
738
965
|
</CapLabel>
|
|
739
966
|
)}
|
|
@@ -819,47 +1046,6 @@ export const Rcs = (props) => {
|
|
|
819
1046
|
setTemplateDescError(error);
|
|
820
1047
|
};
|
|
821
1048
|
|
|
822
|
-
|
|
823
|
-
const templateDescErrorHandler = (value) => {
|
|
824
|
-
let errorMessage = false;
|
|
825
|
-
const { isBraceError } = validateTags({
|
|
826
|
-
content: value,
|
|
827
|
-
tagsParam: tags,
|
|
828
|
-
location,
|
|
829
|
-
tagModule: getDefaultTags,
|
|
830
|
-
isFullMode,
|
|
831
|
-
}) || {};
|
|
832
|
-
|
|
833
|
-
const maxLength = templateType === contentType.text_message
|
|
834
|
-
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
835
|
-
: RCS_RICH_CARD_MAX_LENGTH;
|
|
836
|
-
|
|
837
|
-
if (value === '' && isMediaTypeText) {
|
|
838
|
-
errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
839
|
-
} else if (value?.length > maxLength) {
|
|
840
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
if (isBraceError) {
|
|
844
|
-
errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
845
|
-
}
|
|
846
|
-
return errorMessage;
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const onFallbackMessageChange = ({ target: { value } }) => {
|
|
851
|
-
const error = fallbackMessageErrorHandler(value);
|
|
852
|
-
setFallbackMessage(value);
|
|
853
|
-
setFallbackMessageError(error);
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
const fallbackMessageErrorHandler = (value) => {
|
|
857
|
-
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
858
|
-
return formatMessage(messages.fallbackMsgLenError);
|
|
859
|
-
}
|
|
860
|
-
return false;
|
|
861
|
-
};
|
|
862
|
-
|
|
863
1049
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
864
1050
|
const forbiddenCharactersValidation = (value) => {
|
|
865
1051
|
if (!value) return false;
|
|
@@ -910,39 +1096,12 @@ export const Rcs = (props) => {
|
|
|
910
1096
|
}
|
|
911
1097
|
return false;
|
|
912
1098
|
};
|
|
913
|
-
|
|
914
|
-
const templateHeaderErrorHandler = (value) => {
|
|
915
|
-
let errorMessage = false;
|
|
916
|
-
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
917
|
-
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
918
|
-
} else {
|
|
919
|
-
errorMessage = variableErrorHandling(value);
|
|
920
|
-
}
|
|
921
|
-
return errorMessage;
|
|
922
|
-
};
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const templateMessageErrorHandler = (value) => {
|
|
926
|
-
let errorMessage = false;
|
|
927
|
-
if (value === '') {
|
|
928
|
-
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
929
|
-
} else if (
|
|
930
|
-
value?.length
|
|
931
|
-
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
932
|
-
) {
|
|
933
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
934
|
-
} else {
|
|
935
|
-
errorMessage = variableErrorHandling(value);
|
|
936
|
-
}
|
|
937
|
-
return errorMessage;
|
|
938
|
-
};
|
|
939
|
-
|
|
940
1099
|
|
|
941
1100
|
const onMessageAddVar = () => {
|
|
942
|
-
onAddVar(
|
|
1101
|
+
onAddVar(templateDesc);
|
|
943
1102
|
};
|
|
944
1103
|
|
|
945
|
-
const onAddVar = (
|
|
1104
|
+
const onAddVar = (messageContent) => {
|
|
946
1105
|
// Always append the next variable at the end, like WhatsApp
|
|
947
1106
|
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
948
1107
|
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
@@ -982,66 +1141,6 @@ const onTitleAddVar = () => {
|
|
|
982
1141
|
setTemplateTitleError(error);
|
|
983
1142
|
};
|
|
984
1143
|
|
|
985
|
-
|
|
986
|
-
const splitTemplateVarString = (str) => {
|
|
987
|
-
if (!str) return [];
|
|
988
|
-
const validVarArr = str.match(rcsVarRegex) || [];
|
|
989
|
-
const templateVarArray = [];
|
|
990
|
-
let content = str;
|
|
991
|
-
while (content?.length !== 0) {
|
|
992
|
-
const index = content.indexOf(validVarArr?.[0]);
|
|
993
|
-
if (index !== -1) {
|
|
994
|
-
templateVarArray.push(content.substring(0, index));
|
|
995
|
-
templateVarArray.push(validVarArr?.[0]);
|
|
996
|
-
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
997
|
-
validVarArr?.shift();
|
|
998
|
-
} else {
|
|
999
|
-
templateVarArray.push(content);
|
|
1000
|
-
break;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
return templateVarArray.filter(Boolean);
|
|
1004
|
-
};
|
|
1005
|
-
|
|
1006
|
-
const textAreaValue = (idValue, type) => {
|
|
1007
|
-
if (idValue >= 0) {
|
|
1008
|
-
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1009
|
-
const templateArr = splitTemplateVarString(templateStr);
|
|
1010
|
-
const token = templateArr?.[idValue] || "";
|
|
1011
|
-
if (token && rcsVarTestRegex.test(token)) {
|
|
1012
|
-
const varName = getVarNameFromToken(token);
|
|
1013
|
-
return (cardVarMapped?.[varName] ?? '').toString();
|
|
1014
|
-
}
|
|
1015
|
-
return "";
|
|
1016
|
-
}
|
|
1017
|
-
return "";
|
|
1018
|
-
};
|
|
1019
|
-
|
|
1020
|
-
const textAreaValueChange = (e, type) => {
|
|
1021
|
-
const value = e?.target?.value ?? '';
|
|
1022
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1023
|
-
if (!id) return;
|
|
1024
|
-
const sep = id.lastIndexOf('_');
|
|
1025
|
-
if (sep === -1) return;
|
|
1026
|
-
const isInvalidValue = value?.trim() === "";
|
|
1027
|
-
const token = id.slice(0, sep);
|
|
1028
|
-
const variableName = getVarNameFromToken(token);
|
|
1029
|
-
|
|
1030
|
-
if (variableName) {
|
|
1031
|
-
setCardVarMapped((prev) => ({
|
|
1032
|
-
...prev,
|
|
1033
|
-
[variableName]: isInvalidValue ? "" : value,
|
|
1034
|
-
}));
|
|
1035
|
-
}
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
const setTextAreaId = (e, type) => {
|
|
1039
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1040
|
-
if (!id) return;
|
|
1041
|
-
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
1042
|
-
else setDescTextAreaId(id);
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
1144
|
const renderButtonComponent = () => {
|
|
1046
1145
|
return (
|
|
1047
1146
|
<>
|
|
@@ -1072,39 +1171,56 @@ const splitTemplateVarString = (str) => {
|
|
|
1072
1171
|
);
|
|
1073
1172
|
};
|
|
1074
1173
|
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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
|
-
|
|
1174
|
+
const getRcsValueMap = (fieldTemplateString, fieldType) => {
|
|
1175
|
+
if (!fieldTemplateString) return {};
|
|
1176
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
1177
|
+
const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
1178
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
|
|
1179
|
+
const segmentIdToResolvedValue = {};
|
|
1180
|
+
let varOrdinal = 0;
|
|
1181
|
+
templateSegments.forEach((segmentToken, segmentIndexInField) => {
|
|
1182
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
1183
|
+
const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
|
|
1184
|
+
const varName = getVarNameFromToken(segmentToken);
|
|
1185
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
1186
|
+
varOrdinal += 1;
|
|
1187
|
+
segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
|
|
1188
|
+
cardVarMapped,
|
|
1189
|
+
varName,
|
|
1190
|
+
globalSlot,
|
|
1191
|
+
isEditLike,
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
return segmentIdToResolvedValue;
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
const titleVarSegmentValueMapById = useMemo(
|
|
1199
|
+
() => getRcsValueMap(templateTitle, TITLE_TEXT),
|
|
1200
|
+
[templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1201
|
+
);
|
|
1202
|
+
const descriptionVarSegmentValueMapById = useMemo(
|
|
1203
|
+
() => getRcsValueMap(templateDesc, MESSAGE_TEXT),
|
|
1204
|
+
[templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1205
|
+
);
|
|
1206
|
+
|
|
1207
|
+
const handleRcsVarChange = (id, value, type) => {
|
|
1208
|
+
const sep = id.lastIndexOf('_');
|
|
1209
|
+
if (sep === -1) return;
|
|
1210
|
+
const token = id.slice(0, sep);
|
|
1211
|
+
const variableName = getVarNameFromToken(token);
|
|
1212
|
+
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
1213
|
+
const isInvalidValue = value?.trim() === '';
|
|
1214
|
+
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
1215
|
+
const fieldStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1216
|
+
const globalSlot = getGlobalSlotIndexForRcsFieldId(id, fieldStr, type);
|
|
1217
|
+
setCardVarMapped((previousVarMap) => {
|
|
1218
|
+
const nextVarMap = { ...previousVarMap, [variableName]: coercedSlotValue };
|
|
1219
|
+
if (globalSlot !== null && globalSlot !== undefined) {
|
|
1220
|
+
nextVarMap[String(globalSlot + 1)] = coercedSlotValue;
|
|
1221
|
+
}
|
|
1222
|
+
return nextVarMap;
|
|
1223
|
+
});
|
|
1108
1224
|
};
|
|
1109
1225
|
|
|
1110
1226
|
const renderTextComponent = () => {
|
|
@@ -1148,10 +1264,19 @@ const splitTemplateVarString = (str) => {
|
|
|
1148
1264
|
</>
|
|
1149
1265
|
}
|
|
1150
1266
|
/>
|
|
1151
|
-
<div className="rcs_text_area_wrapper">
|
|
1152
1267
|
{(isEditFlow || !isFullMode) ? (
|
|
1153
|
-
|
|
1268
|
+
<VarSegmentMessageEditor
|
|
1269
|
+
key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
1270
|
+
templateString={templateTitle}
|
|
1271
|
+
valueMap={titleVarSegmentValueMapById}
|
|
1272
|
+
onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
|
|
1273
|
+
onFocus={(id) => setTitleTextAreaId(id)}
|
|
1274
|
+
varRegex={rcsVarRegex}
|
|
1275
|
+
placeholderPrefix=""
|
|
1276
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1277
|
+
/>
|
|
1154
1278
|
) : (
|
|
1279
|
+
<div className="rcs_text_area_wrapper">
|
|
1155
1280
|
<CapInput
|
|
1156
1281
|
className={`rcs-template-title-input ${
|
|
1157
1282
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -1164,8 +1289,8 @@ const splitTemplateVarString = (str) => {
|
|
|
1164
1289
|
errorMessage={templateTitleError}
|
|
1165
1290
|
disabled={isEditFlow || !isFullMode}
|
|
1166
1291
|
/>
|
|
1292
|
+
</div>
|
|
1167
1293
|
)}
|
|
1168
|
-
</div>
|
|
1169
1294
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
1170
1295
|
<CapError className="rcs-template-title-error">
|
|
1171
1296
|
{templateTitleError}
|
|
@@ -1213,7 +1338,18 @@ const splitTemplateVarString = (str) => {
|
|
|
1213
1338
|
<CapRow className="rcs-create-template-message-input">
|
|
1214
1339
|
<div className="rcs_text_area_wrapper">
|
|
1215
1340
|
{(isEditFlow || !isFullMode)
|
|
1216
|
-
?
|
|
1341
|
+
? (
|
|
1342
|
+
<VarSegmentMessageEditor
|
|
1343
|
+
key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
1344
|
+
templateString={templateDesc}
|
|
1345
|
+
valueMap={descriptionVarSegmentValueMapById}
|
|
1346
|
+
onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
|
|
1347
|
+
onFocus={(id) => setDescTextAreaId(id)}
|
|
1348
|
+
varRegex={rcsVarRegex}
|
|
1349
|
+
placeholderPrefix=""
|
|
1350
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1351
|
+
/>
|
|
1352
|
+
)
|
|
1217
1353
|
: (
|
|
1218
1354
|
<>
|
|
1219
1355
|
<TextArea
|
|
@@ -1257,7 +1393,9 @@ const splitTemplateVarString = (str) => {
|
|
|
1257
1393
|
{templateDescError}
|
|
1258
1394
|
</CapError>
|
|
1259
1395
|
)}
|
|
1260
|
-
{
|
|
1396
|
+
{(isEditFlow || !isFullMode)
|
|
1397
|
+
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
1398
|
+
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1261
1399
|
{!isFullMode && hasTag() && (
|
|
1262
1400
|
<CapAlert
|
|
1263
1401
|
message={
|
|
@@ -1277,18 +1415,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1277
1415
|
);
|
|
1278
1416
|
};
|
|
1279
1417
|
|
|
1280
|
-
|
|
1281
|
-
const fallbackSmsLength = () => (
|
|
1282
|
-
<CapLabel type="label1" className="fallback-sms-length">
|
|
1283
|
-
{formatMessage(messages.totalCharacters, {
|
|
1284
|
-
smsCount: Math.ceil(
|
|
1285
|
-
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1286
|
-
),
|
|
1287
|
-
number: fallbackMessage?.length,
|
|
1288
|
-
})}
|
|
1289
|
-
</CapLabel>
|
|
1290
|
-
);
|
|
1291
|
-
|
|
1292
1418
|
// Get character count for title (rich card only)
|
|
1293
1419
|
const getTitleCharacterCount = () => {
|
|
1294
1420
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -1348,7 +1474,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1348
1474
|
const hasTag = () => {
|
|
1349
1475
|
// Check cardVarMapped values for tags
|
|
1350
1476
|
if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
|
|
1351
|
-
const hasTagInMapped = Object.values(cardVarMapped).some(value =>
|
|
1477
|
+
const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
|
|
1352
1478
|
isTagIncluded(value)
|
|
1353
1479
|
);
|
|
1354
1480
|
if (hasTagInMapped) return true;
|
|
@@ -1366,14 +1492,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1366
1492
|
return false;
|
|
1367
1493
|
};
|
|
1368
1494
|
|
|
1369
|
-
//adding creative dlt fallback sms handlers
|
|
1370
|
-
const addDltMsgHandler = () => {
|
|
1371
|
-
setShowDltContainer(true);
|
|
1372
|
-
setDltMode(RCS_DLT_MODE.TEMPLATES);
|
|
1373
|
-
setDltEditData({});
|
|
1374
|
-
setFallbackMessage('');
|
|
1375
|
-
};
|
|
1376
|
-
|
|
1377
1495
|
const closeDltContainerHandler = () => {
|
|
1378
1496
|
setShowDltContainer(false);
|
|
1379
1497
|
setDltMode('');
|
|
@@ -1396,68 +1514,17 @@ const splitTemplateVarString = (str) => {
|
|
|
1396
1514
|
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
1397
1515
|
'',
|
|
1398
1516
|
);
|
|
1517
|
+
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
1518
|
+
|| get(tempData, 'versions.base.name', '')
|
|
1519
|
+
|| '';
|
|
1399
1520
|
closeDltContainerHandler();
|
|
1400
1521
|
setDltEditData(tempData);
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
closeDltContainerHandler();
|
|
1408
|
-
setDltEditData({});
|
|
1409
|
-
setFallbackMessage('');
|
|
1410
|
-
setFallbackMessageError(false);
|
|
1411
|
-
setShowDltCard(false);
|
|
1412
|
-
};
|
|
1413
|
-
|
|
1414
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
1415
|
-
const {
|
|
1416
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
1417
|
-
'sms-editor': smsEditor = '',
|
|
1418
|
-
} = data.versions.base || {};
|
|
1419
|
-
setFallbackPreviewmode(true);
|
|
1420
|
-
setDltPreviewData(
|
|
1421
|
-
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1422
|
-
);
|
|
1423
|
-
};
|
|
1424
|
-
|
|
1425
|
-
const getDltContentCardList = (content, channel) => {
|
|
1426
|
-
const extra = [
|
|
1427
|
-
<CapIcon
|
|
1428
|
-
type="edit"
|
|
1429
|
-
style={{ marginRight: '8px' }}
|
|
1430
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1431
|
-
/>,
|
|
1432
|
-
<CapDropdown
|
|
1433
|
-
overlay={(
|
|
1434
|
-
<CapMenu>
|
|
1435
|
-
<>
|
|
1436
|
-
<CapMenu.Item
|
|
1437
|
-
className="ant-dropdown-menu-item"
|
|
1438
|
-
onClick={() => setFallbackPreviewmode(true)}
|
|
1439
|
-
>
|
|
1440
|
-
{formatMessage(globalMessages.preview)}
|
|
1441
|
-
</CapMenu.Item>
|
|
1442
|
-
<CapMenu.Item
|
|
1443
|
-
className="ant-dropdown-menu-item"
|
|
1444
|
-
onClick={rcsDltCardDeleteHandler}
|
|
1445
|
-
>
|
|
1446
|
-
{formatMessage(globalMessages.delete)}
|
|
1447
|
-
</CapMenu.Item>
|
|
1448
|
-
</>
|
|
1449
|
-
</CapMenu>
|
|
1450
|
-
)}
|
|
1451
|
-
>
|
|
1452
|
-
<CapIcon type="more" />
|
|
1453
|
-
</CapDropdown>,
|
|
1454
|
-
];
|
|
1455
|
-
return {
|
|
1456
|
-
title: channel,
|
|
1457
|
-
content,
|
|
1458
|
-
cardType: channel,
|
|
1459
|
-
extra,
|
|
1460
|
-
};
|
|
1522
|
+
const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
|
|
1523
|
+
setSmsFallbackData({
|
|
1524
|
+
templateName: templateNameFromDlt,
|
|
1525
|
+
content: fallMsg,
|
|
1526
|
+
...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
|
|
1527
|
+
});
|
|
1461
1528
|
};
|
|
1462
1529
|
|
|
1463
1530
|
const getDltSlideBoxContent = () => {
|
|
@@ -1484,7 +1551,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1484
1551
|
isFullMode={isFullMode}
|
|
1485
1552
|
isDltFromRcs
|
|
1486
1553
|
onSelectTemplate={rcsDltEditSelectHandler}
|
|
1487
|
-
handlePeviewTemplate={dltFallbackListingPreviewhandler}
|
|
1488
1554
|
/>
|
|
1489
1555
|
);
|
|
1490
1556
|
} else if (dltMode === RCS_DLT_MODE.EDIT) {
|
|
@@ -1505,147 +1571,32 @@ const splitTemplateVarString = (str) => {
|
|
|
1505
1571
|
return { dltHeader, dltContent };
|
|
1506
1572
|
};
|
|
1507
1573
|
|
|
1508
|
-
const renderFallBackSmsComponent = () =>
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1533
|
-
// }}
|
|
1534
|
-
// title={formatMessage(messages.fallbackToolTip)}
|
|
1535
|
-
// />
|
|
1536
|
-
// </CapRow>
|
|
1537
|
-
// )}
|
|
1538
|
-
// description={formatMessage(messages.fallbackDesc)}
|
|
1539
|
-
// suffix={
|
|
1540
|
-
// isDltEnabled ? null : (
|
|
1541
|
-
// <CapButton
|
|
1542
|
-
// type="flat"
|
|
1543
|
-
// className="fallback-preview-btn"
|
|
1544
|
-
// prefix={<CapIcon type="eye" />}
|
|
1545
|
-
// style={{ color: CAP_SECONDARY.base }}
|
|
1546
|
-
// onClick={() => setFallbackPreviewmode(true)}
|
|
1547
|
-
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1548
|
-
// >
|
|
1549
|
-
// {formatMessage(globalMessages.preview)}
|
|
1550
|
-
// </CapButton>
|
|
1551
|
-
// )
|
|
1552
|
-
// }
|
|
1553
|
-
// />
|
|
1554
|
-
// </CapRow>,
|
|
1555
|
-
// );
|
|
1556
|
-
|
|
1557
|
-
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1558
|
-
// showAddCreativeBtnForDlt
|
|
1559
|
-
// && contentArr.push(
|
|
1560
|
-
// <CapCard className="rcs-dlt-fallback-card">
|
|
1561
|
-
// <CapRow type="flex" justify="center" align="middle">
|
|
1562
|
-
// <CapColumn span={10}>
|
|
1563
|
-
// <CapImage src={addCreativesIcon} />
|
|
1564
|
-
// </CapColumn>
|
|
1565
|
-
// <CapColumn span={14}>
|
|
1566
|
-
// <CapButton
|
|
1567
|
-
// className="add-dlt-btn"
|
|
1568
|
-
// type="secondary"
|
|
1569
|
-
// onClick={addDltMsgHandler}
|
|
1570
|
-
// >
|
|
1571
|
-
// {formatMessage(messages.addSmsCreative)}
|
|
1572
|
-
// </CapButton>
|
|
1573
|
-
// </CapColumn>
|
|
1574
|
-
// </CapRow>
|
|
1575
|
-
// </CapCard>,
|
|
1576
|
-
// );
|
|
1577
|
-
|
|
1578
|
-
// //dlt is enabled and dlt content is added, show it in a card
|
|
1579
|
-
// showCardForDlt
|
|
1580
|
-
// && contentArr.push(
|
|
1581
|
-
// <CapCustomCardList
|
|
1582
|
-
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1583
|
-
// className="rcs-dlt-card"
|
|
1584
|
-
// />,
|
|
1585
|
-
// fallbackMessageError && (
|
|
1586
|
-
// <CapError className="rcs-fallback-len-error">
|
|
1587
|
-
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1588
|
-
// </CapError>
|
|
1589
|
-
// ),
|
|
1590
|
-
// );
|
|
1591
|
-
|
|
1592
|
-
// //dlt is not enabled, show non dlt text area
|
|
1593
|
-
// showNonDltFallbackComp
|
|
1594
|
-
// && contentArr.push(
|
|
1595
|
-
// <>
|
|
1596
|
-
// <CapRow>
|
|
1597
|
-
// <CapHeader
|
|
1598
|
-
// title={(
|
|
1599
|
-
// <CapHeading type="h4">
|
|
1600
|
-
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1601
|
-
// </CapHeading>
|
|
1602
|
-
// )}
|
|
1603
|
-
// suffix={(
|
|
1604
|
-
// <TagList
|
|
1605
|
-
// label={formatMessage(globalMessages.addLabels)}
|
|
1606
|
-
// onTagSelect={onTagSelectFallback}
|
|
1607
|
-
// location={location}
|
|
1608
|
-
// tags={tags || []}
|
|
1609
|
-
// onContextChange={handleOnTagsContextChange}
|
|
1610
|
-
// injectedTags={injectedTags || {}}
|
|
1611
|
-
// selectedOfferDetails={selectedOfferDetails}
|
|
1612
|
-
// />
|
|
1613
|
-
// )}
|
|
1614
|
-
// />
|
|
1615
|
-
// </CapRow>
|
|
1616
|
-
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1617
|
-
// <TextArea
|
|
1618
|
-
// id="rcs_fallback_message_textarea"
|
|
1619
|
-
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1620
|
-
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1621
|
-
// onChange={onFallbackMessageChange}
|
|
1622
|
-
// errorMessage={fallbackMessageError}
|
|
1623
|
-
// value={fallbackMessage || ""}
|
|
1624
|
-
// />
|
|
1625
|
-
// {!aiContentBotDisabled && (
|
|
1626
|
-
// <CapAskAira.ContentGenerationBot
|
|
1627
|
-
// text={fallbackMessage || ""}
|
|
1628
|
-
// setText={(text) => {
|
|
1629
|
-
// onFallbackMessageChange({ target: { value: text } });
|
|
1630
|
-
// }}
|
|
1631
|
-
// iconPlacement="float-br"
|
|
1632
|
-
// rootStyle={{
|
|
1633
|
-
// bottom: "0.5rem",
|
|
1634
|
-
// right: "0.5rem",
|
|
1635
|
-
// position: "absolute",
|
|
1636
|
-
// }}
|
|
1637
|
-
// />
|
|
1638
|
-
// )}
|
|
1639
|
-
// </div>
|
|
1640
|
-
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1641
|
-
// </>
|
|
1642
|
-
// );
|
|
1643
|
-
|
|
1644
|
-
// return <>{contentArr}</>;
|
|
1645
|
-
};
|
|
1574
|
+
const renderFallBackSmsComponent = () => (
|
|
1575
|
+
<SmsFallback
|
|
1576
|
+
value={smsFallbackData}
|
|
1577
|
+
onChange={setSmsFallbackData}
|
|
1578
|
+
parentLocation={location}
|
|
1579
|
+
smsRegister={smsRegister}
|
|
1580
|
+
isFullMode={isFullMode}
|
|
1581
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1582
|
+
channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
|
|
1583
|
+
sectionTitle={
|
|
1584
|
+
smsFallbackData
|
|
1585
|
+
? formatMessage(messages.fallbackLabel)
|
|
1586
|
+
: formatMessage(messages.smsFallbackOptional)
|
|
1587
|
+
}
|
|
1588
|
+
templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
|
|
1589
|
+
templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
|
|
1590
|
+
/* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
|
|
1591
|
+
showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
|
|
1592
|
+
disableSelectTemplate={isEditFlow}
|
|
1593
|
+
eventContextTags={eventContextTags}
|
|
1594
|
+
onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
|
|
1595
|
+
isRcsEditFlow={isEditFlow}
|
|
1596
|
+
/>
|
|
1597
|
+
);
|
|
1646
1598
|
|
|
1647
1599
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
1648
|
-
setImageError(null);
|
|
1649
1600
|
const isRcsThumbnail = index === 1;
|
|
1650
1601
|
actions.uploadRcsAsset(file, type, {
|
|
1651
1602
|
isRcsThumbnail,
|
|
@@ -1657,7 +1608,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1657
1608
|
|
|
1658
1609
|
const setUpdateRcsImageSrc = useCallback(
|
|
1659
1610
|
(val) => {
|
|
1660
|
-
setAssetListImage(val);
|
|
1661
1611
|
updateRcsImageSrc(val);
|
|
1662
1612
|
actions.clearRcsMediaAsset(0);
|
|
1663
1613
|
},
|
|
@@ -1687,7 +1637,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1687
1637
|
|
|
1688
1638
|
|
|
1689
1639
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1690
|
-
setImageError(null);
|
|
1691
1640
|
actions.uploadRcsAsset(file, type, {
|
|
1692
1641
|
...fileParams,
|
|
1693
1642
|
type: 'video',
|
|
@@ -1696,9 +1645,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1696
1645
|
});
|
|
1697
1646
|
};
|
|
1698
1647
|
|
|
1699
|
-
const updateRcsVideoSrc = (val) => {
|
|
1700
|
-
setRcsVideoSrc(val);
|
|
1701
|
-
};
|
|
1702
1648
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
1703
1649
|
setRcsVideoSrc(val);
|
|
1704
1650
|
setAssetList(val);
|
|
@@ -1727,7 +1673,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1727
1673
|
<>
|
|
1728
1674
|
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
1729
1675
|
<CapImageUpload
|
|
1730
|
-
|
|
1676
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1731
1677
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1732
1678
|
imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
|
|
1733
1679
|
imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
|
|
@@ -1739,7 +1685,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1739
1685
|
updateOnReUpload={updateOnRcsThumbnailReUpload}
|
|
1740
1686
|
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
1741
1687
|
index={1}
|
|
1742
|
-
className="cap-custom-image-upload"
|
|
1743
1688
|
key={`rcs-uploaded-image-${currentDimension}`}
|
|
1744
1689
|
imageData={thumbnailData}
|
|
1745
1690
|
channel={RCS}
|
|
@@ -1770,7 +1715,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1770
1715
|
value: dim.type,
|
|
1771
1716
|
label: `${dim.label}`
|
|
1772
1717
|
}))}
|
|
1773
|
-
|
|
1718
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1774
1719
|
/>
|
|
1775
1720
|
</>
|
|
1776
1721
|
)}
|
|
@@ -1784,7 +1729,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1784
1729
|
</div>
|
|
1785
1730
|
) : (
|
|
1786
1731
|
<CapImageUpload
|
|
1787
|
-
style={{ paddingTop: '20px' }}
|
|
1788
1732
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1789
1733
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
1790
1734
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -1795,7 +1739,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1795
1739
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
1796
1740
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
1797
1741
|
index={0}
|
|
1798
|
-
className="cap-custom-image-upload"
|
|
1742
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1799
1743
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
1800
1744
|
imageData={rcsData}
|
|
1801
1745
|
channel={RCS}
|
|
@@ -1825,7 +1769,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1825
1769
|
value: dim.type,
|
|
1826
1770
|
label: `${dim.label}`
|
|
1827
1771
|
}))}
|
|
1828
|
-
|
|
1772
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1829
1773
|
/>
|
|
1830
1774
|
)}
|
|
1831
1775
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -1885,8 +1829,16 @@ const splitTemplateVarString = (str) => {
|
|
|
1885
1829
|
const getRcsPreview = () => {
|
|
1886
1830
|
|
|
1887
1831
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
1888
|
-
const
|
|
1889
|
-
const
|
|
1832
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1833
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
1834
|
+
? 0
|
|
1835
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
1836
|
+
const resolvedTitle = isMediaTypeText
|
|
1837
|
+
? ''
|
|
1838
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
1839
|
+
const resolvedDesc = isSlotMappingMode
|
|
1840
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
1841
|
+
: templateDesc;
|
|
1890
1842
|
return (
|
|
1891
1843
|
<UnifiedPreview
|
|
1892
1844
|
channel={RCS}
|
|
@@ -1909,51 +1861,65 @@ const splitTemplateVarString = (str) => {
|
|
|
1909
1861
|
);
|
|
1910
1862
|
};
|
|
1911
1863
|
|
|
1912
|
-
const getUnmappedDesc = (str, mapping) => {
|
|
1913
|
-
if (!str) return '';
|
|
1914
|
-
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1915
|
-
let result = str;
|
|
1916
|
-
const replacements = [];
|
|
1917
|
-
Object.entries(mapping).forEach(([key, value]) => {
|
|
1918
|
-
const raw = (value ?? '').toString();
|
|
1919
|
-
if (!raw || raw?.trim?.() === '') return;
|
|
1920
|
-
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1921
|
-
replacements.push({ key, needle: raw });
|
|
1922
|
-
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1923
|
-
});
|
|
1924
|
-
const seen = new Set();
|
|
1925
|
-
const uniq = replacements
|
|
1926
|
-
.filter(({ key, needle }) => {
|
|
1927
|
-
const id = `${key}::${needle}`;
|
|
1928
|
-
if (seen.has(id)) return false;
|
|
1929
|
-
seen.add(id);
|
|
1930
|
-
return true;
|
|
1931
|
-
})
|
|
1932
|
-
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1933
|
-
|
|
1934
|
-
uniq.forEach(({ key, needle }) => {
|
|
1935
|
-
if (!needle) return;
|
|
1936
|
-
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1937
|
-
const regex = new RegExp(escaped, 'g');
|
|
1938
|
-
result = result.replace(regex, `{{${key}}}`);
|
|
1939
|
-
});
|
|
1940
|
-
return result;
|
|
1941
|
-
};
|
|
1942
|
-
|
|
1943
1864
|
const createPayload = () => {
|
|
1944
|
-
const
|
|
1945
|
-
const {
|
|
1946
|
-
template_id: templateId = '',
|
|
1947
|
-
template_name = '',
|
|
1948
|
-
'sms-editor': template = '',
|
|
1949
|
-
header: registeredSenderIds = [],
|
|
1950
|
-
} = base;
|
|
1951
|
-
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1952
|
-
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
1865
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1953
1866
|
const alignment = isMediaTypeImage
|
|
1954
1867
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
1955
1868
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
1956
1869
|
|
|
1870
|
+
const heightTypeForCardWidth = isMediaTypeText
|
|
1871
|
+
? undefined
|
|
1872
|
+
: isMediaTypeImage
|
|
1873
|
+
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
|
|
1874
|
+
: isMediaTypeVideo
|
|
1875
|
+
? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
|
|
1876
|
+
: undefined;
|
|
1877
|
+
const cardWidthFromSelection =
|
|
1878
|
+
heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
|
|
1879
|
+
|
|
1880
|
+
/** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
|
|
1881
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
1882
|
+
const smsFallbackMerged = !isFullMode
|
|
1883
|
+
? (() => {
|
|
1884
|
+
const local =
|
|
1885
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
1886
|
+
return {
|
|
1887
|
+
...smsFromApiShape,
|
|
1888
|
+
...local,
|
|
1889
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
1890
|
+
};
|
|
1891
|
+
})()
|
|
1892
|
+
: (smsFallbackData || {});
|
|
1893
|
+
const smsFallbackForPayload = (() => {
|
|
1894
|
+
if (isFullMode) {
|
|
1895
|
+
return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
|
|
1896
|
+
}
|
|
1897
|
+
const mapped = {
|
|
1898
|
+
templateName:
|
|
1899
|
+
smsFallbackMerged.templateName
|
|
1900
|
+
|| smsFallbackMerged.smsTemplateName
|
|
1901
|
+
|| '',
|
|
1902
|
+
// Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
|
|
1903
|
+
content:
|
|
1904
|
+
smsFallbackMerged.content
|
|
1905
|
+
|| smsFallbackMerged.smsContent
|
|
1906
|
+
|| smsFallbackMerged.smsTemplateContent
|
|
1907
|
+
|| smsFallbackMerged.message
|
|
1908
|
+
|| '',
|
|
1909
|
+
templateContent:
|
|
1910
|
+
pickFirstSmsFallbackTemplateString(smsFallbackMerged)
|
|
1911
|
+
|| '',
|
|
1912
|
+
...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
|
|
1913
|
+
&& { unicodeValidity: smsFallbackMerged.unicodeValidity }),
|
|
1914
|
+
...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
1915
|
+
&& Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
1916
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
1917
|
+
smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
1918
|
+
}),
|
|
1919
|
+
};
|
|
1920
|
+
return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
|
|
1921
|
+
})();
|
|
1922
|
+
|
|
1957
1923
|
const payload = {
|
|
1958
1924
|
name: templateName,
|
|
1959
1925
|
versions: {
|
|
@@ -1965,12 +1931,14 @@ const splitTemplateVarString = (str) => {
|
|
|
1965
1931
|
cardSettings: {
|
|
1966
1932
|
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
1967
1933
|
...(alignment && { mediaAlignment: alignment }),
|
|
1968
|
-
cardWidth:
|
|
1934
|
+
cardWidth: cardWidthFromSelection,
|
|
1969
1935
|
},
|
|
1970
1936
|
cardContent: [
|
|
1971
1937
|
{
|
|
1972
|
-
|
|
1973
|
-
|
|
1938
|
+
// Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
|
|
1939
|
+
// / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
|
|
1940
|
+
title: templateTitle,
|
|
1941
|
+
description: templateDesc,
|
|
1974
1942
|
mediaType: templateMediaType,
|
|
1975
1943
|
...(!isMediaTypeText && {media: {
|
|
1976
1944
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -1979,23 +1947,30 @@ const splitTemplateVarString = (str) => {
|
|
|
1979
1947
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
1980
1948
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
1981
1949
|
}}),
|
|
1982
|
-
...(
|
|
1983
|
-
const
|
|
1984
|
-
...(templateTitle
|
|
1985
|
-
...(templateDesc
|
|
1950
|
+
...(isSlotMappingMode && (() => {
|
|
1951
|
+
const templateVarTokens = [
|
|
1952
|
+
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
1953
|
+
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
1986
1954
|
];
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1955
|
+
const persistedSlotVarMap = {};
|
|
1956
|
+
const seenSemanticVarNames = new Set();
|
|
1957
|
+
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
1958
|
+
const varName = getVarNameFromToken(token);
|
|
1959
|
+
if (!varName) return;
|
|
1960
|
+
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
1961
|
+
cardVarMapped,
|
|
1962
|
+
varName,
|
|
1963
|
+
slotIndexZeroBased,
|
|
1964
|
+
isSlotMappingMode,
|
|
1965
|
+
);
|
|
1966
|
+
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
1967
|
+
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
1968
|
+
if (!seenSemanticVarNames.has(varName)) {
|
|
1969
|
+
seenSemanticVarNames.add(varName);
|
|
1970
|
+
persistedSlotVarMap[varName] = sanitizedSlotValue;
|
|
1996
1971
|
}
|
|
1997
1972
|
});
|
|
1998
|
-
return { cardVarMapped:
|
|
1973
|
+
return { cardVarMapped: persistedSlotVarMap };
|
|
1999
1974
|
})()),
|
|
2000
1975
|
...(suggestions.length > 0 && { suggestions }),
|
|
2001
1976
|
}
|
|
@@ -2003,17 +1978,29 @@ const splitTemplateVarString = (str) => {
|
|
|
2003
1978
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
2004
1979
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
2005
1980
|
},
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
1981
|
+
...(smsFallbackForPayload && (() => {
|
|
1982
|
+
const smsBodyText =
|
|
1983
|
+
smsFallbackForPayload.content
|
|
1984
|
+
|| smsFallbackForPayload.templateContent
|
|
1985
|
+
|| smsFallbackForPayload.message
|
|
1986
|
+
|| smsFallbackForPayload.smsContent
|
|
1987
|
+
|| '';
|
|
1988
|
+
return {
|
|
1989
|
+
smsFallBackContent: {
|
|
1990
|
+
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
1991
|
+
smsContent: smsBodyText,
|
|
1992
|
+
// cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
|
|
1993
|
+
message: smsBodyText,
|
|
1994
|
+
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
1995
|
+
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
1996
|
+
}),
|
|
1997
|
+
...(smsFallbackForPayload.rcsSmsFallbackVarMapped
|
|
1998
|
+
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
1999
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
2000
|
+
}),
|
|
2014
2001
|
},
|
|
2015
|
-
}
|
|
2016
|
-
},
|
|
2002
|
+
};
|
|
2003
|
+
})()),
|
|
2017
2004
|
},
|
|
2018
2005
|
},
|
|
2019
2006
|
},
|
|
@@ -2023,6 +2010,84 @@ const splitTemplateVarString = (str) => {
|
|
|
2023
2010
|
return payload;
|
|
2024
2011
|
};
|
|
2025
2012
|
|
|
2013
|
+
/** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
|
|
2014
|
+
const testPreviewFormData = useMemo(() => {
|
|
2015
|
+
const payload = createPayload();
|
|
2016
|
+
const rcs = payload?.versions?.base?.content?.RCS;
|
|
2017
|
+
if (!rcs) return null;
|
|
2018
|
+
// createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
|
|
2019
|
+
const accountIdForCreateMessageMeta =
|
|
2020
|
+
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
2021
|
+
? String(wecrmAccountId)
|
|
2022
|
+
: accountId;
|
|
2023
|
+
const rcsForTest = {
|
|
2024
|
+
...rcs,
|
|
2025
|
+
rcsContent: {
|
|
2026
|
+
...rcs.rcsContent,
|
|
2027
|
+
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
2028
|
+
},
|
|
2029
|
+
};
|
|
2030
|
+
const out = {
|
|
2031
|
+
versions: {
|
|
2032
|
+
base: {
|
|
2033
|
+
content: {
|
|
2034
|
+
RCS: rcsForTest,
|
|
2035
|
+
},
|
|
2036
|
+
},
|
|
2037
|
+
},
|
|
2038
|
+
};
|
|
2039
|
+
const fb = smsFallbackData;
|
|
2040
|
+
if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
|
|
2041
|
+
out.templateConfigs = {
|
|
2042
|
+
templateId: fb.smsTemplateId || '',
|
|
2043
|
+
template: fb.templateContent || fb.content || '',
|
|
2044
|
+
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
2045
|
+
registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
return out;
|
|
2049
|
+
}, [
|
|
2050
|
+
templateName,
|
|
2051
|
+
templateTitle,
|
|
2052
|
+
templateDesc,
|
|
2053
|
+
templateMediaType,
|
|
2054
|
+
cardVarMapped,
|
|
2055
|
+
suggestions,
|
|
2056
|
+
rcsImageSrc,
|
|
2057
|
+
rcsVideoSrc,
|
|
2058
|
+
rcsThumbnailSrc,
|
|
2059
|
+
selectedDimension,
|
|
2060
|
+
smsFallbackData,
|
|
2061
|
+
isFullMode,
|
|
2062
|
+
isEditFlow,
|
|
2063
|
+
templateType,
|
|
2064
|
+
accountId,
|
|
2065
|
+
wecrmAccountId,
|
|
2066
|
+
accessToken,
|
|
2067
|
+
accountName,
|
|
2068
|
+
hostName,
|
|
2069
|
+
smsRegister,
|
|
2070
|
+
]);
|
|
2071
|
+
|
|
2072
|
+
/**
|
|
2073
|
+
* Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
|
|
2074
|
+
* with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
|
|
2075
|
+
* miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
|
|
2076
|
+
*/
|
|
2077
|
+
const librarySmsFallbackMergedForValidation = useMemo(() => {
|
|
2078
|
+
if (isFullMode) {
|
|
2079
|
+
return smsFallbackData;
|
|
2080
|
+
}
|
|
2081
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
2082
|
+
const local =
|
|
2083
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
2084
|
+
return {
|
|
2085
|
+
...smsFromApiShape,
|
|
2086
|
+
...local,
|
|
2087
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
2088
|
+
};
|
|
2089
|
+
}, [isFullMode, templateData, smsFallbackData]);
|
|
2090
|
+
|
|
2026
2091
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
2027
2092
|
// eslint-disable-next-line no-undef
|
|
2028
2093
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -2052,6 +2117,9 @@ const splitTemplateVarString = (str) => {
|
|
|
2052
2117
|
_id: params?.id,
|
|
2053
2118
|
validity: true,
|
|
2054
2119
|
type: RCS,
|
|
2120
|
+
// CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
|
|
2121
|
+
// the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
|
|
2122
|
+
closeSlideBoxAfterSubmit: !isFullMode,
|
|
2055
2123
|
};
|
|
2056
2124
|
getFormData(formDataParams);
|
|
2057
2125
|
};
|
|
@@ -2065,6 +2133,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2065
2133
|
actionCallback({ resp, errorMessage });
|
|
2066
2134
|
setSpin(false); // Always turn off spinner
|
|
2067
2135
|
if (!errorMessage) {
|
|
2136
|
+
setTemplateStatus(RCS_STATUSES.pending);
|
|
2068
2137
|
onCreateComplete();
|
|
2069
2138
|
}
|
|
2070
2139
|
});
|
|
@@ -2076,6 +2145,64 @@ const splitTemplateVarString = (str) => {
|
|
|
2076
2145
|
}
|
|
2077
2146
|
};
|
|
2078
2147
|
|
|
2148
|
+
/** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
|
|
2149
|
+
const smsFallbackBlocksDone = () => {
|
|
2150
|
+
// Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
|
|
2151
|
+
if (
|
|
2152
|
+
!isFullMode
|
|
2153
|
+
&& !isTraiDLTEnable(isFullMode, smsRegister)
|
|
2154
|
+
&& smsFallbackData == null
|
|
2155
|
+
&& hasMeaningfulSmsFallbackShape(
|
|
2156
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
2157
|
+
)
|
|
2158
|
+
) {
|
|
2159
|
+
return true;
|
|
2160
|
+
}
|
|
2161
|
+
if (!smsFallbackData) return false;
|
|
2162
|
+
// Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
|
|
2163
|
+
// concern, not a structural requirement for approval — the registered SMS template body stands on
|
|
2164
|
+
// its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
|
|
2165
|
+
if (isFullMode) return false;
|
|
2166
|
+
const merged = librarySmsFallbackMergedForValidation;
|
|
2167
|
+
const templateText = pickFirstSmsFallbackTemplateString(merged);
|
|
2168
|
+
if (!templateText) {
|
|
2169
|
+
return true;
|
|
2170
|
+
}
|
|
2171
|
+
const rawVarMap =
|
|
2172
|
+
merged.rcsSmsFallbackVarMapped
|
|
2173
|
+
|| merged['rcs-sms-fallback-var-mapped'];
|
|
2174
|
+
const varMap =
|
|
2175
|
+
rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
|
|
2176
|
+
return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
|
|
2177
|
+
};
|
|
2178
|
+
|
|
2179
|
+
/**
|
|
2180
|
+
* Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
|
|
2181
|
+
* semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
|
|
2182
|
+
* / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
|
|
2183
|
+
*/
|
|
2184
|
+
const isLibraryCampaignCardVarMappingIncomplete = () => {
|
|
2185
|
+
if (isFullMode) return false;
|
|
2186
|
+
const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
|
|
2187
|
+
rcsVarTestRegex.test(elem),
|
|
2188
|
+
);
|
|
2189
|
+
const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
|
|
2190
|
+
rcsVarTestRegex.test(elem),
|
|
2191
|
+
);
|
|
2192
|
+
const orderedVarNames = [
|
|
2193
|
+
...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
2194
|
+
...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
2195
|
+
];
|
|
2196
|
+
if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
|
|
2197
|
+
return true;
|
|
2198
|
+
}
|
|
2199
|
+
return orderedVarNames.some((name, globalIdx) => {
|
|
2200
|
+
const v = resolveCardVarMappedSlotValue(cardVarMapped, name, globalIdx, true);
|
|
2201
|
+
const s = v == null ? '' : String(v);
|
|
2202
|
+
return s.trim() === '';
|
|
2203
|
+
});
|
|
2204
|
+
};
|
|
2205
|
+
|
|
2079
2206
|
const isDisableDone = () => {
|
|
2080
2207
|
if(isEditFlow){
|
|
2081
2208
|
return false;
|
|
@@ -2086,40 +2213,16 @@ const splitTemplateVarString = (str) => {
|
|
|
2086
2213
|
}
|
|
2087
2214
|
}
|
|
2088
2215
|
|
|
2089
|
-
if(
|
|
2090
|
-
|
|
2091
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2092
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2093
|
-
|
|
2094
|
-
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2095
|
-
return true;
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
const hasEmptyMapping =
|
|
2099
|
-
cardVarMapped &&
|
|
2100
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2101
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2102
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2103
|
-
return v.trim() === ''; // empty string
|
|
2104
|
-
});
|
|
2105
|
-
|
|
2106
|
-
if (hasEmptyMapping) {
|
|
2107
|
-
return true;
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
const anyMissing = allVars.some(name => {
|
|
2111
|
-
const v = cardVarMapped?.[name];
|
|
2112
|
-
if (typeof v !== 'string') return !v;
|
|
2113
|
-
return v.trim() === '';
|
|
2114
|
-
});
|
|
2115
|
-
if (anyMissing) {
|
|
2116
|
-
return true;
|
|
2117
|
-
}
|
|
2216
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2217
|
+
return true;
|
|
2118
2218
|
}
|
|
2119
2219
|
|
|
2120
|
-
|
|
2220
|
+
if (smsFallbackBlocksDone()) {
|
|
2121
2221
|
return true;
|
|
2222
|
+
}
|
|
2122
2223
|
|
|
2224
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2225
|
+
return true;
|
|
2123
2226
|
}
|
|
2124
2227
|
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
2125
2228
|
return true;
|
|
@@ -2137,53 +2240,36 @@ const splitTemplateVarString = (str) => {
|
|
|
2137
2240
|
return true;
|
|
2138
2241
|
}
|
|
2139
2242
|
}
|
|
2140
|
-
if (templateDescError || templateTitleError
|
|
2243
|
+
if (templateDescError || templateTitleError) {
|
|
2244
|
+
return true;
|
|
2245
|
+
}
|
|
2246
|
+
if (
|
|
2247
|
+
smsFallbackData?.content
|
|
2248
|
+
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2249
|
+
) {
|
|
2141
2250
|
return true;
|
|
2142
2251
|
}
|
|
2143
2252
|
return false;
|
|
2144
2253
|
};
|
|
2145
2254
|
|
|
2146
2255
|
const isEditDisableDone = () => {
|
|
2147
|
-
|
|
2148
2256
|
if (templateStatus !== RCS_STATUSES.approved) {
|
|
2149
2257
|
return true;
|
|
2150
2258
|
}
|
|
2151
2259
|
|
|
2152
|
-
if (!isFullMode) {
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2260
|
+
// if (!isFullMode) {
|
|
2261
|
+
// if (templateName.trim() === '' || templateNameError) {
|
|
2262
|
+
// return true;
|
|
2263
|
+
// }
|
|
2264
|
+
// }
|
|
2265
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2266
|
+
return true;
|
|
2156
2267
|
}
|
|
2157
|
-
if(!isFullMode){
|
|
2158
|
-
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2159
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2160
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2161
2268
|
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
const hasEmptyMapping =
|
|
2167
|
-
cardVarMapped &&
|
|
2168
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2169
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2170
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2171
|
-
return v.trim() === ''; // empty string
|
|
2172
|
-
});
|
|
2173
|
-
|
|
2174
|
-
if (hasEmptyMapping) {
|
|
2175
|
-
return true;
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
const anyMissing = allVars.some(name => {
|
|
2179
|
-
const v = cardVarMapped?.[name];
|
|
2180
|
-
if (typeof v !== 'string') return !v;
|
|
2181
|
-
return v.trim() === '';
|
|
2182
|
-
});
|
|
2183
|
-
if (anyMissing) {
|
|
2184
|
-
return true;
|
|
2185
|
-
}
|
|
2269
|
+
if (smsFallbackBlocksDone()) {
|
|
2270
|
+
return true;
|
|
2186
2271
|
}
|
|
2272
|
+
|
|
2187
2273
|
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2188
2274
|
return true;
|
|
2189
2275
|
}
|
|
@@ -2202,7 +2288,13 @@ const splitTemplateVarString = (str) => {
|
|
|
2202
2288
|
return true;
|
|
2203
2289
|
}
|
|
2204
2290
|
}
|
|
2205
|
-
if (templateTitleError || templateDescError
|
|
2291
|
+
if (templateTitleError || templateDescError) {
|
|
2292
|
+
return true;
|
|
2293
|
+
}
|
|
2294
|
+
if (
|
|
2295
|
+
smsFallbackData?.content
|
|
2296
|
+
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2297
|
+
) {
|
|
2206
2298
|
return true;
|
|
2207
2299
|
}
|
|
2208
2300
|
return false;
|
|
@@ -2252,52 +2344,56 @@ const splitTemplateVarString = (str) => {
|
|
|
2252
2344
|
};
|
|
2253
2345
|
|
|
2254
2346
|
const getMainContent = () => {
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
return (
|
|
2259
|
-
<CapSlideBox
|
|
2260
|
-
show={showDltContainer}
|
|
2261
|
-
header={dltHeader}
|
|
2262
|
-
content={dltContent}
|
|
2263
|
-
handleClose={closeDltContainerHandler}
|
|
2264
|
-
size="size-xl"
|
|
2265
|
-
/>
|
|
2266
|
-
);
|
|
2267
|
-
}
|
|
2347
|
+
// Slideboxes are rendered outside the page-level spinner to avoid
|
|
2348
|
+
// stacking/blur issues during initial loads.
|
|
2349
|
+
if (showDltContainer) return null;
|
|
2268
2350
|
|
|
2269
2351
|
return (
|
|
2270
2352
|
<>
|
|
2271
|
-
{templateStatus !== '' && (
|
|
2272
|
-
<
|
|
2273
|
-
{
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2353
|
+
{templateStatus !== '' && (
|
|
2354
|
+
<CapRow className="template-status-container">
|
|
2355
|
+
<CapColumn span={14}>
|
|
2356
|
+
<CapLabel type="label2">
|
|
2357
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
2358
|
+
</CapLabel>
|
|
2359
|
+
|
|
2360
|
+
{templateStatus && (
|
|
2361
|
+
<CapAlert
|
|
2362
|
+
message={getTemplateStatusMessage()}
|
|
2363
|
+
type={getTemplateStatusType(templateStatus)}
|
|
2364
|
+
/>
|
|
2365
|
+
)}
|
|
2366
|
+
</CapColumn>
|
|
2367
|
+
</CapRow>
|
|
2283
2368
|
)}
|
|
2284
|
-
<CapRow className=
|
|
2369
|
+
<CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
|
|
2285
2370
|
<CapColumn span={14}>
|
|
2286
2371
|
{/* template name */}
|
|
2287
2372
|
{isFullMode && (
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2373
|
+
isEditFlow ? (
|
|
2374
|
+
<div className="rcs-creative-name-readonly">
|
|
2375
|
+
<CapHeading type="h4">
|
|
2376
|
+
{formatMessage(globalMessages.creativeNameLabel)}
|
|
2377
|
+
</CapHeading>
|
|
2378
|
+
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
2379
|
+
{templateName || '-'}
|
|
2380
|
+
</CapHeading>
|
|
2381
|
+
</div>
|
|
2382
|
+
) : (
|
|
2383
|
+
<CapInput
|
|
2384
|
+
id="rcs_template_name_input"
|
|
2385
|
+
data-testid="template_name"
|
|
2386
|
+
onChange={onTemplateNameChange}
|
|
2387
|
+
errorMessage={templateNameError}
|
|
2388
|
+
placeholder={formatMessage(
|
|
2389
|
+
globalMessages.templateNamePlaceholder,
|
|
2390
|
+
)}
|
|
2391
|
+
value={templateName || ''}
|
|
2392
|
+
size="default"
|
|
2393
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
2394
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2395
|
+
/>
|
|
2396
|
+
)
|
|
2301
2397
|
)}
|
|
2302
2398
|
{renderLabel('templateTypeLabel')}
|
|
2303
2399
|
<CapRadioGroup
|
|
@@ -2325,7 +2421,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2325
2421
|
</>
|
|
2326
2422
|
)}
|
|
2327
2423
|
{renderTextComponent()}
|
|
2328
|
-
<CapDivider
|
|
2424
|
+
<CapDivider className="rcs-fallback-section-divider" />
|
|
2329
2425
|
{renderFallBackSmsComponent()}
|
|
2330
2426
|
<div className="rcs-scroll-div" />
|
|
2331
2427
|
</CapColumn>
|
|
@@ -2337,7 +2433,8 @@ const splitTemplateVarString = (str) => {
|
|
|
2337
2433
|
|
|
2338
2434
|
|
|
2339
2435
|
<div className="rcs-footer">
|
|
2340
|
-
{!
|
|
2436
|
+
{/* 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). */}
|
|
2437
|
+
{!isEditFlow && isFullMode && (
|
|
2341
2438
|
<>
|
|
2342
2439
|
<div className="button-disabled-tooltip-wrapper">
|
|
2343
2440
|
<CapButton
|
|
@@ -2358,7 +2455,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2358
2455
|
className="rcs-test-preview-btn"
|
|
2359
2456
|
type="secondary"
|
|
2360
2457
|
disabled={true}
|
|
2361
|
-
style={{ marginLeft: "8px" }}
|
|
2362
2458
|
>
|
|
2363
2459
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
2364
2460
|
</CapButton>
|
|
@@ -2391,51 +2487,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2391
2487
|
</>
|
|
2392
2488
|
)}
|
|
2393
2489
|
</div>
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
{fallbackPreviewmode && (
|
|
2397
|
-
<CapSlideBox
|
|
2398
|
-
className="rcs-fallback-preview"
|
|
2399
|
-
show={fallbackPreviewmode}
|
|
2400
|
-
header={(
|
|
2401
|
-
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2402
|
-
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2403
|
-
</CapHeading>
|
|
2404
|
-
)}
|
|
2405
|
-
content={(
|
|
2406
|
-
<>
|
|
2407
|
-
<UnifiedPreview
|
|
2408
|
-
channel={RCS}
|
|
2409
|
-
content={{
|
|
2410
|
-
rcsPreviewContent: {
|
|
2411
|
-
rcsDesc: tempMsg,
|
|
2412
|
-
},
|
|
2413
|
-
}}
|
|
2414
|
-
device={ANDROID}
|
|
2415
|
-
showDeviceToggle={false}
|
|
2416
|
-
showHeader={false}
|
|
2417
|
-
formatMessage={formatMessage}
|
|
2418
|
-
/>
|
|
2419
|
-
<CapHeading
|
|
2420
|
-
type="h3"
|
|
2421
|
-
style={{ textAlign: 'center' }}
|
|
2422
|
-
className="margin-t-16"
|
|
2423
|
-
>
|
|
2424
|
-
{formatMessage(messages.totalCharacters, {
|
|
2425
|
-
smsCount: Math.ceil(
|
|
2426
|
-
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2427
|
-
),
|
|
2428
|
-
number: tempMsg.length,
|
|
2429
|
-
})}
|
|
2430
|
-
</CapHeading>
|
|
2431
|
-
</>
|
|
2432
|
-
)}
|
|
2433
|
-
handleClose={() => {
|
|
2434
|
-
setFallbackPreviewmode(false);
|
|
2435
|
-
setDltPreviewData('');
|
|
2436
|
-
}}
|
|
2437
|
-
/>
|
|
2438
|
-
)}
|
|
2439
2490
|
</>
|
|
2440
2491
|
);
|
|
2441
2492
|
};
|
|
@@ -2444,12 +2495,38 @@ const splitTemplateVarString = (str) => {
|
|
|
2444
2495
|
<CapSpin spinning={loadingTags || spin}>
|
|
2445
2496
|
{getMainContent()}
|
|
2446
2497
|
</CapSpin>
|
|
2498
|
+
|
|
2499
|
+
{showDltContainer && (() => {
|
|
2500
|
+
const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
|
|
2501
|
+
return (
|
|
2502
|
+
<CapSlideBox
|
|
2503
|
+
show={showDltContainer}
|
|
2504
|
+
header={dltHeader}
|
|
2505
|
+
content={dltContent}
|
|
2506
|
+
handleClose={closeDltContainerHandler}
|
|
2507
|
+
size="size-xl"
|
|
2508
|
+
/>
|
|
2509
|
+
);
|
|
2510
|
+
})()}
|
|
2511
|
+
|
|
2447
2512
|
<TestAndPreviewSlidebox
|
|
2448
2513
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
2449
2514
|
onClose={handleCloseTestAndPreview}
|
|
2450
|
-
formData={
|
|
2451
|
-
content={
|
|
2515
|
+
formData={testPreviewFormData}
|
|
2516
|
+
content={testAndPreviewContent}
|
|
2452
2517
|
currentChannel={RCS}
|
|
2518
|
+
orgUnitId={orgUnitId}
|
|
2519
|
+
smsFallbackContent={
|
|
2520
|
+
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
2521
|
+
? {
|
|
2522
|
+
templateContent:
|
|
2523
|
+
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
2524
|
+
templateName: smsFallbackData.templateName || '',
|
|
2525
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackData?.rcsSmsFallbackVarMapped ?? {},
|
|
2526
|
+
}
|
|
2527
|
+
: null
|
|
2528
|
+
}
|
|
2529
|
+
smsRegister={smsRegister}
|
|
2453
2530
|
/>
|
|
2454
2531
|
</>
|
|
2455
2532
|
);
|