@capillarytech/creatives-library 8.0.353-alpha.6 → 8.0.354

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.
Files changed (127) hide show
  1. package/constants/unified.js +0 -29
  2. package/index.html +1 -0
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +20 -35
  5. package/utils/cdnTransformation.js +63 -3
  6. package/utils/commonUtils.js +1 -19
  7. package/utils/tests/cdnTransformation.test.js +111 -0
  8. package/v2Components/CapActionButton/constants.js +0 -7
  9. package/v2Components/CapActionButton/index.js +108 -166
  10. package/v2Components/CapActionButton/index.scss +6 -157
  11. package/v2Components/CapActionButton/messages.js +3 -19
  12. package/v2Components/CapActionButton/tests/index.test.js +17 -41
  13. package/v2Components/CapTagList/index.js +0 -10
  14. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -72
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -213
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  21. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  22. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -157
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -346
  24. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  25. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  26. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  27. package/v2Components/CommonTestAndPreview/index.js +186 -691
  28. package/v2Components/CommonTestAndPreview/messages.js +3 -45
  29. package/v2Components/CommonTestAndPreview/sagas.js +6 -25
  30. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
  31. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  32. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  33. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  34. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  35. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  36. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  37. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  38. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
  39. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +26 -36
  40. package/v2Components/FormBuilder/index.js +6 -11
  41. package/v2Components/TemplatePreview/_templatePreview.scss +23 -38
  42. package/v2Components/TemplatePreview/index.js +31 -143
  43. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  44. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  45. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  46. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  47. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  48. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  49. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  50. package/v2Containers/CreativesContainer/constants.js +0 -9
  51. package/v2Containers/CreativesContainer/index.js +103 -322
  52. package/v2Containers/CreativesContainer/index.scss +1 -51
  53. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  54. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  55. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  56. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  57. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  58. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  59. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  60. package/v2Containers/Rcs/constants.js +10 -119
  61. package/v2Containers/Rcs/index.js +818 -2450
  62. package/v2Containers/Rcs/index.scss +8 -280
  63. package/v2Containers/Rcs/messages.js +3 -34
  64. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
  65. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  66. package/v2Containers/Rcs/tests/index.test.js +121 -152
  67. package/v2Containers/Rcs/tests/mockData.js +0 -38
  68. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  69. package/v2Containers/Rcs/utils.js +11 -478
  70. package/v2Containers/Sms/Create/index.js +40 -106
  71. package/v2Containers/SmsTrai/Create/index.js +4 -9
  72. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  73. package/v2Containers/SmsTrai/Edit/index.js +130 -640
  74. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  75. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  76. package/v2Containers/SmsWrapper/index.js +8 -37
  77. package/v2Containers/TagList/index.js +0 -6
  78. package/v2Containers/Templates/_templates.scss +9 -166
  79. package/v2Containers/Templates/actions.js +0 -11
  80. package/v2Containers/Templates/constants.js +0 -2
  81. package/v2Containers/Templates/index.js +52 -120
  82. package/v2Containers/Templates/sagas.js +18 -57
  83. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1017 -1062
  84. package/v2Containers/Templates/tests/sagas.test.js +39 -205
  85. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  86. package/v2Containers/TemplatesV2/index.js +23 -86
  87. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  88. package/v2Containers/Whatsapp/index.js +20 -3
  89. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  90. package/utils/rcsPayloadUtils.js +0 -92
  91. package/utils/templateVarUtils.js +0 -201
  92. package/utils/tests/rcsPayloadUtils.test.js +0 -226
  93. package/utils/tests/templateVarUtils.test.js +0 -204
  94. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  95. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  96. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
  97. package/v2Components/SmsFallback/constants.js +0 -73
  98. package/v2Components/SmsFallback/index.js +0 -956
  99. package/v2Components/SmsFallback/index.scss +0 -265
  100. package/v2Components/SmsFallback/messages.js +0 -78
  101. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -119
  102. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  103. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  104. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  105. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -223
  106. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
  107. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  108. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  109. package/v2Components/TemplatePreview/constants.js +0 -2
  110. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  111. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  112. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  113. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  114. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
  115. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  116. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  117. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  118. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  119. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  120. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  121. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  122. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  123. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  124. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  125. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  126. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  127. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -31,8 +31,7 @@ import CustomValuesEditor from './CustomValuesEditor';
