@capillarytech/creatives-library 8.0.345-alpha.15 → 8.0.345-alpha.16

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 (130) hide show
  1. package/constants/unified.js +0 -29
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +0 -13
  4. package/utils/commonUtils.js +1 -19
  5. package/v2Components/CapActionButton/constants.js +0 -7
  6. package/v2Components/CapActionButton/index.js +109 -167
  7. package/v2Components/CapActionButton/index.scss +6 -157
  8. package/v2Components/CapActionButton/messages.js +3 -19
  9. package/v2Components/CapActionButton/tests/index.test.js +17 -41
  10. package/v2Components/CapTagList/index.js +0 -10
  11. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  18. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -160
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -341
  21. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  22. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  23. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  24. package/v2Components/CommonTestAndPreview/index.js +186 -676
  25. package/v2Components/CommonTestAndPreview/messages.js +3 -49
  26. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  27. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  29. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  30. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  31. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  32. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  33. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  34. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  35. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
  36. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  37. package/v2Components/FormBuilder/index.js +10 -8
  38. package/v2Components/TemplatePreview/_templatePreview.scss +23 -33
  39. package/v2Components/TemplatePreview/index.js +28 -143
  40. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  41. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  42. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  43. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  44. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  45. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  46. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  47. package/v2Containers/CreativesContainer/constants.js +0 -9
  48. package/v2Containers/CreativesContainer/index.js +103 -300
  49. package/v2Containers/CreativesContainer/index.scss +1 -51
  50. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  51. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  52. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  53. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  54. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  55. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  56. package/v2Containers/Email/reducer.js +12 -3
  57. package/v2Containers/Email/sagas.js +9 -4
  58. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -0
  59. package/v2Containers/Email/tests/reducer.test.js +47 -0
  60. package/v2Containers/Email/tests/sagas.test.js +146 -6
  61. package/v2Containers/Rcs/constants.js +8 -119
  62. package/v2Containers/Rcs/index.js +811 -2383
  63. package/v2Containers/Rcs/index.scss +6 -276
  64. package/v2Containers/Rcs/messages.js +3 -38
  65. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
  66. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  67. package/v2Containers/Rcs/tests/index.test.js +121 -152
  68. package/v2Containers/Rcs/tests/mockData.js +0 -38
  69. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  70. package/v2Containers/Rcs/utils.js +11 -478
  71. package/v2Containers/Sms/Create/index.js +40 -100
  72. package/v2Containers/SmsTrai/Create/index.js +4 -9
  73. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  74. package/v2Containers/SmsTrai/Edit/index.js +130 -636
  75. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  76. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  77. package/v2Containers/SmsWrapper/index.js +8 -37
  78. package/v2Containers/TagList/index.js +0 -6
  79. package/v2Containers/Templates/_templates.scss +2 -163
  80. package/v2Containers/Templates/actions.js +0 -11
  81. package/v2Containers/Templates/constants.js +0 -2
  82. package/v2Containers/Templates/index.js +54 -119
  83. package/v2Containers/Templates/sagas.js +12 -57
  84. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  85. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  86. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  87. package/v2Containers/TemplatesV2/index.js +23 -86
  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/templateVarUtils.test.js +0 -204
  93. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +0 -18
  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 -87
  97. package/v2Components/SmsFallback/constants.js +0 -73
  98. package/v2Components/SmsFallback/index.js +0 -955
  99. package/v2Components/SmsFallback/index.scss +0 -265
  100. package/v2Components/SmsFallback/messages.js +0 -78
  101. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -118
  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 -197
  106. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -277
  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 -67
  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/index.js.rej +0 -1336
  119. package/v2Containers/Rcs/index.scss.rej +0 -74
  120. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  121. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +0 -128
  122. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  123. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  124. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  125. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  126. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  127. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  128. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  129. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  130. 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,130 +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
