@capillarytech/creatives-library 8.0.330-alpha.0 → 8.0.330
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 +1 -1
- 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 +93 -292
- 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 +895 -1145
- 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,28 +280,23 @@ 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('');
|
|
@@ -268,23 +304,14 @@ export const Rcs = (props) => {
|
|
|
268
304
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
269
305
|
if (!isEmpty(accountObj)) {
|
|
270
306
|
const {
|
|
271
|
-
id: wecrmId,
|
|
272
307
|
sourceAccountIdentifier = '',
|
|
273
308
|
configs = {},
|
|
274
309
|
} = accountObj;
|
|
310
|
+
|
|
275
311
|
setAccountId(sourceAccountIdentifier);
|
|
276
|
-
setWecrmAccountId(
|
|
277
|
-
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
278
|
-
);
|
|
279
312
|
setAccessToken(configs.accessToken || '');
|
|
280
313
|
setHostName(accountObj.hostName || '');
|
|
281
314
|
setAccountName(accountObj.name || '');
|
|
282
|
-
} else {
|
|
283
|
-
setAccountId('');
|
|
284
|
-
setWecrmAccountId('');
|
|
285
|
-
setAccessToken('');
|
|
286
|
-
setHostName('');
|
|
287
|
-
setAccountName('');
|
|
288
315
|
}
|
|
289
316
|
}, [accountData.selectedRcsAccount]);
|
|
290
317
|
|
|
@@ -340,9 +367,7 @@ export const Rcs = (props) => {
|
|
|
340
367
|
if (isFullMode) return;
|
|
341
368
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
342
369
|
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 '')
|
|
370
|
+
const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
|
|
346
371
|
if (!resolved) {
|
|
347
372
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
348
373
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -387,48 +412,13 @@ export const Rcs = (props) => {
|
|
|
387
412
|
|
|
388
413
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
389
414
|
|
|
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) => {
|
|
415
|
+
const resolveTemplateWithMap = (str = '') => {
|
|
417
416
|
if (!str) return '';
|
|
418
|
-
const arr =
|
|
419
|
-
let varOrdinal = 0;
|
|
417
|
+
const arr = splitTemplateVarString(str);
|
|
420
418
|
return arr.map((elem) => {
|
|
421
419
|
if (rcsVarTestRegex.test(elem)) {
|
|
422
420
|
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
|
-
);
|
|
421
|
+
const v = cardVarMapped?.[key];
|
|
432
422
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
433
423
|
return String(v);
|
|
434
424
|
}
|
|
@@ -436,156 +426,107 @@ export const Rcs = (props) => {
|
|
|
436
426
|
}).join('');
|
|
437
427
|
};
|
|
438
428
|
|
|
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
429
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
430
|
+
useEffect(() => {
|
|
431
|
+
if (isFullMode || isEditFlow) return;
|
|
432
|
+
const tokens = [
|
|
433
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
434
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
435
|
+
];
|
|
436
|
+
if (!tokens.length) return;
|
|
437
|
+
setCardVarMapped((prev) => {
|
|
438
|
+
const next = { ...(prev || {}) };
|
|
439
|
+
let changed = false;
|
|
440
|
+
tokens.forEach((t) => {
|
|
441
|
+
const name = getVarNameFromToken(t);
|
|
442
|
+
if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
|
|
443
|
+
next[name] = '';
|
|
444
|
+
changed = true;
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
return changed ? next : prev;
|
|
448
|
+
});
|
|
449
|
+
}, [isFullMode, templateTitle, templateDesc]);
|
|
499
450
|
|
|
500
|
-
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
501
451
|
|
|
452
|
+
const RcsLabel = styled.div`
|
|
453
|
+
display: flex;
|
|
454
|
+
margin-top: 20px;
|
|
455
|
+
`;
|
|
502
456
|
const paramObj = params || {};
|
|
503
457
|
useEffect(() => {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}, [paramObj.id
|
|
458
|
+
const { id } = paramObj;
|
|
459
|
+
if (id && isFullMode) {
|
|
460
|
+
setSpin(true);
|
|
461
|
+
actions.getTemplateDetails(id, setSpin);
|
|
462
|
+
}
|
|
463
|
+
return () => {
|
|
464
|
+
actions.clearEditResponse();
|
|
465
|
+
};
|
|
466
|
+
}, [paramObj.id]);
|
|
513
467
|
|
|
514
468
|
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;
|
|
469
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
470
|
+
|
|
471
|
+
const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
|
|
472
|
+
const arr = splitTemplateVarString(targetString);
|
|
473
|
+
if (!arr?.length) {
|
|
474
|
+
setVarMap({});
|
|
475
|
+
setUpdated([]);
|
|
476
|
+
return;
|
|
542
477
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
478
|
+
const nextVarMap = {};
|
|
479
|
+
const nextUpdated = [...arr];
|
|
480
|
+
arr.forEach((elem, idx) => {
|
|
481
|
+
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
482
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
483
|
+
const id = `${elem}_${idx}`;
|
|
484
|
+
const varName = getVarNameFromToken(elem);
|
|
485
|
+
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
486
|
+
nextVarMap[id] = mappedValue;
|
|
487
|
+
if (mappedValue !== '') {
|
|
488
|
+
nextUpdated[idx] = mappedValue;
|
|
489
|
+
} else {
|
|
490
|
+
nextUpdated[idx] = elem;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
setVarMap(nextVarMap);
|
|
495
|
+
setUpdated(nextUpdated);
|
|
496
|
+
};
|
|
550
497
|
|
|
551
|
-
|
|
552
|
-
|
|
498
|
+
initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
|
|
499
|
+
initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
|
|
500
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
501
|
+
|
|
502
|
+
useEffect(() => {
|
|
503
|
+
if(!isEditFlow && isFullMode){
|
|
553
504
|
setRcsVideoSrc({});
|
|
554
505
|
updateRcsImageSrc('');
|
|
555
506
|
setUpdateRcsImageSrc('');
|
|
556
507
|
updateRcsThumbnailSrc('');
|
|
557
508
|
setAssetList({});
|
|
558
|
-
|
|
559
|
-
|
|
509
|
+
}
|
|
510
|
+
}, [templateMediaType]);
|
|
560
511
|
|
|
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':
|
|
512
|
+
const templateStatusHelper = (details) => {
|
|
513
|
+
const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
|
|
514
|
+
switch (status) {
|
|
515
|
+
case RCS_STATUSES.approved:
|
|
572
516
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
573
517
|
break;
|
|
574
|
-
case
|
|
518
|
+
case RCS_STATUSES.pending:
|
|
575
519
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
576
520
|
break;
|
|
577
|
-
case
|
|
521
|
+
case RCS_STATUSES.awaitingApproval:
|
|
578
522
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
579
523
|
break;
|
|
580
|
-
case
|
|
524
|
+
case RCS_STATUSES.unavailable:
|
|
581
525
|
setTemplateStatus(RCS_STATUSES.unavailable);
|
|
582
526
|
break;
|
|
583
|
-
case
|
|
527
|
+
case RCS_STATUSES.rejected:
|
|
584
528
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
585
529
|
break;
|
|
586
|
-
case 'created':
|
|
587
|
-
setTemplateStatus(RCS_STATUSES.created);
|
|
588
|
-
break;
|
|
589
530
|
default:
|
|
590
531
|
setTemplateStatus(status);
|
|
591
532
|
break;
|
|
@@ -596,6 +537,7 @@ export const Rcs = (props) => {
|
|
|
596
537
|
if (mediaType) {
|
|
597
538
|
setTemplateMediaType(mediaType);
|
|
598
539
|
}
|
|
540
|
+
const tempOrientation = cardSettings.cardOrientation;
|
|
599
541
|
const tempAlignment = cardSettings.mediaAlignment;
|
|
600
542
|
const tempHeight = mediaData.height;
|
|
601
543
|
|
|
@@ -638,206 +580,44 @@ export const Rcs = (props) => {
|
|
|
638
580
|
};
|
|
639
581
|
|
|
640
582
|
useEffect(() => {
|
|
641
|
-
const details =
|
|
583
|
+
const details = isFullMode ? rcsData?.templateDetails : templateData;
|
|
642
584
|
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);
|
|
585
|
+
if (!isFullMode) {
|
|
586
|
+
const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
|
|
587
|
+
setCardVarMapped(tempCardVarMapped);
|
|
677
588
|
}
|
|
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', '');
|
|
589
|
+
const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
720
590
|
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
721
591
|
setTemplateType(contentType.text_message);
|
|
722
592
|
} else {
|
|
723
593
|
setTemplateType(contentType.rich_card);
|
|
724
594
|
}
|
|
725
595
|
setEditFlow(true);
|
|
726
|
-
setTemplateName(details
|
|
727
|
-
const loadedTitle =
|
|
728
|
-
const loadedDesc =
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
isFullMode,
|
|
733
|
-
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
734
|
-
rcsVarRegex,
|
|
735
|
-
});
|
|
596
|
+
setTemplateName(details.name || '');
|
|
597
|
+
const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
|
|
598
|
+
const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
|
|
599
|
+
const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
|
|
600
|
+
const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
|
|
601
|
+
const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
|
|
736
602
|
setTemplateTitle(normalizedTitle);
|
|
737
603
|
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', '');
|
|
604
|
+
setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
|
|
605
|
+
templateStatusHelper(details);
|
|
606
|
+
const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
607
|
+
const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
|
|
763
608
|
setMediaData(mediaData, mediaType, cardSettings);
|
|
764
|
-
|
|
765
|
-
const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
766
|
-
const base = get(smsFallbackContent, 'versions.base', {});
|
|
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);
|
|
828
|
-
}
|
|
829
609
|
}
|
|
830
|
-
}, [
|
|
610
|
+
}, [rcsData, templateData, isFullMode, isEditFlow]);
|
|
611
|
+
|
|
831
612
|
|
|
832
613
|
useEffect(() => {
|
|
833
614
|
if (templateType === contentType.text_message) {
|
|
834
615
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
835
|
-
|
|
836
|
-
|
|
616
|
+
setTemplateTitle('');
|
|
617
|
+
setTemplateTitleError('');
|
|
837
618
|
if (!isEditFlow && isFullMode) {
|
|
838
|
-
setTemplateTitle('');
|
|
839
|
-
setTemplateTitleError('');
|
|
840
619
|
setUpdateRcsImageSrc('');
|
|
620
|
+
setUpdateRcsVideoSrc({});
|
|
841
621
|
setRcsVideoSrc({});
|
|
842
622
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
843
623
|
}
|
|
@@ -864,8 +644,7 @@ export const Rcs = (props) => {
|
|
|
864
644
|
if (!showDltContainer) {
|
|
865
645
|
const { type, module } = location.query || {};
|
|
866
646
|
const isEmbedded = type === EMBEDDED;
|
|
867
|
-
|
|
868
|
-
const context = isEmbedded ? module : 'outbound';
|
|
647
|
+
const context = isEmbedded ? module : DEFAULT;
|
|
869
648
|
const embedded = isEmbedded ? type : FULL;
|
|
870
649
|
const query = {
|
|
871
650
|
layout: SMS,
|
|
@@ -876,9 +655,9 @@ export const Rcs = (props) => {
|
|
|
876
655
|
if (getDefaultTags) {
|
|
877
656
|
query.context = getDefaultTags;
|
|
878
657
|
}
|
|
879
|
-
|
|
658
|
+
globalActions.fetchSchemaForEntity(query);
|
|
880
659
|
}
|
|
881
|
-
}, [showDltContainer
|
|
660
|
+
}, [showDltContainer]);
|
|
882
661
|
|
|
883
662
|
useEffect(() => {
|
|
884
663
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -901,107 +680,39 @@ export const Rcs = (props) => {
|
|
|
901
680
|
context,
|
|
902
681
|
embedded,
|
|
903
682
|
};
|
|
904
|
-
|
|
905
|
-
query.context = getDefaultTags;
|
|
906
|
-
}
|
|
907
|
-
fetchTagSchemaIfNewQuery(query);
|
|
683
|
+
globalActions.fetchSchemaForEntity(query);
|
|
908
684
|
};
|
|
909
685
|
|
|
910
|
-
const
|
|
911
|
-
if (!
|
|
912
|
-
const
|
|
913
|
-
|
|
686
|
+
const onTagSelect = (data, areaId) => {
|
|
687
|
+
if (!areaId) return;
|
|
688
|
+
const sep = areaId.lastIndexOf('_');
|
|
689
|
+
if (sep === -1) return;
|
|
690
|
+
const numId = Number(areaId.slice(sep + 1));
|
|
691
|
+
if (isNaN(numId)) return;
|
|
692
|
+
const token = areaId.slice(0, sep);
|
|
693
|
+
const variableName = getVarNameFromToken(token);
|
|
694
|
+
if (!variableName) return;
|
|
695
|
+
setCardVarMapped((prev) => {
|
|
696
|
+
const base = (prev?.[variableName] ?? '').toString();
|
|
697
|
+
const nextVal = `${base}{{${data}}}`;
|
|
698
|
+
return {
|
|
699
|
+
...(prev || {}),
|
|
700
|
+
[variableName]: nextVal,
|
|
701
|
+
};
|
|
702
|
+
});
|
|
914
703
|
};
|
|
915
704
|
|
|
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
|
-
});
|
|
705
|
+
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
966
706
|
|
|
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
|
-
};
|
|
707
|
+
const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
|
|
1001
708
|
|
|
1002
|
-
const
|
|
709
|
+
const onTagSelectFallback = (data) => {
|
|
710
|
+
const tempMsg = `${fallbackMessage}{{${data}}}`;
|
|
711
|
+
const error = fallbackMessageErrorHandler(tempMsg);
|
|
712
|
+
setFallbackMessage(tempMsg);
|
|
713
|
+
setFallbackMessageError(error);
|
|
714
|
+
};
|
|
1003
715
|
|
|
1004
|
-
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
1005
716
|
|
|
1006
717
|
//removing optout tag for rcs
|
|
1007
718
|
const getRcsTags = () => {
|
|
@@ -1014,14 +725,15 @@ export const Rcs = (props) => {
|
|
|
1014
725
|
};
|
|
1015
726
|
// tag Code end
|
|
1016
727
|
|
|
1017
|
-
const renderLabel = (value, desc) => {
|
|
728
|
+
const renderLabel = (value, showLabel, desc) => {
|
|
729
|
+
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
1018
730
|
return (
|
|
1019
731
|
<>
|
|
1020
|
-
<
|
|
732
|
+
<RcsLabel>
|
|
1021
733
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
1022
|
-
</
|
|
734
|
+
</RcsLabel>
|
|
1023
735
|
{desc && (
|
|
1024
|
-
<CapLabel type="label3"
|
|
736
|
+
<CapLabel type="label3" style={{ marginBottom: '17px' }}>
|
|
1025
737
|
{formatMessage(messages[desc])}
|
|
1026
738
|
</CapLabel>
|
|
1027
739
|
)}
|
|
@@ -1107,6 +819,47 @@ export const Rcs = (props) => {
|
|
|
1107
819
|
setTemplateDescError(error);
|
|
1108
820
|
};
|
|
1109
821
|
|
|
822
|
+
|
|
823
|
+
const templateDescErrorHandler = (value) => {
|
|
824
|
+
let errorMessage = false;
|
|
825
|
+
const { isBraceError } = validateTags({
|
|
826
|
+
content: value,
|
|
827
|
+
tagsParam: tags,
|
|
828
|
+
location,
|
|
829
|
+
tagModule: getDefaultTags,
|
|
830
|
+
isFullMode,
|
|
831
|
+
}) || {};
|
|
832
|
+
|
|
833
|
+
const maxLength = templateType === contentType.text_message
|
|
834
|
+
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
835
|
+
: RCS_RICH_CARD_MAX_LENGTH;
|
|
836
|
+
|
|
837
|
+
if (value === '' && isMediaTypeText) {
|
|
838
|
+
errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
839
|
+
} else if (value?.length > maxLength) {
|
|
840
|
+
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (isBraceError) {
|
|
844
|
+
errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
845
|
+
}
|
|
846
|
+
return errorMessage;
|
|
847
|
+
};
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
const onFallbackMessageChange = ({ target: { value } }) => {
|
|
851
|
+
const error = fallbackMessageErrorHandler(value);
|
|
852
|
+
setFallbackMessage(value);
|
|
853
|
+
setFallbackMessageError(error);
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const fallbackMessageErrorHandler = (value) => {
|
|
857
|
+
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
858
|
+
return formatMessage(messages.fallbackMsgLenError);
|
|
859
|
+
}
|
|
860
|
+
return false;
|
|
861
|
+
};
|
|
862
|
+
|
|
1110
863
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
1111
864
|
const forbiddenCharactersValidation = (value) => {
|
|
1112
865
|
if (!value) return false;
|
|
@@ -1151,43 +904,53 @@ export const Rcs = (props) => {
|
|
|
1151
904
|
if(!isFullMode){
|
|
1152
905
|
return false;
|
|
1153
906
|
}
|
|
1154
|
-
|
|
1155
|
-
if (!/^[\w.]+$/.test(paramName)) {
|
|
907
|
+
if (!/^\w+$/.test(paramName)) {
|
|
1156
908
|
return formatMessage(messages.unknownCharactersError);
|
|
1157
909
|
}
|
|
1158
910
|
}
|
|
1159
911
|
return false;
|
|
1160
912
|
};
|
|
913
|
+
|
|
914
|
+
const templateHeaderErrorHandler = (value) => {
|
|
915
|
+
let errorMessage = false;
|
|
916
|
+
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
917
|
+
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
918
|
+
} else {
|
|
919
|
+
errorMessage = variableErrorHandling(value);
|
|
920
|
+
}
|
|
921
|
+
return errorMessage;
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
const templateMessageErrorHandler = (value) => {
|
|
926
|
+
let errorMessage = false;
|
|
927
|
+
if (value === '') {
|
|
928
|
+
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
929
|
+
} else if (
|
|
930
|
+
value?.length
|
|
931
|
+
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
932
|
+
) {
|
|
933
|
+
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
934
|
+
} else {
|
|
935
|
+
errorMessage = variableErrorHandling(value);
|
|
936
|
+
}
|
|
937
|
+
return errorMessage;
|
|
938
|
+
};
|
|
939
|
+
|
|
1161
940
|
|
|
1162
941
|
const onMessageAddVar = () => {
|
|
1163
|
-
onAddVar(templateDesc);
|
|
942
|
+
onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
|
|
1164
943
|
};
|
|
1165
944
|
|
|
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
|
-
});
|
|
945
|
+
const onAddVar = (type, messageContent, regex) => {
|
|
946
|
+
// Always append the next variable at the end, like WhatsApp
|
|
947
|
+
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
948
|
+
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
1181
949
|
let nextNumber = 1;
|
|
1182
950
|
while (existingNumbers.includes(nextNumber)) {
|
|
1183
951
|
nextNumber++;
|
|
1184
952
|
}
|
|
1185
|
-
|
|
1186
|
-
};
|
|
1187
|
-
|
|
1188
|
-
const onAddVar = (messageContent) => {
|
|
1189
|
-
const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
|
|
1190
|
-
if (nextNumber === null) {
|
|
953
|
+
if (nextNumber > 19) {
|
|
1191
954
|
return;
|
|
1192
955
|
}
|
|
1193
956
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -1199,13 +962,15 @@ const onAddVar = (messageContent) => {
|
|
|
1199
962
|
};
|
|
1200
963
|
|
|
1201
964
|
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.
|
|
965
|
+
// Always append the next variable at the end, like WhatsApp
|
|
1206
966
|
const messageContent = templateTitle;
|
|
1207
|
-
const
|
|
1208
|
-
|
|
967
|
+
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
968
|
+
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
969
|
+
let nextNumber = 1;
|
|
970
|
+
while (existingNumbers.includes(nextNumber)) {
|
|
971
|
+
nextNumber++;
|
|
972
|
+
}
|
|
973
|
+
if (nextNumber > 19) {
|
|
1209
974
|
return;
|
|
1210
975
|
}
|
|
1211
976
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -1217,6 +982,66 @@ const onTitleAddVar = () => {
|
|
|
1217
982
|
setTemplateTitleError(error);
|
|
1218
983
|
};
|
|
1219
984
|
|
|
985
|
+
|
|
986
|
+
const splitTemplateVarString = (str) => {
|
|
987
|
+
if (!str) return [];
|
|
988
|
+
const validVarArr = str.match(rcsVarRegex) || [];
|
|
989
|
+
const templateVarArray = [];
|
|
990
|
+
let content = str;
|
|
991
|
+
while (content?.length !== 0) {
|
|
992
|
+
const index = content.indexOf(validVarArr?.[0]);
|
|
993
|
+
if (index !== -1) {
|
|
994
|
+
templateVarArray.push(content.substring(0, index));
|
|
995
|
+
templateVarArray.push(validVarArr?.[0]);
|
|
996
|
+
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
997
|
+
validVarArr?.shift();
|
|
998
|
+
} else {
|
|
999
|
+
templateVarArray.push(content);
|
|
1000
|
+
break;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return templateVarArray.filter(Boolean);
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
const textAreaValue = (idValue, type) => {
|
|
1007
|
+
if (idValue >= 0) {
|
|
1008
|
+
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1009
|
+
const templateArr = splitTemplateVarString(templateStr);
|
|
1010
|
+
const token = templateArr?.[idValue] || "";
|
|
1011
|
+
if (token && rcsVarTestRegex.test(token)) {
|
|
1012
|
+
const varName = getVarNameFromToken(token);
|
|
1013
|
+
return (cardVarMapped?.[varName] ?? '').toString();
|
|
1014
|
+
}
|
|
1015
|
+
return "";
|
|
1016
|
+
}
|
|
1017
|
+
return "";
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
const textAreaValueChange = (e, type) => {
|
|
1021
|
+
const value = e?.target?.value ?? '';
|
|
1022
|
+
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1023
|
+
if (!id) return;
|
|
1024
|
+
const sep = id.lastIndexOf('_');
|
|
1025
|
+
if (sep === -1) return;
|
|
1026
|
+
const isInvalidValue = value?.trim() === "";
|
|
1027
|
+
const token = id.slice(0, sep);
|
|
1028
|
+
const variableName = getVarNameFromToken(token);
|
|
1029
|
+
|
|
1030
|
+
if (variableName) {
|
|
1031
|
+
setCardVarMapped((prev) => ({
|
|
1032
|
+
...prev,
|
|
1033
|
+
[variableName]: isInvalidValue ? "" : value,
|
|
1034
|
+
}));
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
const setTextAreaId = (e, type) => {
|
|
1039
|
+
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1040
|
+
if (!id) return;
|
|
1041
|
+
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
1042
|
+
else setDescTextAreaId(id);
|
|
1043
|
+
};
|
|
1044
|
+
|
|
1220
1045
|
const renderButtonComponent = () => {
|
|
1221
1046
|
return (
|
|
1222
1047
|
<>
|
|
@@ -1247,65 +1072,39 @@ const onTitleAddVar = () => {
|
|
|
1247
1072
|
);
|
|
1248
1073
|
};
|
|
1249
1074
|
|
|
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
|
-
});
|
|
1075
|
+
const renderedRCSEditMessage = (descArray, type) => {
|
|
1076
|
+
const renderArray = [];
|
|
1077
|
+
if (descArray?.length) {
|
|
1078
|
+
descArray.forEach((elem, index) => {
|
|
1079
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
1080
|
+
// Variable input
|
|
1081
|
+
renderArray.push(
|
|
1082
|
+
<TextArea
|
|
1083
|
+
id={`${elem}_${index}`}
|
|
1084
|
+
key={`${elem}_${index}`}
|
|
1085
|
+
placeholder={`enter the value for ${elem}`}
|
|
1086
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1087
|
+
onChange={e => textAreaValueChange(e, type)}
|
|
1088
|
+
value={textAreaValue(index, type)}
|
|
1089
|
+
onFocus={(e) => setTextAreaId(e, type)}
|
|
1090
|
+
/>
|
|
1091
|
+
);
|
|
1092
|
+
} else if (elem) {
|
|
1093
|
+
// Static text
|
|
1094
|
+
renderArray.push(
|
|
1095
|
+
<TextArea
|
|
1096
|
+
key={`static_${index}`}
|
|
1097
|
+
value={elem}
|
|
1098
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1099
|
+
disabled
|
|
1100
|
+
className="rcs-edit-template-message-static-textarea"
|
|
1101
|
+
style={{ background: '#fafafa', color: '#888' }}
|
|
1102
|
+
/>
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
return renderArray;
|
|
1309
1108
|
};
|
|
1310
1109
|
|
|
1311
1110
|
const renderTextComponent = () => {
|
|
@@ -1349,19 +1148,10 @@ const onTitleAddVar = () => {
|
|
|
1349
1148
|
</>
|
|
1350
1149
|
}
|
|
1351
1150
|
/>
|
|
1151
|
+
<div className="rcs_text_area_wrapper">
|
|
1352
1152
|
{(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
|
-
/>
|
|
1153
|
+
renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
|
|
1363
1154
|
) : (
|
|
1364
|
-
<div className="rcs_text_area_wrapper">
|
|
1365
1155
|
<CapInput
|
|
1366
1156
|
className={`rcs-template-title-input ${
|
|
1367
1157
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -1374,8 +1164,8 @@ const onTitleAddVar = () => {
|
|
|
1374
1164
|
errorMessage={templateTitleError}
|
|
1375
1165
|
disabled={isEditFlow || !isFullMode}
|
|
1376
1166
|
/>
|
|
1377
|
-
</div>
|
|
1378
1167
|
)}
|
|
1168
|
+
</div>
|
|
1379
1169
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
1380
1170
|
<CapError className="rcs-template-title-error">
|
|
1381
1171
|
{templateTitleError}
|
|
@@ -1422,20 +1212,8 @@ const onTitleAddVar = () => {
|
|
|
1422
1212
|
</CapRow>
|
|
1423
1213
|
<CapRow className="rcs-create-template-message-input">
|
|
1424
1214
|
<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
1215
|
{(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
|
-
)
|
|
1216
|
+
? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
|
|
1439
1217
|
: (
|
|
1440
1218
|
<>
|
|
1441
1219
|
<TextArea
|
|
@@ -1479,9 +1257,7 @@ const onTitleAddVar = () => {
|
|
|
1479
1257
|
{templateDescError}
|
|
1480
1258
|
</CapError>
|
|
1481
1259
|
)}
|
|
1482
|
-
{
|
|
1483
|
-
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
1484
|
-
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1260
|
+
{!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
|
|
1485
1261
|
{!isFullMode && hasTag() && (
|
|
1486
1262
|
<CapAlert
|
|
1487
1263
|
message={
|
|
@@ -1501,6 +1277,18 @@ const onTitleAddVar = () => {
|
|
|
1501
1277
|
);
|
|
1502
1278
|
};
|
|
1503
1279
|
|
|
1280
|
+
|
|
1281
|
+
const fallbackSmsLength = () => (
|
|
1282
|
+
<CapLabel type="label1" className="fallback-sms-length">
|
|
1283
|
+
{formatMessage(messages.totalCharacters, {
|
|
1284
|
+
smsCount: Math.ceil(
|
|
1285
|
+
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1286
|
+
),
|
|
1287
|
+
number: fallbackMessage?.length,
|
|
1288
|
+
})}
|
|
1289
|
+
</CapLabel>
|
|
1290
|
+
);
|
|
1291
|
+
|
|
1504
1292
|
// Get character count for title (rich card only)
|
|
1505
1293
|
const getTitleCharacterCount = () => {
|
|
1506
1294
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -1560,7 +1348,7 @@ const onTitleAddVar = () => {
|
|
|
1560
1348
|
const hasTag = () => {
|
|
1561
1349
|
// Check cardVarMapped values for tags
|
|
1562
1350
|
if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
|
|
1563
|
-
const hasTagInMapped = Object.values(cardVarMapped).some(
|
|
1351
|
+
const hasTagInMapped = Object.values(cardVarMapped).some(value =>
|
|
1564
1352
|
isTagIncluded(value)
|
|
1565
1353
|
);
|
|
1566
1354
|
if (hasTagInMapped) return true;
|
|
@@ -1578,6 +1366,14 @@ const onTitleAddVar = () => {
|
|
|
1578
1366
|
return false;
|
|
1579
1367
|
};
|
|
1580
1368
|
|
|
1369
|
+
//adding creative dlt fallback sms handlers
|
|
1370
|
+
const addDltMsgHandler = () => {
|
|
1371
|
+
setShowDltContainer(true);
|
|
1372
|
+
setDltMode(RCS_DLT_MODE.TEMPLATES);
|
|
1373
|
+
setDltEditData({});
|
|
1374
|
+
setFallbackMessage('');
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1581
1377
|
const closeDltContainerHandler = () => {
|
|
1582
1378
|
setShowDltContainer(false);
|
|
1583
1379
|
setDltMode('');
|
|
@@ -1600,17 +1396,68 @@ const onTitleAddVar = () => {
|
|
|
1600
1396
|
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
1601
1397
|
'',
|
|
1602
1398
|
);
|
|
1603
|
-
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
1604
|
-
|| get(tempData, 'versions.base.name', '')
|
|
1605
|
-
|| '';
|
|
1606
1399
|
closeDltContainerHandler();
|
|
1607
1400
|
setDltEditData(tempData);
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1401
|
+
setFallbackMessage(fallMsg);
|
|
1402
|
+
setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
|
|
1403
|
+
setShowDltCard(true);
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
const rcsDltCardDeleteHandler = () => {
|
|
1407
|
+
closeDltContainerHandler();
|
|
1408
|
+
setDltEditData({});
|
|
1409
|
+
setFallbackMessage('');
|
|
1410
|
+
setFallbackMessageError(false);
|
|
1411
|
+
setShowDltCard(false);
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
const dltFallbackListingPreviewhandler = (data) => {
|
|
1415
|
+
const {
|
|
1416
|
+
'updated-sms-editor': updatedSmsEditor = [],
|
|
1417
|
+
'sms-editor': smsEditor = '',
|
|
1418
|
+
} = data.versions.base || {};
|
|
1419
|
+
setFallbackPreviewmode(true);
|
|
1420
|
+
setDltPreviewData(
|
|
1421
|
+
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1422
|
+
);
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
const getDltContentCardList = (content, channel) => {
|
|
1426
|
+
const extra = [
|
|
1427
|
+
<CapIcon
|
|
1428
|
+
type="edit"
|
|
1429
|
+
style={{ marginRight: '8px' }}
|
|
1430
|
+
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1431
|
+
/>,
|
|
1432
|
+
<CapDropdown
|
|
1433
|
+
overlay={(
|
|
1434
|
+
<CapMenu>
|
|
1435
|
+
<>
|
|
1436
|
+
<CapMenu.Item
|
|
1437
|
+
className="ant-dropdown-menu-item"
|
|
1438
|
+
onClick={() => setFallbackPreviewmode(true)}
|
|
1439
|
+
>
|
|
1440
|
+
{formatMessage(globalMessages.preview)}
|
|
1441
|
+
</CapMenu.Item>
|
|
1442
|
+
<CapMenu.Item
|
|
1443
|
+
className="ant-dropdown-menu-item"
|
|
1444
|
+
onClick={rcsDltCardDeleteHandler}
|
|
1445
|
+
>
|
|
1446
|
+
{formatMessage(globalMessages.delete)}
|
|
1447
|
+
</CapMenu.Item>
|
|
1448
|
+
</>
|
|
1449
|
+
</CapMenu>
|
|
1450
|
+
)}
|
|
1451
|
+
>
|
|
1452
|
+
<CapIcon type="more" />
|
|
1453
|
+
</CapDropdown>,
|
|
1454
|
+
];
|
|
1455
|
+
return {
|
|
1456
|
+
title: channel,
|
|
1457
|
+
content,
|
|
1458
|
+
cardType: channel,
|
|
1459
|
+
extra,
|
|
1460
|
+
};
|
|
1614
1461
|
};
|
|
1615
1462
|
|
|
1616
1463
|
const getDltSlideBoxContent = () => {
|
|
@@ -1637,6 +1484,7 @@ const onTitleAddVar = () => {
|
|
|
1637
1484
|
isFullMode={isFullMode}
|
|
1638
1485
|
isDltFromRcs
|
|
1639
1486
|
onSelectTemplate={rcsDltEditSelectHandler}
|
|
1487
|
+
handlePeviewTemplate={dltFallbackListingPreviewhandler}
|
|
1640
1488
|
/>
|
|
1641
1489
|
);
|
|
1642
1490
|
} else if (dltMode === RCS_DLT_MODE.EDIT) {
|
|
@@ -1657,32 +1505,147 @@ const onTitleAddVar = () => {
|
|
|
1657
1505
|
return { dltHeader, dltContent };
|
|
1658
1506
|
};
|
|
1659
1507
|
|
|
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
|
-
|
|
1508
|
+
const renderFallBackSmsComponent = () => {
|
|
1509
|
+
// Completely disable fallback functionality when DLT is disabled
|
|
1510
|
+
return null;
|
|
1511
|
+
|
|
1512
|
+
// const contentArr = [];
|
|
1513
|
+
// const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
|
|
1514
|
+
// const showCardForDlt = isDltEnabled && showDltCard;
|
|
1515
|
+
// const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
|
|
1516
|
+
// //pushing common fallback sms headings
|
|
1517
|
+
// contentArr.push(
|
|
1518
|
+
// <CapRow
|
|
1519
|
+
// style={{
|
|
1520
|
+
// marginBottom: isDltEnabled ? '20px' : '10px',
|
|
1521
|
+
// }}
|
|
1522
|
+
// >
|
|
1523
|
+
// <CapHeader
|
|
1524
|
+
// title={(
|
|
1525
|
+
// <CapRow type="flex">
|
|
1526
|
+
// <CapHeading type="h4">
|
|
1527
|
+
// {formatMessage(messages.fallbackLabel)}
|
|
1528
|
+
// </CapHeading>
|
|
1529
|
+
// <CapTooltipWithInfo
|
|
1530
|
+
// placement="right"
|
|
1531
|
+
// infoIconProps={{
|
|
1532
|
+
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1533
|
+
// }}
|
|
1534
|
+
// title={formatMessage(messages.fallbackToolTip)}
|
|
1535
|
+
// />
|
|
1536
|
+
// </CapRow>
|
|
1537
|
+
// )}
|
|
1538
|
+
// description={formatMessage(messages.fallbackDesc)}
|
|
1539
|
+
// suffix={
|
|
1540
|
+
// isDltEnabled ? null : (
|
|
1541
|
+
// <CapButton
|
|
1542
|
+
// type="flat"
|
|
1543
|
+
// className="fallback-preview-btn"
|
|
1544
|
+
// prefix={<CapIcon type="eye" />}
|
|
1545
|
+
// style={{ color: CAP_SECONDARY.base }}
|
|
1546
|
+
// onClick={() => setFallbackPreviewmode(true)}
|
|
1547
|
+
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1548
|
+
// >
|
|
1549
|
+
// {formatMessage(globalMessages.preview)}
|
|
1550
|
+
// </CapButton>
|
|
1551
|
+
// )
|
|
1552
|
+
// }
|
|
1553
|
+
// />
|
|
1554
|
+
// </CapRow>,
|
|
1555
|
+
// );
|
|
1556
|
+
|
|
1557
|
+
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1558
|
+
// showAddCreativeBtnForDlt
|
|
1559
|
+
// && contentArr.push(
|
|
1560
|
+
// <CapCard className="rcs-dlt-fallback-card">
|
|
1561
|
+
// <CapRow type="flex" justify="center" align="middle">
|
|
1562
|
+
// <CapColumn span={10}>
|
|
1563
|
+
// <CapImage src={addCreativesIcon} />
|
|
1564
|
+
// </CapColumn>
|
|
1565
|
+
// <CapColumn span={14}>
|
|
1566
|
+
// <CapButton
|
|
1567
|
+
// className="add-dlt-btn"
|
|
1568
|
+
// type="secondary"
|
|
1569
|
+
// onClick={addDltMsgHandler}
|
|
1570
|
+
// >
|
|
1571
|
+
// {formatMessage(messages.addSmsCreative)}
|
|
1572
|
+
// </CapButton>
|
|
1573
|
+
// </CapColumn>
|
|
1574
|
+
// </CapRow>
|
|
1575
|
+
// </CapCard>,
|
|
1576
|
+
// );
|
|
1577
|
+
|
|
1578
|
+
// //dlt is enabled and dlt content is added, show it in a card
|
|
1579
|
+
// showCardForDlt
|
|
1580
|
+
// && contentArr.push(
|
|
1581
|
+
// <CapCustomCardList
|
|
1582
|
+
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1583
|
+
// className="rcs-dlt-card"
|
|
1584
|
+
// />,
|
|
1585
|
+
// fallbackMessageError && (
|
|
1586
|
+
// <CapError className="rcs-fallback-len-error">
|
|
1587
|
+
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1588
|
+
// </CapError>
|
|
1589
|
+
// ),
|
|
1590
|
+
// );
|
|
1591
|
+
|
|
1592
|
+
// //dlt is not enabled, show non dlt text area
|
|
1593
|
+
// showNonDltFallbackComp
|
|
1594
|
+
// && contentArr.push(
|
|
1595
|
+
// <>
|
|
1596
|
+
// <CapRow>
|
|
1597
|
+
// <CapHeader
|
|
1598
|
+
// title={(
|
|
1599
|
+
// <CapHeading type="h4">
|
|
1600
|
+
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1601
|
+
// </CapHeading>
|
|
1602
|
+
// )}
|
|
1603
|
+
// suffix={(
|
|
1604
|
+
// <TagList
|
|
1605
|
+
// label={formatMessage(globalMessages.addLabels)}
|
|
1606
|
+
// onTagSelect={onTagSelectFallback}
|
|
1607
|
+
// location={location}
|
|
1608
|
+
// tags={tags || []}
|
|
1609
|
+
// onContextChange={handleOnTagsContextChange}
|
|
1610
|
+
// injectedTags={injectedTags || {}}
|
|
1611
|
+
// selectedOfferDetails={selectedOfferDetails}
|
|
1612
|
+
// />
|
|
1613
|
+
// )}
|
|
1614
|
+
// />
|
|
1615
|
+
// </CapRow>
|
|
1616
|
+
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1617
|
+
// <TextArea
|
|
1618
|
+
// id="rcs_fallback_message_textarea"
|
|
1619
|
+
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1620
|
+
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1621
|
+
// onChange={onFallbackMessageChange}
|
|
1622
|
+
// errorMessage={fallbackMessageError}
|
|
1623
|
+
// value={fallbackMessage || ""}
|
|
1624
|
+
// />
|
|
1625
|
+
// {!aiContentBotDisabled && (
|
|
1626
|
+
// <CapAskAira.ContentGenerationBot
|
|
1627
|
+
// text={fallbackMessage || ""}
|
|
1628
|
+
// setText={(text) => {
|
|
1629
|
+
// onFallbackMessageChange({ target: { value: text } });
|
|
1630
|
+
// }}
|
|
1631
|
+
// iconPlacement="float-br"
|
|
1632
|
+
// rootStyle={{
|
|
1633
|
+
// bottom: "0.5rem",
|
|
1634
|
+
// right: "0.5rem",
|
|
1635
|
+
// position: "absolute",
|
|
1636
|
+
// }}
|
|
1637
|
+
// />
|
|
1638
|
+
// )}
|
|
1639
|
+
// </div>
|
|
1640
|
+
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1641
|
+
// </>
|
|
1642
|
+
// );
|
|
1643
|
+
|
|
1644
|
+
// return <>{contentArr}</>;
|
|
1645
|
+
};
|
|
1684
1646
|
|
|
1685
1647
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
1648
|
+
setImageError(null);
|
|
1686
1649
|
const isRcsThumbnail = index === 1;
|
|
1687
1650
|
actions.uploadRcsAsset(file, type, {
|
|
1688
1651
|
isRcsThumbnail,
|
|
@@ -1694,6 +1657,7 @@ const onTitleAddVar = () => {
|
|
|
1694
1657
|
|
|
1695
1658
|
const setUpdateRcsImageSrc = useCallback(
|
|
1696
1659
|
(val) => {
|
|
1660
|
+
setAssetListImage(val);
|
|
1697
1661
|
updateRcsImageSrc(val);
|
|
1698
1662
|
actions.clearRcsMediaAsset(0);
|
|
1699
1663
|
},
|
|
@@ -1723,6 +1687,7 @@ const onTitleAddVar = () => {
|
|
|
1723
1687
|
|
|
1724
1688
|
|
|
1725
1689
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1690
|
+
setImageError(null);
|
|
1726
1691
|
actions.uploadRcsAsset(file, type, {
|
|
1727
1692
|
...fileParams,
|
|
1728
1693
|
type: 'video',
|
|
@@ -1731,6 +1696,9 @@ const onTitleAddVar = () => {
|
|
|
1731
1696
|
});
|
|
1732
1697
|
};
|
|
1733
1698
|
|
|
1699
|
+
const updateRcsVideoSrc = (val) => {
|
|
1700
|
+
setRcsVideoSrc(val);
|
|
1701
|
+
};
|
|
1734
1702
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
1735
1703
|
setRcsVideoSrc(val);
|
|
1736
1704
|
setAssetList(val);
|
|
@@ -1759,7 +1727,7 @@ const onTitleAddVar = () => {
|
|
|
1759
1727
|
<>
|
|
1760
1728
|
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
1761
1729
|
<CapImageUpload
|
|
1762
|
-
|
|
1730
|
+
style={{ paddingTop: '20px' }}
|
|
1763
1731
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1764
1732
|
imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
|
|
1765
1733
|
imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
|
|
@@ -1771,6 +1739,7 @@ const onTitleAddVar = () => {
|
|
|
1771
1739
|
updateOnReUpload={updateOnRcsThumbnailReUpload}
|
|
1772
1740
|
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
1773
1741
|
index={1}
|
|
1742
|
+
className="cap-custom-image-upload"
|
|
1774
1743
|
key={`rcs-uploaded-image-${currentDimension}`}
|
|
1775
1744
|
imageData={thumbnailData}
|
|
1776
1745
|
channel={RCS}
|
|
@@ -1801,7 +1770,7 @@ const onTitleAddVar = () => {
|
|
|
1801
1770
|
value: dim.type,
|
|
1802
1771
|
label: `${dim.label}`
|
|
1803
1772
|
}))}
|
|
1804
|
-
|
|
1773
|
+
style={{ marginBottom: '20px' }}
|
|
1805
1774
|
/>
|
|
1806
1775
|
</>
|
|
1807
1776
|
)}
|
|
@@ -1815,6 +1784,7 @@ const onTitleAddVar = () => {
|
|
|
1815
1784
|
</div>
|
|
1816
1785
|
) : (
|
|
1817
1786
|
<CapImageUpload
|
|
1787
|
+
style={{ paddingTop: '20px' }}
|
|
1818
1788
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1819
1789
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
1820
1790
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -1825,7 +1795,7 @@ const onTitleAddVar = () => {
|
|
|
1825
1795
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
1826
1796
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
1827
1797
|
index={0}
|
|
1828
|
-
className="cap-custom-image-upload
|
|
1798
|
+
className="cap-custom-image-upload"
|
|
1829
1799
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
1830
1800
|
imageData={rcsData}
|
|
1831
1801
|
channel={RCS}
|
|
@@ -1855,7 +1825,7 @@ const onTitleAddVar = () => {
|
|
|
1855
1825
|
value: dim.type,
|
|
1856
1826
|
label: `${dim.label}`
|
|
1857
1827
|
}))}
|
|
1858
|
-
|
|
1828
|
+
style={{ marginBottom: '20px' }}
|
|
1859
1829
|
/>
|
|
1860
1830
|
)}
|
|
1861
1831
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -1915,16 +1885,8 @@ const onTitleAddVar = () => {
|
|
|
1915
1885
|
const getRcsPreview = () => {
|
|
1916
1886
|
|
|
1917
1887
|
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;
|
|
1888
|
+
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1889
|
+
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
1928
1890
|
return (
|
|
1929
1891
|
<UnifiedPreview
|
|
1930
1892
|
channel={RCS}
|
|
@@ -1947,65 +1909,51 @@ const onTitleAddVar = () => {
|
|
|
1947
1909
|
);
|
|
1948
1910
|
};
|
|
1949
1911
|
|
|
1912
|
+
const getUnmappedDesc = (str, mapping) => {
|
|
1913
|
+
if (!str) return '';
|
|
1914
|
+
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1915
|
+
let result = str;
|
|
1916
|
+
const replacements = [];
|
|
1917
|
+
Object.entries(mapping).forEach(([key, value]) => {
|
|
1918
|
+
const raw = (value ?? '').toString();
|
|
1919
|
+
if (!raw || raw?.trim?.() === '') return;
|
|
1920
|
+
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1921
|
+
replacements.push({ key, needle: raw });
|
|
1922
|
+
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1923
|
+
});
|
|
1924
|
+
const seen = new Set();
|
|
1925
|
+
const uniq = replacements
|
|
1926
|
+
.filter(({ key, needle }) => {
|
|
1927
|
+
const id = `${key}::${needle}`;
|
|
1928
|
+
if (seen.has(id)) return false;
|
|
1929
|
+
seen.add(id);
|
|
1930
|
+
return true;
|
|
1931
|
+
})
|
|
1932
|
+
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1933
|
+
|
|
1934
|
+
uniq.forEach(({ key, needle }) => {
|
|
1935
|
+
if (!needle) return;
|
|
1936
|
+
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1937
|
+
const regex = new RegExp(escaped, 'g');
|
|
1938
|
+
result = result.replace(regex, `{{${key}}}`);
|
|
1939
|
+
});
|
|
1940
|
+
return result;
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1950
1943
|
const createPayload = () => {
|
|
1951
|
-
const
|
|
1944
|
+
const base = get(dltEditData, `versions.base`, {});
|
|
1945
|
+
const {
|
|
1946
|
+
template_id: templateId = '',
|
|
1947
|
+
template_name = '',
|
|
1948
|
+
'sms-editor': template = '',
|
|
1949
|
+
header: registeredSenderIds = [],
|
|
1950
|
+
} = base;
|
|
1951
|
+
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1952
|
+
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
1952
1953
|
const alignment = isMediaTypeImage
|
|
1953
1954
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
1954
1955
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
1955
1956
|
|
|
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
1957
|
const payload = {
|
|
2010
1958
|
name: templateName,
|
|
2011
1959
|
versions: {
|
|
@@ -2017,14 +1965,12 @@ const onTitleAddVar = () => {
|
|
|
2017
1965
|
cardSettings: {
|
|
2018
1966
|
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
2019
1967
|
...(alignment && { mediaAlignment: alignment }),
|
|
2020
|
-
cardWidth:
|
|
1968
|
+
cardWidth: SMALL,
|
|
2021
1969
|
},
|
|
2022
1970
|
cardContent: [
|
|
2023
1971
|
{
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
title: templateTitle,
|
|
2027
|
-
description: templateDesc,
|
|
1972
|
+
title: resolvedTitle,
|
|
1973
|
+
description: resolvedDesc,
|
|
2028
1974
|
mediaType: templateMediaType,
|
|
2029
1975
|
...(!isMediaTypeText && {media: {
|
|
2030
1976
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -2033,32 +1979,23 @@ const onTitleAddVar = () => {
|
|
|
2033
1979
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
2034
1980
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
2035
1981
|
}}),
|
|
2036
|
-
...(
|
|
2037
|
-
const
|
|
2038
|
-
...(templateTitle
|
|
2039
|
-
...(templateDesc
|
|
1982
|
+
...(!isFullMode && (() => {
|
|
1983
|
+
const tokens = [
|
|
1984
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
1985
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
2040
1986
|
];
|
|
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;
|
|
1987
|
+
const allowedKeys = tokens
|
|
1988
|
+
.map((t) => getVarNameFromToken(t))
|
|
1989
|
+
.filter(Boolean);
|
|
1990
|
+
const nextMap = {};
|
|
1991
|
+
allowedKeys.forEach((k) => {
|
|
1992
|
+
if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
|
|
1993
|
+
nextMap[k] = cardVarMapped[k];
|
|
1994
|
+
} else {
|
|
1995
|
+
nextMap[k] = '';
|
|
1996
|
+
}
|
|
2060
1997
|
});
|
|
2061
|
-
return { cardVarMapped:
|
|
1998
|
+
return { cardVarMapped: nextMap };
|
|
2062
1999
|
})()),
|
|
2063
2000
|
...(suggestions.length > 0 && { suggestions }),
|
|
2064
2001
|
}
|
|
@@ -2066,79 +2003,17 @@ const onTitleAddVar = () => {
|
|
|
2066
2003
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
2067
2004
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
2068
2005
|
},
|
|
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
|
-
}),
|
|
2006
|
+
smsFallBackContent: {
|
|
2007
|
+
message: fallbackMessage,
|
|
2008
|
+
...(isDltEnabled && {
|
|
2009
|
+
templateConfigs: {
|
|
2010
|
+
templateId,
|
|
2011
|
+
templateName: template_name,
|
|
2012
|
+
template,
|
|
2013
|
+
registeredSenderIds,
|
|
2139
2014
|
},
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2015
|
+
}),
|
|
2016
|
+
},
|
|
2142
2017
|
},
|
|
2143
2018
|
},
|
|
2144
2019
|
},
|
|
@@ -2148,107 +2023,6 @@ const onTitleAddVar = () => {
|
|
|
2148
2023
|
return payload;
|
|
2149
2024
|
};
|
|
2150
2025
|
|
|
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
2026
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
2253
2027
|
// eslint-disable-next-line no-undef
|
|
2254
2028
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -2278,9 +2052,6 @@ const onTitleAddVar = () => {
|
|
|
2278
2052
|
_id: params?.id,
|
|
2279
2053
|
validity: true,
|
|
2280
2054
|
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
2055
|
};
|
|
2285
2056
|
getFormData(formDataParams);
|
|
2286
2057
|
};
|
|
@@ -2294,7 +2065,6 @@ const onTitleAddVar = () => {
|
|
|
2294
2065
|
actionCallback({ resp, errorMessage });
|
|
2295
2066
|
setSpin(false); // Always turn off spinner
|
|
2296
2067
|
if (!errorMessage) {
|
|
2297
|
-
setTemplateStatus(RCS_STATUSES.pending);
|
|
2298
2068
|
onCreateComplete();
|
|
2299
2069
|
}
|
|
2300
2070
|
});
|
|
@@ -2306,70 +2076,6 @@ const onTitleAddVar = () => {
|
|
|
2306
2076
|
}
|
|
2307
2077
|
};
|
|
2308
2078
|
|
|
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
2079
|
const isDisableDone = () => {
|
|
2374
2080
|
if(isEditFlow){
|
|
2375
2081
|
return false;
|
|
@@ -2380,16 +2086,40 @@ const onTitleAddVar = () => {
|
|
|
2380
2086
|
}
|
|
2381
2087
|
}
|
|
2382
2088
|
|
|
2383
|
-
if
|
|
2384
|
-
|
|
2385
|
-
|
|
2089
|
+
if(!isFullMode){
|
|
2090
|
+
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2091
|
+
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2092
|
+
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2386
2093
|
|
|
2387
|
-
|
|
2388
|
-
|
|
2094
|
+
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2095
|
+
return true;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
const hasEmptyMapping =
|
|
2099
|
+
cardVarMapped &&
|
|
2100
|
+
Object.keys(cardVarMapped).length > 0 &&
|
|
2101
|
+
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2102
|
+
if (typeof v !== 'string') return !v; // null/undefined
|
|
2103
|
+
return v.trim() === ''; // empty string
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
if (hasEmptyMapping) {
|
|
2107
|
+
return true;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
const anyMissing = allVars.some(name => {
|
|
2111
|
+
const v = cardVarMapped?.[name];
|
|
2112
|
+
if (typeof v !== 'string') return !v;
|
|
2113
|
+
return v.trim() === '';
|
|
2114
|
+
});
|
|
2115
|
+
if (anyMissing) {
|
|
2116
|
+
return true;
|
|
2117
|
+
}
|
|
2389
2118
|
}
|
|
2390
2119
|
|
|
2391
|
-
|
|
2120
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2392
2121
|
return true;
|
|
2122
|
+
|
|
2393
2123
|
}
|
|
2394
2124
|
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
2395
2125
|
return true;
|
|
@@ -2407,36 +2137,53 @@ const onTitleAddVar = () => {
|
|
|
2407
2137
|
return true;
|
|
2408
2138
|
}
|
|
2409
2139
|
}
|
|
2410
|
-
if (templateDescError || templateTitleError) {
|
|
2411
|
-
return true;
|
|
2412
|
-
}
|
|
2413
|
-
if (
|
|
2414
|
-
smsFallbackData?.content
|
|
2415
|
-
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2416
|
-
) {
|
|
2140
|
+
if (templateDescError || templateTitleError || fallbackMessageError) {
|
|
2417
2141
|
return true;
|
|
2418
2142
|
}
|
|
2419
2143
|
return false;
|
|
2420
2144
|
};
|
|
2421
2145
|
|
|
2422
2146
|
const isEditDisableDone = () => {
|
|
2147
|
+
|
|
2423
2148
|
if (templateStatus !== RCS_STATUSES.approved) {
|
|
2424
2149
|
return true;
|
|
2425
2150
|
}
|
|
2426
2151
|
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
// }
|
|
2432
|
-
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2433
|
-
return true;
|
|
2152
|
+
if (!isFullMode) {
|
|
2153
|
+
if (templateName.trim() === '' || templateNameError) {
|
|
2154
|
+
return true;
|
|
2155
|
+
}
|
|
2434
2156
|
}
|
|
2157
|
+
if(!isFullMode){
|
|
2158
|
+
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2159
|
+
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2160
|
+
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2435
2161
|
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2162
|
+
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2163
|
+
return true;
|
|
2164
|
+
}
|
|
2439
2165
|
|
|
2166
|
+
const hasEmptyMapping =
|
|
2167
|
+
cardVarMapped &&
|
|
2168
|
+
Object.keys(cardVarMapped).length > 0 &&
|
|
2169
|
+
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2170
|
+
if (typeof v !== 'string') return !v; // null/undefined
|
|
2171
|
+
return v.trim() === ''; // empty string
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
if (hasEmptyMapping) {
|
|
2175
|
+
return true;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
const anyMissing = allVars.some(name => {
|
|
2179
|
+
const v = cardVarMapped?.[name];
|
|
2180
|
+
if (typeof v !== 'string') return !v;
|
|
2181
|
+
return v.trim() === '';
|
|
2182
|
+
});
|
|
2183
|
+
if (anyMissing) {
|
|
2184
|
+
return true;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2440
2187
|
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2441
2188
|
return true;
|
|
2442
2189
|
}
|
|
@@ -2455,13 +2202,7 @@ const onTitleAddVar = () => {
|
|
|
2455
2202
|
return true;
|
|
2456
2203
|
}
|
|
2457
2204
|
}
|
|
2458
|
-
if (templateTitleError || templateDescError) {
|
|
2459
|
-
return true;
|
|
2460
|
-
}
|
|
2461
|
-
if (
|
|
2462
|
-
smsFallbackData?.content
|
|
2463
|
-
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2464
|
-
) {
|
|
2205
|
+
if (templateTitleError || templateDescError || fallbackMessageError) {
|
|
2465
2206
|
return true;
|
|
2466
2207
|
}
|
|
2467
2208
|
return false;
|
|
@@ -2511,56 +2252,52 @@ const onTitleAddVar = () => {
|
|
|
2511
2252
|
};
|
|
2512
2253
|
|
|
2513
2254
|
const getMainContent = () => {
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2255
|
+
if (showDltContainer && !fallbackPreviewmode) {
|
|
2256
|
+
const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
|
|
2257
|
+
const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
|
|
2258
|
+
return (
|
|
2259
|
+
<CapSlideBox
|
|
2260
|
+
show={showDltContainer}
|
|
2261
|
+
header={dltHeader}
|
|
2262
|
+
content={dltContent}
|
|
2263
|
+
handleClose={closeDltContainerHandler}
|
|
2264
|
+
size="size-xl"
|
|
2265
|
+
/>
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2517
2268
|
|
|
2518
2269
|
return (
|
|
2519
2270
|
<>
|
|
2520
|
-
{templateStatus !== '' && (
|
|
2521
|
-
<
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
{
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
)}
|
|
2533
|
-
</CapColumn>
|
|
2534
|
-
</CapRow>
|
|
2271
|
+
{templateStatus !== '' && (<CapRow className="template-status-container">
|
|
2272
|
+
<CapLabel type="label2">
|
|
2273
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
2274
|
+
</CapLabel>
|
|
2275
|
+
|
|
2276
|
+
{templateStatus && (
|
|
2277
|
+
<CapAlert
|
|
2278
|
+
message={getTemplateStatusMessage()}
|
|
2279
|
+
type={getTemplateStatusType(templateStatus)}
|
|
2280
|
+
/>
|
|
2281
|
+
)}
|
|
2282
|
+
</CapRow>
|
|
2535
2283
|
)}
|
|
2536
|
-
<CapRow className=
|
|
2284
|
+
<CapRow className="cap-rcs-creatives">
|
|
2537
2285
|
<CapColumn span={14}>
|
|
2538
2286
|
{/* template name */}
|
|
2539
2287
|
{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
|
-
)
|
|
2288
|
+
<CapInput
|
|
2289
|
+
id="rcs_template_name_input"
|
|
2290
|
+
data-testid="template_name"
|
|
2291
|
+
onChange={onTemplateNameChange}
|
|
2292
|
+
errorMessage={templateNameError}
|
|
2293
|
+
placeholder={formatMessage(
|
|
2294
|
+
globalMessages.templateNamePlaceholder,
|
|
2295
|
+
)}
|
|
2296
|
+
value={templateName || ''}
|
|
2297
|
+
size="default"
|
|
2298
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
2299
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2300
|
+
/>
|
|
2564
2301
|
)}
|
|
2565
2302
|
{renderLabel('templateTypeLabel')}
|
|
2566
2303
|
<CapRadioGroup
|
|
@@ -2588,7 +2325,7 @@ const onTitleAddVar = () => {
|
|
|
2588
2325
|
</>
|
|
2589
2326
|
)}
|
|
2590
2327
|
{renderTextComponent()}
|
|
2591
|
-
<CapDivider
|
|
2328
|
+
<CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
|
|
2592
2329
|
{renderFallBackSmsComponent()}
|
|
2593
2330
|
<div className="rcs-scroll-div" />
|
|
2594
2331
|
</CapColumn>
|
|
@@ -2600,8 +2337,7 @@ const onTitleAddVar = () => {
|
|
|
2600
2337
|
|
|
2601
2338
|
|
|
2602
2339
|
<div className="rcs-footer">
|
|
2603
|
-
{
|
|
2604
|
-
{!isEditFlow && isFullMode && (
|
|
2340
|
+
{!isEditFlow && (
|
|
2605
2341
|
<>
|
|
2606
2342
|
<div className="button-disabled-tooltip-wrapper">
|
|
2607
2343
|
<CapButton
|
|
@@ -2622,6 +2358,7 @@ const onTitleAddVar = () => {
|
|
|
2622
2358
|
className="rcs-test-preview-btn"
|
|
2623
2359
|
type="secondary"
|
|
2624
2360
|
disabled={true}
|
|
2361
|
+
style={{ marginLeft: "8px" }}
|
|
2625
2362
|
>
|
|
2626
2363
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
2627
2364
|
</CapButton>
|
|
@@ -2654,6 +2391,51 @@ const onTitleAddVar = () => {
|
|
|
2654
2391
|
</>
|
|
2655
2392
|
)}
|
|
2656
2393
|
</div>
|
|
2394
|
+
|
|
2395
|
+
|
|
2396
|
+
{fallbackPreviewmode && (
|
|
2397
|
+
<CapSlideBox
|
|
2398
|
+
className="rcs-fallback-preview"
|
|
2399
|
+
show={fallbackPreviewmode}
|
|
2400
|
+
header={(
|
|
2401
|
+
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2402
|
+
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2403
|
+
</CapHeading>
|
|
2404
|
+
)}
|
|
2405
|
+
content={(
|
|
2406
|
+
<>
|
|
2407
|
+
<UnifiedPreview
|
|
2408
|
+
channel={RCS}
|
|
2409
|
+
content={{
|
|
2410
|
+
rcsPreviewContent: {
|
|
2411
|
+
rcsDesc: tempMsg,
|
|
2412
|
+
},
|
|
2413
|
+
}}
|
|
2414
|
+
device={ANDROID}
|
|
2415
|
+
showDeviceToggle={false}
|
|
2416
|
+
showHeader={false}
|
|
2417
|
+
formatMessage={formatMessage}
|
|
2418
|
+
/>
|
|
2419
|
+
<CapHeading
|
|
2420
|
+
type="h3"
|
|
2421
|
+
style={{ textAlign: 'center' }}
|
|
2422
|
+
className="margin-t-16"
|
|
2423
|
+
>
|
|
2424
|
+
{formatMessage(messages.totalCharacters, {
|
|
2425
|
+
smsCount: Math.ceil(
|
|
2426
|
+
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2427
|
+
),
|
|
2428
|
+
number: tempMsg.length,
|
|
2429
|
+
})}
|
|
2430
|
+
</CapHeading>
|
|
2431
|
+
</>
|
|
2432
|
+
)}
|
|
2433
|
+
handleClose={() => {
|
|
2434
|
+
setFallbackPreviewmode(false);
|
|
2435
|
+
setDltPreviewData('');
|
|
2436
|
+
}}
|
|
2437
|
+
/>
|
|
2438
|
+
)}
|
|
2657
2439
|
</>
|
|
2658
2440
|
);
|
|
2659
2441
|
};
|
|
@@ -2662,44 +2444,12 @@ const onTitleAddVar = () => {
|
|
|
2662
2444
|
<CapSpin spinning={loadingTags || spin}>
|
|
2663
2445
|
{getMainContent()}
|
|
2664
2446
|
</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
2447
|
<TestAndPreviewSlidebox
|
|
2680
2448
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
2681
2449
|
onClose={handleCloseTestAndPreview}
|
|
2682
|
-
formData={
|
|
2683
|
-
content={
|
|
2450
|
+
formData={null} // RCS doesn't use formData structure like SMS
|
|
2451
|
+
content={getTemplateContent()}
|
|
2684
2452
|
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
2453
|
/>
|
|
2704
2454
|
</>
|
|
2705
2455
|
);
|