@capillarytech/creatives-library 8.0.316-alpha.2 → 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 +13 -13
- 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/InApp/index.js +4 -5
- package/v2Containers/Rcs/constants.js +32 -1
- package/v2Containers/Rcs/index.js +953 -882
- 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/Viber/index.js +4 -9
- 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 +10 -32
- 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,44 +21,34 @@ 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,
|
|
57
37
|
setInjectedTags,
|
|
58
|
-
selectCurrentOrgDetails,
|
|
59
38
|
} from '../Cap/selectors';
|
|
60
39
|
import * as RcsActions from './actions';
|
|
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,
|
|
@@ -76,14 +65,11 @@ import {
|
|
|
76
65
|
RCS_IMG_SIZE,
|
|
77
66
|
RCS_DLT_MODE,
|
|
78
67
|
CTA,
|
|
79
|
-
AI_CONTENT_BOT_DISABLED,
|
|
80
68
|
RCS_STATUSES,
|
|
81
69
|
TITLE_TEXT,
|
|
82
70
|
MESSAGE_TEXT,
|
|
83
71
|
ALLOWED_EXTENSIONS_VIDEO_REGEX,
|
|
84
72
|
RCS_VIDEO_SIZE,
|
|
85
|
-
TEMPLATE_HEADER_MAX_LENGTH,
|
|
86
|
-
TEMPLATE_MESSAGE_MAX_LENGTH,
|
|
87
73
|
RCS_THUMBNAIL_MIN_SIZE,
|
|
88
74
|
RCS_THUMBNAIL_MAX_SIZE,
|
|
89
75
|
contentType,
|
|
@@ -96,35 +82,45 @@ import {
|
|
|
96
82
|
MAX_BUTTONS,
|
|
97
83
|
INITIAL_SUGGESTIONS_DATA_STOP,
|
|
98
84
|
RCS_BUTTON_TYPES,
|
|
99
|
-
titletype,
|
|
100
|
-
descType,
|
|
101
85
|
STANDALONE,
|
|
102
86
|
VERTICAL,
|
|
103
87
|
SMALL,
|
|
104
88
|
MEDIUM,
|
|
105
89
|
RICHCARD,
|
|
90
|
+
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
91
|
+
RCS_TAG_AREA_FIELD_TITLE,
|
|
92
|
+
RCS_TAG_AREA_FIELD_DESC,
|
|
106
93
|
} from './constants';
|
|
107
94
|
import globalMessages from '../Cap/messages';
|
|
108
95
|
import messages from './messages';
|
|
109
96
|
import creativesMessages from '../CreativesContainer/messages';
|
|
110
97
|
import withCreatives from '../../hoc/withCreatives';
|
|
111
98
|
import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
112
|
-
import
|
|
99
|
+
import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
|
|
100
|
+
import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
113
101
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
102
|
+
import { splitTemplateVarString } from '../../utils/templateVarUtils';
|
|
114
103
|
import CapImageUpload from '../../v2Components/CapImageUpload';
|
|
115
|
-
import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
|
|
116
104
|
import Templates from '../Templates';
|
|
117
105
|
import SmsTraiEdit from '../SmsTrai/Edit';
|
|
106
|
+
import SmsFallback from '../../v2Components/SmsFallback';
|
|
107
|
+
import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
|
|
118
108
|
import TagList from '../TagList';
|
|
119
109
|
import { validateTags } from '../../utils/tagValidations';
|
|
120
|
-
import {
|
|
110
|
+
import { isTraiDLTEnable } from '../../utils/common';
|
|
121
111
|
import { isTagIncluded } from '../../utils/commonUtils';
|
|
122
112
|
import injectReducer from '../../utils/injectReducer';
|
|
123
113
|
import v2RcsReducer from './reducer';
|
|
124
|
-
import {
|
|
125
|
-
|
|
114
|
+
import {
|
|
115
|
+
areAllRcsSmsFallbackVarSlotsFilled,
|
|
116
|
+
buildRcsNumericMustachePlaceholderRegex,
|
|
117
|
+
getTemplateStatusType,
|
|
118
|
+
normalizeCardVarMapped,
|
|
119
|
+
coalesceCardVarMappedToTemplate,
|
|
120
|
+
resolveCardVarMappedSlotValue,
|
|
121
|
+
sanitizeCardVarMappedValue,
|
|
122
|
+
} from './utils';
|
|
126
123
|
|
|
127
|
-
const { Group: CapCheckboxGroup } = CapCheckbox;
|
|
128
124
|
export const Rcs = (props) => {
|
|
129
125
|
const {
|
|
130
126
|
intl,
|
|
@@ -138,17 +134,15 @@ export const Rcs = (props) => {
|
|
|
138
134
|
templatesActions,
|
|
139
135
|
globalActions,
|
|
140
136
|
location,
|
|
141
|
-
handleClose,
|
|
142
137
|
getDefaultTags,
|
|
143
138
|
supportedTags,
|
|
144
139
|
metaEntities,
|
|
145
140
|
injectedTags,
|
|
146
141
|
loadingTags,
|
|
147
142
|
getFormData,
|
|
148
|
-
isDltEnabled,
|
|
149
143
|
smsRegister,
|
|
144
|
+
orgUnitId,
|
|
150
145
|
selectedOfferDetails,
|
|
151
|
-
currentOrgDetails,
|
|
152
146
|
eventContextTags,
|
|
153
147
|
accountData = {},
|
|
154
148
|
// TestAndPreviewSlidebox props
|
|
@@ -158,8 +152,8 @@ export const Rcs = (props) => {
|
|
|
158
152
|
} = props || {};
|
|
159
153
|
const { formatMessage } = intl;
|
|
160
154
|
const { TextArea } = CapInput;
|
|
161
|
-
const { CapCustomCardList } = CapCustomCard;
|
|
162
155
|
const [isEditFlow, setEditFlow] = useState(false);
|
|
156
|
+
const isEditLike = isEditFlow || !isFullMode;
|
|
163
157
|
const [tags, updateTags] = useState([]);
|
|
164
158
|
const [spin, setSpin] = useState(false);
|
|
165
159
|
//template
|
|
@@ -168,112 +162,71 @@ export const Rcs = (props) => {
|
|
|
168
162
|
const [templateMediaType, setTemplateMediaType] = useState(
|
|
169
163
|
RCS_MEDIA_TYPES.NONE,
|
|
170
164
|
);
|
|
171
|
-
const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
|
|
172
165
|
const [templateTitle, setTemplateTitle] = useState('');
|
|
173
166
|
const [templateDesc, setTemplateDesc] = useState('');
|
|
174
167
|
const [templateDescError, setTemplateDescError] = useState(false);
|
|
175
168
|
const [templateStatus, setTemplateStatus] = useState('');
|
|
176
|
-
const [templateDate, setTemplateDate] = useState('');
|
|
177
|
-
//fallback
|
|
178
|
-
const [fallbackMessage, setFallbackMessage] = useState('');
|
|
179
|
-
const [fallbackMessageError, setFallbackMessageError] = useState(false);
|
|
180
169
|
//fallback dlt
|
|
181
170
|
const [showDltContainer, setShowDltContainer] = useState(false);
|
|
182
171
|
const [dltMode, setDltMode] = useState('');
|
|
183
172
|
const [dltEditData, setDltEditData] = useState({});
|
|
184
|
-
|
|
185
|
-
const [
|
|
186
|
-
const [dltPreviewData, setDltPreviewData] = useState('');
|
|
173
|
+
/** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
|
|
174
|
+
const [smsFallbackData, setSmsFallbackData] = useState(undefined);
|
|
187
175
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
|
|
188
|
-
const
|
|
176
|
+
const buttonType = RCS_BUTTON_TYPES.NONE;
|
|
189
177
|
const [suggestionError, setSuggestionError] = useState(true);
|
|
190
178
|
const [templateType, setTemplateType] = useState('text_message');
|
|
191
|
-
const [templateHeader, setTemplateHeader] = useState('');
|
|
192
|
-
const [templateMessage, setTemplateMessage] = useState('');
|
|
193
|
-
const [templateHeaderError, setTemplateHeaderError] = useState('');
|
|
194
|
-
const [templateMessageError, setTemplateMessageError] = useState('');
|
|
195
|
-
const validVarRegex = /\{\{(\d+)\}\}/g;
|
|
196
|
-
const [updatedTitleData, setUpdatedTitleData] = useState([]);
|
|
197
|
-
const [updatedDescData, setUpdatedDescData] = useState([]);
|
|
198
179
|
const [titleVarMappedData, setTitleVarMappedData] = useState({});
|
|
199
180
|
const [descVarMappedData, setDescVarMappedData] = useState({});
|
|
200
181
|
const [titleTextAreaId, setTitleTextAreaId] = useState();
|
|
201
182
|
const [descTextAreaId, setDescTextAreaId] = useState();
|
|
202
183
|
const [assetList, setAssetList] = useState({});
|
|
203
|
-
const [assetListImage, setAssetListImage] = useState('');
|
|
204
184
|
const [rcsImageSrc, updateRcsImageSrc] = useState('');
|
|
205
185
|
const [rcsVideoSrc, setRcsVideoSrc] = useState({});
|
|
206
186
|
const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
|
|
207
187
|
const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
208
|
-
const [imageError, setImageError] = useState(null);
|
|
209
188
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
210
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
|
+
);
|
|
211
203
|
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
226
|
-
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
227
|
-
|
|
228
|
-
// Build media preview object (same pattern as getRcsPreview)
|
|
229
|
-
const mediaPreview = {};
|
|
230
|
-
if (isMediaTypeImage && rcsImageSrc) {
|
|
231
|
-
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
232
|
-
}
|
|
233
|
-
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
234
|
-
// For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
|
|
235
|
-
if (rcsThumbnailSrc) {
|
|
236
|
-
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
237
|
-
} else if (rcsVideoSrc?.videoSrc) {
|
|
238
|
-
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
239
|
-
}
|
|
240
|
-
// Also include thumbnailSrc separately if available
|
|
241
|
-
if (rcsThumbnailSrc) {
|
|
242
|
-
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
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;
|
|
243
217
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// templateDesc is used for ALL types (text message body or rich card description)
|
|
250
|
-
// For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
|
|
251
|
-
const contentObj = {
|
|
252
|
-
// Map templateTitle to templateHeader and templateDesc to templateMessage
|
|
253
|
-
templateHeader: templateTitle,
|
|
254
|
-
templateMessage: templateDesc,
|
|
255
|
-
...mediaPreview,
|
|
256
|
-
...(suggestions.length > 0 && {
|
|
257
|
-
suggestions: suggestions,
|
|
258
|
-
}),
|
|
259
|
-
};
|
|
218
|
+
lastTagSchemaQueryKeyRef.current = key;
|
|
219
|
+
globalActions.fetchSchemaForEntity(query);
|
|
220
|
+
},
|
|
221
|
+
[globalActions],
|
|
222
|
+
);
|
|
260
223
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
templateMediaType,
|
|
264
|
-
templateTitle,
|
|
265
|
-
templateDesc,
|
|
266
|
-
rcsImageSrc,
|
|
267
|
-
rcsVideoSrc,
|
|
268
|
-
rcsThumbnailSrc,
|
|
269
|
-
suggestions,
|
|
270
|
-
selectedDimension,
|
|
271
|
-
]);
|
|
224
|
+
// TestAndPreviewSlidebox state
|
|
225
|
+
const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
|
|
272
226
|
|
|
273
227
|
// Handle Test and Preview button click
|
|
274
228
|
const handleTestAndPreview = useCallback(() => {
|
|
275
229
|
setShowTestAndPreviewSlidebox(true);
|
|
276
|
-
setIsTestAndPreviewMode(true);
|
|
277
230
|
if (propsHandleTestAndPreview) {
|
|
278
231
|
propsHandleTestAndPreview();
|
|
279
232
|
}
|
|
@@ -282,23 +235,24 @@ export const Rcs = (props) => {
|
|
|
282
235
|
// Handle close Test and Preview slidebox
|
|
283
236
|
const handleCloseTestAndPreview = useCallback(() => {
|
|
284
237
|
setShowTestAndPreviewSlidebox(false);
|
|
285
|
-
setIsTestAndPreviewMode(false);
|
|
286
238
|
if (propsHandleCloseTestAndPreview) {
|
|
287
239
|
propsHandleCloseTestAndPreview();
|
|
288
240
|
}
|
|
289
241
|
}, [propsHandleCloseTestAndPreview]);
|
|
290
242
|
|
|
291
|
-
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
};
|
|
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
|
+
}, []);
|
|
300
251
|
|
|
252
|
+
/** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
|
|
301
253
|
const [accountId, setAccountId] = useState('');
|
|
254
|
+
/** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
|
|
255
|
+
const [wecrmAccountId, setWecrmAccountId] = useState('');
|
|
302
256
|
const [accessToken, setAccessToken] = useState('');
|
|
303
257
|
const [hostName, setHostName] = useState('');
|
|
304
258
|
const [accountName, setAccountName] = useState('');
|
|
@@ -306,14 +260,23 @@ export const Rcs = (props) => {
|
|
|
306
260
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
307
261
|
if (!isEmpty(accountObj)) {
|
|
308
262
|
const {
|
|
263
|
+
id: wecrmId,
|
|
309
264
|
sourceAccountIdentifier = '',
|
|
310
265
|
configs = {},
|
|
311
266
|
} = accountObj;
|
|
312
|
-
|
|
313
267
|
setAccountId(sourceAccountIdentifier);
|
|
268
|
+
setWecrmAccountId(
|
|
269
|
+
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
270
|
+
);
|
|
314
271
|
setAccessToken(configs.accessToken || '');
|
|
315
272
|
setHostName(accountObj.hostName || '');
|
|
316
273
|
setAccountName(accountObj.name || '');
|
|
274
|
+
} else {
|
|
275
|
+
setAccountId('');
|
|
276
|
+
setWecrmAccountId('');
|
|
277
|
+
setAccessToken('');
|
|
278
|
+
setHostName('');
|
|
279
|
+
setAccountName('');
|
|
317
280
|
}
|
|
318
281
|
}, [accountData.selectedRcsAccount]);
|
|
319
282
|
|
|
@@ -331,10 +294,7 @@ export const Rcs = (props) => {
|
|
|
331
294
|
label: formatMessage(messages.mediaVideo),
|
|
332
295
|
},
|
|
333
296
|
];
|
|
334
|
-
const
|
|
335
|
-
const isAiContentBotDisabled = accessibleFeatures?.includes(
|
|
336
|
-
AI_CONTENT_BOT_DISABLED
|
|
337
|
-
);
|
|
297
|
+
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
338
298
|
|
|
339
299
|
const updateButtonChange = (data, index) => {
|
|
340
300
|
if (data && data.text) {
|
|
@@ -372,7 +332,9 @@ export const Rcs = (props) => {
|
|
|
372
332
|
if (isFullMode) return;
|
|
373
333
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
374
334
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
375
|
-
const
|
|
335
|
+
const slotOffset =
|
|
336
|
+
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
337
|
+
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
376
338
|
if (!resolved) {
|
|
377
339
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
378
340
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -417,13 +379,41 @@ export const Rcs = (props) => {
|
|
|
417
379
|
|
|
418
380
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
419
381
|
|
|
420
|
-
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) => {
|
|
421
403
|
if (!str) return '';
|
|
422
|
-
const arr =
|
|
404
|
+
const arr = splitTemplateVarStringRcs(str);
|
|
405
|
+
let varOrdinal = 0;
|
|
423
406
|
return arr.map((elem) => {
|
|
424
407
|
if (rcsVarTestRegex.test(elem)) {
|
|
425
408
|
const key = getVarNameFromToken(elem);
|
|
426
|
-
const
|
|
409
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
410
|
+
varOrdinal += 1;
|
|
411
|
+
const v = resolveCardVarMappedSlotValue(
|
|
412
|
+
cardVarMapped,
|
|
413
|
+
key,
|
|
414
|
+
globalSlot,
|
|
415
|
+
isEditLike,
|
|
416
|
+
);
|
|
427
417
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
428
418
|
return String(v);
|
|
429
419
|
}
|
|
@@ -431,107 +421,154 @@ export const Rcs = (props) => {
|
|
|
431
421
|
}).join('');
|
|
432
422
|
};
|
|
433
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;
|
|
434
433
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
+
]);
|
|
455
483
|
|
|
484
|
+
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
456
485
|
|
|
457
|
-
const RcsLabel = styled.div`
|
|
458
|
-
display: flex;
|
|
459
|
-
margin-top: 20px;
|
|
460
|
-
`;
|
|
461
486
|
const paramObj = params || {};
|
|
462
487
|
useEffect(() => {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}, [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]);
|
|
472
497
|
|
|
473
498
|
useEffect(() => {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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;
|
|
482
525
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
487
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
488
|
-
const id = `${elem}_${idx}`;
|
|
489
|
-
const varName = getVarNameFromToken(elem);
|
|
490
|
-
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
491
|
-
nextVarMap[id] = mappedValue;
|
|
492
|
-
if (mappedValue !== '') {
|
|
493
|
-
nextUpdated[idx] = mappedValue;
|
|
494
|
-
} else {
|
|
495
|
-
nextUpdated[idx] = elem;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
setVarMap(nextVarMap);
|
|
500
|
-
setUpdated(nextUpdated);
|
|
501
|
-
};
|
|
526
|
+
});
|
|
527
|
+
setVarMap(nextVarMap);
|
|
528
|
+
};
|
|
502
529
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
530
|
+
initField(templateTitle, setTitleVarMappedData, 0);
|
|
531
|
+
initField(templateDesc, setDescVarMappedData, titleTokenCount);
|
|
532
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
533
|
+
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
if (!isEditFlow && isFullMode) {
|
|
509
536
|
setRcsVideoSrc({});
|
|
510
537
|
updateRcsImageSrc('');
|
|
511
538
|
setUpdateRcsImageSrc('');
|
|
512
539
|
updateRcsThumbnailSrc('');
|
|
513
540
|
setAssetList({});
|
|
514
|
-
|
|
515
|
-
|
|
541
|
+
}
|
|
542
|
+
}, [templateMediaType]);
|
|
516
543
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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':
|
|
521
555
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
522
556
|
break;
|
|
523
|
-
case
|
|
557
|
+
case 'pending':
|
|
524
558
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
525
559
|
break;
|
|
526
|
-
case
|
|
560
|
+
case 'awaitingapproval':
|
|
527
561
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
528
562
|
break;
|
|
529
|
-
case
|
|
563
|
+
case 'unavailable':
|
|
530
564
|
setTemplateStatus(RCS_STATUSES.unavailable);
|
|
531
565
|
break;
|
|
532
|
-
case
|
|
566
|
+
case 'rejected':
|
|
533
567
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
534
568
|
break;
|
|
569
|
+
case 'created':
|
|
570
|
+
setTemplateStatus(RCS_STATUSES.created);
|
|
571
|
+
break;
|
|
535
572
|
default:
|
|
536
573
|
setTemplateStatus(status);
|
|
537
574
|
break;
|
|
@@ -542,7 +579,6 @@ export const Rcs = (props) => {
|
|
|
542
579
|
if (mediaType) {
|
|
543
580
|
setTemplateMediaType(mediaType);
|
|
544
581
|
}
|
|
545
|
-
const tempOrientation = cardSettings.cardOrientation;
|
|
546
582
|
const tempAlignment = cardSettings.mediaAlignment;
|
|
547
583
|
const tempHeight = mediaData.height;
|
|
548
584
|
|
|
@@ -585,44 +621,197 @@ export const Rcs = (props) => {
|
|
|
585
621
|
};
|
|
586
622
|
|
|
587
623
|
useEffect(() => {
|
|
588
|
-
const details =
|
|
624
|
+
const details = rcsHydrationDetails;
|
|
589
625
|
if (details && Object.keys(details).length > 0) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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);
|
|
593
660
|
}
|
|
594
|
-
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', '');
|
|
595
703
|
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
596
704
|
setTemplateType(contentType.text_message);
|
|
597
705
|
} else {
|
|
598
706
|
setTemplateType(contentType.rich_card);
|
|
599
707
|
}
|
|
600
708
|
setEditFlow(true);
|
|
601
|
-
setTemplateName(details
|
|
602
|
-
const loadedTitle =
|
|
603
|
-
const loadedDesc =
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
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
|
+
});
|
|
607
719
|
setTemplateTitle(normalizedTitle);
|
|
608
720
|
setTemplateDesc(normalizedDesc);
|
|
609
|
-
setSuggestions(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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', '');
|
|
613
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
|
+
}
|
|
614
803
|
}
|
|
615
|
-
}, [
|
|
616
|
-
|
|
804
|
+
}, [rcsHydrationDetails, isFullMode]);
|
|
617
805
|
|
|
618
806
|
useEffect(() => {
|
|
619
807
|
if (templateType === contentType.text_message) {
|
|
620
808
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
621
|
-
|
|
622
|
-
|
|
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.
|
|
623
811
|
if (!isEditFlow && isFullMode) {
|
|
812
|
+
setTemplateTitle('');
|
|
813
|
+
setTemplateTitleError('');
|
|
624
814
|
setUpdateRcsImageSrc('');
|
|
625
|
-
setUpdateRcsVideoSrc({});
|
|
626
815
|
setRcsVideoSrc({});
|
|
627
816
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
628
817
|
}
|
|
@@ -649,7 +838,8 @@ export const Rcs = (props) => {
|
|
|
649
838
|
if (!showDltContainer) {
|
|
650
839
|
const { type, module } = location.query || {};
|
|
651
840
|
const isEmbedded = type === EMBEDDED;
|
|
652
|
-
|
|
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';
|
|
653
843
|
const embedded = isEmbedded ? type : FULL;
|
|
654
844
|
const query = {
|
|
655
845
|
layout: SMS,
|
|
@@ -660,9 +850,9 @@ export const Rcs = (props) => {
|
|
|
660
850
|
if (getDefaultTags) {
|
|
661
851
|
query.context = getDefaultTags;
|
|
662
852
|
}
|
|
663
|
-
|
|
853
|
+
fetchTagSchemaIfNewQuery(query);
|
|
664
854
|
}
|
|
665
|
-
}, [showDltContainer]);
|
|
855
|
+
}, [showDltContainer, fetchTagSchemaIfNewQuery]);
|
|
666
856
|
|
|
667
857
|
useEffect(() => {
|
|
668
858
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -685,39 +875,72 @@ export const Rcs = (props) => {
|
|
|
685
875
|
context,
|
|
686
876
|
embedded,
|
|
687
877
|
};
|
|
688
|
-
|
|
878
|
+
if (getDefaultTags) {
|
|
879
|
+
query.context = getDefaultTags;
|
|
880
|
+
}
|
|
881
|
+
fetchTagSchemaIfNewQuery(query);
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
|
|
885
|
+
if (!templateStr || !numericVarName || !tagName) return templateStr;
|
|
886
|
+
const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
|
|
887
|
+
return templateStr.replace(re, `{{${tagName}}}`);
|
|
689
888
|
};
|
|
690
889
|
|
|
691
|
-
const onTagSelect = (data, areaId) => {
|
|
890
|
+
const onTagSelect = (data, areaId, field) => {
|
|
692
891
|
if (!areaId) return;
|
|
693
892
|
const sep = areaId.lastIndexOf('_');
|
|
694
893
|
if (sep === -1) return;
|
|
695
|
-
const
|
|
696
|
-
if (isNaN(
|
|
894
|
+
const slotSuffix = areaId.slice(sep + 1);
|
|
895
|
+
if (slotSuffix === '' || isNaN(Number(slotSuffix))) return;
|
|
697
896
|
const token = areaId.slice(0, sep);
|
|
698
897
|
const variableName = getVarNameFromToken(token);
|
|
699
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
|
+
|
|
700
904
|
setCardVarMapped((prev) => {
|
|
701
905
|
const base = (prev?.[variableName] ?? '').toString();
|
|
702
906
|
const nextVal = `${base}{{${data}}}`;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
[variableName]
|
|
706
|
-
|
|
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;
|
|
707
920
|
});
|
|
708
|
-
};
|
|
709
921
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
+
}
|
|
719
939
|
};
|
|
720
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);
|
|
721
944
|
|
|
722
945
|
//removing optout tag for rcs
|
|
723
946
|
const getRcsTags = () => {
|
|
@@ -730,15 +953,14 @@ export const Rcs = (props) => {
|
|
|
730
953
|
};
|
|
731
954
|
// tag Code end
|
|
732
955
|
|
|
733
|
-
const renderLabel = (value,
|
|
734
|
-
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
956
|
+
const renderLabel = (value, desc) => {
|
|
735
957
|
return (
|
|
736
958
|
<>
|
|
737
|
-
<
|
|
959
|
+
<div className="rcs-form-section-heading">
|
|
738
960
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
739
|
-
</
|
|
961
|
+
</div>
|
|
740
962
|
{desc && (
|
|
741
|
-
<CapLabel type="label3"
|
|
963
|
+
<CapLabel type="label3" className="rcs-form-field-caption">
|
|
742
964
|
{formatMessage(messages[desc])}
|
|
743
965
|
</CapLabel>
|
|
744
966
|
)}
|
|
@@ -824,47 +1046,6 @@ export const Rcs = (props) => {
|
|
|
824
1046
|
setTemplateDescError(error);
|
|
825
1047
|
};
|
|
826
1048
|
|
|
827
|
-
|
|
828
|
-
const templateDescErrorHandler = (value) => {
|
|
829
|
-
let errorMessage = false;
|
|
830
|
-
const { isBraceError } = validateTags({
|
|
831
|
-
content: value,
|
|
832
|
-
tagsParam: tags,
|
|
833
|
-
location,
|
|
834
|
-
tagModule: getDefaultTags,
|
|
835
|
-
isFullMode,
|
|
836
|
-
}) || {};
|
|
837
|
-
|
|
838
|
-
const maxLength = templateType === contentType.text_message
|
|
839
|
-
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
840
|
-
: RCS_RICH_CARD_MAX_LENGTH;
|
|
841
|
-
|
|
842
|
-
if (value === '' && isMediaTypeText) {
|
|
843
|
-
errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
844
|
-
} else if (value?.length > maxLength) {
|
|
845
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
if (isBraceError) {
|
|
849
|
-
errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
850
|
-
}
|
|
851
|
-
return errorMessage;
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
const onFallbackMessageChange = ({ target: { value } }) => {
|
|
856
|
-
const error = fallbackMessageErrorHandler(value);
|
|
857
|
-
setFallbackMessage(value);
|
|
858
|
-
setFallbackMessageError(error);
|
|
859
|
-
};
|
|
860
|
-
|
|
861
|
-
const fallbackMessageErrorHandler = (value) => {
|
|
862
|
-
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
863
|
-
return formatMessage(messages.fallbackMsgLenError);
|
|
864
|
-
}
|
|
865
|
-
return false;
|
|
866
|
-
};
|
|
867
|
-
|
|
868
1049
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
869
1050
|
const forbiddenCharactersValidation = (value) => {
|
|
870
1051
|
if (!value) return false;
|
|
@@ -915,39 +1096,12 @@ export const Rcs = (props) => {
|
|
|
915
1096
|
}
|
|
916
1097
|
return false;
|
|
917
1098
|
};
|
|
918
|
-
|
|
919
|
-
const templateHeaderErrorHandler = (value) => {
|
|
920
|
-
let errorMessage = false;
|
|
921
|
-
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
922
|
-
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
923
|
-
} else {
|
|
924
|
-
errorMessage = variableErrorHandling(value);
|
|
925
|
-
}
|
|
926
|
-
return errorMessage;
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
const templateMessageErrorHandler = (value) => {
|
|
931
|
-
let errorMessage = false;
|
|
932
|
-
if (value === '') {
|
|
933
|
-
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
934
|
-
} else if (
|
|
935
|
-
value?.length
|
|
936
|
-
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
937
|
-
) {
|
|
938
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
939
|
-
} else {
|
|
940
|
-
errorMessage = variableErrorHandling(value);
|
|
941
|
-
}
|
|
942
|
-
return errorMessage;
|
|
943
|
-
};
|
|
944
|
-
|
|
945
1099
|
|
|
946
1100
|
const onMessageAddVar = () => {
|
|
947
|
-
onAddVar(
|
|
1101
|
+
onAddVar(templateDesc);
|
|
948
1102
|
};
|
|
949
1103
|
|
|
950
|
-
const onAddVar = (
|
|
1104
|
+
const onAddVar = (messageContent) => {
|
|
951
1105
|
// Always append the next variable at the end, like WhatsApp
|
|
952
1106
|
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
953
1107
|
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
@@ -987,66 +1141,6 @@ const onTitleAddVar = () => {
|
|
|
987
1141
|
setTemplateTitleError(error);
|
|
988
1142
|
};
|
|
989
1143
|
|
|
990
|
-
|
|
991
|
-
const splitTemplateVarString = (str) => {
|
|
992
|
-
if (!str) return [];
|
|
993
|
-
const validVarArr = str.match(rcsVarRegex) || [];
|
|
994
|
-
const templateVarArray = [];
|
|
995
|
-
let content = str;
|
|
996
|
-
while (content?.length !== 0) {
|
|
997
|
-
const index = content.indexOf(validVarArr?.[0]);
|
|
998
|
-
if (index !== -1) {
|
|
999
|
-
templateVarArray.push(content.substring(0, index));
|
|
1000
|
-
templateVarArray.push(validVarArr?.[0]);
|
|
1001
|
-
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
1002
|
-
validVarArr?.shift();
|
|
1003
|
-
} else {
|
|
1004
|
-
templateVarArray.push(content);
|
|
1005
|
-
break;
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
return templateVarArray.filter(Boolean);
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
const textAreaValue = (idValue, type) => {
|
|
1012
|
-
if (idValue >= 0) {
|
|
1013
|
-
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1014
|
-
const templateArr = splitTemplateVarString(templateStr);
|
|
1015
|
-
const token = templateArr?.[idValue] || "";
|
|
1016
|
-
if (token && rcsVarTestRegex.test(token)) {
|
|
1017
|
-
const varName = getVarNameFromToken(token);
|
|
1018
|
-
return (cardVarMapped?.[varName] ?? '').toString();
|
|
1019
|
-
}
|
|
1020
|
-
return "";
|
|
1021
|
-
}
|
|
1022
|
-
return "";
|
|
1023
|
-
};
|
|
1024
|
-
|
|
1025
|
-
const textAreaValueChange = (e, type) => {
|
|
1026
|
-
const value = e?.target?.value ?? '';
|
|
1027
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1028
|
-
if (!id) return;
|
|
1029
|
-
const sep = id.lastIndexOf('_');
|
|
1030
|
-
if (sep === -1) return;
|
|
1031
|
-
const isInvalidValue = value?.trim() === "";
|
|
1032
|
-
const token = id.slice(0, sep);
|
|
1033
|
-
const variableName = getVarNameFromToken(token);
|
|
1034
|
-
|
|
1035
|
-
if (variableName) {
|
|
1036
|
-
setCardVarMapped((prev) => ({
|
|
1037
|
-
...prev,
|
|
1038
|
-
[variableName]: isInvalidValue ? "" : value,
|
|
1039
|
-
}));
|
|
1040
|
-
}
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
const setTextAreaId = (e, type) => {
|
|
1044
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1045
|
-
if (!id) return;
|
|
1046
|
-
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
1047
|
-
else setDescTextAreaId(id);
|
|
1048
|
-
};
|
|
1049
|
-
|
|
1050
1144
|
const renderButtonComponent = () => {
|
|
1051
1145
|
return (
|
|
1052
1146
|
<>
|
|
@@ -1077,39 +1171,56 @@ const splitTemplateVarString = (str) => {
|
|
|
1077
1171
|
);
|
|
1078
1172
|
};
|
|
1079
1173
|
|
|
1080
|
-
const
|
|
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
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
+
});
|
|
1113
1224
|
};
|
|
1114
1225
|
|
|
1115
1226
|
const renderTextComponent = () => {
|
|
@@ -1153,10 +1264,19 @@ const splitTemplateVarString = (str) => {
|
|
|
1153
1264
|
</>
|
|
1154
1265
|
}
|
|
1155
1266
|
/>
|
|
1156
|
-
<div className="rcs_text_area_wrapper">
|
|
1157
1267
|
{(isEditFlow || !isFullMode) ? (
|
|
1158
|
-
|
|
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
|
+
/>
|
|
1159
1278
|
) : (
|
|
1279
|
+
<div className="rcs_text_area_wrapper">
|
|
1160
1280
|
<CapInput
|
|
1161
1281
|
className={`rcs-template-title-input ${
|
|
1162
1282
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -1169,8 +1289,8 @@ const splitTemplateVarString = (str) => {
|
|
|
1169
1289
|
errorMessage={templateTitleError}
|
|
1170
1290
|
disabled={isEditFlow || !isFullMode}
|
|
1171
1291
|
/>
|
|
1292
|
+
</div>
|
|
1172
1293
|
)}
|
|
1173
|
-
</div>
|
|
1174
1294
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
1175
1295
|
<CapError className="rcs-template-title-error">
|
|
1176
1296
|
{templateTitleError}
|
|
@@ -1218,7 +1338,18 @@ const splitTemplateVarString = (str) => {
|
|
|
1218
1338
|
<CapRow className="rcs-create-template-message-input">
|
|
1219
1339
|
<div className="rcs_text_area_wrapper">
|
|
1220
1340
|
{(isEditFlow || !isFullMode)
|
|
1221
|
-
?
|
|
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
|
+
)
|
|
1222
1353
|
: (
|
|
1223
1354
|
<>
|
|
1224
1355
|
<TextArea
|
|
@@ -1240,7 +1371,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1240
1371
|
data-testid="rcs_text_area"
|
|
1241
1372
|
disabled={(isEditFlow || !isFullMode)}
|
|
1242
1373
|
/>
|
|
1243
|
-
{!
|
|
1374
|
+
{!aiContentBotDisabled && !isEditFlow && (
|
|
1244
1375
|
<CapAskAira.ContentGenerationBot
|
|
1245
1376
|
text={templateDesc || ""}
|
|
1246
1377
|
setText={(text) => onTemplateDescChange({ target: { value: text } })}
|
|
@@ -1262,7 +1393,9 @@ const splitTemplateVarString = (str) => {
|
|
|
1262
1393
|
{templateDescError}
|
|
1263
1394
|
</CapError>
|
|
1264
1395
|
)}
|
|
1265
|
-
{
|
|
1396
|
+
{(isEditFlow || !isFullMode)
|
|
1397
|
+
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
1398
|
+
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1266
1399
|
{!isFullMode && hasTag() && (
|
|
1267
1400
|
<CapAlert
|
|
1268
1401
|
message={
|
|
@@ -1282,18 +1415,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1282
1415
|
);
|
|
1283
1416
|
};
|
|
1284
1417
|
|
|
1285
|
-
|
|
1286
|
-
const fallbackSmsLength = () => (
|
|
1287
|
-
<CapLabel type="label1" className="fallback-sms-length">
|
|
1288
|
-
{formatMessage(messages.totalCharacters, {
|
|
1289
|
-
smsCount: Math.ceil(
|
|
1290
|
-
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1291
|
-
),
|
|
1292
|
-
number: fallbackMessage?.length,
|
|
1293
|
-
})}
|
|
1294
|
-
</CapLabel>
|
|
1295
|
-
);
|
|
1296
|
-
|
|
1297
1418
|
// Get character count for title (rich card only)
|
|
1298
1419
|
const getTitleCharacterCount = () => {
|
|
1299
1420
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -1353,7 +1474,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1353
1474
|
const hasTag = () => {
|
|
1354
1475
|
// Check cardVarMapped values for tags
|
|
1355
1476
|
if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
|
|
1356
|
-
const hasTagInMapped = Object.values(cardVarMapped).some(value =>
|
|
1477
|
+
const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
|
|
1357
1478
|
isTagIncluded(value)
|
|
1358
1479
|
);
|
|
1359
1480
|
if (hasTagInMapped) return true;
|
|
@@ -1371,14 +1492,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1371
1492
|
return false;
|
|
1372
1493
|
};
|
|
1373
1494
|
|
|
1374
|
-
//adding creative dlt fallback sms handlers
|
|
1375
|
-
const addDltMsgHandler = () => {
|
|
1376
|
-
setShowDltContainer(true);
|
|
1377
|
-
setDltMode(RCS_DLT_MODE.TEMPLATES);
|
|
1378
|
-
setDltEditData({});
|
|
1379
|
-
setFallbackMessage('');
|
|
1380
|
-
};
|
|
1381
|
-
|
|
1382
1495
|
const closeDltContainerHandler = () => {
|
|
1383
1496
|
setShowDltContainer(false);
|
|
1384
1497
|
setDltMode('');
|
|
@@ -1401,68 +1514,17 @@ const splitTemplateVarString = (str) => {
|
|
|
1401
1514
|
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
1402
1515
|
'',
|
|
1403
1516
|
);
|
|
1517
|
+
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
1518
|
+
|| get(tempData, 'versions.base.name', '')
|
|
1519
|
+
|| '';
|
|
1404
1520
|
closeDltContainerHandler();
|
|
1405
1521
|
setDltEditData(tempData);
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
closeDltContainerHandler();
|
|
1413
|
-
setDltEditData({});
|
|
1414
|
-
setFallbackMessage('');
|
|
1415
|
-
setFallbackMessageError(false);
|
|
1416
|
-
setShowDltCard(false);
|
|
1417
|
-
};
|
|
1418
|
-
|
|
1419
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
1420
|
-
const {
|
|
1421
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
1422
|
-
'sms-editor': smsEditor = '',
|
|
1423
|
-
} = data.versions.base || {};
|
|
1424
|
-
setFallbackPreviewmode(true);
|
|
1425
|
-
setDltPreviewData(
|
|
1426
|
-
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1427
|
-
);
|
|
1428
|
-
};
|
|
1429
|
-
|
|
1430
|
-
const getDltContentCardList = (content, channel) => {
|
|
1431
|
-
const extra = [
|
|
1432
|
-
<CapIcon
|
|
1433
|
-
type="edit"
|
|
1434
|
-
style={{ marginRight: '8px' }}
|
|
1435
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1436
|
-
/>,
|
|
1437
|
-
<CapDropdown
|
|
1438
|
-
overlay={(
|
|
1439
|
-
<CapMenu>
|
|
1440
|
-
<>
|
|
1441
|
-
<CapMenu.Item
|
|
1442
|
-
className="ant-dropdown-menu-item"
|
|
1443
|
-
onClick={() => setFallbackPreviewmode(true)}
|
|
1444
|
-
>
|
|
1445
|
-
{formatMessage(globalMessages.preview)}
|
|
1446
|
-
</CapMenu.Item>
|
|
1447
|
-
<CapMenu.Item
|
|
1448
|
-
className="ant-dropdown-menu-item"
|
|
1449
|
-
onClick={rcsDltCardDeleteHandler}
|
|
1450
|
-
>
|
|
1451
|
-
{formatMessage(globalMessages.delete)}
|
|
1452
|
-
</CapMenu.Item>
|
|
1453
|
-
</>
|
|
1454
|
-
</CapMenu>
|
|
1455
|
-
)}
|
|
1456
|
-
>
|
|
1457
|
-
<CapIcon type="more" />
|
|
1458
|
-
</CapDropdown>,
|
|
1459
|
-
];
|
|
1460
|
-
return {
|
|
1461
|
-
title: channel,
|
|
1462
|
-
content,
|
|
1463
|
-
cardType: channel,
|
|
1464
|
-
extra,
|
|
1465
|
-
};
|
|
1522
|
+
const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
|
|
1523
|
+
setSmsFallbackData({
|
|
1524
|
+
templateName: templateNameFromDlt,
|
|
1525
|
+
content: fallMsg,
|
|
1526
|
+
...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
|
|
1527
|
+
});
|
|
1466
1528
|
};
|
|
1467
1529
|
|
|
1468
1530
|
const getDltSlideBoxContent = () => {
|
|
@@ -1489,7 +1551,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1489
1551
|
isFullMode={isFullMode}
|
|
1490
1552
|
isDltFromRcs
|
|
1491
1553
|
onSelectTemplate={rcsDltEditSelectHandler}
|
|
1492
|
-
handlePeviewTemplate={dltFallbackListingPreviewhandler}
|
|
1493
1554
|
/>
|
|
1494
1555
|
);
|
|
1495
1556
|
} else if (dltMode === RCS_DLT_MODE.EDIT) {
|
|
@@ -1510,147 +1571,32 @@ const splitTemplateVarString = (str) => {
|
|
|
1510
1571
|
return { dltHeader, dltContent };
|
|
1511
1572
|
};
|
|
1512
1573
|
|
|
1513
|
-
const renderFallBackSmsComponent = () =>
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1538
|
-
// }}
|
|
1539
|
-
// title={formatMessage(messages.fallbackToolTip)}
|
|
1540
|
-
// />
|
|
1541
|
-
// </CapRow>
|
|
1542
|
-
// )}
|
|
1543
|
-
// description={formatMessage(messages.fallbackDesc)}
|
|
1544
|
-
// suffix={
|
|
1545
|
-
// isDltEnabled ? null : (
|
|
1546
|
-
// <CapButton
|
|
1547
|
-
// type="flat"
|
|
1548
|
-
// className="fallback-preview-btn"
|
|
1549
|
-
// prefix={<CapIcon type="eye" />}
|
|
1550
|
-
// style={{ color: CAP_SECONDARY.base }}
|
|
1551
|
-
// onClick={() => setFallbackPreviewmode(true)}
|
|
1552
|
-
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1553
|
-
// >
|
|
1554
|
-
// {formatMessage(globalMessages.preview)}
|
|
1555
|
-
// </CapButton>
|
|
1556
|
-
// )
|
|
1557
|
-
// }
|
|
1558
|
-
// />
|
|
1559
|
-
// </CapRow>,
|
|
1560
|
-
// );
|
|
1561
|
-
|
|
1562
|
-
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1563
|
-
// showAddCreativeBtnForDlt
|
|
1564
|
-
// && contentArr.push(
|
|
1565
|
-
// <CapCard className="rcs-dlt-fallback-card">
|
|
1566
|
-
// <CapRow type="flex" justify="center" align="middle">
|
|
1567
|
-
// <CapColumn span={10}>
|
|
1568
|
-
// <CapImage src={addCreativesIcon} />
|
|
1569
|
-
// </CapColumn>
|
|
1570
|
-
// <CapColumn span={14}>
|
|
1571
|
-
// <CapButton
|
|
1572
|
-
// className="add-dlt-btn"
|
|
1573
|
-
// type="secondary"
|
|
1574
|
-
// onClick={addDltMsgHandler}
|
|
1575
|
-
// >
|
|
1576
|
-
// {formatMessage(messages.addSmsCreative)}
|
|
1577
|
-
// </CapButton>
|
|
1578
|
-
// </CapColumn>
|
|
1579
|
-
// </CapRow>
|
|
1580
|
-
// </CapCard>,
|
|
1581
|
-
// );
|
|
1582
|
-
|
|
1583
|
-
// //dlt is enabled and dlt content is added, show it in a card
|
|
1584
|
-
// showCardForDlt
|
|
1585
|
-
// && contentArr.push(
|
|
1586
|
-
// <CapCustomCardList
|
|
1587
|
-
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1588
|
-
// className="rcs-dlt-card"
|
|
1589
|
-
// />,
|
|
1590
|
-
// fallbackMessageError && (
|
|
1591
|
-
// <CapError className="rcs-fallback-len-error">
|
|
1592
|
-
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1593
|
-
// </CapError>
|
|
1594
|
-
// ),
|
|
1595
|
-
// );
|
|
1596
|
-
|
|
1597
|
-
// //dlt is not enabled, show non dlt text area
|
|
1598
|
-
// showNonDltFallbackComp
|
|
1599
|
-
// && contentArr.push(
|
|
1600
|
-
// <>
|
|
1601
|
-
// <CapRow>
|
|
1602
|
-
// <CapHeader
|
|
1603
|
-
// title={(
|
|
1604
|
-
// <CapHeading type="h4">
|
|
1605
|
-
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1606
|
-
// </CapHeading>
|
|
1607
|
-
// )}
|
|
1608
|
-
// suffix={(
|
|
1609
|
-
// <TagList
|
|
1610
|
-
// label={formatMessage(globalMessages.addLabels)}
|
|
1611
|
-
// onTagSelect={onTagSelectFallback}
|
|
1612
|
-
// location={location}
|
|
1613
|
-
// tags={tags || []}
|
|
1614
|
-
// onContextChange={handleOnTagsContextChange}
|
|
1615
|
-
// injectedTags={injectedTags || {}}
|
|
1616
|
-
// selectedOfferDetails={selectedOfferDetails}
|
|
1617
|
-
// />
|
|
1618
|
-
// )}
|
|
1619
|
-
// />
|
|
1620
|
-
// </CapRow>
|
|
1621
|
-
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1622
|
-
// <TextArea
|
|
1623
|
-
// id="rcs_fallback_message_textarea"
|
|
1624
|
-
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1625
|
-
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1626
|
-
// onChange={onFallbackMessageChange}
|
|
1627
|
-
// errorMessage={fallbackMessageError}
|
|
1628
|
-
// value={fallbackMessage || ""}
|
|
1629
|
-
// />
|
|
1630
|
-
// {!isAiContentBotDisabled && (
|
|
1631
|
-
// <CapAskAira.ContentGenerationBot
|
|
1632
|
-
// text={fallbackMessage || ""}
|
|
1633
|
-
// setText={(text) => {
|
|
1634
|
-
// onFallbackMessageChange({ target: { value: text } });
|
|
1635
|
-
// }}
|
|
1636
|
-
// iconPlacement="float-br"
|
|
1637
|
-
// rootStyle={{
|
|
1638
|
-
// bottom: "0.5rem",
|
|
1639
|
-
// right: "0.5rem",
|
|
1640
|
-
// position: "absolute",
|
|
1641
|
-
// }}
|
|
1642
|
-
// />
|
|
1643
|
-
// )}
|
|
1644
|
-
// </div>
|
|
1645
|
-
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1646
|
-
// </>
|
|
1647
|
-
// );
|
|
1648
|
-
|
|
1649
|
-
// return <>{contentArr}</>;
|
|
1650
|
-
};
|
|
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
|
+
);
|
|
1651
1598
|
|
|
1652
1599
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
1653
|
-
setImageError(null);
|
|
1654
1600
|
const isRcsThumbnail = index === 1;
|
|
1655
1601
|
actions.uploadRcsAsset(file, type, {
|
|
1656
1602
|
isRcsThumbnail,
|
|
@@ -1662,7 +1608,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1662
1608
|
|
|
1663
1609
|
const setUpdateRcsImageSrc = useCallback(
|
|
1664
1610
|
(val) => {
|
|
1665
|
-
setAssetListImage(val);
|
|
1666
1611
|
updateRcsImageSrc(val);
|
|
1667
1612
|
actions.clearRcsMediaAsset(0);
|
|
1668
1613
|
},
|
|
@@ -1692,7 +1637,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1692
1637
|
|
|
1693
1638
|
|
|
1694
1639
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1695
|
-
setImageError(null);
|
|
1696
1640
|
actions.uploadRcsAsset(file, type, {
|
|
1697
1641
|
...fileParams,
|
|
1698
1642
|
type: 'video',
|
|
@@ -1701,9 +1645,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1701
1645
|
});
|
|
1702
1646
|
};
|
|
1703
1647
|
|
|
1704
|
-
const updateRcsVideoSrc = (val) => {
|
|
1705
|
-
setRcsVideoSrc(val);
|
|
1706
|
-
};
|
|
1707
1648
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
1708
1649
|
setRcsVideoSrc(val);
|
|
1709
1650
|
setAssetList(val);
|
|
@@ -1732,7 +1673,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1732
1673
|
<>
|
|
1733
1674
|
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
1734
1675
|
<CapImageUpload
|
|
1735
|
-
|
|
1676
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1736
1677
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1737
1678
|
imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
|
|
1738
1679
|
imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
|
|
@@ -1744,7 +1685,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1744
1685
|
updateOnReUpload={updateOnRcsThumbnailReUpload}
|
|
1745
1686
|
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
1746
1687
|
index={1}
|
|
1747
|
-
className="cap-custom-image-upload"
|
|
1748
1688
|
key={`rcs-uploaded-image-${currentDimension}`}
|
|
1749
1689
|
imageData={thumbnailData}
|
|
1750
1690
|
channel={RCS}
|
|
@@ -1775,7 +1715,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1775
1715
|
value: dim.type,
|
|
1776
1716
|
label: `${dim.label}`
|
|
1777
1717
|
}))}
|
|
1778
|
-
|
|
1718
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1779
1719
|
/>
|
|
1780
1720
|
</>
|
|
1781
1721
|
)}
|
|
@@ -1789,7 +1729,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1789
1729
|
</div>
|
|
1790
1730
|
) : (
|
|
1791
1731
|
<CapImageUpload
|
|
1792
|
-
style={{ paddingTop: '20px' }}
|
|
1793
1732
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1794
1733
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
1795
1734
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -1800,7 +1739,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1800
1739
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
1801
1740
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
1802
1741
|
index={0}
|
|
1803
|
-
className="cap-custom-image-upload"
|
|
1742
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1804
1743
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
1805
1744
|
imageData={rcsData}
|
|
1806
1745
|
channel={RCS}
|
|
@@ -1830,7 +1769,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1830
1769
|
value: dim.type,
|
|
1831
1770
|
label: `${dim.label}`
|
|
1832
1771
|
}))}
|
|
1833
|
-
|
|
1772
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1834
1773
|
/>
|
|
1835
1774
|
)}
|
|
1836
1775
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -1890,8 +1829,16 @@ const splitTemplateVarString = (str) => {
|
|
|
1890
1829
|
const getRcsPreview = () => {
|
|
1891
1830
|
|
|
1892
1831
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
1893
|
-
const
|
|
1894
|
-
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;
|
|
1895
1842
|
return (
|
|
1896
1843
|
<UnifiedPreview
|
|
1897
1844
|
channel={RCS}
|
|
@@ -1914,51 +1861,65 @@ const splitTemplateVarString = (str) => {
|
|
|
1914
1861
|
);
|
|
1915
1862
|
};
|
|
1916
1863
|
|
|
1917
|
-
const getUnmappedDesc = (str, mapping) => {
|
|
1918
|
-
if (!str) return '';
|
|
1919
|
-
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1920
|
-
let result = str;
|
|
1921
|
-
const replacements = [];
|
|
1922
|
-
Object.entries(mapping).forEach(([key, value]) => {
|
|
1923
|
-
const raw = (value ?? '').toString();
|
|
1924
|
-
if (!raw || raw?.trim?.() === '') return;
|
|
1925
|
-
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1926
|
-
replacements.push({ key, needle: raw });
|
|
1927
|
-
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1928
|
-
});
|
|
1929
|
-
const seen = new Set();
|
|
1930
|
-
const uniq = replacements
|
|
1931
|
-
.filter(({ key, needle }) => {
|
|
1932
|
-
const id = `${key}::${needle}`;
|
|
1933
|
-
if (seen.has(id)) return false;
|
|
1934
|
-
seen.add(id);
|
|
1935
|
-
return true;
|
|
1936
|
-
})
|
|
1937
|
-
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1938
|
-
|
|
1939
|
-
uniq.forEach(({ key, needle }) => {
|
|
1940
|
-
if (!needle) return;
|
|
1941
|
-
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1942
|
-
const regex = new RegExp(escaped, 'g');
|
|
1943
|
-
result = result.replace(regex, `{{${key}}}`);
|
|
1944
|
-
});
|
|
1945
|
-
return result;
|
|
1946
|
-
};
|
|
1947
|
-
|
|
1948
1864
|
const createPayload = () => {
|
|
1949
|
-
const
|
|
1950
|
-
const {
|
|
1951
|
-
template_id: templateId = '',
|
|
1952
|
-
template_name = '',
|
|
1953
|
-
'sms-editor': template = '',
|
|
1954
|
-
header: registeredSenderIds = [],
|
|
1955
|
-
} = base;
|
|
1956
|
-
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1957
|
-
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
1865
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1958
1866
|
const alignment = isMediaTypeImage
|
|
1959
1867
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
1960
1868
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
1961
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
|
+
|
|
1962
1923
|
const payload = {
|
|
1963
1924
|
name: templateName,
|
|
1964
1925
|
versions: {
|
|
@@ -1970,12 +1931,14 @@ const splitTemplateVarString = (str) => {
|
|
|
1970
1931
|
cardSettings: {
|
|
1971
1932
|
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
1972
1933
|
...(alignment && { mediaAlignment: alignment }),
|
|
1973
|
-
cardWidth:
|
|
1934
|
+
cardWidth: cardWidthFromSelection,
|
|
1974
1935
|
},
|
|
1975
1936
|
cardContent: [
|
|
1976
1937
|
{
|
|
1977
|
-
|
|
1978
|
-
|
|
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,
|
|
1979
1942
|
mediaType: templateMediaType,
|
|
1980
1943
|
...(!isMediaTypeText && {media: {
|
|
1981
1944
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -1984,23 +1947,30 @@ const splitTemplateVarString = (str) => {
|
|
|
1984
1947
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
1985
1948
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
1986
1949
|
}}),
|
|
1987
|
-
...(
|
|
1988
|
-
const
|
|
1989
|
-
...(templateTitle
|
|
1990
|
-
...(templateDesc
|
|
1950
|
+
...(isSlotMappingMode && (() => {
|
|
1951
|
+
const templateVarTokens = [
|
|
1952
|
+
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
1953
|
+
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
1991
1954
|
];
|
|
1992
|
-
const
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
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;
|
|
2001
1971
|
}
|
|
2002
1972
|
});
|
|
2003
|
-
return { cardVarMapped:
|
|
1973
|
+
return { cardVarMapped: persistedSlotVarMap };
|
|
2004
1974
|
})()),
|
|
2005
1975
|
...(suggestions.length > 0 && { suggestions }),
|
|
2006
1976
|
}
|
|
@@ -2008,17 +1978,29 @@ const splitTemplateVarString = (str) => {
|
|
|
2008
1978
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
2009
1979
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
2010
1980
|
},
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
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
|
+
}),
|
|
2019
2001
|
},
|
|
2020
|
-
}
|
|
2021
|
-
},
|
|
2002
|
+
};
|
|
2003
|
+
})()),
|
|
2022
2004
|
},
|
|
2023
2005
|
},
|
|
2024
2006
|
},
|
|
@@ -2028,6 +2010,84 @@ const splitTemplateVarString = (str) => {
|
|
|
2028
2010
|
return payload;
|
|
2029
2011
|
};
|
|
2030
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
|
+
|
|
2031
2091
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
2032
2092
|
// eslint-disable-next-line no-undef
|
|
2033
2093
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -2057,6 +2117,9 @@ const splitTemplateVarString = (str) => {
|
|
|
2057
2117
|
_id: params?.id,
|
|
2058
2118
|
validity: true,
|
|
2059
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,
|
|
2060
2123
|
};
|
|
2061
2124
|
getFormData(formDataParams);
|
|
2062
2125
|
};
|
|
@@ -2070,6 +2133,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2070
2133
|
actionCallback({ resp, errorMessage });
|
|
2071
2134
|
setSpin(false); // Always turn off spinner
|
|
2072
2135
|
if (!errorMessage) {
|
|
2136
|
+
setTemplateStatus(RCS_STATUSES.pending);
|
|
2073
2137
|
onCreateComplete();
|
|
2074
2138
|
}
|
|
2075
2139
|
});
|
|
@@ -2081,6 +2145,64 @@ const splitTemplateVarString = (str) => {
|
|
|
2081
2145
|
}
|
|
2082
2146
|
};
|
|
2083
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
|
+
|
|
2084
2206
|
const isDisableDone = () => {
|
|
2085
2207
|
if(isEditFlow){
|
|
2086
2208
|
return false;
|
|
@@ -2091,40 +2213,16 @@ const splitTemplateVarString = (str) => {
|
|
|
2091
2213
|
}
|
|
2092
2214
|
}
|
|
2093
2215
|
|
|
2094
|
-
if(
|
|
2095
|
-
|
|
2096
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2097
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2098
|
-
|
|
2099
|
-
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2100
|
-
return true;
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
const hasEmptyMapping =
|
|
2104
|
-
cardVarMapped &&
|
|
2105
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2106
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2107
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2108
|
-
return v.trim() === ''; // empty string
|
|
2109
|
-
});
|
|
2110
|
-
|
|
2111
|
-
if (hasEmptyMapping) {
|
|
2112
|
-
return true;
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
const anyMissing = allVars.some(name => {
|
|
2116
|
-
const v = cardVarMapped?.[name];
|
|
2117
|
-
if (typeof v !== 'string') return !v;
|
|
2118
|
-
return v.trim() === '';
|
|
2119
|
-
});
|
|
2120
|
-
if (anyMissing) {
|
|
2121
|
-
return true;
|
|
2122
|
-
}
|
|
2216
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2217
|
+
return true;
|
|
2123
2218
|
}
|
|
2124
2219
|
|
|
2125
|
-
|
|
2220
|
+
if (smsFallbackBlocksDone()) {
|
|
2126
2221
|
return true;
|
|
2222
|
+
}
|
|
2127
2223
|
|
|
2224
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2225
|
+
return true;
|
|
2128
2226
|
}
|
|
2129
2227
|
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
2130
2228
|
return true;
|
|
@@ -2142,53 +2240,36 @@ const splitTemplateVarString = (str) => {
|
|
|
2142
2240
|
return true;
|
|
2143
2241
|
}
|
|
2144
2242
|
}
|
|
2145
|
-
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
|
+
) {
|
|
2146
2250
|
return true;
|
|
2147
2251
|
}
|
|
2148
2252
|
return false;
|
|
2149
2253
|
};
|
|
2150
2254
|
|
|
2151
2255
|
const isEditDisableDone = () => {
|
|
2152
|
-
|
|
2153
2256
|
if (templateStatus !== RCS_STATUSES.approved) {
|
|
2154
2257
|
return true;
|
|
2155
2258
|
}
|
|
2156
2259
|
|
|
2157
|
-
if (!isFullMode) {
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2260
|
+
// if (!isFullMode) {
|
|
2261
|
+
// if (templateName.trim() === '' || templateNameError) {
|
|
2262
|
+
// return true;
|
|
2263
|
+
// }
|
|
2264
|
+
// }
|
|
2265
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2266
|
+
return true;
|
|
2161
2267
|
}
|
|
2162
|
-
if(!isFullMode){
|
|
2163
|
-
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2164
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2165
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2166
|
-
|
|
2167
|
-
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2168
|
-
return true;
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
const hasEmptyMapping =
|
|
2172
|
-
cardVarMapped &&
|
|
2173
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2174
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2175
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2176
|
-
return v.trim() === ''; // empty string
|
|
2177
|
-
});
|
|
2178
2268
|
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
const anyMissing = allVars.some(name => {
|
|
2184
|
-
const v = cardVarMapped?.[name];
|
|
2185
|
-
if (typeof v !== 'string') return !v;
|
|
2186
|
-
return v.trim() === '';
|
|
2187
|
-
});
|
|
2188
|
-
if (anyMissing) {
|
|
2189
|
-
return true;
|
|
2190
|
-
}
|
|
2269
|
+
if (smsFallbackBlocksDone()) {
|
|
2270
|
+
return true;
|
|
2191
2271
|
}
|
|
2272
|
+
|
|
2192
2273
|
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2193
2274
|
return true;
|
|
2194
2275
|
}
|
|
@@ -2207,7 +2288,13 @@ const splitTemplateVarString = (str) => {
|
|
|
2207
2288
|
return true;
|
|
2208
2289
|
}
|
|
2209
2290
|
}
|
|
2210
|
-
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
|
+
) {
|
|
2211
2298
|
return true;
|
|
2212
2299
|
}
|
|
2213
2300
|
return false;
|
|
@@ -2257,52 +2344,56 @@ const splitTemplateVarString = (str) => {
|
|
|
2257
2344
|
};
|
|
2258
2345
|
|
|
2259
2346
|
const getMainContent = () => {
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
return (
|
|
2264
|
-
<CapSlideBox
|
|
2265
|
-
show={showDltContainer}
|
|
2266
|
-
header={dltHeader}
|
|
2267
|
-
content={dltContent}
|
|
2268
|
-
handleClose={closeDltContainerHandler}
|
|
2269
|
-
size="size-xl"
|
|
2270
|
-
/>
|
|
2271
|
-
);
|
|
2272
|
-
}
|
|
2347
|
+
// Slideboxes are rendered outside the page-level spinner to avoid
|
|
2348
|
+
// stacking/blur issues during initial loads.
|
|
2349
|
+
if (showDltContainer) return null;
|
|
2273
2350
|
|
|
2274
2351
|
return (
|
|
2275
2352
|
<>
|
|
2276
|
-
{templateStatus !== '' && (
|
|
2277
|
-
<
|
|
2278
|
-
{
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
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>
|
|
2288
2368
|
)}
|
|
2289
|
-
<CapRow className=
|
|
2369
|
+
<CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
|
|
2290
2370
|
<CapColumn span={14}>
|
|
2291
2371
|
{/* template name */}
|
|
2292
2372
|
{isFullMode && (
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
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
|
+
)
|
|
2306
2397
|
)}
|
|
2307
2398
|
{renderLabel('templateTypeLabel')}
|
|
2308
2399
|
<CapRadioGroup
|
|
@@ -2330,7 +2421,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2330
2421
|
</>
|
|
2331
2422
|
)}
|
|
2332
2423
|
{renderTextComponent()}
|
|
2333
|
-
<CapDivider
|
|
2424
|
+
<CapDivider className="rcs-fallback-section-divider" />
|
|
2334
2425
|
{renderFallBackSmsComponent()}
|
|
2335
2426
|
<div className="rcs-scroll-div" />
|
|
2336
2427
|
</CapColumn>
|
|
@@ -2342,7 +2433,8 @@ const splitTemplateVarString = (str) => {
|
|
|
2342
2433
|
|
|
2343
2434
|
|
|
2344
2435
|
<div className="rcs-footer">
|
|
2345
|
-
{!
|
|
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 && (
|
|
2346
2438
|
<>
|
|
2347
2439
|
<div className="button-disabled-tooltip-wrapper">
|
|
2348
2440
|
<CapButton
|
|
@@ -2363,7 +2455,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2363
2455
|
className="rcs-test-preview-btn"
|
|
2364
2456
|
type="secondary"
|
|
2365
2457
|
disabled={true}
|
|
2366
|
-
style={{ marginLeft: "8px" }}
|
|
2367
2458
|
>
|
|
2368
2459
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
2369
2460
|
</CapButton>
|
|
@@ -2396,51 +2487,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2396
2487
|
</>
|
|
2397
2488
|
)}
|
|
2398
2489
|
</div>
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
{fallbackPreviewmode && (
|
|
2402
|
-
<CapSlideBox
|
|
2403
|
-
className="rcs-fallback-preview"
|
|
2404
|
-
show={fallbackPreviewmode}
|
|
2405
|
-
header={(
|
|
2406
|
-
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2407
|
-
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2408
|
-
</CapHeading>
|
|
2409
|
-
)}
|
|
2410
|
-
content={(
|
|
2411
|
-
<>
|
|
2412
|
-
<UnifiedPreview
|
|
2413
|
-
channel={RCS}
|
|
2414
|
-
content={{
|
|
2415
|
-
rcsPreviewContent: {
|
|
2416
|
-
rcsDesc: tempMsg,
|
|
2417
|
-
},
|
|
2418
|
-
}}
|
|
2419
|
-
device={ANDROID}
|
|
2420
|
-
showDeviceToggle={false}
|
|
2421
|
-
showHeader={false}
|
|
2422
|
-
formatMessage={formatMessage}
|
|
2423
|
-
/>
|
|
2424
|
-
<CapHeading
|
|
2425
|
-
type="h3"
|
|
2426
|
-
style={{ textAlign: 'center' }}
|
|
2427
|
-
className="margin-t-16"
|
|
2428
|
-
>
|
|
2429
|
-
{formatMessage(messages.totalCharacters, {
|
|
2430
|
-
smsCount: Math.ceil(
|
|
2431
|
-
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2432
|
-
),
|
|
2433
|
-
number: tempMsg.length,
|
|
2434
|
-
})}
|
|
2435
|
-
</CapHeading>
|
|
2436
|
-
</>
|
|
2437
|
-
)}
|
|
2438
|
-
handleClose={() => {
|
|
2439
|
-
setFallbackPreviewmode(false);
|
|
2440
|
-
setDltPreviewData('');
|
|
2441
|
-
}}
|
|
2442
|
-
/>
|
|
2443
|
-
)}
|
|
2444
2490
|
</>
|
|
2445
2491
|
);
|
|
2446
2492
|
};
|
|
@@ -2449,12 +2495,38 @@ const splitTemplateVarString = (str) => {
|
|
|
2449
2495
|
<CapSpin spinning={loadingTags || spin}>
|
|
2450
2496
|
{getMainContent()}
|
|
2451
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
|
+
|
|
2452
2512
|
<TestAndPreviewSlidebox
|
|
2453
2513
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
2454
2514
|
onClose={handleCloseTestAndPreview}
|
|
2455
|
-
formData={
|
|
2456
|
-
content={
|
|
2515
|
+
formData={testPreviewFormData}
|
|
2516
|
+
content={testAndPreviewContent}
|
|
2457
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}
|
|
2458
2530
|
/>
|
|
2459
2531
|
</>
|
|
2460
2532
|
);
|
|
@@ -2466,7 +2538,6 @@ const mapStateToProps = createStructuredSelector({
|
|
|
2466
2538
|
metaEntities: makeSelectMetaEntities(),
|
|
2467
2539
|
loadingTags: isLoadingMetaEntities(),
|
|
2468
2540
|
injectedTags: setInjectedTags(),
|
|
2469
|
-
currentOrgDetails: selectCurrentOrgDetails(),
|
|
2470
2541
|
});
|
|
2471
2542
|
|
|
2472
2543
|
const mapDispatchToProps = (dispatch) => ({
|