@capillarytech/creatives-library 8.0.318 → 8.0.320
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/templateVarUtils.test.js +160 -0
- package/v2Components/CapTagList/index.js +10 -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 +7 -1
- 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/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 +36 -4
- 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 -99
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/Rcs/constants.js +32 -1
- package/v2Containers/Rcs/index.js +950 -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 +81 -36
- 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 +609 -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 +37 -8
- package/v2Containers/TagList/index.js +6 -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/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import PropTypes from 'prop-types';
|
|
10
10
|
import React, {
|
|
11
|
-
useState, useEffect, useMemo, useRef,
|
|
11
|
+
useState, useEffect, useMemo, useRef, useCallback,
|
|
12
12
|
} from 'react';
|
|
13
13
|
import { FormattedMessage } from 'react-intl';
|
|
14
14
|
import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
|
|
@@ -28,7 +28,8 @@ import CustomValuesEditor from './CustomValuesEditor';
|
|
|
28
28
|
import SendTestMessage from './SendTestMessage';
|
|
29
29
|
import PreviewSection from './PreviewSection';
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
import * as Api from '../../services/api';
|
|
32
|
+
import { extractTemplateVariables } from '../../utils/templateVarUtils';
|
|
32
33
|
import {
|
|
33
34
|
CHANNELS,
|
|
34
35
|
TEST,
|
|
@@ -70,10 +71,120 @@ import {
|
|
|
70
71
|
IMAGE,
|
|
71
72
|
VIDEO,
|
|
72
73
|
URL,
|
|
74
|
+
PREVIEW_TAB_RCS,
|
|
75
|
+
PREVIEW_TAB_SMS_FALLBACK,
|
|
76
|
+
CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
|
|
77
|
+
RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
78
|
+
RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
79
|
+
RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
80
|
+
RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
81
|
+
SMS_MUSTACHE_TAG_PATTERN,
|
|
73
82
|
} from './constants';
|
|
74
|
-
|
|
75
|
-
// Import utilities
|
|
76
83
|
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
84
|
+
import {
|
|
85
|
+
normalizePreviewApiPayload,
|
|
86
|
+
extractPreviewFromLiquidResponse,
|
|
87
|
+
getSmsFallbackTextForTagExtraction,
|
|
88
|
+
} from './previewApiUtils';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Drop empty GSM rows. RCS/DLT responses often set gsm_sender_id equal to domainName — keep those rows
|
|
92
|
+
* for RCS defaults (ModifyDeliverySettings uses the same rule for the sender dropdown).
|
|
93
|
+
*/
|
|
94
|
+
const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEchoFilter = false } = {}) => {
|
|
95
|
+
const normalizedDomainName =
|
|
96
|
+
domain?.domainName != null ? String(domain.domainName).trim().toLowerCase() : '';
|
|
97
|
+
return (gsmSenders || []).filter((gsmSenderRow) => {
|
|
98
|
+
const rawValue = gsmSenderRow?.value;
|
|
99
|
+
if (rawValue == null) return false;
|
|
100
|
+
const trimmedSenderValue = String(rawValue).trim();
|
|
101
|
+
if (!trimmedSenderValue) return false;
|
|
102
|
+
const senderMatchesDomainLabel =
|
|
103
|
+
normalizedDomainName && trimmedSenderValue.toLowerCase() === normalizedDomainName;
|
|
104
|
+
if (!skipDomainNameEchoFilter && senderMatchesDomainLabel) return false;
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** Preview payload from Redux may be an Immutable Map — normalize for React state. */
|
|
110
|
+
const toPlainPreviewData = (data) => {
|
|
111
|
+
if (data == null) return null;
|
|
112
|
+
const plain = typeof data.toJS === 'function' ? data.toJS() : data;
|
|
113
|
+
return normalizePreviewApiPayload(plain);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Merge existing customValues with tag keys from categorized groups.
|
|
118
|
+
* Each group is { required, optional } (arrays of tag objects with fullPath).
|
|
119
|
+
* Preserves existing values; adds '' for any tag key not yet present.
|
|
120
|
+
* Reusable for RCS+fallback and any flow that needs to ensure customValues has keys for tags.
|
|
121
|
+
*/
|
|
122
|
+
const mergeCustomValuesWithTagKeys = (prev, ...categorizedGroups) => {
|
|
123
|
+
const next = { ...(prev || {}) };
|
|
124
|
+
categorizedGroups.forEach((group) => {
|
|
125
|
+
[...(group.required || []), ...(group.optional || [])].forEach((tag) => {
|
|
126
|
+
const key = tag?.fullPath;
|
|
127
|
+
if (key && next[key] === undefined) next[key] = '';
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
return next;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/** True when `body` contains `{{name}}` mustache tokens (user-fillable personalization tags).
|
|
134
|
+
* DLT `{#name#}` slots are pre-bound template variables and are intentionally excluded. */
|
|
135
|
+
const smsTemplateHasMustacheTags = (body) =>
|
|
136
|
+
typeof body === 'string' && SMS_MUSTACHE_TAG_PATTERN.test(body);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Build tag rows from `{{…}}` mustache tokens only — DLT `{#…#}` slots are excluded because
|
|
140
|
+
* they are pre-bound template variables, not user-fillable personalization tags.
|
|
141
|
+
* Passing a mustache-only captureRegex to extractTemplateVariables skips the DLT branch.
|
|
142
|
+
* A non-global regex is used so ensureGlobalRegexForExecLoop creates a fresh instance on each call.
|
|
143
|
+
*/
|
|
144
|
+
const buildSyntheticSmsMustacheTags = (body = '') => {
|
|
145
|
+
if (!body || typeof body !== 'string') return [];
|
|
146
|
+
return extractTemplateVariables(body, /\{\{([^}]+)\}\}/).map((name) => ({
|
|
147
|
+
name,
|
|
148
|
+
metaData: { userDriven: false },
|
|
149
|
+
children: [],
|
|
150
|
+
}));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** RCS createMessageMeta: media shape (mediaUrl, thumbnailUrl, height string). */
|
|
154
|
+
const normalizeRcsTestCardMedia = (media) => {
|
|
155
|
+
if (!media || typeof media !== 'object') return undefined;
|
|
156
|
+
const mediaUrl =
|
|
157
|
+
media.mediaUrl != null && String(media.mediaUrl).trim() !== ''
|
|
158
|
+
? String(media.mediaUrl)
|
|
159
|
+
: media.url != null && String(media.url).trim() !== ''
|
|
160
|
+
? String(media.url)
|
|
161
|
+
: '';
|
|
162
|
+
const thumbnailUrl = media.thumbnailUrl != null ? String(media.thumbnailUrl) : '';
|
|
163
|
+
const height = media.height != null ? String(media.height) : undefined;
|
|
164
|
+
const out = { mediaUrl, thumbnailUrl };
|
|
165
|
+
if (height) out.height = height;
|
|
166
|
+
return out;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** RCS createMessageMeta: suggestion shape (index, type, text, phoneNumber, url, postback). */
|
|
170
|
+
const mapRcsSuggestionForTestMeta = (suggestionRow, index) => ({
|
|
171
|
+
index,
|
|
172
|
+
type: suggestionRow?.type ?? '',
|
|
173
|
+
text: suggestionRow?.text != null ? String(suggestionRow.text) : '',
|
|
174
|
+
phoneNumber:
|
|
175
|
+
suggestionRow?.phoneNumber != null
|
|
176
|
+
? String(suggestionRow.phoneNumber)
|
|
177
|
+
: suggestionRow?.phone_number != null
|
|
178
|
+
? String(suggestionRow.phone_number)
|
|
179
|
+
: '',
|
|
180
|
+
url: suggestionRow?.url !== undefined ? suggestionRow.url : null,
|
|
181
|
+
postback:
|
|
182
|
+
suggestionRow?.postback != null
|
|
183
|
+
? String(suggestionRow.postback)
|
|
184
|
+
: suggestionRow?.text != null
|
|
185
|
+
? String(suggestionRow.text)
|
|
186
|
+
: '',
|
|
187
|
+
});
|
|
77
188
|
|
|
78
189
|
/**
|
|
79
190
|
* Preview Component Factory - REMOVED IN PHASE 5
|
|
@@ -85,7 +196,7 @@ import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
|
85
196
|
*/
|
|
86
197
|
const CommonTestAndPreview = (props) => {
|
|
87
198
|
const {
|
|
88
|
-
intl: { formatMessage },
|
|
199
|
+
intl: { formatMessage, locale: userLocale = 'en' },
|
|
89
200
|
show,
|
|
90
201
|
onClose,
|
|
91
202
|
channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
|
|
@@ -121,19 +232,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
121
232
|
...additionalProps
|
|
122
233
|
} = props;
|
|
123
234
|
|
|
235
|
+
const smsFallbackContent = additionalProps?.smsFallbackContent;
|
|
236
|
+
const smsFallbackTextForTagExtraction = useMemo(
|
|
237
|
+
() => getSmsFallbackTextForTagExtraction(smsFallbackContent),
|
|
238
|
+
[smsFallbackContent],
|
|
239
|
+
);
|
|
124
240
|
// ============================================
|
|
125
241
|
// STATE MANAGEMENT
|
|
126
242
|
// ============================================
|
|
127
243
|
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
|
128
244
|
const [requiredTags, setRequiredTags] = useState([]);
|
|
129
245
|
const [optionalTags, setOptionalTags] = useState([]);
|
|
246
|
+
const [smsFallbackExtractedTags, setSmsFallbackExtractedTags] = useState([]);
|
|
247
|
+
const [smsFallbackRequiredTags, setSmsFallbackRequiredTags] = useState([]);
|
|
248
|
+
const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
|
|
249
|
+
const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
|
|
130
250
|
const [customValues, setCustomValues] = useState({});
|
|
131
251
|
const [showJSON, setShowJSON] = useState(false);
|
|
132
252
|
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const initialDevice = (channel === CHANNELS.SMS || channel === CHANNELS.WHATSAPP || channel === CHANNELS.RCS || channel === CHANNELS.INAPP || channel === CHANNELS.MOBILEPUSH || channel === CHANNELS.VIBER) ? ANDROID : DESKTOP;
|
|
253
|
+
|
|
254
|
+
const initialDevice = CHANNELS_USING_ANDROID_PREVIEW_DEVICE.includes(channel) ? ANDROID : DESKTOP;
|
|
136
255
|
const [previewDevice, setPreviewDevice] = useState(initialDevice);
|
|
256
|
+
const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
|
|
257
|
+
const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
|
|
137
258
|
// Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
|
|
138
259
|
const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
|
|
139
260
|
const [previewDataHtml, setPreviewDataHtml] = useState(() => {
|
|
@@ -166,15 +287,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
166
287
|
[CHANNELS.WHATSAPP]: {
|
|
167
288
|
domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
|
|
168
289
|
},
|
|
290
|
+
[CHANNELS.RCS]: {
|
|
291
|
+
domainId: null,
|
|
292
|
+
domainGatewayMapId: null,
|
|
293
|
+
gsmSenderId: '',
|
|
294
|
+
smsFallbackDomainId: null,
|
|
295
|
+
cdmaSenderId: '', // gsmSenderId = RCS sender (domainId|senderId), cdmaSenderId = SMS fallback
|
|
296
|
+
},
|
|
169
297
|
});
|
|
170
298
|
|
|
171
|
-
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
|
|
299
|
+
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
|
|
172
300
|
const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
|
|
173
301
|
const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
|
|
174
302
|
const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
|
|
175
303
|
const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
|
|
176
304
|
|
|
177
|
-
// Fetch sender details and WeCRM accounts when Test & Preview opens
|
|
305
|
+
// Fetch sender details and WeCRM accounts when Test & Preview opens (SMS, Email, WhatsApp, RCS — same process)
|
|
178
306
|
useEffect(() => {
|
|
179
307
|
if (!show || !channel) {
|
|
180
308
|
return;
|
|
@@ -183,6 +311,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
183
311
|
if (actions.getSenderDetailsRequested) {
|
|
184
312
|
actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
|
|
185
313
|
}
|
|
314
|
+
// SMS domains/senders are needed for RCS delivery UI (fallback row + slidebox) whenever RCS is open — not only when fallback body exists.
|
|
315
|
+
if (channel === CHANNELS.RCS && actions.getSenderDetailsRequested) {
|
|
316
|
+
actions.getSenderDetailsRequested({ channel: CHANNELS.SMS, orgUnitId: orgUnitId ?? -1 });
|
|
317
|
+
}
|
|
186
318
|
if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
|
|
187
319
|
actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
|
|
188
320
|
}
|
|
@@ -194,20 +326,84 @@ const CommonTestAndPreview = (props) => {
|
|
|
194
326
|
// Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
|
|
195
327
|
useEffect(() => {
|
|
196
328
|
if (!channel || !channelsWithDeliverySettings.includes(channel)) return;
|
|
329
|
+
|
|
330
|
+
if (channel === CHANNELS.RCS) {
|
|
331
|
+
const rcsDomainRows = senderDetailsByChannel?.[CHANNELS.RCS] || [];
|
|
332
|
+
const smsFallbackDomainRows = senderDetailsByChannel?.[CHANNELS.SMS] || [];
|
|
333
|
+
if (!rcsDomainRows.length) return;
|
|
334
|
+
|
|
335
|
+
const currentRcsDeliverySettings = testPreviewDeliverySettings?.[CHANNELS.RCS] || {};
|
|
336
|
+
const isRcsGsmSenderUnset = !currentRcsDeliverySettings?.gsmSenderId;
|
|
337
|
+
const isSmsFallbackSenderUnset = !currentRcsDeliverySettings?.cdmaSenderId;
|
|
338
|
+
const isRcsDeliveryFullyUnset = isRcsGsmSenderUnset && isSmsFallbackSenderUnset;
|
|
339
|
+
const shouldOnlyFillSmsFallbackSender =
|
|
340
|
+
!isRcsGsmSenderUnset && isSmsFallbackSenderUnset && smsFallbackDomainRows.length > 0;
|
|
341
|
+
|
|
342
|
+
if (!isRcsDeliveryFullyUnset && !shouldOnlyFillSmsFallbackSender) return;
|
|
343
|
+
|
|
344
|
+
const firstRcsDomain = rcsDomainRows[0];
|
|
345
|
+
const firstSmsFallbackDomain = smsFallbackDomainRows[0];
|
|
346
|
+
const usableRcsGsmSenders = filterUsableGsmSendersForDomain(
|
|
347
|
+
firstRcsDomain,
|
|
348
|
+
firstRcsDomain?.gsmSenders,
|
|
349
|
+
{ skipDomainNameEchoFilter: true },
|
|
350
|
+
);
|
|
351
|
+
const usableSmsFallbackGsmSenders = firstSmsFallbackDomain
|
|
352
|
+
? filterUsableGsmSendersForDomain(firstSmsFallbackDomain, firstSmsFallbackDomain?.gsmSenders)
|
|
353
|
+
: [];
|
|
354
|
+
const defaultRcsGsmSender = usableRcsGsmSenders[0];
|
|
355
|
+
const defaultSmsFallbackGsmSender = usableSmsFallbackGsmSenders[0];
|
|
356
|
+
const rcsSenderCompositeValue =
|
|
357
|
+
firstRcsDomain?.domainId != null && defaultRcsGsmSender?.value != null
|
|
358
|
+
? `${firstRcsDomain.domainId}|${defaultRcsGsmSender.value}`
|
|
359
|
+
: (defaultRcsGsmSender?.value || '');
|
|
360
|
+
const smsFallbackSenderCompositeValue =
|
|
361
|
+
firstSmsFallbackDomain?.domainId != null && defaultSmsFallbackGsmSender?.value != null
|
|
362
|
+
? `${firstSmsFallbackDomain.domainId}|${defaultSmsFallbackGsmSender.value}`
|
|
363
|
+
: (defaultSmsFallbackGsmSender?.value || '');
|
|
364
|
+
|
|
365
|
+
setTestPreviewDeliverySettings((prev) => {
|
|
366
|
+
const previousRcsSettings = prev?.[CHANNELS.RCS] || {};
|
|
367
|
+
if (shouldOnlyFillSmsFallbackSender) {
|
|
368
|
+
if (!smsFallbackSenderCompositeValue) return prev;
|
|
369
|
+
return {
|
|
370
|
+
...prev,
|
|
371
|
+
[CHANNELS.RCS]: {
|
|
372
|
+
...previousRcsSettings,
|
|
373
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
374
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
...prev,
|
|
380
|
+
[CHANNELS.RCS]: {
|
|
381
|
+
domainId: firstRcsDomain?.domainId ?? null,
|
|
382
|
+
domainGatewayMapId: firstRcsDomain?.dgmId ?? null,
|
|
383
|
+
gsmSenderId: rcsSenderCompositeValue,
|
|
384
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
385
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
197
392
|
const domains = senderDetailsByChannel[channel];
|
|
198
393
|
if (!domains || domains.length === 0) return;
|
|
199
394
|
const {
|
|
200
|
-
domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
|
|
395
|
+
domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
|
|
201
396
|
} = testPreviewDeliverySettings[channel] || {};
|
|
202
|
-
const isEmptySelection = !domainId && !gsmSenderId && !senderEmail && !senderMobNum;
|
|
397
|
+
const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
|
|
203
398
|
if (!isEmptySelection) return;
|
|
204
399
|
|
|
205
400
|
const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
|
|
206
401
|
const matchedWhatsappAccount = whatsappAccountFromForm
|
|
207
402
|
? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
|
|
208
403
|
: null;
|
|
209
|
-
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled
|
|
210
|
-
? domains.filter((domain) => (domain
|
|
404
|
+
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
|
|
405
|
+
? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
|
|
406
|
+
registeredSenderIds?.includes(gsm?.value)))
|
|
211
407
|
: domains;
|
|
212
408
|
const [defaultDomain] = domains;
|
|
213
409
|
const [firstSmsDomain] = smsDomains;
|
|
@@ -222,8 +418,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
222
418
|
const next = { ...prev };
|
|
223
419
|
if (channel === CHANNELS.SMS) {
|
|
224
420
|
const smsGsmSenders = smsTraiDltEnabled
|
|
225
|
-
? (firstDomain
|
|
226
|
-
: firstDomain
|
|
421
|
+
? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
|
|
422
|
+
: firstDomain?.gsmSenders;
|
|
227
423
|
next[channel] = {
|
|
228
424
|
domainId: firstDomain.domainId,
|
|
229
425
|
domainGatewayMapId: firstDomain.dgmId,
|
|
@@ -256,10 +452,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
256
452
|
// MEMOIZED VALUES
|
|
257
453
|
// ============================================
|
|
258
454
|
|
|
455
|
+
const allTags = useMemo(
|
|
456
|
+
() => [...requiredTags, ...optionalTags, ...smsFallbackRequiredTags, ...smsFallbackOptionalTags],
|
|
457
|
+
[requiredTags, optionalTags, smsFallbackRequiredTags, smsFallbackOptionalTags]
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const allRequiredTags = useMemo(
|
|
461
|
+
() => [...requiredTags, ...smsFallbackRequiredTags],
|
|
462
|
+
[requiredTags, smsFallbackRequiredTags]
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const buildEmptyValues = useCallback(
|
|
466
|
+
() => allTags.reduce((acc, tag) => {
|
|
467
|
+
const key = tag?.fullPath;
|
|
468
|
+
if (key) acc[key] = '';
|
|
469
|
+
return acc;
|
|
470
|
+
}, {}),
|
|
471
|
+
[allTags]
|
|
472
|
+
);
|
|
473
|
+
|
|
259
474
|
// Check if update preview button should be disabled
|
|
260
475
|
const isUpdatePreviewDisabled = useMemo(() => (
|
|
261
|
-
|
|
262
|
-
), [
|
|
476
|
+
allRequiredTags.some((tag) => !customValues[tag.fullPath])
|
|
477
|
+
), [allRequiredTags, customValues]);
|
|
263
478
|
|
|
264
479
|
// Get current content based on channel and editor type
|
|
265
480
|
const getCurrentContent = useMemo(() => {
|
|
@@ -303,6 +518,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
303
518
|
return currentTabData.base['sms-editor'];
|
|
304
519
|
}
|
|
305
520
|
}
|
|
521
|
+
// DLT / Test & Preview shape: { templateConfigs: { template, templateId, ... } }
|
|
522
|
+
if (formData.templateConfigs?.template) {
|
|
523
|
+
const smsDltTemplateValue = formData.templateConfigs.template;
|
|
524
|
+
if (typeof smsDltTemplateValue === 'string') return smsDltTemplateValue;
|
|
525
|
+
if (Array.isArray(smsDltTemplateValue)) return smsDltTemplateValue.join('');
|
|
526
|
+
return '';
|
|
527
|
+
}
|
|
306
528
|
}
|
|
307
529
|
|
|
308
530
|
// SMS channel fallback - if formData is not available, use content directly
|
|
@@ -348,7 +570,70 @@ const CommonTestAndPreview = (props) => {
|
|
|
348
570
|
return content || '';
|
|
349
571
|
}, [channel, formData, currentTab, beeContent, content, beeInstance]);
|
|
350
572
|
|
|
351
|
-
|
|
573
|
+
const leftPanelExtractedTags = useMemo(() => {
|
|
574
|
+
if (channel === CHANNELS.SMS) {
|
|
575
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
576
|
+
if (!smsTemplateHasMustacheTags(smsEditorBody)) return [];
|
|
577
|
+
const extractTagsFromApi = extractedTags ?? [];
|
|
578
|
+
if (extractTagsFromApi.length > 0) return extractTagsFromApi;
|
|
579
|
+
return buildSyntheticSmsMustacheTags(smsEditorBody);
|
|
580
|
+
}
|
|
581
|
+
const hasFallbackSmsBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
582
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsBody) {
|
|
583
|
+
const rcsPrimaryTags = extractedTags ?? [];
|
|
584
|
+
const fallbackSmsTextForTags = smsFallbackTextForTagExtraction ?? '';
|
|
585
|
+
const fallbackSmsTagRows = smsTemplateHasMustacheTags(fallbackSmsTextForTags)
|
|
586
|
+
? (smsFallbackExtractedTags?.length > 0
|
|
587
|
+
? smsFallbackExtractedTags
|
|
588
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsTextForTags))
|
|
589
|
+
: [];
|
|
590
|
+
const mergedRcsAndFallbackTags = [...rcsPrimaryTags, ...fallbackSmsTagRows];
|
|
591
|
+
if (mergedRcsAndFallbackTags.length > 0) return mergedRcsAndFallbackTags;
|
|
592
|
+
return buildSyntheticSmsMustacheTags(fallbackSmsTextForTags);
|
|
593
|
+
}
|
|
594
|
+
return extractedTags ?? [];
|
|
595
|
+
}, [
|
|
596
|
+
channel,
|
|
597
|
+
extractedTags,
|
|
598
|
+
getCurrentContent,
|
|
599
|
+
smsFallbackContent,
|
|
600
|
+
smsFallbackExtractedTags,
|
|
601
|
+
smsFallbackTextForTagExtraction,
|
|
602
|
+
]);
|
|
603
|
+
|
|
604
|
+
const isRcsSmsFallbackPreviewEnabled =
|
|
605
|
+
channel === CHANNELS.RCS
|
|
606
|
+
&& !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
607
|
+
// Only treat as SMS when user is on the Fallback SMS tab — not whenever fallback exists (RCS tab needs RCS preview API).
|
|
608
|
+
const isSmsFallbackTabActive = isRcsSmsFallbackPreviewEnabled && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK;
|
|
609
|
+
const activeChannelForActions = isSmsFallbackTabActive ? CHANNELS.SMS : channel;
|
|
610
|
+
// VarSegment slot values live in rcsSmsFallbackVarMapped; raw templateContent alone is stale for /preview Body.
|
|
611
|
+
const resolvedSmsFallbackBodyForPreviewTab =
|
|
612
|
+
smsFallbackTextForTagExtraction
|
|
613
|
+
|| smsFallbackContent?.templateContent
|
|
614
|
+
|| smsFallbackContent?.content
|
|
615
|
+
|| '';
|
|
616
|
+
const activeContentForActions = isSmsFallbackTabActive
|
|
617
|
+
? resolvedSmsFallbackBodyForPreviewTab
|
|
618
|
+
: getCurrentContent;
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* SMS fallback pane must show /preview API result when user updated preview on that tab (plain text).
|
|
622
|
+
* Skip when resolvedBody is RCS-shaped (e.g. user last previewed on RCS tab).
|
|
623
|
+
*/
|
|
624
|
+
// smsFallbackPreviewText is the single source of truth for the resolved SMS fallback preview.
|
|
625
|
+
// It is set only by syncSmsFallbackPreview (called from handleUpdatePreview and the
|
|
626
|
+
// prefilled-values effect) and reset to undefined on discard / slidebox close.
|
|
627
|
+
// Using previewDataHtml as a fallback is unsafe because that state is shared with the primary
|
|
628
|
+
// RCS preview and can contain stale SMS or RCS content.
|
|
629
|
+
const smsFallbackResolvedText = useMemo(() => {
|
|
630
|
+
const hasFallbackBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
631
|
+
if (channel !== CHANNELS.RCS || !hasFallbackBody) return undefined;
|
|
632
|
+
if (smsFallbackPreviewText != null) return smsFallbackPreviewText;
|
|
633
|
+
return undefined;
|
|
634
|
+
}, [channel, smsFallbackContent, smsFallbackPreviewText]);
|
|
635
|
+
|
|
636
|
+
// Build test entities tree data from testCustomers prop
|
|
352
637
|
const testEntitiesTreeData = useMemo(() => {
|
|
353
638
|
const groupsNode = {
|
|
354
639
|
title: 'Groups',
|
|
@@ -460,6 +745,37 @@ const CommonTestAndPreview = (props) => {
|
|
|
460
745
|
}
|
|
461
746
|
};
|
|
462
747
|
|
|
748
|
+
/**
|
|
749
|
+
* When RCS has SMS fallback, refresh fallback preview text via the same Liquid /preview API
|
|
750
|
+
* (separate call with SMS channel + fallback template body). Used after primary preview updates.
|
|
751
|
+
*/
|
|
752
|
+
const syncSmsFallbackPreview = async (customValuesForResolve, selectedCustomerObj) => {
|
|
753
|
+
const fallbackBodyForLiquidPreview =
|
|
754
|
+
getSmsFallbackTextForTagExtraction(smsFallbackContent)
|
|
755
|
+
|| smsFallbackContent?.templateContent
|
|
756
|
+
|| smsFallbackContent?.content
|
|
757
|
+
|| '';
|
|
758
|
+
if (channel !== CHANNELS.RCS || !String(fallbackBodyForLiquidPreview).trim()) return;
|
|
759
|
+
try {
|
|
760
|
+
const smsFallbackPayload = preparePreviewPayload(
|
|
761
|
+
CHANNELS.SMS,
|
|
762
|
+
formData || {},
|
|
763
|
+
fallbackBodyForLiquidPreview,
|
|
764
|
+
customValuesForResolve,
|
|
765
|
+
selectedCustomerObj
|
|
766
|
+
);
|
|
767
|
+
const fallbackResponse = await Api.updateEmailPreview(smsFallbackPayload);
|
|
768
|
+
const fallbackPreview = extractPreviewFromLiquidResponse(fallbackResponse);
|
|
769
|
+
setSmsFallbackPreviewText(
|
|
770
|
+
typeof fallbackPreview?.resolvedBody === 'string'
|
|
771
|
+
? fallbackPreview.resolvedBody
|
|
772
|
+
: undefined
|
|
773
|
+
);
|
|
774
|
+
} catch (e) {
|
|
775
|
+
/* keep existing smsFallbackPreviewText on failure */
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
|
|
463
779
|
/**
|
|
464
780
|
* Prepare payload for tag extraction based on channel
|
|
465
781
|
*/
|
|
@@ -554,7 +870,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
554
870
|
} = carousel || {};
|
|
555
871
|
const buttonData = buttons.map((button, index) => {
|
|
556
872
|
const {
|
|
557
|
-
type, text, phone_number, urlType, url,
|
|
873
|
+
type, text, phone_number: phoneNumber, urlType, url,
|
|
558
874
|
} = button || {};
|
|
559
875
|
const buttonObj = {
|
|
560
876
|
type,
|
|
@@ -562,7 +878,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
562
878
|
index,
|
|
563
879
|
};
|
|
564
880
|
if (type === PHONE_NUMBER) {
|
|
565
|
-
buttonObj.phoneNumber =
|
|
881
|
+
buttonObj.phoneNumber = phoneNumber;
|
|
566
882
|
}
|
|
567
883
|
if (type === URL) {
|
|
568
884
|
buttonObj.url = url;
|
|
@@ -591,7 +907,131 @@ const CommonTestAndPreview = (props) => {
|
|
|
591
907
|
};
|
|
592
908
|
});
|
|
593
909
|
|
|
594
|
-
|
|
910
|
+
/**
|
|
911
|
+
* Build createMessageMeta payload for RCS (test message).
|
|
912
|
+
* rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
|
|
913
|
+
* Then rcsDeliverySettings, executionParams, clientName last.
|
|
914
|
+
*/
|
|
915
|
+
const buildRcsTestMessagePayload = (formDataObj, _contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra = {}) => {
|
|
916
|
+
const plainCustom =
|
|
917
|
+
customValuesObj != null && typeof customValuesObj.toJS === 'function'
|
|
918
|
+
? customValuesObj.toJS()
|
|
919
|
+
: (customValuesObj || {});
|
|
920
|
+
const userVarMap = Object.fromEntries(
|
|
921
|
+
Object.entries(plainCustom).filter(([, v]) => v != null && String(v).trim() !== '')
|
|
922
|
+
);
|
|
923
|
+
const rcsData = formDataObj?.versions?.base?.content?.RCS ?? formDataObj?.content?.RCS ?? {};
|
|
924
|
+
const rcsContent = rcsData?.rcsContent || {};
|
|
925
|
+
const smsFallback = rcsData?.smsFallBackContent || {};
|
|
926
|
+
let cardContentList = [];
|
|
927
|
+
if (Array.isArray(rcsContent?.cardContent)) {
|
|
928
|
+
cardContentList = rcsContent.cardContent;
|
|
929
|
+
} else if (rcsContent?.cardContent) {
|
|
930
|
+
cardContentList = [rcsContent.cardContent];
|
|
931
|
+
}
|
|
932
|
+
// Merge test customValues into cardVarMapped (template snapshot + user-entered RCS + fallback SMS tags).
|
|
933
|
+
const cardContent = cardContentList.map((rcsCard) => {
|
|
934
|
+
const baseMap = rcsCard.cardVarMapped || {};
|
|
935
|
+
const mergedCardVarMapped =
|
|
936
|
+
Object.keys(userVarMap).length > 0 ? { ...baseMap, ...userVarMap } : baseMap;
|
|
937
|
+
const mediaNorm = rcsCard?.media ? normalizeRcsTestCardMedia(rcsCard.media) : undefined;
|
|
938
|
+
const suggestionsRaw = Array.isArray(rcsCard?.suggestions) ? rcsCard.suggestions : [];
|
|
939
|
+
const suggestionsMapped = suggestionsRaw.map((suggestionItem, i) =>
|
|
940
|
+
mapRcsSuggestionForTestMeta(suggestionItem, i));
|
|
941
|
+
return {
|
|
942
|
+
title: rcsCard?.title ?? '',
|
|
943
|
+
description: rcsCard?.description ?? '',
|
|
944
|
+
mediaType: rcsCard?.mediaType ?? MEDIA_TYPE_TEXT,
|
|
945
|
+
...(mediaNorm && { media: mediaNorm }),
|
|
946
|
+
...(Object.keys(mergedCardVarMapped).length > 0 && { cardVarMapped: mergedCardVarMapped }),
|
|
947
|
+
...(suggestionsMapped.length > 0 && { suggestions: suggestionsMapped }),
|
|
948
|
+
};
|
|
949
|
+
});
|
|
950
|
+
// Prefer parent `smsFallbackContent` snapshot (rcsExtra) — includes VarSegment-resolved body via
|
|
951
|
+
// getSmsFallbackTextForTagExtraction. Nested formData.smsFallBackContent is often stale vs live editor.
|
|
952
|
+
const rcsExtraFallbackTemplate = rcsExtra?.smsFallbackTemplateContent;
|
|
953
|
+
const hasResolvedFallbackBodyFromRcsExtra =
|
|
954
|
+
rcsExtraFallbackTemplate != null
|
|
955
|
+
&& String(rcsExtraFallbackTemplate).trim() !== '';
|
|
956
|
+
const smsMessageRaw = hasResolvedFallbackBodyFromRcsExtra
|
|
957
|
+
? String(rcsExtraFallbackTemplate)
|
|
958
|
+
: (smsFallback?.smsContent ?? smsFallback?.message ?? '');
|
|
959
|
+
const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
|
|
960
|
+
? deliverySettingsOverride.cdmaSenderId.split('|')[1]
|
|
961
|
+
: deliverySettingsOverride?.cdmaSenderId;
|
|
962
|
+
const deliveryFallbackSmsId =
|
|
963
|
+
typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
|
|
964
|
+
const creativeFallbackSmsId =
|
|
965
|
+
smsFallback?.senderId != null ? String(smsFallback.senderId).trim() : '';
|
|
966
|
+
const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
|
|
967
|
+
|
|
968
|
+
const smsFallBackContent =
|
|
969
|
+
smsMessageRaw.trim() !== ''
|
|
970
|
+
? { message: smsMessageRaw }
|
|
971
|
+
: undefined;
|
|
972
|
+
|
|
973
|
+
// accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
|
|
974
|
+
const accountIdForMeta =
|
|
975
|
+
rcsContent?.accountId != null && String(rcsContent.accountId).trim() !== ''
|
|
976
|
+
? String(rcsContent.accountId)
|
|
977
|
+
: undefined;
|
|
978
|
+
|
|
979
|
+
const rcsRichCardContent = {
|
|
980
|
+
contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
981
|
+
cardType: rcsContent?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
982
|
+
cardSettings: rcsContent?.cardSettings ?? {
|
|
983
|
+
cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
984
|
+
cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
985
|
+
},
|
|
986
|
+
...(cardContent.length > 0 && { cardContent }),
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
const rcsMessageContent = {
|
|
990
|
+
channel: CHANNELS.RCS,
|
|
991
|
+
...(accountIdForMeta && { accountId: accountIdForMeta }),
|
|
992
|
+
rcsRichCardContent,
|
|
993
|
+
...(smsFallBackContent && { smsFallBackContent }),
|
|
994
|
+
};
|
|
995
|
+
const rcsComposite = deliverySettingsOverride?.gsmSenderId ?? '';
|
|
996
|
+
const [rcsDomainId, rcsSenderId] = rcsComposite.includes('|') ? rcsComposite.split('|') : ['', rcsComposite];
|
|
997
|
+
const rcsDeliverySettings = {
|
|
998
|
+
channelSettings: {
|
|
999
|
+
channel: CHANNELS.RCS,
|
|
1000
|
+
rcsSender: (rcsSenderId || deliverySettingsOverride?.rcsSender) ?? '',
|
|
1001
|
+
domainId:
|
|
1002
|
+
rcsDomainId !== '' && rcsDomainId !== undefined && !Number.isNaN(Number(rcsDomainId))
|
|
1003
|
+
? Number(rcsDomainId)
|
|
1004
|
+
: (deliverySettingsOverride?.domainId ?? 0),
|
|
1005
|
+
fallbackSmsSenderId: fallbackSmsSenderIdForChannel,
|
|
1006
|
+
},
|
|
1007
|
+
additionalSettings: {
|
|
1008
|
+
useTinyUrl: false,
|
|
1009
|
+
encryptUrl: false,
|
|
1010
|
+
linkTrackingEnabled: false,
|
|
1011
|
+
bypassControlUser: false,
|
|
1012
|
+
userSubscriptionDisabled: false,
|
|
1013
|
+
},
|
|
1014
|
+
};
|
|
1015
|
+
const { clientName: baseClientName = CLIENT_NAME_CREATIVES, ...restBase } = basePayload;
|
|
1016
|
+
return {
|
|
1017
|
+
...restBase,
|
|
1018
|
+
rcsMessageContent,
|
|
1019
|
+
rcsDeliverySettings,
|
|
1020
|
+
executionParams: {},
|
|
1021
|
+
clientName: baseClientName,
|
|
1022
|
+
};
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const prepareTestMessagePayload = (
|
|
1026
|
+
channelType,
|
|
1027
|
+
formDataObj,
|
|
1028
|
+
contentStr,
|
|
1029
|
+
customValuesObj,
|
|
1030
|
+
recipientDetails,
|
|
1031
|
+
previewDataObj,
|
|
1032
|
+
deliverySettingsOverride,
|
|
1033
|
+
rcsExtra = {},
|
|
1034
|
+
) => {
|
|
595
1035
|
// Base payload structure common to all channels
|
|
596
1036
|
const basePayload = {
|
|
597
1037
|
ouId: -1,
|
|
@@ -672,12 +1112,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
672
1112
|
},
|
|
673
1113
|
smsDeliverySettings: {
|
|
674
1114
|
channelSettings: {
|
|
1115
|
+
channel: CHANNELS.SMS,
|
|
675
1116
|
gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
|
|
676
1117
|
domainId: deliverySettingsOverride?.domainId ?? null,
|
|
677
1118
|
domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
|
|
678
1119
|
targetNdnc: false,
|
|
679
1120
|
cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
|
|
680
|
-
channel: CHANNELS.SMS,
|
|
681
1121
|
},
|
|
682
1122
|
additionalSettings: {
|
|
683
1123
|
useTinyUrl: false,
|
|
@@ -821,7 +1261,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
821
1261
|
return {
|
|
822
1262
|
...basePayload,
|
|
823
1263
|
whatsappMessageContent: {
|
|
824
|
-
messageBody: templateEditorValue || '',
|
|
1264
|
+
messageBody: resolvedMessageBody || templateEditorValue || '',
|
|
825
1265
|
accountId: formDataObj?.accountId || '',
|
|
826
1266
|
sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
|
|
827
1267
|
accountName: formDataObj?.accountName || '',
|
|
@@ -846,16 +1286,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
846
1286
|
}
|
|
847
1287
|
|
|
848
1288
|
case CHANNELS.RCS:
|
|
849
|
-
return
|
|
850
|
-
...basePayload,
|
|
851
|
-
rcsMessageContent: {
|
|
852
|
-
channel: CHANNELS.RCS,
|
|
853
|
-
messageBody: contentStr,
|
|
854
|
-
rcsType: additionalProps?.rcsType,
|
|
855
|
-
rcsImageSrc: formDataObj?.rcsImageSrc,
|
|
856
|
-
rcsSuggestions: formDataObj?.rcsSuggestions,
|
|
857
|
-
},
|
|
858
|
-
};
|
|
1289
|
+
return buildRcsTestMessagePayload(formDataObj, contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra);
|
|
859
1290
|
|
|
860
1291
|
case CHANNELS.INAPP: {
|
|
861
1292
|
// InApp payload structure similar to MobilePush
|
|
@@ -1957,6 +2388,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
1957
2388
|
formatMessage,
|
|
1958
2389
|
lastModified: formData?.lastModified,
|
|
1959
2390
|
updatedByName: formData?.updatedByName,
|
|
2391
|
+
smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
|
|
2392
|
+
smsFallbackResolvedText,
|
|
2393
|
+
activePreviewTab,
|
|
2394
|
+
onPreviewTabChange: setActivePreviewTab,
|
|
1960
2395
|
};
|
|
1961
2396
|
};
|
|
1962
2397
|
|
|
@@ -1996,7 +2431,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
1996
2431
|
}, [show, beeInstance, currentTab, channel]);
|
|
1997
2432
|
|
|
1998
2433
|
/**
|
|
1999
|
-
* Initial data load when slidebox opens
|
|
2434
|
+
* Initial data load when slidebox opens.
|
|
2435
|
+
* EXTRACT TAGS CALL SITES (on open/edit RCS):
|
|
2436
|
+
* 1. Here (non-email): actions.extractTagsRequested() at line ~2161 when show is true.
|
|
2437
|
+
* 2. RCS SMS fallback useEffect below: Api.extractTagsWithMetaData() for fallback message.
|
|
2438
|
+
* 3. handleExtractTags() (user clicks "Enter custom values for tags") – not from effects.
|
|
2439
|
+
* The "Process extracted tags" effect only processes API results and must not call extract again.
|
|
2000
2440
|
*/
|
|
2001
2441
|
useEffect(() => {
|
|
2002
2442
|
if (show) {
|
|
@@ -2081,7 +2521,61 @@ const CommonTestAndPreview = (props) => {
|
|
|
2081
2521
|
actions.getTestGroupsRequested();
|
|
2082
2522
|
}
|
|
2083
2523
|
}
|
|
2084
|
-
|
|
2524
|
+
// getCurrentContent: RCS applies cardVarMapped → placeholder resolution; re-extract when it changes.
|
|
2525
|
+
}, [show, beeInstance, currentTab, channel, getCurrentContent]);
|
|
2526
|
+
|
|
2527
|
+
/**
|
|
2528
|
+
* RCS with SMS fallback: extract tags for fallback SMS content as well
|
|
2529
|
+
* (so we can show a separate "Fallback SMS tags" section in left panel).
|
|
2530
|
+
*/
|
|
2531
|
+
useEffect(() => {
|
|
2532
|
+
let cancelled = false;
|
|
2533
|
+
|
|
2534
|
+
if (!show || channel !== CHANNELS.RCS) {
|
|
2535
|
+
return () => {
|
|
2536
|
+
cancelled = true;
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
if (!smsFallbackContent?.templateContent && !smsFallbackContent?.content) {
|
|
2541
|
+
setSmsFallbackExtractedTags([]);
|
|
2542
|
+
setSmsFallbackRequiredTags([]);
|
|
2543
|
+
setSmsFallbackOptionalTags([]);
|
|
2544
|
+
return () => {
|
|
2545
|
+
cancelled = true;
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
setIsExtractingSmsFallbackTags(true);
|
|
2550
|
+
(async () => {
|
|
2551
|
+
try {
|
|
2552
|
+
const fallbackBodyForExtractApi = getSmsFallbackTextForTagExtraction(smsFallbackContent);
|
|
2553
|
+
const payload = {
|
|
2554
|
+
messageTitle: '',
|
|
2555
|
+
messageBody: fallbackBodyForExtractApi || '',
|
|
2556
|
+
};
|
|
2557
|
+
const response = await Api.extractTagsWithMetaData(payload); //not using saga action here because we dont store fallbacksms related data in store but only in useState since this is only used in RCS SMS fallback
|
|
2558
|
+
let smsFallbackTagTree = response?.data ?? [];
|
|
2559
|
+
if (!Array.isArray(smsFallbackTagTree)) smsFallbackTagTree = [];
|
|
2560
|
+
if (!smsTemplateHasMustacheTags(fallbackBodyForExtractApi)) {
|
|
2561
|
+
smsFallbackTagTree = [];
|
|
2562
|
+
} else if (smsFallbackTagTree.length === 0) {
|
|
2563
|
+
smsFallbackTagTree = buildSyntheticSmsMustacheTags(fallbackBodyForExtractApi);
|
|
2564
|
+
}
|
|
2565
|
+
if (cancelled) return;
|
|
2566
|
+
setSmsFallbackExtractedTags(smsFallbackTagTree);
|
|
2567
|
+
} catch (e) {
|
|
2568
|
+
if (cancelled) return;
|
|
2569
|
+
setSmsFallbackExtractedTags([]);
|
|
2570
|
+
} finally {
|
|
2571
|
+
if (!cancelled) setIsExtractingSmsFallbackTags(false);
|
|
2572
|
+
}
|
|
2573
|
+
})();
|
|
2574
|
+
|
|
2575
|
+
return () => {
|
|
2576
|
+
cancelled = true;
|
|
2577
|
+
};
|
|
2578
|
+
}, [show, channel, smsFallbackContent]);
|
|
2085
2579
|
|
|
2086
2580
|
/**
|
|
2087
2581
|
* Email-specific: Handle content updates for both BEE and CKEditor
|
|
@@ -2133,15 +2627,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
2133
2627
|
setSelectedCustomer(null);
|
|
2134
2628
|
setRequiredTags([]);
|
|
2135
2629
|
setOptionalTags([]);
|
|
2630
|
+
setSmsFallbackExtractedTags([]);
|
|
2631
|
+
setSmsFallbackRequiredTags([]);
|
|
2632
|
+
setSmsFallbackOptionalTags([]);
|
|
2633
|
+
setIsExtractingSmsFallbackTags(false);
|
|
2136
2634
|
setCustomValues({});
|
|
2137
2635
|
setShowJSON(false);
|
|
2138
2636
|
setTagsExtracted(false);
|
|
2139
2637
|
setPreviewDevice(DESKTOP);
|
|
2638
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2639
|
+
setSmsFallbackPreviewText(undefined);
|
|
2140
2640
|
setSelectedTestEntities([]);
|
|
2141
2641
|
actions.clearPrefilledValues();
|
|
2142
2642
|
} else {
|
|
2143
2643
|
// Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
|
|
2144
2644
|
setPreviewDevice(initialDevice);
|
|
2645
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2145
2646
|
}
|
|
2146
2647
|
}, [show, initialDevice]);
|
|
2147
2648
|
|
|
@@ -2150,79 +2651,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2150
2651
|
*/
|
|
2151
2652
|
useEffect(() => {
|
|
2152
2653
|
if (previewData) {
|
|
2153
|
-
setPreviewDataHtml(previewData);
|
|
2654
|
+
setPreviewDataHtml(toPlainPreviewData(previewData));
|
|
2154
2655
|
}
|
|
2155
2656
|
}, [previewData]);
|
|
2156
2657
|
|
|
2157
|
-
/**
|
|
2158
|
-
* Process extracted tags and categorize them
|
|
2159
|
-
*/
|
|
2160
|
-
useEffect(() => {
|
|
2161
|
-
// Categorize tags into required and optional
|
|
2162
|
-
const required = [];
|
|
2163
|
-
const optional = [];
|
|
2164
|
-
let hasPersonalizationTags = false;
|
|
2165
|
-
|
|
2166
|
-
if (extractedTags?.length > 0) {
|
|
2167
|
-
const processTag = (tag, parentPath = '') => {
|
|
2168
|
-
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
2169
|
-
|
|
2170
|
-
// Skip unsubscribe tag for input fields
|
|
2171
|
-
if (tag?.name === UNSUBSCRIBE_TAG_NAME) {
|
|
2172
|
-
return;
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
hasPersonalizationTags = true;
|
|
2176
|
-
|
|
2177
|
-
if (tag?.metaData?.userDriven === false) {
|
|
2178
|
-
required.push({
|
|
2179
|
-
...tag,
|
|
2180
|
-
fullPath: currentPath,
|
|
2181
|
-
});
|
|
2182
|
-
} else if (tag?.metaData?.userDriven === true) {
|
|
2183
|
-
optional.push({
|
|
2184
|
-
...tag,
|
|
2185
|
-
fullPath: currentPath,
|
|
2186
|
-
});
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
|
-
if (tag?.children?.length > 0) {
|
|
2190
|
-
tag.children.forEach((child) => processTag(child, currentPath));
|
|
2191
|
-
}
|
|
2192
|
-
};
|
|
2193
|
-
|
|
2194
|
-
extractedTags.forEach((tag) => processTag(tag));
|
|
2195
|
-
|
|
2196
|
-
if (hasPersonalizationTags) {
|
|
2197
|
-
setRequiredTags(required);
|
|
2198
|
-
setOptionalTags(optional);
|
|
2199
|
-
setTagsExtracted(true); // Mark tags as extracted and processed
|
|
2200
|
-
|
|
2201
|
-
// Initialize custom values for required tags
|
|
2202
|
-
const initialValues = {};
|
|
2203
|
-
required.forEach((tag) => {
|
|
2204
|
-
initialValues[tag?.fullPath] = '';
|
|
2205
|
-
});
|
|
2206
|
-
optional.forEach((tag) => {
|
|
2207
|
-
initialValues[tag?.fullPath] = '';
|
|
2208
|
-
});
|
|
2209
|
-
setCustomValues(initialValues);
|
|
2210
|
-
} else {
|
|
2211
|
-
// Reset all tag-related state if no personalization tags
|
|
2212
|
-
setRequiredTags([]);
|
|
2213
|
-
setOptionalTags([]);
|
|
2214
|
-
setCustomValues({});
|
|
2215
|
-
setTagsExtracted(false);
|
|
2216
|
-
}
|
|
2217
|
-
} else {
|
|
2218
|
-
// Reset all tag-related state if no tags
|
|
2219
|
-
setRequiredTags([]);
|
|
2220
|
-
setOptionalTags([]);
|
|
2221
|
-
setCustomValues({});
|
|
2222
|
-
setTagsExtracted(false);
|
|
2223
|
-
}
|
|
2224
|
-
}, [extractedTags]);
|
|
2225
|
-
|
|
2226
2658
|
/**
|
|
2227
2659
|
* Handle customer selection and fetch prefilled values
|
|
2228
2660
|
*/
|
|
@@ -2230,17 +2662,15 @@ const CommonTestAndPreview = (props) => {
|
|
|
2230
2662
|
if (selectedCustomer && config.enableCustomerSearch !== false) {
|
|
2231
2663
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2232
2664
|
|
|
2233
|
-
// Get all available tags
|
|
2234
|
-
const allTags = [...requiredTags, ...optionalTags];
|
|
2235
2665
|
const requiredTagObj = {};
|
|
2236
|
-
|
|
2666
|
+
allRequiredTags.forEach((tag) => {
|
|
2237
2667
|
requiredTagObj[tag?.fullPath] = '';
|
|
2238
2668
|
});
|
|
2239
2669
|
if (allTags.length > 0) {
|
|
2240
2670
|
const payload = preparePreviewPayload(
|
|
2241
|
-
|
|
2671
|
+
activeChannelForActions,
|
|
2242
2672
|
formData || {},
|
|
2243
|
-
|
|
2673
|
+
activeContentForActions,
|
|
2244
2674
|
{
|
|
2245
2675
|
...requiredTagObj,
|
|
2246
2676
|
},
|
|
@@ -2249,7 +2679,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2249
2679
|
actions.getPrefilledValuesRequested(payload);
|
|
2250
2680
|
}
|
|
2251
2681
|
}
|
|
2252
|
-
}, [selectedCustomer]);
|
|
2682
|
+
}, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
|
|
2253
2683
|
|
|
2254
2684
|
/**
|
|
2255
2685
|
* Update custom values with prefilled values from API
|
|
@@ -2260,7 +2690,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2260
2690
|
if (prefilledValues && selectedCustomer) {
|
|
2261
2691
|
// Always replace all values with prefilled values
|
|
2262
2692
|
const updatedValues = {};
|
|
2263
|
-
|
|
2693
|
+
allTags.forEach((tag) => {
|
|
2264
2694
|
updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
|
|
2265
2695
|
});
|
|
2266
2696
|
|
|
@@ -2268,16 +2698,17 @@ const CommonTestAndPreview = (props) => {
|
|
|
2268
2698
|
|
|
2269
2699
|
// Update preview with prefilled values (this is a valid preview call trigger)
|
|
2270
2700
|
const payload = preparePreviewPayload(
|
|
2271
|
-
|
|
2701
|
+
activeChannelForActions,
|
|
2272
2702
|
formData || {},
|
|
2273
|
-
|
|
2703
|
+
activeContentForActions,
|
|
2274
2704
|
updatedValues,
|
|
2275
2705
|
selectedCustomer
|
|
2276
2706
|
);
|
|
2277
2707
|
actions.updatePreviewRequested(payload);
|
|
2278
2708
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2709
|
+
void syncSmsFallbackPreview(updatedValues, selectedCustomer);
|
|
2279
2710
|
}
|
|
2280
|
-
}, [JSON.stringify(prefilledValues), selectedCustomer]);
|
|
2711
|
+
}, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
|
|
2281
2712
|
|
|
2282
2713
|
/**
|
|
2283
2714
|
* Map channel constants to display names (lowercase for message)
|
|
@@ -2370,11 +2801,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2370
2801
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2371
2802
|
|
|
2372
2803
|
// Clear any existing values while waiting for prefilled values
|
|
2373
|
-
|
|
2374
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2375
|
-
emptyValues[tag?.fullPath] = '';
|
|
2376
|
-
});
|
|
2377
|
-
setCustomValues(emptyValues);
|
|
2804
|
+
setCustomValues(buildEmptyValues());
|
|
2378
2805
|
};
|
|
2379
2806
|
|
|
2380
2807
|
/**
|
|
@@ -2388,11 +2815,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2388
2815
|
actions.clearPreviewErrors();
|
|
2389
2816
|
|
|
2390
2817
|
// Initialize empty values for all tags
|
|
2391
|
-
|
|
2392
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2393
|
-
emptyValues[tag?.fullPath] = '';
|
|
2394
|
-
});
|
|
2395
|
-
setCustomValues(emptyValues);
|
|
2818
|
+
setCustomValues(buildEmptyValues());
|
|
2396
2819
|
|
|
2397
2820
|
// Don't make preview call when clearing selection - just reset to raw content
|
|
2398
2821
|
// Preview will be shown using raw formData/content
|
|
@@ -2428,17 +2851,17 @@ const CommonTestAndPreview = (props) => {
|
|
|
2428
2851
|
*/
|
|
2429
2852
|
const handleDiscardCustomValues = () => {
|
|
2430
2853
|
// Initialize empty values for all tags
|
|
2431
|
-
const emptyValues =
|
|
2432
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2433
|
-
emptyValues[tag?.fullPath] = '';
|
|
2434
|
-
});
|
|
2854
|
+
const emptyValues = buildEmptyValues();
|
|
2435
2855
|
setCustomValues(emptyValues);
|
|
2436
2856
|
|
|
2857
|
+
// Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
|
|
2858
|
+
setSmsFallbackPreviewText(undefined);
|
|
2859
|
+
|
|
2437
2860
|
// Update preview with empty values (this is a valid preview call trigger)
|
|
2438
2861
|
const payload = preparePreviewPayload(
|
|
2439
|
-
|
|
2862
|
+
activeChannelForActions,
|
|
2440
2863
|
formData || {},
|
|
2441
|
-
|
|
2864
|
+
activeContentForActions,
|
|
2442
2865
|
emptyValues,
|
|
2443
2866
|
selectedCustomer
|
|
2444
2867
|
);
|
|
@@ -2453,13 +2876,15 @@ const CommonTestAndPreview = (props) => {
|
|
|
2453
2876
|
const handleUpdatePreview = async () => {
|
|
2454
2877
|
try {
|
|
2455
2878
|
const payload = preparePreviewPayload(
|
|
2456
|
-
|
|
2879
|
+
activeChannelForActions,
|
|
2457
2880
|
formData || {},
|
|
2458
|
-
|
|
2881
|
+
activeContentForActions,
|
|
2459
2882
|
customValues,
|
|
2460
2883
|
selectedCustomer
|
|
2461
2884
|
);
|
|
2462
2885
|
await actions.updatePreviewRequested(payload);
|
|
2886
|
+
|
|
2887
|
+
await syncSmsFallbackPreview(customValues, selectedCustomer);
|
|
2463
2888
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2464
2889
|
} catch (error) {
|
|
2465
2890
|
CapNotification.error({
|
|
@@ -2469,25 +2894,115 @@ const CommonTestAndPreview = (props) => {
|
|
|
2469
2894
|
};
|
|
2470
2895
|
|
|
2471
2896
|
/**
|
|
2472
|
-
*
|
|
2897
|
+
* Categorize extracted tags into required/optional.
|
|
2473
2898
|
*/
|
|
2474
|
-
const
|
|
2475
|
-
|
|
2476
|
-
|
|
2899
|
+
const categorizeTags = (tagsTree = []) => {
|
|
2900
|
+
const required = [];
|
|
2901
|
+
const optional = [];
|
|
2902
|
+
let hasPersonalizationTags = false;
|
|
2903
|
+
const processTag = (tag, parentPath = '') => {
|
|
2904
|
+
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
2905
|
+
|
|
2906
|
+
// Skip unsubscribe tag for input fields
|
|
2907
|
+
if (tag?.name === UNSUBSCRIBE_TAG_NAME) return;
|
|
2908
|
+
|
|
2909
|
+
hasPersonalizationTags = true;
|
|
2910
|
+
const userDriven = tag?.metaData?.userDriven;
|
|
2911
|
+
if (userDriven === true) {
|
|
2912
|
+
optional.push({ ...tag, fullPath: currentPath });
|
|
2913
|
+
} else {
|
|
2914
|
+
// false or missing (SMS/DLT extract often omits metaData) → required for test values
|
|
2915
|
+
required.push({ ...tag, fullPath: currentPath });
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
if (tag?.children?.length > 0) {
|
|
2919
|
+
tag.children.forEach((child) => processTag(child, currentPath));
|
|
2920
|
+
}
|
|
2921
|
+
};
|
|
2922
|
+
|
|
2923
|
+
(tagsTree || []).forEach((tag) => processTag(tag));
|
|
2924
|
+
return { required, optional, hasPersonalizationTags };
|
|
2925
|
+
};
|
|
2926
|
+
|
|
2927
|
+
/**
|
|
2928
|
+
* Apply tag extraction when content comes from RCS + SMS fallback (no API call).
|
|
2929
|
+
*/
|
|
2930
|
+
const applyRcsSmsFallbackTagExtraction = () => {
|
|
2931
|
+
const rcsPrimaryCategorized = categorizeTags(extractedTags ?? []);
|
|
2932
|
+
const fallbackSmsResolvedForTags = smsFallbackTextForTagExtraction ?? '';
|
|
2933
|
+
let fallbackSmsTagTree = smsFallbackExtractedTags?.length > 0
|
|
2934
|
+
? smsFallbackExtractedTags
|
|
2935
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsResolvedForTags);
|
|
2936
|
+
if (!smsTemplateHasMustacheTags(fallbackSmsResolvedForTags)) {
|
|
2937
|
+
fallbackSmsTagTree = [];
|
|
2938
|
+
}
|
|
2939
|
+
const fallbackSmsCategorized = categorizeTags(fallbackSmsTagTree);
|
|
2940
|
+
setRequiredTags(rcsPrimaryCategorized.required);
|
|
2941
|
+
setOptionalTags(rcsPrimaryCategorized.optional);
|
|
2942
|
+
setSmsFallbackRequiredTags(fallbackSmsCategorized.required);
|
|
2943
|
+
setSmsFallbackOptionalTags(fallbackSmsCategorized.optional);
|
|
2944
|
+
setTagsExtracted(
|
|
2945
|
+
rcsPrimaryCategorized.hasPersonalizationTags || fallbackSmsCategorized.hasPersonalizationTags,
|
|
2946
|
+
);
|
|
2947
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, rcsPrimaryCategorized, fallbackSmsCategorized));
|
|
2948
|
+
};
|
|
2949
|
+
|
|
2950
|
+
/**
|
|
2951
|
+
* When extract-tags API returns, map Redux `extractedTags` into required/optional so
|
|
2952
|
+
* CustomValuesEditor shows personalization fields (effect was previously commented out).
|
|
2953
|
+
* RCS + SMS fallback: merge primary + fallback tag trees when fallback template exists.
|
|
2954
|
+
*/
|
|
2955
|
+
useEffect(() => {
|
|
2956
|
+
if (!show) return;
|
|
2957
|
+
const hasFallbackSmsTemplate = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
2958
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsTemplate) {
|
|
2959
|
+
applyRcsSmsFallbackTagExtraction();
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
2963
|
+
let smsTagSource = channel === CHANNELS.SMS && (!extractedTags || extractedTags.length === 0)
|
|
2964
|
+
? buildSyntheticSmsMustacheTags(smsEditorBody)
|
|
2965
|
+
: (extractedTags ?? []);
|
|
2966
|
+
if (channel === CHANNELS.SMS && !smsTemplateHasMustacheTags(smsEditorBody)) {
|
|
2967
|
+
smsTagSource = [];
|
|
2968
|
+
}
|
|
2969
|
+
const { required, optional, hasPersonalizationTags } = categorizeTags(smsTagSource);
|
|
2970
|
+
setRequiredTags(required);
|
|
2971
|
+
setOptionalTags(optional);
|
|
2972
|
+
setTagsExtracted(hasPersonalizationTags);
|
|
2973
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, { required, optional }, { required: [], optional: [] }));
|
|
2974
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- applyRcsSmsFallbackTagExtraction closes over latest extractedTags/smsFallbackExtractedTags
|
|
2975
|
+
}, [show, extractedTags, channel, smsFallbackContent, smsFallbackExtractedTags, getCurrentContent, smsFallbackTextForTagExtraction]);
|
|
2477
2976
|
|
|
2977
|
+
/**
|
|
2978
|
+
* Get content to run tag extraction on (channel-specific).
|
|
2979
|
+
*/
|
|
2980
|
+
const getContentForTagExtraction = () => {
|
|
2981
|
+
let contentToExtract = activeContentForActions;
|
|
2478
2982
|
if (channel === CHANNELS.EMAIL && formData) {
|
|
2479
2983
|
const currentTabData = formData[currentTab - 1];
|
|
2480
2984
|
const activeTab = currentTabData?.activeTab;
|
|
2481
2985
|
const templateContent = currentTabData?.[activeTab]?.['template-content'];
|
|
2482
2986
|
contentToExtract = templateContent || contentToExtract;
|
|
2483
2987
|
}
|
|
2988
|
+
return contentToExtract;
|
|
2989
|
+
};
|
|
2484
2990
|
|
|
2485
|
-
|
|
2991
|
+
/**
|
|
2992
|
+
* Handle extract tags
|
|
2993
|
+
*/
|
|
2994
|
+
const handleExtractTags = () => {
|
|
2995
|
+
if (channel === CHANNELS.RCS) {
|
|
2996
|
+
applyRcsSmsFallbackTagExtraction();
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
const contentToExtract = getContentForTagExtraction();
|
|
2486
3001
|
const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
|
|
2487
3002
|
const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
|
|
3003
|
+
const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
|
|
2488
3004
|
|
|
2489
|
-
if (
|
|
2490
|
-
// If only unsubscribe tag is present, show noTagsExtracted message
|
|
3005
|
+
if (onlyUnsubscribe) {
|
|
2491
3006
|
setTagsExtracted(false);
|
|
2492
3007
|
setRequiredTags([]);
|
|
2493
3008
|
setOptionalTags([]);
|
|
@@ -2495,10 +3010,9 @@ const CommonTestAndPreview = (props) => {
|
|
|
2495
3010
|
return;
|
|
2496
3011
|
}
|
|
2497
3012
|
|
|
2498
|
-
// Extract tags
|
|
2499
3013
|
setTagsExtracted(true);
|
|
2500
3014
|
const { templateSubject, templateContent } = prepareTagExtractionPayload(
|
|
2501
|
-
|
|
3015
|
+
activeChannelForActions,
|
|
2502
3016
|
formData || {},
|
|
2503
3017
|
contentToExtract
|
|
2504
3018
|
);
|
|
@@ -2518,7 +3032,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2518
3032
|
const handleSendTestMessage = () => {
|
|
2519
3033
|
const allUserIds = [];
|
|
2520
3034
|
selectedTestEntities.forEach((entityId) => {
|
|
2521
|
-
const group = testGroups.find((
|
|
3035
|
+
const group = testGroups.find((testGroup) => testGroup.groupId === entityId);
|
|
2522
3036
|
if (group) {
|
|
2523
3037
|
allUserIds.push(...group.userIds);
|
|
2524
3038
|
} else {
|
|
@@ -2527,11 +3041,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
2527
3041
|
});
|
|
2528
3042
|
const uniqueUserIds = [...new Set(allUserIds)];
|
|
2529
3043
|
|
|
2530
|
-
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
|
|
3044
|
+
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
|
|
2531
3045
|
? testPreviewDeliverySettings[channel]
|
|
2532
3046
|
: null;
|
|
2533
3047
|
|
|
2534
|
-
//
|
|
3048
|
+
// createMessageMeta must match the creative channel and full creative (RCS + SMS fallback in one meta).
|
|
3049
|
+
// Do not use activeChannelForActions / activeContentForActions — those follow the RCS vs Fallback SMS *preview* tab.
|
|
2535
3050
|
const initialPayload = prepareTestMessagePayload(
|
|
2536
3051
|
channel,
|
|
2537
3052
|
formData || content || {},
|
|
@@ -2539,7 +3054,16 @@ const CommonTestAndPreview = (props) => {
|
|
|
2539
3054
|
customValues,
|
|
2540
3055
|
uniqueUserIds,
|
|
2541
3056
|
previewData,
|
|
2542
|
-
deliveryOverride
|
|
3057
|
+
deliveryOverride,
|
|
3058
|
+
channel === CHANNELS.RCS
|
|
3059
|
+
? {
|
|
3060
|
+
smsFallbackTemplateContent:
|
|
3061
|
+
smsFallbackTextForTagExtraction
|
|
3062
|
+
|| smsFallbackContent?.templateContent
|
|
3063
|
+
|| smsFallbackContent?.content
|
|
3064
|
+
|| '',
|
|
3065
|
+
}
|
|
3066
|
+
: {},
|
|
2543
3067
|
);
|
|
2544
3068
|
|
|
2545
3069
|
actions.createMessageMetaRequested(
|
|
@@ -2572,11 +3096,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2572
3096
|
// ============================================
|
|
2573
3097
|
// RENDER HELPER FUNCTIONS
|
|
2574
3098
|
// ============================================
|
|
2575
|
-
|
|
2576
3099
|
const renderLeftPanelContent = () => (
|
|
2577
3100
|
<LeftPanelContent
|
|
2578
|
-
isExtractingTags={isExtractingTags}
|
|
2579
|
-
extractedTags={
|
|
3101
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
3102
|
+
extractedTags={leftPanelExtractedTags}
|
|
2580
3103
|
selectedCustomer={selectedCustomer}
|
|
2581
3104
|
handleCustomerSelect={handleCustomerSelect}
|
|
2582
3105
|
handleSearchCustomer={handleSearchCustomer}
|
|
@@ -2594,15 +3117,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
2594
3117
|
|
|
2595
3118
|
const renderCustomValuesEditor = () => (
|
|
2596
3119
|
<CustomValuesEditor
|
|
2597
|
-
isExtractingTags={isExtractingTags}
|
|
3120
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
2598
3121
|
isUpdatePreviewDisabled={isUpdatePreviewDisabled}
|
|
2599
3122
|
showJSON={showJSON}
|
|
2600
3123
|
setShowJSON={setShowJSON}
|
|
2601
3124
|
customValues={customValues}
|
|
2602
3125
|
handleJSONTextChange={handleJSONTextChange}
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
3126
|
+
sections={[
|
|
3127
|
+
{
|
|
3128
|
+
key: channel,
|
|
3129
|
+
title:
|
|
3130
|
+
channel === CHANNELS.RCS
|
|
3131
|
+
? messages.rcsTagsSectionTitle
|
|
3132
|
+
: messages[`${channel}TagsSectionTitle`],
|
|
3133
|
+
requiredTags,
|
|
3134
|
+
optionalTags,
|
|
3135
|
+
},
|
|
3136
|
+
{
|
|
3137
|
+
key: PREVIEW_TAB_SMS_FALLBACK,
|
|
3138
|
+
title: channel === CHANNELS.RCS && smsFallbackContent?.templateContent ? messages.smsFallbackTagsSectionTitle : null,
|
|
3139
|
+
requiredTags: smsFallbackRequiredTags,
|
|
3140
|
+
optionalTags: smsFallbackOptionalTags,
|
|
3141
|
+
},
|
|
3142
|
+
]}
|
|
2606
3143
|
handleCustomValueChange={handleCustomValueChange}
|
|
2607
3144
|
handleDiscardCustomValues={handleDiscardCustomValues}
|
|
2608
3145
|
handleUpdatePreview={handleUpdatePreview}
|
|
@@ -2632,12 +3169,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
2632
3169
|
isSendingTestMessage={isSendingTestMessage}
|
|
2633
3170
|
formatMessage={formatMessage}
|
|
2634
3171
|
deliverySettings={testPreviewDeliverySettings[channel]}
|
|
2635
|
-
|
|
3172
|
+
senderDetailsByChannel={senderDetailsByChannel}
|
|
2636
3173
|
wecrmAccounts={wecrmAccounts}
|
|
2637
3174
|
onSaveDeliverySettings={handleSaveDeliverySettings}
|
|
2638
3175
|
isLoadingSenderDetails={isLoadingSenderDetails}
|
|
2639
3176
|
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
2640
3177
|
registeredSenderIds={registeredSenderIds}
|
|
3178
|
+
isChannelSmsFallbackPreviewEnabled={channel === CHANNELS.RCS && !!smsFallbackContent?.templateContent}
|
|
2641
3179
|
/>
|
|
2642
3180
|
);
|
|
2643
3181
|
|