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