@capillarytech/creatives-library 8.0.316 → 8.0.317-alpha.0
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 +15 -0
- package/package.json +1 -1
- package/services/api.js +6 -0
- package/services/tests/api.test.js +7 -0
- package/utils/common.js +6 -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/CommunicationFlow/CommunicationFlow.js +291 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
- package/v2Containers/CommunicationFlow/constants.js +200 -0
- package/v2Containers/CommunicationFlow/index.js +102 -0
- package/v2Containers/CommunicationFlow/messages.js +346 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -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 +12 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +289 -93
- 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('');
|
|
@@ -304,14 +261,23 @@ export const Rcs = (props) => {
|
|
|
304
261
|
const accountObj = accountData.selectedRcsAccount || {};
|
|
305
262
|
if (!isEmpty(accountObj)) {
|
|
306
263
|
const {
|
|
264
|
+
id: wecrmId,
|
|
307
265
|
sourceAccountIdentifier = '',
|
|
308
266
|
configs = {},
|
|
309
267
|
} = accountObj;
|
|
310
|
-
|
|
311
268
|
setAccountId(sourceAccountIdentifier);
|
|
269
|
+
setWecrmAccountId(
|
|
270
|
+
wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
|
|
271
|
+
);
|
|
312
272
|
setAccessToken(configs.accessToken || '');
|
|
313
273
|
setHostName(accountObj.hostName || '');
|
|
314
274
|
setAccountName(accountObj.name || '');
|
|
275
|
+
} else {
|
|
276
|
+
setAccountId('');
|
|
277
|
+
setWecrmAccountId('');
|
|
278
|
+
setAccessToken('');
|
|
279
|
+
setHostName('');
|
|
280
|
+
setAccountName('');
|
|
315
281
|
}
|
|
316
282
|
}, [accountData.selectedRcsAccount]);
|
|
317
283
|
|
|
@@ -367,7 +333,9 @@ export const Rcs = (props) => {
|
|
|
367
333
|
if (isFullMode) return;
|
|
368
334
|
if (loadingTags || !tags || tags.length === 0) return;
|
|
369
335
|
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
370
|
-
const
|
|
336
|
+
const slotOffset =
|
|
337
|
+
type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
|
|
338
|
+
const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
|
|
371
339
|
if (!resolved) {
|
|
372
340
|
if (type === TITLE_TEXT) setTemplateTitleError(false);
|
|
373
341
|
if (type === MESSAGE_TEXT) setTemplateDescError(false);
|
|
@@ -412,13 +380,41 @@ export const Rcs = (props) => {
|
|
|
412
380
|
|
|
413
381
|
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
414
382
|
|
|
415
|
-
const
|
|
383
|
+
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
384
|
+
|
|
385
|
+
/** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
|
|
386
|
+
const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
|
|
387
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
388
|
+
const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
389
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
|
|
390
|
+
let varOrdinal = 0;
|
|
391
|
+
for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
|
|
392
|
+
const segmentToken = templateSegments[segmentIndexInField];
|
|
393
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
394
|
+
if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
|
|
395
|
+
return offset + varOrdinal;
|
|
396
|
+
}
|
|
397
|
+
varOrdinal += 1;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
|
|
416
404
|
if (!str) return '';
|
|
417
|
-
const arr =
|
|
405
|
+
const arr = splitTemplateVarStringRcs(str);
|
|
406
|
+
let varOrdinal = 0;
|
|
418
407
|
return arr.map((elem) => {
|
|
419
408
|
if (rcsVarTestRegex.test(elem)) {
|
|
420
409
|
const key = getVarNameFromToken(elem);
|
|
421
|
-
const
|
|
410
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
411
|
+
varOrdinal += 1;
|
|
412
|
+
const v = resolveCardVarMappedSlotValue(
|
|
413
|
+
cardVarMapped,
|
|
414
|
+
key,
|
|
415
|
+
globalSlot,
|
|
416
|
+
isEditLike,
|
|
417
|
+
);
|
|
422
418
|
if (isNil(v) || String(v)?.trim?.() === '') return elem;
|
|
423
419
|
return String(v);
|
|
424
420
|
}
|
|
@@ -426,107 +422,154 @@ export const Rcs = (props) => {
|
|
|
426
422
|
}).join('');
|
|
427
423
|
};
|
|
428
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
|
|
427
|
+
* (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
|
|
428
|
+
* TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
|
|
429
|
+
*/
|
|
430
|
+
const getTemplateContent = useCallback(() => {
|
|
431
|
+
const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
|
|
432
|
+
const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
|
|
433
|
+
const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
|
|
429
434
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
435
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
436
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
437
|
+
? 0
|
|
438
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
439
|
+
const resolvedTitle = isMediaTypeText
|
|
440
|
+
? ''
|
|
441
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
442
|
+
const resolvedDesc = isSlotMappingMode
|
|
443
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
444
|
+
: templateDesc;
|
|
445
|
+
|
|
446
|
+
const mediaPreview = {};
|
|
447
|
+
if (isMediaTypeImage && rcsImageSrc) {
|
|
448
|
+
mediaPreview.rcsImageSrc = rcsImageSrc;
|
|
449
|
+
}
|
|
450
|
+
if (isMediaTypeVideo && !isMediaTypeText) {
|
|
451
|
+
if (rcsThumbnailSrc) {
|
|
452
|
+
mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
|
|
453
|
+
} else if (rcsVideoSrc?.videoSrc) {
|
|
454
|
+
mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
|
|
455
|
+
}
|
|
456
|
+
if (rcsThumbnailSrc) {
|
|
457
|
+
mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const contentObj = {
|
|
462
|
+
templateHeader: resolvedTitle,
|
|
463
|
+
templateMessage: resolvedDesc,
|
|
464
|
+
...mediaPreview,
|
|
465
|
+
...(suggestions.length > 0 && {
|
|
466
|
+
suggestions: suggestions,
|
|
467
|
+
}),
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
return contentObj;
|
|
471
|
+
}, [
|
|
472
|
+
templateMediaType,
|
|
473
|
+
templateTitle,
|
|
474
|
+
templateDesc,
|
|
475
|
+
rcsImageSrc,
|
|
476
|
+
rcsVideoSrc,
|
|
477
|
+
rcsThumbnailSrc,
|
|
478
|
+
suggestions,
|
|
479
|
+
selectedDimension,
|
|
480
|
+
isFullMode,
|
|
481
|
+
isEditFlow,
|
|
482
|
+
cardVarMapped,
|
|
483
|
+
]);
|
|
450
484
|
|
|
485
|
+
const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
|
|
451
486
|
|
|
452
|
-
const RcsLabel = styled.div`
|
|
453
|
-
display: flex;
|
|
454
|
-
margin-top: 20px;
|
|
455
|
-
`;
|
|
456
487
|
const paramObj = params || {};
|
|
457
488
|
useEffect(() => {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}, [paramObj.id]);
|
|
489
|
+
const { id } = paramObj;
|
|
490
|
+
if (id && isFullMode) {
|
|
491
|
+
setSpin(true);
|
|
492
|
+
actions.getTemplateDetails(id, setSpin);
|
|
493
|
+
}
|
|
494
|
+
return () => {
|
|
495
|
+
actions.clearEditResponse();
|
|
496
|
+
};
|
|
497
|
+
}, [paramObj.id, isFullMode]);
|
|
467
498
|
|
|
468
499
|
useEffect(() => {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
500
|
+
if (!(isEditFlow || !isFullMode)) return;
|
|
501
|
+
|
|
502
|
+
const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
|
|
503
|
+
|
|
504
|
+
const initField = (targetString, setVarMap, slotOffset) => {
|
|
505
|
+
const arr = splitTemplateVarStringRcs(targetString);
|
|
506
|
+
if (!arr?.length) {
|
|
507
|
+
setVarMap({});
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const nextVarMap = {};
|
|
511
|
+
let varOrdinal = 0;
|
|
512
|
+
arr.forEach((elem, idx) => {
|
|
513
|
+
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
514
|
+
if (rcsVarTestRegex.test(elem)) {
|
|
515
|
+
const id = `${elem}_${idx}`;
|
|
516
|
+
const varName = getVarNameFromToken(elem);
|
|
517
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
518
|
+
varOrdinal += 1;
|
|
519
|
+
const mappedValue = resolveCardVarMappedSlotValue(
|
|
520
|
+
cardVarMapped,
|
|
521
|
+
varName,
|
|
522
|
+
globalSlot,
|
|
523
|
+
isEditLike,
|
|
524
|
+
);
|
|
525
|
+
nextVarMap[id] = mappedValue;
|
|
477
526
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
// RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
|
|
482
|
-
if (rcsVarTestRegex.test(elem)) {
|
|
483
|
-
const id = `${elem}_${idx}`;
|
|
484
|
-
const varName = getVarNameFromToken(elem);
|
|
485
|
-
const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
|
|
486
|
-
nextVarMap[id] = mappedValue;
|
|
487
|
-
if (mappedValue !== '') {
|
|
488
|
-
nextUpdated[idx] = mappedValue;
|
|
489
|
-
} else {
|
|
490
|
-
nextUpdated[idx] = elem;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
setVarMap(nextVarMap);
|
|
495
|
-
setUpdated(nextUpdated);
|
|
496
|
-
};
|
|
527
|
+
});
|
|
528
|
+
setVarMap(nextVarMap);
|
|
529
|
+
};
|
|
497
530
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
531
|
+
initField(templateTitle, setTitleVarMappedData, 0);
|
|
532
|
+
initField(templateDesc, setDescVarMappedData, titleTokenCount);
|
|
533
|
+
}, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
|
|
534
|
+
|
|
535
|
+
useEffect(() => {
|
|
536
|
+
if (!isEditFlow && isFullMode) {
|
|
504
537
|
setRcsVideoSrc({});
|
|
505
538
|
updateRcsImageSrc('');
|
|
506
539
|
setUpdateRcsImageSrc('');
|
|
507
540
|
updateRcsThumbnailSrc('');
|
|
508
541
|
setAssetList({});
|
|
509
|
-
|
|
510
|
-
|
|
542
|
+
}
|
|
543
|
+
}, [templateMediaType]);
|
|
511
544
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
545
|
+
/** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
|
|
546
|
+
const templateStatusHelper = (cardContentFirst) => {
|
|
547
|
+
const raw =
|
|
548
|
+
cardContentFirst?.Status
|
|
549
|
+
?? cardContentFirst?.status
|
|
550
|
+
?? cardContentFirst?.approvalStatus
|
|
551
|
+
?? '';
|
|
552
|
+
const status = typeof raw === 'string' ? raw.trim() : String(raw);
|
|
553
|
+
const n = status.toLowerCase();
|
|
554
|
+
switch (n) {
|
|
555
|
+
case 'approved':
|
|
516
556
|
setTemplateStatus(RCS_STATUSES.approved);
|
|
517
557
|
break;
|
|
518
|
-
case
|
|
558
|
+
case 'pending':
|
|
519
559
|
setTemplateStatus(RCS_STATUSES.pending);
|
|
520
560
|
break;
|
|
521
|
-
case
|
|
561
|
+
case 'awaitingapproval':
|
|
522
562
|
setTemplateStatus(RCS_STATUSES.awaitingApproval);
|
|
523
563
|
break;
|
|
524
|
-
case
|
|
564
|
+
case 'unavailable':
|
|
525
565
|
setTemplateStatus(RCS_STATUSES.unavailable);
|
|
526
566
|
break;
|
|
527
|
-
case
|
|
567
|
+
case 'rejected':
|
|
528
568
|
setTemplateStatus(RCS_STATUSES.rejected);
|
|
529
569
|
break;
|
|
570
|
+
case 'created':
|
|
571
|
+
setTemplateStatus(RCS_STATUSES.created);
|
|
572
|
+
break;
|
|
530
573
|
default:
|
|
531
574
|
setTemplateStatus(status);
|
|
532
575
|
break;
|
|
@@ -537,7 +580,6 @@ export const Rcs = (props) => {
|
|
|
537
580
|
if (mediaType) {
|
|
538
581
|
setTemplateMediaType(mediaType);
|
|
539
582
|
}
|
|
540
|
-
const tempOrientation = cardSettings.cardOrientation;
|
|
541
583
|
const tempAlignment = cardSettings.mediaAlignment;
|
|
542
584
|
const tempHeight = mediaData.height;
|
|
543
585
|
|
|
@@ -580,44 +622,197 @@ export const Rcs = (props) => {
|
|
|
580
622
|
};
|
|
581
623
|
|
|
582
624
|
useEffect(() => {
|
|
583
|
-
const details =
|
|
625
|
+
const details = rcsHydrationDetails;
|
|
584
626
|
if (details && Object.keys(details).length > 0) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
627
|
+
// Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
|
|
628
|
+
const cardFromVersions = get(
|
|
629
|
+
details,
|
|
630
|
+
'versions.base.content.RCS.rcsContent.cardContent[0]',
|
|
631
|
+
);
|
|
632
|
+
const cardFromTop = get(details, 'rcsContent.cardContent[0]');
|
|
633
|
+
const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
|
|
634
|
+
const cardVarMappedFromCardContent =
|
|
635
|
+
card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
|
|
636
|
+
? card0.cardVarMapped
|
|
637
|
+
: {};
|
|
638
|
+
const cardVarMappedFromRootMirror =
|
|
639
|
+
details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
|
|
640
|
+
? details.rcsCardVarMapped
|
|
641
|
+
: {};
|
|
642
|
+
// Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
|
|
643
|
+
// nested versions.cardContent[0].cardVarMapped is dropped on reload.
|
|
644
|
+
const mergedCardVarMappedFromPayload = {
|
|
645
|
+
...cardVarMappedFromRootMirror,
|
|
646
|
+
...cardVarMappedFromCardContent,
|
|
647
|
+
};
|
|
648
|
+
const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
|
|
649
|
+
const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
|
|
650
|
+
const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
|
|
651
|
+
Object.keys(mergedCardVarMappedFromPayload)
|
|
652
|
+
.sort()
|
|
653
|
+
.reduce((accumulator, mapKey) => {
|
|
654
|
+
accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
|
|
655
|
+
return accumulator;
|
|
656
|
+
}, {}),
|
|
657
|
+
)}`;
|
|
658
|
+
if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
|
|
659
|
+
lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
|
|
660
|
+
setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
|
|
588
661
|
}
|
|
589
|
-
const
|
|
662
|
+
const tokenListForMap = [
|
|
663
|
+
...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
|
|
664
|
+
...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
|
|
665
|
+
];
|
|
666
|
+
const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
|
|
667
|
+
// Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
|
|
668
|
+
// getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
|
|
669
|
+
const cardVarMappedBeforeCoalesce = isFullMode
|
|
670
|
+
? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
|
|
671
|
+
: { ...mergedCardVarMappedFromPayload };
|
|
672
|
+
const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
|
|
673
|
+
cardVarMappedBeforeCoalesce,
|
|
674
|
+
loadedTitleForMap,
|
|
675
|
+
loadedDescForMap,
|
|
676
|
+
rcsVarRegex,
|
|
677
|
+
);
|
|
678
|
+
const cardVarMappedAfterNumericSlotSync = !isFullMode
|
|
679
|
+
? syncCardVarMappedSemanticsFromSlots(
|
|
680
|
+
cardVarMappedAfterCoalesce,
|
|
681
|
+
loadedTitleForMap,
|
|
682
|
+
loadedDescForMap,
|
|
683
|
+
rcsVarRegex,
|
|
684
|
+
)
|
|
685
|
+
: cardVarMappedAfterCoalesce;
|
|
686
|
+
const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
|
|
687
|
+
// Pre-populate variable/tag mappings while opening an existing template in edit flows
|
|
688
|
+
setCardVarMapped((previousVarMapState) => {
|
|
689
|
+
const previousVarMap = previousVarMapState ?? {};
|
|
690
|
+
if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
|
|
691
|
+
const previousVarMapKeys = Object.keys(previousVarMap);
|
|
692
|
+
const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
|
|
693
|
+
if (previousVarMapKeys.length === nextVarMapKeys.length) {
|
|
694
|
+
const allSlotValuesMatchPrevious = previousVarMapKeys.every(
|
|
695
|
+
(key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
|
|
696
|
+
);
|
|
697
|
+
if (allSlotValuesMatchPrevious) return previousVarMapState;
|
|
698
|
+
}
|
|
699
|
+
return hydratedCardVarMappedResult;
|
|
700
|
+
});
|
|
701
|
+
const mediaType =
|
|
702
|
+
card0.mediaType
|
|
703
|
+
|| get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
|
|
590
704
|
if (mediaType === RCS_MEDIA_TYPES.NONE) {
|
|
591
705
|
setTemplateType(contentType.text_message);
|
|
592
706
|
} else {
|
|
593
707
|
setTemplateType(contentType.rich_card);
|
|
594
708
|
}
|
|
595
709
|
setEditFlow(true);
|
|
596
|
-
setTemplateName(details
|
|
597
|
-
const loadedTitle =
|
|
598
|
-
const loadedDesc =
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
710
|
+
setTemplateName(details?.name || details?.creativeName || '');
|
|
711
|
+
const loadedTitle = loadedTitleForMap;
|
|
712
|
+
const loadedDesc = loadedDescForMap;
|
|
713
|
+
const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
|
|
714
|
+
loadedTitle,
|
|
715
|
+
loadedDesc,
|
|
716
|
+
isFullMode,
|
|
717
|
+
cardVarMappedAfterHydration: hydratedCardVarMappedResult,
|
|
718
|
+
rcsVarRegex,
|
|
719
|
+
});
|
|
602
720
|
setTemplateTitle(normalizedTitle);
|
|
603
721
|
setTemplateDesc(normalizedDesc);
|
|
604
|
-
setSuggestions(
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
722
|
+
setSuggestions(
|
|
723
|
+
Array.isArray(card0.suggestions)
|
|
724
|
+
? card0.suggestions
|
|
725
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
|
|
726
|
+
);
|
|
727
|
+
const cardForStatus = {
|
|
728
|
+
...card0,
|
|
729
|
+
Status:
|
|
730
|
+
card0.Status
|
|
731
|
+
?? card0.status
|
|
732
|
+
?? card0.approvalStatus
|
|
733
|
+
?? get(details, 'templateStatus')
|
|
734
|
+
?? get(details, 'approvalStatus')
|
|
735
|
+
?? get(details, 'creativeStatus')
|
|
736
|
+
?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
|
|
737
|
+
?? '',
|
|
738
|
+
};
|
|
739
|
+
templateStatusHelper(cardForStatus);
|
|
740
|
+
const mediaData =
|
|
741
|
+
card0.media != null && card0.media !== ''
|
|
742
|
+
? card0.media
|
|
743
|
+
: get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
|
|
744
|
+
const cardSettings =
|
|
745
|
+
get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
|
|
746
|
+
|| get(details, 'rcsContent.cardSettings', '');
|
|
608
747
|
setMediaData(mediaData, mediaType, cardSettings);
|
|
748
|
+
|
|
749
|
+
const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
|
|
750
|
+
const base = get(smsFallbackContent, 'versions.base', {});
|
|
751
|
+
const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
|
|
752
|
+
const smsEditor = base['sms-editor'];
|
|
753
|
+
const fromNested = Array.isArray(updatedEditor)
|
|
754
|
+
? updatedEditor.join('')
|
|
755
|
+
: (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
|
|
756
|
+
const fallbackMessage = smsFallbackContent.smsContent
|
|
757
|
+
|| smsFallbackContent.smsTemplateContent
|
|
758
|
+
|| smsFallbackContent.message
|
|
759
|
+
|| fromNested
|
|
760
|
+
|| '';
|
|
761
|
+
const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
|
|
762
|
+
const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
|
|
763
|
+
const hasFallbackPayload =
|
|
764
|
+
smsFallbackContent
|
|
765
|
+
&& Object.keys(smsFallbackContent).length > 0
|
|
766
|
+
&& (
|
|
767
|
+
!!smsFallbackContent.smsTemplateName
|
|
768
|
+
|| !!fallbackMessage
|
|
769
|
+
|| hasVarMapped
|
|
770
|
+
);
|
|
771
|
+
if (hasFallbackPayload) {
|
|
772
|
+
if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
|
|
773
|
+
console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
|
|
774
|
+
}
|
|
775
|
+
const unicodeFromApi =
|
|
776
|
+
typeof smsFallbackContent.unicodeValidity === 'boolean'
|
|
777
|
+
? smsFallbackContent.unicodeValidity
|
|
778
|
+
: (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
|
|
779
|
+
const nextSmsState = {
|
|
780
|
+
templateName: smsFallbackContent.smsTemplateName || '',
|
|
781
|
+
content: fallbackMessage,
|
|
782
|
+
templateContent: fallbackMessage,
|
|
783
|
+
unicodeValidity: unicodeFromApi,
|
|
784
|
+
...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
|
|
785
|
+
};
|
|
786
|
+
const hydrationKey = JSON.stringify({
|
|
787
|
+
creativeKey: details._id || details.name || details.creativeName || '',
|
|
788
|
+
templateName: nextSmsState.templateName,
|
|
789
|
+
content: nextSmsState.content,
|
|
790
|
+
unicodeValidity: nextSmsState.unicodeValidity,
|
|
791
|
+
varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
|
|
792
|
+
});
|
|
793
|
+
if (
|
|
794
|
+
isFullMode
|
|
795
|
+
|| lastSmsFallbackHydrationKeyRef.current !== hydrationKey
|
|
796
|
+
) {
|
|
797
|
+
lastSmsFallbackHydrationKeyRef.current = hydrationKey;
|
|
798
|
+
setSmsFallbackData(nextSmsState);
|
|
799
|
+
}
|
|
800
|
+
} else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
|
|
801
|
+
lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
|
|
802
|
+
setSmsFallbackData(null);
|
|
803
|
+
}
|
|
609
804
|
}
|
|
610
|
-
}, [
|
|
611
|
-
|
|
805
|
+
}, [rcsHydrationDetails, isFullMode]);
|
|
612
806
|
|
|
613
807
|
useEffect(() => {
|
|
614
808
|
if (templateType === contentType.text_message) {
|
|
615
809
|
setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
|
|
616
|
-
|
|
617
|
-
|
|
810
|
+
// Full-mode create only: switching to plain text clears draft title/media. Never clear when
|
|
811
|
+
// hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
|
|
618
812
|
if (!isEditFlow && isFullMode) {
|
|
813
|
+
setTemplateTitle('');
|
|
814
|
+
setTemplateTitleError('');
|
|
619
815
|
setUpdateRcsImageSrc('');
|
|
620
|
-
setUpdateRcsVideoSrc({});
|
|
621
816
|
setRcsVideoSrc({});
|
|
622
817
|
setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
|
|
623
818
|
}
|
|
@@ -644,7 +839,8 @@ export const Rcs = (props) => {
|
|
|
644
839
|
if (!showDltContainer) {
|
|
645
840
|
const { type, module } = location.query || {};
|
|
646
841
|
const isEmbedded = type === EMBEDDED;
|
|
647
|
-
|
|
842
|
+
// Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
|
|
843
|
+
const context = isEmbedded ? module : 'outbound';
|
|
648
844
|
const embedded = isEmbedded ? type : FULL;
|
|
649
845
|
const query = {
|
|
650
846
|
layout: SMS,
|
|
@@ -655,9 +851,9 @@ export const Rcs = (props) => {
|
|
|
655
851
|
if (getDefaultTags) {
|
|
656
852
|
query.context = getDefaultTags;
|
|
657
853
|
}
|
|
658
|
-
|
|
854
|
+
fetchTagSchemaIfNewQuery(query);
|
|
659
855
|
}
|
|
660
|
-
}, [showDltContainer]);
|
|
856
|
+
}, [showDltContainer, fetchTagSchemaIfNewQuery]);
|
|
661
857
|
|
|
662
858
|
useEffect(() => {
|
|
663
859
|
let tag = get(metaEntities, `tags.standard`, []);
|
|
@@ -680,39 +876,72 @@ export const Rcs = (props) => {
|
|
|
680
876
|
context,
|
|
681
877
|
embedded,
|
|
682
878
|
};
|
|
683
|
-
|
|
879
|
+
if (getDefaultTags) {
|
|
880
|
+
query.context = getDefaultTags;
|
|
881
|
+
}
|
|
882
|
+
fetchTagSchemaIfNewQuery(query);
|
|
684
883
|
};
|
|
685
884
|
|
|
686
|
-
const
|
|
885
|
+
const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
|
|
886
|
+
if (!templateStr || !numericVarName || !tagName) return templateStr;
|
|
887
|
+
const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
|
|
888
|
+
return templateStr.replace(re, `{{${tagName}}}`);
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
const onTagSelect = (data, areaId, field) => {
|
|
687
892
|
if (!areaId) return;
|
|
688
893
|
const sep = areaId.lastIndexOf('_');
|
|
689
894
|
if (sep === -1) return;
|
|
690
|
-
const
|
|
691
|
-
if (isNaN(
|
|
895
|
+
const slotSuffix = areaId.slice(sep + 1);
|
|
896
|
+
if (slotSuffix === '' || isNaN(Number(slotSuffix))) return;
|
|
692
897
|
const token = areaId.slice(0, sep);
|
|
693
898
|
const variableName = getVarNameFromToken(token);
|
|
694
899
|
if (!variableName) return;
|
|
900
|
+
const isNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(variableName));
|
|
901
|
+
const fieldStr = field === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
|
|
902
|
+
const fieldType = field === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
|
|
903
|
+
const globalSlotForArea = getGlobalSlotIndexForRcsFieldId(areaId, fieldStr, fieldType);
|
|
904
|
+
|
|
695
905
|
setCardVarMapped((prev) => {
|
|
696
906
|
const base = (prev?.[variableName] ?? '').toString();
|
|
697
907
|
const nextVal = `${base}{{${data}}}`;
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
[variableName]
|
|
701
|
-
|
|
908
|
+
const next = { ...(prev || {}) };
|
|
909
|
+
if (isNumericSlot) {
|
|
910
|
+
delete next[variableName];
|
|
911
|
+
next[data] = nextVal;
|
|
912
|
+
} else {
|
|
913
|
+
next[variableName] = nextVal;
|
|
914
|
+
// resolveCardVarMappedSlotValue prefers numeric slot keys ("1","2",…) over semantic names;
|
|
915
|
+
// hydration may set "1": ''. Use global slot index — suffix is segment index (includes static text), not var ordinal.
|
|
916
|
+
if (globalSlotForArea !== null && globalSlotForArea !== undefined) {
|
|
917
|
+
next[String(globalSlotForArea + 1)] = nextVal;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return next;
|
|
702
921
|
});
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
|
|
706
922
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
923
|
+
if (isNumericSlot && (field === RCS_TAG_AREA_FIELD_TITLE || field === RCS_TAG_AREA_FIELD_DESC)) {
|
|
924
|
+
if (field === RCS_TAG_AREA_FIELD_TITLE) {
|
|
925
|
+
setTemplateTitle((prev) => {
|
|
926
|
+
const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
|
|
927
|
+
if (nextStr === prev) return prev;
|
|
928
|
+
setTemplateTitleError(variableErrorHandling(nextStr));
|
|
929
|
+
return nextStr;
|
|
930
|
+
});
|
|
931
|
+
} else {
|
|
932
|
+
setTemplateDesc((prev) => {
|
|
933
|
+
const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
|
|
934
|
+
if (nextStr === prev) return prev;
|
|
935
|
+
setTemplateDescError(variableErrorHandling(nextStr));
|
|
936
|
+
return nextStr;
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
}
|
|
714
940
|
};
|
|
715
941
|
|
|
942
|
+
const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
|
|
943
|
+
|
|
944
|
+
const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
|
|
716
945
|
|
|
717
946
|
//removing optout tag for rcs
|
|
718
947
|
const getRcsTags = () => {
|
|
@@ -725,15 +954,14 @@ export const Rcs = (props) => {
|
|
|
725
954
|
};
|
|
726
955
|
// tag Code end
|
|
727
956
|
|
|
728
|
-
const renderLabel = (value,
|
|
729
|
-
const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
|
|
957
|
+
const renderLabel = (value, desc) => {
|
|
730
958
|
return (
|
|
731
959
|
<>
|
|
732
|
-
<
|
|
960
|
+
<div className="rcs-form-section-heading">
|
|
733
961
|
<CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
|
|
734
|
-
</
|
|
962
|
+
</div>
|
|
735
963
|
{desc && (
|
|
736
|
-
<CapLabel type="label3"
|
|
964
|
+
<CapLabel type="label3" className="rcs-form-field-caption">
|
|
737
965
|
{formatMessage(messages[desc])}
|
|
738
966
|
</CapLabel>
|
|
739
967
|
)}
|
|
@@ -819,47 +1047,6 @@ export const Rcs = (props) => {
|
|
|
819
1047
|
setTemplateDescError(error);
|
|
820
1048
|
};
|
|
821
1049
|
|
|
822
|
-
|
|
823
|
-
const templateDescErrorHandler = (value) => {
|
|
824
|
-
let errorMessage = false;
|
|
825
|
-
const { isBraceError } = validateTags({
|
|
826
|
-
content: value,
|
|
827
|
-
tagsParam: tags,
|
|
828
|
-
location,
|
|
829
|
-
tagModule: getDefaultTags,
|
|
830
|
-
isFullMode,
|
|
831
|
-
}) || {};
|
|
832
|
-
|
|
833
|
-
const maxLength = templateType === contentType.text_message
|
|
834
|
-
? RCS_TEXT_MESSAGE_MAX_LENGTH
|
|
835
|
-
: RCS_RICH_CARD_MAX_LENGTH;
|
|
836
|
-
|
|
837
|
-
if (value === '' && isMediaTypeText) {
|
|
838
|
-
errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
|
|
839
|
-
} else if (value?.length > maxLength) {
|
|
840
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
if (isBraceError) {
|
|
844
|
-
errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
845
|
-
}
|
|
846
|
-
return errorMessage;
|
|
847
|
-
};
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const onFallbackMessageChange = ({ target: { value } }) => {
|
|
851
|
-
const error = fallbackMessageErrorHandler(value);
|
|
852
|
-
setFallbackMessage(value);
|
|
853
|
-
setFallbackMessageError(error);
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
const fallbackMessageErrorHandler = (value) => {
|
|
857
|
-
if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
|
|
858
|
-
return formatMessage(messages.fallbackMsgLenError);
|
|
859
|
-
}
|
|
860
|
-
return false;
|
|
861
|
-
};
|
|
862
|
-
|
|
863
1050
|
// Check for forbidden characters: square brackets [] and single curly braces {}
|
|
864
1051
|
const forbiddenCharactersValidation = (value) => {
|
|
865
1052
|
if (!value) return false;
|
|
@@ -910,39 +1097,12 @@ export const Rcs = (props) => {
|
|
|
910
1097
|
}
|
|
911
1098
|
return false;
|
|
912
1099
|
};
|
|
913
|
-
|
|
914
|
-
const templateHeaderErrorHandler = (value) => {
|
|
915
|
-
let errorMessage = false;
|
|
916
|
-
if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
|
|
917
|
-
errorMessage = formatMessage(messages.templateHeaderLengthError);
|
|
918
|
-
} else {
|
|
919
|
-
errorMessage = variableErrorHandling(value);
|
|
920
|
-
}
|
|
921
|
-
return errorMessage;
|
|
922
|
-
};
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const templateMessageErrorHandler = (value) => {
|
|
926
|
-
let errorMessage = false;
|
|
927
|
-
if (value === '') {
|
|
928
|
-
errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
|
|
929
|
-
} else if (
|
|
930
|
-
value?.length
|
|
931
|
-
> TEMPLATE_MESSAGE_MAX_LENGTH
|
|
932
|
-
) {
|
|
933
|
-
errorMessage = formatMessage(messages.templateMessageLengthError);
|
|
934
|
-
} else {
|
|
935
|
-
errorMessage = variableErrorHandling(value);
|
|
936
|
-
}
|
|
937
|
-
return errorMessage;
|
|
938
|
-
};
|
|
939
|
-
|
|
940
1100
|
|
|
941
1101
|
const onMessageAddVar = () => {
|
|
942
|
-
onAddVar(
|
|
1102
|
+
onAddVar(templateDesc);
|
|
943
1103
|
};
|
|
944
1104
|
|
|
945
|
-
const onAddVar = (
|
|
1105
|
+
const onAddVar = (messageContent) => {
|
|
946
1106
|
// Always append the next variable at the end, like WhatsApp
|
|
947
1107
|
const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
|
|
948
1108
|
const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
|
|
@@ -982,66 +1142,6 @@ const onTitleAddVar = () => {
|
|
|
982
1142
|
setTemplateTitleError(error);
|
|
983
1143
|
};
|
|
984
1144
|
|
|
985
|
-
|
|
986
|
-
const splitTemplateVarString = (str) => {
|
|
987
|
-
if (!str) return [];
|
|
988
|
-
const validVarArr = str.match(rcsVarRegex) || [];
|
|
989
|
-
const templateVarArray = [];
|
|
990
|
-
let content = str;
|
|
991
|
-
while (content?.length !== 0) {
|
|
992
|
-
const index = content.indexOf(validVarArr?.[0]);
|
|
993
|
-
if (index !== -1) {
|
|
994
|
-
templateVarArray.push(content.substring(0, index));
|
|
995
|
-
templateVarArray.push(validVarArr?.[0]);
|
|
996
|
-
content = content.substring(index + validVarArr?.[0]?.length, content?.length);
|
|
997
|
-
validVarArr?.shift();
|
|
998
|
-
} else {
|
|
999
|
-
templateVarArray.push(content);
|
|
1000
|
-
break;
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
return templateVarArray.filter(Boolean);
|
|
1004
|
-
};
|
|
1005
|
-
|
|
1006
|
-
const textAreaValue = (idValue, type) => {
|
|
1007
|
-
if (idValue >= 0) {
|
|
1008
|
-
const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1009
|
-
const templateArr = splitTemplateVarString(templateStr);
|
|
1010
|
-
const token = templateArr?.[idValue] || "";
|
|
1011
|
-
if (token && rcsVarTestRegex.test(token)) {
|
|
1012
|
-
const varName = getVarNameFromToken(token);
|
|
1013
|
-
return (cardVarMapped?.[varName] ?? '').toString();
|
|
1014
|
-
}
|
|
1015
|
-
return "";
|
|
1016
|
-
}
|
|
1017
|
-
return "";
|
|
1018
|
-
};
|
|
1019
|
-
|
|
1020
|
-
const textAreaValueChange = (e, type) => {
|
|
1021
|
-
const value = e?.target?.value ?? '';
|
|
1022
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1023
|
-
if (!id) return;
|
|
1024
|
-
const sep = id.lastIndexOf('_');
|
|
1025
|
-
if (sep === -1) return;
|
|
1026
|
-
const isInvalidValue = value?.trim() === "";
|
|
1027
|
-
const token = id.slice(0, sep);
|
|
1028
|
-
const variableName = getVarNameFromToken(token);
|
|
1029
|
-
|
|
1030
|
-
if (variableName) {
|
|
1031
|
-
setCardVarMapped((prev) => ({
|
|
1032
|
-
...prev,
|
|
1033
|
-
[variableName]: isInvalidValue ? "" : value,
|
|
1034
|
-
}));
|
|
1035
|
-
}
|
|
1036
|
-
};
|
|
1037
|
-
|
|
1038
|
-
const setTextAreaId = (e, type) => {
|
|
1039
|
-
const id = e?.target?.id || e?.currentTarget?.id || '';
|
|
1040
|
-
if (!id) return;
|
|
1041
|
-
if (type === TITLE_TEXT) setTitleTextAreaId(id);
|
|
1042
|
-
else setDescTextAreaId(id);
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
1145
|
const renderButtonComponent = () => {
|
|
1046
1146
|
return (
|
|
1047
1147
|
<>
|
|
@@ -1072,39 +1172,56 @@ const splitTemplateVarString = (str) => {
|
|
|
1072
1172
|
);
|
|
1073
1173
|
};
|
|
1074
1174
|
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1175
|
+
const getRcsValueMap = (fieldTemplateString, fieldType) => {
|
|
1176
|
+
if (!fieldTemplateString) return {};
|
|
1177
|
+
const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
|
|
1178
|
+
const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
|
|
1179
|
+
const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
|
|
1180
|
+
const segmentIdToResolvedValue = {};
|
|
1181
|
+
let varOrdinal = 0;
|
|
1182
|
+
templateSegments.forEach((segmentToken, segmentIndexInField) => {
|
|
1183
|
+
if (rcsVarTestRegex.test(segmentToken)) {
|
|
1184
|
+
const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
|
|
1185
|
+
const varName = getVarNameFromToken(segmentToken);
|
|
1186
|
+
const globalSlot = slotOffset + varOrdinal;
|
|
1187
|
+
varOrdinal += 1;
|
|
1188
|
+
segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
|
|
1189
|
+
cardVarMapped,
|
|
1190
|
+
varName,
|
|
1191
|
+
globalSlot,
|
|
1192
|
+
isEditLike,
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
return segmentIdToResolvedValue;
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
const titleVarSegmentValueMapById = useMemo(
|
|
1200
|
+
() => getRcsValueMap(templateTitle, TITLE_TEXT),
|
|
1201
|
+
[templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1202
|
+
);
|
|
1203
|
+
const descriptionVarSegmentValueMapById = useMemo(
|
|
1204
|
+
() => getRcsValueMap(templateDesc, MESSAGE_TEXT),
|
|
1205
|
+
[templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode],
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1208
|
+
const handleRcsVarChange = (id, value, type) => {
|
|
1209
|
+
const sep = id.lastIndexOf('_');
|
|
1210
|
+
if (sep === -1) return;
|
|
1211
|
+
const token = id.slice(0, sep);
|
|
1212
|
+
const variableName = getVarNameFromToken(token);
|
|
1213
|
+
if (variableName === undefined || variableName === null || variableName === '') return;
|
|
1214
|
+
const isInvalidValue = value?.trim() === '';
|
|
1215
|
+
const coercedSlotValue = isInvalidValue ? '' : value;
|
|
1216
|
+
const fieldStr = type === TITLE_TEXT ? templateTitle : templateDesc;
|
|
1217
|
+
const globalSlot = getGlobalSlotIndexForRcsFieldId(id, fieldStr, type);
|
|
1218
|
+
setCardVarMapped((previousVarMap) => {
|
|
1219
|
+
const nextVarMap = { ...previousVarMap, [variableName]: coercedSlotValue };
|
|
1220
|
+
if (globalSlot !== null && globalSlot !== undefined) {
|
|
1221
|
+
nextVarMap[String(globalSlot + 1)] = coercedSlotValue;
|
|
1222
|
+
}
|
|
1223
|
+
return nextVarMap;
|
|
1224
|
+
});
|
|
1108
1225
|
};
|
|
1109
1226
|
|
|
1110
1227
|
const renderTextComponent = () => {
|
|
@@ -1148,10 +1265,19 @@ const splitTemplateVarString = (str) => {
|
|
|
1148
1265
|
</>
|
|
1149
1266
|
}
|
|
1150
1267
|
/>
|
|
1151
|
-
<div className="rcs_text_area_wrapper">
|
|
1152
1268
|
{(isEditFlow || !isFullMode) ? (
|
|
1153
|
-
|
|
1269
|
+
<VarSegmentMessageEditor
|
|
1270
|
+
key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
1271
|
+
templateString={templateTitle}
|
|
1272
|
+
valueMap={titleVarSegmentValueMapById}
|
|
1273
|
+
onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
|
|
1274
|
+
onFocus={(id) => setTitleTextAreaId(id)}
|
|
1275
|
+
varRegex={rcsVarRegex}
|
|
1276
|
+
placeholderPrefix=""
|
|
1277
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1278
|
+
/>
|
|
1154
1279
|
) : (
|
|
1280
|
+
<div className="rcs_text_area_wrapper">
|
|
1155
1281
|
<CapInput
|
|
1156
1282
|
className={`rcs-template-title-input ${
|
|
1157
1283
|
!isTemplateApproved ? "rcs-edit-disabled" : ""
|
|
@@ -1164,8 +1290,8 @@ const splitTemplateVarString = (str) => {
|
|
|
1164
1290
|
errorMessage={templateTitleError}
|
|
1165
1291
|
disabled={isEditFlow || !isFullMode}
|
|
1166
1292
|
/>
|
|
1293
|
+
</div>
|
|
1167
1294
|
)}
|
|
1168
|
-
</div>
|
|
1169
1295
|
{(isEditFlow || !isFullMode) && templateTitleError && (
|
|
1170
1296
|
<CapError className="rcs-template-title-error">
|
|
1171
1297
|
{templateTitleError}
|
|
@@ -1213,7 +1339,18 @@ const splitTemplateVarString = (str) => {
|
|
|
1213
1339
|
<CapRow className="rcs-create-template-message-input">
|
|
1214
1340
|
<div className="rcs_text_area_wrapper">
|
|
1215
1341
|
{(isEditFlow || !isFullMode)
|
|
1216
|
-
?
|
|
1342
|
+
? (
|
|
1343
|
+
<VarSegmentMessageEditor
|
|
1344
|
+
key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
|
|
1345
|
+
templateString={templateDesc}
|
|
1346
|
+
valueMap={descriptionVarSegmentValueMapById}
|
|
1347
|
+
onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
|
|
1348
|
+
onFocus={(id) => setDescTextAreaId(id)}
|
|
1349
|
+
varRegex={rcsVarRegex}
|
|
1350
|
+
placeholderPrefix=""
|
|
1351
|
+
getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
|
|
1352
|
+
/>
|
|
1353
|
+
)
|
|
1217
1354
|
: (
|
|
1218
1355
|
<>
|
|
1219
1356
|
<TextArea
|
|
@@ -1257,7 +1394,9 @@ const splitTemplateVarString = (str) => {
|
|
|
1257
1394
|
{templateDescError}
|
|
1258
1395
|
</CapError>
|
|
1259
1396
|
)}
|
|
1260
|
-
{
|
|
1397
|
+
{(isEditFlow || !isFullMode)
|
|
1398
|
+
? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
|
|
1399
|
+
: (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
|
|
1261
1400
|
{!isFullMode && hasTag() && (
|
|
1262
1401
|
<CapAlert
|
|
1263
1402
|
message={
|
|
@@ -1277,18 +1416,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1277
1416
|
);
|
|
1278
1417
|
};
|
|
1279
1418
|
|
|
1280
|
-
|
|
1281
|
-
const fallbackSmsLength = () => (
|
|
1282
|
-
<CapLabel type="label1" className="fallback-sms-length">
|
|
1283
|
-
{formatMessage(messages.totalCharacters, {
|
|
1284
|
-
smsCount: Math.ceil(
|
|
1285
|
-
fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
1286
|
-
),
|
|
1287
|
-
number: fallbackMessage?.length,
|
|
1288
|
-
})}
|
|
1289
|
-
</CapLabel>
|
|
1290
|
-
);
|
|
1291
|
-
|
|
1292
1419
|
// Get character count for title (rich card only)
|
|
1293
1420
|
const getTitleCharacterCount = () => {
|
|
1294
1421
|
if (templateType === contentType.text_message) return 0;
|
|
@@ -1348,7 +1475,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1348
1475
|
const hasTag = () => {
|
|
1349
1476
|
// Check cardVarMapped values for tags
|
|
1350
1477
|
if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
|
|
1351
|
-
const hasTagInMapped = Object.values(cardVarMapped).some(value =>
|
|
1478
|
+
const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
|
|
1352
1479
|
isTagIncluded(value)
|
|
1353
1480
|
);
|
|
1354
1481
|
if (hasTagInMapped) return true;
|
|
@@ -1366,14 +1493,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1366
1493
|
return false;
|
|
1367
1494
|
};
|
|
1368
1495
|
|
|
1369
|
-
//adding creative dlt fallback sms handlers
|
|
1370
|
-
const addDltMsgHandler = () => {
|
|
1371
|
-
setShowDltContainer(true);
|
|
1372
|
-
setDltMode(RCS_DLT_MODE.TEMPLATES);
|
|
1373
|
-
setDltEditData({});
|
|
1374
|
-
setFallbackMessage('');
|
|
1375
|
-
};
|
|
1376
|
-
|
|
1377
1496
|
const closeDltContainerHandler = () => {
|
|
1378
1497
|
setShowDltContainer(false);
|
|
1379
1498
|
setDltMode('');
|
|
@@ -1396,68 +1515,17 @@ const splitTemplateVarString = (str) => {
|
|
|
1396
1515
|
const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
|
|
1397
1516
|
'',
|
|
1398
1517
|
);
|
|
1518
|
+
const templateNameFromDlt = get(dltEditData, 'name', '')
|
|
1519
|
+
|| get(tempData, 'versions.base.name', '')
|
|
1520
|
+
|| '';
|
|
1399
1521
|
closeDltContainerHandler();
|
|
1400
1522
|
setDltEditData(tempData);
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
closeDltContainerHandler();
|
|
1408
|
-
setDltEditData({});
|
|
1409
|
-
setFallbackMessage('');
|
|
1410
|
-
setFallbackMessageError(false);
|
|
1411
|
-
setShowDltCard(false);
|
|
1412
|
-
};
|
|
1413
|
-
|
|
1414
|
-
const dltFallbackListingPreviewhandler = (data) => {
|
|
1415
|
-
const {
|
|
1416
|
-
'updated-sms-editor': updatedSmsEditor = [],
|
|
1417
|
-
'sms-editor': smsEditor = '',
|
|
1418
|
-
} = data.versions.base || {};
|
|
1419
|
-
setFallbackPreviewmode(true);
|
|
1420
|
-
setDltPreviewData(
|
|
1421
|
-
updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
|
|
1422
|
-
);
|
|
1423
|
-
};
|
|
1424
|
-
|
|
1425
|
-
const getDltContentCardList = (content, channel) => {
|
|
1426
|
-
const extra = [
|
|
1427
|
-
<CapIcon
|
|
1428
|
-
type="edit"
|
|
1429
|
-
style={{ marginRight: '8px' }}
|
|
1430
|
-
onClick={() => rcsDltEditSelectHandler(dltEditData)}
|
|
1431
|
-
/>,
|
|
1432
|
-
<CapDropdown
|
|
1433
|
-
overlay={(
|
|
1434
|
-
<CapMenu>
|
|
1435
|
-
<>
|
|
1436
|
-
<CapMenu.Item
|
|
1437
|
-
className="ant-dropdown-menu-item"
|
|
1438
|
-
onClick={() => setFallbackPreviewmode(true)}
|
|
1439
|
-
>
|
|
1440
|
-
{formatMessage(globalMessages.preview)}
|
|
1441
|
-
</CapMenu.Item>
|
|
1442
|
-
<CapMenu.Item
|
|
1443
|
-
className="ant-dropdown-menu-item"
|
|
1444
|
-
onClick={rcsDltCardDeleteHandler}
|
|
1445
|
-
>
|
|
1446
|
-
{formatMessage(globalMessages.delete)}
|
|
1447
|
-
</CapMenu.Item>
|
|
1448
|
-
</>
|
|
1449
|
-
</CapMenu>
|
|
1450
|
-
)}
|
|
1451
|
-
>
|
|
1452
|
-
<CapIcon type="more" />
|
|
1453
|
-
</CapDropdown>,
|
|
1454
|
-
];
|
|
1455
|
-
return {
|
|
1456
|
-
title: channel,
|
|
1457
|
-
content,
|
|
1458
|
-
cardType: channel,
|
|
1459
|
-
extra,
|
|
1460
|
-
};
|
|
1523
|
+
const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
|
|
1524
|
+
setSmsFallbackData({
|
|
1525
|
+
templateName: templateNameFromDlt,
|
|
1526
|
+
content: fallMsg,
|
|
1527
|
+
...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
|
|
1528
|
+
});
|
|
1461
1529
|
};
|
|
1462
1530
|
|
|
1463
1531
|
const getDltSlideBoxContent = () => {
|
|
@@ -1484,7 +1552,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1484
1552
|
isFullMode={isFullMode}
|
|
1485
1553
|
isDltFromRcs
|
|
1486
1554
|
onSelectTemplate={rcsDltEditSelectHandler}
|
|
1487
|
-
handlePeviewTemplate={dltFallbackListingPreviewhandler}
|
|
1488
1555
|
/>
|
|
1489
1556
|
);
|
|
1490
1557
|
} else if (dltMode === RCS_DLT_MODE.EDIT) {
|
|
@@ -1505,147 +1572,32 @@ const splitTemplateVarString = (str) => {
|
|
|
1505
1572
|
return { dltHeader, dltContent };
|
|
1506
1573
|
};
|
|
1507
1574
|
|
|
1508
|
-
const renderFallBackSmsComponent = () =>
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
// style: { marginLeft: '4px', marginTop: '3px' },
|
|
1533
|
-
// }}
|
|
1534
|
-
// title={formatMessage(messages.fallbackToolTip)}
|
|
1535
|
-
// />
|
|
1536
|
-
// </CapRow>
|
|
1537
|
-
// )}
|
|
1538
|
-
// description={formatMessage(messages.fallbackDesc)}
|
|
1539
|
-
// suffix={
|
|
1540
|
-
// isDltEnabled ? null : (
|
|
1541
|
-
// <CapButton
|
|
1542
|
-
// type="flat"
|
|
1543
|
-
// className="fallback-preview-btn"
|
|
1544
|
-
// prefix={<CapIcon type="eye" />}
|
|
1545
|
-
// style={{ color: CAP_SECONDARY.base }}
|
|
1546
|
-
// onClick={() => setFallbackPreviewmode(true)}
|
|
1547
|
-
// disabled={fallbackMessage === '' || fallbackMessageError}
|
|
1548
|
-
// >
|
|
1549
|
-
// {formatMessage(globalMessages.preview)}
|
|
1550
|
-
// </CapButton>
|
|
1551
|
-
// )
|
|
1552
|
-
// }
|
|
1553
|
-
// />
|
|
1554
|
-
// </CapRow>,
|
|
1555
|
-
// );
|
|
1556
|
-
|
|
1557
|
-
//dlt is enabled, and dlt content is not yet added, show button to add dlt creative
|
|
1558
|
-
// showAddCreativeBtnForDlt
|
|
1559
|
-
// && contentArr.push(
|
|
1560
|
-
// <CapCard className="rcs-dlt-fallback-card">
|
|
1561
|
-
// <CapRow type="flex" justify="center" align="middle">
|
|
1562
|
-
// <CapColumn span={10}>
|
|
1563
|
-
// <CapImage src={addCreativesIcon} />
|
|
1564
|
-
// </CapColumn>
|
|
1565
|
-
// <CapColumn span={14}>
|
|
1566
|
-
// <CapButton
|
|
1567
|
-
// className="add-dlt-btn"
|
|
1568
|
-
// type="secondary"
|
|
1569
|
-
// onClick={addDltMsgHandler}
|
|
1570
|
-
// >
|
|
1571
|
-
// {formatMessage(messages.addSmsCreative)}
|
|
1572
|
-
// </CapButton>
|
|
1573
|
-
// </CapColumn>
|
|
1574
|
-
// </CapRow>
|
|
1575
|
-
// </CapCard>,
|
|
1576
|
-
// );
|
|
1577
|
-
|
|
1578
|
-
// //dlt is enabled and dlt content is added, show it in a card
|
|
1579
|
-
// showCardForDlt
|
|
1580
|
-
// && contentArr.push(
|
|
1581
|
-
// <CapCustomCardList
|
|
1582
|
-
// cardList={[getDltContentCardList(fallbackMessage, SMS)]}
|
|
1583
|
-
// className="rcs-dlt-card"
|
|
1584
|
-
// />,
|
|
1585
|
-
// fallbackMessageError && (
|
|
1586
|
-
// <CapError className="rcs-fallback-len-error">
|
|
1587
|
-
// {formatMessage(messages.fallbackMsgLenError)}
|
|
1588
|
-
// </CapError>
|
|
1589
|
-
// ),
|
|
1590
|
-
// );
|
|
1591
|
-
|
|
1592
|
-
// //dlt is not enabled, show non dlt text area
|
|
1593
|
-
// showNonDltFallbackComp
|
|
1594
|
-
// && contentArr.push(
|
|
1595
|
-
// <>
|
|
1596
|
-
// <CapRow>
|
|
1597
|
-
// <CapHeader
|
|
1598
|
-
// title={(
|
|
1599
|
-
// <CapHeading type="h4">
|
|
1600
|
-
// {formatMessage(messages.fallbackTextAreaLabel)}
|
|
1601
|
-
// </CapHeading>
|
|
1602
|
-
// )}
|
|
1603
|
-
// suffix={(
|
|
1604
|
-
// <TagList
|
|
1605
|
-
// label={formatMessage(globalMessages.addLabels)}
|
|
1606
|
-
// onTagSelect={onTagSelectFallback}
|
|
1607
|
-
// location={location}
|
|
1608
|
-
// tags={tags || []}
|
|
1609
|
-
// onContextChange={handleOnTagsContextChange}
|
|
1610
|
-
// injectedTags={injectedTags || {}}
|
|
1611
|
-
// selectedOfferDetails={selectedOfferDetails}
|
|
1612
|
-
// />
|
|
1613
|
-
// )}
|
|
1614
|
-
// />
|
|
1615
|
-
// </CapRow>
|
|
1616
|
-
// <div className="rcs_fallback_msg_textarea_wrapper">
|
|
1617
|
-
// <TextArea
|
|
1618
|
-
// id="rcs_fallback_message_textarea"
|
|
1619
|
-
// autosize={{ minRows: 3, maxRows: 5 }}
|
|
1620
|
-
// placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
|
|
1621
|
-
// onChange={onFallbackMessageChange}
|
|
1622
|
-
// errorMessage={fallbackMessageError}
|
|
1623
|
-
// value={fallbackMessage || ""}
|
|
1624
|
-
// />
|
|
1625
|
-
// {!aiContentBotDisabled && (
|
|
1626
|
-
// <CapAskAira.ContentGenerationBot
|
|
1627
|
-
// text={fallbackMessage || ""}
|
|
1628
|
-
// setText={(text) => {
|
|
1629
|
-
// onFallbackMessageChange({ target: { value: text } });
|
|
1630
|
-
// }}
|
|
1631
|
-
// iconPlacement="float-br"
|
|
1632
|
-
// rootStyle={{
|
|
1633
|
-
// bottom: "0.5rem",
|
|
1634
|
-
// right: "0.5rem",
|
|
1635
|
-
// position: "absolute",
|
|
1636
|
-
// }}
|
|
1637
|
-
// />
|
|
1638
|
-
// )}
|
|
1639
|
-
// </div>
|
|
1640
|
-
// <CapRow>{fallbackSmsLength()}</CapRow>
|
|
1641
|
-
// </>
|
|
1642
|
-
// );
|
|
1643
|
-
|
|
1644
|
-
// return <>{contentArr}</>;
|
|
1645
|
-
};
|
|
1575
|
+
const renderFallBackSmsComponent = () => (
|
|
1576
|
+
<SmsFallback
|
|
1577
|
+
value={smsFallbackData}
|
|
1578
|
+
onChange={setSmsFallbackData}
|
|
1579
|
+
parentLocation={location}
|
|
1580
|
+
smsRegister={smsRegister}
|
|
1581
|
+
isFullMode={isFullMode}
|
|
1582
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1583
|
+
channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
|
|
1584
|
+
sectionTitle={
|
|
1585
|
+
smsFallbackData
|
|
1586
|
+
? formatMessage(messages.fallbackLabel)
|
|
1587
|
+
: formatMessage(messages.smsFallbackOptional)
|
|
1588
|
+
}
|
|
1589
|
+
templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
|
|
1590
|
+
templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
|
|
1591
|
+
/* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
|
|
1592
|
+
showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
|
|
1593
|
+
disableSelectTemplate={isEditFlow}
|
|
1594
|
+
eventContextTags={eventContextTags}
|
|
1595
|
+
onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
|
|
1596
|
+
isRcsEditFlow={isEditFlow}
|
|
1597
|
+
/>
|
|
1598
|
+
);
|
|
1646
1599
|
|
|
1647
1600
|
const uploadRcsImage = useCallback((file, type, fileParams, index) => {
|
|
1648
|
-
setImageError(null);
|
|
1649
1601
|
const isRcsThumbnail = index === 1;
|
|
1650
1602
|
actions.uploadRcsAsset(file, type, {
|
|
1651
1603
|
isRcsThumbnail,
|
|
@@ -1657,7 +1609,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1657
1609
|
|
|
1658
1610
|
const setUpdateRcsImageSrc = useCallback(
|
|
1659
1611
|
(val) => {
|
|
1660
|
-
setAssetListImage(val);
|
|
1661
1612
|
updateRcsImageSrc(val);
|
|
1662
1613
|
actions.clearRcsMediaAsset(0);
|
|
1663
1614
|
},
|
|
@@ -1687,7 +1638,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1687
1638
|
|
|
1688
1639
|
|
|
1689
1640
|
const uploadRcsVideo = (file, type, fileParams) => {
|
|
1690
|
-
setImageError(null);
|
|
1691
1641
|
actions.uploadRcsAsset(file, type, {
|
|
1692
1642
|
...fileParams,
|
|
1693
1643
|
type: 'video',
|
|
@@ -1696,9 +1646,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1696
1646
|
});
|
|
1697
1647
|
};
|
|
1698
1648
|
|
|
1699
|
-
const updateRcsVideoSrc = (val) => {
|
|
1700
|
-
setRcsVideoSrc(val);
|
|
1701
|
-
};
|
|
1702
1649
|
const setUpdateRcsVideoSrc = useCallback((index, val) => {
|
|
1703
1650
|
setRcsVideoSrc(val);
|
|
1704
1651
|
setAssetList(val);
|
|
@@ -1727,7 +1674,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1727
1674
|
<>
|
|
1728
1675
|
<CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
|
|
1729
1676
|
<CapImageUpload
|
|
1730
|
-
|
|
1677
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1731
1678
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1732
1679
|
imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
|
|
1733
1680
|
imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
|
|
@@ -1739,7 +1686,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1739
1686
|
updateOnReUpload={updateOnRcsThumbnailReUpload}
|
|
1740
1687
|
minImgSize={RCS_THUMBNAIL_MIN_SIZE}
|
|
1741
1688
|
index={1}
|
|
1742
|
-
className="cap-custom-image-upload"
|
|
1743
1689
|
key={`rcs-uploaded-image-${currentDimension}`}
|
|
1744
1690
|
imageData={thumbnailData}
|
|
1745
1691
|
channel={RCS}
|
|
@@ -1770,7 +1716,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1770
1716
|
value: dim.type,
|
|
1771
1717
|
label: `${dim.label}`
|
|
1772
1718
|
}))}
|
|
1773
|
-
|
|
1719
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1774
1720
|
/>
|
|
1775
1721
|
</>
|
|
1776
1722
|
)}
|
|
@@ -1784,7 +1730,6 @@ const splitTemplateVarString = (str) => {
|
|
|
1784
1730
|
</div>
|
|
1785
1731
|
) : (
|
|
1786
1732
|
<CapImageUpload
|
|
1787
|
-
style={{ paddingTop: '20px' }}
|
|
1788
1733
|
allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
|
|
1789
1734
|
imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
|
|
1790
1735
|
imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
|
|
@@ -1795,7 +1740,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1795
1740
|
updateImageSrc={setUpdateRcsImageSrc}
|
|
1796
1741
|
updateOnReUpload={updateOnRcsImageReUpload}
|
|
1797
1742
|
index={0}
|
|
1798
|
-
className="cap-custom-image-upload"
|
|
1743
|
+
className="cap-custom-image-upload rcs-image-upload--top-spacing"
|
|
1799
1744
|
key={`rcs-uploaded-image-${selectedDimension}`}
|
|
1800
1745
|
imageData={rcsData}
|
|
1801
1746
|
channel={RCS}
|
|
@@ -1825,7 +1770,7 @@ const splitTemplateVarString = (str) => {
|
|
|
1825
1770
|
value: dim.type,
|
|
1826
1771
|
label: `${dim.label}`
|
|
1827
1772
|
}))}
|
|
1828
|
-
|
|
1773
|
+
className="rcs-dimension-select--bottom-spacing"
|
|
1829
1774
|
/>
|
|
1830
1775
|
)}
|
|
1831
1776
|
{(isEditFlow || !isFullMode) ? (
|
|
@@ -1885,8 +1830,16 @@ const splitTemplateVarString = (str) => {
|
|
|
1885
1830
|
const getRcsPreview = () => {
|
|
1886
1831
|
|
|
1887
1832
|
const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
|
|
1888
|
-
const
|
|
1889
|
-
const
|
|
1833
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1834
|
+
const titleVarCountForResolve = isMediaTypeText
|
|
1835
|
+
? 0
|
|
1836
|
+
: ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
|
|
1837
|
+
const resolvedTitle = isMediaTypeText
|
|
1838
|
+
? ''
|
|
1839
|
+
: (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
|
|
1840
|
+
const resolvedDesc = isSlotMappingMode
|
|
1841
|
+
? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
|
|
1842
|
+
: templateDesc;
|
|
1890
1843
|
return (
|
|
1891
1844
|
<UnifiedPreview
|
|
1892
1845
|
channel={RCS}
|
|
@@ -1909,51 +1862,65 @@ const splitTemplateVarString = (str) => {
|
|
|
1909
1862
|
);
|
|
1910
1863
|
};
|
|
1911
1864
|
|
|
1912
|
-
const getUnmappedDesc = (str, mapping) => {
|
|
1913
|
-
if (!str) return '';
|
|
1914
|
-
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
1915
|
-
let result = str;
|
|
1916
|
-
const replacements = [];
|
|
1917
|
-
Object.entries(mapping).forEach(([key, value]) => {
|
|
1918
|
-
const raw = (value ?? '').toString();
|
|
1919
|
-
if (!raw || raw?.trim?.() === '') return;
|
|
1920
|
-
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
1921
|
-
replacements.push({ key, needle: raw });
|
|
1922
|
-
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
1923
|
-
});
|
|
1924
|
-
const seen = new Set();
|
|
1925
|
-
const uniq = replacements
|
|
1926
|
-
.filter(({ key, needle }) => {
|
|
1927
|
-
const id = `${key}::${needle}`;
|
|
1928
|
-
if (seen.has(id)) return false;
|
|
1929
|
-
seen.add(id);
|
|
1930
|
-
return true;
|
|
1931
|
-
})
|
|
1932
|
-
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
1933
|
-
|
|
1934
|
-
uniq.forEach(({ key, needle }) => {
|
|
1935
|
-
if (!needle) return;
|
|
1936
|
-
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
1937
|
-
const regex = new RegExp(escaped, 'g');
|
|
1938
|
-
result = result.replace(regex, `{{${key}}}`);
|
|
1939
|
-
});
|
|
1940
|
-
return result;
|
|
1941
|
-
};
|
|
1942
|
-
|
|
1943
1865
|
const createPayload = () => {
|
|
1944
|
-
const
|
|
1945
|
-
const {
|
|
1946
|
-
template_id: templateId = '',
|
|
1947
|
-
template_name = '',
|
|
1948
|
-
'sms-editor': template = '',
|
|
1949
|
-
header: registeredSenderIds = [],
|
|
1950
|
-
} = base;
|
|
1951
|
-
const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
|
|
1952
|
-
const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
|
|
1866
|
+
const isSlotMappingMode = isEditFlow || !isFullMode;
|
|
1953
1867
|
const alignment = isMediaTypeImage
|
|
1954
1868
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
|
|
1955
1869
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
|
|
1956
1870
|
|
|
1871
|
+
const heightTypeForCardWidth = isMediaTypeText
|
|
1872
|
+
? undefined
|
|
1873
|
+
: isMediaTypeImage
|
|
1874
|
+
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
|
|
1875
|
+
: isMediaTypeVideo
|
|
1876
|
+
? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
|
|
1877
|
+
: undefined;
|
|
1878
|
+
const cardWidthFromSelection =
|
|
1879
|
+
heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
|
|
1880
|
+
|
|
1881
|
+
/** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
|
|
1882
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
1883
|
+
const smsFallbackMerged = !isFullMode
|
|
1884
|
+
? (() => {
|
|
1885
|
+
const local =
|
|
1886
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
1887
|
+
return {
|
|
1888
|
+
...smsFromApiShape,
|
|
1889
|
+
...local,
|
|
1890
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
1891
|
+
};
|
|
1892
|
+
})()
|
|
1893
|
+
: (smsFallbackData || {});
|
|
1894
|
+
const smsFallbackForPayload = (() => {
|
|
1895
|
+
if (isFullMode) {
|
|
1896
|
+
return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
|
|
1897
|
+
}
|
|
1898
|
+
const mapped = {
|
|
1899
|
+
templateName:
|
|
1900
|
+
smsFallbackMerged.templateName
|
|
1901
|
+
|| smsFallbackMerged.smsTemplateName
|
|
1902
|
+
|| '',
|
|
1903
|
+
// Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
|
|
1904
|
+
content:
|
|
1905
|
+
smsFallbackMerged.content
|
|
1906
|
+
|| smsFallbackMerged.smsContent
|
|
1907
|
+
|| smsFallbackMerged.smsTemplateContent
|
|
1908
|
+
|| smsFallbackMerged.message
|
|
1909
|
+
|| '',
|
|
1910
|
+
templateContent:
|
|
1911
|
+
pickFirstSmsFallbackTemplateString(smsFallbackMerged)
|
|
1912
|
+
|| '',
|
|
1913
|
+
...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
|
|
1914
|
+
&& { unicodeValidity: smsFallbackMerged.unicodeValidity }),
|
|
1915
|
+
...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
1916
|
+
&& Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
|
|
1917
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
|
|
1918
|
+
smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
|
|
1919
|
+
}),
|
|
1920
|
+
};
|
|
1921
|
+
return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
|
|
1922
|
+
})();
|
|
1923
|
+
|
|
1957
1924
|
const payload = {
|
|
1958
1925
|
name: templateName,
|
|
1959
1926
|
versions: {
|
|
@@ -1965,12 +1932,14 @@ const splitTemplateVarString = (str) => {
|
|
|
1965
1932
|
cardSettings: {
|
|
1966
1933
|
cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
|
|
1967
1934
|
...(alignment && { mediaAlignment: alignment }),
|
|
1968
|
-
cardWidth:
|
|
1935
|
+
cardWidth: cardWidthFromSelection,
|
|
1969
1936
|
},
|
|
1970
1937
|
cardContent: [
|
|
1971
1938
|
{
|
|
1972
|
-
|
|
1973
|
-
|
|
1939
|
+
// Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
|
|
1940
|
+
// / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
|
|
1941
|
+
title: templateTitle,
|
|
1942
|
+
description: templateDesc,
|
|
1974
1943
|
mediaType: templateMediaType,
|
|
1975
1944
|
...(!isMediaTypeText && {media: {
|
|
1976
1945
|
mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
|
|
@@ -1979,23 +1948,30 @@ const splitTemplateVarString = (str) => {
|
|
|
1979
1948
|
? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
|
|
1980
1949
|
: RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
|
|
1981
1950
|
}}),
|
|
1982
|
-
...(
|
|
1983
|
-
const
|
|
1984
|
-
...(templateTitle
|
|
1985
|
-
...(templateDesc
|
|
1951
|
+
...(isSlotMappingMode && (() => {
|
|
1952
|
+
const templateVarTokens = [
|
|
1953
|
+
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
1954
|
+
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
1986
1955
|
];
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1956
|
+
const persistedSlotVarMap = {};
|
|
1957
|
+
const seenSemanticVarNames = new Set();
|
|
1958
|
+
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
1959
|
+
const varName = getVarNameFromToken(token);
|
|
1960
|
+
if (!varName) return;
|
|
1961
|
+
const resolvedRawValue = resolveCardVarMappedSlotValue(
|
|
1962
|
+
cardVarMapped,
|
|
1963
|
+
varName,
|
|
1964
|
+
slotIndexZeroBased,
|
|
1965
|
+
isSlotMappingMode,
|
|
1966
|
+
);
|
|
1967
|
+
const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
|
|
1968
|
+
persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
|
|
1969
|
+
if (!seenSemanticVarNames.has(varName)) {
|
|
1970
|
+
seenSemanticVarNames.add(varName);
|
|
1971
|
+
persistedSlotVarMap[varName] = sanitizedSlotValue;
|
|
1996
1972
|
}
|
|
1997
1973
|
});
|
|
1998
|
-
return { cardVarMapped:
|
|
1974
|
+
return { cardVarMapped: persistedSlotVarMap };
|
|
1999
1975
|
})()),
|
|
2000
1976
|
...(suggestions.length > 0 && { suggestions }),
|
|
2001
1977
|
}
|
|
@@ -2003,17 +1979,29 @@ const splitTemplateVarString = (str) => {
|
|
|
2003
1979
|
contentType: isFullMode ? templateType : RICHCARD,
|
|
2004
1980
|
...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
|
|
2005
1981
|
},
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
1982
|
+
...(smsFallbackForPayload && (() => {
|
|
1983
|
+
const smsBodyText =
|
|
1984
|
+
smsFallbackForPayload.content
|
|
1985
|
+
|| smsFallbackForPayload.templateContent
|
|
1986
|
+
|| smsFallbackForPayload.message
|
|
1987
|
+
|| smsFallbackForPayload.smsContent
|
|
1988
|
+
|| '';
|
|
1989
|
+
return {
|
|
1990
|
+
smsFallBackContent: {
|
|
1991
|
+
smsTemplateName: smsFallbackForPayload.templateName || '',
|
|
1992
|
+
smsContent: smsBodyText,
|
|
1993
|
+
// cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
|
|
1994
|
+
message: smsBodyText,
|
|
1995
|
+
...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
|
|
1996
|
+
unicodeValidity: smsFallbackForPayload.unicodeValidity,
|
|
1997
|
+
}),
|
|
1998
|
+
...(smsFallbackForPayload.rcsSmsFallbackVarMapped
|
|
1999
|
+
&& Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
|
|
2000
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
|
|
2001
|
+
}),
|
|
2014
2002
|
},
|
|
2015
|
-
}
|
|
2016
|
-
},
|
|
2003
|
+
};
|
|
2004
|
+
})()),
|
|
2017
2005
|
},
|
|
2018
2006
|
},
|
|
2019
2007
|
},
|
|
@@ -2023,6 +2011,84 @@ const splitTemplateVarString = (str) => {
|
|
|
2023
2011
|
return payload;
|
|
2024
2012
|
};
|
|
2025
2013
|
|
|
2014
|
+
/** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
|
|
2015
|
+
const testPreviewFormData = useMemo(() => {
|
|
2016
|
+
const payload = createPayload();
|
|
2017
|
+
const rcs = payload?.versions?.base?.content?.RCS;
|
|
2018
|
+
if (!rcs) return null;
|
|
2019
|
+
// createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
|
|
2020
|
+
const accountIdForCreateMessageMeta =
|
|
2021
|
+
(wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
|
|
2022
|
+
? String(wecrmAccountId)
|
|
2023
|
+
: accountId;
|
|
2024
|
+
const rcsForTest = {
|
|
2025
|
+
...rcs,
|
|
2026
|
+
rcsContent: {
|
|
2027
|
+
...rcs.rcsContent,
|
|
2028
|
+
...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
|
|
2029
|
+
},
|
|
2030
|
+
};
|
|
2031
|
+
const out = {
|
|
2032
|
+
versions: {
|
|
2033
|
+
base: {
|
|
2034
|
+
content: {
|
|
2035
|
+
RCS: rcsForTest,
|
|
2036
|
+
},
|
|
2037
|
+
},
|
|
2038
|
+
},
|
|
2039
|
+
};
|
|
2040
|
+
const fb = smsFallbackData;
|
|
2041
|
+
if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
|
|
2042
|
+
out.templateConfigs = {
|
|
2043
|
+
templateId: fb.smsTemplateId || '',
|
|
2044
|
+
template: fb.templateContent || fb.content || '',
|
|
2045
|
+
traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
|
|
2046
|
+
registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
return out;
|
|
2050
|
+
}, [
|
|
2051
|
+
templateName,
|
|
2052
|
+
templateTitle,
|
|
2053
|
+
templateDesc,
|
|
2054
|
+
templateMediaType,
|
|
2055
|
+
cardVarMapped,
|
|
2056
|
+
suggestions,
|
|
2057
|
+
rcsImageSrc,
|
|
2058
|
+
rcsVideoSrc,
|
|
2059
|
+
rcsThumbnailSrc,
|
|
2060
|
+
selectedDimension,
|
|
2061
|
+
smsFallbackData,
|
|
2062
|
+
isFullMode,
|
|
2063
|
+
isEditFlow,
|
|
2064
|
+
templateType,
|
|
2065
|
+
accountId,
|
|
2066
|
+
wecrmAccountId,
|
|
2067
|
+
accessToken,
|
|
2068
|
+
accountName,
|
|
2069
|
+
hostName,
|
|
2070
|
+
smsRegister,
|
|
2071
|
+
]);
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
|
|
2075
|
+
* with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
|
|
2076
|
+
* miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
|
|
2077
|
+
*/
|
|
2078
|
+
const librarySmsFallbackMergedForValidation = useMemo(() => {
|
|
2079
|
+
if (isFullMode) {
|
|
2080
|
+
return smsFallbackData;
|
|
2081
|
+
}
|
|
2082
|
+
const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
2083
|
+
const local =
|
|
2084
|
+
smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
|
|
2085
|
+
return {
|
|
2086
|
+
...smsFromApiShape,
|
|
2087
|
+
...local,
|
|
2088
|
+
rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
|
|
2089
|
+
};
|
|
2090
|
+
}, [isFullMode, templateData, smsFallbackData]);
|
|
2091
|
+
|
|
2026
2092
|
const actionCallback = ({ errorMessage, resp }, isEdit) => {
|
|
2027
2093
|
// eslint-disable-next-line no-undef
|
|
2028
2094
|
const error = errorMessage?.message || errorMessage;
|
|
@@ -2052,6 +2118,9 @@ const splitTemplateVarString = (str) => {
|
|
|
2052
2118
|
_id: params?.id,
|
|
2053
2119
|
validity: true,
|
|
2054
2120
|
type: RCS,
|
|
2121
|
+
// CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
|
|
2122
|
+
// the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
|
|
2123
|
+
closeSlideBoxAfterSubmit: !isFullMode,
|
|
2055
2124
|
};
|
|
2056
2125
|
getFormData(formDataParams);
|
|
2057
2126
|
};
|
|
@@ -2065,6 +2134,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2065
2134
|
actionCallback({ resp, errorMessage });
|
|
2066
2135
|
setSpin(false); // Always turn off spinner
|
|
2067
2136
|
if (!errorMessage) {
|
|
2137
|
+
setTemplateStatus(RCS_STATUSES.pending);
|
|
2068
2138
|
onCreateComplete();
|
|
2069
2139
|
}
|
|
2070
2140
|
});
|
|
@@ -2076,6 +2146,64 @@ const splitTemplateVarString = (str) => {
|
|
|
2076
2146
|
}
|
|
2077
2147
|
};
|
|
2078
2148
|
|
|
2149
|
+
/** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
|
|
2150
|
+
const smsFallbackBlocksDone = () => {
|
|
2151
|
+
// Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
|
|
2152
|
+
if (
|
|
2153
|
+
!isFullMode
|
|
2154
|
+
&& !isTraiDLTEnable(isFullMode, smsRegister)
|
|
2155
|
+
&& smsFallbackData == null
|
|
2156
|
+
&& hasMeaningfulSmsFallbackShape(
|
|
2157
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
|
|
2158
|
+
)
|
|
2159
|
+
) {
|
|
2160
|
+
return true;
|
|
2161
|
+
}
|
|
2162
|
+
if (!smsFallbackData) return false;
|
|
2163
|
+
// Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
|
|
2164
|
+
// concern, not a structural requirement for approval — the registered SMS template body stands on
|
|
2165
|
+
// its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
|
|
2166
|
+
if (isFullMode) return false;
|
|
2167
|
+
const merged = librarySmsFallbackMergedForValidation;
|
|
2168
|
+
const templateText = pickFirstSmsFallbackTemplateString(merged);
|
|
2169
|
+
if (!templateText) {
|
|
2170
|
+
return true;
|
|
2171
|
+
}
|
|
2172
|
+
const rawVarMap =
|
|
2173
|
+
merged.rcsSmsFallbackVarMapped
|
|
2174
|
+
|| merged['rcs-sms-fallback-var-mapped'];
|
|
2175
|
+
const varMap =
|
|
2176
|
+
rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
|
|
2177
|
+
return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
|
|
2178
|
+
};
|
|
2179
|
+
|
|
2180
|
+
/**
|
|
2181
|
+
* Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
|
|
2182
|
+
* semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
|
|
2183
|
+
* / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
|
|
2184
|
+
*/
|
|
2185
|
+
const isLibraryCampaignCardVarMappingIncomplete = () => {
|
|
2186
|
+
if (isFullMode) return false;
|
|
2187
|
+
const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
|
|
2188
|
+
rcsVarTestRegex.test(elem),
|
|
2189
|
+
);
|
|
2190
|
+
const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
|
|
2191
|
+
rcsVarTestRegex.test(elem),
|
|
2192
|
+
);
|
|
2193
|
+
const orderedVarNames = [
|
|
2194
|
+
...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
2195
|
+
...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
|
|
2196
|
+
];
|
|
2197
|
+
if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
|
|
2198
|
+
return true;
|
|
2199
|
+
}
|
|
2200
|
+
return orderedVarNames.some((name, globalIdx) => {
|
|
2201
|
+
const v = resolveCardVarMappedSlotValue(cardVarMapped, name, globalIdx, true);
|
|
2202
|
+
const s = v == null ? '' : String(v);
|
|
2203
|
+
return s.trim() === '';
|
|
2204
|
+
});
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2079
2207
|
const isDisableDone = () => {
|
|
2080
2208
|
if(isEditFlow){
|
|
2081
2209
|
return false;
|
|
@@ -2086,40 +2214,16 @@ const splitTemplateVarString = (str) => {
|
|
|
2086
2214
|
}
|
|
2087
2215
|
}
|
|
2088
2216
|
|
|
2089
|
-
if(
|
|
2090
|
-
|
|
2091
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2092
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2093
|
-
|
|
2094
|
-
if (allVars.length > 0 && isEmpty(cardVarMapped)) {
|
|
2095
|
-
return true;
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
const hasEmptyMapping =
|
|
2099
|
-
cardVarMapped &&
|
|
2100
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2101
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2102
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2103
|
-
return v.trim() === ''; // empty string
|
|
2104
|
-
});
|
|
2105
|
-
|
|
2106
|
-
if (hasEmptyMapping) {
|
|
2107
|
-
return true;
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
const anyMissing = allVars.some(name => {
|
|
2111
|
-
const v = cardVarMapped?.[name];
|
|
2112
|
-
if (typeof v !== 'string') return !v;
|
|
2113
|
-
return v.trim() === '';
|
|
2114
|
-
});
|
|
2115
|
-
if (anyMissing) {
|
|
2116
|
-
return true;
|
|
2117
|
-
}
|
|
2217
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2218
|
+
return true;
|
|
2118
2219
|
}
|
|
2119
2220
|
|
|
2120
|
-
|
|
2221
|
+
if (smsFallbackBlocksDone()) {
|
|
2121
2222
|
return true;
|
|
2223
|
+
}
|
|
2122
2224
|
|
|
2225
|
+
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2226
|
+
return true;
|
|
2123
2227
|
}
|
|
2124
2228
|
if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
|
|
2125
2229
|
return true;
|
|
@@ -2137,53 +2241,36 @@ const splitTemplateVarString = (str) => {
|
|
|
2137
2241
|
return true;
|
|
2138
2242
|
}
|
|
2139
2243
|
}
|
|
2140
|
-
if (templateDescError || templateTitleError
|
|
2244
|
+
if (templateDescError || templateTitleError) {
|
|
2245
|
+
return true;
|
|
2246
|
+
}
|
|
2247
|
+
if (
|
|
2248
|
+
smsFallbackData?.content
|
|
2249
|
+
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2250
|
+
) {
|
|
2141
2251
|
return true;
|
|
2142
2252
|
}
|
|
2143
2253
|
return false;
|
|
2144
2254
|
};
|
|
2145
2255
|
|
|
2146
2256
|
const isEditDisableDone = () => {
|
|
2147
|
-
|
|
2148
2257
|
if (templateStatus !== RCS_STATUSES.approved) {
|
|
2149
2258
|
return true;
|
|
2150
2259
|
}
|
|
2151
2260
|
|
|
2152
|
-
if (!isFullMode) {
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2261
|
+
// if (!isFullMode) {
|
|
2262
|
+
// if (templateName.trim() === '' || templateNameError) {
|
|
2263
|
+
// return true;
|
|
2264
|
+
// }
|
|
2265
|
+
// }
|
|
2266
|
+
if (isLibraryCampaignCardVarMappingIncomplete()) {
|
|
2267
|
+
return true;
|
|
2156
2268
|
}
|
|
2157
|
-
if(!isFullMode){
|
|
2158
|
-
const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2159
|
-
const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
|
|
2160
|
-
const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
|
|
2161
2269
|
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
const hasEmptyMapping =
|
|
2167
|
-
cardVarMapped &&
|
|
2168
|
-
Object.keys(cardVarMapped).length > 0 &&
|
|
2169
|
-
Object.entries(cardVarMapped).some(([_, v]) => {
|
|
2170
|
-
if (typeof v !== 'string') return !v; // null/undefined
|
|
2171
|
-
return v.trim() === ''; // empty string
|
|
2172
|
-
});
|
|
2173
|
-
|
|
2174
|
-
if (hasEmptyMapping) {
|
|
2175
|
-
return true;
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
const anyMissing = allVars.some(name => {
|
|
2179
|
-
const v = cardVarMapped?.[name];
|
|
2180
|
-
if (typeof v !== 'string') return !v;
|
|
2181
|
-
return v.trim() === '';
|
|
2182
|
-
});
|
|
2183
|
-
if (anyMissing) {
|
|
2184
|
-
return true;
|
|
2185
|
-
}
|
|
2270
|
+
if (smsFallbackBlocksDone()) {
|
|
2271
|
+
return true;
|
|
2186
2272
|
}
|
|
2273
|
+
|
|
2187
2274
|
if (isMediaTypeText && templateDesc.trim() === '') {
|
|
2188
2275
|
return true;
|
|
2189
2276
|
}
|
|
@@ -2202,7 +2289,13 @@ const splitTemplateVarString = (str) => {
|
|
|
2202
2289
|
return true;
|
|
2203
2290
|
}
|
|
2204
2291
|
}
|
|
2205
|
-
if (templateTitleError || templateDescError
|
|
2292
|
+
if (templateTitleError || templateDescError) {
|
|
2293
|
+
return true;
|
|
2294
|
+
}
|
|
2295
|
+
if (
|
|
2296
|
+
smsFallbackData?.content
|
|
2297
|
+
&& smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
|
|
2298
|
+
) {
|
|
2206
2299
|
return true;
|
|
2207
2300
|
}
|
|
2208
2301
|
return false;
|
|
@@ -2252,52 +2345,56 @@ const splitTemplateVarString = (str) => {
|
|
|
2252
2345
|
};
|
|
2253
2346
|
|
|
2254
2347
|
const getMainContent = () => {
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
return (
|
|
2259
|
-
<CapSlideBox
|
|
2260
|
-
show={showDltContainer}
|
|
2261
|
-
header={dltHeader}
|
|
2262
|
-
content={dltContent}
|
|
2263
|
-
handleClose={closeDltContainerHandler}
|
|
2264
|
-
size="size-xl"
|
|
2265
|
-
/>
|
|
2266
|
-
);
|
|
2267
|
-
}
|
|
2348
|
+
// Slideboxes are rendered outside the page-level spinner to avoid
|
|
2349
|
+
// stacking/blur issues during initial loads.
|
|
2350
|
+
if (showDltContainer) return null;
|
|
2268
2351
|
|
|
2269
2352
|
return (
|
|
2270
2353
|
<>
|
|
2271
|
-
{templateStatus !== '' && (
|
|
2272
|
-
<
|
|
2273
|
-
{
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2354
|
+
{templateStatus !== '' && (
|
|
2355
|
+
<CapRow className="template-status-container">
|
|
2356
|
+
<CapColumn span={14}>
|
|
2357
|
+
<CapLabel type="label2">
|
|
2358
|
+
{formatMessage(messages.templateStatusLabel)}
|
|
2359
|
+
</CapLabel>
|
|
2360
|
+
|
|
2361
|
+
{templateStatus && (
|
|
2362
|
+
<CapAlert
|
|
2363
|
+
message={getTemplateStatusMessage()}
|
|
2364
|
+
type={getTemplateStatusType(templateStatus)}
|
|
2365
|
+
/>
|
|
2366
|
+
)}
|
|
2367
|
+
</CapColumn>
|
|
2368
|
+
</CapRow>
|
|
2283
2369
|
)}
|
|
2284
|
-
<CapRow className=
|
|
2370
|
+
<CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
|
|
2285
2371
|
<CapColumn span={14}>
|
|
2286
2372
|
{/* template name */}
|
|
2287
2373
|
{isFullMode && (
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2374
|
+
isEditFlow ? (
|
|
2375
|
+
<div className="rcs-creative-name-readonly">
|
|
2376
|
+
<CapHeading type="h4">
|
|
2377
|
+
{formatMessage(globalMessages.creativeNameLabel)}
|
|
2378
|
+
</CapHeading>
|
|
2379
|
+
<CapHeading type="h5" className="rcs-creative-name-value">
|
|
2380
|
+
{templateName || '-'}
|
|
2381
|
+
</CapHeading>
|
|
2382
|
+
</div>
|
|
2383
|
+
) : (
|
|
2384
|
+
<CapInput
|
|
2385
|
+
id="rcs_template_name_input"
|
|
2386
|
+
data-testid="template_name"
|
|
2387
|
+
onChange={onTemplateNameChange}
|
|
2388
|
+
errorMessage={templateNameError}
|
|
2389
|
+
placeholder={formatMessage(
|
|
2390
|
+
globalMessages.templateNamePlaceholder,
|
|
2391
|
+
)}
|
|
2392
|
+
value={templateName || ''}
|
|
2393
|
+
size="default"
|
|
2394
|
+
label={formatMessage(globalMessages.creativeNameLabel)}
|
|
2395
|
+
disabled={(isEditFlow || !isFullMode)}
|
|
2396
|
+
/>
|
|
2397
|
+
)
|
|
2301
2398
|
)}
|
|
2302
2399
|
{renderLabel('templateTypeLabel')}
|
|
2303
2400
|
<CapRadioGroup
|
|
@@ -2325,7 +2422,7 @@ const splitTemplateVarString = (str) => {
|
|
|
2325
2422
|
</>
|
|
2326
2423
|
)}
|
|
2327
2424
|
{renderTextComponent()}
|
|
2328
|
-
<CapDivider
|
|
2425
|
+
<CapDivider className="rcs-fallback-section-divider" />
|
|
2329
2426
|
{renderFallBackSmsComponent()}
|
|
2330
2427
|
<div className="rcs-scroll-div" />
|
|
2331
2428
|
</CapColumn>
|
|
@@ -2337,7 +2434,8 @@ const splitTemplateVarString = (str) => {
|
|
|
2337
2434
|
|
|
2338
2435
|
|
|
2339
2436
|
<div className="rcs-footer">
|
|
2340
|
-
{!
|
|
2437
|
+
{/* 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). */}
|
|
2438
|
+
{!isEditFlow && isFullMode && (
|
|
2341
2439
|
<>
|
|
2342
2440
|
<div className="button-disabled-tooltip-wrapper">
|
|
2343
2441
|
<CapButton
|
|
@@ -2358,7 +2456,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2358
2456
|
className="rcs-test-preview-btn"
|
|
2359
2457
|
type="secondary"
|
|
2360
2458
|
disabled={true}
|
|
2361
|
-
style={{ marginLeft: "8px" }}
|
|
2362
2459
|
>
|
|
2363
2460
|
<FormattedMessage {...creativesMessages.testAndPreview} />
|
|
2364
2461
|
</CapButton>
|
|
@@ -2391,51 +2488,6 @@ const splitTemplateVarString = (str) => {
|
|
|
2391
2488
|
</>
|
|
2392
2489
|
)}
|
|
2393
2490
|
</div>
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
{fallbackPreviewmode && (
|
|
2397
|
-
<CapSlideBox
|
|
2398
|
-
className="rcs-fallback-preview"
|
|
2399
|
-
show={fallbackPreviewmode}
|
|
2400
|
-
header={(
|
|
2401
|
-
<CapHeading type="h7" style={{ color: CAP_G01 }}>
|
|
2402
|
-
{formatMessage(messages.fallbackPreviewtitle)}
|
|
2403
|
-
</CapHeading>
|
|
2404
|
-
)}
|
|
2405
|
-
content={(
|
|
2406
|
-
<>
|
|
2407
|
-
<UnifiedPreview
|
|
2408
|
-
channel={RCS}
|
|
2409
|
-
content={{
|
|
2410
|
-
rcsPreviewContent: {
|
|
2411
|
-
rcsDesc: tempMsg,
|
|
2412
|
-
},
|
|
2413
|
-
}}
|
|
2414
|
-
device={ANDROID}
|
|
2415
|
-
showDeviceToggle={false}
|
|
2416
|
-
showHeader={false}
|
|
2417
|
-
formatMessage={formatMessage}
|
|
2418
|
-
/>
|
|
2419
|
-
<CapHeading
|
|
2420
|
-
type="h3"
|
|
2421
|
-
style={{ textAlign: 'center' }}
|
|
2422
|
-
className="margin-t-16"
|
|
2423
|
-
>
|
|
2424
|
-
{formatMessage(messages.totalCharacters, {
|
|
2425
|
-
smsCount: Math.ceil(
|
|
2426
|
-
tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
|
|
2427
|
-
),
|
|
2428
|
-
number: tempMsg.length,
|
|
2429
|
-
})}
|
|
2430
|
-
</CapHeading>
|
|
2431
|
-
</>
|
|
2432
|
-
)}
|
|
2433
|
-
handleClose={() => {
|
|
2434
|
-
setFallbackPreviewmode(false);
|
|
2435
|
-
setDltPreviewData('');
|
|
2436
|
-
}}
|
|
2437
|
-
/>
|
|
2438
|
-
)}
|
|
2439
2491
|
</>
|
|
2440
2492
|
);
|
|
2441
2493
|
};
|
|
@@ -2444,12 +2496,38 @@ const splitTemplateVarString = (str) => {
|
|
|
2444
2496
|
<CapSpin spinning={loadingTags || spin}>
|
|
2445
2497
|
{getMainContent()}
|
|
2446
2498
|
</CapSpin>
|
|
2499
|
+
|
|
2500
|
+
{showDltContainer && (() => {
|
|
2501
|
+
const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
|
|
2502
|
+
return (
|
|
2503
|
+
<CapSlideBox
|
|
2504
|
+
show={showDltContainer}
|
|
2505
|
+
header={dltHeader}
|
|
2506
|
+
content={dltContent}
|
|
2507
|
+
handleClose={closeDltContainerHandler}
|
|
2508
|
+
size="size-xl"
|
|
2509
|
+
/>
|
|
2510
|
+
);
|
|
2511
|
+
})()}
|
|
2512
|
+
|
|
2447
2513
|
<TestAndPreviewSlidebox
|
|
2448
2514
|
show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
|
|
2449
2515
|
onClose={handleCloseTestAndPreview}
|
|
2450
|
-
formData={
|
|
2451
|
-
content={
|
|
2516
|
+
formData={testPreviewFormData}
|
|
2517
|
+
content={testAndPreviewContent}
|
|
2452
2518
|
currentChannel={RCS}
|
|
2519
|
+
orgUnitId={orgUnitId}
|
|
2520
|
+
smsFallbackContent={
|
|
2521
|
+
smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
|
|
2522
|
+
? {
|
|
2523
|
+
templateContent:
|
|
2524
|
+
smsFallbackData.templateContent || smsFallbackData.content || '',
|
|
2525
|
+
templateName: smsFallbackData.templateName || '',
|
|
2526
|
+
[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackData?.rcsSmsFallbackVarMapped ?? {},
|
|
2527
|
+
}
|
|
2528
|
+
: null
|
|
2529
|
+
}
|
|
2530
|
+
smsRegister={smsRegister}
|
|
2453
2531
|
/>
|
|
2454
2532
|
</>
|
|
2455
2533
|
);
|