@capillarytech/creatives-library 8.0.345-alpha.15 → 8.0.345-alpha.16
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 -29
- package/package.json +1 -1
- package/services/tests/api.test.js +0 -13
- package/utils/commonUtils.js +1 -19
- package/v2Components/CapActionButton/constants.js +0 -7
- package/v2Components/CapActionButton/index.js +109 -167
- package/v2Components/CapActionButton/index.scss +6 -157
- package/v2Components/CapActionButton/messages.js +3 -19
- package/v2Components/CapActionButton/tests/index.test.js +17 -41
- 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 +5 -10
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -160
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -341
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
- package/v2Components/CommonTestAndPreview/constants.js +2 -38
- package/v2Components/CommonTestAndPreview/index.js +186 -676
- package/v2Components/CommonTestAndPreview/messages.js +3 -49
- package/v2Components/CommonTestAndPreview/sagas.js +6 -15
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
- 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/PreviewSection.test.js +1 -8
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
- package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/FormBuilder/index.js +10 -8
- package/v2Components/TemplatePreview/_templatePreview.scss +23 -33
- package/v2Components/TemplatePreview/index.js +28 -143
- package/v2Components/TemplatePreview/tests/index.test.js +0 -142
- 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 +103 -300
- 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 +15 -20
- package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
- package/v2Containers/Email/reducer.js +12 -3
- package/v2Containers/Email/sagas.js +9 -4
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -0
- package/v2Containers/Email/tests/reducer.test.js +47 -0
- package/v2Containers/Email/tests/sagas.test.js +146 -6
- package/v2Containers/Rcs/constants.js +8 -119
- package/v2Containers/Rcs/index.js +811 -2383
- package/v2Containers/Rcs/index.scss +6 -276
- package/v2Containers/Rcs/messages.js +3 -38
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
- package/v2Containers/Rcs/tests/index.test.js +121 -152
- package/v2Containers/Rcs/tests/mockData.js +0 -38
- package/v2Containers/Rcs/tests/utils.test.js +30 -646
- package/v2Containers/Rcs/utils.js +11 -478
- package/v2Containers/Sms/Create/index.js +40 -100
- package/v2Containers/SmsTrai/Create/index.js +4 -9
- package/v2Containers/SmsTrai/Edit/constants.js +0 -2
- package/v2Containers/SmsTrai/Edit/index.js +130 -636
- package/v2Containers/SmsTrai/Edit/messages.js +4 -14
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
- package/v2Containers/SmsWrapper/index.js +8 -37
- package/v2Containers/TagList/index.js +0 -6
- package/v2Containers/Templates/_templates.scss +2 -163
- package/v2Containers/Templates/actions.js +0 -11
- package/v2Containers/Templates/constants.js +0 -2
- package/v2Containers/Templates/index.js +54 -119
- 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 +34 -578
- package/utils/rcsPayloadUtils.js +0 -92
- package/utils/templateVarUtils.js +0 -201
- package/utils/tests/templateVarUtils.test.js +0 -204
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +0 -18
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
- 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/TemplatePreview/constants.js +0 -2
- 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/index.js.rej +0 -1336
- package/v2Containers/Rcs/index.scss.rej +0 -74
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +0 -128
- 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';
|
|
@@ -33,7 +34,6 @@ import CapError from '@capillarytech/cap-ui-library/CapError';
|
|
|
33
34
|
import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
|
|
34
35
|
import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
|
|
35
36
|
import CapLink from '@capillarytech/cap-ui-library/CapLink';
|
|
36
|
-
import CapTab from '@capillarytech/cap-ui-library/CapTab';
|
|
37
37
|
|
|
38
38
|
import {
|
|
39
39
|
CAP_G01,
|
|
@@ -49,30 +49,17 @@ import {
|
|
|
49
49
|
import CapVideoUpload from '../../v2Components/CapVideoUpload';
|
|
50
50
|
import * as globalActions from '../Cap/actions';
|
|
51
51
|
import CapActionButton from '../../v2Components/CapActionButton';
|
|
52
|
-
import TemplatePreview from '../../v2Components/TemplatePreview';
|
|
53
52
|
import { makeSelectRcs, makeSelectAccount } from './selectors';
|
|
54
53
|
import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
|
|
55
54
|
import {
|
|
56
55
|
isLoadingMetaEntities,
|
|
57
56
|
makeSelectMetaEntities,
|
|
58
57
|
setInjectedTags,
|
|
59
|
-
selectCurrentOrgDetails,
|
|
60
58
|
} from '../Cap/selectors';
|
|
61
59
|
import * as RcsActions from './actions';
|
|
62
60
|
import { isAiContentBotDisabled } from '../../utils/common';
|
|
63
61
|
import * as TemplatesActions from '../Templates/actions';
|
|
64
62
|
import './index.scss';
|
|
65
|
-
import {
|
|
66
|
-
normalizeLibraryLoadedTitleDesc,
|
|
67
|
-
mergeRcsSmsFallBackContentFromDetails,
|
|
68
|
-
mergeRcsSmsFallbackVarMapLayers,
|
|
69
|
-
extractRegisteredSenderIdsFromSmsFallbackRecord,
|
|
70
|
-
pickFirstSmsFallbackTemplateString,
|
|
71
|
-
syncCardVarMappedSemanticsFromSlots,
|
|
72
|
-
hasMeaningfulSmsFallbackShape,
|
|
73
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
74
|
-
pickRcsCardVarMappedEntries,
|
|
75
|
-
} from './rcsLibraryHydrationUtils';
|
|
76
63
|
import {
|
|
77
64
|
RCS,
|
|
78
65
|
SMS,
|
|
@@ -89,7 +76,6 @@ import {
|
|
|
89
76
|
RCS_IMG_SIZE,
|
|
90
77
|
RCS_DLT_MODE,
|
|
91
78
|
CTA,
|
|
92
|
-
AI_CONTENT_BOT_DISABLED,
|
|
93
79
|
RCS_STATUSES,
|
|
94
80
|
TITLE_TEXT,
|
|
95
81
|
MESSAGE_TEXT,
|
|
@@ -104,15 +90,9 @@ import {
|
|
|
104
90
|
rcsVarTestRegex,
|
|
105
91
|
RCS_IMAGE_DIMENSIONS,
|
|
106
92
|
RCS_TEXT_MESSAGE_MAX_LENGTH,
|
|
107
|
-
RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP,
|
|
108
93
|
RCS_RICH_CARD_MAX_LENGTH,
|
|
109
94
|
RCS_VIDEO_THUMBNAIL_DIMENSIONS,
|
|
110
|
-
RCS_CAROUSEL_IMAGE_DIMENSIONS,
|
|
111
|
-
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS,
|
|
112
|
-
RCS_CAROUSEL_IMG_SIZE,
|
|
113
|
-
RCS_CAROUSEL_VIDEO_SIZE,
|
|
114
95
|
MAX_BUTTONS,
|
|
115
|
-
INITIAL_SUGGESTIONS,
|
|
116
96
|
INITIAL_SUGGESTIONS_DATA_STOP,
|
|
117
97
|
RCS_BUTTON_TYPES,
|
|
118
98
|
titletype,
|
|
@@ -122,47 +102,25 @@ import {
|
|
|
122
102
|
SMALL,
|
|
123
103
|
MEDIUM,
|
|
124
104
|
RICHCARD,
|
|
125
|
-
HOST_INFOBIP,
|
|
126
|
-
HOST_ICS,
|
|
127
|
-
CAROUSEL_HEIGHT_OPTIONS,
|
|
128
|
-
CAROUSEL_WIDTH_OPTIONS,
|
|
129
|
-
STOP,
|
|
130
|
-
RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
|
|
131
|
-
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
132
|
-
RCS_NUMERIC_VAR_TOKEN_REGEX,
|
|
133
|
-
RCS_TAG_AREA_FIELD_TITLE,
|
|
134
|
-
RCS_TAG_AREA_FIELD_DESC,
|
|
135
105
|
} from './constants';
|
|
136
106
|
import globalMessages from '../Cap/messages';
|
|
137
107
|
import messages from './messages';
|
|
138
108
|
import creativesMessages from '../CreativesContainer/messages';
|
|
139
109
|
import withCreatives from '../../hoc/withCreatives';
|
|
140
110
|
import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
|
|
141
|
-
import
|
|
142
|
-
import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
111
|
+
import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
|
|
143
112
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
144
|
-
import { splitTemplateVarString } from '../../utils/templateVarUtils';
|
|
145
113
|
import CapImageUpload from '../../v2Components/CapImageUpload';
|
|
114
|
+
import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
|
|
146
115
|
import Templates from '../Templates';
|
|
147
116
|
import SmsTraiEdit from '../SmsTrai/Edit';
|
|
148
|
-
import SmsFallback from '../../v2Components/SmsFallback';
|
|
149
|
-
import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
|
|
150
117
|
import TagList from '../TagList';
|
|
151
118
|
import { validateTags } from '../../utils/tagValidations';
|
|
152
|
-
import {
|
|
119
|
+
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
153
120
|
import { isTagIncluded } from '../../utils/commonUtils';
|
|
154
121
|
import injectReducer from '../../utils/injectReducer';
|
|
155
122
|
import v2RcsReducer from './reducer';
|
|
156
|
-
import {
|
|
157
|
-
areAllRcsSmsFallbackVarSlotsFilled,
|
|
158
|
-
buildRcsNumericMustachePlaceholderRegex,
|
|
159
|
-
getTemplateStatusType,
|
|
160
|
-
normalizeCardVarMapped,
|
|
161
|
-
coalesceCardVarMappedToTemplate,
|
|
162
|
-
getRcsSemanticVarNamesSpanningTitleAndDesc,
|
|
163
|
-
resolveCardVarMappedSlotValue,
|
|
164
|
-
sanitizeCardVarMappedValue,
|
|
165
|
-
} from './utils';
|
|
123
|
+
import { getTemplateStatusType } from './utils';
|
|
166
124
|
|
|
167
125
|
|
|
168
126
|
const { Group: CapCheckboxGroup } = CapCheckbox;
|
|
@@ -179,18 +137,19 @@ export const Rcs = (props) => {
|
|
|
179
137
|
templatesActions,
|
|
180
138
|
globalActions,
|
|
181
139
|
location,
|
|
140
|
+
handleClose,
|
|
182
141
|
getDefaultTags,
|
|
183
142
|
supportedTags,
|
|
184
143
|
metaEntities,
|
|
185
144
|
injectedTags,
|
|
186
145
|
loadingTags,
|
|
187
146
|
getFormData,
|
|
147
|
+
isDltEnabled,
|
|
188
148
|
smsRegister,
|
|
189
|
-
orgUnitId,
|
|
190
149
|
selectedOfferDetails,
|
|
191
150
|
eventContextTags,
|
|
151
|
+
waitEventContextTags,
|
|
192
152
|
accountData = {},
|
|
193
|
-
currentOrgDetails,
|
|
194
153
|
// TestAndPreviewSlidebox props
|
|
195
154
|
showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
|
|
196
155
|
handleTestAndPreview: propsHandleTestAndPreview,
|
|
@@ -199,25 +158,7 @@ export const Rcs = (props) => {
|
|
|
199
158
|
const { formatMessage } = intl;
|
|
200
159
|
const { TextArea } = CapInput;
|
|
201
160
|
const { CapCustomCardList } = CapCustomCard;
|
|
202
|
-
|
|
203
|
-
// Defensive: React cannot render plain objects as children (crashes with
|
|
204
|
-
// "Objects are not valid as a React child"). Some campaigns (!isFullMode) flows
|
|
205
|
-
// can accidentally set an error state to an object (e.g. `{}`).
|
|
206
|
-
const normalizeErrorMessage = (err) => {
|
|
207
|
-
if (!err) return '';
|
|
208
|
-
if (React.isValidElement(err)) return err;
|
|
209
|
-
if (typeof err === 'string') return err;
|
|
210
|
-
if (typeof err === 'number') return String(err);
|
|
211
|
-
if (typeof err === 'object' && typeof err.message === 'string') return err.message;
|
|
212
|
-
try {
|
|
213
|
-
return JSON.stringify(err);
|
|
214
|
-
} catch (e) {
|
|
215
|
-
return String(err);
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
161
|
const [isEditFlow, setEditFlow] = useState(false);
|
|
220
|
-
const isEditLike = isEditFlow || !isFullMode;
|
|
221
162
|
const [tags, updateTags] = useState([]);
|
|
222
163
|
const [spin, setSpin] = useState(false);
|
|
223
164
|
//template
|
|
@@ -226,21 +167,33 @@ export const Rcs = (props) => {
|
|
|
226
167
|
const [templateMediaType, setTemplateMediaType] = useState(
|
|
227
168
|
RCS_MEDIA_TYPES.NONE,
|
|
228
169
|
);
|
|
170
|
+
const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
|
|
229
171
|
const [templateTitle, setTemplateTitle] = useState('');
|
|
230
172
|
const [templateDesc, setTemplateDesc] = useState('');
|
|
231
173
|
const [templateDescError, setTemplateDescError] = useState(false);
|
|
232
174
|
const [templateStatus, setTemplateStatus] = useState('');
|
|
175
|
+
const [templateDate, setTemplateDate] = useState('');
|
|
176
|
+
//fallback
|
|
177
|
+
const [fallbackMessage, setFallbackMessage] = useState('');
|
|
178
|
+
const [fallbackMessageError, setFallbackMessageError] = useState(false);
|
|
233
179
|
//fallback dlt
|
|
234
180
|
const [showDltContainer, setShowDltContainer] = useState(false);
|
|
235
181
|
const [dltMode, setDltMode] = useState('');
|
|
236
182
|
const [dltEditData, setDltEditData] = useState({});
|
|
237
|
-
|
|
238
|
-
const [
|
|
183
|
+
const [showDltCard, setShowDltCard] = useState(false);
|
|
184
|
+
const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
|
|
185
|
+
const [dltPreviewData, setDltPreviewData] = useState('');
|
|
239
186
|
const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
|
|
240
|
-
const buttonType = RCS_BUTTON_TYPES.NONE;
|
|
187
|
+
const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
|
|
241
188
|
const [suggestionError, setSuggestionError] = useState(true);
|
|
242
|
-
const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
|
|
243
189
|
const [templateType, setTemplateType] = useState('text_message');
|
|
190
|
+
const [templateHeader, setTemplateHeader] = useState('');
|
|
191
|
+
const [templateMessage, setTemplateMessage] = useState('');
|
|
192
|
+
const [templateHeaderError, setTemplateHeaderError] = useState('');
|
|
193
|
+
const [templateMessageError, setTemplateMessageError] = useState('');
|
|
194
|
+
const validVarRegex = /\{\{(\d+)\}\}/g;
|
|
195
|
+
const [updatedTitleData, setUpdatedTitleData] = useState([]);
|
|
196
|
+
const [updatedDescData, setUpdatedDescData] = useState([]);
|
|
244
197
|
const [titleVarMappedData, setTitleVarMappedData] = useState({});
|
|
245
198
|
const [descVarMappedData, setDescVarMappedData] = useState({});
|
|
246
199
|
const [titleTextAreaId, setTitleTextAreaId] = useState();
|
|
@@ -251,58 +204,75 @@ export const Rcs = (props) => {
|
|
|
251
204
|
const [rcsVideoSrc, setRcsVideoSrc] = useState({});
|
|
252
205
|
const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
|
|
253
206
|
const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
254
|
-
|
|
255
|
-
const [selectedCarousel, setSelectedCarousel] = useState('');
|
|
256
|
-
const [selectedCarouselHeight, setSelectedCarouselHeight] = useState(MEDIUM);
|
|
257
|
-
const [selectedCarouselWidth, setSelectedCarouselWidth] = useState(SMALL);
|
|
258
|
-
const [carouselData, setCarouselData] = useState([]);
|
|
259
|
-
const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
|
|
260
|
-
const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
|
|
261
|
-
const [carouselResetNonce, setCarouselResetNonce] = useState(0);
|
|
262
|
-
const [carouselFocusedVarId, setCarouselFocusedVarId] = useState('');
|
|
263
|
-
const [imageError, setImageError] = useState(null);
|
|
207
|
+
const [imageError, setImageError] = useState(null);
|
|
264
208
|
const [templateTitleError, setTemplateTitleError] = useState(false);
|
|
265
209
|
const [cardVarMapped, setCardVarMapped] = useState({});
|
|
266
|
-
/** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
|
|
267
|
-
const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
|
|
268
|
-
const lastHydratedRcsCardVarSignatureRef = useRef(null);
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
|
|
272
|
-
* without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
|
|
273
|
-
* fallback card / content stopped appearing until re-selected.
|
|
274
|
-
*/
|
|
275
|
-
const rcsHydrationDetails = useMemo(
|
|
276
|
-
() => (isFullMode ? rcsData?.templateDetails : templateData),
|
|
277
|
-
[isFullMode, rcsData?.templateDetails, templateData],
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
/** 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'). */
|
|
281
|
-
const lastTagSchemaQueryKeyRef = useRef(null);
|
|
282
|
-
/**
|
|
283
|
-
* Library: parent often passes a new `templateData` object reference every render. Re-applying the same
|
|
284
|
-
* SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
|
|
285
|
-
*/
|
|
286
|
-
const lastSmsFallbackHydrationKeyRef = useRef(null);
|
|
287
|
-
|
|
288
|
-
const fetchTagSchemaIfNewQuery = useCallback(
|
|
289
|
-
(query) => {
|
|
290
|
-
const key = JSON.stringify(query);
|
|
291
|
-
if (lastTagSchemaQueryKeyRef.current === key) {
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
lastTagSchemaQueryKeyRef.current = key;
|
|
295
|
-
globalActions.fetchSchemaForEntity(query);
|
|
296
|
-
},
|
|
297
|
-
[globalActions],
|
|
298
|
-
);
|
|
299
210
|
|
|
300
211
|
// TestAndPreviewSlidebox state
|
|
301
212
|
const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
|
|
213
|
+
const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
|
|
214
|
+
|
|
215
|
+
const tempMsg = dltPreviewData === '' ? fallbackMessage : dltPreviewData;
|
|
216
|
+
|
|
217
|
+
// Get template content for TestAndPreviewSlidebox
|
|
218
|
+
// Reference: Based on getRcsPreview() function (lines 2087-2111) which prepares content for TemplatePreview
|
|
219
|
+
// getRcsPreview ALWAYS uses templateTitle and templateDesc for ALL template types (text_message, rich_card, carousel)
|
|
220
|
+
// renderTextComponent (lines 1317-1485) also uses templateTitle and templateDesc
|
|
221
|
+
// Note: templateHeader and templateMessage are defined but NOT used in the component
|
|
222
|
+
const getTemplateContent = useCallback(() => {
|
|
223
|
+
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
224
|
+
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
225
|
+
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
226
|
+
|
|
227
|
+
// Build media preview object (same pattern as getRcsPreview)
|
|
228
|
+
const mediaPreview = {};
|
|
229
|
+
if (isMediaTypeImage && rcsImageSrc) {
|
|
230
|
+
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
231
|
+
}
|
|
232
|
+
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
233
|
+
// For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
|
|
234
|
+
if (rcsThumbnailSrc) {
|
|
235
|
+
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
236
|
+
} else if (rcsVideoSrc?.videoSrc) {
|
|
237
|
+
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
238
|
+
}
|
|
239
|
+
// Also include thumbnailSrc separately if available
|
|
240
|
+
if (rcsThumbnailSrc) {
|
|
241
|
+
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Build content object
|
|
246
|
+
// Reference: getRcsPreview (line 2091-2092) uses templateTitle and templateDesc for ALL cases
|
|
247
|
+
// templateTitle is used for rich_card/carousel title, empty for text_message
|
|
248
|
+
// templateDesc is used for ALL types (text message body or rich card description)
|
|
249
|
+
// For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
|
|
250
|
+
const contentObj = {
|
|
251
|
+
// Map templateTitle to templateHeader and templateDesc to templateMessage
|
|
252
|
+
templateHeader: templateTitle,
|
|
253
|
+
templateMessage: templateDesc,
|
|
254
|
+
...mediaPreview,
|
|
255
|
+
...(suggestions.length > 0 && {
|
|
256
|
+
suggestions: suggestions,
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return contentObj;
|
|
261
|
+
}, [
|
|
262
|
+
templateMediaType,
|
|
263
|
+
templateTitle,
|
|
264
|
+
templateDesc,
|
|
265
|
+
rcsImageSrc,
|
|
266
|
+
rcsVideoSrc,
|
|
267
|
+
rcsThumbnailSrc,
|
|
268
|
+
suggestions,
|
|
269
|
+
selectedDimension,
|
|
270
|
+
]);
|
|
302
271
|
|
|
303
272
|
// Handle Test and Preview button click
|
|
304
273
|
const handleTestAndPreview = useCallback(() => {
|
|
305
274
|
setShowTestAndPreviewSlidebox(true);
|
|
275
|
+
setIsTestAndPreviewMode(true);
|
|
306
276
|
if (propsHandleTestAndPreview) {
|
|
307
277
|
propsHandleTestAndPreview();
|
|
308
278
|
}
|
|
@@ -326,721 +296,31 @@ export const Rcs = (props) => {
|
|
|
326
296
|
// For video
|
|
327
297
|
return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
|
|
328
298
|
};
|
|
329
|
-
/** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
|
|
330
|
-
const handleSmsFallbackEditorStateChange = useCallback((patch) => {
|
|
331
|
-
setSmsFallbackData((prev) => {
|
|
332
|
-
if (!patch || typeof patch !== 'object') return prev;
|
|
333
|
-
// Bail out when no template has been selected yet — SmsTraiEdit fires
|
|
334
|
-
// onRcsFallbackEditorStateChange on mount (unicodeValidity, varMapped),
|
|
335
|
-
// which would create a non-null smsFallbackData from nothing and cause
|
|
336
|
-
// the card to appear as "Untitled creative" before any save.
|
|
337
|
-
if (!prev) return prev;
|
|
338
|
-
return { ...prev, ...patch };
|
|
339
|
-
});
|
|
340
|
-
}, []);
|
|
341
299
|
|
|
342
|
-
/** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
|
|
343
300
|
const [accountId, setAccountId] = useState('');
|
|
344
|
-
/** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
|
|
345
|
-
const [wecrmAccountId, setWecrmAccountId] = useState('');
|
|
346
301
|
const [accessToken, setAccessToken] = useState('');
|
|
347
302
|
const [hostName, setHostName] = useState('');
|
|
348
303
|
const [accountName, setAccountName] = useState('');
|
|
349
|
-
const
|
|
350
|
-
const isHostIcs = hostName === HOST_ICS;
|
|
351
|
-
|
|
352
|
-
useEffect(() => {
|
|
353
|
-
setSuggestions(isHostIcs ? INITIAL_SUGGESTIONS_DATA_STOP : []);
|
|
354
|
-
}, [isHostIcs]);
|
|
304
|
+
const [rcsAccount, setRcsAccount] = useState('');
|
|
355
305
|
useEffect(() => {
|
|
356
306
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
357
307
|
if (!isEmpty(accountObj)) {
|
|
358
308
|
const {
|
|
359
|
-
id: wecrmId,
|
|
360
309
|
sourceAccountIdentifier = '',
|
|
361
310
|
configs = {},
|
|
362
311
|
} = accountObj;
|
|
312
|
+
|
|
363
313
|
setAccountId(sourceAccountIdentifier);
|
|
364
|
-
setWecrmAccountId(
|
|
365
|
-
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
366
|
-
);
|
|
367
314
|
setAccessToken(configs.accessToken || '');
|
|
368
315
|
setHostName(accountObj.hostName || '');
|
|
369
316
|
setAccountName(accountObj.name || '');
|
|
370
|
-
|
|
371
|
-
setAccountId('');
|
|
372
|
-
setWecrmAccountId('');
|
|
373
|
-
setAccessToken('');
|
|
374
|
-
setHostName('');
|
|
375
|
-
setAccountName('');
|
|
317
|
+
setRcsAccount(accountObj.id || '');
|
|
376
318
|
}
|
|
377
319
|
}, [accountData.selectedRcsAccount]);
|
|
378
320
|
|
|
379
321
|
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
380
322
|
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
381
323
|
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
382
|
-
const isCarouselType = templateType === contentType.carousel;
|
|
383
|
-
|
|
384
|
-
const MAX_RCS_CAROUSEL_ALLOWED = 10;
|
|
385
|
-
// Uploads for RCS are stored in redux under dynamic keys `uploadedAssetData${index}`.
|
|
386
|
-
// Carousel needs per-card indices; otherwise all cards "restore" the last uploaded asset
|
|
387
|
-
// and show the same media/thumbnail.
|
|
388
|
-
const RCS_CAROUSEL_ASSET_INDEX_BASE = 10; // keep away from standalone indices 0 (media) and 1 (thumbnail)
|
|
389
|
-
const getCarouselImageAssetIndex = (cardIndex) =>
|
|
390
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
|
|
391
|
-
const getCarouselVideoAssetIndex = (cardIndex) =>
|
|
392
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 1;
|
|
393
|
-
const getCarouselThumbnailAssetIndex = (cardIndex) =>
|
|
394
|
-
RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 2;
|
|
395
|
-
const isThumbnailAssetIndex = (index) => {
|
|
396
|
-
if (index === 1) return true; // standalone thumbnail
|
|
397
|
-
if (index >= RCS_CAROUSEL_ASSET_INDEX_BASE) {
|
|
398
|
-
return ((index - RCS_CAROUSEL_ASSET_INDEX_BASE) % 3) === 2; // carousel thumbnail slot
|
|
399
|
-
}
|
|
400
|
-
return false;
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
// Carousel dimension key: `${HEIGHT}_${WIDTH}` (e.g., SHORT_SMALL)
|
|
404
|
-
const getCarouselDimensionKey = () => `${selectedCarouselHeight}_${selectedCarouselWidth}`;
|
|
405
|
-
|
|
406
|
-
const clearCarouselCardMedia = (cardIndex, { clearImage = true, clearVideo = true, clearThumb = true } = {}) => {
|
|
407
|
-
setCarouselData((prev = []) => {
|
|
408
|
-
const updated = cloneDeep(prev);
|
|
409
|
-
if (!updated?.[cardIndex]) return updated;
|
|
410
|
-
if (clearImage) updated[cardIndex].imageSrc = '';
|
|
411
|
-
if (clearVideo) updated[cardIndex].videoAsset = {};
|
|
412
|
-
if (clearThumb) updated[cardIndex].thumbnailSrc = '';
|
|
413
|
-
return updated;
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
if (clearImage) actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIndex));
|
|
417
|
-
if (clearVideo) actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIndex));
|
|
418
|
-
if (clearThumb) actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIndex));
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const resetCarouselMediaForAllCards = () => {
|
|
422
|
-
setCarouselData((prev = []) => {
|
|
423
|
-
const updated = cloneDeep(prev);
|
|
424
|
-
updated.forEach((card) => {
|
|
425
|
-
if (!card) return;
|
|
426
|
-
card.imageSrc = '';
|
|
427
|
-
card.videoAsset = {};
|
|
428
|
-
card.thumbnailSrc = '';
|
|
429
|
-
});
|
|
430
|
-
return updated;
|
|
431
|
-
});
|
|
432
|
-
(carouselData || []).forEach((_, idx) => {
|
|
433
|
-
actions.clearRcsMediaAsset(getCarouselImageAssetIndex(idx));
|
|
434
|
-
actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(idx));
|
|
435
|
-
actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(idx));
|
|
436
|
-
});
|
|
437
|
-
// Force tab panes to remount after global reset (some tab implementations cache inactive panes)
|
|
438
|
-
setCarouselResetNonce((n) => n + 1);
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
const RCS_CAROUSEL_INITIAL_CARD = {
|
|
442
|
-
title: '',
|
|
443
|
-
description: '',
|
|
444
|
-
mediaType: RCS_MEDIA_TYPES.IMAGE, // per-card
|
|
445
|
-
imageSrc: '',
|
|
446
|
-
videoAsset: {}, // CapVideoUpload object shape
|
|
447
|
-
thumbnailSrc: '',
|
|
448
|
-
suggestions: [],
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
const RCS_CAROUSEL_INITIAL_FIRST_CARD = {
|
|
452
|
-
...RCS_CAROUSEL_INITIAL_CARD,
|
|
453
|
-
suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
const ensureFirstCardDefaultPhoneSuggestions = (cards) => {
|
|
457
|
-
const next = cloneDeep(cards || []);
|
|
458
|
-
if (next.length === 0) return next;
|
|
459
|
-
const s = next[0].suggestions;
|
|
460
|
-
if (!Array.isArray(s) || s.length === 0) {
|
|
461
|
-
next[0] = {
|
|
462
|
-
...next[0],
|
|
463
|
-
suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
return next;
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
// Always use functional updates: image upload completes in CapImageUpload's useEffect and calls
|
|
470
|
-
// updateImageSrc → this handler. If we cloned carouselData from render, we'd overwrite title/body/
|
|
471
|
-
// suggestions typed since the last commit (stale closure).
|
|
472
|
-
const handleCarouselValueChange = (carouselIndex, fields) => {
|
|
473
|
-
setCarouselData((prev = []) => {
|
|
474
|
-
const updated = cloneDeep(prev);
|
|
475
|
-
if (!updated[carouselIndex]) return prev;
|
|
476
|
-
fields.forEach(({ fieldName, value }) => {
|
|
477
|
-
updated[carouselIndex][fieldName] = value;
|
|
478
|
-
});
|
|
479
|
-
return updated;
|
|
480
|
-
});
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
const updateCarouselErrors = (carouselIndex, patch) => {
|
|
484
|
-
setCarouselErrors((prev = []) => {
|
|
485
|
-
const next = Array.isArray(prev) ? [...prev] : [];
|
|
486
|
-
next[carouselIndex] = { ...(next[carouselIndex] || {}), ...patch };
|
|
487
|
-
return next;
|
|
488
|
-
});
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
const deleteCarouselCard = (index) => {
|
|
492
|
-
let nextIdx = 0;
|
|
493
|
-
flushSync(() => {
|
|
494
|
-
setCarouselData((prev = []) => {
|
|
495
|
-
const updated = cloneDeep(prev);
|
|
496
|
-
if (index < 0 || index >= updated.length) return updated;
|
|
497
|
-
updated.splice(index, 1);
|
|
498
|
-
nextIdx = Math.max(0, Math.min(index - 1, updated.length - 1));
|
|
499
|
-
return ensureFirstCardDefaultPhoneSuggestions(updated);
|
|
500
|
-
});
|
|
501
|
-
});
|
|
502
|
-
setCarouselErrors((prev = []) => {
|
|
503
|
-
const next = Array.isArray(prev) ? [...prev] : [];
|
|
504
|
-
next.splice(index, 1);
|
|
505
|
-
return next;
|
|
506
|
-
});
|
|
507
|
-
setActiveCarouselIndex(`${nextIdx}`);
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
const carouselButtonTextHasForbiddenChars = (value) => {
|
|
511
|
-
if (!value) return false;
|
|
512
|
-
if (value.includes('[') || value.includes(']')) return true;
|
|
513
|
-
const withoutValidVariables = value.replace(/\{\{[^}]*\}\}/g, '');
|
|
514
|
-
if (withoutValidVariables.includes('{') || withoutValidVariables.includes('}')) return true;
|
|
515
|
-
return false;
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
const isCompleteSavedCarouselSuggestion = (s) => {
|
|
519
|
-
if (!s || !s.isSaved) return false;
|
|
520
|
-
const text = (s.text || '').trim();
|
|
521
|
-
if (!text || !isValidText(text) || carouselButtonTextHasForbiddenChars(text)) return false;
|
|
522
|
-
if (s.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
|
|
523
|
-
return String(s.phoneNumber || '').length >= 5;
|
|
524
|
-
}
|
|
525
|
-
if (s.type === RCS_BUTTON_TYPES.CTA) {
|
|
526
|
-
const url = String(s.url || '').trim();
|
|
527
|
-
if (!url || url.length > URL_MAX_LENGTH) return false;
|
|
528
|
-
const subtype = s.urlType || RCS_CTA_URL_TYPE.STATIC;
|
|
529
|
-
if (subtype === RCS_CTA_URL_TYPE.DYNAMIC) {
|
|
530
|
-
return true;
|
|
531
|
-
}
|
|
532
|
-
if (!isUrl(url)) return false;
|
|
533
|
-
const varMatches = url.match(invalidVarRegex);
|
|
534
|
-
return !(varMatches && varMatches.length > 0);
|
|
535
|
-
}
|
|
536
|
-
if (s.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
|
|
537
|
-
return true;
|
|
538
|
-
}
|
|
539
|
-
return false;
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
const isCarouselCardValid = (card, cardIndex) => {
|
|
543
|
-
if (!card) return false;
|
|
544
|
-
if (!card.title || !card.title.trim()) return false;
|
|
545
|
-
if ((card.title || '').length > TEMPLATE_TITLE_MAX_LENGTH) return false;
|
|
546
|
-
if (!card.description || !card.description.trim()) return false;
|
|
547
|
-
if ((card.description || '').length > RCS_RICH_CARD_MAX_LENGTH) return false;
|
|
548
|
-
let mediaOk = false;
|
|
549
|
-
if (card.mediaType === RCS_MEDIA_TYPES.IMAGE) {
|
|
550
|
-
mediaOk = !!(card.imageSrc && String(card.imageSrc).trim());
|
|
551
|
-
} else if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
|
|
552
|
-
const hasVideo = !!(card.videoAsset && card.videoAsset.videoSrc && String(card.videoAsset.videoSrc).trim());
|
|
553
|
-
const hasThumb = !!(card.thumbnailSrc && String(card.thumbnailSrc).trim());
|
|
554
|
-
mediaOk = hasVideo && hasThumb;
|
|
555
|
-
} else {
|
|
556
|
-
return false;
|
|
557
|
-
}
|
|
558
|
-
if (!mediaOk) return false;
|
|
559
|
-
if (cardIndex === 0) {
|
|
560
|
-
const sugg = Array.isArray(card.suggestions) ? card.suggestions : [];
|
|
561
|
-
if (!sugg.some(isCompleteSavedCarouselSuggestion)) return false;
|
|
562
|
-
}
|
|
563
|
-
return true;
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
const checkDisableAddCarouselButton = () => {
|
|
567
|
-
const idx = parseInt(activeCarouselIndex, 10);
|
|
568
|
-
const activeCard = carouselData?.[idx];
|
|
569
|
-
return !isCarouselCardValid(activeCard, idx);
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
const addCarouselCard = () => {
|
|
573
|
-
let newIndex = 0;
|
|
574
|
-
flushSync(() => {
|
|
575
|
-
setCarouselData((prev = []) => {
|
|
576
|
-
const updated = cloneDeep(prev);
|
|
577
|
-
updated.push(cloneDeep(RCS_CAROUSEL_INITIAL_CARD));
|
|
578
|
-
newIndex = updated.length - 1;
|
|
579
|
-
return updated;
|
|
580
|
-
});
|
|
581
|
-
});
|
|
582
|
-
setCarouselErrors((prev = []) => ([...(Array.isArray(prev) ? prev : []), {}]));
|
|
583
|
-
setActiveCarouselIndex(`${newIndex}`);
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
// Initialize carousel data when switching to carousel type
|
|
587
|
-
useEffect(() => {
|
|
588
|
-
if (!isCarouselType) return;
|
|
589
|
-
if (!carouselData || carouselData.length === 0) {
|
|
590
|
-
setCarouselData([cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)]);
|
|
591
|
-
setCarouselErrors([{}]);
|
|
592
|
-
setActiveCarouselIndex('0');
|
|
593
|
-
}
|
|
594
|
-
}, [isCarouselType]);
|
|
595
|
-
|
|
596
|
-
// keep derived carousel key in sync
|
|
597
|
-
useEffect(() => {
|
|
598
|
-
if (!isCarouselType) return;
|
|
599
|
-
if (!selectedCarouselHeight || !selectedCarouselWidth) return;
|
|
600
|
-
// Required format: HEIGHT_WIDTH
|
|
601
|
-
setSelectedCarousel(`${selectedCarouselHeight}_${selectedCarouselWidth}`);
|
|
602
|
-
}, [isCarouselType, selectedCarouselHeight, selectedCarouselWidth]);
|
|
603
|
-
|
|
604
|
-
const renderCarouselDimensionSelection = () => {
|
|
605
|
-
if (!isCarouselType) return null;
|
|
606
|
-
return (
|
|
607
|
-
<CapRow className="rcs-carousel-dimension-section">
|
|
608
|
-
<CapRow gutter={16} className="rcs-carousel-dimension-row">
|
|
609
|
-
<CapColumn span={12}>
|
|
610
|
-
<CapHeading type="h4" className="rcs-carousel-dimension-label">Card height</CapHeading>
|
|
611
|
-
<CapSelect
|
|
612
|
-
id="rcs-carousel-height-select"
|
|
613
|
-
value={selectedCarouselHeight}
|
|
614
|
-
onChange={(val) => {
|
|
615
|
-
// Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
|
|
616
|
-
resetCarouselMediaForAllCards();
|
|
617
|
-
setSelectedCarouselHeight(val);
|
|
618
|
-
}}
|
|
619
|
-
options={CAROUSEL_HEIGHT_OPTIONS}
|
|
620
|
-
disabled={isEditFlow || !isFullMode}
|
|
621
|
-
/>
|
|
622
|
-
</CapColumn>
|
|
623
|
-
<CapColumn span={12}>
|
|
624
|
-
<CapHeading type="h4" className="rcs-carousel-dimension-label">Card width</CapHeading>
|
|
625
|
-
<CapSelect
|
|
626
|
-
id="rcs-carousel-width-select"
|
|
627
|
-
value={selectedCarouselWidth}
|
|
628
|
-
onChange={(val) => {
|
|
629
|
-
// Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
|
|
630
|
-
resetCarouselMediaForAllCards();
|
|
631
|
-
setSelectedCarouselWidth(val);
|
|
632
|
-
}}
|
|
633
|
-
options={CAROUSEL_WIDTH_OPTIONS}
|
|
634
|
-
disabled={isEditFlow || !isFullMode}
|
|
635
|
-
/>
|
|
636
|
-
</CapColumn>
|
|
637
|
-
</CapRow>
|
|
638
|
-
{!!selectedCarousel && (
|
|
639
|
-
<CapLabel type="label3" className="rcs-carousel-selected-dimension">
|
|
640
|
-
Selected: {selectedCarousel}
|
|
641
|
-
</CapLabel>
|
|
642
|
-
)}
|
|
643
|
-
</CapRow>
|
|
644
|
-
);
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
// Reuse rich-card buttons UI per carousel card
|
|
648
|
-
const renderButtonComponentForCarouselCard = (cardIndex) => {
|
|
649
|
-
const card = carouselData?.[cardIndex] || {};
|
|
650
|
-
const suggestionsForCard = card.suggestions || [];
|
|
651
|
-
return (
|
|
652
|
-
<>
|
|
653
|
-
<CapHeader
|
|
654
|
-
className="rcs-button-cta"
|
|
655
|
-
title={(
|
|
656
|
-
<CapRow type="flex">
|
|
657
|
-
<CapHeading type="h4">
|
|
658
|
-
{formatMessage(messages.btnLabel)}
|
|
659
|
-
</CapHeading>
|
|
660
|
-
</CapRow>
|
|
661
|
-
)}
|
|
662
|
-
description={(
|
|
663
|
-
<CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
|
|
664
|
-
)}
|
|
665
|
-
/>
|
|
666
|
-
<CapActionButton
|
|
667
|
-
buttonType={RCS_BUTTON_TYPES.NONE}
|
|
668
|
-
updateButtonChange={(data, btnIndex) => {
|
|
669
|
-
// Match existing behavior: allow CapActionButton to manage save gating.
|
|
670
|
-
const updated = cloneDeep(suggestionsForCard);
|
|
671
|
-
if (btnIndex === MAX_BUTTONS) {
|
|
672
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: data }]);
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
updated[btnIndex] = data;
|
|
676
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
|
|
677
|
-
}}
|
|
678
|
-
deleteButtonHandler={(btnIndex) => {
|
|
679
|
-
if (cardIndex === 0 && btnIndex === 0) {
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const savedCount = (suggestionsForCard || []).filter((x) => x && x.isSaved).length;
|
|
683
|
-
const target = (suggestionsForCard || []).find((s) => s && s.index === btnIndex);
|
|
684
|
-
if (cardIndex === 0 && target?.isSaved && savedCount <= 1) {
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
const updated = cloneDeep(suggestionsForCard)
|
|
688
|
-
.filter((i) => i.index !== btnIndex)
|
|
689
|
-
.map((i, idx) => ({ ...i, index: idx }));
|
|
690
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
|
|
691
|
-
}}
|
|
692
|
-
suggestions={suggestionsForCard}
|
|
693
|
-
isEditFlow={isEditFlow}
|
|
694
|
-
isFullMode={isFullMode}
|
|
695
|
-
maxButtons={MAX_BUTTONS}
|
|
696
|
-
host={hostName}
|
|
697
|
-
minSavedSuggestions={cardIndex === 0 ? 1 : 0}
|
|
698
|
-
hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
|
|
699
|
-
/>
|
|
700
|
-
</>
|
|
701
|
-
);
|
|
702
|
-
};
|
|
703
|
-
|
|
704
|
-
const renderCarouselCardMedia = (cardIndex) => {
|
|
705
|
-
const card = carouselData?.[cardIndex] || {};
|
|
706
|
-
const dimKey = getCarouselDimensionKey();
|
|
707
|
-
|
|
708
|
-
if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
|
|
709
|
-
return (
|
|
710
|
-
<>
|
|
711
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Video</CapHeading>
|
|
712
|
-
<CapVideoUpload
|
|
713
|
-
index={getCarouselVideoAssetIndex(cardIndex)}
|
|
714
|
-
allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
|
|
715
|
-
videoSize={RCS_CAROUSEL_VIDEO_SIZE}
|
|
716
|
-
isFullMode={isFullMode}
|
|
717
|
-
uploadAsset={uploadRcsVideo}
|
|
718
|
-
uploadedAssetList={card.videoAsset || {}}
|
|
719
|
-
onVideoUploadUpdateAssestList={(_, val) => {
|
|
720
|
-
handleCarouselValueChange(cardIndex, [{ fieldName: 'videoAsset', value: val }]);
|
|
721
|
-
}}
|
|
722
|
-
videoData={rcsData}
|
|
723
|
-
className="cap-custom-video-upload"
|
|
724
|
-
formClassName={"rcs-video-upload"}
|
|
725
|
-
channel={RCS}
|
|
726
|
-
errorMessage={formatMessage(messages.videoErrorMessage)}
|
|
727
|
-
showVideoNameAndDuration={false}
|
|
728
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
729
|
-
channelSpecificStyle={!isFullMode}
|
|
730
|
-
/>
|
|
731
|
-
|
|
732
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
733
|
-
<CapImageUpload
|
|
734
|
-
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
735
|
-
imgWidth={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.width}
|
|
736
|
-
imgHeight={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.height}
|
|
737
|
-
imgSize={RCS_THUMBNAIL_MAX_SIZE}
|
|
738
|
-
uploadAsset={uploadRcsImage}
|
|
739
|
-
isFullMode={isFullMode}
|
|
740
|
-
imageSrc={card.thumbnailSrc}
|
|
741
|
-
updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: val }])}
|
|
742
|
-
updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: '' }])}
|
|
743
|
-
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
744
|
-
index={getCarouselThumbnailAssetIndex(cardIndex)}
|
|
745
|
-
className="cap-custom-image-upload"
|
|
746
|
-
key={`rcs-carousel-thumb-${cardIndex}-${dimKey}`}
|
|
747
|
-
imageData={rcsData}
|
|
748
|
-
channel={RCS}
|
|
749
|
-
channelSpecificStyle={!isFullMode}
|
|
750
|
-
skipDimensionValidation={true}
|
|
751
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
752
|
-
disabled={isEditFlow || !isFullMode}
|
|
753
|
-
/>
|
|
754
|
-
</>
|
|
755
|
-
);
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// Default: IMAGE
|
|
759
|
-
return (
|
|
760
|
-
<>
|
|
761
|
-
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Image</CapHeading>
|
|
762
|
-
<CapImageUpload
|
|
763
|
-
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
764
|
-
imgWidth={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.width}
|
|
765
|
-
imgHeight={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.height}
|
|
766
|
-
imgSize={RCS_CAROUSEL_IMG_SIZE}
|
|
767
|
-
uploadAsset={uploadRcsImage}
|
|
768
|
-
isFullMode={isFullMode}
|
|
769
|
-
imageSrc={card.imageSrc}
|
|
770
|
-
updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: val }])}
|
|
771
|
-
updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: '' }])}
|
|
772
|
-
index={getCarouselImageAssetIndex(cardIndex)}
|
|
773
|
-
className="cap-custom-image-upload"
|
|
774
|
-
key={`rcs-carousel-image-${cardIndex}-${dimKey}`}
|
|
775
|
-
imageData={rcsData}
|
|
776
|
-
channel={RCS}
|
|
777
|
-
channelSpecificStyle={!isFullMode}
|
|
778
|
-
skipDimensionValidation={true}
|
|
779
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
780
|
-
disabled={isEditFlow || !isFullMode}
|
|
781
|
-
/>
|
|
782
|
-
</>
|
|
783
|
-
);
|
|
784
|
-
};
|
|
785
|
-
|
|
786
|
-
const renderCarouselCardButtons = (cardIndex) => {
|
|
787
|
-
return renderButtonComponentForCarouselCard(cardIndex);
|
|
788
|
-
};
|
|
789
|
-
|
|
790
|
-
const getCarouselTabPanes = () => {
|
|
791
|
-
return (carouselData || []).map((card, index) => {
|
|
792
|
-
return {
|
|
793
|
-
key: index,
|
|
794
|
-
tab: index + 1,
|
|
795
|
-
content: (
|
|
796
|
-
<CapCard
|
|
797
|
-
title={`Card ${index + 1}`}
|
|
798
|
-
extra={
|
|
799
|
-
!isEditFlow &&
|
|
800
|
-
(carouselData.length === 1 ? (
|
|
801
|
-
<CapTooltip title={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}>
|
|
802
|
-
<span className="button-disabled-tooltip-wrapper rcs-carousel-delete-tooltip-wrap">
|
|
803
|
-
<CapButton
|
|
804
|
-
className="rcs-carousel-card-delete"
|
|
805
|
-
type="flat"
|
|
806
|
-
onClick={() => deleteCarouselCard(index)}
|
|
807
|
-
disabled
|
|
808
|
-
aria-label={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}
|
|
809
|
-
>
|
|
810
|
-
<CapIcon type="delete" />
|
|
811
|
-
</CapButton>
|
|
812
|
-
</span>
|
|
813
|
-
</CapTooltip>
|
|
814
|
-
) : (
|
|
815
|
-
<CapButton
|
|
816
|
-
className="rcs-carousel-card-delete"
|
|
817
|
-
type="flat"
|
|
818
|
-
onClick={() => deleteCarouselCard(index)}
|
|
819
|
-
aria-label={formatMessage(globalMessages.delete)}
|
|
820
|
-
>
|
|
821
|
-
<CapIcon type="delete" />
|
|
822
|
-
</CapButton>
|
|
823
|
-
))
|
|
824
|
-
}
|
|
825
|
-
className="rcs-carousel-card"
|
|
826
|
-
>
|
|
827
|
-
{/* Media selection should be at top of card */}
|
|
828
|
-
<CapRow className="rcs-carousel-media-selection">
|
|
829
|
-
<CapColumn className="rcs-carousel-media-selection-heading">
|
|
830
|
-
<CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
|
|
831
|
-
</CapColumn>
|
|
832
|
-
<CapColumn>
|
|
833
|
-
<CapRadioGroup
|
|
834
|
-
id={`rcs-carousel-media-radio-${index}`}
|
|
835
|
-
options={mediaRadioOptions}
|
|
836
|
-
value={card.mediaType}
|
|
837
|
-
onChange={({ target: { value } }) => {
|
|
838
|
-
// Reset media fields when switching type
|
|
839
|
-
if (value === RCS_MEDIA_TYPES.IMAGE) {
|
|
840
|
-
// Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
|
|
841
|
-
clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
|
|
842
|
-
handleCarouselValueChange(index, [
|
|
843
|
-
{ fieldName: 'mediaType', value },
|
|
844
|
-
{ fieldName: 'videoAsset', value: {} },
|
|
845
|
-
{ fieldName: 'thumbnailSrc', value: '' },
|
|
846
|
-
]);
|
|
847
|
-
} else {
|
|
848
|
-
// Switching to VIDEO: clear image upload so it doesn't auto-restore.
|
|
849
|
-
clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
|
|
850
|
-
handleCarouselValueChange(index, [
|
|
851
|
-
{ fieldName: 'mediaType', value },
|
|
852
|
-
{ fieldName: 'imageSrc', value: '' },
|
|
853
|
-
]);
|
|
854
|
-
}
|
|
855
|
-
}}
|
|
856
|
-
disabled={isEditFlow || !isFullMode}
|
|
857
|
-
className="rcs-radio"
|
|
858
|
-
/>
|
|
859
|
-
</CapColumn>
|
|
860
|
-
</CapRow>
|
|
861
|
-
|
|
862
|
-
<CapRow className="rcs-carousel-media-upload">
|
|
863
|
-
{renderCarouselCardMedia(index)}
|
|
864
|
-
</CapRow>
|
|
865
|
-
|
|
866
|
-
{/* Title after media */}
|
|
867
|
-
<CapRow className="rcs-carousel-card-row">
|
|
868
|
-
<CapHeader
|
|
869
|
-
className="rcs-template-title-label"
|
|
870
|
-
title={<CapHeading type="h4">Card title</CapHeading>}
|
|
871
|
-
suffix={
|
|
872
|
-
(isEditFlow || !isFullMode) ? (
|
|
873
|
-
<TagList
|
|
874
|
-
label={formatMessage(globalMessages.addLabels)}
|
|
875
|
-
onTagSelect={onCarouselTagSelect}
|
|
876
|
-
location={location}
|
|
877
|
-
tags={getRcsTags()}
|
|
878
|
-
onContextChange={handleOnTagsContextChange}
|
|
879
|
-
injectedTags={injectedTags || {}}
|
|
880
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
881
|
-
/>
|
|
882
|
-
) : (
|
|
883
|
-
<CapButton
|
|
884
|
-
data-testid={`rcs-carousel-title-add-var-${index}`}
|
|
885
|
-
type="flat"
|
|
886
|
-
isAddBtn
|
|
887
|
-
onClick={() => appendVarToCarouselField(index, 'title')}
|
|
888
|
-
disabled={!!carouselErrors?.[index]?.title}
|
|
889
|
-
>
|
|
890
|
-
{formatMessage(messages.addVar)}
|
|
891
|
-
</CapButton>
|
|
892
|
-
)
|
|
893
|
-
}
|
|
894
|
-
/>
|
|
895
|
-
<CapRow className="rcs_text_area_wrapper">
|
|
896
|
-
{(isEditFlow || !isFullMode) ? (
|
|
897
|
-
renderCarouselEditMessage(card.title || '')
|
|
898
|
-
) : (
|
|
899
|
-
<CapInput
|
|
900
|
-
value={card.title || ''}
|
|
901
|
-
placeholder={formatMessage(messages.templateTitlePlaceholder)}
|
|
902
|
-
onChange={({ target: { value } }) => {
|
|
903
|
-
let error = false;
|
|
904
|
-
if (value?.length > TEMPLATE_TITLE_MAX_LENGTH) {
|
|
905
|
-
error = formatMessage(messages.templateHeaderLengthError);
|
|
906
|
-
} else {
|
|
907
|
-
error = variableErrorHandling(value);
|
|
908
|
-
}
|
|
909
|
-
updateCarouselErrors(index, { title: error });
|
|
910
|
-
handleCarouselValueChange(index, [{ fieldName: 'title', value }]);
|
|
911
|
-
}}
|
|
912
|
-
disabled={isEditFlow || !isFullMode}
|
|
913
|
-
errorMessage={carouselErrors?.[index]?.title}
|
|
914
|
-
/>
|
|
915
|
-
)}
|
|
916
|
-
</CapRow>
|
|
917
|
-
</CapRow>
|
|
918
|
-
{!isEditFlow && (
|
|
919
|
-
<CapRow className="rcs-carousel-character-count-row">
|
|
920
|
-
{renderCarouselCharacterCount(
|
|
921
|
-
getCarouselTitleCharacterCount(index),
|
|
922
|
-
getTitleMaxLength(),
|
|
923
|
-
)}
|
|
924
|
-
</CapRow>
|
|
925
|
-
)}
|
|
926
|
-
|
|
927
|
-
{/* Description after title */}
|
|
928
|
-
<CapRow className="rcs-carousel-card-row">
|
|
929
|
-
<CapHeader
|
|
930
|
-
title={<CapHeading type="h4">Card body text</CapHeading>}
|
|
931
|
-
suffix={
|
|
932
|
-
(isEditFlow || !isFullMode) ? (
|
|
933
|
-
<TagList
|
|
934
|
-
label={formatMessage(globalMessages.addLabels)}
|
|
935
|
-
onTagSelect={onCarouselTagSelect}
|
|
936
|
-
location={location}
|
|
937
|
-
tags={getRcsTags()}
|
|
938
|
-
onContextChange={handleOnTagsContextChange}
|
|
939
|
-
injectedTags={injectedTags || {}}
|
|
940
|
-
selectedOfferDetails={selectedOfferDetails}
|
|
941
|
-
/>
|
|
942
|
-
) : (
|
|
943
|
-
<CapButton
|
|
944
|
-
data-testid={`rcs-carousel-desc-add-var-${index}`}
|
|
945
|
-
type="flat"
|
|
946
|
-
isAddBtn
|
|
947
|
-
onClick={() => appendVarToCarouselField(index, 'description')}
|
|
948
|
-
disabled={!!carouselErrors?.[index]?.description}
|
|
949
|
-
>
|
|
950
|
-
{formatMessage(messages.addVar)}
|
|
951
|
-
</CapButton>
|
|
952
|
-
)
|
|
953
|
-
}
|
|
954
|
-
/>
|
|
955
|
-
<CapRow className="rcs_text_area_wrapper">
|
|
956
|
-
{(isEditFlow || !isFullMode) ? (
|
|
957
|
-
renderCarouselEditMessage(card.description || '')
|
|
958
|
-
) : (
|
|
959
|
-
<TextArea
|
|
960
|
-
autosize={{ minRows: 3, maxRows: 5 }}
|
|
961
|
-
value={card.description || ''}
|
|
962
|
-
placeholder={formatMessage(messages.templateDescPlaceholder)}
|
|
963
|
-
onChange={({ target: { value } }) => {
|
|
964
|
-
let error = false;
|
|
965
|
-
if (value?.length > RCS_RICH_CARD_MAX_LENGTH) {
|
|
966
|
-
error = formatMessage(messages.templateMessageLengthError);
|
|
967
|
-
} else {
|
|
968
|
-
error = variableErrorHandling(value);
|
|
969
|
-
}
|
|
970
|
-
updateCarouselErrors(index, { description: error });
|
|
971
|
-
handleCarouselValueChange(index, [{ fieldName: 'description', value }]);
|
|
972
|
-
}}
|
|
973
|
-
disabled={isEditFlow || !isFullMode}
|
|
974
|
-
errorMessage={
|
|
975
|
-
carouselErrors?.[index]?.description && (
|
|
976
|
-
<CapError className="rcs-template-message-error">
|
|
977
|
-
{carouselErrors[index].description}
|
|
978
|
-
</CapError>
|
|
979
|
-
)
|
|
980
|
-
}
|
|
981
|
-
/>
|
|
982
|
-
)}
|
|
983
|
-
</CapRow>
|
|
984
|
-
</CapRow>
|
|
985
|
-
{!isEditFlow && (
|
|
986
|
-
<CapRow className="rcs-carousel-character-count-row">
|
|
987
|
-
{renderCarouselCharacterCount(
|
|
988
|
-
getCarouselDescriptionCharacterCount(index),
|
|
989
|
-
getDescriptionMaxLength(),
|
|
990
|
-
)}
|
|
991
|
-
</CapRow>
|
|
992
|
-
)}
|
|
993
|
-
|
|
994
|
-
<CapDivider className="rcs-carousel-card-divider" />
|
|
995
|
-
{renderCarouselCardButtons(index)}
|
|
996
|
-
</CapCard>
|
|
997
|
-
),
|
|
998
|
-
};
|
|
999
|
-
});
|
|
1000
|
-
};
|
|
1001
|
-
|
|
1002
|
-
const renderCarouselSection = () => {
|
|
1003
|
-
if (!isCarouselType) return null;
|
|
1004
|
-
|
|
1005
|
-
const operations = (
|
|
1006
|
-
<>
|
|
1007
|
-
<CapDivider type="vertical" />
|
|
1008
|
-
<CapButton
|
|
1009
|
-
onClick={addCarouselCard}
|
|
1010
|
-
type="flat"
|
|
1011
|
-
className="add-carousel-content-button"
|
|
1012
|
-
disabled={
|
|
1013
|
-
isEditFlow ||
|
|
1014
|
-
!isFullMode ||
|
|
1015
|
-
MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0) ||
|
|
1016
|
-
checkDisableAddCarouselButton()
|
|
1017
|
-
}
|
|
1018
|
-
>
|
|
1019
|
-
<CapIcon type="plus" />
|
|
1020
|
-
</CapButton>
|
|
1021
|
-
</>
|
|
1022
|
-
);
|
|
1023
|
-
|
|
1024
|
-
return (
|
|
1025
|
-
<CapRow className="rcs-carousel-section">
|
|
1026
|
-
{renderCarouselDimensionSelection()}
|
|
1027
|
-
<CapRow className="rcs-carousel-tab">
|
|
1028
|
-
<CapTab
|
|
1029
|
-
key={`rcs-carousel-tab-${carouselResetNonce}`}
|
|
1030
|
-
defaultActiveKey="0"
|
|
1031
|
-
activeKey={activeCarouselIndex}
|
|
1032
|
-
tabBarExtraContent={operations}
|
|
1033
|
-
onChange={(key) => {
|
|
1034
|
-
setActiveCarouselIndex(`${key}`);
|
|
1035
|
-
// reset focused var when switching cards (as per requirement)
|
|
1036
|
-
setCarouselFocusedVarId('');
|
|
1037
|
-
}}
|
|
1038
|
-
panes={getCarouselTabPanes()}
|
|
1039
|
-
/>
|
|
1040
|
-
</CapRow>
|
|
1041
|
-
</CapRow>
|
|
1042
|
-
);
|
|
1043
|
-
};
|
|
1044
324
|
|
|
1045
325
|
const mediaRadioOptions = [
|
|
1046
326
|
{
|
|
@@ -1049,16 +329,11 @@ export const Rcs = (props) => {
|
|
|
1049
329
|
},
|
|
1050
330
|
{
|
|
1051
331
|
value: RCS_MEDIA_TYPES.VIDEO,
|
|
1052
|
-
label: formatMessage(
|
|
1053
|
-
templateType === contentType.carousel
|
|
1054
|
-
? messages.carouselMediaVideoOption
|
|
1055
|
-
: messages.mediaVideo,
|
|
1056
|
-
),
|
|
332
|
+
label: formatMessage(messages.mediaVideo),
|
|
1057
333
|
},
|
|
1058
334
|
];
|
|
1059
335
|
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
1060
336
|
|
|
1061
|
-
|
|
1062
337
|
const updateButtonChange = (data, index) => {
|
|
1063
338
|
if (data && data.text) {
|
|
1064
339
|
const forbiddenError = forbiddenCharactersValidation(data.text);
|
|
@@ -1095,9 +370,7 @@ export const Rcs = (props) => {
|
|
|
1095
370
|
if (isFullMode) return;
|
|
1096
371
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
1097
372
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1098
|
-
const
|
|
1099
|
-
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
1100
|
-
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
373
|
+
const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
|
|
1101
374
|
if (!resolved) {
|
|
1102
375
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
1103
376
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -1124,16 +397,10 @@ export const Rcs = (props) => {
|
|
|
1124
397
|
tagModule: getDefaultTags,
|
|
1125
398
|
isFullMode,
|
|
1126
399
|
}) || {};
|
|
1127
|
-
|
|
1128
|
-
validationResponse?.
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1132
|
-
unsupportedTags: validationResponse.unsupportedTags,
|
|
1133
|
-
})) ||
|
|
1134
|
-
(validationResponse.isBraceError &&
|
|
1135
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
1136
|
-
false;
|
|
400
|
+
const errorMsg =
|
|
401
|
+
(validationResponse?.isBraceError &&
|
|
402
|
+
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
403
|
+
false;
|
|
1137
404
|
if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
|
|
1138
405
|
if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
|
|
1139
406
|
};
|
|
@@ -1146,98 +413,15 @@ export const Rcs = (props) => {
|
|
|
1146
413
|
validateResolvedTagsForType(MESSAGE_TEXT);
|
|
1147
414
|
}, [cardVarMapped, templateDesc, tags, injectedTags, loadingTags]);
|
|
1148
415
|
|
|
1149
|
-
useEffect(() => {
|
|
1150
|
-
if (isFullMode || !isCarouselType) return;
|
|
1151
|
-
if (loadingTags || !tags || tags.length === 0) return;
|
|
1152
|
-
(carouselData || []).forEach((card, idx) => {
|
|
1153
|
-
['title', 'description'].forEach((field) => {
|
|
1154
|
-
const templateStr = card?.[field] || '';
|
|
1155
|
-
if (!templateStr) return;
|
|
1156
|
-
const resolved = resolveTemplateWithMap(templateStr);
|
|
1157
|
-
if (!resolved) {
|
|
1158
|
-
updateCarouselErrors(idx, { [field]: false });
|
|
1159
|
-
return;
|
|
1160
|
-
}
|
|
1161
|
-
let contentForValidation = resolved;
|
|
1162
|
-
const placeholderTokens = templateStr.match(rcsVarRegex) || [];
|
|
1163
|
-
placeholderTokens.forEach((t) => {
|
|
1164
|
-
const escaped = t.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1165
|
-
contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
|
|
1166
|
-
});
|
|
1167
|
-
if (!contentForValidation.trim()) {
|
|
1168
|
-
updateCarouselErrors(idx, { [field]: false });
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
|
-
const validationResponse = validateTags({
|
|
1172
|
-
content: contentForValidation,
|
|
1173
|
-
tagsParam: tags,
|
|
1174
|
-
injectedTagsParams: injectedTags,
|
|
1175
|
-
location,
|
|
1176
|
-
tagModule: getDefaultTags,
|
|
1177
|
-
eventContextTags,
|
|
1178
|
-
isFullMode,
|
|
1179
|
-
}) || {};
|
|
1180
|
-
const errorMsg =
|
|
1181
|
-
(validationResponse?.unsupportedTags?.length > 0 &&
|
|
1182
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1183
|
-
unsupportedTags: validationResponse.unsupportedTags,
|
|
1184
|
-
})) ||
|
|
1185
|
-
(validationResponse.isBraceError &&
|
|
1186
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
|
|
1187
|
-
false;
|
|
1188
|
-
updateCarouselErrors(idx, { [field]: errorMsg });
|
|
1189
|
-
});
|
|
1190
|
-
});
|
|
1191
|
-
}, [cardVarMapped, carouselData, tags, injectedTags, loadingTags, isCarouselType]);
|
|
1192
|
-
|
|
1193
416
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
1194
417
|
|
|
1195
|
-
const
|
|
1196
|
-
|
|
1197
|
-
/** Same `{{tag}}` in both title and description must not share one semantic map entry. */
|
|
1198
|
-
const rcsSpanningSemanticVarNames = useMemo(
|
|
1199
|
-
() => getRcsSemanticVarNamesSpanningTitleAndDesc(templateTitle, templateDesc, rcsVarRegex),
|
|
1200
|
-
[templateTitle, templateDesc],
|
|
1201
|
-
);
|
|
1202
|
-
|
|
1203
|
-
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
1204
|
-
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
1205
|
-
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
1206
|
-
const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
1207
|
-
const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
1208
|
-
let varOrdinal = 0;
|
|
1209
|
-
for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
|
|
1210
|
-
const segmentToken = templateSegments[segmentIndexInField];
|
|
1211
|
-
if (rcsVarTestRegex.test(segmentToken)) {
|
|
1212
|
-
if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
|
|
1213
|
-
return offset + varOrdinal;
|
|
1214
|
-
}
|
|
1215
|
-
varOrdinal += 1;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
return null;
|
|
1219
|
-
};
|
|
1220
|
-
|
|
1221
|
-
/**
|
|
1222
|
-
* Master-branch resolve: uses numeric slot keys + semantic spanning detection for correct
|
|
1223
|
-
* multi-field variable resolution.
|
|
1224
|
-
*/
|
|
1225
|
-
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
418
|
+
const resolveTemplateWithMap = (str = '') => {
|
|
1226
419
|
if (!str) return '';
|
|
1227
|
-
const arr =
|
|
1228
|
-
let varOrdinal = 0;
|
|
420
|
+
const arr = splitTemplateVarString(str);
|
|
1229
421
|
return arr.map((elem) => {
|
|
1230
422
|
if (rcsVarTestRegex.test(elem)) {
|
|
1231
423
|
const key = getVarNameFromToken(elem);
|
|
1232
|
-
const
|
|
1233
|
-
varOrdinal += 1;
|
|
1234
|
-
const v = resolveCardVarMappedSlotValue(
|
|
1235
|
-
cardVarMapped,
|
|
1236
|
-
key,
|
|
1237
|
-
globalSlot,
|
|
1238
|
-
isEditLike,
|
|
1239
|
-
rcsSpanningSemanticVarNames.has(key),
|
|
1240
|
-
);
|
|
424
|
+
const v = cardVarMapped?.[key];
|
|
1241
425
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
1242
426
|
return String(v);
|
|
1243
427
|
}
|
|
@@ -1245,128 +429,39 @@ export const Rcs = (props) => {
|
|
|
1245
429
|
}).join('');
|
|
1246
430
|
};
|
|
1247
431
|
|
|
1248
|
-
const buildCarouselCardsForPreview = (cards = []) => (cards || []).map((card = {}) => {
|
|
1249
|
-
const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
|
|
1250
|
-
const videoSrc = card.videoAsset?.videoSrc || '';
|
|
1251
|
-
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(card.title || '') : (card.title || '');
|
|
1252
|
-
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(card.description || '') : (card.description || '');
|
|
1253
|
-
return {
|
|
1254
|
-
mediaType: (card.mediaType || '').toLowerCase(),
|
|
1255
|
-
imageSrc: card.imageSrc || '',
|
|
1256
|
-
videoSrc,
|
|
1257
|
-
videoPreviewImg: videoThumb,
|
|
1258
|
-
title: resolvedTitle,
|
|
1259
|
-
bodyText: resolvedDesc,
|
|
1260
|
-
suggestions: card.suggestions || [],
|
|
1261
|
-
};
|
|
1262
|
-
});
|
|
1263
432
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
if (
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
},
|
|
1285
|
-
};
|
|
1286
|
-
}
|
|
1287
|
-
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
1288
|
-
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
1289
|
-
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
1290
|
-
|
|
1291
|
-
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1292
|
-
const titleVarCountForResolve = isMediaTypeText
|
|
1293
|
-
? 0
|
|
1294
|
-
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
1295
|
-
const resolvedTitle = isMediaTypeText
|
|
1296
|
-
? ''
|
|
1297
|
-
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
1298
|
-
const resolvedDesc = isSlotMappingMode
|
|
1299
|
-
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
1300
|
-
: templateDesc;
|
|
1301
|
-
|
|
1302
|
-
const mediaPreview = {};
|
|
1303
|
-
if (isMediaTypeImage && rcsImageSrc) {
|
|
1304
|
-
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
1305
|
-
}
|
|
1306
|
-
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
1307
|
-
if (rcsThumbnailSrc) {
|
|
1308
|
-
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
1309
|
-
} else if (rcsVideoSrc?.videoSrc) {
|
|
1310
|
-
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
1311
|
-
}
|
|
1312
|
-
if (rcsThumbnailSrc) {
|
|
1313
|
-
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
const contentObj = {
|
|
1318
|
-
templateHeader: resolvedTitle,
|
|
1319
|
-
templateMessage: resolvedDesc,
|
|
1320
|
-
...mediaPreview,
|
|
1321
|
-
...(suggestions.length > 0 && {
|
|
1322
|
-
suggestions: suggestions,
|
|
1323
|
-
}),
|
|
1324
|
-
};
|
|
1325
|
-
|
|
1326
|
-
return contentObj;
|
|
1327
|
-
}, [
|
|
1328
|
-
templateMediaType,
|
|
1329
|
-
templateTitle,
|
|
1330
|
-
templateDesc,
|
|
1331
|
-
rcsImageSrc,
|
|
1332
|
-
rcsVideoSrc,
|
|
1333
|
-
rcsThumbnailSrc,
|
|
1334
|
-
suggestions,
|
|
1335
|
-
selectedDimension,
|
|
1336
|
-
isFullMode,
|
|
1337
|
-
isEditFlow,
|
|
1338
|
-
cardVarMapped,
|
|
1339
|
-
rcsSpanningSemanticVarNames,
|
|
1340
|
-
carouselData,
|
|
1341
|
-
selectedCarouselHeight,
|
|
1342
|
-
selectedCarouselWidth,
|
|
1343
|
-
]);
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
if (isFullMode || isEditFlow) return;
|
|
435
|
+
const tokens = [
|
|
436
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
437
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
438
|
+
];
|
|
439
|
+
if (!tokens.length) return;
|
|
440
|
+
setCardVarMapped((prev) => {
|
|
441
|
+
const next = { ...(prev || {}) };
|
|
442
|
+
let changed = false;
|
|
443
|
+
tokens.forEach((t) => {
|
|
444
|
+
const name = getVarNameFromToken(t);
|
|
445
|
+
if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
|
|
446
|
+
next[name] = '';
|
|
447
|
+
changed = true;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
return changed ? next : prev;
|
|
451
|
+
});
|
|
452
|
+
}, [isFullMode, templateTitle, templateDesc]);
|
|
1344
453
|
|
|
1345
|
-
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
1346
454
|
|
|
455
|
+
const RcsLabel = styled.div`
|
|
456
|
+
display: flex;
|
|
457
|
+
margin-top: 20px;
|
|
458
|
+
`;
|
|
1347
459
|
const paramObj = params || {};
|
|
1348
460
|
useEffect(() => {
|
|
1349
461
|
const { id } = paramObj;
|
|
1350
462
|
if (id && isFullMode) {
|
|
1351
463
|
setSpin(true);
|
|
1352
464
|
actions.getTemplateDetails(id, setSpin);
|
|
1353
|
-
} else if (!id && isFullMode) {
|
|
1354
|
-
// Create New: clear standalone media and ALL possible carousel card Redux slots.
|
|
1355
|
-
// Redux persists across mounts so we must clear unconditionally (carouselData may be [] on fresh mount).
|
|
1356
|
-
updateRcsImageSrc('');
|
|
1357
|
-
setRcsVideoSrc({});
|
|
1358
|
-
setRcsThumbnailSrc('');
|
|
1359
|
-
setAssetList({});
|
|
1360
|
-
setEditFlow(false);
|
|
1361
|
-
actions.clearRcsMediaAsset(0);
|
|
1362
|
-
actions.clearRcsMediaAsset(1);
|
|
1363
|
-
for (let cardIdx = 0; cardIdx < MAX_RCS_CAROUSEL_ALLOWED; cardIdx += 1) {
|
|
1364
|
-
actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIdx));
|
|
1365
|
-
actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIdx));
|
|
1366
|
-
actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIdx));
|
|
1367
|
-
}
|
|
1368
|
-
setCarouselData([]);
|
|
1369
|
-
setCarouselErrors([]);
|
|
1370
465
|
}
|
|
1371
466
|
return () => {
|
|
1372
467
|
actions.clearEditResponse();
|
|
@@ -1374,79 +469,67 @@ export const Rcs = (props) => {
|
|
|
1374
469
|
}, [paramObj.id]);
|
|
1375
470
|
|
|
1376
471
|
useEffect(() => {
|
|
1377
|
-
|
|
472
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
1378
473
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
return;
|
|
1386
|
-
}
|
|
1387
|
-
const nextVarMap = {};
|
|
1388
|
-
let varOrdinal = 0;
|
|
1389
|
-
arr.forEach((elem, idx) => {
|
|
1390
|
-
// Mustache tokens: {{1}}, {{user_name}}, {{tag.FORMAT_1}}, etc.
|
|
1391
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
1392
|
-
const id = `${elem}_${idx}`;
|
|
1393
|
-
const varName = getVarNameFromToken(elem);
|
|
1394
|
-
const globalSlot = slotOffset + varOrdinal;
|
|
1395
|
-
varOrdinal += 1;
|
|
1396
|
-
const mappedValue = resolveCardVarMappedSlotValue(
|
|
1397
|
-
cardVarMapped,
|
|
1398
|
-
varName,
|
|
1399
|
-
globalSlot,
|
|
1400
|
-
isEditLike,
|
|
1401
|
-
rcsSpanningSemanticVarNames.has(varName),
|
|
1402
|
-
);
|
|
1403
|
-
nextVarMap[id] = mappedValue;
|
|
474
|
+
const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
|
|
475
|
+
const arr = splitTemplateVarString(targetString);
|
|
476
|
+
if (!arr?.length) {
|
|
477
|
+
setVarMap({});
|
|
478
|
+
setUpdated([]);
|
|
479
|
+
return;
|
|
1404
480
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
481
|
+
const nextVarMap = {};
|
|
482
|
+
const nextUpdated = [...arr];
|
|
483
|
+
arr.forEach((elem, idx) => {
|
|
484
|
+
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
485
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
486
|
+
const id = `${elem}_${idx}`;
|
|
487
|
+
const varName = getVarNameFromToken(elem);
|
|
488
|
+
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
489
|
+
nextVarMap[id] = mappedValue;
|
|
490
|
+
if (mappedValue !== '') {
|
|
491
|
+
nextUpdated[idx] = mappedValue;
|
|
492
|
+
} else {
|
|
493
|
+
nextUpdated[idx] = elem;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
setVarMap(nextVarMap);
|
|
498
|
+
setUpdated(nextUpdated);
|
|
499
|
+
};
|
|
1412
500
|
|
|
1413
|
-
|
|
1414
|
-
|
|
501
|
+
initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
|
|
502
|
+
initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
|
|
503
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
504
|
+
|
|
505
|
+
useEffect(() => {
|
|
506
|
+
if(!isEditFlow && isFullMode){
|
|
1415
507
|
setRcsVideoSrc({});
|
|
1416
508
|
updateRcsImageSrc('');
|
|
1417
|
-
|
|
509
|
+
setUpdateRcsImageSrc('');
|
|
510
|
+
updateRcsThumbnailSrc('');
|
|
1418
511
|
setAssetList({});
|
|
1419
|
-
|
|
1420
|
-
|
|
512
|
+
}
|
|
513
|
+
}, [templateMediaType]);
|
|
1421
514
|
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
?? cardContentFirst?.status
|
|
1427
|
-
?? cardContentFirst?.approvalStatus
|
|
1428
|
-
?? '';
|
|
1429
|
-
const status = typeof raw === 'string' ? raw.trim() : String(raw);
|
|
1430
|
-
const n = status.toLowerCase();
|
|
1431
|
-
switch (n) {
|
|
1432
|
-
case 'approved':
|
|
515
|
+
const templateStatusHelper = (details) => {
|
|
516
|
+
const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
|
|
517
|
+
switch (status) {
|
|
518
|
+
case RCS_STATUSES.approved:
|
|
1433
519
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
1434
520
|
break;
|
|
1435
|
-
case
|
|
521
|
+
case RCS_STATUSES.pending:
|
|
1436
522
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
1437
523
|
break;
|
|
1438
|
-
case
|
|
524
|
+
case RCS_STATUSES.awaitingApproval:
|
|
1439
525
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
1440
526
|
break;
|
|
1441
|
-
case
|
|
527
|
+
case RCS_STATUSES.unavailable:
|
|
1442
528
|
setTemplateStatus(RCS_STATUSES.unavailable);
|
|
1443
529
|
break;
|
|
1444
|
-
case
|
|
530
|
+
case RCS_STATUSES.rejected:
|
|
1445
531
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
1446
532
|
break;
|
|
1447
|
-
case 'created':
|
|
1448
|
-
setTemplateStatus(RCS_STATUSES.created);
|
|
1449
|
-
break;
|
|
1450
533
|
default:
|
|
1451
534
|
setTemplateStatus(status);
|
|
1452
535
|
break;
|
|
@@ -1500,283 +583,48 @@ export const Rcs = (props) => {
|
|
|
1500
583
|
};
|
|
1501
584
|
|
|
1502
585
|
useEffect(() => {
|
|
1503
|
-
const details =
|
|
586
|
+
const details = isFullMode ? rcsData?.templateDetails : templateData;
|
|
1504
587
|
if (details && Object.keys(details).length > 0) {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
'versions.base.content.RCS.rcsContent.cardContent[0]',
|
|
1509
|
-
);
|
|
1510
|
-
const rcsContent = get(details, 'versions.base.content.RCS.rcsContent', {});
|
|
1511
|
-
const cardType = (rcsContent?.cardType || '').toString().toLowerCase();
|
|
1512
|
-
|
|
1513
|
-
setEditFlow(true);
|
|
1514
|
-
setTemplateName(details?.name || details?.creativeName || '');
|
|
1515
|
-
|
|
1516
|
-
const cardFromTop = get(details, 'rcsContent.cardContent[0]');
|
|
1517
|
-
const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
|
|
1518
|
-
const cardVarMappedFromCardContent =
|
|
1519
|
-
card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
|
|
1520
|
-
? card0.cardVarMapped
|
|
1521
|
-
: {};
|
|
1522
|
-
const cardVarMappedFromRootMirror =
|
|
1523
|
-
details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
|
|
1524
|
-
? details.rcsCardVarMapped
|
|
1525
|
-
: {};
|
|
1526
|
-
// Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
|
|
1527
|
-
// nested versions.cardContent[0].cardVarMapped is dropped on reload.
|
|
1528
|
-
const mergedCardVarMappedFromPayload = {
|
|
1529
|
-
...cardVarMappedFromRootMirror,
|
|
1530
|
-
...cardVarMappedFromCardContent,
|
|
1531
|
-
};
|
|
1532
|
-
const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
|
|
1533
|
-
const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
|
|
1534
|
-
const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
|
|
1535
|
-
Object.keys(mergedCardVarMappedFromPayload)
|
|
1536
|
-
.sort()
|
|
1537
|
-
.reduce((accumulator, mapKey) => {
|
|
1538
|
-
accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
|
|
1539
|
-
return accumulator;
|
|
1540
|
-
}, {}),
|
|
1541
|
-
)}`;
|
|
1542
|
-
if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
|
|
1543
|
-
lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
|
|
1544
|
-
setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
|
|
1545
|
-
}
|
|
1546
|
-
const tokenListForMap = [
|
|
1547
|
-
...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
|
|
1548
|
-
...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
|
|
1549
|
-
];
|
|
1550
|
-
const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
1551
|
-
// Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
|
|
1552
|
-
// getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
|
|
1553
|
-
const cardVarMappedBeforeCoalesce = isFullMode
|
|
1554
|
-
? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
|
|
1555
|
-
: { ...mergedCardVarMappedFromPayload };
|
|
1556
|
-
const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
1557
|
-
cardVarMappedBeforeCoalesce,
|
|
1558
|
-
loadedTitleForMap,
|
|
1559
|
-
loadedDescForMap,
|
|
1560
|
-
rcsVarRegex,
|
|
1561
|
-
);
|
|
1562
|
-
const cardVarMappedAfterNumericSlotSync = !isFullMode
|
|
1563
|
-
? syncCardVarMappedSemanticsFromSlots(
|
|
1564
|
-
cardVarMappedAfterCoalesce,
|
|
1565
|
-
loadedTitleForMap,
|
|
1566
|
-
loadedDescForMap,
|
|
1567
|
-
rcsVarRegex,
|
|
1568
|
-
)
|
|
1569
|
-
: cardVarMappedAfterCoalesce;
|
|
1570
|
-
const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
|
|
1571
|
-
// Pre-populate variable/tag mappings while opening an existing template in edit flows
|
|
1572
|
-
setCardVarMapped((previousVarMapState) => {
|
|
1573
|
-
const previousVarMap = previousVarMapState ?? {};
|
|
1574
|
-
if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
|
|
1575
|
-
const previousVarMapKeys = Object.keys(previousVarMap);
|
|
1576
|
-
const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
|
|
1577
|
-
if (previousVarMapKeys.length === nextVarMapKeys.length) {
|
|
1578
|
-
const allSlotValuesMatchPrevious = previousVarMapKeys.every(
|
|
1579
|
-
(key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
|
|
1580
|
-
);
|
|
1581
|
-
if (allSlotValuesMatchPrevious) return previousVarMapState;
|
|
1582
|
-
}
|
|
1583
|
-
return hydratedCardVarMappedResult;
|
|
1584
|
-
});
|
|
1585
|
-
|
|
1586
|
-
if (cardType === contentType.carousel) {
|
|
1587
|
-
|
|
1588
|
-
setTemplateType(contentType.carousel);
|
|
1589
|
-
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1590
|
-
const cardSettings = rcsContent?.cardSettings || {};
|
|
1591
|
-
const cardWidth = cardSettings?.cardWidth || SMALL;
|
|
1592
|
-
setSelectedCarouselWidth(cardWidth);
|
|
1593
|
-
|
|
1594
|
-
const cards = Array.isArray(rcsContent?.cardContent) ? rcsContent.cardContent : [];
|
|
1595
|
-
const firstHeight = cards?.[0]?.media?.height || MEDIUM;
|
|
1596
|
-
setSelectedCarouselHeight(firstHeight);
|
|
1597
|
-
setSelectedCarousel(`${firstHeight}_${cardWidth}`);
|
|
1598
|
-
setActiveCarouselIndex('0');
|
|
1599
|
-
|
|
1600
|
-
const hydratedCards = cards.map((c = {}, idx) => {
|
|
1601
|
-
const mediaType = c.mediaType;
|
|
1602
|
-
const media = c.media || {};
|
|
1603
|
-
const mediaUrl = media.mediaUrl || '';
|
|
1604
|
-
const thumbUrl = media.thumbnailUrl || '';
|
|
1605
|
-
const rawSuggestions = Array.isArray(c.suggestions) ? c.suggestions : [];
|
|
1606
|
-
const suggestions = idx === 0 && rawSuggestions.length === 0
|
|
1607
|
-
? cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS)
|
|
1608
|
-
: rawSuggestions;
|
|
1609
|
-
return {
|
|
1610
|
-
title: c.title || '',
|
|
1611
|
-
description: c.description || '',
|
|
1612
|
-
mediaType,
|
|
1613
|
-
imageSrc: mediaType === RCS_MEDIA_TYPES.IMAGE ? mediaUrl : '',
|
|
1614
|
-
videoAsset: mediaType === RCS_MEDIA_TYPES.VIDEO ? {
|
|
1615
|
-
videoSrc: mediaUrl,
|
|
1616
|
-
previewUrl: thumbUrl,
|
|
1617
|
-
videoThumbnail: thumbUrl,
|
|
1618
|
-
videoName: c?.media?.videoName || '',
|
|
1619
|
-
} : {},
|
|
1620
|
-
thumbnailSrc: mediaType === RCS_MEDIA_TYPES.VIDEO ? thumbUrl : '',
|
|
1621
|
-
suggestions,
|
|
1622
|
-
};
|
|
1623
|
-
});
|
|
1624
|
-
setCarouselData(
|
|
1625
|
-
hydratedCards.length > 0
|
|
1626
|
-
? ensureFirstCardDefaultPhoneSuggestions(hydratedCards)
|
|
1627
|
-
: [cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)],
|
|
1628
|
-
);
|
|
1629
|
-
setCarouselErrors(new Array(hydratedCards.length > 0 ? hydratedCards.length : 1).fill({}));
|
|
1630
|
-
|
|
1631
|
-
// Status bar uses first card's Status, keep existing behavior.
|
|
1632
|
-
if (isHostInfoBip) {
|
|
1633
|
-
setTemplateStatus('');
|
|
1634
|
-
} else {
|
|
1635
|
-
const firstCard = cards?.[0] || {};
|
|
1636
|
-
const cardForCarouselStatus = {
|
|
1637
|
-
...firstCard,
|
|
1638
|
-
Status:
|
|
1639
|
-
firstCard.Status
|
|
1640
|
-
?? firstCard.status
|
|
1641
|
-
?? firstCard.approvalStatus
|
|
1642
|
-
?? get(details, 'templateStatus')
|
|
1643
|
-
?? get(details, 'approvalStatus')
|
|
1644
|
-
?? get(details, 'creativeStatus')
|
|
1645
|
-
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
1646
|
-
?? '',
|
|
1647
|
-
};
|
|
1648
|
-
templateStatusHelper(cardForCarouselStatus);
|
|
1649
|
-
}
|
|
1650
|
-
return;
|
|
588
|
+
if (!isFullMode) {
|
|
589
|
+
const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
|
|
590
|
+
setCardVarMapped(tempCardVarMapped);
|
|
1651
591
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
card0.mediaType
|
|
1655
|
-
|| get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
1656
|
-
if (cardType !== contentType.carousel && mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
592
|
+
const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
593
|
+
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
1657
594
|
setTemplateType(contentType.text_message);
|
|
1658
|
-
} else if (cardType !== contentType.carousel && mediaType !== RCS_MEDIA_TYPES.NONE) {
|
|
1659
|
-
setTemplateType(contentType.rich_card);
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
const loadedTitle = loadedTitleForMap;
|
|
1663
|
-
const loadedDesc = loadedDescForMap;
|
|
1664
|
-
const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
|
|
1665
|
-
loadedTitle,
|
|
1666
|
-
loadedDesc,
|
|
1667
|
-
isFullMode,
|
|
1668
|
-
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
1669
|
-
rcsVarRegex,
|
|
1670
|
-
});
|
|
1671
|
-
setTemplateTitle(normalizedTitle);
|
|
1672
|
-
setTemplateDesc(normalizedDesc);
|
|
1673
|
-
setSuggestions(
|
|
1674
|
-
Array.isArray(card0.suggestions)
|
|
1675
|
-
? card0.suggestions
|
|
1676
|
-
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
|
|
1677
|
-
);
|
|
1678
|
-
const cardForStatus = {
|
|
1679
|
-
...card0,
|
|
1680
|
-
Status:
|
|
1681
|
-
card0.Status
|
|
1682
|
-
?? card0.status
|
|
1683
|
-
?? card0.approvalStatus
|
|
1684
|
-
?? get(details, 'templateStatus')
|
|
1685
|
-
?? get(details, 'approvalStatus')
|
|
1686
|
-
?? get(details, 'creativeStatus')
|
|
1687
|
-
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
1688
|
-
?? '',
|
|
1689
|
-
};
|
|
1690
|
-
if (isHostInfoBip) {
|
|
1691
|
-
setTemplateStatus('');
|
|
1692
595
|
} else {
|
|
1693
|
-
|
|
596
|
+
setTemplateType(contentType.rich_card);
|
|
1694
597
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
const
|
|
1707
|
-
const
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|| smsFallbackContent.smsTemplateContent
|
|
1713
|
-
|| smsFallbackContent.message
|
|
1714
|
-
|| fromNested
|
|
1715
|
-
|| '';
|
|
1716
|
-
const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
1717
|
-
const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
|
|
1718
|
-
const hasFallbackPayload =
|
|
1719
|
-
smsFallbackContent
|
|
1720
|
-
&& Object.keys(smsFallbackContent).length > 0
|
|
1721
|
-
&& (
|
|
1722
|
-
!!smsFallbackContent.smsTemplateName
|
|
1723
|
-
|| !!fallbackMessage
|
|
1724
|
-
|| hasVarMapped
|
|
1725
|
-
);
|
|
1726
|
-
if (hasFallbackPayload) {
|
|
1727
|
-
if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
|
|
1728
|
-
console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
|
|
1729
|
-
}
|
|
1730
|
-
const unicodeFromApi =
|
|
1731
|
-
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
1732
|
-
? smsFallbackContent.unicodeValidity
|
|
1733
|
-
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
1734
|
-
const registeredSenderIdsFromApi =
|
|
1735
|
-
extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackContent);
|
|
1736
|
-
const nextSmsState = {
|
|
1737
|
-
templateName: smsFallbackContent.smsTemplateName || '',
|
|
1738
|
-
content: fallbackMessage,
|
|
1739
|
-
templateContent: fallbackMessage,
|
|
1740
|
-
unicodeValidity: unicodeFromApi,
|
|
1741
|
-
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
1742
|
-
...(Array.isArray(registeredSenderIdsFromApi) && registeredSenderIdsFromApi.length > 0
|
|
1743
|
-
? { registeredSenderIds: registeredSenderIdsFromApi }
|
|
1744
|
-
: {}),
|
|
1745
|
-
};
|
|
1746
|
-
const hydrationKey = JSON.stringify({
|
|
1747
|
-
creativeKey: details._id || details.name || details.creativeName || '',
|
|
1748
|
-
templateName: nextSmsState.templateName,
|
|
1749
|
-
content: nextSmsState.content,
|
|
1750
|
-
unicodeValidity: nextSmsState.unicodeValidity,
|
|
1751
|
-
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
1752
|
-
senderIds:
|
|
1753
|
-
Array.isArray(registeredSenderIdsFromApi)
|
|
1754
|
-
? registeredSenderIdsFromApi.join('\u001f')
|
|
1755
|
-
: '',
|
|
1756
|
-
});
|
|
1757
|
-
if (
|
|
1758
|
-
isFullMode
|
|
1759
|
-
|| lastSmsFallbackHydrationKeyRef.current !== hydrationKey
|
|
1760
|
-
) {
|
|
1761
|
-
lastSmsFallbackHydrationKeyRef.current = hydrationKey;
|
|
1762
|
-
setSmsFallbackData(nextSmsState);
|
|
1763
|
-
}
|
|
1764
|
-
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
1765
|
-
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
1766
|
-
setSmsFallbackData(null);
|
|
598
|
+
setEditFlow(true);
|
|
599
|
+
setTemplateName(details.name || '');
|
|
600
|
+
const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
|
|
601
|
+
const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
|
|
602
|
+
const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
|
|
603
|
+
const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
|
|
604
|
+
const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
|
|
605
|
+
setTemplateTitle(normalizedTitle);
|
|
606
|
+
setTemplateDesc(normalizedDesc);
|
|
607
|
+
setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
|
|
608
|
+
templateStatusHelper(details);
|
|
609
|
+
const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
610
|
+
const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
|
|
611
|
+
setMediaData(mediaData, mediaType, cardSettings);
|
|
612
|
+
if (details?.edit) {
|
|
613
|
+
const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
|
|
614
|
+
setRcsAccount(rcsAccountId);
|
|
1767
615
|
}
|
|
1768
616
|
}
|
|
1769
|
-
}, [
|
|
617
|
+
}, [rcsData, templateData, isFullMode, isEditFlow]);
|
|
618
|
+
|
|
1770
619
|
|
|
1771
620
|
useEffect(() => {
|
|
1772
621
|
if (templateType === contentType.text_message) {
|
|
1773
622
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
1774
|
-
|
|
1775
|
-
|
|
623
|
+
setTemplateTitle('');
|
|
624
|
+
setTemplateTitleError('');
|
|
1776
625
|
if (!isEditFlow && isFullMode) {
|
|
1777
|
-
setTemplateTitle('');
|
|
1778
|
-
setTemplateTitleError('');
|
|
1779
626
|
setUpdateRcsImageSrc('');
|
|
627
|
+
setUpdateRcsVideoSrc({});
|
|
1780
628
|
setRcsVideoSrc({});
|
|
1781
629
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
1782
630
|
}
|
|
@@ -1803,8 +651,7 @@ export const Rcs = (props) => {
|
|
|
1803
651
|
if (!showDltContainer) {
|
|
1804
652
|
const { type, module } = location.query || {};
|
|
1805
653
|
const isEmbedded = type === EMBEDDED;
|
|
1806
|
-
|
|
1807
|
-
const context = isEmbedded ? module : 'outbound';
|
|
654
|
+
const context = isEmbedded ? module : DEFAULT;
|
|
1808
655
|
const embedded = isEmbedded ? type : FULL;
|
|
1809
656
|
const query = {
|
|
1810
657
|
layout: SMS,
|
|
@@ -1815,9 +662,9 @@ export const Rcs = (props) => {
|
|
|
1815
662
|
if (getDefaultTags) {
|
|
1816
663
|
query.context = getDefaultTags;
|
|
1817
664
|
}
|
|
1818
|
-
|
|
665
|
+
globalActions.fetchSchemaForEntity(query);
|
|
1819
666
|
}
|
|
1820
|
-
}, [showDltContainer
|
|
667
|
+
}, [showDltContainer]);
|
|
1821
668
|
|
|
1822
669
|
useEffect(() => {
|
|
1823
670
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -1840,114 +687,16 @@ export const Rcs = (props) => {
|
|
|
1840
687
|
context,
|
|
1841
688
|
embedded,
|
|
1842
689
|
};
|
|
1843
|
-
if (getDefaultTags) {
|
|
1844
|
-
query.context = getDefaultTags;
|
|
1845
|
-
}
|
|
1846
|
-
fetchTagSchemaIfNewQuery(query);
|
|
1847
690
|
globalActions.fetchSchemaForEntity(query);
|
|
1848
691
|
};
|
|
1849
692
|
|
|
1850
|
-
const
|
|
1851
|
-
if (!
|
|
1852
|
-
const
|
|
1853
|
-
return templateStr.replace(re, `{{${tagName}}}`);
|
|
1854
|
-
};
|
|
1855
|
-
|
|
1856
|
-
const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
|
|
1857
|
-
if (!varSegmentCompositeDomId) return;
|
|
1858
|
-
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
1859
|
-
if (underscoreIndexInCompositeId === -1) return;
|
|
1860
|
-
const segmentIndexSuffix = varSegmentCompositeDomId.slice(underscoreIndexInCompositeId + 1);
|
|
1861
|
-
if (segmentIndexSuffix === '' || isNaN(Number(segmentIndexSuffix))) return;
|
|
1862
|
-
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
1863
|
-
const semanticOrNumericVarName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
1864
|
-
if (!semanticOrNumericVarName) return;
|
|
1865
|
-
const isNumericPlaceholderSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(semanticOrNumericVarName));
|
|
1866
|
-
const templateStringForField =
|
|
1867
|
-
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
1868
|
-
const titleOrMessageFieldType =
|
|
1869
|
-
tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
1870
|
-
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
1871
|
-
varSegmentCompositeDomId,
|
|
1872
|
-
templateStringForField,
|
|
1873
|
-
titleOrMessageFieldType,
|
|
1874
|
-
);
|
|
1875
|
-
const cardVarMappedNumericSlotKey =
|
|
1876
|
-
globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined
|
|
1877
|
-
? String(globalVarSlotIndexZeroBased + 1)
|
|
1878
|
-
: null;
|
|
1879
|
-
|
|
1880
|
-
setCardVarMapped((previousCardVarMapped) => {
|
|
1881
|
-
const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
|
|
1882
|
-
if (isNumericPlaceholderSlot) {
|
|
1883
|
-
const existingValueBeforeAppend = (
|
|
1884
|
-
previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
|
|
1885
|
-
).toString();
|
|
1886
|
-
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
1887
|
-
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
1888
|
-
updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
|
|
1889
|
-
} else {
|
|
1890
|
-
// Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
|
|
1891
|
-
// "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
|
|
1892
|
-
// read/write the global numeric slot only and drop the shared semantic key.
|
|
1893
|
-
const existingValueBeforeAppend = cardVarMappedNumericSlotKey
|
|
1894
|
-
? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
|
|
1895
|
-
: String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
|
|
1896
|
-
const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
|
|
1897
|
-
delete updatedCardVarMapped[semanticOrNumericVarName];
|
|
1898
|
-
if (cardVarMappedNumericSlotKey) {
|
|
1899
|
-
updatedCardVarMapped[cardVarMappedNumericSlotKey] = mappedValueAfterAppendingTag;
|
|
1900
|
-
} else {
|
|
1901
|
-
updatedCardVarMapped[semanticOrNumericVarName] = mappedValueAfterAppendingTag;
|
|
1902
|
-
}
|
|
1903
|
-
}
|
|
1904
|
-
return updatedCardVarMapped;
|
|
1905
|
-
});
|
|
1906
|
-
|
|
1907
|
-
if (
|
|
1908
|
-
isNumericPlaceholderSlot
|
|
1909
|
-
&& (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
|
|
1910
|
-
) {
|
|
1911
|
-
if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
|
|
1912
|
-
setTemplateTitle((previousTitle) => {
|
|
1913
|
-
const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
1914
|
-
previousTitle || '',
|
|
1915
|
-
semanticOrNumericVarName,
|
|
1916
|
-
selectedTagNameFromPicker,
|
|
1917
|
-
);
|
|
1918
|
-
if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
|
|
1919
|
-
setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
|
|
1920
|
-
// 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
|
|
1921
|
-
setRcsVarSegmentEditorRemountKey((k) => k + 1);
|
|
1922
|
-
return titleAfterReplacingNumericPlaceholder;
|
|
1923
|
-
});
|
|
1924
|
-
} else {
|
|
1925
|
-
setTemplateDesc((previousDescription) => {
|
|
1926
|
-
const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
|
|
1927
|
-
previousDescription || '',
|
|
1928
|
-
semanticOrNumericVarName,
|
|
1929
|
-
selectedTagNameFromPicker,
|
|
1930
|
-
);
|
|
1931
|
-
if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
|
|
1932
|
-
return previousDescription;
|
|
1933
|
-
}
|
|
1934
|
-
setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
|
|
1935
|
-
setRcsVarSegmentEditorRemountKey((k) => k + 1);
|
|
1936
|
-
return descriptionAfterReplacingNumericPlaceholder;
|
|
1937
|
-
});
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
};
|
|
1941
|
-
|
|
1942
|
-
const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
|
|
1943
|
-
|
|
1944
|
-
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
1945
|
-
|
|
1946
|
-
const onCarouselTagSelect = (data) => {
|
|
1947
|
-
if (!carouselFocusedVarId) return;
|
|
1948
|
-
const sep = carouselFocusedVarId.lastIndexOf('_');
|
|
693
|
+
const onTagSelect = (data, areaId) => {
|
|
694
|
+
if (!areaId) return;
|
|
695
|
+
const sep = areaId.lastIndexOf('_');
|
|
1949
696
|
if (sep === -1) return;
|
|
1950
|
-
const
|
|
697
|
+
const numId = Number(areaId.slice(sep + 1));
|
|
698
|
+
if (isNaN(numId)) return;
|
|
699
|
+
const token = areaId.slice(0, sep);
|
|
1951
700
|
const variableName = getVarNameFromToken(token);
|
|
1952
701
|
if (!variableName) return;
|
|
1953
702
|
setCardVarMapped((prev) => {
|
|
@@ -1960,6 +709,10 @@ export const Rcs = (props) => {
|
|
|
1960
709
|
});
|
|
1961
710
|
};
|
|
1962
711
|
|
|
712
|
+
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
713
|
+
|
|
714
|
+
const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
|
|
715
|
+
|
|
1963
716
|
const onTagSelectFallback = (data) => {
|
|
1964
717
|
const tempMsg = `${fallbackMessage}{{${data}}}`;
|
|
1965
718
|
const error = fallbackMessageErrorHandler(tempMsg);
|
|
@@ -1979,14 +732,15 @@ export const Rcs = (props) => {
|
|
|
1979
732
|
};
|
|
1980
733
|
// tag Code end
|
|
1981
734
|
|
|
1982
|
-
const renderLabel = (value, desc) => {
|
|
735
|
+
const renderLabel = (value, showLabel, desc) => {
|
|
736
|
+
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
1983
737
|
return (
|
|
1984
738
|
<>
|
|
1985
|
-
<
|
|
739
|
+
<RcsLabel>
|
|
1986
740
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
1987
|
-
</
|
|
741
|
+
</RcsLabel>
|
|
1988
742
|
{desc && (
|
|
1989
|
-
<CapLabel type="label3"
|
|
743
|
+
<CapLabel type="label3" style={{ marginBottom: '17px' }}>
|
|
1990
744
|
{formatMessage(messages[desc])}
|
|
1991
745
|
</CapLabel>
|
|
1992
746
|
)}
|
|
@@ -2003,14 +757,15 @@ export const Rcs = (props) => {
|
|
|
2003
757
|
value: contentType.rich_card,
|
|
2004
758
|
label: formatMessage(messages.richCard),
|
|
2005
759
|
},
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
:
|
|
760
|
+
{
|
|
761
|
+
value: contentType.carousel,
|
|
762
|
+
label: (
|
|
763
|
+
<CapTooltip title={formatMessage(messages.disabledCarouselTooltip)}>
|
|
764
|
+
{formatMessage(messages.carousel)}
|
|
765
|
+
</CapTooltip>
|
|
766
|
+
),
|
|
767
|
+
disabled: true,
|
|
768
|
+
},
|
|
2014
769
|
];
|
|
2015
770
|
|
|
2016
771
|
const onTemplateNameChange = ({ target: { value } }) => {
|
|
@@ -2021,10 +776,6 @@ export const Rcs = (props) => {
|
|
|
2021
776
|
|
|
2022
777
|
const onTemplateTypeChange = ({ target: { value } }) => {
|
|
2023
778
|
setTemplateType(value);
|
|
2024
|
-
// Carousel has per-card media; keep template-level media type neutral.
|
|
2025
|
-
if (value === contentType.carousel) {
|
|
2026
|
-
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
2027
|
-
}
|
|
2028
779
|
};
|
|
2029
780
|
|
|
2030
781
|
|
|
@@ -2045,39 +796,8 @@ export const Rcs = (props) => {
|
|
|
2045
796
|
const onTemplateMediaTypeChange = ({ target: { value } }) => {
|
|
2046
797
|
setTemplateMediaType(value);
|
|
2047
798
|
};
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
if (descArray?.length) {
|
|
2051
|
-
descArray.forEach((elem, index) => {
|
|
2052
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
2053
|
-
// Variable input
|
|
2054
|
-
renderArray.push(
|
|
2055
|
-
<TextArea
|
|
2056
|
-
id={`${elem}_${index}`}
|
|
2057
|
-
key={`${elem}_${index}`}
|
|
2058
|
-
placeholder={`enter the value for ${elem}`}
|
|
2059
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2060
|
-
onChange={e => textAreaValueChange(e, type)}
|
|
2061
|
-
value={textAreaValue(index, type)}
|
|
2062
|
-
onFocus={(e) => setTextAreaId(e, type)}
|
|
2063
|
-
/>
|
|
2064
|
-
);
|
|
2065
|
-
} else if (elem) {
|
|
2066
|
-
// Static text
|
|
2067
|
-
renderArray.push(
|
|
2068
|
-
<TextArea
|
|
2069
|
-
key={`static_${index}`}
|
|
2070
|
-
value={elem}
|
|
2071
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2072
|
-
disabled
|
|
2073
|
-
className="rcs-edit-template-message-static-textarea"
|
|
2074
|
-
/>
|
|
2075
|
-
);
|
|
2076
|
-
}
|
|
2077
|
-
});
|
|
2078
|
-
}
|
|
2079
|
-
return renderArray;
|
|
2080
|
-
};
|
|
799
|
+
|
|
800
|
+
|
|
2081
801
|
const onTemplateTitleChange = ({ target: { value } }) => {
|
|
2082
802
|
let errorMessage = false;
|
|
2083
803
|
if (templateType === contentType.rich_card && !value.trim()) {
|
|
@@ -2093,7 +813,7 @@ export const Rcs = (props) => {
|
|
|
2093
813
|
|
|
2094
814
|
const onTemplateDescChange = ({ target: { value } }) => {
|
|
2095
815
|
let errorMessage = false;
|
|
2096
|
-
if(templateType === contentType.text_message && value?.length >
|
|
816
|
+
if(templateType === contentType.text_message && value?.length > RCS_TEXT_MESSAGE_MAX_LENGTH){
|
|
2097
817
|
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
2098
818
|
} else if(templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH){
|
|
2099
819
|
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
@@ -2109,16 +829,16 @@ export const Rcs = (props) => {
|
|
|
2109
829
|
|
|
2110
830
|
const templateDescErrorHandler = (value) => {
|
|
2111
831
|
let errorMessage = false;
|
|
2112
|
-
const {
|
|
832
|
+
const { isBraceError } = validateTags({
|
|
2113
833
|
content: value,
|
|
2114
834
|
tagsParam: tags,
|
|
2115
|
-
injectedTagsParams: injectedTags,
|
|
2116
835
|
location,
|
|
2117
836
|
tagModule: getDefaultTags,
|
|
837
|
+
isFullMode,
|
|
2118
838
|
}) || {};
|
|
2119
839
|
|
|
2120
840
|
const maxLength = templateType === contentType.text_message
|
|
2121
|
-
?
|
|
841
|
+
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
2122
842
|
: RCS_RICH_CARD_MAX_LENGTH;
|
|
2123
843
|
|
|
2124
844
|
if (value === '' && isMediaTypeText) {
|
|
@@ -2141,25 +861,10 @@ export const Rcs = (props) => {
|
|
|
2141
861
|
};
|
|
2142
862
|
|
|
2143
863
|
const fallbackMessageErrorHandler = (value) => {
|
|
2144
|
-
let errorMessage = false;
|
|
2145
|
-
const { unsupportedTags } = validateTags({
|
|
2146
|
-
content: value,
|
|
2147
|
-
tagsParam: tags,
|
|
2148
|
-
injectedTagsParams: injectedTags,
|
|
2149
|
-
location,
|
|
2150
|
-
tagModule: getDefaultTags,
|
|
2151
|
-
}) || {};
|
|
2152
864
|
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
2153
|
-
|
|
2154
|
-
} else if (unsupportedTags?.length > 0) {
|
|
2155
|
-
errorMessage = formatMessage(
|
|
2156
|
-
globalMessages.unsupportedTagsValidationError,
|
|
2157
|
-
{
|
|
2158
|
-
unsupportedTags,
|
|
2159
|
-
},
|
|
2160
|
-
);
|
|
865
|
+
return formatMessage(messages.fallbackMsgLenError);
|
|
2161
866
|
}
|
|
2162
|
-
return
|
|
867
|
+
return false;
|
|
2163
868
|
};
|
|
2164
869
|
|
|
2165
870
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
@@ -2206,43 +911,53 @@ export const Rcs = (props) => {
|
|
|
2206
911
|
if(!isFullMode){
|
|
2207
912
|
return false;
|
|
2208
913
|
}
|
|
2209
|
-
|
|
2210
|
-
if (!/^[\w.]+$/.test(paramName)) {
|
|
914
|
+
if (!/^\w+$/.test(paramName)) {
|
|
2211
915
|
return formatMessage(messages.unknownCharactersError);
|
|
2212
916
|
}
|
|
2213
917
|
}
|
|
2214
918
|
return false;
|
|
2215
919
|
};
|
|
920
|
+
|
|
921
|
+
const templateHeaderErrorHandler = (value) => {
|
|
922
|
+
let errorMessage = false;
|
|
923
|
+
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
924
|
+
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
925
|
+
} else {
|
|
926
|
+
errorMessage = variableErrorHandling(value);
|
|
927
|
+
}
|
|
928
|
+
return errorMessage;
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
const templateMessageErrorHandler = (value) => {
|
|
933
|
+
let errorMessage = false;
|
|
934
|
+
if (value === '') {
|
|
935
|
+
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
936
|
+
} else if (
|
|
937
|
+
value?.length
|
|
938
|
+
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
939
|
+
) {
|
|
940
|
+
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
941
|
+
} else {
|
|
942
|
+
errorMessage = variableErrorHandling(value);
|
|
943
|
+
}
|
|
944
|
+
return errorMessage;
|
|
945
|
+
};
|
|
946
|
+
|
|
2216
947
|
|
|
2217
948
|
const onMessageAddVar = () => {
|
|
2218
|
-
onAddVar(templateDesc);
|
|
949
|
+
onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
|
|
2219
950
|
};
|
|
2220
951
|
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
* (duplicate numbers would share a cardVarMapped key and bleed values across fields).
|
|
2226
|
-
*/
|
|
2227
|
-
const getNextRcsNumericVarNumber = (titleStr, descStr) => {
|
|
2228
|
-
const allExistingVars = [
|
|
2229
|
-
...(titleStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
|
|
2230
|
-
...(descStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
|
|
2231
|
-
];
|
|
2232
|
-
const existingNumbers = allExistingVars.flatMap(v => {
|
|
2233
|
-
const m = v.match(/\d+/);
|
|
2234
|
-
return m ? [parseInt(m[0], 10)] : [];
|
|
2235
|
-
});
|
|
952
|
+
const onAddVar = (type, messageContent, regex) => {
|
|
953
|
+
// Always append the next variable at the end, like WhatsApp
|
|
954
|
+
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
955
|
+
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
2236
956
|
let nextNumber = 1;
|
|
2237
957
|
while (existingNumbers.includes(nextNumber)) {
|
|
2238
958
|
nextNumber++;
|
|
2239
959
|
}
|
|
2240
|
-
|
|
2241
|
-
};
|
|
2242
|
-
|
|
2243
|
-
const onAddVar = (messageContent) => {
|
|
2244
|
-
const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
|
|
2245
|
-
if (nextNumber === null) {
|
|
960
|
+
if (nextNumber > 19) {
|
|
2246
961
|
return;
|
|
2247
962
|
}
|
|
2248
963
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -2254,13 +969,15 @@ const onAddVar = (messageContent) => {
|
|
|
2254
969
|
};
|
|
2255
970
|
|
|
2256
971
|
const onTitleAddVar = () => {
|
|
2257
|
-
//
|
|
2258
|
-
// duplicate a number already used in the description. Duplicate numeric
|
|
2259
|
-
// names would share the same cardVarMapped semantic key, causing the
|
|
2260
|
-
// description slot to reflect the title slot value and vice-versa.
|
|
972
|
+
// Always append the next variable at the end, like WhatsApp
|
|
2261
973
|
const messageContent = templateTitle;
|
|
2262
|
-
const
|
|
2263
|
-
|
|
974
|
+
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
975
|
+
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
976
|
+
let nextNumber = 1;
|
|
977
|
+
while (existingNumbers.includes(nextNumber)) {
|
|
978
|
+
nextNumber++;
|
|
979
|
+
}
|
|
980
|
+
if (nextNumber > 19) {
|
|
2264
981
|
return;
|
|
2265
982
|
}
|
|
2266
983
|
const nextVar = `{{${nextNumber}}}`;
|
|
@@ -2272,35 +989,26 @@ const onTitleAddVar = () => {
|
|
|
2272
989
|
setTemplateTitleError(error);
|
|
2273
990
|
};
|
|
2274
991
|
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
if (!token) return;
|
|
2296
|
-
setCarouselData((prev = []) => {
|
|
2297
|
-
const updated = cloneDeep(prev);
|
|
2298
|
-
if (!updated[cardIndex]) return prev;
|
|
2299
|
-
const current = (updated[cardIndex][fieldName] || '').toString();
|
|
2300
|
-
updated[cardIndex][fieldName] = `${current}${token}`;
|
|
2301
|
-
return updated;
|
|
2302
|
-
});
|
|
2303
|
-
};
|
|
992
|
+
|
|
993
|
+
const splitTemplateVarString = (str) => {
|
|
994
|
+
if (!str) return [];
|
|
995
|
+
const validVarArr = str.match(rcsVarRegex) || [];
|
|
996
|
+
const templateVarArray = [];
|
|
997
|
+
let content = str;
|
|
998
|
+
while (content?.length !== 0) {
|
|
999
|
+
const index = content.indexOf(validVarArr?.[0]);
|
|
1000
|
+
if (index !== -1) {
|
|
1001
|
+
templateVarArray.push(content.substring(0, index));
|
|
1002
|
+
templateVarArray.push(validVarArr?.[0]);
|
|
1003
|
+
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
1004
|
+
validVarArr?.shift();
|
|
1005
|
+
} else {
|
|
1006
|
+
templateVarArray.push(content);
|
|
1007
|
+
break;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return templateVarArray.filter(Boolean);
|
|
1011
|
+
};
|
|
2304
1012
|
|
|
2305
1013
|
const textAreaValue = (idValue, type) => {
|
|
2306
1014
|
if (idValue >= 0) {
|
|
@@ -2316,46 +1024,6 @@ const onTitleAddVar = () => {
|
|
|
2316
1024
|
return "";
|
|
2317
1025
|
};
|
|
2318
1026
|
|
|
2319
|
-
// Carousel: render variable-value editor for a given template string (title/description).
|
|
2320
|
-
// This matches rich-card/text edit behavior: static pieces are read-only, variable tokens are editable.
|
|
2321
|
-
const renderCarouselEditMessage = (templateStr) => {
|
|
2322
|
-
const renderArray = [];
|
|
2323
|
-
const templateArr = splitTemplateVarString(templateStr);
|
|
2324
|
-
if (templateArr?.length) {
|
|
2325
|
-
templateArr.forEach((elem, index) => {
|
|
2326
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
2327
|
-
const varName = getVarNameFromToken(elem);
|
|
2328
|
-
renderArray.push(
|
|
2329
|
-
<div key={`${elem}_${index}`} className="var-segment-message-editor__var-slot">
|
|
2330
|
-
<TextArea
|
|
2331
|
-
id={`${elem}_${index}`}
|
|
2332
|
-
placeholder={`enter the value for ${elem}`}
|
|
2333
|
-
autosize={{ minRows: 1, maxRows: 3 }}
|
|
2334
|
-
onChange={(e) => textAreaValueChange(e, TITLE_TEXT)}
|
|
2335
|
-
value={varName ? ((cardVarMapped?.[varName] ?? '').toString()) : ''}
|
|
2336
|
-
onFocus={(e) => {
|
|
2337
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
2338
|
-
setCarouselFocusedVarId(id);
|
|
2339
|
-
}}
|
|
2340
|
-
/>
|
|
2341
|
-
</div>
|
|
2342
|
-
);
|
|
2343
|
-
} else if (elem) {
|
|
2344
|
-
renderArray.push(
|
|
2345
|
-
<CapHeading
|
|
2346
|
-
key={`static_${index}`}
|
|
2347
|
-
type="h4"
|
|
2348
|
-
className="rcs-edit-template-message-split"
|
|
2349
|
-
>
|
|
2350
|
-
{elem}
|
|
2351
|
-
</CapHeading>
|
|
2352
|
-
);
|
|
2353
|
-
}
|
|
2354
|
-
});
|
|
2355
|
-
}
|
|
2356
|
-
return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
|
|
2357
|
-
};
|
|
2358
|
-
|
|
2359
1027
|
const textAreaValueChange = (e, type) => {
|
|
2360
1028
|
const value = e?.target?.value ?? '';
|
|
2361
1029
|
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
@@ -2375,9 +1043,7 @@ const onTitleAddVar = () => {
|
|
|
2375
1043
|
};
|
|
2376
1044
|
|
|
2377
1045
|
const setTextAreaId = (e, type) => {
|
|
2378
|
-
|
|
2379
|
-
// have an `.target.id` shape. Support both.
|
|
2380
|
-
const id = typeof e === 'string' ? e : (e?.target?.id || e?.currentTarget?.id || '');
|
|
1046
|
+
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
2381
1047
|
if (!id) return;
|
|
2382
1048
|
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
2383
1049
|
else setDescTextAreaId(id);
|
|
@@ -2408,71 +1074,44 @@ const onTitleAddVar = () => {
|
|
|
2408
1074
|
isEditFlow={isEditFlow}
|
|
2409
1075
|
isFullMode={isFullMode}
|
|
2410
1076
|
maxButtons={MAX_BUTTONS}
|
|
2411
|
-
|
|
2412
|
-
/>
|
|
1077
|
+
/>
|
|
2413
1078
|
</>
|
|
2414
1079
|
);
|
|
2415
1080
|
};
|
|
2416
1081
|
|
|
2417
|
-
const
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
const handleRcsVarChange = (varSegmentCompositeDomId, value, type) => {
|
|
2452
|
-
const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
|
|
2453
|
-
if (underscoreIndexInCompositeId === -1) return;
|
|
2454
|
-
const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
|
|
2455
|
-
const variableName = getVarNameFromToken(mustacheTokenFromCompositeId);
|
|
2456
|
-
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
2457
|
-
const isInvalidValue = value?.trim() === '';
|
|
2458
|
-
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
2459
|
-
const templateStringForField = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
2460
|
-
const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
|
|
2461
|
-
varSegmentCompositeDomId,
|
|
2462
|
-
templateStringForField,
|
|
2463
|
-
type,
|
|
2464
|
-
);
|
|
2465
|
-
setCardVarMapped((previousCardVarMapped) => {
|
|
2466
|
-
const updatedCardVarMapped = { ...previousCardVarMapped };
|
|
2467
|
-
// Remove stale semantic key: keeping it causes every other slot sharing the same
|
|
2468
|
-
// variable name (e.g. {{adv}} in both title and description) to read the same value
|
|
2469
|
-
// via the semantic-key fallback in resolveCardVarMappedSlotValue.
|
|
2470
|
-
delete updatedCardVarMapped[variableName];
|
|
2471
|
-
if (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined) {
|
|
2472
|
-
updatedCardVarMapped[String(globalVarSlotIndexZeroBased + 1)] = coercedSlotValue;
|
|
2473
|
-
}
|
|
2474
|
-
return updatedCardVarMapped;
|
|
2475
|
-
});
|
|
1082
|
+
const renderedRCSEditMessage = (descArray, type) => {
|
|
1083
|
+
const renderArray = [];
|
|
1084
|
+
if (descArray?.length) {
|
|
1085
|
+
descArray.forEach((elem, index) => {
|
|
1086
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
1087
|
+
// Variable input
|
|
1088
|
+
renderArray.push(
|
|
1089
|
+
<TextArea
|
|
1090
|
+
id={`${elem}_${index}`}
|
|
1091
|
+
key={`${elem}_${index}`}
|
|
1092
|
+
placeholder={`enter the value for ${elem}`}
|
|
1093
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1094
|
+
onChange={e => textAreaValueChange(e, type)}
|
|
1095
|
+
value={textAreaValue(index, type)}
|
|
1096
|
+
onFocus={(e) => setTextAreaId(e, type)}
|
|
1097
|
+
/>
|
|
1098
|
+
);
|
|
1099
|
+
} else if (elem) {
|
|
1100
|
+
// Static text
|
|
1101
|
+
renderArray.push(
|
|
1102
|
+
<TextArea
|
|
1103
|
+
key={`static_${index}`}
|
|
1104
|
+
value={elem}
|
|
1105
|
+
autosize={{ minRows: 1, maxRows: 3 }}
|
|
1106
|
+
disabled
|
|
1107
|
+
className="rcs-edit-template-message-static-textarea"
|
|
1108
|
+
style={{ background: '#fafafa', color: '#888' }}
|
|
1109
|
+
/>
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
return renderArray;
|
|
2476
1115
|
};
|
|
2477
1116
|
|
|
2478
1117
|
const renderTextComponent = () => {
|
|
@@ -2492,7 +1131,6 @@ const onTitleAddVar = () => {
|
|
|
2492
1131
|
}
|
|
2493
1132
|
suffix={
|
|
2494
1133
|
<>
|
|
2495
|
-
|
|
2496
1134
|
{(isEditFlow || !isFullMode) ? (
|
|
2497
1135
|
<TagList
|
|
2498
1136
|
label={formatMessage(globalMessages.addLabels)}
|
|
@@ -2509,28 +1147,18 @@ const onTitleAddVar = () => {
|
|
|
2509
1147
|
type="flat"
|
|
2510
1148
|
isAddBtn
|
|
2511
1149
|
onClick={onTitleAddVar}
|
|
2512
|
-
disabled={templateTitleError}
|
|
1150
|
+
disabled={!!templateTitleError}
|
|
2513
1151
|
>
|
|
2514
1152
|
{formatMessage(messages.addVar)}
|
|
2515
1153
|
</CapButton>
|
|
2516
|
-
|
|
1154
|
+
)}
|
|
2517
1155
|
</>
|
|
2518
|
-
}
|
|
2519
|
-
/>
|
|
2520
|
-
|
|
2521
|
-
{(isEditFlow || !isFullMode) ? (
|
|
2522
|
-
<VarSegmentMessageEditor
|
|
2523
|
-
key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
2524
|
-
templateString={templateTitle}
|
|
2525
|
-
valueMap={titleVarSegmentValueMapById}
|
|
2526
|
-
onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
|
|
2527
|
-
onFocus={(id) => setTitleTextAreaId(id)}
|
|
2528
|
-
varRegex={rcsVarRegex}
|
|
2529
|
-
placeholderPrefix=""
|
|
2530
|
-
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1156
|
+
}
|
|
2531
1157
|
/>
|
|
1158
|
+
<div className="rcs_text_area_wrapper">
|
|
1159
|
+
{(isEditFlow || !isFullMode) ? (
|
|
1160
|
+
renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
|
|
2532
1161
|
) : (
|
|
2533
|
-
<div className="rcs_text_area_wrapper">
|
|
2534
1162
|
<CapInput
|
|
2535
1163
|
className={`rcs-template-title-input ${
|
|
2536
1164
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -2543,8 +1171,8 @@ const onTitleAddVar = () => {
|
|
|
2543
1171
|
errorMessage={templateTitleError}
|
|
2544
1172
|
disabled={isEditFlow || !isFullMode}
|
|
2545
1173
|
/>
|
|
2546
|
-
</div>
|
|
2547
1174
|
)}
|
|
1175
|
+
</div>
|
|
2548
1176
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
2549
1177
|
<CapError className="rcs-template-title-error">
|
|
2550
1178
|
{templateTitleError}
|
|
@@ -2552,7 +1180,7 @@ const onTitleAddVar = () => {
|
|
|
2552
1180
|
)}
|
|
2553
1181
|
{!isEditFlow && isFullMode && renderTitleCharacterCount()}
|
|
2554
1182
|
</>
|
|
2555
|
-
|
|
1183
|
+
)}
|
|
2556
1184
|
|
|
2557
1185
|
{/* Template Message */}
|
|
2558
1186
|
<CapRow id="rcs-template-message-label">
|
|
@@ -2590,21 +1218,9 @@ const onTitleAddVar = () => {
|
|
|
2590
1218
|
/>
|
|
2591
1219
|
</CapRow>
|
|
2592
1220
|
<CapRow className="rcs-create-template-message-input">
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
(
|
|
2597
|
-
<VarSegmentMessageEditor
|
|
2598
|
-
key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
2599
|
-
templateString={templateDesc}
|
|
2600
|
-
valueMap={descriptionVarSegmentValueMapById}
|
|
2601
|
-
onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
|
|
2602
|
-
onFocus={(id) => setDescTextAreaId(id)}
|
|
2603
|
-
varRegex={rcsVarRegex}
|
|
2604
|
-
placeholderPrefix=""
|
|
2605
|
-
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
2606
|
-
/>
|
|
2607
|
-
)
|
|
1221
|
+
<div className="rcs_text_area_wrapper">
|
|
1222
|
+
{(isEditFlow || !isFullMode)
|
|
1223
|
+
? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
|
|
2608
1224
|
: (
|
|
2609
1225
|
<>
|
|
2610
1226
|
<TextArea
|
|
@@ -2642,15 +1258,13 @@ const onTitleAddVar = () => {
|
|
|
2642
1258
|
</>
|
|
2643
1259
|
)
|
|
2644
1260
|
}
|
|
2645
|
-
</
|
|
1261
|
+
</div>
|
|
2646
1262
|
{(isEditFlow || !isFullMode) && templateDescError && (
|
|
2647
1263
|
<CapError className="rcs-template-message-error">
|
|
2648
1264
|
{templateDescError}
|
|
2649
1265
|
</CapError>
|
|
2650
1266
|
)}
|
|
2651
|
-
{
|
|
2652
|
-
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
2653
|
-
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1267
|
+
{!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
|
|
2654
1268
|
{!isFullMode && hasTag() && (
|
|
2655
1269
|
<CapAlert
|
|
2656
1270
|
message={
|
|
@@ -2664,13 +1278,24 @@ const onTitleAddVar = () => {
|
|
|
2664
1278
|
/>
|
|
2665
1279
|
)}
|
|
2666
1280
|
</CapRow>
|
|
2667
|
-
{(
|
|
2668
|
-
renderButtonComponent()}
|
|
1281
|
+
{renderButtonComponent()}
|
|
2669
1282
|
</>
|
|
2670
1283
|
|
|
2671
1284
|
);
|
|
2672
1285
|
};
|
|
2673
1286
|
|
|
1287
|
+
|
|
1288
|
+
const fallbackSmsLength = () => (
|
|
1289
|
+
<CapLabel type="label1" className="fallback-sms-length">
|
|
1290
|
+
{formatMessage(messages.totalCharacters, {
|
|
1291
|
+
smsCount: Math.ceil(
|
|
1292
|
+
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1293
|
+
),
|
|
1294
|
+
number: fallbackMessage?.length,
|
|
1295
|
+
})}
|
|
1296
|
+
</CapLabel>
|
|
1297
|
+
);
|
|
1298
|
+
|
|
2674
1299
|
// Get character count for title (rich card only)
|
|
2675
1300
|
const getTitleCharacterCount = () => {
|
|
2676
1301
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -2685,7 +1310,7 @@ const onTitleAddVar = () => {
|
|
|
2685
1310
|
// Get max length for description based on template type
|
|
2686
1311
|
const getDescriptionMaxLength = () => {
|
|
2687
1312
|
return templateType === contentType.text_message
|
|
2688
|
-
?
|
|
1313
|
+
? RCS_TEXT_MESSAGE_MAX_LENGTH // 160 for text message
|
|
2689
1314
|
: RCS_RICH_CARD_MAX_LENGTH; // 2000 for rich card description
|
|
2690
1315
|
};
|
|
2691
1316
|
|
|
@@ -2708,64 +1333,7 @@ const onTitleAddVar = () => {
|
|
|
2708
1333
|
maxLength,
|
|
2709
1334
|
})}
|
|
2710
1335
|
</CapLabel>
|
|
2711
|
-
);
|
|
2712
|
-
};
|
|
2713
|
-
|
|
2714
|
-
const rcsDltCardDeleteHandler = () => {
|
|
2715
|
-
closeDltContainerHandler();
|
|
2716
|
-
setDltEditData({});
|
|
2717
|
-
setFallbackMessage('');
|
|
2718
|
-
setFallbackMessageError(false);
|
|
2719
|
-
setShowDltCard(false);
|
|
2720
|
-
};
|
|
2721
|
-
|
|
2722
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
2723
|
-
const {
|
|
2724
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
2725
|
-
'sms-editor': smsEditor = '',
|
|
2726
|
-
} = data.versions.base || {};
|
|
2727
|
-
setFallbackPreviewmode(true);
|
|
2728
|
-
setDltPreviewData(
|
|
2729
|
-
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
2730
|
-
);
|
|
2731
|
-
};
|
|
2732
|
-
|
|
2733
|
-
const getDltContentCardList = (content, channel) => {
|
|
2734
|
-
const extra = [
|
|
2735
|
-
<CapIcon
|
|
2736
|
-
type="edit"
|
|
2737
|
-
style={{ marginRight: '8px' }}
|
|
2738
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
2739
|
-
/>,
|
|
2740
|
-
<CapDropdown
|
|
2741
|
-
overlay={(
|
|
2742
|
-
<CapMenu>
|
|
2743
|
-
<>
|
|
2744
|
-
<CapMenu.Item
|
|
2745
|
-
className="ant-dropdown-menu-item"
|
|
2746
|
-
onClick={() => setFallbackPreviewmode(true)}
|
|
2747
|
-
>
|
|
2748
|
-
{formatMessage(globalMessages.preview)}
|
|
2749
|
-
</CapMenu.Item>
|
|
2750
|
-
<CapMenu.Item
|
|
2751
|
-
className="ant-dropdown-menu-item"
|
|
2752
|
-
onClick={rcsDltCardDeleteHandler}
|
|
2753
|
-
>
|
|
2754
|
-
{formatMessage(globalMessages.delete)}
|
|
2755
|
-
</CapMenu.Item>
|
|
2756
|
-
</>
|
|
2757
|
-
</CapMenu>
|
|
2758
|
-
)}
|
|
2759
|
-
>
|
|
2760
|
-
<CapIcon type="more" />
|
|
2761
|
-
</CapDropdown>,
|
|
2762
|
-
];
|
|
2763
|
-
return {
|
|
2764
|
-
title: channel,
|
|
2765
|
-
content,
|
|
2766
|
-
cardType: channel,
|
|
2767
|
-
extra,
|
|
2768
|
-
};
|
|
1336
|
+
);
|
|
2769
1337
|
};
|
|
2770
1338
|
|
|
2771
1339
|
// Render character count for description/message
|
|
@@ -2783,26 +1351,6 @@ const onTitleAddVar = () => {
|
|
|
2783
1351
|
);
|
|
2784
1352
|
};
|
|
2785
1353
|
|
|
2786
|
-
// Carousel: per-card character counts (same limits as rich card)
|
|
2787
|
-
const getCarouselTitleCharacterCount = (cardIndex) => {
|
|
2788
|
-
const t = carouselData?.[cardIndex]?.title || '';
|
|
2789
|
-
return t ? t.length : 0;
|
|
2790
|
-
};
|
|
2791
|
-
|
|
2792
|
-
const getCarouselDescriptionCharacterCount = (cardIndex) => {
|
|
2793
|
-
const d = carouselData?.[cardIndex]?.description || '';
|
|
2794
|
-
return d ? d.length : 0;
|
|
2795
|
-
};
|
|
2796
|
-
|
|
2797
|
-
const renderCarouselCharacterCount = (currentLength, maxLength, className = "rcs-character-count") => (
|
|
2798
|
-
<CapLabel type="label1" className={className}>
|
|
2799
|
-
{formatMessage(messages.templateMessageLength, {
|
|
2800
|
-
currentLength,
|
|
2801
|
-
maxLength,
|
|
2802
|
-
})}
|
|
2803
|
-
</CapLabel>
|
|
2804
|
-
);
|
|
2805
|
-
|
|
2806
1354
|
// Check if any RCS variables contain tags (similar to Zalo hasTag logic)
|
|
2807
1355
|
const hasTag = () => {
|
|
2808
1356
|
// Check cardVarMapped values for tags
|
|
@@ -2855,17 +1403,68 @@ const onTitleAddVar = () => {
|
|
|
2855
1403
|
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
2856
1404
|
'',
|
|
2857
1405
|
);
|
|
2858
|
-
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
2859
|
-
|| get(tempData, 'versions.base.name', '')
|
|
2860
|
-
|| '';
|
|
2861
1406
|
closeDltContainerHandler();
|
|
2862
1407
|
setDltEditData(tempData);
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
1408
|
+
setFallbackMessage(fallMsg);
|
|
1409
|
+
setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
|
|
1410
|
+
setShowDltCard(true);
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
const rcsDltCardDeleteHandler = () => {
|
|
1414
|
+
closeDltContainerHandler();
|
|
1415
|
+
setDltEditData({});
|
|
1416
|
+
setFallbackMessage('');
|
|
1417
|
+
setFallbackMessageError(false);
|
|
1418
|
+
setShowDltCard(false);
|
|
1419
|
+
};
|
|
1420
|
+
|
|
1421
|
+
const dltFallbackListingPreviewhandler = (data) => {
|
|
1422
|
+
const {
|
|
1423
|
+
'updated-sms-editor': updatedSmsEditor = [],
|
|
1424
|
+
'sms-editor': smsEditor = '',
|
|
1425
|
+
} = data.versions.base || {};
|
|
1426
|
+
setFallbackPreviewmode(true);
|
|
1427
|
+
setDltPreviewData(
|
|
1428
|
+
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1429
|
+
);
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
const getDltContentCardList = (content, channel) => {
|
|
1433
|
+
const extra = [
|
|
1434
|
+
<CapIcon
|
|
1435
|
+
type="edit"
|
|
1436
|
+
style={{ marginRight: '8px' }}
|
|
1437
|
+
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1438
|
+
/>,
|
|
1439
|
+
<CapDropdown
|
|
1440
|
+
overlay={(
|
|
1441
|
+
<CapMenu>
|
|
1442
|
+
<>
|
|
1443
|
+
<CapMenu.Item
|
|
1444
|
+
className="ant-dropdown-menu-item"
|
|
1445
|
+
onClick={() => setFallbackPreviewmode(true)}
|
|
1446
|
+
>
|
|
1447
|
+
{formatMessage(globalMessages.preview)}
|
|
1448
|
+
</CapMenu.Item>
|
|
1449
|
+
<CapMenu.Item
|
|
1450
|
+
className="ant-dropdown-menu-item"
|
|
1451
|
+
onClick={rcsDltCardDeleteHandler}
|
|
1452
|
+
>
|
|
1453
|
+
{formatMessage(globalMessages.delete)}
|
|
1454
|
+
</CapMenu.Item>
|
|
1455
|
+
</>
|
|
1456
|
+
</CapMenu>
|
|
1457
|
+
)}
|
|
1458
|
+
>
|
|
1459
|
+
<CapIcon type="more" />
|
|
1460
|
+
</CapDropdown>,
|
|
1461
|
+
];
|
|
1462
|
+
return {
|
|
1463
|
+
title: channel,
|
|
1464
|
+
content,
|
|
1465
|
+
cardType: channel,
|
|
1466
|
+
extra,
|
|
1467
|
+
};
|
|
2869
1468
|
};
|
|
2870
1469
|
|
|
2871
1470
|
const getDltSlideBoxContent = () => {
|
|
@@ -2913,34 +1512,148 @@ const onTitleAddVar = () => {
|
|
|
2913
1512
|
return { dltHeader, dltContent };
|
|
2914
1513
|
};
|
|
2915
1514
|
|
|
2916
|
-
const renderFallBackSmsComponent = () =>
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
1515
|
+
const renderFallBackSmsComponent = () => {
|
|
1516
|
+
// Completely disable fallback functionality when DLT is disabled
|
|
1517
|
+
return null;
|
|
1518
|
+
|
|
1519
|
+
// const contentArr = [];
|
|
1520
|
+
// const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
|
|
1521
|
+
// const showCardForDlt = isDltEnabled && showDltCard;
|
|
1522
|
+
// const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
|
|
1523
|
+
// //pushing common fallback sms headings
|
|
1524
|
+
// contentArr.push(
|
|
1525
|
+
// <CapRow
|
|
1526
|
+
// style={{
|
|
1527
|
+
// marginBottom: isDltEnabled ? '20px' : '10px',
|
|
1528
|
+
// }}
|
|
1529
|
+
// >
|
|
1530
|
+
// <CapHeader
|
|
1531
|
+
// title={(
|
|
1532
|
+
// <CapRow type="flex">
|
|
1533
|
+
// <CapHeading type="h4">
|
|
1534
|
+
// {formatMessage(messages.fallbackLabel)}
|
|
1535
|
+
// </CapHeading>
|
|
1536
|
+
// <CapTooltipWithInfo
|
|
1537
|
+
// placement="right"
|
|
1538
|
+
// infoIconProps={{
|
|
1539
|
+
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1540
|
+
// }}
|
|
1541
|
+
// title={formatMessage(messages.fallbackToolTip)}
|
|
1542
|
+
// />
|
|
1543
|
+
// </CapRow>
|
|
1544
|
+
// )}
|
|
1545
|
+
// description={formatMessage(messages.fallbackDesc)}
|
|
1546
|
+
// suffix={
|
|
1547
|
+
// isDltEnabled ? null : (
|
|
1548
|
+
// <CapButton
|
|
1549
|
+
// type="flat"
|
|
1550
|
+
// className="fallback-preview-btn"
|
|
1551
|
+
// prefix={<CapIcon type="eye" />}
|
|
1552
|
+
// style={{ color: CAP_SECONDARY.base }}
|
|
1553
|
+
// onClick={() => setFallbackPreviewmode(true)}
|
|
1554
|
+
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1555
|
+
// >
|
|
1556
|
+
// {formatMessage(globalMessages.preview)}
|
|
1557
|
+
// </CapButton>
|
|
1558
|
+
// )
|
|
1559
|
+
// }
|
|
1560
|
+
// />
|
|
1561
|
+
// </CapRow>,
|
|
1562
|
+
// );
|
|
1563
|
+
|
|
1564
|
+
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1565
|
+
// showAddCreativeBtnForDlt
|
|
1566
|
+
// && contentArr.push(
|
|
1567
|
+
// <CapCard className="rcs-dlt-fallback-card">
|
|
1568
|
+
// <CapRow type="flex" justify="center" align="middle">
|
|
1569
|
+
// <CapColumn span={10}>
|
|
1570
|
+
// <CapImage src={addCreativesIcon} />
|
|
1571
|
+
// </CapColumn>
|
|
1572
|
+
// <CapColumn span={14}>
|
|
1573
|
+
// <CapButton
|
|
1574
|
+
// className="add-dlt-btn"
|
|
1575
|
+
// type="secondary"
|
|
1576
|
+
// onClick={addDltMsgHandler}
|
|
1577
|
+
// >
|
|
1578
|
+
// {formatMessage(messages.addSmsCreative)}
|
|
1579
|
+
// </CapButton>
|
|
1580
|
+
// </CapColumn>
|
|
1581
|
+
// </CapRow>
|
|
1582
|
+
// </CapCard>,
|
|
1583
|
+
// );
|
|
1584
|
+
|
|
1585
|
+
// //dlt is enabled and dlt content is added, show it in a card
|
|
1586
|
+
// showCardForDlt
|
|
1587
|
+
// && contentArr.push(
|
|
1588
|
+
// <CapCustomCardList
|
|
1589
|
+
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1590
|
+
// className="rcs-dlt-card"
|
|
1591
|
+
// />,
|
|
1592
|
+
// fallbackMessageError && (
|
|
1593
|
+
// <CapError className="rcs-fallback-len-error">
|
|
1594
|
+
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1595
|
+
// </CapError>
|
|
1596
|
+
// ),
|
|
1597
|
+
// );
|
|
1598
|
+
|
|
1599
|
+
// //dlt is not enabled, show non dlt text area
|
|
1600
|
+
// showNonDltFallbackComp
|
|
1601
|
+
// && contentArr.push(
|
|
1602
|
+
// <>
|
|
1603
|
+
// <CapRow>
|
|
1604
|
+
// <CapHeader
|
|
1605
|
+
// title={(
|
|
1606
|
+
// <CapHeading type="h4">
|
|
1607
|
+
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1608
|
+
// </CapHeading>
|
|
1609
|
+
// )}
|
|
1610
|
+
// suffix={(
|
|
1611
|
+
// <TagList
|
|
1612
|
+
// label={formatMessage(globalMessages.addLabels)}
|
|
1613
|
+
// onTagSelect={onTagSelectFallback}
|
|
1614
|
+
// location={location}
|
|
1615
|
+
// tags={tags || []}
|
|
1616
|
+
// onContextChange={handleOnTagsContextChange}
|
|
1617
|
+
// injectedTags={injectedTags || {}}
|
|
1618
|
+
// selectedOfferDetails={selectedOfferDetails}
|
|
1619
|
+
// />
|
|
1620
|
+
// )}
|
|
1621
|
+
// />
|
|
1622
|
+
// </CapRow>
|
|
1623
|
+
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1624
|
+
// <TextArea
|
|
1625
|
+
// id="rcs_fallback_message_textarea"
|
|
1626
|
+
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1627
|
+
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1628
|
+
// onChange={onFallbackMessageChange}
|
|
1629
|
+
// errorMessage={fallbackMessageError}
|
|
1630
|
+
// value={fallbackMessage || ""}
|
|
1631
|
+
// />
|
|
1632
|
+
// {!aiContentBotDisabled && (
|
|
1633
|
+
// <CapAskAira.ContentGenerationBot
|
|
1634
|
+
// text={fallbackMessage || ""}
|
|
1635
|
+
// setText={(text) => {
|
|
1636
|
+
// onFallbackMessageChange({ target: { value: text } });
|
|
1637
|
+
// }}
|
|
1638
|
+
// iconPlacement="float-br"
|
|
1639
|
+
// rootStyle={{
|
|
1640
|
+
// bottom: "0.5rem",
|
|
1641
|
+
// right: "0.5rem",
|
|
1642
|
+
// position: "absolute",
|
|
1643
|
+
// }}
|
|
1644
|
+
// />
|
|
1645
|
+
// )}
|
|
1646
|
+
// </div>
|
|
1647
|
+
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1648
|
+
// </>
|
|
1649
|
+
// );
|
|
1650
|
+
|
|
1651
|
+
// return <>{contentArr}</>;
|
|
1652
|
+
};
|
|
2940
1653
|
|
|
2941
1654
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
2942
1655
|
setImageError(null);
|
|
2943
|
-
const isRcsThumbnail =
|
|
1656
|
+
const isRcsThumbnail = index === 1;
|
|
2944
1657
|
actions.uploadRcsAsset(file, type, {
|
|
2945
1658
|
isRcsThumbnail,
|
|
2946
1659
|
...fileParams,
|
|
@@ -2978,7 +1691,10 @@ const onTitleAddVar = () => {
|
|
|
2978
1691
|
const updateOnRcsImageReUpload = useCallback(() => {
|
|
2979
1692
|
setUpdateRcsImageSrc('');
|
|
2980
1693
|
}, []);
|
|
1694
|
+
|
|
1695
|
+
|
|
2981
1696
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1697
|
+
setImageError(null);
|
|
2982
1698
|
actions.uploadRcsAsset(file, type, {
|
|
2983
1699
|
...fileParams,
|
|
2984
1700
|
type: 'video',
|
|
@@ -2987,6 +1703,9 @@ const onTitleAddVar = () => {
|
|
|
2987
1703
|
});
|
|
2988
1704
|
};
|
|
2989
1705
|
|
|
1706
|
+
const updateRcsVideoSrc = (val) => {
|
|
1707
|
+
setRcsVideoSrc(val);
|
|
1708
|
+
};
|
|
2990
1709
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
2991
1710
|
setRcsVideoSrc(val);
|
|
2992
1711
|
setAssetList(val);
|
|
@@ -3009,7 +1728,7 @@ const onTitleAddVar = () => {
|
|
|
3009
1728
|
updateRcsThumbnailSrc('');
|
|
3010
1729
|
};
|
|
3011
1730
|
|
|
3012
|
-
|
|
1731
|
+
const renderThumbnailComponent = () => {
|
|
3013
1732
|
const currentDimension = selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
3014
1733
|
return !isEditFlow && (
|
|
3015
1734
|
<>
|
|
@@ -3033,7 +1752,6 @@ const onTitleAddVar = () => {
|
|
|
3033
1752
|
channel={RCS}
|
|
3034
1753
|
channelSpecificStyle={!isFullMode}
|
|
3035
1754
|
skipDimensionValidation={true}
|
|
3036
|
-
showReUploadButton={!isEditFlow && isFullMode}
|
|
3037
1755
|
/>
|
|
3038
1756
|
</>
|
|
3039
1757
|
)
|
|
@@ -3059,7 +1777,7 @@ const onTitleAddVar = () => {
|
|
|
3059
1777
|
value: dim.type,
|
|
3060
1778
|
label: `${dim.label}`
|
|
3061
1779
|
}))}
|
|
3062
|
-
|
|
1780
|
+
style={{ marginBottom: '20px' }}
|
|
3063
1781
|
/>
|
|
3064
1782
|
</>
|
|
3065
1783
|
)}
|
|
@@ -3073,7 +1791,7 @@ const onTitleAddVar = () => {
|
|
|
3073
1791
|
</div>
|
|
3074
1792
|
) : (
|
|
3075
1793
|
<CapImageUpload
|
|
3076
|
-
|
|
1794
|
+
style={{ paddingTop: '20px' }}
|
|
3077
1795
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
3078
1796
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
3079
1797
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -3084,7 +1802,7 @@ const onTitleAddVar = () => {
|
|
|
3084
1802
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
3085
1803
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
3086
1804
|
index={0}
|
|
3087
|
-
className="cap-custom-image-upload
|
|
1805
|
+
className="cap-custom-image-upload"
|
|
3088
1806
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
3089
1807
|
imageData={rcsData}
|
|
3090
1808
|
channel={RCS}
|
|
@@ -3095,7 +1813,7 @@ const onTitleAddVar = () => {
|
|
|
3095
1813
|
|
|
3096
1814
|
</>
|
|
3097
1815
|
);
|
|
3098
|
-
|
|
1816
|
+
}
|
|
3099
1817
|
|
|
3100
1818
|
const renderVideoComponent = () => {
|
|
3101
1819
|
const currentDimension =selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
|
|
@@ -3114,7 +1832,7 @@ const onTitleAddVar = () => {
|
|
|
3114
1832
|
value: dim.type,
|
|
3115
1833
|
label: `${dim.label}`
|
|
3116
1834
|
}))}
|
|
3117
|
-
|
|
1835
|
+
style={{ marginBottom: '20px' }}
|
|
3118
1836
|
/>
|
|
3119
1837
|
)}
|
|
3120
1838
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -3172,48 +1890,10 @@ const onTitleAddVar = () => {
|
|
|
3172
1890
|
};
|
|
3173
1891
|
|
|
3174
1892
|
const getRcsPreview = () => {
|
|
3175
|
-
|
|
3176
|
-
if (templateType === contentType.carousel) {
|
|
3177
|
-
const cardsForPreview = buildCarouselCardsForPreview(carouselData);
|
|
3178
|
-
const carouselDimKey = getCarouselDimensionKey();
|
|
3179
|
-
const carouselImgDims =
|
|
3180
|
-
RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
|
|
3181
|
-
const carouselVidDims =
|
|
3182
|
-
RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
|
|
3183
|
-
|| RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
|
|
3184
|
-
// Debug log for embedded/library mode preview payload (carousel)
|
|
3185
|
-
// eslint-disable-next-line no-console
|
|
3186
|
-
return (
|
|
3187
|
-
<UnifiedPreview
|
|
3188
|
-
channel={RCS}
|
|
3189
|
-
content={{
|
|
3190
|
-
carouselData: cardsForPreview,
|
|
3191
|
-
carouselPreviewDimensions: {
|
|
3192
|
-
imageWidth: carouselImgDims.width,
|
|
3193
|
-
imageHeight: carouselImgDims.height,
|
|
3194
|
-
videoThumbWidth: carouselVidDims.width,
|
|
3195
|
-
videoThumbHeight: carouselVidDims.height,
|
|
3196
|
-
},
|
|
3197
|
-
}}
|
|
3198
|
-
device={ANDROID}
|
|
3199
|
-
showDeviceToggle={false}
|
|
3200
|
-
showHeader={false}
|
|
3201
|
-
formatMessage={formatMessage}
|
|
3202
|
-
/>
|
|
3203
|
-
);
|
|
3204
|
-
}
|
|
3205
1893
|
|
|
3206
1894
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
3207
|
-
const
|
|
3208
|
-
const
|
|
3209
|
-
? 0
|
|
3210
|
-
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
3211
|
-
const resolvedTitle = isMediaTypeText
|
|
3212
|
-
? ''
|
|
3213
|
-
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
3214
|
-
const resolvedDesc = isSlotMappingMode
|
|
3215
|
-
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
3216
|
-
: templateDesc;
|
|
1895
|
+
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1896
|
+
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
3217
1897
|
return (
|
|
3218
1898
|
<UnifiedPreview
|
|
3219
1899
|
channel={RCS}
|
|
@@ -3236,65 +1916,51 @@ const onTitleAddVar = () => {
|
|
|
3236
1916
|
);
|
|
3237
1917
|
};
|
|
3238
1918
|
|
|
1919
|
+
const getUnmappedDesc = (str, mapping) => {
|
|
1920
|
+
if (!str) return '';
|
|
1921
|
+
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1922
|
+
let result = str;
|
|
1923
|
+
const replacements = [];
|
|
1924
|
+
Object.entries(mapping).forEach(([key, value]) => {
|
|
1925
|
+
const raw = (value ?? '').toString();
|
|
1926
|
+
if (!raw || raw?.trim?.() === '') return;
|
|
1927
|
+
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1928
|
+
replacements.push({ key, needle: raw });
|
|
1929
|
+
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1930
|
+
});
|
|
1931
|
+
const seen = new Set();
|
|
1932
|
+
const uniq = replacements
|
|
1933
|
+
.filter(({ key, needle }) => {
|
|
1934
|
+
const id = `${key}::${needle}`;
|
|
1935
|
+
if (seen.has(id)) return false;
|
|
1936
|
+
seen.add(id);
|
|
1937
|
+
return true;
|
|
1938
|
+
})
|
|
1939
|
+
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1940
|
+
|
|
1941
|
+
uniq.forEach(({ key, needle }) => {
|
|
1942
|
+
if (!needle) return;
|
|
1943
|
+
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1944
|
+
const regex = new RegExp(escaped, 'g');
|
|
1945
|
+
result = result.replace(regex, `{{${key}}}`);
|
|
1946
|
+
});
|
|
1947
|
+
return result;
|
|
1948
|
+
};
|
|
1949
|
+
|
|
3239
1950
|
const createPayload = () => {
|
|
3240
|
-
const
|
|
1951
|
+
const base = get(dltEditData, `versions.base`, {});
|
|
1952
|
+
const {
|
|
1953
|
+
template_id: templateId = '',
|
|
1954
|
+
template_name = '',
|
|
1955
|
+
'sms-editor': template = '',
|
|
1956
|
+
header: registeredSenderIds = [],
|
|
1957
|
+
} = base;
|
|
1958
|
+
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1959
|
+
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
3241
1960
|
const alignment = isMediaTypeImage
|
|
3242
1961
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
3243
1962
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
3244
1963
|
|
|
3245
|
-
const heightTypeForCardWidth = isMediaTypeText
|
|
3246
|
-
? undefined
|
|
3247
|
-
: isMediaTypeImage
|
|
3248
|
-
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
|
|
3249
|
-
: isMediaTypeVideo
|
|
3250
|
-
? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
|
|
3251
|
-
: undefined;
|
|
3252
|
-
const cardWidthFromSelection =
|
|
3253
|
-
heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
|
|
3254
|
-
|
|
3255
|
-
/** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
|
|
3256
|
-
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
3257
|
-
const smsFallbackMerged = !isFullMode
|
|
3258
|
-
? (() => {
|
|
3259
|
-
const local =
|
|
3260
|
-
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
3261
|
-
return {
|
|
3262
|
-
...smsFromApiShape,
|
|
3263
|
-
...local,
|
|
3264
|
-
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
3265
|
-
};
|
|
3266
|
-
})()
|
|
3267
|
-
: (smsFallbackData || {});
|
|
3268
|
-
const smsFallbackForPayload = (() => {
|
|
3269
|
-
if (isFullMode) {
|
|
3270
|
-
return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
|
|
3271
|
-
}
|
|
3272
|
-
const mapped = {
|
|
3273
|
-
templateName:
|
|
3274
|
-
smsFallbackMerged.templateName
|
|
3275
|
-
|| smsFallbackMerged.smsTemplateName
|
|
3276
|
-
|| '',
|
|
3277
|
-
// Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
|
|
3278
|
-
content:
|
|
3279
|
-
smsFallbackMerged.content
|
|
3280
|
-
|| smsFallbackMerged.smsContent
|
|
3281
|
-
|| smsFallbackMerged.smsTemplateContent
|
|
3282
|
-
|| smsFallbackMerged.message
|
|
3283
|
-
|| '',
|
|
3284
|
-
templateContent:
|
|
3285
|
-
pickFirstSmsFallbackTemplateString(smsFallbackMerged)
|
|
3286
|
-
|| '',
|
|
3287
|
-
...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
|
|
3288
|
-
&& { unicodeValidity: smsFallbackMerged.unicodeValidity }),
|
|
3289
|
-
...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
3290
|
-
&& Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
3291
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
3292
|
-
smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
3293
|
-
}),
|
|
3294
|
-
};
|
|
3295
|
-
return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
|
|
3296
|
-
})();
|
|
3297
|
-
|
|
3298
1964
|
const payload = {
|
|
3299
1965
|
name: templateName,
|
|
3300
1966
|
versions: {
|
|
@@ -3302,18 +1968,17 @@ const onTitleAddVar = () => {
|
|
|
3302
1968
|
content: {
|
|
3303
1969
|
RCS: {
|
|
3304
1970
|
rcsContent: {
|
|
1971
|
+
...(rcsAccount && !isFullMode && { accountId: rcsAccount }),
|
|
3305
1972
|
cardType: STANDALONE,
|
|
3306
1973
|
cardSettings: {
|
|
3307
1974
|
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
3308
1975
|
...(alignment && { mediaAlignment: alignment }),
|
|
3309
|
-
cardWidth:
|
|
1976
|
+
cardWidth: SMALL,
|
|
3310
1977
|
},
|
|
3311
1978
|
cardContent: [
|
|
3312
1979
|
{
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
title: templateTitle,
|
|
3316
|
-
description: templateDesc,
|
|
1980
|
+
title: resolvedTitle,
|
|
1981
|
+
description: resolvedDesc,
|
|
3317
1982
|
mediaType: templateMediaType,
|
|
3318
1983
|
...(!isMediaTypeText && {media: {
|
|
3319
1984
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -3322,32 +1987,23 @@ const onTitleAddVar = () => {
|
|
|
3322
1987
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
3323
1988
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
3324
1989
|
}}),
|
|
3325
|
-
...(
|
|
3326
|
-
const
|
|
3327
|
-
...(templateTitle
|
|
3328
|
-
...(templateDesc
|
|
1990
|
+
...(!isFullMode && (() => {
|
|
1991
|
+
const tokens = [
|
|
1992
|
+
...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
|
|
1993
|
+
...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
|
|
3329
1994
|
];
|
|
3330
|
-
const
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
3341
|
-
cardVarMappedForRcsCardOnly,
|
|
3342
|
-
varName,
|
|
3343
|
-
slotIndexZeroBased,
|
|
3344
|
-
isSlotMappingMode,
|
|
3345
|
-
rcsSpanningSemanticVarNames.has(varName),
|
|
3346
|
-
);
|
|
3347
|
-
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
3348
|
-
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
1995
|
+
const allowedKeys = tokens
|
|
1996
|
+
.map((t) => getVarNameFromToken(t))
|
|
1997
|
+
.filter(Boolean);
|
|
1998
|
+
const nextMap = {};
|
|
1999
|
+
allowedKeys.forEach((k) => {
|
|
2000
|
+
if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
|
|
2001
|
+
nextMap[k] = cardVarMapped[k];
|
|
2002
|
+
} else {
|
|
2003
|
+
nextMap[k] = '';
|
|
2004
|
+
}
|
|
3349
2005
|
});
|
|
3350
|
-
return { cardVarMapped:
|
|
2006
|
+
return { cardVarMapped: nextMap };
|
|
3351
2007
|
})()),
|
|
3352
2008
|
...(suggestions.length > 0 && { suggestions }),
|
|
3353
2009
|
}
|
|
@@ -3355,88 +2011,17 @@ const onTitleAddVar = () => {
|
|
|
3355
2011
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
3356
2012
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
3357
2013
|
},
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
const m = smsFallbackMerged || {};
|
|
3370
|
-
const tcSibling = m.templateConfigs && typeof m.templateConfigs === 'object'
|
|
3371
|
-
? m.templateConfigs
|
|
3372
|
-
: {};
|
|
3373
|
-
const smsFallbackTemplateId =
|
|
3374
|
-
(m.smsTemplateId != null && String(m.smsTemplateId).trim() !== ''
|
|
3375
|
-
? String(m.smsTemplateId)
|
|
3376
|
-
: '')
|
|
3377
|
-
|| (tcSibling.templateId != null && String(tcSibling.templateId).trim() !== ''
|
|
3378
|
-
? String(tcSibling.templateId)
|
|
3379
|
-
: '');
|
|
3380
|
-
const smsFallbackTemplateStr =
|
|
3381
|
-
pickFirstSmsFallbackTemplateString(m)
|
|
3382
|
-
|| (typeof m.templateContent === 'string' ? m.templateContent : '')
|
|
3383
|
-
|| (typeof tcSibling.template === 'string' ? tcSibling.template : '')
|
|
3384
|
-
|| '';
|
|
3385
|
-
const smsFallbackTemplateName =
|
|
3386
|
-
m.templateName
|
|
3387
|
-
|| m.smsTemplateName
|
|
3388
|
-
|| tcSibling.templateName
|
|
3389
|
-
|| tcSibling.name
|
|
3390
|
-
|| '';
|
|
3391
|
-
const registeredSenderIdsForPayload = Array.isArray(m.registeredSenderIds)
|
|
3392
|
-
? m.registeredSenderIds
|
|
3393
|
-
: Array.isArray(tcSibling.registeredSenderIds)
|
|
3394
|
-
? tcSibling.registeredSenderIds
|
|
3395
|
-
: Array.isArray(tcSibling.header)
|
|
3396
|
-
? tcSibling.header
|
|
3397
|
-
: null;
|
|
3398
|
-
const hasRegisteredSenderIds = Array.isArray(registeredSenderIdsForPayload);
|
|
3399
|
-
const smsFallbackTemplateConfigs =
|
|
3400
|
-
smsFallbackTemplateId || hasRegisteredSenderIds
|
|
3401
|
-
? {
|
|
3402
|
-
...(smsFallbackTemplateId && { templateId: smsFallbackTemplateId }),
|
|
3403
|
-
...(smsFallbackTemplateStr && { template: smsFallbackTemplateStr }),
|
|
3404
|
-
...(smsFallbackTemplateName && {
|
|
3405
|
-
templateName: smsFallbackTemplateName,
|
|
3406
|
-
}),
|
|
3407
|
-
...(hasRegisteredSenderIds && {
|
|
3408
|
-
registeredSenderIds: registeredSenderIdsForPayload,
|
|
3409
|
-
}),
|
|
3410
|
-
}
|
|
3411
|
-
: null;
|
|
3412
|
-
const isDltCampaign = !isFullMode && isTraiDLTEnable(isFullMode, smsRegister);
|
|
3413
|
-
return {
|
|
3414
|
-
smsFallBackContent: isFullMode
|
|
3415
|
-
? {
|
|
3416
|
-
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
3417
|
-
smsContent: smsBodyText,
|
|
3418
|
-
// cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
|
|
3419
|
-
message: smsBodyText,
|
|
3420
|
-
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
3421
|
-
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
3422
|
-
}),
|
|
3423
|
-
...(smsFallbackForPayload.rcsSmsFallbackVarMapped
|
|
3424
|
-
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
3425
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
3426
|
-
}),
|
|
3427
|
-
...(smsFallbackTemplateConfigs && {
|
|
3428
|
-
templateConfigs: smsFallbackTemplateConfigs,
|
|
3429
|
-
}),
|
|
3430
|
-
}
|
|
3431
|
-
: {
|
|
3432
|
-
// campaign payload: non-DLT → message only; DLT → message + templateConfigs
|
|
3433
|
-
message: smsBodyText,
|
|
3434
|
-
...(isDltCampaign && smsFallbackTemplateConfigs && {
|
|
3435
|
-
templateConfigs: smsFallbackTemplateConfigs,
|
|
3436
|
-
}),
|
|
3437
|
-
},
|
|
3438
|
-
};
|
|
3439
|
-
})()),
|
|
2014
|
+
smsFallBackContent: {
|
|
2015
|
+
message: fallbackMessage,
|
|
2016
|
+
...(isDltEnabled && {
|
|
2017
|
+
templateConfigs: {
|
|
2018
|
+
templateId,
|
|
2019
|
+
templateName: template_name,
|
|
2020
|
+
template,
|
|
2021
|
+
registeredSenderIds,
|
|
2022
|
+
},
|
|
2023
|
+
}),
|
|
2024
|
+
},
|
|
3440
2025
|
},
|
|
3441
2026
|
},
|
|
3442
2027
|
},
|
|
@@ -3446,109 +2031,6 @@ const onTitleAddVar = () => {
|
|
|
3446
2031
|
return payload;
|
|
3447
2032
|
};
|
|
3448
2033
|
|
|
3449
|
-
/** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
|
|
3450
|
-
const testPreviewFormData = useMemo(() => {
|
|
3451
|
-
const payload = createPayload();
|
|
3452
|
-
const rcs = payload?.versions?.base?.content?.RCS;
|
|
3453
|
-
if (!rcs) return null;
|
|
3454
|
-
// createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
|
|
3455
|
-
const accountIdForCreateMessageMeta =
|
|
3456
|
-
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
3457
|
-
? String(wecrmAccountId)
|
|
3458
|
-
: accountId;
|
|
3459
|
-
const isSlotMappingModeForPreview = isEditFlow || !isFullMode;
|
|
3460
|
-
let rcsForTest = {
|
|
3461
|
-
...rcs,
|
|
3462
|
-
rcsContent: {
|
|
3463
|
-
...rcs.rcsContent,
|
|
3464
|
-
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
3465
|
-
},
|
|
3466
|
-
};
|
|
3467
|
-
/** Approval payload keeps numeric-only `cardVarMapped`; preview APIs still need semantic keys. */
|
|
3468
|
-
if (isSlotMappingModeForPreview) {
|
|
3469
|
-
const cardContent = rcsForTest.rcsContent?.cardContent;
|
|
3470
|
-
if (Array.isArray(cardContent) && cardContent[0]) {
|
|
3471
|
-
const fullCardVarMapped = coalesceCardVarMappedToTemplate(
|
|
3472
|
-
pickRcsCardVarMappedEntries(cardVarMapped),
|
|
3473
|
-
templateTitle,
|
|
3474
|
-
templateDesc,
|
|
3475
|
-
rcsVarRegex,
|
|
3476
|
-
);
|
|
3477
|
-
rcsForTest = {
|
|
3478
|
-
...rcsForTest,
|
|
3479
|
-
rcsContent: {
|
|
3480
|
-
...rcsForTest.rcsContent,
|
|
3481
|
-
cardContent: [
|
|
3482
|
-
{ ...cardContent[0], cardVarMapped: fullCardVarMapped },
|
|
3483
|
-
...cardContent.slice(1),
|
|
3484
|
-
],
|
|
3485
|
-
},
|
|
3486
|
-
};
|
|
3487
|
-
}
|
|
3488
|
-
}
|
|
3489
|
-
const out = {
|
|
3490
|
-
versions: {
|
|
3491
|
-
base: {
|
|
3492
|
-
content: {
|
|
3493
|
-
RCS: rcsForTest,
|
|
3494
|
-
},
|
|
3495
|
-
},
|
|
3496
|
-
},
|
|
3497
|
-
};
|
|
3498
|
-
const fb = smsFallbackData;
|
|
3499
|
-
if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
|
|
3500
|
-
out.templateConfigs = {
|
|
3501
|
-
templateId: fb.smsTemplateId || '',
|
|
3502
|
-
template: fb.templateContent || fb.content || '',
|
|
3503
|
-
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
3504
|
-
registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
|
|
3505
|
-
};
|
|
3506
|
-
}
|
|
3507
|
-
return out;
|
|
3508
|
-
}, [
|
|
3509
|
-
templateName,
|
|
3510
|
-
templateTitle,
|
|
3511
|
-
templateDesc,
|
|
3512
|
-
templateMediaType,
|
|
3513
|
-
cardVarMapped,
|
|
3514
|
-
suggestions,
|
|
3515
|
-
rcsImageSrc,
|
|
3516
|
-
rcsVideoSrc,
|
|
3517
|
-
rcsThumbnailSrc,
|
|
3518
|
-
selectedDimension,
|
|
3519
|
-
smsFallbackData,
|
|
3520
|
-
isFullMode,
|
|
3521
|
-
isEditFlow,
|
|
3522
|
-
templateType,
|
|
3523
|
-
accountId,
|
|
3524
|
-
wecrmAccountId,
|
|
3525
|
-
accessToken,
|
|
3526
|
-
accountName,
|
|
3527
|
-
hostName,
|
|
3528
|
-
smsRegister,
|
|
3529
|
-
]);
|
|
3530
|
-
|
|
3531
|
-
/**
|
|
3532
|
-
* Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
|
|
3533
|
-
* with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
|
|
3534
|
-
* miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
|
|
3535
|
-
*/
|
|
3536
|
-
const librarySmsFallbackMergedForValidation = useMemo(() => {
|
|
3537
|
-
if (isFullMode) {
|
|
3538
|
-
return smsFallbackData;
|
|
3539
|
-
}
|
|
3540
|
-
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
3541
|
-
const local =
|
|
3542
|
-
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
3543
|
-
return {
|
|
3544
|
-
...smsFromApiShape,
|
|
3545
|
-
...local,
|
|
3546
|
-
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
3547
|
-
};
|
|
3548
|
-
}, [isFullMode, templateData, smsFallbackData]);
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
2034
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
3553
2035
|
// eslint-disable-next-line no-undef
|
|
3554
2036
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -3578,9 +2060,6 @@ const onTitleAddVar = () => {
|
|
|
3578
2060
|
_id: params?.id,
|
|
3579
2061
|
validity: true,
|
|
3580
2062
|
type: RCS,
|
|
3581
|
-
// CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
|
|
3582
|
-
// the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
|
|
3583
|
-
closeSlideBoxAfterSubmit: !isFullMode,
|
|
3584
2063
|
};
|
|
3585
2064
|
getFormData(formDataParams);
|
|
3586
2065
|
};
|
|
@@ -3594,7 +2073,6 @@ const onTitleAddVar = () => {
|
|
|
3594
2073
|
actionCallback({ resp, errorMessage });
|
|
3595
2074
|
setSpin(false); // Always turn off spinner
|
|
3596
2075
|
if (!errorMessage) {
|
|
3597
|
-
setTemplateStatus(RCS_STATUSES.pending);
|
|
3598
2076
|
onCreateComplete();
|
|
3599
2077
|
}
|
|
3600
2078
|
});
|
|
@@ -3606,86 +2084,6 @@ const onTitleAddVar = () => {
|
|
|
3606
2084
|
}
|
|
3607
2085
|
};
|
|
3608
2086
|
|
|
3609
|
-
/** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
|
|
3610
|
-
const smsFallbackBlocksDone = () => {
|
|
3611
|
-
// Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
|
|
3612
|
-
if (
|
|
3613
|
-
!isFullMode
|
|
3614
|
-
&& !isTraiDLTEnable(isFullMode, smsRegister)
|
|
3615
|
-
&& smsFallbackData == null
|
|
3616
|
-
&& hasMeaningfulSmsFallbackShape(
|
|
3617
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
3618
|
-
)
|
|
3619
|
-
) {
|
|
3620
|
-
return true;
|
|
3621
|
-
}
|
|
3622
|
-
if (!smsFallbackData) return false;
|
|
3623
|
-
// Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
|
|
3624
|
-
// concern, not a structural requirement for approval — the registered SMS template body stands on
|
|
3625
|
-
// its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
|
|
3626
|
-
if (isFullMode) return false;
|
|
3627
|
-
const merged = librarySmsFallbackMergedForValidation;
|
|
3628
|
-
const templateText = pickFirstSmsFallbackTemplateString(merged);
|
|
3629
|
-
if (!templateText) {
|
|
3630
|
-
return true;
|
|
3631
|
-
}
|
|
3632
|
-
const rawVarMap =
|
|
3633
|
-
merged.rcsSmsFallbackVarMapped
|
|
3634
|
-
|| merged['rcs-sms-fallback-var-mapped'];
|
|
3635
|
-
const varMap =
|
|
3636
|
-
rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
|
|
3637
|
-
return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
|
|
3638
|
-
};
|
|
3639
|
-
|
|
3640
|
-
/**
|
|
3641
|
-
* Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
|
|
3642
|
-
* semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
|
|
3643
|
-
* / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
|
|
3644
|
-
*/
|
|
3645
|
-
const isLibraryCampaignCardVarMappingIncomplete = () => {
|
|
3646
|
-
if (isFullMode) return false;
|
|
3647
|
-
const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
|
|
3648
|
-
rcsVarTestRegex.test(elem),
|
|
3649
|
-
);
|
|
3650
|
-
const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
|
|
3651
|
-
rcsVarTestRegex.test(elem),
|
|
3652
|
-
);
|
|
3653
|
-
const orderedVarNames = [
|
|
3654
|
-
...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
3655
|
-
...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
3656
|
-
];
|
|
3657
|
-
if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
|
|
3658
|
-
return true;
|
|
3659
|
-
}
|
|
3660
|
-
return orderedVarNames.some((name, globalIdx) => {
|
|
3661
|
-
const v = resolveCardVarMappedSlotValue(
|
|
3662
|
-
cardVarMapped,
|
|
3663
|
-
name,
|
|
3664
|
-
globalIdx,
|
|
3665
|
-
true,
|
|
3666
|
-
rcsSpanningSemanticVarNames.has(name),
|
|
3667
|
-
);
|
|
3668
|
-
const s = v == null ? '' : String(v);
|
|
3669
|
-
return s.trim() === '';
|
|
3670
|
-
});
|
|
3671
|
-
};
|
|
3672
|
-
|
|
3673
|
-
const isCarouselLibraryIncomplete = () => {
|
|
3674
|
-
if (!isCarouselType || isFullMode) return false;
|
|
3675
|
-
if ((carouselErrors || []).some((err) => err?.title || err?.description)) return true;
|
|
3676
|
-
const unfilledVar = (carouselData || []).some((card) =>
|
|
3677
|
-
['title', 'description'].some((field) => {
|
|
3678
|
-
const tokens = splitTemplateVarStringRcs(card?.[field] || '').filter((t) => rcsVarTestRegex.test(t));
|
|
3679
|
-
return tokens.some((t) => {
|
|
3680
|
-
const name = t.replace(/^\{\{|\}\}$/g, '');
|
|
3681
|
-
const v = cardVarMapped?.[name];
|
|
3682
|
-
return v == null || String(v).trim() === '';
|
|
3683
|
-
});
|
|
3684
|
-
})
|
|
3685
|
-
);
|
|
3686
|
-
return unfilledVar;
|
|
3687
|
-
};
|
|
3688
|
-
|
|
3689
2087
|
const isDisableDone = () => {
|
|
3690
2088
|
if(isEditFlow){
|
|
3691
2089
|
return false;
|
|
@@ -3696,26 +2094,46 @@ const onTitleAddVar = () => {
|
|
|
3696
2094
|
}
|
|
3697
2095
|
}
|
|
3698
2096
|
|
|
3699
|
-
if
|
|
3700
|
-
|
|
3701
|
-
|
|
2097
|
+
if(!isFullMode){
|
|
2098
|
+
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2099
|
+
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2100
|
+
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
3702
2101
|
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
2102
|
+
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2103
|
+
return true;
|
|
2104
|
+
}
|
|
3706
2105
|
|
|
3707
|
-
|
|
3708
|
-
|
|
2106
|
+
const hasEmptyMapping =
|
|
2107
|
+
cardVarMapped &&
|
|
2108
|
+
Object.keys(cardVarMapped).length > 0 &&
|
|
2109
|
+
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2110
|
+
if (typeof v !== 'string') return !v; // null/undefined
|
|
2111
|
+
return v.trim() === ''; // empty string
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
if (hasEmptyMapping) {
|
|
2115
|
+
return true;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
const anyMissing = allVars.some(name => {
|
|
2119
|
+
const v = cardVarMapped?.[name];
|
|
2120
|
+
if (typeof v !== 'string') return !v;
|
|
2121
|
+
return v.trim() === '';
|
|
2122
|
+
});
|
|
2123
|
+
if (anyMissing) {
|
|
2124
|
+
return true;
|
|
2125
|
+
}
|
|
3709
2126
|
}
|
|
3710
2127
|
|
|
3711
|
-
|
|
2128
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
3712
2129
|
return true;
|
|
2130
|
+
|
|
3713
2131
|
}
|
|
3714
|
-
if (
|
|
2132
|
+
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
3715
2133
|
return true;
|
|
3716
2134
|
}
|
|
3717
2135
|
|
|
3718
|
-
if (
|
|
2136
|
+
if (isMediaTypeVideo && (rcsVideoSrc.videoSrc === '' || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
3719
2137
|
return true;
|
|
3720
2138
|
}
|
|
3721
2139
|
if (buttonType.includes(CTA)) {
|
|
@@ -3727,36 +2145,53 @@ const onTitleAddVar = () => {
|
|
|
3727
2145
|
return true;
|
|
3728
2146
|
}
|
|
3729
2147
|
}
|
|
3730
|
-
if (templateDescError || templateTitleError) {
|
|
3731
|
-
return true;
|
|
3732
|
-
}
|
|
3733
|
-
if (
|
|
3734
|
-
smsFallbackData?.content
|
|
3735
|
-
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
3736
|
-
) {
|
|
2148
|
+
if (templateDescError || templateTitleError || fallbackMessageError) {
|
|
3737
2149
|
return true;
|
|
3738
2150
|
}
|
|
3739
2151
|
return false;
|
|
3740
2152
|
};
|
|
3741
2153
|
|
|
3742
2154
|
const isEditDisableDone = () => {
|
|
3743
|
-
|
|
2155
|
+
|
|
2156
|
+
if (templateStatus !== RCS_STATUSES.approved) {
|
|
3744
2157
|
return true;
|
|
3745
2158
|
}
|
|
3746
2159
|
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
// }
|
|
3752
|
-
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
3753
|
-
return true;
|
|
2160
|
+
if (!isFullMode) {
|
|
2161
|
+
if (templateName.trim() === '' || templateNameError) {
|
|
2162
|
+
return true;
|
|
2163
|
+
}
|
|
3754
2164
|
}
|
|
2165
|
+
if(!isFullMode){
|
|
2166
|
+
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2167
|
+
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2168
|
+
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
3755
2169
|
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
2170
|
+
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2171
|
+
return true;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
const hasEmptyMapping =
|
|
2175
|
+
cardVarMapped &&
|
|
2176
|
+
Object.keys(cardVarMapped).length > 0 &&
|
|
2177
|
+
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2178
|
+
if (typeof v !== 'string') return !v; // null/undefined
|
|
2179
|
+
return v.trim() === ''; // empty string
|
|
2180
|
+
});
|
|
2181
|
+
|
|
2182
|
+
if (hasEmptyMapping) {
|
|
2183
|
+
return true;
|
|
2184
|
+
}
|
|
3759
2185
|
|
|
2186
|
+
const anyMissing = allVars.some(name => {
|
|
2187
|
+
const v = cardVarMapped?.[name];
|
|
2188
|
+
if (typeof v !== 'string') return !v;
|
|
2189
|
+
return v.trim() === '';
|
|
2190
|
+
});
|
|
2191
|
+
if (anyMissing) {
|
|
2192
|
+
return true;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
3760
2195
|
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
3761
2196
|
return true;
|
|
3762
2197
|
}
|
|
@@ -3775,13 +2210,7 @@ const onTitleAddVar = () => {
|
|
|
3775
2210
|
return true;
|
|
3776
2211
|
}
|
|
3777
2212
|
}
|
|
3778
|
-
if (templateTitleError || templateDescError) {
|
|
3779
|
-
return true;
|
|
3780
|
-
}
|
|
3781
|
-
if (
|
|
3782
|
-
smsFallbackData?.content
|
|
3783
|
-
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
3784
|
-
) {
|
|
2213
|
+
if (templateTitleError || templateDescError || fallbackMessageError) {
|
|
3785
2214
|
return true;
|
|
3786
2215
|
}
|
|
3787
2216
|
return false;
|
|
@@ -3831,58 +2260,54 @@ const onTitleAddVar = () => {
|
|
|
3831
2260
|
};
|
|
3832
2261
|
|
|
3833
2262
|
const getMainContent = () => {
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
2263
|
+
if (showDltContainer && !fallbackPreviewmode) {
|
|
2264
|
+
const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
|
|
2265
|
+
const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
|
|
2266
|
+
return (
|
|
2267
|
+
<CapSlideBox
|
|
2268
|
+
show={showDltContainer}
|
|
2269
|
+
header={dltHeader}
|
|
2270
|
+
content={dltContent}
|
|
2271
|
+
handleClose={closeDltContainerHandler}
|
|
2272
|
+
size="size-xl"
|
|
2273
|
+
/>
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
3837
2277
|
return (
|
|
3838
2278
|
<>
|
|
3839
|
-
{templateStatus !== '' && (
|
|
3840
|
-
<
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
{
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
)}
|
|
3852
|
-
</CapColumn>
|
|
3853
|
-
</CapRow>
|
|
2279
|
+
{templateStatus !== '' && (<CapRow className="template-status-container">
|
|
2280
|
+
<CapLabel type="label2">
|
|
2281
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
2282
|
+
</CapLabel>
|
|
2283
|
+
|
|
2284
|
+
{templateStatus && (
|
|
2285
|
+
<CapAlert
|
|
2286
|
+
message={getTemplateStatusMessage()}
|
|
2287
|
+
type={getTemplateStatusType(templateStatus)}
|
|
2288
|
+
/>
|
|
2289
|
+
)}
|
|
2290
|
+
</CapRow>
|
|
3854
2291
|
)}
|
|
3855
|
-
<CapRow className=
|
|
2292
|
+
<CapRow className="cap-rcs-creatives">
|
|
3856
2293
|
<CapColumn span={14}>
|
|
3857
2294
|
{/* template name */}
|
|
3858
2295
|
{isFullMode && (
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
onChange={onTemplateNameChange}
|
|
3873
|
-
errorMessage={templateNameError}
|
|
3874
|
-
placeholder={formatMessage(
|
|
3875
|
-
globalMessages.templateNamePlaceholder,
|
|
3876
|
-
)}
|
|
3877
|
-
value={templateName || ''}
|
|
3878
|
-
size="default"
|
|
3879
|
-
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
3880
|
-
disabled={(isEditFlow || !isFullMode)}
|
|
3881
|
-
/>
|
|
3882
|
-
)
|
|
2296
|
+
<CapInput
|
|
2297
|
+
id="rcs_template_name_input"
|
|
2298
|
+
data-testid="template_name"
|
|
2299
|
+
onChange={onTemplateNameChange}
|
|
2300
|
+
errorMessage={templateNameError}
|
|
2301
|
+
placeholder={formatMessage(
|
|
2302
|
+
globalMessages.templateNamePlaceholder,
|
|
2303
|
+
)}
|
|
2304
|
+
value={templateName || ''}
|
|
2305
|
+
size="default"
|
|
2306
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
2307
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2308
|
+
/>
|
|
3883
2309
|
)}
|
|
3884
2310
|
{renderLabel('templateTypeLabel')}
|
|
3885
|
-
|
|
3886
2311
|
<CapRadioGroup
|
|
3887
2312
|
id="select-rcs-template-type"
|
|
3888
2313
|
options={TEMPLATE_TYPE_OPTIONS}
|
|
@@ -3891,30 +2316,24 @@ const onTitleAddVar = () => {
|
|
|
3891
2316
|
disabled={(isEditFlow || !isFullMode)}
|
|
3892
2317
|
/>
|
|
3893
2318
|
|
|
3894
|
-
{
|
|
3895
|
-
|
|
3896
|
-
) : (
|
|
2319
|
+
{/* Show media only for rich_card or carousel */}
|
|
2320
|
+
{(templateType === contentType.rich_card || templateType === contentType.carousel) && (
|
|
3897
2321
|
<>
|
|
3898
|
-
{
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
{
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
<div className="rcs-container-image">
|
|
3910
|
-
{getMediaBasedComponent()}
|
|
3911
|
-
</div>
|
|
3912
|
-
</>
|
|
3913
|
-
)}
|
|
3914
|
-
{renderTextComponent()}
|
|
2322
|
+
{renderLabel('mediaLabel')}
|
|
2323
|
+
<CapRadioGroup
|
|
2324
|
+
options={mediaRadioOptions || []}
|
|
2325
|
+
value={templateMediaType}
|
|
2326
|
+
onChange={onTemplateMediaTypeChange}
|
|
2327
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2328
|
+
className="rcs-radio"
|
|
2329
|
+
/>
|
|
2330
|
+
<div className="rcs-container-image">
|
|
2331
|
+
{getMediaBasedComponent()}
|
|
2332
|
+
</div>
|
|
3915
2333
|
</>
|
|
3916
2334
|
)}
|
|
3917
|
-
|
|
2335
|
+
{renderTextComponent()}
|
|
2336
|
+
<CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
|
|
3918
2337
|
{renderFallBackSmsComponent()}
|
|
3919
2338
|
<div className="rcs-scroll-div" />
|
|
3920
2339
|
</CapColumn>
|
|
@@ -3926,8 +2345,7 @@ const onTitleAddVar = () => {
|
|
|
3926
2345
|
|
|
3927
2346
|
|
|
3928
2347
|
<div className="rcs-footer">
|
|
3929
|
-
{
|
|
3930
|
-
{!isEditFlow && isFullMode && (
|
|
2348
|
+
{!isEditFlow && (
|
|
3931
2349
|
<>
|
|
3932
2350
|
<div className="button-disabled-tooltip-wrapper">
|
|
3933
2351
|
<CapButton
|
|
@@ -3935,9 +2353,7 @@ const onTitleAddVar = () => {
|
|
|
3935
2353
|
disabled={isDisableDone()}
|
|
3936
2354
|
className="rcs-done-btn"
|
|
3937
2355
|
>
|
|
3938
|
-
<FormattedMessage
|
|
3939
|
-
{...(isHostInfoBip ? messages.doneButtonLabel : messages.sendForApprovalButtonLabel)}
|
|
3940
|
-
/>
|
|
2356
|
+
<FormattedMessage {...messages.sendForApprovalButtonLabel} />
|
|
3941
2357
|
</CapButton>
|
|
3942
2358
|
</div>
|
|
3943
2359
|
<CapTooltip
|
|
@@ -3950,6 +2366,7 @@ const onTitleAddVar = () => {
|
|
|
3950
2366
|
className="rcs-test-preview-btn"
|
|
3951
2367
|
type="secondary"
|
|
3952
2368
|
disabled={true}
|
|
2369
|
+
style={{ marginLeft: "8px" }}
|
|
3953
2370
|
>
|
|
3954
2371
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
3955
2372
|
</CapButton>
|
|
@@ -3970,7 +2387,7 @@ const onTitleAddVar = () => {
|
|
|
3970
2387
|
</div>
|
|
3971
2388
|
</>
|
|
3972
2389
|
)}
|
|
3973
|
-
{isEditFlow &&
|
|
2390
|
+
{isEditFlow && templateStatus === RCS_STATUSES.approved && (
|
|
3974
2391
|
<>
|
|
3975
2392
|
<CapButton
|
|
3976
2393
|
onClick={handleTestAndPreview}
|
|
@@ -3982,6 +2399,51 @@ const onTitleAddVar = () => {
|
|
|
3982
2399
|
</>
|
|
3983
2400
|
)}
|
|
3984
2401
|
</div>
|
|
2402
|
+
|
|
2403
|
+
|
|
2404
|
+
{fallbackPreviewmode && (
|
|
2405
|
+
<CapSlideBox
|
|
2406
|
+
className="rcs-fallback-preview"
|
|
2407
|
+
show={fallbackPreviewmode}
|
|
2408
|
+
header={(
|
|
2409
|
+
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2410
|
+
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2411
|
+
</CapHeading>
|
|
2412
|
+
)}
|
|
2413
|
+
content={(
|
|
2414
|
+
<>
|
|
2415
|
+
<UnifiedPreview
|
|
2416
|
+
channel={RCS}
|
|
2417
|
+
content={{
|
|
2418
|
+
rcsPreviewContent: {
|
|
2419
|
+
rcsDesc: tempMsg,
|
|
2420
|
+
},
|
|
2421
|
+
}}
|
|
2422
|
+
device={ANDROID}
|
|
2423
|
+
showDeviceToggle={false}
|
|
2424
|
+
showHeader={false}
|
|
2425
|
+
formatMessage={formatMessage}
|
|
2426
|
+
/>
|
|
2427
|
+
<CapHeading
|
|
2428
|
+
type="h3"
|
|
2429
|
+
style={{ textAlign: 'center' }}
|
|
2430
|
+
className="margin-t-16"
|
|
2431
|
+
>
|
|
2432
|
+
{formatMessage(messages.totalCharacters, {
|
|
2433
|
+
smsCount: Math.ceil(
|
|
2434
|
+
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2435
|
+
),
|
|
2436
|
+
number: tempMsg.length,
|
|
2437
|
+
})}
|
|
2438
|
+
</CapHeading>
|
|
2439
|
+
</>
|
|
2440
|
+
)}
|
|
2441
|
+
handleClose={() => {
|
|
2442
|
+
setFallbackPreviewmode(false);
|
|
2443
|
+
setDltPreviewData('');
|
|
2444
|
+
}}
|
|
2445
|
+
/>
|
|
2446
|
+
)}
|
|
3985
2447
|
</>
|
|
3986
2448
|
);
|
|
3987
2449
|
};
|
|
@@ -3990,57 +2452,23 @@ const onTitleAddVar = () => {
|
|
|
3990
2452
|
<CapSpin spinning={loadingTags || spin}>
|
|
3991
2453
|
{getMainContent()}
|
|
3992
2454
|
</CapSpin>
|
|
3993
|
-
|
|
3994
|
-
{showDltContainer && (() => {
|
|
3995
|
-
const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
|
|
3996
|
-
return (
|
|
3997
|
-
<CapSlideBox
|
|
3998
|
-
show={showDltContainer}
|
|
3999
|
-
header={dltHeader}
|
|
4000
|
-
content={dltContent}
|
|
4001
|
-
handleClose={closeDltContainerHandler}
|
|
4002
|
-
size="size-xl"
|
|
4003
|
-
/>
|
|
4004
|
-
);
|
|
4005
|
-
})()}
|
|
4006
|
-
|
|
4007
2455
|
<TestAndPreviewSlidebox
|
|
4008
2456
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
4009
2457
|
onClose={handleCloseTestAndPreview}
|
|
4010
|
-
formData={
|
|
4011
|
-
content={
|
|
2458
|
+
formData={null} // RCS doesn't use formData structure like SMS
|
|
2459
|
+
content={getTemplateContent()}
|
|
4012
2460
|
currentChannel={RCS}
|
|
4013
|
-
orgUnitId={orgUnitId}
|
|
4014
|
-
rcsTestPreviewOptions={{ isLibraryMode: !isFullMode }}
|
|
4015
|
-
smsFallbackContent={
|
|
4016
|
-
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
4017
|
-
? {
|
|
4018
|
-
templateContent:
|
|
4019
|
-
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
4020
|
-
templateName: smsFallbackData.templateName || '',
|
|
4021
|
-
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: !isFullMode
|
|
4022
|
-
? mergeRcsSmsFallbackVarMapLayers(
|
|
4023
|
-
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
4024
|
-
smsFallbackData,
|
|
4025
|
-
)
|
|
4026
|
-
: mergeRcsSmsFallbackVarMapLayers({}, smsFallbackData),
|
|
4027
|
-
}
|
|
4028
|
-
: null
|
|
4029
|
-
}
|
|
4030
|
-
smsRegister={smsRegister}
|
|
4031
2461
|
/>
|
|
4032
2462
|
</>
|
|
4033
2463
|
);
|
|
4034
2464
|
};
|
|
4035
2465
|
|
|
4036
|
-
|
|
4037
2466
|
const mapStateToProps = createStructuredSelector({
|
|
4038
2467
|
rcsData: makeSelectRcs(),
|
|
4039
2468
|
accountData: makeSelectAccount(),
|
|
4040
2469
|
metaEntities: makeSelectMetaEntities(),
|
|
4041
2470
|
loadingTags: isLoadingMetaEntities(),
|
|
4042
2471
|
injectedTags: setInjectedTags(),
|
|
4043
|
-
currentOrgDetails: selectCurrentOrgDetails(),
|
|
4044
2472
|
});
|
|
4045
2473
|
|
|
4046
2474
|
const mapDispatchToProps = (dispatch) => ({
|