31
31
  import SendTestMessage from './SendTestMessage';
32
32
  import PreviewSection from './PreviewSection';
33
33
 
34
- import * as Api from '../../services/api';
35
- import { extractTemplateVariables } from '../../utils/templateVarUtils';
34
+ // Import constants
36
35
  import AddTestCustomerButton from './AddTestCustomer';
37
36
  import ExistingCustomerModal from './ExistingCustomerModal';
38
37
  // Import constants
@@ -82,23 +81,11 @@ import {
82
81
  IMAGE,
83
82
  VIDEO,
84
83
  URL,
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,
84
+ CHANNELS_USING_ANDROID_PREVIEW_DEVICE
93
85
  } from './constants';
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
86
 
87
+ // Import utilities
88
+ import { getCdnUrl } from '../../utils/cdnTransformation';
102
89
  import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
103
90
  import { getMembersLookup } from '../../services/api';
104
91
 
@@ -121,85 +108,6 @@ const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEch
121
108
  });
122
109
  };
123
110
 
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
- });
203
111
 
204
112
  /**
205
113
  * CapTreeSelect and group resolution use strict equality; API data may mix numeric and string ids.
@@ -225,7 +133,7 @@ const testEntityIdsEqual = (a, b) => {
225
133
  */
226
134
  const CommonTestAndPreview = (props) => {
227
135
  const {
228
- intl: { formatMessage, locale: userLocale = 'en' },
136
+ intl: { formatMessage },
229
137
  show,
230
138
  onClose,
231
139
  channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
@@ -261,21 +169,12 @@ const CommonTestAndPreview = (props) => {
261
169
  ...additionalProps
262
170
  } = props;
263
171
 
264
- const smsFallbackContent = additionalProps?.smsFallbackContent;
265
- const smsFallbackTextForTagExtraction = useMemo(
266
- () => getSmsFallbackTextForTagExtraction(smsFallbackContent),
267
- [smsFallbackContent],
268
- );
269
172
  // ============================================
270
173
  // STATE MANAGEMENT
271
174
  // ============================================
272
175
  const [selectedCustomer, setSelectedCustomer] = useState(null);
273
176
  const [requiredTags, setRequiredTags] = useState([]);
274
177
  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);
279
178
  const [customValues, setCustomValues] = useState({});
280
179
  const [showJSON, setShowJSON] = useState(false);
281
180
  const [tagsExtracted, setTagsExtracted] = useState(false);
@@ -287,8 +186,6 @@ const CommonTestAndPreview = (props) => {
287
186
  const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
288
187
 
289
188
  const [previewDevice, setPreviewDevice] = useState(initialDevice);
290
- const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
291
- const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
292
189
  // Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
293
190
  const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
294
191
  const [previewDataHtml, setPreviewDataHtml] = useState(() => {
@@ -321,22 +218,15 @@ const CommonTestAndPreview = (props) => {
321
218
  [CHANNELS.WHATSAPP]: {
322
219
  domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
323
220
  },
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
- },
331
221
  });
332
222
 
333
- const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
223
+ const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
334
224
  const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
335
225
  const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
336
226
  const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
337
227
  const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
338
228
 
339
- // Fetch sender details and WeCRM accounts when Test & Preview opens (SMS, Email, WhatsApp, RCS — same process)
229
+ // Fetch sender details and WeCRM accounts when Test & Preview opens for SMS/Email/WhatsApp
340
230
  useEffect(() => {
341
231
  if (!show || !channel) {
342
232
  return;
@@ -345,99 +235,39 @@ const CommonTestAndPreview = (props) => {
345
235
  if (actions.getSenderDetailsRequested) {
346
236
  actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
347
237
  }
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
- }
352
238
  if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
353
239
  actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
354
240
  }
355
241
  }
356
242
  }, [show, channel, orgUnitId, actions]);
357
243
 
244
+ useEffect(() => {
245
+ if (!show) {
246
+ setCustomerModal([false, '']);
247
+ setSearchValue('');
248
+ setCustomerData({ name: '', email: '', mobile: '', customerId: '' });
249
+ }
250
+ }, [show]);
251
+
358
252
  const findDefault = (arr) => (arr && arr.find((x) => x.default)) || (arr && arr[0]) || {};
359
253
 
360
254
  // Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
361
255
  useEffect(() => {
362
256
  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
-
426
257
  const domains = senderDetailsByChannel[channel];
427
258
  if (!domains || domains.length === 0) return;
428
259
  const {
429
- domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
260
+ domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
430
261
  } = testPreviewDeliverySettings[channel] || {};
431
- const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
262
+ const isEmptySelection = !domainId && !gsmSenderId && !senderEmail && !senderMobNum;
432
263
  if (!isEmptySelection) return;
433
264
 
434
265
  const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
435
266
  const matchedWhatsappAccount = whatsappAccountFromForm
436
267
  ? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
437
268
  : null;
438
- const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
439
- ? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
440
- registeredSenderIds?.includes(gsm?.value)))
269
+ const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled
270
+ ? domains.filter((domain) => (domain.gsmSenders || []).some((gsm) => registeredSenderIds.includes(gsm.value)))
441
271
  : domains;
442
272
  const [defaultDomain] = domains;
443
273
  const [firstSmsDomain] = smsDomains;
@@ -452,8 +282,8 @@ const CommonTestAndPreview = (props) => {
452
282
  const next = { ...prev };
453
283
  if (channel === CHANNELS.SMS) {
454
284
  const smsGsmSenders = smsTraiDltEnabled
455
- ? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
456
- : firstDomain?.gsmSenders;
285
+ ? (firstDomain.gsmSenders || []).filter((gsm) => registeredSenderIds.includes(gsm.value))
286
+ : firstDomain.gsmSenders;
457
287
  next[channel] = {
458
288
  domainId: firstDomain.domainId,
459
289
  domainGatewayMapId: firstDomain.dgmId,
@@ -486,29 +316,10 @@ const CommonTestAndPreview = (props) => {
486
316
  // MEMOIZED VALUES
487
317
  // ============================================
488
318
 
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
-
508
319
  // Check if update preview button should be disabled
509
320
  const isUpdatePreviewDisabled = useMemo(() => (
510
- allRequiredTags.some((tag) => !customValues[tag.fullPath])
511
- ), [allRequiredTags, customValues]);
321
+ requiredTags.some((tag) => !customValues[tag.fullPath])
322
+ ), [requiredTags, customValues]);
512
323
 
513
324
  // Get current content based on channel and editor type
514
325
  const getCurrentContent = useMemo(() => {
@@ -552,13 +363,6 @@ const CommonTestAndPreview = (props) => {
552
363
  return currentTabData.base['sms-editor'];
553
364
  }
554
365
  }
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
- }
562
366
  }
563
367
 
564
368
  // SMS channel fallback - if formData is not available, use content directly
@@ -604,70 +408,7 @@ const CommonTestAndPreview = (props) => {
604
408
  return content || '';
605
409
  }, [channel, formData, currentTab, beeContent, content, beeInstance]);
606
410
 
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
411
+ // Build test entities tree data
671
412
  // Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
672
413
  const testEntitiesTreeData = useMemo(() => {
673
414
  const groupsNode = {
@@ -676,7 +417,7 @@ const CommonTestAndPreview = (props) => {
676
417
  selectable: false,
677
418
  children: testGroups?.map((group) => ({
678
419
  title: group?.groupName,
679
- value: normalizeTestEntityId(group?.groupId),
420
+ value: 'group:' + normalizeTestEntityId(group?.groupId),
680
421
  })),
681
422
  };
682
423
 
@@ -686,7 +427,7 @@ const CommonTestAndPreview = (props) => {
686
427
  selectable: false,
687
428
  children: testCustomers?.map((customer) => ({
688
429
  title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
689
- value: normalizeTestEntityId(customer?.userId ?? customer?.customerId),
430
+ value: 'customer:' + normalizeTestEntityId(customer?.userId ?? customer?.customerId),
690
431
  })) || [],
691
432
  };
692
433
 
@@ -766,10 +507,11 @@ const CommonTestAndPreview = (props) => {
766
507
  email: customerData?.email || '',
767
508
  mobile: customerData?.mobile || '',
768
509
  });
510
+ const prefixedAddedId = 'customer:' + normalizedAddedId;
769
511
  setSelectedTestEntities((prev) => (
770
- prev.some((id) => testEntityIdsEqual(id, normalizedAddedId))
512
+ prev.some((id) => id === prefixedAddedId)
771
513
  ? prev
772
- : [...prev, normalizedAddedId]
514
+ : [...prev, prefixedAddedId]
773
515
  ));
774
516
  }
775
517
  handleCloseCustomerModal();
@@ -873,37 +615,6 @@ const CommonTestAndPreview = (props) => {
873
615
  }
874
616
  };
875
617
 
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
-
907
618
  /**
908
619
  * Prepare payload for tag extraction based on channel
909
620
  */
@@ -998,7 +709,7 @@ const CommonTestAndPreview = (props) => {
998
709
  } = carousel || {};
999
710
  const buttonData = buttons.map((button, index) => {
1000
711
  const {
1001
- type, text, phone_number: phoneNumber, urlType, url,
712
+ type, text, phone_number, urlType, url,
1002
713
  } = button || {};
1003
714
  const buttonObj = {
1004
715
  type,
@@ -1006,7 +717,7 @@ const CommonTestAndPreview = (props) => {
1006
717
  index,
1007
718
  };
1008
719
  if (type === PHONE_NUMBER) {
1009
- buttonObj.phoneNumber = phoneNumber;
720
+ buttonObj.phoneNumber = phone_number;
1010
721
  }
1011
722
  if (type === URL) {
1012
723
  buttonObj.url = url;
@@ -1035,133 +746,7 @@ const CommonTestAndPreview = (props) => {
1035
746
  };
1036
747
  });
1037
748
 
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
- ) => {
749
+ const prepareTestMessagePayload = (channelType, formDataObj, contentStr, customValuesObj, recipientDetails, previewDataObj, deliverySettingsOverride) => {
1165
750
  // Base payload structure common to all channels
1166
751
  const basePayload = {
1167
752
  ouId: -1,
@@ -1242,12 +827,12 @@ const CommonTestAndPreview = (props) => {
1242
827
  },
1243
828
  smsDeliverySettings: {
1244
829
  channelSettings: {
1245
- channel: CHANNELS.SMS,
1246
830
  gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
1247
831
  domainId: deliverySettingsOverride?.domainId ?? null,
1248
832
  domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
1249
833
  targetNdnc: false,
1250
834
  cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
835
+ channel: CHANNELS.SMS,
1251
836
  },
1252
837
  additionalSettings: {
1253
838
  useTinyUrl: false,
@@ -1391,7 +976,7 @@ const CommonTestAndPreview = (props) => {
1391
976
  return {
1392
977
  ...basePayload,
1393
978
  whatsappMessageContent: {
1394
- messageBody: resolvedMessageBody || templateEditorValue || '',
979
+ messageBody: templateEditorValue || '',
1395
980
  accountId: formDataObj?.accountId || '',
1396
981
  sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
1397
982
  accountName: formDataObj?.accountName || '',
@@ -1416,7 +1001,16 @@ const CommonTestAndPreview = (props) => {
1416
1001
  }
1417
1002
 
1418
1003
  case CHANNELS.RCS:
1419
- return buildRcsTestMessagePayload(formDataObj, contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra);
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
+ };
1420
1014
 
1421
1015
  case CHANNELS.INAPP: {
1422
1016
  // InApp payload structure similar to MobilePush
@@ -2518,10 +2112,6 @@ const CommonTestAndPreview = (props) => {
2518
2112
  formatMessage,
2519
2113
  lastModified: formData?.lastModified,
2520
2114
  updatedByName: formData?.updatedByName,
2521
- smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
2522
- smsFallbackResolvedText,
2523
- activePreviewTab,
2524
- onPreviewTabChange: setActivePreviewTab,
2525
2115
  };
2526
2116
  };
2527
2117
 
@@ -2561,12 +2151,7 @@ const CommonTestAndPreview = (props) => {
2561
2151
  }, [show, beeInstance, currentTab, channel]);
2562
2152
 
2563
2153
  /**
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.
2154
+ * Initial data load when slidebox opens
2570
2155
  */
2571
2156
  useEffect(() => {
2572
2157
  if (show) {
@@ -2651,61 +2236,7 @@ const CommonTestAndPreview = (props) => {
2651
2236
  actions.getTestGroupsRequested();
2652
2237
  }
2653
2238
  }
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]);
2239
+ }, [show, beeInstance, currentTab, channel]);
2709
2240
 
2710
2241
  /**
2711
2242
  * Email-specific: Handle content updates for both BEE and CKEditor
@@ -2757,22 +2288,15 @@ const CommonTestAndPreview = (props) => {
2757
2288
  setSelectedCustomer(null);
2758
2289
  setRequiredTags([]);
2759
2290
  setOptionalTags([]);
2760
- setSmsFallbackExtractedTags([]);
2761
- setSmsFallbackRequiredTags([]);
2762
- setSmsFallbackOptionalTags([]);
2763
- setIsExtractingSmsFallbackTags(false);
2764
2291
  setCustomValues({});
2765
2292
  setShowJSON(false);
2766
2293
  setTagsExtracted(false);
2767
2294
  setPreviewDevice(DESKTOP);
2768
- setActivePreviewTab(PREVIEW_TAB_RCS);
2769
- setSmsFallbackPreviewText(undefined);
2770
2295
  setSelectedTestEntities([]);
2771
2296
  actions.clearPrefilledValues();
2772
2297
  } else {
2773
2298
  // Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
2774
2299
  setPreviewDevice(initialDevice);
2775
- setActivePreviewTab(PREVIEW_TAB_RCS);
2776
2300
  }
2777
2301
  }, [show, initialDevice]);
2778
2302
 
@@ -2781,10 +2305,79 @@ const CommonTestAndPreview = (props) => {
2781
2305
  */
2782
2306
  useEffect(() => {
2783
2307
  if (previewData) {
2784
- setPreviewDataHtml(toPlainPreviewData(previewData));
2308
+ setPreviewDataHtml(previewData);
2785
2309
  }
2786
2310
  }, [previewData]);
2787
2311
 
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
+
2788
2381
  /**
2789
2382
  * Handle customer selection and fetch prefilled values
2790
2383
  */
@@ -2792,15 +2385,17 @@ const CommonTestAndPreview = (props) => {
2792
2385
  if (selectedCustomer && config.enableCustomerSearch !== false) {
2793
2386
  setTagsExtracted(true); // Auto-open custom values editor
2794
2387
 
2388
+ // Get all available tags
2389
+ const allTags = [...requiredTags, ...optionalTags];
2795
2390
  const requiredTagObj = {};
2796
- allRequiredTags.forEach((tag) => {
2391
+ requiredTags.forEach((tag) => {
2797
2392
  requiredTagObj[tag?.fullPath] = '';
2798
2393
  });
2799
2394
  if (allTags.length > 0) {
2800
2395
  const payload = preparePreviewPayload(
2801
- activeChannelForActions,
2396
+ channel,
2802
2397
  formData || {},
2803
- activeContentForActions,
2398
+ getCurrentContent,
2804
2399
  {
2805
2400
  ...requiredTagObj,
2806
2401
  },
@@ -2809,7 +2404,7 @@ const CommonTestAndPreview = (props) => {
2809
2404
  actions.getPrefilledValuesRequested(payload);
2810
2405
  }
2811
2406
  }
2812
- }, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
2407
+ }, [selectedCustomer]);
2813
2408
 
2814
2409
  /**
2815
2410
  * Update custom values with prefilled values from API
@@ -2820,29 +2415,24 @@ const CommonTestAndPreview = (props) => {
2820
2415
  if (prefilledValues && selectedCustomer) {
2821
2416
  // Always replace all values with prefilled values
2822
2417
  const updatedValues = {};
2823
- allTags.forEach((tag) => {
2418
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2824
2419
  updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
2825
2420
  });
2826
2421
 
2827
2422
  setCustomValues(updatedValues);
2828
2423
 
2829
2424
  // 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;
2834
2425
  const payload = preparePreviewPayload(
2835
- previewChannelForPrefill,
2426
+ channel,
2836
2427
  formData || {},
2837
- previewContentForPrefill,
2428
+ getCurrentContent,
2838
2429
  updatedValues,
2839
2430
  selectedCustomer
2840
2431
  );
2841
2432
  actions.updatePreviewRequested(payload);
2842
2433
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
2843
- void syncSmsFallbackPreview(updatedValues, selectedCustomer);
2844
2434
  }
2845
- }, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
2435
+ }, [JSON.stringify(prefilledValues), selectedCustomer]);
2846
2436
 
2847
2437
  /**
2848
2438
  * Map channel constants to display names (lowercase for message)
@@ -2936,7 +2526,11 @@ const CommonTestAndPreview = (props) => {
2936
2526
  setTagsExtracted(true); // Auto-open custom values editor
2937
2527
 
2938
2528
  // Clear any existing values while waiting for prefilled values
2939
- setCustomValues(buildEmptyValues());
2529
+ const emptyValues = {};
2530
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2531
+ emptyValues[tag?.fullPath] = '';
2532
+ });
2533
+ setCustomValues(emptyValues);
2940
2534
  };
2941
2535
 
2942
2536
  /**
@@ -2950,7 +2544,11 @@ const CommonTestAndPreview = (props) => {
2950
2544
  actions.clearPreviewErrors();
2951
2545
 
2952
2546
  // Initialize empty values for all tags
2953
- setCustomValues(buildEmptyValues());
2547
+ const emptyValues = {};
2548
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2549
+ emptyValues[tag?.fullPath] = '';
2550
+ });
2551
+ setCustomValues(emptyValues);
2954
2552
 
2955
2553
  // Don't make preview call when clearing selection - just reset to raw content
2956
2554
  // Preview will be shown using raw formData/content
@@ -2986,20 +2584,17 @@ const CommonTestAndPreview = (props) => {
2986
2584
  */
2987
2585
  const handleDiscardCustomValues = () => {
2988
2586
  // Initialize empty values for all tags
2989
- const emptyValues = buildEmptyValues();
2587
+ const emptyValues = {};
2588
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2589
+ emptyValues[tag?.fullPath] = '';
2590
+ });
2990
2591
  setCustomValues(emptyValues);
2991
2592
 
2992
- // Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
2993
- setSmsFallbackPreviewText(undefined);
2994
-
2995
2593
  // 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;
2999
2594
  const payload = preparePreviewPayload(
3000
- previewChannelForDiscard,
2595
+ channel,
3001
2596
  formData || {},
3002
- previewContentForDiscard,
2597
+ getCurrentContent,
3003
2598
  emptyValues,
3004
2599
  selectedCustomer
3005
2600
  );
@@ -3013,21 +2608,14 @@ const CommonTestAndPreview = (props) => {
3013
2608
  */
3014
2609
  const handleUpdatePreview = async () => {
3015
2610
  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;
3021
2611
  const payload = preparePreviewPayload(
3022
- previewChannel,
2612
+ channel,
3023
2613
  formData || {},
3024
- previewContent,
2614
+ getCurrentContent,
3025
2615
  customValues,
3026
2616
  selectedCustomer
3027
2617
  );
3028
2618
  await actions.updatePreviewRequested(payload);
3029
-
3030
- await syncSmsFallbackPreview(customValues, selectedCustomer);
3031
2619
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
3032
2620
  } catch (error) {
3033
2621
  CapNotification.error({
@@ -3037,115 +2625,25 @@ const CommonTestAndPreview = (props) => {
3037
2625
  };
3038
2626
 
3039
2627
  /**
3040
- * Categorize extracted tags into required/optional.
3041
- */
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
- };
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.
2628
+ * Handle extract tags
3097
2629
  */
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]);
2630
+ const handleExtractTags = () => {
2631
+ // Get content based on channel
2632
+ let contentToExtract = getCurrentContent;
3119
2633
 
3120
- /**
3121
- * Get content to run tag extraction on (channel-specific).
3122
- */
3123
- const getContentForTagExtraction = () => {
3124
- let contentToExtract = activeContentForActions;
3125
2634
  if (channel === CHANNELS.EMAIL && formData) {
3126
2635
  const currentTabData = formData[currentTab - 1];
3127
2636
  const activeTab = currentTabData?.activeTab;
3128
2637
  const templateContent = currentTabData?.[activeTab]?.['template-content'];
3129
2638
  contentToExtract = templateContent || contentToExtract;
3130
2639
  }
3131
- return contentToExtract;
3132
- };
3133
2640
 
3134
- /**
3135
- * Handle extract tags
3136
- */
3137
- const handleExtractTags = () => {
3138
- if (channel === CHANNELS.RCS) {
3139
- applyRcsSmsFallbackTagExtraction();
3140
- return;
3141
- }
3142
-
3143
- const contentToExtract = getContentForTagExtraction();
2641
+ // Check for personalization tags (excluding unsubscribe)
3144
2642
  const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
3145
2643
  const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
3146
- const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
3147
2644
 
3148
- if (onlyUnsubscribe) {
2645
+ if (!hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME)) {
2646
+ // If only unsubscribe tag is present, show noTagsExtracted message
3149
2647
  setTagsExtracted(false);
3150
2648
  setRequiredTags([]);
3151
2649
  setOptionalTags([]);
@@ -3153,9 +2651,10 @@ const CommonTestAndPreview = (props) => {
3153
2651
  return;
3154
2652
  }
3155
2653
 
2654
+ // Extract tags
3156
2655
  setTagsExtracted(true);
3157
2656
  const { templateSubject, templateContent } = prepareTagExtractionPayload(
3158
- activeChannelForActions,
2657
+ channel,
3159
2658
  formData || {},
3160
2659
  contentToExtract
3161
2660
  );
@@ -3217,7 +2716,7 @@ const CommonTestAndPreview = (props) => {
3217
2716
  if (existingTestCustomer) {
3218
2717
  const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
3219
2718
  if (entityId != null) {
3220
- const id = normalizeTestEntityId(entityId);
2719
+ const id = 'customer:' + normalizeTestEntityId(entityId);
3221
2720
  setSelectedTestEntities((prev) => (
3222
2721
  prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
3223
2722
  ));
@@ -3254,7 +2753,7 @@ const CommonTestAndPreview = (props) => {
3254
2753
  (c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
3255
2754
  );
3256
2755
  if (alreadyInTestListByCustomerId) {
3257
- const id = normalizeTestEntityId(customerIdFromLookup);
2756
+ const id = 'customer:' + normalizeTestEntityId(customerIdFromLookup);
3258
2757
  setSelectedTestEntities((prev) => (
3259
2758
  prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
3260
2759
  ));
@@ -3291,21 +2790,26 @@ const CommonTestAndPreview = (props) => {
3291
2790
  const handleSendTestMessage = () => {
3292
2791
  const allUserIds = [];
3293
2792
  selectedTestEntities.forEach((entityId) => {
3294
- const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, entityId));
3295
- if (group) {
3296
- allUserIds.push(...group.userIds);
2793
+ if (String(entityId).startsWith('group:')) {
2794
+ const rawId = String(entityId).slice('group:'.length);
2795
+ const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, rawId));
2796
+ if (group) {
2797
+ allUserIds.push(...group.userIds);
2798
+ }
3297
2799
  } else {
3298
- allUserIds.push(entityId);
2800
+ const rawId = String(entityId).startsWith('customer:')
2801
+ ? String(entityId).slice('customer:'.length)
2802
+ : String(entityId);
2803
+ allUserIds.push(Number(rawId));
3299
2804
  }
3300
2805
  });
3301
2806
  const uniqueUserIds = [...new Set(allUserIds)];
3302
2807
 
3303
- const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
2808
+ const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
3304
2809
  ? testPreviewDeliverySettings[channel]
3305
2810
  : null;
3306
2811
 
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.
2812
+ // Create initial payload based on channel
3309
2813
  const initialPayload = prepareTestMessagePayload(
3310
2814
  channel,
3311
2815
  formData || content || {},
@@ -3313,8 +2817,7 @@ const CommonTestAndPreview = (props) => {
3313
2817
  customValues,
3314
2818
  uniqueUserIds,
3315
2819
  previewData,
3316
- deliveryOverride,
3317
- {},
2820
+ deliveryOverride
3318
2821
  );
3319
2822
 
3320
2823
  actions.createMessageMetaRequested(
@@ -3347,10 +2850,11 @@ const CommonTestAndPreview = (props) => {
3347
2850
  // ============================================
3348
2851
  // RENDER HELPER FUNCTIONS
3349
2852
  // ============================================
2853
+
3350
2854
  const renderLeftPanelContent = () => (
3351
2855
  <LeftPanelContent
3352
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
3353
- extractedTags={leftPanelExtractedTags}
2856
+ isExtractingTags={isExtractingTags}
2857
+ extractedTags={extractedTags}
3354
2858
  selectedCustomer={selectedCustomer}
3355
2859
  handleCustomerSelect={handleCustomerSelect}
3356
2860
  handleSearchCustomer={handleSearchCustomer}
@@ -3368,29 +2872,15 @@ const CommonTestAndPreview = (props) => {
3368
2872
 
3369
2873
  const renderCustomValuesEditor = () => (
3370
2874
  <CustomValuesEditor
3371
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
2875
+ isExtractingTags={isExtractingTags}
3372
2876
  isUpdatePreviewDisabled={isUpdatePreviewDisabled}
3373
2877
  showJSON={showJSON}
3374
2878
  setShowJSON={setShowJSON}
3375
2879
  customValues={customValues}
3376
2880
  handleJSONTextChange={handleJSONTextChange}
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
- ]}
2881
+ extractedTags={extractedTags}
2882
+ requiredTags={requiredTags}
2883
+ optionalTags={optionalTags}
3394
2884
  handleCustomValueChange={handleCustomValueChange}
3395
2885
  handleDiscardCustomValues={handleDiscardCustomValues}
3396
2886
  handleUpdatePreview={handleUpdatePreview}
@@ -3406,13 +2896,18 @@ const CommonTestAndPreview = (props) => {
3406
2896
  }));
3407
2897
  };
3408
2898
 
3409
- /** Trim pasted emails (trailing CR/LF). Allow any input for SMS; valid-only check is in renderAddTestCustomerButton. */
2899
+ /** Trim pasted emails (trailing CR/LF). SMS: strip non-digits so pasted formatted numbers match isValidMobile / API. */
3410
2900
  const handleTestCustomersSearch = useCallback((value) => {
3411
2901
  if (value == null || value === '') {
3412
2902
  setSearchValue('');
3413
2903
  return;
3414
2904
  }
3415
- setSearchValue(String(value).trim());
2905
+ const raw = String(value).trim();
2906
+ if (channel === CHANNELS.SMS) {
2907
+ setSearchValue(formatPhoneNumber(raw));
2908
+ } else {
2909
+ setSearchValue(raw);
2910
+ }
3416
2911
  }, [channel]);
3417
2912
 
3418
2913
  const renderSendTestMessage = () => (
@@ -3430,13 +2925,12 @@ const CommonTestAndPreview = (props) => {
3430
2925
  renderAddTestCustomerButton={renderAddTestCustomerButton}
3431
2926
  formatMessage={formatMessage}
3432
2927
  deliverySettings={testPreviewDeliverySettings[channel]}
3433
- senderDetailsByChannel={senderDetailsByChannel}
2928
+ senderDetailsOptions={senderDetailsByChannel[channel]}
3434
2929
  wecrmAccounts={wecrmAccounts}
3435
2930
  onSaveDeliverySettings={handleSaveDeliverySettings}
3436
2931
  isLoadingSenderDetails={isLoadingSenderDetails}
3437
2932
  smsTraiDltEnabled={smsTraiDltEnabled}
3438
2933
  registeredSenderIds={registeredSenderIds}
3439
- isChannelSmsFallbackPreviewEnabled={isRcsSmsFallbackPreviewEnabled}
3440
2934
  searchValue={searchValue}
3441
2935
  setSearchValue={handleTestCustomersSearch}
3442
2936
  />
@@ -3450,13 +2944,14 @@ const CommonTestAndPreview = (props) => {
3450
2944
 
3451
2945
  const renderAddTestCustomerButton = () => {
3452
2946
  const raw = (searchValue || '').trim();
2947
+ const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
3453
2948
  const showAddButton =
3454
2949
  [CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
3455
- (channel === CHANNELS.EMAIL ? isValidEmail(raw) : isValidMobile(formatPhoneNumber(raw)));
2950
+ (channel === CHANNELS.EMAIL ? isValidEmail(value) : isValidMobile(value));
3456
2951
  if (!showAddButton) return null;
3457
2952
  return (
3458
2953
  <AddTestCustomerButton
3459
- searchValue={channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw}
2954
+ searchValue={value}
3460
2955
  handleAddTestCustomer={handleAddTestCustomer}
3461
2956
  />
3462
2957
  );