@capillarytech/creatives-library 8.0.353-alpha.4 → 8.0.353-alpha.6
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 +29 -0
- package/package.json +1 -1
- package/services/tests/api.test.js +35 -20
- package/utils/commonUtils.js +19 -1
- package/utils/rcsPayloadUtils.js +92 -0
- package/utils/templateVarUtils.js +201 -0
- package/utils/tests/rcsPayloadUtils.test.js +226 -0
- package/utils/tests/templateVarUtils.test.js +204 -0
- package/v2Components/CapActionButton/constants.js +7 -0
- package/v2Components/CapActionButton/index.js +166 -108
- package/v2Components/CapActionButton/index.scss +157 -6
- package/v2Components/CapActionButton/messages.js +19 -3
- package/v2Components/CapActionButton/tests/index.test.js +41 -17
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +213 -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 +10 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +346 -76
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -2
- package/v2Components/CommonTestAndPreview/index.js +691 -186
- package/v2Components/CommonTestAndPreview/messages.js +45 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +25 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
- 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/PreviewSection.test.js +8 -1
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
- 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 +36 -26
- package/v2Components/FormBuilder/index.js +74 -166
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -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 +119 -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 +223 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TemplatePreview/_templatePreview.scss +38 -23
- package/v2Components/TemplatePreview/constants.js +2 -0
- package/v2Components/TemplatePreview/index.js +143 -31
- package/v2Components/TemplatePreview/tests/index.test.js +142 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +13 -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/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 +9 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
- package/v2Containers/CreativesContainer/index.js +346 -163
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
- 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 -1
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/constants.js +119 -10
- package/v2Containers/Rcs/index.js +2445 -813
- package/v2Containers/Rcs/index.scss +280 -8
- package/v2Containers/Rcs/messages.js +34 -3
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/index.test.js +152 -121
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
- package/v2Containers/Rcs/tests/utils.test.js +646 -30
- package/v2Containers/Rcs/utils.js +478 -11
- package/v2Containers/Sms/Create/index.js +106 -40
- 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 +640 -130
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +14 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
- 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 +166 -9
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +120 -52
- package/v2Containers/Templates/sagas.js +56 -12
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
- package/v2Containers/Templates/tests/sagas.test.js +199 -16
- 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/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -31,7 +31,8 @@ import CustomValuesEditor from './CustomValuesEditor';
|
|
|
31
31
|
import SendTestMessage from './SendTestMessage';
|
|
32
32
|
import PreviewSection from './PreviewSection';
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
import * as Api from '../../services/api';
|
|
35
|
+
import { extractTemplateVariables } from '../../utils/templateVarUtils';
|
|
35
36
|
import AddTestCustomerButton from './AddTestCustomer';
|
|
36
37
|
import ExistingCustomerModal from './ExistingCustomerModal';
|
|
37
38
|
// Import constants
|
|
@@ -81,11 +82,23 @@ import {
|
|
|
81
82
|
IMAGE,
|
|
82
83
|
VIDEO,
|
|
83
84
|
URL,
|
|
84
|
-
|
|
85
|
+
PREVIEW_TAB_RCS,
|
|
86
|
+
PREVIEW_TAB_SMS_FALLBACK,
|
|
87
|
+
CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
|
|
88
|
+
RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
89
|
+
RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
90
|
+
RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
91
|
+
RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
92
|
+
SMS_MUSTACHE_TAG_PATTERN,
|
|
85
93
|
} from './constants';
|
|
86
|
-
|
|
87
|
-
// Import utilities
|
|
88
94
|
import { getCdnUrl } from '../../utils/cdnTransformation';
|
|
95
|
+
import {
|
|
96
|
+
normalizePreviewApiPayload,
|
|
97
|
+
extractPreviewFromLiquidResponse,
|
|
98
|
+
getSmsFallbackTextForTagExtraction,
|
|
99
|
+
} from './previewApiUtils';
|
|
100
|
+
import { pickFirstSmsFallbackTemplateString } from '../../v2Containers/Rcs/rcsLibraryHydrationUtils';
|
|
101
|
+
|
|
89
102
|
import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
|
|
90
103
|
import { getMembersLookup } from '../../services/api';
|
|
91
104
|
|
|
@@ -108,6 +121,85 @@ const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEch
|
|
|
108
121
|
});
|
|
109
122
|
};
|
|
110
123
|
|
|
124
|
+
/** Preview payload from Redux may be an Immutable Map — normalize for React state. */
|
|
125
|
+
const toPlainPreviewData = (data) => {
|
|
126
|
+
if (data == null) return null;
|
|
127
|
+
const plain = typeof data.toJS === 'function' ? data.toJS() : data;
|
|
128
|
+
return normalizePreviewApiPayload(plain);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Merge existing customValues with tag keys from categorized groups.
|
|
133
|
+
* Each group is { required, optional } (arrays of tag objects with fullPath).
|
|
134
|
+
* Preserves existing values; adds '' for any tag key not yet present.
|
|
135
|
+
* Reusable for RCS+fallback and any flow that needs to ensure customValues has keys for tags.
|
|
136
|
+
*/
|
|
137
|
+
const mergeCustomValuesWithTagKeys = (prev, ...categorizedGroups) => {
|
|
138
|
+
const next = { ...(prev || {}) };
|
|
139
|
+
categorizedGroups.forEach((group) => {
|
|
140
|
+
[...(group.required || []), ...(group.optional || [])].forEach((tag) => {
|
|
141
|
+
const key = tag?.fullPath;
|
|
142
|
+
if (key && next[key] === undefined) next[key] = '';
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
return next;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** True when `body` contains `{{name}}` mustache tokens (user-fillable personalization tags).
|
|
149
|
+
* DLT `{#name#}` slots are pre-bound template variables and are intentionally excluded. */
|
|
150
|
+
const smsTemplateHasMustacheTags = (body) =>
|
|
151
|
+
typeof body === 'string' && SMS_MUSTACHE_TAG_PATTERN.test(body);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Build tag rows from `{{…}}` mustache tokens only — DLT `{#…#}` slots are excluded because
|
|
155
|
+
* they are pre-bound template variables, not user-fillable personalization tags.
|
|
156
|
+
* Passing a mustache-only captureRegex to extractTemplateVariables skips the DLT branch.
|
|
157
|
+
* A non-global regex is used so ensureGlobalRegexForExecLoop creates a fresh instance on each call.
|
|
158
|
+
*/
|
|
159
|
+
const buildSyntheticSmsMustacheTags = (body = '') => {
|
|
160
|
+
if (!body || typeof body !== 'string') return [];
|
|
161
|
+
return extractTemplateVariables(body, /\{\{([^}]+)\}\}/).map((name) => ({
|
|
162
|
+
name,
|
|
163
|
+
metaData: { userDriven: false },
|
|
164
|
+
children: [],
|
|
165
|
+
}));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** RCS createMessageMeta: media shape (mediaUrl, thumbnailUrl, height string). */
|
|
169
|
+
const normalizeRcsTestCardMedia = (media) => {
|
|
170
|
+
if (!media || typeof media !== 'object') return undefined;
|
|
171
|
+
const mediaUrl =
|
|
172
|
+
media.mediaUrl != null && String(media.mediaUrl).trim() !== ''
|
|
173
|
+
? String(media.mediaUrl)
|
|
174
|
+
: media.url != null && String(media.url).trim() !== ''
|
|
175
|
+
? String(media.url)
|
|
176
|
+
: '';
|
|
177
|
+
const thumbnailUrl = media.thumbnailUrl != null ? String(media.thumbnailUrl) : '';
|
|
178
|
+
const height = media.height != null ? String(media.height) : undefined;
|
|
179
|
+
const out = { mediaUrl, thumbnailUrl };
|
|
180
|
+
if (height) out.height = height;
|
|
181
|
+
return out;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** RCS createMessageMeta: suggestion shape (index, type, text, phoneNumber, url, postback). */
|
|
185
|
+
const mapRcsSuggestionForTestMeta = (suggestionRow, index) => ({
|
|
186
|
+
index,
|
|
187
|
+
type: suggestionRow?.type ?? '',
|
|
188
|
+
text: suggestionRow?.text != null ? String(suggestionRow.text) : '',
|
|
189
|
+
phoneNumber:
|
|
190
|
+
suggestionRow?.phoneNumber != null
|
|
191
|
+
? String(suggestionRow.phoneNumber)
|
|
192
|
+
: suggestionRow?.phone_number != null
|
|
193
|
+
? String(suggestionRow.phone_number)
|
|
194
|
+
: '',
|
|
195
|
+
url: suggestionRow?.url !== undefined ? suggestionRow.url : null,
|
|
196
|
+
postback:
|
|
197
|
+
suggestionRow?.postback != null
|
|
198
|
+
? String(suggestionRow.postback)
|
|
199
|
+
: suggestionRow?.text != null
|
|
200
|
+
? String(suggestionRow.text)
|
|
201
|
+
: '',
|
|
202
|
+
});
|
|
111
203
|
|
|
112
204
|
/**
|
|
113
205
|
* CapTreeSelect and group resolution use strict equality; API data may mix numeric and string ids.
|
|
@@ -133,7 +225,7 @@ const testEntityIdsEqual = (a, b) => {
|
|
|
133
225
|
*/
|
|
134
226
|
const CommonTestAndPreview = (props) => {
|
|
135
227
|
const {
|
|
136
|
-
intl: { formatMessage },
|
|
228
|
+
intl: { formatMessage, locale: userLocale = 'en' },
|
|
137
229
|
show,
|
|
138
230
|
onClose,
|
|
139
231
|
channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
|
|
@@ -169,12 +261,21 @@ const CommonTestAndPreview = (props) => {
|
|
|
169
261
|
...additionalProps
|
|
170
262
|
} = props;
|
|
171
263
|
|
|
264
|
+
const smsFallbackContent = additionalProps?.smsFallbackContent;
|
|
265
|
+
const smsFallbackTextForTagExtraction = useMemo(
|
|
266
|
+
() => getSmsFallbackTextForTagExtraction(smsFallbackContent),
|
|
267
|
+
[smsFallbackContent],
|
|
268
|
+
);
|
|
172
269
|
// ============================================
|
|
173
270
|
// STATE MANAGEMENT
|
|
174
271
|
// ============================================
|
|
175
272
|
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
|
176
273
|
const [requiredTags, setRequiredTags] = useState([]);
|
|
177
274
|
const [optionalTags, setOptionalTags] = useState([]);
|
|
275
|
+
const [smsFallbackExtractedTags, setSmsFallbackExtractedTags] = useState([]);
|
|
276
|
+
const [smsFallbackRequiredTags, setSmsFallbackRequiredTags] = useState([]);
|
|
277
|
+
const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
|
|
278
|
+
const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
|
|
178
279
|
const [customValues, setCustomValues] = useState({});
|
|
179
280
|
const [showJSON, setShowJSON] = useState(false);
|
|
180
281
|
const [tagsExtracted, setTagsExtracted] = useState(false);
|
|
@@ -186,6 +287,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
186
287
|
const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
|
|
187
288
|
|
|
188
289
|
const [previewDevice, setPreviewDevice] = useState(initialDevice);
|
|
290
|
+
const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
|
|
291
|
+
const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
|
|
189
292
|
// Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
|
|
190
293
|
const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
|
|
191
294
|
const [previewDataHtml, setPreviewDataHtml] = useState(() => {
|
|
@@ -218,15 +321,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
218
321
|
[CHANNELS.WHATSAPP]: {
|
|
219
322
|
domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
|
|
220
323
|
},
|
|
324
|
+
[CHANNELS.RCS]: {
|
|
325
|
+
domainId: null,
|
|
326
|
+
domainGatewayMapId: null,
|
|
327
|
+
gsmSenderId: '',
|
|
328
|
+
smsFallbackDomainId: null,
|
|
329
|
+
cdmaSenderId: '', // gsmSenderId = RCS sender (domainId|senderId), cdmaSenderId = SMS fallback
|
|
330
|
+
},
|
|
221
331
|
});
|
|
222
332
|
|
|
223
|
-
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
|
|
333
|
+
const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
|
|
224
334
|
const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
|
|
225
335
|
const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
|
|
226
336
|
const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
|
|
227
337
|
const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
|
|
228
338
|
|
|
229
|
-
// Fetch sender details and WeCRM accounts when Test & Preview opens
|
|
339
|
+
// Fetch sender details and WeCRM accounts when Test & Preview opens (SMS, Email, WhatsApp, RCS — same process)
|
|
230
340
|
useEffect(() => {
|
|
231
341
|
if (!show || !channel) {
|
|
232
342
|
return;
|
|
@@ -235,39 +345,99 @@ const CommonTestAndPreview = (props) => {
|
|
|
235
345
|
if (actions.getSenderDetailsRequested) {
|
|
236
346
|
actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
|
|
237
347
|
}
|
|
348
|
+
// SMS domains/senders are needed for RCS delivery UI (fallback row + slidebox) whenever RCS is open — not only when fallback body exists.
|
|
349
|
+
if (channel === CHANNELS.RCS && actions.getSenderDetailsRequested) {
|
|
350
|
+
actions.getSenderDetailsRequested({ channel: CHANNELS.SMS, orgUnitId: orgUnitId ?? -1 });
|
|
351
|
+
}
|
|
238
352
|
if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
|
|
239
353
|
actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
|
|
240
354
|
}
|
|
241
355
|
}
|
|
242
356
|
}, [show, channel, orgUnitId, actions]);
|
|
243
357
|
|
|
244
|
-
useEffect(() => {
|
|
245
|
-
if (!show) {
|
|
246
|
-
setCustomerModal([false, '']);
|
|
247
|
-
setSearchValue('');
|
|
248
|
-
setCustomerData({ name: '', email: '', mobile: '', customerId: '' });
|
|
249
|
-
}
|
|
250
|
-
}, [show]);
|
|
251
|
-
|
|
252
358
|
const findDefault = (arr) => (arr && arr.find((x) => x.default)) || (arr && arr[0]) || {};
|
|
253
359
|
|
|
254
360
|
// Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
|
|
255
361
|
useEffect(() => {
|
|
256
362
|
if (!channel || !channelsWithDeliverySettings.includes(channel)) return;
|
|
363
|
+
|
|
364
|
+
if (channel === CHANNELS.RCS) {
|
|
365
|
+
const rcsDomainRows = senderDetailsByChannel?.[CHANNELS.RCS] || [];
|
|
366
|
+
const smsFallbackDomainRows = senderDetailsByChannel?.[CHANNELS.SMS] || [];
|
|
367
|
+
if (!rcsDomainRows.length) return;
|
|
368
|
+
|
|
369
|
+
const currentRcsDeliverySettings = testPreviewDeliverySettings?.[CHANNELS.RCS] || {};
|
|
370
|
+
const isRcsGsmSenderUnset = !currentRcsDeliverySettings?.gsmSenderId;
|
|
371
|
+
const isSmsFallbackSenderUnset = !currentRcsDeliverySettings?.cdmaSenderId;
|
|
372
|
+
const isRcsDeliveryFullyUnset = isRcsGsmSenderUnset && isSmsFallbackSenderUnset;
|
|
373
|
+
const shouldOnlyFillSmsFallbackSender =
|
|
374
|
+
!isRcsGsmSenderUnset && isSmsFallbackSenderUnset && smsFallbackDomainRows.length > 0;
|
|
375
|
+
|
|
376
|
+
if (!isRcsDeliveryFullyUnset && !shouldOnlyFillSmsFallbackSender) return;
|
|
377
|
+
|
|
378
|
+
const firstRcsDomain = rcsDomainRows[0];
|
|
379
|
+
const firstSmsFallbackDomain = smsFallbackDomainRows[0];
|
|
380
|
+
const usableRcsGsmSenders = filterUsableGsmSendersForDomain(
|
|
381
|
+
firstRcsDomain,
|
|
382
|
+
firstRcsDomain?.gsmSenders,
|
|
383
|
+
{ skipDomainNameEchoFilter: true },
|
|
384
|
+
);
|
|
385
|
+
const usableSmsFallbackGsmSenders = firstSmsFallbackDomain
|
|
386
|
+
? filterUsableGsmSendersForDomain(firstSmsFallbackDomain, firstSmsFallbackDomain?.gsmSenders)
|
|
387
|
+
: [];
|
|
388
|
+
const defaultRcsGsmSender = usableRcsGsmSenders[0];
|
|
389
|
+
const defaultSmsFallbackGsmSender = usableSmsFallbackGsmSenders[0];
|
|
390
|
+
const rcsSenderCompositeValue =
|
|
391
|
+
firstRcsDomain?.domainId != null && defaultRcsGsmSender?.value != null
|
|
392
|
+
? `${firstRcsDomain.domainId}|${defaultRcsGsmSender.value}`
|
|
393
|
+
: (defaultRcsGsmSender?.value || '');
|
|
394
|
+
const smsFallbackSenderCompositeValue =
|
|
395
|
+
firstSmsFallbackDomain?.domainId != null && defaultSmsFallbackGsmSender?.value != null
|
|
396
|
+
? `${firstSmsFallbackDomain.domainId}|${defaultSmsFallbackGsmSender.value}`
|
|
397
|
+
: (defaultSmsFallbackGsmSender?.value || '');
|
|
398
|
+
|
|
399
|
+
setTestPreviewDeliverySettings((prev) => {
|
|
400
|
+
const previousRcsSettings = prev?.[CHANNELS.RCS] || {};
|
|
401
|
+
if (shouldOnlyFillSmsFallbackSender) {
|
|
402
|
+
if (!smsFallbackSenderCompositeValue) return prev;
|
|
403
|
+
return {
|
|
404
|
+
...prev,
|
|
405
|
+
[CHANNELS.RCS]: {
|
|
406
|
+
...previousRcsSettings,
|
|
407
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
408
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
...prev,
|
|
414
|
+
[CHANNELS.RCS]: {
|
|
415
|
+
domainId: firstRcsDomain?.domainId ?? null,
|
|
416
|
+
domainGatewayMapId: firstRcsDomain?.dgmId ?? null,
|
|
417
|
+
gsmSenderId: rcsSenderCompositeValue,
|
|
418
|
+
smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
|
|
419
|
+
cdmaSenderId: smsFallbackSenderCompositeValue,
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
|
|
257
426
|
const domains = senderDetailsByChannel[channel];
|
|
258
427
|
if (!domains || domains.length === 0) return;
|
|
259
428
|
const {
|
|
260
|
-
domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
|
|
429
|
+
domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
|
|
261
430
|
} = testPreviewDeliverySettings[channel] || {};
|
|
262
|
-
const isEmptySelection = !domainId && !gsmSenderId && !senderEmail && !senderMobNum;
|
|
431
|
+
const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
|
|
263
432
|
if (!isEmptySelection) return;
|
|
264
433
|
|
|
265
434
|
const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
|
|
266
435
|
const matchedWhatsappAccount = whatsappAccountFromForm
|
|
267
436
|
? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
|
|
268
437
|
: null;
|
|
269
|
-
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled
|
|
270
|
-
? domains.filter((domain) => (domain
|
|
438
|
+
const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
|
|
439
|
+
? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
|
|
440
|
+
registeredSenderIds?.includes(gsm?.value)))
|
|
271
441
|
: domains;
|
|
272
442
|
const [defaultDomain] = domains;
|
|
273
443
|
const [firstSmsDomain] = smsDomains;
|
|
@@ -282,8 +452,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
282
452
|
const next = { ...prev };
|
|
283
453
|
if (channel === CHANNELS.SMS) {
|
|
284
454
|
const smsGsmSenders = smsTraiDltEnabled
|
|
285
|
-
? (firstDomain
|
|
286
|
-
: firstDomain
|
|
455
|
+
? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
|
|
456
|
+
: firstDomain?.gsmSenders;
|
|
287
457
|
next[channel] = {
|
|
288
458
|
domainId: firstDomain.domainId,
|
|
289
459
|
domainGatewayMapId: firstDomain.dgmId,
|
|
@@ -316,10 +486,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
316
486
|
// MEMOIZED VALUES
|
|
317
487
|
// ============================================
|
|
318
488
|
|
|
489
|
+
const allTags = useMemo(
|
|
490
|
+
() => [...requiredTags, ...optionalTags, ...smsFallbackRequiredTags, ...smsFallbackOptionalTags],
|
|
491
|
+
[requiredTags, optionalTags, smsFallbackRequiredTags, smsFallbackOptionalTags]
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
const allRequiredTags = useMemo(
|
|
495
|
+
() => [...requiredTags, ...smsFallbackRequiredTags],
|
|
496
|
+
[requiredTags, smsFallbackRequiredTags]
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
const buildEmptyValues = useCallback(
|
|
500
|
+
() => allTags.reduce((acc, tag) => {
|
|
501
|
+
const key = tag?.fullPath;
|
|
502
|
+
if (key) acc[key] = '';
|
|
503
|
+
return acc;
|
|
504
|
+
}, {}),
|
|
505
|
+
[allTags]
|
|
506
|
+
);
|
|
507
|
+
|
|
319
508
|
// Check if update preview button should be disabled
|
|
320
509
|
const isUpdatePreviewDisabled = useMemo(() => (
|
|
321
|
-
|
|
322
|
-
), [
|
|
510
|
+
allRequiredTags.some((tag) => !customValues[tag.fullPath])
|
|
511
|
+
), [allRequiredTags, customValues]);
|
|
323
512
|
|
|
324
513
|
// Get current content based on channel and editor type
|
|
325
514
|
const getCurrentContent = useMemo(() => {
|
|
@@ -363,6 +552,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
363
552
|
return currentTabData.base['sms-editor'];
|
|
364
553
|
}
|
|
365
554
|
}
|
|
555
|
+
// DLT / Test & Preview shape: { templateConfigs: { template, templateId, ... } }
|
|
556
|
+
if (formData.templateConfigs?.template) {
|
|
557
|
+
const smsDltTemplateValue = formData.templateConfigs.template;
|
|
558
|
+
if (typeof smsDltTemplateValue === 'string') return smsDltTemplateValue;
|
|
559
|
+
if (Array.isArray(smsDltTemplateValue)) return smsDltTemplateValue.join('');
|
|
560
|
+
return '';
|
|
561
|
+
}
|
|
366
562
|
}
|
|
367
563
|
|
|
368
564
|
// SMS channel fallback - if formData is not available, use content directly
|
|
@@ -408,7 +604,70 @@ const CommonTestAndPreview = (props) => {
|
|
|
408
604
|
return content || '';
|
|
409
605
|
}, [channel, formData, currentTab, beeContent, content, beeInstance]);
|
|
410
606
|
|
|
411
|
-
|
|
607
|
+
const leftPanelExtractedTags = useMemo(() => {
|
|
608
|
+
if (channel === CHANNELS.SMS) {
|
|
609
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
610
|
+
if (!smsTemplateHasMustacheTags(smsEditorBody)) return [];
|
|
611
|
+
const extractTagsFromApi = extractedTags ?? [];
|
|
612
|
+
if (extractTagsFromApi.length > 0) return extractTagsFromApi;
|
|
613
|
+
return buildSyntheticSmsMustacheTags(smsEditorBody);
|
|
614
|
+
}
|
|
615
|
+
const hasFallbackSmsBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
616
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsBody) {
|
|
617
|
+
const rcsPrimaryTags = extractedTags ?? [];
|
|
618
|
+
const fallbackSmsTextForTags = smsFallbackTextForTagExtraction ?? '';
|
|
619
|
+
const fallbackSmsTagRows = smsTemplateHasMustacheTags(fallbackSmsTextForTags)
|
|
620
|
+
? (smsFallbackExtractedTags?.length > 0
|
|
621
|
+
? smsFallbackExtractedTags
|
|
622
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsTextForTags))
|
|
623
|
+
: [];
|
|
624
|
+
const mergedRcsAndFallbackTags = [...rcsPrimaryTags, ...fallbackSmsTagRows];
|
|
625
|
+
if (mergedRcsAndFallbackTags.length > 0) return mergedRcsAndFallbackTags;
|
|
626
|
+
return buildSyntheticSmsMustacheTags(fallbackSmsTextForTags);
|
|
627
|
+
}
|
|
628
|
+
return extractedTags ?? [];
|
|
629
|
+
}, [
|
|
630
|
+
channel,
|
|
631
|
+
extractedTags,
|
|
632
|
+
getCurrentContent,
|
|
633
|
+
smsFallbackContent,
|
|
634
|
+
smsFallbackExtractedTags,
|
|
635
|
+
smsFallbackTextForTagExtraction,
|
|
636
|
+
]);
|
|
637
|
+
|
|
638
|
+
const isRcsSmsFallbackPreviewEnabled =
|
|
639
|
+
channel === CHANNELS.RCS
|
|
640
|
+
&& !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
641
|
+
// Only treat as SMS when user is on the Fallback SMS tab — not whenever fallback exists (RCS tab needs RCS preview API).
|
|
642
|
+
const isSmsFallbackTabActive = isRcsSmsFallbackPreviewEnabled && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK;
|
|
643
|
+
const activeChannelForActions = isSmsFallbackTabActive ? CHANNELS.SMS : channel;
|
|
644
|
+
// VarSegment slot values live in rcsSmsFallbackVarMapped; raw templateContent alone is stale for /preview Body.
|
|
645
|
+
const resolvedSmsFallbackBodyForPreviewTab =
|
|
646
|
+
smsFallbackTextForTagExtraction
|
|
647
|
+
|| smsFallbackContent?.templateContent
|
|
648
|
+
|| smsFallbackContent?.content
|
|
649
|
+
|| '';
|
|
650
|
+
const activeContentForActions = isSmsFallbackTabActive
|
|
651
|
+
? resolvedSmsFallbackBodyForPreviewTab
|
|
652
|
+
: getCurrentContent;
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* SMS fallback pane must show /preview API result when user updated preview on that tab (plain text).
|
|
656
|
+
* Skip when resolvedBody is RCS-shaped (e.g. user last previewed on RCS tab).
|
|
657
|
+
*/
|
|
658
|
+
// smsFallbackPreviewText is the single source of truth for the resolved SMS fallback preview.
|
|
659
|
+
// It is set only by syncSmsFallbackPreview (called from handleUpdatePreview and the
|
|
660
|
+
// prefilled-values effect) and reset to undefined on discard / slidebox close.
|
|
661
|
+
// Using previewDataHtml as a fallback is unsafe because that state is shared with the primary
|
|
662
|
+
// RCS preview and can contain stale SMS or RCS content.
|
|
663
|
+
const smsFallbackResolvedText = useMemo(() => {
|
|
664
|
+
const hasFallbackBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
665
|
+
if (channel !== CHANNELS.RCS || !hasFallbackBody) return undefined;
|
|
666
|
+
if (smsFallbackPreviewText != null) return smsFallbackPreviewText;
|
|
667
|
+
return undefined;
|
|
668
|
+
}, [channel, smsFallbackContent, smsFallbackPreviewText]);
|
|
669
|
+
|
|
670
|
+
// Build test entities tree data from testCustomers prop
|
|
412
671
|
// Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
|
|
413
672
|
const testEntitiesTreeData = useMemo(() => {
|
|
414
673
|
const groupsNode = {
|
|
@@ -417,7 +676,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
417
676
|
selectable: false,
|
|
418
677
|
children: testGroups?.map((group) => ({
|
|
419
678
|
title: group?.groupName,
|
|
420
|
-
value:
|
|
679
|
+
value: normalizeTestEntityId(group?.groupId),
|
|
421
680
|
})),
|
|
422
681
|
};
|
|
423
682
|
|
|
@@ -427,7 +686,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
427
686
|
selectable: false,
|
|
428
687
|
children: testCustomers?.map((customer) => ({
|
|
429
688
|
title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
|
|
430
|
-
value:
|
|
689
|
+
value: normalizeTestEntityId(customer?.userId ?? customer?.customerId),
|
|
431
690
|
})) || [],
|
|
432
691
|
};
|
|
433
692
|
|
|
@@ -507,11 +766,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
507
766
|
email: customerData?.email || '',
|
|
508
767
|
mobile: customerData?.mobile || '',
|
|
509
768
|
});
|
|
510
|
-
const prefixedAddedId = 'customer:' + normalizedAddedId;
|
|
511
769
|
setSelectedTestEntities((prev) => (
|
|
512
|
-
prev.some((id) => id
|
|
770
|
+
prev.some((id) => testEntityIdsEqual(id, normalizedAddedId))
|
|
513
771
|
? prev
|
|
514
|
-
: [...prev,
|
|
772
|
+
: [...prev, normalizedAddedId]
|
|
515
773
|
));
|
|
516
774
|
}
|
|
517
775
|
handleCloseCustomerModal();
|
|
@@ -615,6 +873,37 @@ const CommonTestAndPreview = (props) => {
|
|
|
615
873
|
}
|
|
616
874
|
};
|
|
617
875
|
|
|
876
|
+
/**
|
|
877
|
+
* When RCS has SMS fallback, refresh fallback preview text via the same Liquid /preview API
|
|
878
|
+
* (separate call with SMS channel + fallback template body). Used after primary preview updates.
|
|
879
|
+
*/
|
|
880
|
+
const syncSmsFallbackPreview = async (customValuesForResolve, selectedCustomerObj) => {
|
|
881
|
+
const fallbackBodyForLiquidPreview =
|
|
882
|
+
getSmsFallbackTextForTagExtraction(smsFallbackContent)
|
|
883
|
+
|| smsFallbackContent?.templateContent
|
|
884
|
+
|| smsFallbackContent?.content
|
|
885
|
+
|| '';
|
|
886
|
+
if (channel !== CHANNELS.RCS || !String(fallbackBodyForLiquidPreview).trim()) return;
|
|
887
|
+
try {
|
|
888
|
+
const smsFallbackPayload = preparePreviewPayload(
|
|
889
|
+
CHANNELS.SMS,
|
|
890
|
+
formData || {},
|
|
891
|
+
fallbackBodyForLiquidPreview,
|
|
892
|
+
customValuesForResolve,
|
|
893
|
+
selectedCustomerObj
|
|
894
|
+
);
|
|
895
|
+
const fallbackResponse = await Api.updateEmailPreview(smsFallbackPayload);
|
|
896
|
+
const fallbackPreview = extractPreviewFromLiquidResponse(fallbackResponse);
|
|
897
|
+
setSmsFallbackPreviewText(
|
|
898
|
+
typeof fallbackPreview?.resolvedBody === 'string'
|
|
899
|
+
? fallbackPreview.resolvedBody
|
|
900
|
+
: undefined
|
|
901
|
+
);
|
|
902
|
+
} catch (e) {
|
|
903
|
+
/* keep existing smsFallbackPreviewText on failure */
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
|
|
618
907
|
/**
|
|
619
908
|
* Prepare payload for tag extraction based on channel
|
|
620
909
|
*/
|
|
@@ -709,7 +998,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
709
998
|
} = carousel || {};
|
|
710
999
|
const buttonData = buttons.map((button, index) => {
|
|
711
1000
|
const {
|
|
712
|
-
type, text, phone_number, urlType, url,
|
|
1001
|
+
type, text, phone_number: phoneNumber, urlType, url,
|
|
713
1002
|
} = button || {};
|
|
714
1003
|
const buttonObj = {
|
|
715
1004
|
type,
|
|
@@ -717,7 +1006,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
717
1006
|
index,
|
|
718
1007
|
};
|
|
719
1008
|
if (type === PHONE_NUMBER) {
|
|
720
|
-
buttonObj.phoneNumber =
|
|
1009
|
+
buttonObj.phoneNumber = phoneNumber;
|
|
721
1010
|
}
|
|
722
1011
|
if (type === URL) {
|
|
723
1012
|
buttonObj.url = url;
|
|
@@ -746,7 +1035,133 @@ const CommonTestAndPreview = (props) => {
|
|
|
746
1035
|
};
|
|
747
1036
|
});
|
|
748
1037
|
|
|
749
|
-
|
|
1038
|
+
/**
|
|
1039
|
+
* Build createMessageMeta payload for RCS (test message).
|
|
1040
|
+
* rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
|
|
1041
|
+
* Then rcsDeliverySettings, executionParams, clientName last.
|
|
1042
|
+
*/
|
|
1043
|
+
const buildRcsTestMessagePayload = (
|
|
1044
|
+
creativeFormData,
|
|
1045
|
+
_unusedEditorContentString,
|
|
1046
|
+
_customValuesObj,
|
|
1047
|
+
deliverySettingsOverride,
|
|
1048
|
+
basePayload,
|
|
1049
|
+
_rcsTestMetaExtras = {},
|
|
1050
|
+
) => {
|
|
1051
|
+
const rcsSectionFromForm =
|
|
1052
|
+
creativeFormData?.versions?.base?.content?.RCS ?? creativeFormData?.content?.RCS ?? {};
|
|
1053
|
+
const rcsContentFromForm = rcsSectionFromForm?.rcsContent || {};
|
|
1054
|
+
const smsFallbackFromCreativeForm = rcsSectionFromForm?.smsFallBackContent || {};
|
|
1055
|
+
let rcsCardPayloadList = [];
|
|
1056
|
+
if (Array.isArray(rcsContentFromForm?.cardContent)) {
|
|
1057
|
+
rcsCardPayloadList = rcsContentFromForm.cardContent;
|
|
1058
|
+
} else if (rcsContentFromForm?.cardContent) {
|
|
1059
|
+
rcsCardPayloadList = [rcsContentFromForm.cardContent];
|
|
1060
|
+
}
|
|
1061
|
+
// Raw title/description with template tags; SMS fallback uses tagged template fields (pickFirst…).
|
|
1062
|
+
const cardContentForTestMetaApi = rcsCardPayloadList.map((singleRcsCardPayload) => {
|
|
1063
|
+
const normalizedCardMediaForTestApi = singleRcsCardPayload?.media
|
|
1064
|
+
? normalizeRcsTestCardMedia(singleRcsCardPayload.media)
|
|
1065
|
+
: undefined;
|
|
1066
|
+
const suggestionsFromCard = Array.isArray(singleRcsCardPayload?.suggestions)
|
|
1067
|
+
? singleRcsCardPayload.suggestions
|
|
1068
|
+
: [];
|
|
1069
|
+
const suggestionsFormattedForTestMeta = suggestionsFromCard.map((suggestionItem, index) =>
|
|
1070
|
+
mapRcsSuggestionForTestMeta(suggestionItem, index));
|
|
1071
|
+
return {
|
|
1072
|
+
title: singleRcsCardPayload?.title ?? '',
|
|
1073
|
+
description: singleRcsCardPayload?.description ?? '',
|
|
1074
|
+
mediaType: singleRcsCardPayload?.mediaType ?? MEDIA_TYPE_TEXT,
|
|
1075
|
+
...(normalizedCardMediaForTestApi && { media: normalizedCardMediaForTestApi }),
|
|
1076
|
+
...(suggestionsFormattedForTestMeta.length > 0 && {
|
|
1077
|
+
suggestions: suggestionsFormattedForTestMeta,
|
|
1078
|
+
}),
|
|
1079
|
+
};
|
|
1080
|
+
});
|
|
1081
|
+
// Use the component-level smsFallbackContent prop (has rcsSmsFallbackVarMapped) so DLT
|
|
1082
|
+
// {#var#} slots are converted to {{tagName}} mustache tags before sending to createMessageMeta.
|
|
1083
|
+
const smsFallbackTaggedTemplateBody =
|
|
1084
|
+
getSmsFallbackTextForTagExtraction(smsFallbackContent)
|
|
1085
|
+
|| pickFirstSmsFallbackTemplateString(smsFallbackFromCreativeForm)
|
|
1086
|
+
|| '';
|
|
1087
|
+
const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
|
|
1088
|
+
? deliverySettingsOverride.cdmaSenderId.split('|')[1]
|
|
1089
|
+
: deliverySettingsOverride?.cdmaSenderId;
|
|
1090
|
+
const deliveryFallbackSmsId =
|
|
1091
|
+
typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
|
|
1092
|
+
const creativeFallbackSmsId =
|
|
1093
|
+
smsFallbackFromCreativeForm?.senderId != null
|
|
1094
|
+
? String(smsFallbackFromCreativeForm.senderId).trim()
|
|
1095
|
+
: '';
|
|
1096
|
+
const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
|
|
1097
|
+
|
|
1098
|
+
const smsFallBackContent =
|
|
1099
|
+
smsFallbackTaggedTemplateBody.trim() !== ''
|
|
1100
|
+
? { message: smsFallbackTaggedTemplateBody }
|
|
1101
|
+
: undefined;
|
|
1102
|
+
|
|
1103
|
+
// accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
|
|
1104
|
+
const accountIdForMeta =
|
|
1105
|
+
rcsContentFromForm?.accountId != null && String(rcsContentFromForm.accountId).trim() !== ''
|
|
1106
|
+
? String(rcsContentFromForm.accountId)
|
|
1107
|
+
: undefined;
|
|
1108
|
+
|
|
1109
|
+
const rcsRichCardContent = {
|
|
1110
|
+
contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
|
|
1111
|
+
cardType: rcsContentFromForm?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
|
|
1112
|
+
cardSettings: rcsContentFromForm?.cardSettings ?? {
|
|
1113
|
+
cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
|
|
1114
|
+
cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
|
|
1115
|
+
},
|
|
1116
|
+
...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
|
|
1117
|
+
};
|
|
1118
|
+
|
|
1119
|
+
const rcsMessageContent = {
|
|
1120
|
+
channel: CHANNELS.RCS,
|
|
1121
|
+
...(accountIdForMeta && { accountId: accountIdForMeta }),
|
|
1122
|
+
rcsRichCardContent,
|
|
1123
|
+
...(smsFallBackContent && { smsFallBackContent }),
|
|
1124
|
+
};
|
|
1125
|
+
const rcsComposite = deliverySettingsOverride?.gsmSenderId ?? '';
|
|
1126
|
+
const [rcsDomainId, rcsSenderId] = rcsComposite.includes('|') ? rcsComposite.split('|') : ['', rcsComposite];
|
|
1127
|
+
const rcsDeliverySettings = {
|
|
1128
|
+
channelSettings: {
|
|
1129
|
+
channel: CHANNELS.RCS,
|
|
1130
|
+
rcsSender: (rcsSenderId || deliverySettingsOverride?.rcsSender) ?? '',
|
|
1131
|
+
domainId:
|
|
1132
|
+
rcsDomainId !== '' && rcsDomainId !== undefined && !Number.isNaN(Number(rcsDomainId))
|
|
1133
|
+
? Number(rcsDomainId)
|
|
1134
|
+
: (deliverySettingsOverride?.domainId ?? 0),
|
|
1135
|
+
fallbackSmsSenderId: fallbackSmsSenderIdForChannel,
|
|
1136
|
+
},
|
|
1137
|
+
additionalSettings: {
|
|
1138
|
+
useTinyUrl: false,
|
|
1139
|
+
encryptUrl: false,
|
|
1140
|
+
linkTrackingEnabled: false,
|
|
1141
|
+
bypassControlUser: false,
|
|
1142
|
+
userSubscriptionDisabled: false,
|
|
1143
|
+
},
|
|
1144
|
+
};
|
|
1145
|
+
const { clientName: baseClientName = CLIENT_NAME_CREATIVES, ...restBase } = basePayload;
|
|
1146
|
+
return {
|
|
1147
|
+
...restBase,
|
|
1148
|
+
rcsMessageContent,
|
|
1149
|
+
rcsDeliverySettings,
|
|
1150
|
+
executionParams: {},
|
|
1151
|
+
clientName: baseClientName,
|
|
1152
|
+
};
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
const prepareTestMessagePayload = (
|
|
1156
|
+
channelType,
|
|
1157
|
+
formDataObj,
|
|
1158
|
+
contentStr,
|
|
1159
|
+
customValuesObj,
|
|
1160
|
+
recipientDetails,
|
|
1161
|
+
previewDataObj,
|
|
1162
|
+
deliverySettingsOverride,
|
|
1163
|
+
rcsExtra = {},
|
|
1164
|
+
) => {
|
|
750
1165
|
// Base payload structure common to all channels
|
|
751
1166
|
const basePayload = {
|
|
752
1167
|
ouId: -1,
|
|
@@ -827,12 +1242,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
827
1242
|
},
|
|
828
1243
|
smsDeliverySettings: {
|
|
829
1244
|
channelSettings: {
|
|
1245
|
+
channel: CHANNELS.SMS,
|
|
830
1246
|
gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
|
|
831
1247
|
domainId: deliverySettingsOverride?.domainId ?? null,
|
|
832
1248
|
domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
|
|
833
1249
|
targetNdnc: false,
|
|
834
1250
|
cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
|
|
835
|
-
channel: CHANNELS.SMS,
|
|
836
1251
|
},
|
|
837
1252
|
additionalSettings: {
|
|
838
1253
|
useTinyUrl: false,
|
|
@@ -976,7 +1391,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
976
1391
|
return {
|
|
977
1392
|
...basePayload,
|
|
978
1393
|
whatsappMessageContent: {
|
|
979
|
-
messageBody: templateEditorValue || '',
|
|
1394
|
+
messageBody: resolvedMessageBody || templateEditorValue || '',
|
|
980
1395
|
accountId: formDataObj?.accountId || '',
|
|
981
1396
|
sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
|
|
982
1397
|
accountName: formDataObj?.accountName || '',
|
|
@@ -1001,16 +1416,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
1001
1416
|
}
|
|
1002
1417
|
|
|
1003
1418
|
case CHANNELS.RCS:
|
|
1004
|
-
return
|
|
1005
|
-
...basePayload,
|
|
1006
|
-
rcsMessageContent: {
|
|
1007
|
-
channel: CHANNELS.RCS,
|
|
1008
|
-
messageBody: contentStr,
|
|
1009
|
-
rcsType: additionalProps?.rcsType,
|
|
1010
|
-
rcsImageSrc: formDataObj?.rcsImageSrc,
|
|
1011
|
-
rcsSuggestions: formDataObj?.rcsSuggestions,
|
|
1012
|
-
},
|
|
1013
|
-
};
|
|
1419
|
+
return buildRcsTestMessagePayload(formDataObj, contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra);
|
|
1014
1420
|
|
|
1015
1421
|
case CHANNELS.INAPP: {
|
|
1016
1422
|
// InApp payload structure similar to MobilePush
|
|
@@ -2112,6 +2518,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2112
2518
|
formatMessage,
|
|
2113
2519
|
lastModified: formData?.lastModified,
|
|
2114
2520
|
updatedByName: formData?.updatedByName,
|
|
2521
|
+
smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
|
|
2522
|
+
smsFallbackResolvedText,
|
|
2523
|
+
activePreviewTab,
|
|
2524
|
+
onPreviewTabChange: setActivePreviewTab,
|
|
2115
2525
|
};
|
|
2116
2526
|
};
|
|
2117
2527
|
|
|
@@ -2151,7 +2561,12 @@ const CommonTestAndPreview = (props) => {
|
|
|
2151
2561
|
}, [show, beeInstance, currentTab, channel]);
|
|
2152
2562
|
|
|
2153
2563
|
/**
|
|
2154
|
-
* Initial data load when slidebox opens
|
|
2564
|
+
* Initial data load when slidebox opens.
|
|
2565
|
+
* EXTRACT TAGS CALL SITES (on open/edit RCS):
|
|
2566
|
+
* 1. Here (non-email): actions.extractTagsRequested() at line ~2161 when show is true.
|
|
2567
|
+
* 2. RCS SMS fallback useEffect below: Api.extractTagsWithMetaData() for fallback message.
|
|
2568
|
+
* 3. handleExtractTags() (user clicks "Enter custom values for tags") – not from effects.
|
|
2569
|
+
* The "Process extracted tags" effect only processes API results and must not call extract again.
|
|
2155
2570
|
*/
|
|
2156
2571
|
useEffect(() => {
|
|
2157
2572
|
if (show) {
|
|
@@ -2236,7 +2651,61 @@ const CommonTestAndPreview = (props) => {
|
|
|
2236
2651
|
actions.getTestGroupsRequested();
|
|
2237
2652
|
}
|
|
2238
2653
|
}
|
|
2239
|
-
|
|
2654
|
+
// getCurrentContent: RCS applies cardVarMapped → placeholder resolution; re-extract when it changes.
|
|
2655
|
+
}, [show, beeInstance, currentTab, channel, getCurrentContent]);
|
|
2656
|
+
|
|
2657
|
+
/**
|
|
2658
|
+
* RCS with SMS fallback: extract tags for fallback SMS content as well
|
|
2659
|
+
* (so we can show a separate "Fallback SMS tags" section in left panel).
|
|
2660
|
+
*/
|
|
2661
|
+
useEffect(() => {
|
|
2662
|
+
let cancelled = false;
|
|
2663
|
+
|
|
2664
|
+
if (!show || channel !== CHANNELS.RCS) {
|
|
2665
|
+
return () => {
|
|
2666
|
+
cancelled = true;
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
if (!smsFallbackContent?.templateContent && !smsFallbackContent?.content) {
|
|
2671
|
+
setSmsFallbackExtractedTags([]);
|
|
2672
|
+
setSmsFallbackRequiredTags([]);
|
|
2673
|
+
setSmsFallbackOptionalTags([]);
|
|
2674
|
+
return () => {
|
|
2675
|
+
cancelled = true;
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
setIsExtractingSmsFallbackTags(true);
|
|
2680
|
+
(async () => {
|
|
2681
|
+
try {
|
|
2682
|
+
const fallbackBodyForExtractApi = getSmsFallbackTextForTagExtraction(smsFallbackContent);
|
|
2683
|
+
const payload = {
|
|
2684
|
+
messageTitle: '',
|
|
2685
|
+
messageBody: fallbackBodyForExtractApi || '',
|
|
2686
|
+
};
|
|
2687
|
+
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
|
|
2688
|
+
let smsFallbackTagTree = response?.data ?? [];
|
|
2689
|
+
if (!Array.isArray(smsFallbackTagTree)) smsFallbackTagTree = [];
|
|
2690
|
+
if (!smsTemplateHasMustacheTags(fallbackBodyForExtractApi)) {
|
|
2691
|
+
smsFallbackTagTree = [];
|
|
2692
|
+
} else if (smsFallbackTagTree.length === 0) {
|
|
2693
|
+
smsFallbackTagTree = buildSyntheticSmsMustacheTags(fallbackBodyForExtractApi);
|
|
2694
|
+
}
|
|
2695
|
+
if (cancelled) return;
|
|
2696
|
+
setSmsFallbackExtractedTags(smsFallbackTagTree);
|
|
2697
|
+
} catch (e) {
|
|
2698
|
+
if (cancelled) return;
|
|
2699
|
+
setSmsFallbackExtractedTags([]);
|
|
2700
|
+
} finally {
|
|
2701
|
+
if (!cancelled) setIsExtractingSmsFallbackTags(false);
|
|
2702
|
+
}
|
|
2703
|
+
})();
|
|
2704
|
+
|
|
2705
|
+
return () => {
|
|
2706
|
+
cancelled = true;
|
|
2707
|
+
};
|
|
2708
|
+
}, [show, channel, smsFallbackContent]);
|
|
2240
2709
|
|
|
2241
2710
|
/**
|
|
2242
2711
|
* Email-specific: Handle content updates for both BEE and CKEditor
|
|
@@ -2288,15 +2757,22 @@ const CommonTestAndPreview = (props) => {
|
|
|
2288
2757
|
setSelectedCustomer(null);
|
|
2289
2758
|
setRequiredTags([]);
|
|
2290
2759
|
setOptionalTags([]);
|
|
2760
|
+
setSmsFallbackExtractedTags([]);
|
|
2761
|
+
setSmsFallbackRequiredTags([]);
|
|
2762
|
+
setSmsFallbackOptionalTags([]);
|
|
2763
|
+
setIsExtractingSmsFallbackTags(false);
|
|
2291
2764
|
setCustomValues({});
|
|
2292
2765
|
setShowJSON(false);
|
|
2293
2766
|
setTagsExtracted(false);
|
|
2294
2767
|
setPreviewDevice(DESKTOP);
|
|
2768
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2769
|
+
setSmsFallbackPreviewText(undefined);
|
|
2295
2770
|
setSelectedTestEntities([]);
|
|
2296
2771
|
actions.clearPrefilledValues();
|
|
2297
2772
|
} else {
|
|
2298
2773
|
// Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
|
|
2299
2774
|
setPreviewDevice(initialDevice);
|
|
2775
|
+
setActivePreviewTab(PREVIEW_TAB_RCS);
|
|
2300
2776
|
}
|
|
2301
2777
|
}, [show, initialDevice]);
|
|
2302
2778
|
|
|
@@ -2305,79 +2781,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2305
2781
|
*/
|
|
2306
2782
|
useEffect(() => {
|
|
2307
2783
|
if (previewData) {
|
|
2308
|
-
setPreviewDataHtml(previewData);
|
|
2784
|
+
setPreviewDataHtml(toPlainPreviewData(previewData));
|
|
2309
2785
|
}
|
|
2310
2786
|
}, [previewData]);
|
|
2311
2787
|
|
|
2312
|
-
/**
|
|
2313
|
-
* Process extracted tags and categorize them
|
|
2314
|
-
*/
|
|
2315
|
-
useEffect(() => {
|
|
2316
|
-
// Categorize tags into required and optional
|
|
2317
|
-
const required = [];
|
|
2318
|
-
const optional = [];
|
|
2319
|
-
let hasPersonalizationTags = false;
|
|
2320
|
-
|
|
2321
|
-
if (extractedTags?.length > 0) {
|
|
2322
|
-
const processTag = (tag, parentPath = '') => {
|
|
2323
|
-
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
2324
|
-
|
|
2325
|
-
// Skip unsubscribe tag for input fields
|
|
2326
|
-
if (tag?.name === UNSUBSCRIBE_TAG_NAME) {
|
|
2327
|
-
return;
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
hasPersonalizationTags = true;
|
|
2331
|
-
|
|
2332
|
-
if (tag?.metaData?.userDriven === false) {
|
|
2333
|
-
required.push({
|
|
2334
|
-
...tag,
|
|
2335
|
-
fullPath: currentPath,
|
|
2336
|
-
});
|
|
2337
|
-
} else if (tag?.metaData?.userDriven === true) {
|
|
2338
|
-
optional.push({
|
|
2339
|
-
...tag,
|
|
2340
|
-
fullPath: currentPath,
|
|
2341
|
-
});
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
if (tag?.children?.length > 0) {
|
|
2345
|
-
tag.children.forEach((child) => processTag(child, currentPath));
|
|
2346
|
-
}
|
|
2347
|
-
};
|
|
2348
|
-
|
|
2349
|
-
extractedTags.forEach((tag) => processTag(tag));
|
|
2350
|
-
|
|
2351
|
-
if (hasPersonalizationTags) {
|
|
2352
|
-
setRequiredTags(required);
|
|
2353
|
-
setOptionalTags(optional);
|
|
2354
|
-
setTagsExtracted(true); // Mark tags as extracted and processed
|
|
2355
|
-
|
|
2356
|
-
// Initialize custom values for required tags
|
|
2357
|
-
const initialValues = {};
|
|
2358
|
-
required.forEach((tag) => {
|
|
2359
|
-
initialValues[tag?.fullPath] = '';
|
|
2360
|
-
});
|
|
2361
|
-
optional.forEach((tag) => {
|
|
2362
|
-
initialValues[tag?.fullPath] = '';
|
|
2363
|
-
});
|
|
2364
|
-
setCustomValues(initialValues);
|
|
2365
|
-
} else {
|
|
2366
|
-
// Reset all tag-related state if no personalization tags
|
|
2367
|
-
setRequiredTags([]);
|
|
2368
|
-
setOptionalTags([]);
|
|
2369
|
-
setCustomValues({});
|
|
2370
|
-
setTagsExtracted(false);
|
|
2371
|
-
}
|
|
2372
|
-
} else {
|
|
2373
|
-
// Reset all tag-related state if no tags
|
|
2374
|
-
setRequiredTags([]);
|
|
2375
|
-
setOptionalTags([]);
|
|
2376
|
-
setCustomValues({});
|
|
2377
|
-
setTagsExtracted(false);
|
|
2378
|
-
}
|
|
2379
|
-
}, [extractedTags]);
|
|
2380
|
-
|
|
2381
2788
|
/**
|
|
2382
2789
|
* Handle customer selection and fetch prefilled values
|
|
2383
2790
|
*/
|
|
@@ -2385,17 +2792,15 @@ const CommonTestAndPreview = (props) => {
|
|
|
2385
2792
|
if (selectedCustomer && config.enableCustomerSearch !== false) {
|
|
2386
2793
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2387
2794
|
|
|
2388
|
-
// Get all available tags
|
|
2389
|
-
const allTags = [...requiredTags, ...optionalTags];
|
|
2390
2795
|
const requiredTagObj = {};
|
|
2391
|
-
|
|
2796
|
+
allRequiredTags.forEach((tag) => {
|
|
2392
2797
|
requiredTagObj[tag?.fullPath] = '';
|
|
2393
2798
|
});
|
|
2394
2799
|
if (allTags.length > 0) {
|
|
2395
2800
|
const payload = preparePreviewPayload(
|
|
2396
|
-
|
|
2801
|
+
activeChannelForActions,
|
|
2397
2802
|
formData || {},
|
|
2398
|
-
|
|
2803
|
+
activeContentForActions,
|
|
2399
2804
|
{
|
|
2400
2805
|
...requiredTagObj,
|
|
2401
2806
|
},
|
|
@@ -2404,7 +2809,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2404
2809
|
actions.getPrefilledValuesRequested(payload);
|
|
2405
2810
|
}
|
|
2406
2811
|
}
|
|
2407
|
-
}, [selectedCustomer]);
|
|
2812
|
+
}, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
|
|
2408
2813
|
|
|
2409
2814
|
/**
|
|
2410
2815
|
* Update custom values with prefilled values from API
|
|
@@ -2415,24 +2820,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
2415
2820
|
if (prefilledValues && selectedCustomer) {
|
|
2416
2821
|
// Always replace all values with prefilled values
|
|
2417
2822
|
const updatedValues = {};
|
|
2418
|
-
|
|
2823
|
+
allTags.forEach((tag) => {
|
|
2419
2824
|
updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
|
|
2420
2825
|
});
|
|
2421
2826
|
|
|
2422
2827
|
setCustomValues(updatedValues);
|
|
2423
2828
|
|
|
2424
2829
|
// Update preview with prefilled values (this is a valid preview call trigger)
|
|
2830
|
+
// For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
|
|
2831
|
+
// SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
|
|
2832
|
+
const previewChannelForPrefill = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
|
|
2833
|
+
const previewContentForPrefill = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
|
|
2425
2834
|
const payload = preparePreviewPayload(
|
|
2426
|
-
|
|
2835
|
+
previewChannelForPrefill,
|
|
2427
2836
|
formData || {},
|
|
2428
|
-
|
|
2837
|
+
previewContentForPrefill,
|
|
2429
2838
|
updatedValues,
|
|
2430
2839
|
selectedCustomer
|
|
2431
2840
|
);
|
|
2432
2841
|
actions.updatePreviewRequested(payload);
|
|
2433
2842
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2843
|
+
void syncSmsFallbackPreview(updatedValues, selectedCustomer);
|
|
2434
2844
|
}
|
|
2435
|
-
}, [JSON.stringify(prefilledValues), selectedCustomer]);
|
|
2845
|
+
}, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
|
|
2436
2846
|
|
|
2437
2847
|
/**
|
|
2438
2848
|
* Map channel constants to display names (lowercase for message)
|
|
@@ -2526,11 +2936,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2526
2936
|
setTagsExtracted(true); // Auto-open custom values editor
|
|
2527
2937
|
|
|
2528
2938
|
// Clear any existing values while waiting for prefilled values
|
|
2529
|
-
|
|
2530
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2531
|
-
emptyValues[tag?.fullPath] = '';
|
|
2532
|
-
});
|
|
2533
|
-
setCustomValues(emptyValues);
|
|
2939
|
+
setCustomValues(buildEmptyValues());
|
|
2534
2940
|
};
|
|
2535
2941
|
|
|
2536
2942
|
/**
|
|
@@ -2544,11 +2950,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2544
2950
|
actions.clearPreviewErrors();
|
|
2545
2951
|
|
|
2546
2952
|
// Initialize empty values for all tags
|
|
2547
|
-
|
|
2548
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2549
|
-
emptyValues[tag?.fullPath] = '';
|
|
2550
|
-
});
|
|
2551
|
-
setCustomValues(emptyValues);
|
|
2953
|
+
setCustomValues(buildEmptyValues());
|
|
2552
2954
|
|
|
2553
2955
|
// Don't make preview call when clearing selection - just reset to raw content
|
|
2554
2956
|
// Preview will be shown using raw formData/content
|
|
@@ -2584,17 +2986,20 @@ const CommonTestAndPreview = (props) => {
|
|
|
2584
2986
|
*/
|
|
2585
2987
|
const handleDiscardCustomValues = () => {
|
|
2586
2988
|
// Initialize empty values for all tags
|
|
2587
|
-
const emptyValues =
|
|
2588
|
-
[...requiredTags, ...optionalTags].forEach((tag) => {
|
|
2589
|
-
emptyValues[tag?.fullPath] = '';
|
|
2590
|
-
});
|
|
2989
|
+
const emptyValues = buildEmptyValues();
|
|
2591
2990
|
setCustomValues(emptyValues);
|
|
2592
2991
|
|
|
2992
|
+
// Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
|
|
2993
|
+
setSmsFallbackPreviewText(undefined);
|
|
2994
|
+
|
|
2593
2995
|
// Update preview with empty values (this is a valid preview call trigger)
|
|
2996
|
+
// For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
|
|
2997
|
+
const previewChannelForDiscard = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
|
|
2998
|
+
const previewContentForDiscard = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
|
|
2594
2999
|
const payload = preparePreviewPayload(
|
|
2595
|
-
|
|
3000
|
+
previewChannelForDiscard,
|
|
2596
3001
|
formData || {},
|
|
2597
|
-
|
|
3002
|
+
previewContentForDiscard,
|
|
2598
3003
|
emptyValues,
|
|
2599
3004
|
selectedCustomer
|
|
2600
3005
|
);
|
|
@@ -2608,14 +3013,21 @@ const CommonTestAndPreview = (props) => {
|
|
|
2608
3013
|
*/
|
|
2609
3014
|
const handleUpdatePreview = async () => {
|
|
2610
3015
|
try {
|
|
3016
|
+
// For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped,
|
|
3017
|
+
// even when the user triggers update from the SMS fallback tab.
|
|
3018
|
+
// SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
|
|
3019
|
+
const previewChannel = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
|
|
3020
|
+
const previewContent = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
|
|
2611
3021
|
const payload = preparePreviewPayload(
|
|
2612
|
-
|
|
3022
|
+
previewChannel,
|
|
2613
3023
|
formData || {},
|
|
2614
|
-
|
|
3024
|
+
previewContent,
|
|
2615
3025
|
customValues,
|
|
2616
3026
|
selectedCustomer
|
|
2617
3027
|
);
|
|
2618
3028
|
await actions.updatePreviewRequested(payload);
|
|
3029
|
+
|
|
3030
|
+
await syncSmsFallbackPreview(customValues, selectedCustomer);
|
|
2619
3031
|
setHasPreviewCallBeenMade(true); // Mark that preview call was made
|
|
2620
3032
|
} catch (error) {
|
|
2621
3033
|
CapNotification.error({
|
|
@@ -2625,25 +3037,115 @@ const CommonTestAndPreview = (props) => {
|
|
|
2625
3037
|
};
|
|
2626
3038
|
|
|
2627
3039
|
/**
|
|
2628
|
-
*
|
|
3040
|
+
* Categorize extracted tags into required/optional.
|
|
2629
3041
|
*/
|
|
2630
|
-
const
|
|
2631
|
-
|
|
2632
|
-
|
|
3042
|
+
const categorizeTags = (tagsTree = []) => {
|
|
3043
|
+
const required = [];
|
|
3044
|
+
const optional = [];
|
|
3045
|
+
let hasPersonalizationTags = false;
|
|
3046
|
+
const processTag = (tag, parentPath = '') => {
|
|
3047
|
+
const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
|
|
3048
|
+
|
|
3049
|
+
// Skip unsubscribe tag for input fields
|
|
3050
|
+
if (tag?.name === UNSUBSCRIBE_TAG_NAME) return;
|
|
3051
|
+
|
|
3052
|
+
hasPersonalizationTags = true;
|
|
3053
|
+
const userDriven = tag?.metaData?.userDriven;
|
|
3054
|
+
if (userDriven === true) {
|
|
3055
|
+
optional.push({ ...tag, fullPath: currentPath });
|
|
3056
|
+
} else {
|
|
3057
|
+
// false or missing (SMS/DLT extract often omits metaData) → required for test values
|
|
3058
|
+
required.push({ ...tag, fullPath: currentPath });
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
if (tag?.children?.length > 0) {
|
|
3062
|
+
tag.children.forEach((child) => processTag(child, currentPath));
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
|
|
3066
|
+
(tagsTree || []).forEach((tag) => processTag(tag));
|
|
3067
|
+
return { required, optional, hasPersonalizationTags };
|
|
3068
|
+
};
|
|
3069
|
+
|
|
3070
|
+
/**
|
|
3071
|
+
* Apply tag extraction when content comes from RCS + SMS fallback (no API call).
|
|
3072
|
+
*/
|
|
3073
|
+
const applyRcsSmsFallbackTagExtraction = () => {
|
|
3074
|
+
const rcsPrimaryCategorized = categorizeTags(extractedTags ?? []);
|
|
3075
|
+
const fallbackSmsResolvedForTags = smsFallbackTextForTagExtraction ?? '';
|
|
3076
|
+
let fallbackSmsTagTree = smsFallbackExtractedTags?.length > 0
|
|
3077
|
+
? smsFallbackExtractedTags
|
|
3078
|
+
: buildSyntheticSmsMustacheTags(fallbackSmsResolvedForTags);
|
|
3079
|
+
if (!smsTemplateHasMustacheTags(fallbackSmsResolvedForTags)) {
|
|
3080
|
+
fallbackSmsTagTree = [];
|
|
3081
|
+
}
|
|
3082
|
+
const fallbackSmsCategorized = categorizeTags(fallbackSmsTagTree);
|
|
3083
|
+
setRequiredTags(rcsPrimaryCategorized.required);
|
|
3084
|
+
setOptionalTags(rcsPrimaryCategorized.optional);
|
|
3085
|
+
setSmsFallbackRequiredTags(fallbackSmsCategorized.required);
|
|
3086
|
+
setSmsFallbackOptionalTags(fallbackSmsCategorized.optional);
|
|
3087
|
+
setTagsExtracted(
|
|
3088
|
+
rcsPrimaryCategorized.hasPersonalizationTags || fallbackSmsCategorized.hasPersonalizationTags,
|
|
3089
|
+
);
|
|
3090
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, rcsPrimaryCategorized, fallbackSmsCategorized));
|
|
3091
|
+
};
|
|
2633
3092
|
|
|
3093
|
+
/**
|
|
3094
|
+
* When extract-tags API returns, map Redux `extractedTags` into required/optional so
|
|
3095
|
+
* CustomValuesEditor shows personalization fields (effect was previously commented out).
|
|
3096
|
+
* RCS + SMS fallback: merge primary + fallback tag trees when fallback template exists.
|
|
3097
|
+
*/
|
|
3098
|
+
useEffect(() => {
|
|
3099
|
+
if (!show) return;
|
|
3100
|
+
const hasFallbackSmsTemplate = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
|
|
3101
|
+
if (channel === CHANNELS.RCS && hasFallbackSmsTemplate) {
|
|
3102
|
+
applyRcsSmsFallbackTagExtraction();
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
|
|
3106
|
+
let smsTagSource = channel === CHANNELS.SMS && (!extractedTags || extractedTags.length === 0)
|
|
3107
|
+
? buildSyntheticSmsMustacheTags(smsEditorBody)
|
|
3108
|
+
: (extractedTags ?? []);
|
|
3109
|
+
if (channel === CHANNELS.SMS && !smsTemplateHasMustacheTags(smsEditorBody)) {
|
|
3110
|
+
smsTagSource = [];
|
|
3111
|
+
}
|
|
3112
|
+
const { required, optional, hasPersonalizationTags } = categorizeTags(smsTagSource);
|
|
3113
|
+
setRequiredTags(required);
|
|
3114
|
+
setOptionalTags(optional);
|
|
3115
|
+
setTagsExtracted(hasPersonalizationTags);
|
|
3116
|
+
setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, { required, optional }, { required: [], optional: [] }));
|
|
3117
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- applyRcsSmsFallbackTagExtraction closes over latest extractedTags/smsFallbackExtractedTags
|
|
3118
|
+
}, [show, extractedTags, channel, smsFallbackContent, smsFallbackExtractedTags, getCurrentContent, smsFallbackTextForTagExtraction]);
|
|
3119
|
+
|
|
3120
|
+
/**
|
|
3121
|
+
* Get content to run tag extraction on (channel-specific).
|
|
3122
|
+
*/
|
|
3123
|
+
const getContentForTagExtraction = () => {
|
|
3124
|
+
let contentToExtract = activeContentForActions;
|
|
2634
3125
|
if (channel === CHANNELS.EMAIL && formData) {
|
|
2635
3126
|
const currentTabData = formData[currentTab - 1];
|
|
2636
3127
|
const activeTab = currentTabData?.activeTab;
|
|
2637
3128
|
const templateContent = currentTabData?.[activeTab]?.['template-content'];
|
|
2638
3129
|
contentToExtract = templateContent || contentToExtract;
|
|
2639
3130
|
}
|
|
3131
|
+
return contentToExtract;
|
|
3132
|
+
};
|
|
2640
3133
|
|
|
2641
|
-
|
|
3134
|
+
/**
|
|
3135
|
+
* Handle extract tags
|
|
3136
|
+
*/
|
|
3137
|
+
const handleExtractTags = () => {
|
|
3138
|
+
if (channel === CHANNELS.RCS) {
|
|
3139
|
+
applyRcsSmsFallbackTagExtraction();
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
|
|
3143
|
+
const contentToExtract = getContentForTagExtraction();
|
|
2642
3144
|
const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
|
|
2643
3145
|
const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
|
|
3146
|
+
const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
|
|
2644
3147
|
|
|
2645
|
-
if (
|
|
2646
|
-
// If only unsubscribe tag is present, show noTagsExtracted message
|
|
3148
|
+
if (onlyUnsubscribe) {
|
|
2647
3149
|
setTagsExtracted(false);
|
|
2648
3150
|
setRequiredTags([]);
|
|
2649
3151
|
setOptionalTags([]);
|
|
@@ -2651,10 +3153,9 @@ const CommonTestAndPreview = (props) => {
|
|
|
2651
3153
|
return;
|
|
2652
3154
|
}
|
|
2653
3155
|
|
|
2654
|
-
// Extract tags
|
|
2655
3156
|
setTagsExtracted(true);
|
|
2656
3157
|
const { templateSubject, templateContent } = prepareTagExtractionPayload(
|
|
2657
|
-
|
|
3158
|
+
activeChannelForActions,
|
|
2658
3159
|
formData || {},
|
|
2659
3160
|
contentToExtract
|
|
2660
3161
|
);
|
|
@@ -2716,7 +3217,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2716
3217
|
if (existingTestCustomer) {
|
|
2717
3218
|
const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
|
|
2718
3219
|
if (entityId != null) {
|
|
2719
|
-
const id =
|
|
3220
|
+
const id = normalizeTestEntityId(entityId);
|
|
2720
3221
|
setSelectedTestEntities((prev) => (
|
|
2721
3222
|
prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
|
|
2722
3223
|
));
|
|
@@ -2753,7 +3254,7 @@ const CommonTestAndPreview = (props) => {
|
|
|
2753
3254
|
(c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
|
|
2754
3255
|
);
|
|
2755
3256
|
if (alreadyInTestListByCustomerId) {
|
|
2756
|
-
const id =
|
|
3257
|
+
const id = normalizeTestEntityId(customerIdFromLookup);
|
|
2757
3258
|
setSelectedTestEntities((prev) => (
|
|
2758
3259
|
prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
|
|
2759
3260
|
));
|
|
@@ -2790,26 +3291,21 @@ const CommonTestAndPreview = (props) => {
|
|
|
2790
3291
|
const handleSendTestMessage = () => {
|
|
2791
3292
|
const allUserIds = [];
|
|
2792
3293
|
selectedTestEntities.forEach((entityId) => {
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
if (group) {
|
|
2797
|
-
allUserIds.push(...group.userIds);
|
|
2798
|
-
}
|
|
3294
|
+
const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, entityId));
|
|
3295
|
+
if (group) {
|
|
3296
|
+
allUserIds.push(...group.userIds);
|
|
2799
3297
|
} else {
|
|
2800
|
-
|
|
2801
|
-
? String(entityId).slice('customer:'.length)
|
|
2802
|
-
: String(entityId);
|
|
2803
|
-
allUserIds.push(Number(rawId));
|
|
3298
|
+
allUserIds.push(entityId);
|
|
2804
3299
|
}
|
|
2805
3300
|
});
|
|
2806
3301
|
const uniqueUserIds = [...new Set(allUserIds)];
|
|
2807
3302
|
|
|
2808
|
-
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
|
|
3303
|
+
const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
|
|
2809
3304
|
? testPreviewDeliverySettings[channel]
|
|
2810
3305
|
: null;
|
|
2811
3306
|
|
|
2812
|
-
//
|
|
3307
|
+
// createMessageMeta must match the creative channel and full creative (RCS + SMS fallback in one meta).
|
|
3308
|
+
// Do not use activeChannelForActions / activeContentForActions — those follow the RCS vs Fallback SMS *preview* tab.
|
|
2813
3309
|
const initialPayload = prepareTestMessagePayload(
|
|
2814
3310
|
channel,
|
|
2815
3311
|
formData || content || {},
|
|
@@ -2817,7 +3313,8 @@ const CommonTestAndPreview = (props) => {
|
|
|
2817
3313
|
customValues,
|
|
2818
3314
|
uniqueUserIds,
|
|
2819
3315
|
previewData,
|
|
2820
|
-
deliveryOverride
|
|
3316
|
+
deliveryOverride,
|
|
3317
|
+
{},
|
|
2821
3318
|
);
|
|
2822
3319
|
|
|
2823
3320
|
actions.createMessageMetaRequested(
|
|
@@ -2850,11 +3347,10 @@ const CommonTestAndPreview = (props) => {
|
|
|
2850
3347
|
// ============================================
|
|
2851
3348
|
// RENDER HELPER FUNCTIONS
|
|
2852
3349
|
// ============================================
|
|
2853
|
-
|
|
2854
3350
|
const renderLeftPanelContent = () => (
|
|
2855
3351
|
<LeftPanelContent
|
|
2856
|
-
isExtractingTags={isExtractingTags}
|
|
2857
|
-
extractedTags={
|
|
3352
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
3353
|
+
extractedTags={leftPanelExtractedTags}
|
|
2858
3354
|
selectedCustomer={selectedCustomer}
|
|
2859
3355
|
handleCustomerSelect={handleCustomerSelect}
|
|
2860
3356
|
handleSearchCustomer={handleSearchCustomer}
|
|
@@ -2872,15 +3368,29 @@ const CommonTestAndPreview = (props) => {
|
|
|
2872
3368
|
|
|
2873
3369
|
const renderCustomValuesEditor = () => (
|
|
2874
3370
|
<CustomValuesEditor
|
|
2875
|
-
isExtractingTags={isExtractingTags}
|
|
3371
|
+
isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
|
|
2876
3372
|
isUpdatePreviewDisabled={isUpdatePreviewDisabled}
|
|
2877
3373
|
showJSON={showJSON}
|
|
2878
3374
|
setShowJSON={setShowJSON}
|
|
2879
3375
|
customValues={customValues}
|
|
2880
3376
|
handleJSONTextChange={handleJSONTextChange}
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
3377
|
+
sections={[
|
|
3378
|
+
{
|
|
3379
|
+
key: channel,
|
|
3380
|
+
title:
|
|
3381
|
+
channel === CHANNELS.RCS
|
|
3382
|
+
? messages.rcsTagsSectionTitle
|
|
3383
|
+
: messages[`${channel}TagsSectionTitle`],
|
|
3384
|
+
requiredTags,
|
|
3385
|
+
optionalTags,
|
|
3386
|
+
},
|
|
3387
|
+
{
|
|
3388
|
+
key: PREVIEW_TAB_SMS_FALLBACK,
|
|
3389
|
+
title: isRcsSmsFallbackPreviewEnabled ? messages.smsFallbackTagsSectionTitle : null,
|
|
3390
|
+
requiredTags: smsFallbackRequiredTags,
|
|
3391
|
+
optionalTags: smsFallbackOptionalTags,
|
|
3392
|
+
},
|
|
3393
|
+
]}
|
|
2884
3394
|
handleCustomValueChange={handleCustomValueChange}
|
|
2885
3395
|
handleDiscardCustomValues={handleDiscardCustomValues}
|
|
2886
3396
|
handleUpdatePreview={handleUpdatePreview}
|
|
@@ -2896,18 +3406,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
2896
3406
|
}));
|
|
2897
3407
|
};
|
|
2898
3408
|
|
|
2899
|
-
/** Trim pasted emails (trailing CR/LF).
|
|
3409
|
+
/** Trim pasted emails (trailing CR/LF). Allow any input for SMS; valid-only check is in renderAddTestCustomerButton. */
|
|
2900
3410
|
const handleTestCustomersSearch = useCallback((value) => {
|
|
2901
3411
|
if (value == null || value === '') {
|
|
2902
3412
|
setSearchValue('');
|
|
2903
3413
|
return;
|
|
2904
3414
|
}
|
|
2905
|
-
|
|
2906
|
-
if (channel === CHANNELS.SMS) {
|
|
2907
|
-
setSearchValue(formatPhoneNumber(raw));
|
|
2908
|
-
} else {
|
|
2909
|
-
setSearchValue(raw);
|
|
2910
|
-
}
|
|
3415
|
+
setSearchValue(String(value).trim());
|
|
2911
3416
|
}, [channel]);
|
|
2912
3417
|
|
|
2913
3418
|
const renderSendTestMessage = () => (
|
|
@@ -2925,12 +3430,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
2925
3430
|
renderAddTestCustomerButton={renderAddTestCustomerButton}
|
|
2926
3431
|
formatMessage={formatMessage}
|
|
2927
3432
|
deliverySettings={testPreviewDeliverySettings[channel]}
|
|
2928
|
-
|
|
3433
|
+
senderDetailsByChannel={senderDetailsByChannel}
|
|
2929
3434
|
wecrmAccounts={wecrmAccounts}
|
|
2930
3435
|
onSaveDeliverySettings={handleSaveDeliverySettings}
|
|
2931
3436
|
isLoadingSenderDetails={isLoadingSenderDetails}
|
|
2932
3437
|
smsTraiDltEnabled={smsTraiDltEnabled}
|
|
2933
3438
|
registeredSenderIds={registeredSenderIds}
|
|
3439
|
+
isChannelSmsFallbackPreviewEnabled={isRcsSmsFallbackPreviewEnabled}
|
|
2934
3440
|
searchValue={searchValue}
|
|
2935
3441
|
setSearchValue={handleTestCustomersSearch}
|
|
2936
3442
|
/>
|
|
@@ -2944,14 +3450,13 @@ const CommonTestAndPreview = (props) => {
|
|
|
2944
3450
|
|
|
2945
3451
|
const renderAddTestCustomerButton = () => {
|
|
2946
3452
|
const raw = (searchValue || '').trim();
|
|
2947
|
-
const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
|
|
2948
3453
|
const showAddButton =
|
|
2949
3454
|
[CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
|
|
2950
|
-
(channel === CHANNELS.EMAIL ? isValidEmail(
|
|
3455
|
+
(channel === CHANNELS.EMAIL ? isValidEmail(raw) : isValidMobile(formatPhoneNumber(raw)));
|
|
2951
3456
|
if (!showAddButton) return null;
|
|
2952
3457
|
return (
|
|
2953
3458
|
<AddTestCustomerButton
|
|
2954
|
-
searchValue={
|
|
3459
|
+
searchValue={channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw}
|
|
2955
3460
|
handleAddTestCustomer={handleAddTestCustomer}
|
|
2956
3461
|
/>
|
|
2957
3462
|
);
|