- const smsFallbackTaggedTemplateBody = pickFirstSmsFallbackTemplateString(
1082
- smsFallbackFromCreativeForm,
1083
- ) || '';
1084
- const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
1085
- ? deliverySettingsOverride.cdmaSenderId.split('|')[1]
1086
- : deliverySettingsOverride?.cdmaSenderId;
1087
- const deliveryFallbackSmsId =
1088
- typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
1089
- const creativeFallbackSmsId =
1090
- smsFallbackFromCreativeForm?.senderId != null
1091
- ? String(smsFallbackFromCreativeForm.senderId).trim()
1092
- : '';
1093
- const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
1094
-
1095
- const smsFallBackContent =
1096
- smsFallbackTaggedTemplateBody.trim() !== ''
1097
- ? { message: smsFallbackTaggedTemplateBody }
1098
- : undefined;
1099
-
1100
- // accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
1101
- const accountIdForMeta =
1102
- rcsContentFromForm?.accountId != null && String(rcsContentFromForm.accountId).trim() !== ''
1103
- ? String(rcsContentFromForm.accountId)
1104
- : undefined;
1105
-
1106
- const rcsRichCardContent = {
1107
- contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
1108
- cardType: rcsContentFromForm?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
1109
- cardSettings: rcsContentFromForm?.cardSettings ?? {
1110
- cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
1111
- cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
1112
- },
1113
- ...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
1114
- };
1115
-
1116
- const rcsMessageContent = {
1117
- channel: CHANNELS.RCS,
1118
- ...(accountIdForMeta && { accountId: accountIdForMeta }),
1119
- rcsRichCardContent,
1120
- ...(smsFallBackContent && { smsFallBackContent }),
1121
- };
1122
- const rcsComposite = deliverySettingsOverride?.gsmSenderId ?? '';
1123
- const [rcsDomainId, rcsSenderId] = rcsComposite.includes('|') ? rcsComposite.split('|') : ['', rcsComposite];
1124
- const rcsDeliverySettings = {
1125
- channelSettings: {
1126
- channel: CHANNELS.RCS,
1127
- rcsSender: (rcsSenderId || deliverySettingsOverride?.rcsSender) ?? '',
1128
- domainId:
1129
- rcsDomainId !== '' && rcsDomainId !== undefined && !Number.isNaN(Number(rcsDomainId))
1130
- ? Number(rcsDomainId)
1131
- : (deliverySettingsOverride?.domainId ?? 0),
1132
- fallbackSmsSenderId: fallbackSmsSenderIdForChannel,
1133
- },
1134
- additionalSettings: {
1135
- useTinyUrl: false,
1136
- encryptUrl: false,
1137
- linkTrackingEnabled: false,
1138
- bypassControlUser: false,
1139
- userSubscriptionDisabled: false,
1140
- },
1141
- };
1142
- const { clientName: baseClientName = CLIENT_NAME_CREATIVES, ...restBase } = basePayload;
1143
- return {
1144
- ...restBase,
1145
- rcsMessageContent,
1146
- rcsDeliverySettings,
1147
- executionParams: {},
1148
- clientName: baseClientName,
1149
- };
1150
- };
1151
-
1152
- const prepareTestMessagePayload = (
1153
- channelType,
1154
- formDataObj,
1155
- contentStr,
1156
- customValuesObj,
1157
- recipientDetails,
1158
- previewDataObj,
1159
- deliverySettingsOverride,
1160
- rcsExtra = {},
1161
- ) => {
749
+ const prepareTestMessagePayload = (channelType, formDataObj, contentStr, customValuesObj, recipientDetails, previewDataObj, deliverySettingsOverride) => {
1162
750
  // Base payload structure common to all channels
1163
751
  const basePayload = {
1164
752
  ouId: -1,
@@ -1239,12 +827,12 @@ const CommonTestAndPreview = (props) => {
1239
827
  },
1240
828
  smsDeliverySettings: {
1241
829
  channelSettings: {
1242
- channel: CHANNELS.SMS,
1243
830
  gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
1244
831
  domainId: deliverySettingsOverride?.domainId ?? null,
1245
832
  domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
1246
833
  targetNdnc: false,
1247
834
  cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
835
+ channel: CHANNELS.SMS,
1248
836
  },
1249
837
  additionalSettings: {
1250
838
  useTinyUrl: false,
@@ -1388,7 +976,7 @@ const CommonTestAndPreview = (props) => {
1388
976
  return {
1389
977
  ...basePayload,
1390
978
  whatsappMessageContent: {
1391
- messageBody: resolvedMessageBody || templateEditorValue || '',
979
+ messageBody: templateEditorValue || '',
1392
980
  accountId: formDataObj?.accountId || '',
1393
981
  sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
1394
982
  accountName: formDataObj?.accountName || '',
@@ -1413,7 +1001,16 @@ const CommonTestAndPreview = (props) => {
1413
1001
  }
1414
1002
 
1415
1003
  case CHANNELS.RCS:
1416
- 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
+ };
1417
1014
 
1418
1015
  case CHANNELS.INAPP: {
1419
1016
  // InApp payload structure similar to MobilePush
@@ -2515,10 +2112,6 @@ const CommonTestAndPreview = (props) => {
2515
2112
  formatMessage,
2516
2113
  lastModified: formData?.lastModified,
2517
2114
  updatedByName: formData?.updatedByName,
2518
- smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
2519
- smsFallbackResolvedText,
2520
- activePreviewTab,
2521
- onPreviewTabChange: setActivePreviewTab,
2522
2115
  };
2523
2116
  };
2524
2117
 
@@ -2558,12 +2151,7 @@ const CommonTestAndPreview = (props) => {
2558
2151
  }, [show, beeInstance, currentTab, channel]);
2559
2152
 
2560
2153
  /**
2561
- * Initial data load when slidebox opens.
2562
- * EXTRACT TAGS CALL SITES (on open/edit RCS):
2563
- * 1. Here (non-email): actions.extractTagsRequested() at line ~2161 when show is true.
2564
- * 2. RCS SMS fallback useEffect below: Api.extractTagsWithMetaData() for fallback message.
2565
- * 3. handleExtractTags() (user clicks "Enter custom values for tags") – not from effects.
2566
- * The "Process extracted tags" effect only processes API results and must not call extract again.
2154
+ * Initial data load when slidebox opens
2567
2155
  */
2568
2156
  useEffect(() => {
2569
2157
  if (show) {
@@ -2648,61 +2236,7 @@ const CommonTestAndPreview = (props) => {
2648
2236
  actions.getTestGroupsRequested();
2649
2237
  }
2650
2238
  }
2651
- // getCurrentContent: RCS applies cardVarMapped → placeholder resolution; re-extract when it changes.
2652
- }, [show, beeInstance, currentTab, channel, getCurrentContent]);
2653
-
2654
- /**
2655
- * RCS with SMS fallback: extract tags for fallback SMS content as well
2656
- * (so we can show a separate "Fallback SMS tags" section in left panel).
2657
- */
2658
- useEffect(() => {
2659
- let cancelled = false;
2660
-
2661
- if (!show || channel !== CHANNELS.RCS) {
2662
- return () => {
2663
- cancelled = true;
2664
- };
2665
- }
2666
-
2667
- if (!smsFallbackContent?.templateContent && !smsFallbackContent?.content) {
2668
- setSmsFallbackExtractedTags([]);
2669
- setSmsFallbackRequiredTags([]);
2670
- setSmsFallbackOptionalTags([]);
2671
- return () => {
2672
- cancelled = true;
2673
- };
2674
- }
2675
-
2676
- setIsExtractingSmsFallbackTags(true);
2677
- (async () => {
2678
- try {
2679
- const fallbackBodyForExtractApi = getSmsFallbackTextForTagExtraction(smsFallbackContent);
2680
- const payload = {
2681
- messageTitle: '',
2682
- messageBody: fallbackBodyForExtractApi || '',
2683
- };
2684
- 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
2685
- let smsFallbackTagTree = response?.data ?? [];
2686
- if (!Array.isArray(smsFallbackTagTree)) smsFallbackTagTree = [];
2687
- if (!smsTemplateHasMustacheTags(fallbackBodyForExtractApi)) {
2688
- smsFallbackTagTree = [];
2689
- } else if (smsFallbackTagTree.length === 0) {
2690
- smsFallbackTagTree = buildSyntheticSmsMustacheTags(fallbackBodyForExtractApi);
2691
- }
2692
- if (cancelled) return;
2693
- setSmsFallbackExtractedTags(smsFallbackTagTree);
2694
- } catch (e) {
2695
- if (cancelled) return;
2696
- setSmsFallbackExtractedTags([]);
2697
- } finally {
2698
- if (!cancelled) setIsExtractingSmsFallbackTags(false);
2699
- }
2700
- })();
2701
-
2702
- return () => {
2703
- cancelled = true;
2704
- };
2705
- }, [show, channel, smsFallbackContent]);
2239
+ }, [show, beeInstance, currentTab, channel]);
2706
2240
 
2707
2241
  /**
2708
2242
  * Email-specific: Handle content updates for both BEE and CKEditor
@@ -2754,22 +2288,15 @@ const CommonTestAndPreview = (props) => {
2754
2288
  setSelectedCustomer(null);
2755
2289
  setRequiredTags([]);
2756
2290
  setOptionalTags([]);
2757
- setSmsFallbackExtractedTags([]);
2758
- setSmsFallbackRequiredTags([]);
2759
- setSmsFallbackOptionalTags([]);
2760
- setIsExtractingSmsFallbackTags(false);
2761
2291
  setCustomValues({});
2762
2292
  setShowJSON(false);
2763
2293
  setTagsExtracted(false);
2764
2294
  setPreviewDevice(DESKTOP);
2765
- setActivePreviewTab(PREVIEW_TAB_RCS);
2766
- setSmsFallbackPreviewText(undefined);
2767
2295
  setSelectedTestEntities([]);
2768
2296
  actions.clearPrefilledValues();
2769
2297
  } else {
2770
2298
  // Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
2771
2299
  setPreviewDevice(initialDevice);
2772
- setActivePreviewTab(PREVIEW_TAB_RCS);
2773
2300
  }
2774
2301
  }, [show, initialDevice]);
2775
2302
 
@@ -2778,10 +2305,79 @@ const CommonTestAndPreview = (props) => {
2778
2305
  */
2779
2306
  useEffect(() => {
2780
2307
  if (previewData) {
2781
- setPreviewDataHtml(toPlainPreviewData(previewData));
2308
+ setPreviewDataHtml(previewData);
2782
2309
  }
2783
2310
  }, [previewData]);
2784
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
+
2785
2381
  /**
2786
2382
  * Handle customer selection and fetch prefilled values
2787
2383
  */
@@ -2789,15 +2385,17 @@ const CommonTestAndPreview = (props) => {
2789
2385
  if (selectedCustomer && config.enableCustomerSearch !== false) {
2790
2386
  setTagsExtracted(true); // Auto-open custom values editor
2791
2387
 
2388
+ // Get all available tags
2389
+ const allTags = [...requiredTags, ...optionalTags];
2792
2390
  const requiredTagObj = {};
2793
- allRequiredTags.forEach((tag) => {
2391
+ requiredTags.forEach((tag) => {
2794
2392
  requiredTagObj[tag?.fullPath] = '';
2795
2393
  });
2796
2394
  if (allTags.length > 0) {
2797
2395
  const payload = preparePreviewPayload(
2798
- activeChannelForActions,
2396
+ channel,
2799
2397
  formData || {},
2800
- activeContentForActions,
2398
+ getCurrentContent,
2801
2399
  {
2802
2400
  ...requiredTagObj,
2803
2401
  },
@@ -2806,7 +2404,7 @@ const CommonTestAndPreview = (props) => {
2806
2404
  actions.getPrefilledValuesRequested(payload);
2807
2405
  }
2808
2406
  }
2809
- }, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
2407
+ }, [selectedCustomer]);
2810
2408
 
2811
2409
  /**
2812
2410
  * Update custom values with prefilled values from API
@@ -2817,7 +2415,7 @@ const CommonTestAndPreview = (props) => {
2817
2415
  if (prefilledValues && selectedCustomer) {
2818
2416
  // Always replace all values with prefilled values
2819
2417
  const updatedValues = {};
2820
- allTags.forEach((tag) => {
2418
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2821
2419
  updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
2822
2420
  });
2823
2421
 
@@ -2825,17 +2423,16 @@ const CommonTestAndPreview = (props) => {
2825
2423
 
2826
2424
  // Update preview with prefilled values (this is a valid preview call trigger)
2827
2425
  const payload = preparePreviewPayload(
2828
- activeChannelForActions,
2426
+ channel,
2829
2427
  formData || {},
2830
- activeContentForActions,
2428
+ getCurrentContent,
2831
2429
  updatedValues,
2832
2430
  selectedCustomer
2833
2431
  );
2834
2432
  actions.updatePreviewRequested(payload);
2835
2433
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
2836
- void syncSmsFallbackPreview(updatedValues, selectedCustomer);
2837
2434
  }
2838
- }, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
2435
+ }, [JSON.stringify(prefilledValues), selectedCustomer]);
2839
2436
 
2840
2437
  /**
2841
2438
  * Map channel constants to display names (lowercase for message)
@@ -2929,7 +2526,11 @@ const CommonTestAndPreview = (props) => {
2929
2526
  setTagsExtracted(true); // Auto-open custom values editor
2930
2527
 
2931
2528
  // Clear any existing values while waiting for prefilled values
2932
- setCustomValues(buildEmptyValues());
2529
+ const emptyValues = {};
2530
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2531
+ emptyValues[tag?.fullPath] = '';
2532
+ });
2533
+ setCustomValues(emptyValues);
2933
2534
  };
2934
2535
 
2935
2536
  /**
@@ -2943,7 +2544,11 @@ const CommonTestAndPreview = (props) => {
2943
2544
  actions.clearPreviewErrors();
2944
2545
 
2945
2546
  // Initialize empty values for all tags
2946
- setCustomValues(buildEmptyValues());
2547
+ const emptyValues = {};
2548
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2549
+ emptyValues[tag?.fullPath] = '';
2550
+ });
2551
+ setCustomValues(emptyValues);
2947
2552
 
2948
2553
  // Don't make preview call when clearing selection - just reset to raw content
2949
2554
  // Preview will be shown using raw formData/content
@@ -2979,17 +2584,17 @@ const CommonTestAndPreview = (props) => {
2979
2584
  */
2980
2585
  const handleDiscardCustomValues = () => {
2981
2586
  // Initialize empty values for all tags
2982
- const emptyValues = buildEmptyValues();
2587
+ const emptyValues = {};
2588
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2589
+ emptyValues[tag?.fullPath] = '';
2590
+ });
2983
2591
  setCustomValues(emptyValues);
2984
2592
 
2985
- // Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
2986
- setSmsFallbackPreviewText(undefined);
2987
-
2988
2593
  // Update preview with empty values (this is a valid preview call trigger)
2989
2594
  const payload = preparePreviewPayload(
2990
- activeChannelForActions,
2595
+ channel,
2991
2596
  formData || {},
2992
- activeContentForActions,
2597
+ getCurrentContent,
2993
2598
  emptyValues,
2994
2599
  selectedCustomer
2995
2600
  );
@@ -3004,15 +2609,13 @@ const CommonTestAndPreview = (props) => {
3004
2609
  const handleUpdatePreview = async () => {
3005
2610
  try {
3006
2611
  const payload = preparePreviewPayload(
3007
- activeChannelForActions,
2612
+ channel,
3008
2613
  formData || {},
3009
- activeContentForActions,
2614
+ getCurrentContent,
3010
2615
  customValues,
3011
2616
  selectedCustomer
3012
2617
  );
3013
2618
  await actions.updatePreviewRequested(payload);
3014
-
3015
- await syncSmsFallbackPreview(customValues, selectedCustomer);
3016
2619
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
3017
2620
  } catch (error) {
3018
2621
  CapNotification.error({
@@ -3022,115 +2625,25 @@ const CommonTestAndPreview = (props) => {
3022
2625
  };
3023
2626
 
3024
2627
  /**
3025
- * Categorize extracted tags into required/optional.
3026
- */
3027
- const categorizeTags = (tagsTree = []) => {
3028
- const required = [];
3029
- const optional = [];
3030
- let hasPersonalizationTags = false;
3031
- const processTag = (tag, parentPath = '') => {
3032
- const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
3033
-
3034
- // Skip unsubscribe tag for input fields
3035
- if (tag?.name === UNSUBSCRIBE_TAG_NAME) return;
3036
-
3037
- hasPersonalizationTags = true;
3038
- const userDriven = tag?.metaData?.userDriven;
3039
- if (userDriven === true) {
3040
- optional.push({ ...tag, fullPath: currentPath });
3041
- } else {
3042
- // false or missing (SMS/DLT extract often omits metaData) → required for test values
3043
- required.push({ ...tag, fullPath: currentPath });
3044
- }
3045
-
3046
- if (tag?.children?.length > 0) {
3047
- tag.children.forEach((child) => processTag(child, currentPath));
3048
- }
3049
- };
3050
-
3051
- (tagsTree || []).forEach((tag) => processTag(tag));
3052
- return { required, optional, hasPersonalizationTags };
3053
- };
3054
-
3055
- /**
3056
- * Apply tag extraction when content comes from RCS + SMS fallback (no API call).
3057
- */
3058
- const applyRcsSmsFallbackTagExtraction = () => {
3059
- const rcsPrimaryCategorized = categorizeTags(extractedTags ?? []);
3060
- const fallbackSmsResolvedForTags = smsFallbackTextForTagExtraction ?? '';
3061
- let fallbackSmsTagTree = smsFallbackExtractedTags?.length > 0
3062
- ? smsFallbackExtractedTags
3063
- : buildSyntheticSmsMustacheTags(fallbackSmsResolvedForTags);
3064
- if (!smsTemplateHasMustacheTags(fallbackSmsResolvedForTags)) {
3065
- fallbackSmsTagTree = [];
3066
- }
3067
- const fallbackSmsCategorized = categorizeTags(fallbackSmsTagTree);
3068
- setRequiredTags(rcsPrimaryCategorized.required);
3069
- setOptionalTags(rcsPrimaryCategorized.optional);
3070
- setSmsFallbackRequiredTags(fallbackSmsCategorized.required);
3071
- setSmsFallbackOptionalTags(fallbackSmsCategorized.optional);
3072
- setTagsExtracted(
3073
- rcsPrimaryCategorized.hasPersonalizationTags || fallbackSmsCategorized.hasPersonalizationTags,
3074
- );
3075
- setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, rcsPrimaryCategorized, fallbackSmsCategorized));
3076
- };
3077
-
3078
- /**
3079
- * When extract-tags API returns, map Redux `extractedTags` into required/optional so
3080
- * CustomValuesEditor shows personalization fields (effect was previously commented out).
3081
- * RCS + SMS fallback: merge primary + fallback tag trees when fallback template exists.
2628
+ * Handle extract tags
3082
2629
  */
3083
- useEffect(() => {
3084
- if (!show) return;
3085
- const hasFallbackSmsTemplate = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
3086
- if (channel === CHANNELS.RCS && hasFallbackSmsTemplate) {
3087
- applyRcsSmsFallbackTagExtraction();
3088
- return;
3089
- }
3090
- const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
3091
- let smsTagSource = channel === CHANNELS.SMS && (!extractedTags || extractedTags.length === 0)
3092
- ? buildSyntheticSmsMustacheTags(smsEditorBody)
3093
- : (extractedTags ?? []);
3094
- if (channel === CHANNELS.SMS && !smsTemplateHasMustacheTags(smsEditorBody)) {
3095
- smsTagSource = [];
3096
- }
3097
- const { required, optional, hasPersonalizationTags } = categorizeTags(smsTagSource);
3098
- setRequiredTags(required);
3099
- setOptionalTags(optional);
3100
- setTagsExtracted(hasPersonalizationTags);
3101
- setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, { required, optional }, { required: [], optional: [] }));
3102
- // eslint-disable-next-line react-hooks/exhaustive-deps -- applyRcsSmsFallbackTagExtraction closes over latest extractedTags/smsFallbackExtractedTags
3103
- }, [show, extractedTags, channel, smsFallbackContent, smsFallbackExtractedTags, getCurrentContent, smsFallbackTextForTagExtraction]);
2630
+ const handleExtractTags = () => {
2631
+ // Get content based on channel
2632
+ let contentToExtract = getCurrentContent;
3104
2633
 
3105
- /**
3106
- * Get content to run tag extraction on (channel-specific).
3107
- */
3108
- const getContentForTagExtraction = () => {
3109
- let contentToExtract = activeContentForActions;
3110
2634
  if (channel === CHANNELS.EMAIL && formData) {
3111
2635
  const currentTabData = formData[currentTab - 1];
3112
2636
  const activeTab = currentTabData?.activeTab;
3113
2637
  const templateContent = currentTabData?.[activeTab]?.['template-content'];
3114
2638
  contentToExtract = templateContent || contentToExtract;
3115
2639
  }
3116
- return contentToExtract;
3117
- };
3118
2640
 
3119
- /**
3120
- * Handle extract tags
3121
- */
3122
- const handleExtractTags = () => {
3123
- if (channel === CHANNELS.RCS) {
3124
- applyRcsSmsFallbackTagExtraction();
3125
- return;
3126
- }
3127
-
3128
- const contentToExtract = getContentForTagExtraction();
2641
+ // Check for personalization tags (excluding unsubscribe)
3129
2642
  const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
3130
2643
  const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
3131
- const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
3132
2644
 
3133
- if (onlyUnsubscribe) {
2645
+ if (!hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME)) {
2646
+ // If only unsubscribe tag is present, show noTagsExtracted message
3134
2647
  setTagsExtracted(false);
3135
2648
  setRequiredTags([]);
3136
2649
  setOptionalTags([]);
@@ -3138,9 +2651,10 @@ const CommonTestAndPreview = (props) => {
3138
2651
  return;
3139
2652
  }
3140
2653
 
2654
+ // Extract tags
3141
2655
  setTagsExtracted(true);
3142
2656
  const { templateSubject, templateContent } = prepareTagExtractionPayload(
3143
- activeChannelForActions,
2657
+ channel,
3144
2658
  formData || {},
3145
2659
  contentToExtract
3146
2660
  );
@@ -3202,7 +2716,7 @@ const CommonTestAndPreview = (props) => {
3202
2716
  if (existingTestCustomer) {
3203
2717
  const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
3204
2718
  if (entityId != null) {
3205
- const id = normalizeTestEntityId(entityId);
2719
+ const id = 'customer:' + normalizeTestEntityId(entityId);
3206
2720
  setSelectedTestEntities((prev) => (
3207
2721
  prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
3208
2722
  ));
@@ -3239,7 +2753,7 @@ const CommonTestAndPreview = (props) => {
3239
2753
  (c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
3240
2754
  );
3241
2755
  if (alreadyInTestListByCustomerId) {
3242
- const id = normalizeTestEntityId(customerIdFromLookup);
2756
+ const id = 'customer:' + normalizeTestEntityId(customerIdFromLookup);
3243
2757
  setSelectedTestEntities((prev) => (
3244
2758
  prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
3245
2759
  ));
@@ -3276,21 +2790,26 @@ const CommonTestAndPreview = (props) => {
3276
2790
  const handleSendTestMessage = () => {
3277
2791
  const allUserIds = [];
3278
2792
  selectedTestEntities.forEach((entityId) => {
3279
- const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, entityId));
3280
- if (group) {
3281
- 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
+ }
3282
2799
  } else {
3283
- allUserIds.push(entityId);
2800
+ const rawId = String(entityId).startsWith('customer:')
2801
+ ? String(entityId).slice('customer:'.length)
2802
+ : String(entityId);
2803
+ allUserIds.push(Number(rawId));
3284
2804
  }
3285
2805
  });
3286
2806
  const uniqueUserIds = [...new Set(allUserIds)];
3287
2807
 
3288
- const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
2808
+ const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
3289
2809
  ? testPreviewDeliverySettings[channel]
3290
2810
  : null;
3291
2811
 
3292
- // createMessageMeta must match the creative channel and full creative (RCS + SMS fallback in one meta).
3293
- // Do not use activeChannelForActions / activeContentForActions — those follow the RCS vs Fallback SMS *preview* tab.
2812
+ // Create initial payload based on channel
3294
2813
  const initialPayload = prepareTestMessagePayload(
3295
2814
  channel,
3296
2815
  formData || content || {},
@@ -3298,8 +2817,7 @@ const CommonTestAndPreview = (props) => {
3298
2817
  customValues,
3299
2818
  uniqueUserIds,
3300
2819
  previewData,
3301
- deliveryOverride,
3302
- {},
2820
+ deliveryOverride
3303
2821
  );
3304
2822
 
3305
2823
  actions.createMessageMetaRequested(
@@ -3332,10 +2850,11 @@ const CommonTestAndPreview = (props) => {
3332
2850
  // ============================================
3333
2851
  // RENDER HELPER FUNCTIONS
3334
2852
  // ============================================
2853
+
3335
2854
  const renderLeftPanelContent = () => (
3336
2855
  <LeftPanelContent
3337
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
3338
- extractedTags={leftPanelExtractedTags}
2856
+ isExtractingTags={isExtractingTags}
2857
+ extractedTags={extractedTags}
3339
2858
  selectedCustomer={selectedCustomer}
3340
2859
  handleCustomerSelect={handleCustomerSelect}
3341
2860
  handleSearchCustomer={handleSearchCustomer}
@@ -3353,29 +2872,15 @@ const CommonTestAndPreview = (props) => {
3353
2872
 
3354
2873
  const renderCustomValuesEditor = () => (
3355
2874
  <CustomValuesEditor
3356
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
2875
+ isExtractingTags={isExtractingTags}
3357
2876
  isUpdatePreviewDisabled={isUpdatePreviewDisabled}
3358
2877
  showJSON={showJSON}
3359
2878
  setShowJSON={setShowJSON}
3360
2879
  customValues={customValues}
3361
2880
  handleJSONTextChange={handleJSONTextChange}
3362
- sections={[
3363
- {
3364
- key: channel,
3365
- title:
3366
- channel === CHANNELS.RCS
3367
- ? messages.rcsTagsSectionTitle
3368
- : messages[`${channel}TagsSectionTitle`],
3369
- requiredTags,
3370
- optionalTags,
3371
- },
3372
- {
3373
- key: PREVIEW_TAB_SMS_FALLBACK,
3374
- title: channel === CHANNELS.RCS && smsFallbackContent?.templateContent ? messages.smsFallbackTagsSectionTitle : null,
3375
- requiredTags: smsFallbackRequiredTags,
3376
- optionalTags: smsFallbackOptionalTags,
3377
- },
3378
- ]}
2881
+ extractedTags={extractedTags}
2882
+ requiredTags={requiredTags}
2883
+ optionalTags={optionalTags}
3379
2884
  handleCustomValueChange={handleCustomValueChange}
3380
2885
  handleDiscardCustomValues={handleDiscardCustomValues}
3381
2886
  handleUpdatePreview={handleUpdatePreview}
@@ -3391,13 +2896,18 @@ const CommonTestAndPreview = (props) => {
3391
2896
  }));
3392
2897
  };
3393
2898
 
3394
- /** 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. */
3395
2900
  const handleTestCustomersSearch = useCallback((value) => {
3396
2901
  if (value == null || value === '') {
3397
2902
  setSearchValue('');
3398
2903
  return;
3399
2904
  }
3400
- 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
+ }
3401
2911
  }, [channel]);
3402
2912
 
3403
2913
  const renderSendTestMessage = () => (
@@ -3415,13 +2925,12 @@ const CommonTestAndPreview = (props) => {
3415
2925
  renderAddTestCustomerButton={renderAddTestCustomerButton}
3416
2926
  formatMessage={formatMessage}
3417
2927
  deliverySettings={testPreviewDeliverySettings[channel]}
3418
- senderDetailsByChannel={senderDetailsByChannel}
2928
+ senderDetailsOptions={senderDetailsByChannel[channel]}
3419
2929
  wecrmAccounts={wecrmAccounts}
3420
2930
  onSaveDeliverySettings={handleSaveDeliverySettings}
3421
2931
  isLoadingSenderDetails={isLoadingSenderDetails}
3422
2932
  smsTraiDltEnabled={smsTraiDltEnabled}
3423
2933
  registeredSenderIds={registeredSenderIds}
3424
- isChannelSmsFallbackPreviewEnabled={channel === CHANNELS.RCS && !!smsFallbackContent?.templateContent}
3425
2934
  searchValue={searchValue}
3426
2935
  setSearchValue={handleTestCustomersSearch}
3427
2936
  />
@@ -3435,13 +2944,14 @@ const CommonTestAndPreview = (props) => {
3435
2944
 
3436
2945
  const renderAddTestCustomerButton = () => {
3437
2946
  const raw = (searchValue || '').trim();
2947
+ const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
3438
2948
  const showAddButton =
3439
2949
  [CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
3440
- (channel === CHANNELS.EMAIL ? isValidEmail(raw) : isValidMobile(formatPhoneNumber(raw)));
2950
+ (channel === CHANNELS.EMAIL ? isValidEmail(value) : isValidMobile(value));
3441
2951
  if (!showAddButton) return null;
3442
2952
  return (
3443
2953
  <AddTestCustomerButton
3444
- searchValue={channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw}
2954
+ searchValue={value}
3445
2955
  handleAddTestCustomer={handleAddTestCustomer}
3446
2956
  />
3447
2957
  );