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