@capillarytech/creatives-library 9.0.15-alpha.1 → 9.0.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 (132) hide show
  1. package/constants/unified.js +0 -29
  2. package/global-styles.js +1 -1
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +20 -35
  5. package/utils/commonUtils.js +1 -19
  6. package/v2Components/CapActionButton/constants.js +0 -7
  7. package/v2Components/CapActionButton/index.js +109 -168
  8. package/v2Components/CapActionButton/index.scss +5 -156
  9. package/v2Components/CapActionButton/messages.js +3 -19
  10. package/v2Components/CapActionButton/tests/index.test.js +18 -42
  11. package/v2Components/CapImageUpload/index.js +2 -2
  12. package/v2Components/CapTagList/index.js +0 -10
  13. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +48 -70
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -214
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +9 -83
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -60
  20. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  21. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +23 -201
  22. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -355
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +8 -155
  24. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  25. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  26. package/v2Components/CommonTestAndPreview/index.js +222 -761
  27. package/v2Components/CommonTestAndPreview/messages.js +3 -45
  28. package/v2Components/CommonTestAndPreview/sagas.js +6 -25
  29. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -1
  30. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +267 -284
  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 -133
  39. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -31
  40. package/v2Components/TemplatePreview/_templatePreview.scss +22 -41
  41. package/v2Components/TemplatePreview/index.js +31 -143
  42. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  43. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  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/CommunicationFlow/Tests/CommunicationFlow.test.js +1 -4
  48. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +0 -4
  49. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  50. package/v2Containers/CreativesContainer/SlideBoxContent.js +7 -36
  51. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -14
  52. package/v2Containers/CreativesContainer/SlideBoxHeader.js +5 -36
  53. package/v2Containers/CreativesContainer/constants.js +0 -11
  54. package/v2Containers/CreativesContainer/index.js +104 -323
  55. package/v2Containers/CreativesContainer/index.scss +1 -83
  56. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -79
  57. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  58. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  59. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +90 -333
  60. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  61. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  62. package/v2Containers/Email/index.js +5 -2
  63. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  64. package/v2Containers/Rcs/constants.js +11 -131
  65. package/v2Containers/Rcs/index.js +810 -2605
  66. package/v2Containers/Rcs/index.scss +8 -289
  67. package/v2Containers/Rcs/messages.js +3 -34
  68. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +38537 -73535
  69. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +8 -0
  70. package/v2Containers/Rcs/tests/index.test.js +36 -147
  71. package/v2Containers/Rcs/tests/mockData.js +0 -38
  72. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  73. package/v2Containers/Rcs/utils.js +11 -478
  74. package/v2Containers/Sms/Create/index.js +48 -115
  75. package/v2Containers/SmsTrai/Create/index.js +4 -9
  76. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  77. package/v2Containers/SmsTrai/Edit/index.js +169 -678
  78. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  79. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2854 -5455
  80. package/v2Containers/SmsWrapper/index.js +8 -37
  81. package/v2Containers/TagList/index.js +0 -6
  82. package/v2Containers/Templates/_templates.scss +12 -171
  83. package/v2Containers/Templates/actions.js +0 -11
  84. package/v2Containers/Templates/constants.js +0 -2
  85. package/v2Containers/Templates/index.js +55 -125
  86. package/v2Containers/Templates/sagas.js +13 -57
  87. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1015 -1060
  88. package/v2Containers/Templates/tests/sagas.test.js +16 -199
  89. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  90. package/v2Containers/TemplatesV2/index.js +23 -86
  91. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  92. package/v2Containers/Whatsapp/index.js +20 -3
  93. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  94. package/utils/rcsPayloadUtils.js +0 -92
  95. package/utils/templateVarUtils.js +0 -201
  96. package/utils/tests/rcsPayloadUtils.test.js +0 -226
  97. package/utils/tests/templateVarUtils.test.js +0 -204
  98. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  99. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  100. package/v2Components/CommonTestAndPreview/utils.js +0 -84
  101. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
  102. package/v2Components/SmsFallback/constants.js +0 -94
  103. package/v2Components/SmsFallback/index.js +0 -958
  104. package/v2Components/SmsFallback/index.scss +0 -266
  105. package/v2Components/SmsFallback/messages.js +0 -78
  106. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -120
  107. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  108. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  109. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  110. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -208
  111. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
  112. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  113. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  114. package/v2Components/TemplatePreview/constants.js +0 -2
  115. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  116. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  117. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  118. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -17
  119. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
  120. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  121. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  122. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  123. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -227
  124. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  125. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  126. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  127. package/v2Containers/SmsTrai/Edit/index.scss +0 -126
  128. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  129. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  130. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  131. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  132. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -31,13 +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 {
36
- smsTemplateHasMustacheTags,
37
- buildSyntheticSmsMustacheTags,
38
- normalizeRcsTestCardMedia,
39
- mapRcsSuggestionForTestMeta,
40
- } from './utils';
34
+ // Import constants
41
35
  import AddTestCustomerButton from './AddTestCustomer';
42
36
  import ExistingCustomerModal from './ExistingCustomerModal';
43
37
  // Import constants
@@ -87,23 +81,12 @@ import {
87
81
  IMAGE,
88
82
  VIDEO,
89
83
  URL,
90
- PREVIEW_TAB_RCS,
91
- PREVIEW_TAB_SMS_FALLBACK,
92
84
  CHANNELS_USING_ANDROID_PREVIEW_DEVICE,
93
85
  DAYS,
94
- RCS_TEST_META_CONTENT_TYPE_RICHCARD,
95
- RCS_TEST_META_CARD_TYPE_STANDALONE,
96
- RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
97
- RCS_TEST_META_CARD_WIDTH_SMALL,
98
86
  } from './constants';
99
- import { getCdnUrl } from '../../utils/cdnTransformation';
100
- import {
101
- normalizePreviewApiPayload,
102
- extractPreviewFromLiquidResponse,
103
- getSmsFallbackTextForTagExtraction,
104
- } from './previewApiUtils';
105
- import { pickFirstSmsFallbackTemplateString } from '../../v2Containers/Rcs/rcsLibraryHydrationUtils';
106
87
 
88
+ // Import utilities
89
+ import { getCdnUrl } from '../../utils/cdnTransformation';
107
90
  import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
108
91
  import { getMembersLookup } from '../../services/api';
109
92
 
@@ -126,32 +109,6 @@ const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEch
126
109
  });
127
110
  };
128
111
 
129
- /** Preview payload from Redux may be an Immutable Map — normalize for React state. */
130
- const toPlainPreviewData = (data) => {
131
- if (data == null) return null;
132
- const plain = typeof data.toJS === 'function' ? data.toJS() : data;
133
- return normalizePreviewApiPayload(plain);
134
- };
135
-
136
- /**
137
- * Merge existing customValues with tag keys from categorized groups.
138
- * Each group is { required, optional } (arrays of tag objects with fullPath).
139
- * Preserves existing values; adds '' for any tag key not yet present.
140
- * Reusable for RCS+fallback and any flow that needs to ensure customValues has keys for tags.
141
- */
142
- const mergeCustomValuesWithTagKeys = (prev, ...categorizedGroups) => {
143
- const next = { ...(prev || {}) };
144
- categorizedGroups.forEach((group) => {
145
- [...(group.required || []), ...(group.optional || [])].forEach((tag) => {
146
- const key = tag?.fullPath;
147
- if (key && next[key] === undefined) next[key] = '';
148
- });
149
- });
150
- return next;
151
- };
152
-
153
- /** True when `body` contains `{{name}}` mustache tokens (user-fillable personalization tags).
154
- * DLT `{#name#}` slots are pre-bound template variables and are intentionally excluded. */
155
112
 
156
113
  /**
157
114
  * CapTreeSelect and group resolution use strict equality; API data may mix numeric and string ids.
@@ -177,7 +134,7 @@ const testEntityIdsEqual = (a, b) => {
177
134
  */
178
135
  const CommonTestAndPreview = (props) => {
179
136
  const {
180
- intl: { formatMessage, locale: userLocale = 'en' },
137
+ intl: { formatMessage },
181
138
  show,
182
139
  onClose,
183
140
  channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
@@ -213,23 +170,13 @@ const CommonTestAndPreview = (props) => {
213
170
  ...additionalProps
214
171
  } = props;
215
172
 
216
- const smsFallbackContent = additionalProps?.smsFallbackContent;
217
- const smsFallbackTextForTagExtraction = useMemo(
218
- () => getSmsFallbackTextForTagExtraction(smsFallbackContent),
219
- [smsFallbackContent],
220
- );
221
173
  // ============================================
222
174
  // STATE MANAGEMENT
223
175
  // ============================================
224
176
  const [selectedCustomer, setSelectedCustomer] = useState(null);
225
177
  const [requiredTags, setRequiredTags] = useState([]);
226
178
  const [optionalTags, setOptionalTags] = useState([]);
227
- const [smsFallbackExtractedTags, setSmsFallbackExtractedTags] = useState([]);
228
- const [smsFallbackRequiredTags, setSmsFallbackRequiredTags] = useState([]);
229
- const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
230
- const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
231
179
  const [customValues, setCustomValues] = useState({});
232
- const previewCustomValuesRef = useRef({});
233
180
  const [showJSON, setShowJSON] = useState(false);
234
181
  const [tagsExtracted, setTagsExtracted] = useState(false);
235
182
 
@@ -240,8 +187,6 @@ const CommonTestAndPreview = (props) => {
240
187
  const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
241
188
 
242
189
  const [previewDevice, setPreviewDevice] = useState(initialDevice);
243
- const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
244
- const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
245
190
  // Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
246
191
  const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
247
192
  const [previewDataHtml, setPreviewDataHtml] = useState(() => {
@@ -274,22 +219,15 @@ const CommonTestAndPreview = (props) => {
274
219
  [CHANNELS.WHATSAPP]: {
275
220
  domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
276
221
  },
277
- [CHANNELS.RCS]: {
278
- domainId: null,
279
- domainGatewayMapId: null,
280
- gsmSenderId: '',
281
- smsFallbackDomainId: null,
282
- cdmaSenderId: '', // gsmSenderId = RCS sender (domainId|senderId), cdmaSenderId = SMS fallback
283
- },
284
222
  });
285
223
 
286
- const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
224
+ const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
287
225
  const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
288
226
  const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
289
227
  const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
290
228
  const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
291
229
 
292
- // Fetch sender details and WeCRM accounts when Test & Preview opens (SMS, Email, WhatsApp, RCS — same process)
230
+ // Fetch sender details and WeCRM accounts when Test & Preview opens for SMS/Email/WhatsApp
293
231
  useEffect(() => {
294
232
  if (!show || !channel) {
295
233
  return;
@@ -298,99 +236,39 @@ const CommonTestAndPreview = (props) => {
298
236
  if (actions.getSenderDetailsRequested) {
299
237
  actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
300
238
  }
301
- // SMS domains/senders are needed for RCS delivery UI (fallback row + slidebox) whenever RCS is open — not only when fallback body exists.
302
- if (channel === CHANNELS.RCS && actions.getSenderDetailsRequested) {
303
- actions.getSenderDetailsRequested({ channel: CHANNELS.SMS, orgUnitId: orgUnitId ?? -1 });
304
- }
305
239
  if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
306
240
  actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
307
241
  }
308
242
  }
309
243
  }, [show, channel, orgUnitId, actions]);
310
244
 
245
+ useEffect(() => {
246
+ if (!show) {
247
+ setCustomerModal([false, '']);
248
+ setSearchValue('');
249
+ setCustomerData({ name: '', email: '', mobile: '', customerId: '' });
250
+ }
251
+ }, [show]);
252
+
311
253
  const findDefault = (arr) => (arr && arr.find((x) => x.default)) || (arr && arr[0]) || {};
312
254
 
313
255
  // Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
314
256
  useEffect(() => {
315
257
  if (!channel || !channelsWithDeliverySettings.includes(channel)) return;
316
-
317
- if (channel === CHANNELS.RCS) {
318
- const rcsDomainRows = senderDetailsByChannel?.[CHANNELS.RCS] || [];
319
- const smsFallbackDomainRows = senderDetailsByChannel?.[CHANNELS.SMS] || [];
320
- if (!rcsDomainRows.length) return;
321
-
322
- const currentRcsDeliverySettings = testPreviewDeliverySettings?.[CHANNELS.RCS] || {};
323
- const isRcsGsmSenderUnset = !currentRcsDeliverySettings?.gsmSenderId;
324
- const isSmsFallbackSenderUnset = !currentRcsDeliverySettings?.cdmaSenderId;
325
- const isRcsDeliveryFullyUnset = isRcsGsmSenderUnset && isSmsFallbackSenderUnset;
326
- const shouldOnlyFillSmsFallbackSender =
327
- !isRcsGsmSenderUnset && isSmsFallbackSenderUnset && smsFallbackDomainRows.length > 0;
328
-
329
- if (!isRcsDeliveryFullyUnset && !shouldOnlyFillSmsFallbackSender) return;
330
-
331
- const firstRcsDomain = rcsDomainRows[0];
332
- const firstSmsFallbackDomain = smsFallbackDomainRows[0];
333
- const usableRcsGsmSenders = filterUsableGsmSendersForDomain(
334
- firstRcsDomain,
335
- firstRcsDomain?.gsmSenders,
336
- { skipDomainNameEchoFilter: true },
337
- );
338
- const usableSmsFallbackGsmSenders = firstSmsFallbackDomain
339
- ? filterUsableGsmSendersForDomain(firstSmsFallbackDomain, firstSmsFallbackDomain?.gsmSenders)
340
- : [];
341
- const defaultRcsGsmSender = usableRcsGsmSenders[0];
342
- const defaultSmsFallbackGsmSender = usableSmsFallbackGsmSenders[0];
343
- const rcsSenderCompositeValue =
344
- firstRcsDomain?.domainId != null && defaultRcsGsmSender?.value != null
345
- ? `${firstRcsDomain.domainId}|${defaultRcsGsmSender.value}`
346
- : (defaultRcsGsmSender?.value || '');
347
- const smsFallbackSenderCompositeValue =
348
- firstSmsFallbackDomain?.domainId != null && defaultSmsFallbackGsmSender?.value != null
349
- ? `${firstSmsFallbackDomain.domainId}|${defaultSmsFallbackGsmSender.value}`
350
- : (defaultSmsFallbackGsmSender?.value || '');
351
-
352
- setTestPreviewDeliverySettings((prev) => {
353
- const previousRcsSettings = prev?.[CHANNELS.RCS] || {};
354
- if (shouldOnlyFillSmsFallbackSender) {
355
- if (!smsFallbackSenderCompositeValue) return prev;
356
- return {
357
- ...prev,
358
- [CHANNELS.RCS]: {
359
- ...previousRcsSettings,
360
- smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
361
- cdmaSenderId: smsFallbackSenderCompositeValue,
362
- },
363
- };
364
- }
365
- return {
366
- ...prev,
367
- [CHANNELS.RCS]: {
368
- domainId: firstRcsDomain?.domainId ?? null,
369
- domainGatewayMapId: firstRcsDomain?.dgmId ?? null,
370
- gsmSenderId: rcsSenderCompositeValue,
371
- smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
372
- cdmaSenderId: smsFallbackSenderCompositeValue,
373
- },
374
- };
375
- });
376
- return;
377
- }
378
-
379
258
  const domains = senderDetailsByChannel[channel];
380
259
  if (!domains || domains.length === 0) return;
381
260
  const {
382
- domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
261
+ domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
383
262
  } = testPreviewDeliverySettings[channel] || {};
384
- const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
263
+ const isEmptySelection = !domainId && !gsmSenderId && !senderEmail && !senderMobNum;
385
264
  if (!isEmptySelection) return;
386
265
 
387
266
  const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
388
267
  const matchedWhatsappAccount = whatsappAccountFromForm
389
268
  ? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
390
269
  : null;
391
- const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
392
- ? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
393
- registeredSenderIds?.includes(gsm?.value)))
270
+ const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled
271
+ ? domains.filter((domain) => (domain.gsmSenders || []).some((gsm) => registeredSenderIds.includes(gsm.value)))
394
272
  : domains;
395
273
  const [defaultDomain] = domains;
396
274
  const [firstSmsDomain] = smsDomains;
@@ -405,8 +283,8 @@ const CommonTestAndPreview = (props) => {
405
283
  const next = { ...prev };
406
284
  if (channel === CHANNELS.SMS) {
407
285
  const smsGsmSenders = smsTraiDltEnabled
408
- ? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
409
- : firstDomain?.gsmSenders;
286
+ ? (firstDomain.gsmSenders || []).filter((gsm) => registeredSenderIds.includes(gsm.value))
287
+ : firstDomain.gsmSenders;
410
288
  next[channel] = {
411
289
  domainId: firstDomain.domainId,
412
290
  domainGatewayMapId: firstDomain.dgmId,
@@ -439,29 +317,10 @@ const CommonTestAndPreview = (props) => {
439
317
  // MEMOIZED VALUES
440
318
  // ============================================
441
319
 
442
- const allTags = useMemo(
443
- () => [...requiredTags, ...optionalTags, ...smsFallbackRequiredTags, ...smsFallbackOptionalTags],
444
- [requiredTags, optionalTags, smsFallbackRequiredTags, smsFallbackOptionalTags]
445
- );
446
-
447
- const allRequiredTags = useMemo(
448
- () => [...requiredTags, ...smsFallbackRequiredTags],
449
- [requiredTags, smsFallbackRequiredTags]
450
- );
451
-
452
- const buildEmptyValues = useCallback(
453
- () => allTags.reduce((acc, tag) => {
454
- const key = tag?.fullPath;
455
- if (key) acc[key] = '';
456
- return acc;
457
- }, {}),
458
- [allTags]
459
- );
460
-
461
320
  // Check if update preview button should be disabled
462
321
  const isUpdatePreviewDisabled = useMemo(() => (
463
- allRequiredTags.some((tag) => !customValues[tag.fullPath])
464
- ), [allRequiredTags, customValues]);
322
+ requiredTags.some((tag) => !customValues[tag.fullPath])
323
+ ), [requiredTags, customValues]);
465
324
 
466
325
  // Get current content based on channel and editor type
467
326
  const getCurrentContent = useMemo(() => {
@@ -505,13 +364,6 @@ const CommonTestAndPreview = (props) => {
505
364
  return currentTabData.base['sms-editor'];
506
365
  }
507
366
  }
508
- // DLT / Test & Preview shape: { templateConfigs: { template, templateId, ... } }
509
- if (formData.templateConfigs?.template) {
510
- const smsDltTemplateValue = formData.templateConfigs.template;
511
- if (typeof smsDltTemplateValue === 'string') return smsDltTemplateValue;
512
- if (Array.isArray(smsDltTemplateValue)) return smsDltTemplateValue.join('');
513
- return '';
514
- }
515
367
  }
516
368
 
517
369
  // SMS channel fallback - if formData is not available, use content directly
@@ -564,70 +416,7 @@ const CommonTestAndPreview = (props) => {
564
416
  return content || '';
565
417
  }, [channel, formData, currentTab, beeContent, content, beeInstance]);
566
418
 
567
- const leftPanelExtractedTags = useMemo(() => {
568
- if (channel === CHANNELS.SMS) {
569
- const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
570
- if (!smsTemplateHasMustacheTags(smsEditorBody)) return [];
571
- const extractTagsFromApi = extractedTags ?? [];
572
- if (extractTagsFromApi.length > 0) return extractTagsFromApi;
573
- return buildSyntheticSmsMustacheTags(smsEditorBody);
574
- }
575
- const hasFallbackSmsBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
576
- if (channel === CHANNELS.RCS && hasFallbackSmsBody) {
577
- const rcsPrimaryTags = extractedTags ?? [];
578
- const fallbackSmsTextForTags = smsFallbackTextForTagExtraction ?? '';
579
- const fallbackSmsTagRows = smsTemplateHasMustacheTags(fallbackSmsTextForTags)
580
- ? (smsFallbackExtractedTags?.length > 0
581
- ? smsFallbackExtractedTags
582
- : buildSyntheticSmsMustacheTags(fallbackSmsTextForTags))
583
- : [];
584
- const mergedRcsAndFallbackTags = [...rcsPrimaryTags, ...fallbackSmsTagRows];
585
- if (mergedRcsAndFallbackTags.length > 0) return mergedRcsAndFallbackTags;
586
- return buildSyntheticSmsMustacheTags(fallbackSmsTextForTags);
587
- }
588
- return extractedTags ?? [];
589
- }, [
590
- channel,
591
- extractedTags,
592
- getCurrentContent,
593
- smsFallbackContent,
594
- smsFallbackExtractedTags,
595
- smsFallbackTextForTagExtraction,
596
- ]);
597
-
598
- const isRcsSmsFallbackPreviewEnabled =
599
- channel === CHANNELS.RCS
600
- && !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
601
- // Only treat as SMS when user is on the Fallback SMS tab — not whenever fallback exists (RCS tab needs RCS preview API).
602
- const isSmsFallbackTabActive = isRcsSmsFallbackPreviewEnabled && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK;
603
- const activeChannelForActions = isSmsFallbackTabActive ? CHANNELS.SMS : channel;
604
- // VarSegment slot values live in rcsSmsFallbackVarMapped; raw templateContent alone is stale for /preview Body.
605
- const resolvedSmsFallbackBodyForPreviewTab =
606
- smsFallbackTextForTagExtraction
607
- || smsFallbackContent?.templateContent
608
- || smsFallbackContent?.content
609
- || '';
610
- const activeContentForActions = isSmsFallbackTabActive
611
- ? resolvedSmsFallbackBodyForPreviewTab
612
- : getCurrentContent;
613
-
614
- /**
615
- * SMS fallback pane must show /preview API result when user updated preview on that tab (plain text).
616
- * Skip when resolvedBody is RCS-shaped (e.g. user last previewed on RCS tab).
617
- */
618
- // smsFallbackPreviewText is the single source of truth for the resolved SMS fallback preview.
619
- // It is set only by syncSmsFallbackPreview (called from handleUpdatePreview and the
620
- // prefilled-values effect) and reset to undefined on discard / slidebox close.
621
- // Using previewDataHtml as a fallback is unsafe because that state is shared with the primary
622
- // RCS preview and can contain stale SMS or RCS content.
623
- const smsFallbackResolvedText = useMemo(() => {
624
- const hasFallbackBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
625
- if (channel !== CHANNELS.RCS || !hasFallbackBody) return undefined;
626
- if (smsFallbackPreviewText != null) return smsFallbackPreviewText;
627
- return undefined;
628
- }, [channel, smsFallbackContent, smsFallbackPreviewText]);
629
-
630
- // Build test entities tree data from testCustomers prop
419
+ // Build test entities tree data
631
420
  // Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
632
421
  const testEntitiesTreeData = useMemo(() => {
633
422
  const groupsNode = {
@@ -636,7 +425,7 @@ const CommonTestAndPreview = (props) => {
636
425
  selectable: false,
637
426
  children: testGroups?.map((group) => ({
638
427
  title: group?.groupName,
639
- value: normalizeTestEntityId(group?.groupId),
428
+ value: 'group:' + normalizeTestEntityId(group?.groupId),
640
429
  })),
641
430
  };
642
431
 
@@ -646,7 +435,7 @@ const CommonTestAndPreview = (props) => {
646
435
  selectable: false,
647
436
  children: testCustomers?.map((customer) => ({
648
437
  title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
649
- value: normalizeTestEntityId(customer?.userId ?? customer?.customerId),
438
+ value: 'customer:' + normalizeTestEntityId(customer?.userId ?? customer?.customerId),
650
439
  })) || [],
651
440
  };
652
441
 
@@ -726,10 +515,11 @@ const CommonTestAndPreview = (props) => {
726
515
  email: customerData?.email || '',
727
516
  mobile: customerData?.mobile || '',
728
517
  });
518
+ const prefixedAddedId = 'customer:' + normalizedAddedId;
729
519
  setSelectedTestEntities((prev) => (
730
- prev.some((id) => testEntityIdsEqual(id, normalizedAddedId))
520
+ prev.some((id) => id === prefixedAddedId)
731
521
  ? prev
732
- : [...prev, normalizedAddedId]
522
+ : [...prev, prefixedAddedId]
733
523
  ));
734
524
  }
735
525
  handleCloseCustomerModal();
@@ -793,33 +583,13 @@ const CommonTestAndPreview = (props) => {
793
583
  whatsappContent: formDataObj?.whatsappContent,
794
584
  };
795
585
 
796
- case CHANNELS.RCS: {
797
- // For carousel, replace {{N}} tokens in carouselData with resolved semantic vars from
798
- // formData.cardContent so the preview API can resolve {{member.firstName}} etc.
799
- const formDataCardContent =
800
- formDataObj?.versions?.base?.content?.RCS?.rcsContent?.cardContent
801
- ?? formDataObj?.content?.RCS?.rcsContent?.cardContent;
802
- let parsedRcs = {};
803
- try { parsedRcs = JSON.parse(contentStr); } catch (e) { parsedRcs = typeof contentStr === 'object' ? contentStr : {}; }
804
- if (Array.isArray(parsedRcs.carouselData) && Array.isArray(formDataCardContent) && formDataCardContent.length > 0) {
805
- const resolvedCarouselData = parsedRcs.carouselData.map((card, idx) => {
806
- const formCard = formDataCardContent[idx] || {};
807
- return {
808
- ...card,
809
- title: formCard.title || card.title,
810
- bodyText: formCard.description || card.bodyText,
811
- };
812
- });
813
- return {
814
- ...basePayload,
815
- messageBody: JSON.stringify({ ...parsedRcs, carouselData: resolvedCarouselData }),
816
- };
817
- }
586
+ case CHANNELS.RCS:
818
587
  return {
819
588
  ...basePayload,
820
589
  messageBody: contentStr,
590
+ // messageTitle: resolveTagsInText(formDataObj?.rcsTitle || '', customValuesObj),
591
+ // rcsDesc: formDataObj?.rcsDesc,
821
592
  };
822
- }
823
593
 
824
594
  case CHANNELS.INAPP:
825
595
  return {
@@ -859,37 +629,6 @@ const CommonTestAndPreview = (props) => {
859
629
  }
860
630
  };
861
631
 
862
- /**
863
- * When RCS has SMS fallback, refresh fallback preview text via the same Liquid /preview API
864
- * (separate call with SMS channel + fallback template body). Used after primary preview updates.
865
- */
866
- const syncSmsFallbackPreview = async (customValuesForResolve, selectedCustomerObj) => {
867
- const fallbackBodyForLiquidPreview =
868
- getSmsFallbackTextForTagExtraction(smsFallbackContent)
869
- || smsFallbackContent?.templateContent
870
- || smsFallbackContent?.content
871
- || '';
872
- if (channel !== CHANNELS.RCS || !String(fallbackBodyForLiquidPreview).trim()) return;
873
- try {
874
- const smsFallbackPayload = preparePreviewPayload(
875
- CHANNELS.SMS,
876
- formData || {},
877
- fallbackBodyForLiquidPreview,
878
- customValuesForResolve,
879
- selectedCustomerObj
880
- );
881
- const fallbackResponse = await Api.updateEmailPreview(smsFallbackPayload);
882
- const fallbackPreview = extractPreviewFromLiquidResponse(fallbackResponse);
883
- setSmsFallbackPreviewText(
884
- typeof fallbackPreview?.resolvedBody === 'string'
885
- ? fallbackPreview.resolvedBody
886
- : undefined
887
- );
888
- } catch (e) {
889
- /* keep existing smsFallbackPreviewText on failure */
890
- }
891
- };
892
-
893
632
  /**
894
633
  * Prepare payload for tag extraction based on channel
895
634
  */
@@ -913,32 +652,11 @@ const CommonTestAndPreview = (props) => {
913
652
  templateContent: contentStr,
914
653
  };
915
654
 
916
- case CHANNELS.RCS: {
917
- // Carousel templates don't have rcsTitle/rcsDesc — their content lives in cardContent[].
918
- // Use the resolved card strings from formDataObj (resolved by resolveTemplateWithMap in
919
- // testPreviewFormData) so the extraction API receives actual Capillary tag expressions
920
- // instead of numeric {{1}}/{{2}} placeholders.
921
- const rcsCardContentArr =
922
- formDataObj?.versions?.base?.content?.RCS?.rcsContent?.cardContent
923
- ?? formDataObj?.content?.RCS?.rcsContent?.cardContent;
924
- if (Array.isArray(rcsCardContentArr) && rcsCardContentArr.length > 0) {
925
- const carouselTagText = rcsCardContentArr
926
- .map((c) => `${c.title || ''} ${c.description || ''}`)
927
- .join(' ')
928
- .trim();
929
- if (carouselTagText) {
930
- return {
931
- templateSubject: formDataObj?.rcsTitle || '',
932
- templateContent: carouselTagText,
933
- };
934
- }
935
- }
936
- // Standalone (non-carousel) card: use rcsTitle/rcsDesc directly.
655
+ case CHANNELS.RCS:
937
656
  return {
938
657
  templateSubject: formDataObj?.rcsTitle || '',
939
658
  templateContent: formDataObj?.rcsDesc || contentStr,
940
659
  };
941
- }
942
660
 
943
661
  case CHANNELS.INAPP:
944
662
  return {
@@ -1011,7 +729,7 @@ const CommonTestAndPreview = (props) => {
1011
729
  } = carousel || {};
1012
730
  const buttonData = buttons.map((button, index) => {
1013
731
  const {
1014
- type, text, phone_number: phoneNumber, urlType, url,
732
+ type, text, phone_number, urlType, url,
1015
733
  } = button || {};
1016
734
  const buttonObj = {
1017
735
  type,
@@ -1019,7 +737,7 @@ const CommonTestAndPreview = (props) => {
1019
737
  index,
1020
738
  };
1021
739
  if (type === PHONE_NUMBER) {
1022
- buttonObj.phoneNumber = phoneNumber;
740
+ buttonObj.phoneNumber = phone_number;
1023
741
  }
1024
742
  if (type === URL) {
1025
743
  buttonObj.url = url;
@@ -1048,159 +766,7 @@ const CommonTestAndPreview = (props) => {
1048
766
  };
1049
767
  });
1050
768
 
1051
- /**
1052
- * Build createMessageMeta payload for RCS (test message).
1053
- * rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
1054
- * Then rcsDeliverySettings, executionParams, clientName last.
1055
- */
1056
- const buildRcsTestMessagePayload = (
1057
- creativeFormData,
1058
- _unusedEditorContentString,
1059
- _customValuesObj,
1060
- deliverySettingsOverride,
1061
- basePayload,
1062
- _rcsTestMetaExtras = {},
1063
- ) => {
1064
- const rcsSectionFromForm =
1065
- creativeFormData?.versions?.base?.content?.RCS ?? creativeFormData?.content?.RCS ?? {};
1066
- const rcsContentFromForm = rcsSectionFromForm?.rcsContent || {};
1067
- const smsFallbackFromCreativeForm = rcsSectionFromForm?.smsFallBackContent || {};
1068
- let rcsCardPayloadList = [];
1069
- if (Array.isArray(rcsContentFromForm?.cardContent)) {
1070
- rcsCardPayloadList = rcsContentFromForm.cardContent;
1071
- } else if (rcsContentFromForm?.cardContent) {
1072
- rcsCardPayloadList = [rcsContentFromForm.cardContent];
1073
- }
1074
- // Raw title/description with template tags; SMS fallback uses tagged template fields (pickFirst…).
1075
- const cardContentForTestMetaApi = rcsCardPayloadList.map((singleRcsCardPayload) => {
1076
- const normalizedCardMediaForTestApi = singleRcsCardPayload?.media
1077
- ? normalizeRcsTestCardMedia(singleRcsCardPayload.media)
1078
- : undefined;
1079
- const suggestionsFromCard = Array.isArray(singleRcsCardPayload?.suggestions)
1080
- ? singleRcsCardPayload.suggestions
1081
- : [];
1082
- const suggestionsFormattedForTestMeta = suggestionsFromCard.map((suggestionItem, index) =>
1083
- mapRcsSuggestionForTestMeta(suggestionItem, index));
1084
- return {
1085
- title: singleRcsCardPayload?.title ?? '',
1086
- description: singleRcsCardPayload?.description ?? '',
1087
- mediaType: singleRcsCardPayload?.mediaType ?? MEDIA_TYPE_TEXT,
1088
- ...(normalizedCardMediaForTestApi && { media: normalizedCardMediaForTestApi }),
1089
- ...(suggestionsFormattedForTestMeta.length > 0 && {
1090
- suggestions: suggestionsFormattedForTestMeta,
1091
- }),
1092
- };
1093
- });
1094
- // Use the component-level smsFallbackContent prop (has rcsSmsFallbackVarMapped) so DLT
1095
- // {#var#} slots are converted to {{tagName}} mustache tags before sending to createMessageMeta.
1096
- const smsFallbackTaggedTemplateBody =
1097
- getSmsFallbackTextForTagExtraction(smsFallbackContent)
1098
- || pickFirstSmsFallbackTemplateString(smsFallbackFromCreativeForm)
1099
- || '';
1100
- const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
1101
- ? deliverySettingsOverride.cdmaSenderId.split('|')[1]
1102
- : deliverySettingsOverride?.cdmaSenderId;
1103
- const deliveryFallbackSmsId =
1104
- typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
1105
- const creativeFallbackSmsId =
1106
- smsFallbackFromCreativeForm?.senderId != null
1107
- ? String(smsFallbackFromCreativeForm.senderId).trim()
1108
- : '';
1109
- const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
1110
-
1111
- // For DLT orgs: include templateConfigs inside smsFallBackContent so createMessageMeta
1112
- // receives the DLT template ID, registered sender IDs, and body alongside the message.
1113
- // Priority: smsFallBackContent.templateConfigs from the RCS payload (clean API shape,
1114
- // built by createPayload) → fallback to root-level formData.templateConfigs (strip the
1115
- // UI-only traiDltEnabled flag before sending to the API).
1116
- const smsFallbackTemplateConfigsForApi = (() => {
1117
- if (
1118
- smsFallbackFromCreativeForm?.templateConfigs
1119
- && typeof smsFallbackFromCreativeForm.templateConfigs === 'object'
1120
- ) {
1121
- return smsFallbackFromCreativeForm.templateConfigs;
1122
- }
1123
- const rootTc = creativeFormData?.templateConfigs;
1124
- if (rootTc && typeof rootTc === 'object') {
1125
- // eslint-disable-next-line no-unused-vars
1126
- const { traiDltEnabled: _uiFlag, ...tcForApi } = rootTc;
1127
- return Object.keys(tcForApi).length > 0 ? tcForApi : null;
1128
- }
1129
- return null;
1130
- })();
1131
-
1132
- const smsFallBackContent =
1133
- smsFallbackTaggedTemplateBody.trim() !== ''
1134
- ? {
1135
- message: smsFallbackTaggedTemplateBody,
1136
- ...(smsFallbackTemplateConfigsForApi && {
1137
- templateConfigs: smsFallbackTemplateConfigsForApi,
1138
- }),
1139
- }
1140
- : undefined;
1141
-
1142
- // accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
1143
- const accountIdForMeta =
1144
- rcsContentFromForm?.accountId != null && String(rcsContentFromForm.accountId).trim() !== ''
1145
- ? String(rcsContentFromForm.accountId)
1146
- : undefined;
1147
-
1148
- const rcsRichCardContent = {
1149
- contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
1150
- cardType: rcsContentFromForm?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
1151
- cardSettings: rcsContentFromForm?.cardSettings ?? {
1152
- cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
1153
- cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
1154
- },
1155
- ...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
1156
- };
1157
-
1158
- const rcsMessageContent = {
1159
- channel: CHANNELS.RCS,
1160
- ...(accountIdForMeta && { accountId: accountIdForMeta }),
1161
- rcsRichCardContent,
1162
- ...(smsFallBackContent && { smsFallBackContent }),
1163
- };
1164
- const rcsComposite = deliverySettingsOverride?.gsmSenderId ?? '';
1165
- const [rcsDomainId, rcsSenderId] = rcsComposite.includes('|') ? rcsComposite.split('|') : ['', rcsComposite];
1166
- const rcsDeliverySettings = {
1167
- channelSettings: {
1168
- channel: CHANNELS.RCS,
1169
- rcsSender: (rcsSenderId || deliverySettingsOverride?.rcsSender) ?? '',
1170
- domainId:
1171
- rcsDomainId !== '' && rcsDomainId !== undefined && !Number.isNaN(Number(rcsDomainId))
1172
- ? Number(rcsDomainId)
1173
- : (deliverySettingsOverride?.domainId ?? 0),
1174
- fallbackSmsSenderId: fallbackSmsSenderIdForChannel,
1175
- },
1176
- additionalSettings: {
1177
- useTinyUrl: false,
1178
- encryptUrl: false,
1179
- linkTrackingEnabled: false,
1180
- bypassControlUser: false,
1181
- userSubscriptionDisabled: false,
1182
- },
1183
- };
1184
- const { clientName: baseClientName = CLIENT_NAME_CREATIVES, ...restBase } = basePayload;
1185
- return {
1186
- ...restBase,
1187
- rcsMessageContent,
1188
- rcsDeliverySettings,
1189
- executionParams: {},
1190
- clientName: baseClientName,
1191
- };
1192
- };
1193
-
1194
- const prepareTestMessagePayload = (
1195
- channelType,
1196
- formDataObj,
1197
- contentStr,
1198
- customValuesObj,
1199
- recipientDetails,
1200
- previewDataObj,
1201
- deliverySettingsOverride,
1202
- rcsExtra = {},
1203
- ) => {
769
+ const prepareTestMessagePayload = (channelType, formDataObj, contentStr, customValuesObj, recipientDetails, previewDataObj, deliverySettingsOverride) => {
1204
770
  // Base payload structure common to all channels
1205
771
  const basePayload = {
1206
772
  ouId: -1,
@@ -1281,12 +847,12 @@ const CommonTestAndPreview = (props) => {
1281
847
  },
1282
848
  smsDeliverySettings: {
1283
849
  channelSettings: {
1284
- channel: CHANNELS.SMS,
1285
850
  gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
1286
851
  domainId: deliverySettingsOverride?.domainId ?? null,
1287
852
  domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
1288
853
  targetNdnc: false,
1289
854
  cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
855
+ channel: CHANNELS.SMS,
1290
856
  },
1291
857
  additionalSettings: {
1292
858
  useTinyUrl: false,
@@ -1430,7 +996,7 @@ const CommonTestAndPreview = (props) => {
1430
996
  return {
1431
997
  ...basePayload,
1432
998
  whatsappMessageContent: {
1433
- messageBody: resolvedMessageBody || templateEditorValue || '',
999
+ messageBody: templateEditorValue || '',
1434
1000
  accountId: formDataObj?.accountId || '',
1435
1001
  sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
1436
1002
  accountName: formDataObj?.accountName || '',
@@ -1455,7 +1021,16 @@ const CommonTestAndPreview = (props) => {
1455
1021
  }
1456
1022
 
1457
1023
  case CHANNELS.RCS:
1458
- return buildRcsTestMessagePayload(formDataObj, contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra);
1024
+ return {
1025
+ ...basePayload,
1026
+ rcsMessageContent: {
1027
+ channel: CHANNELS.RCS,
1028
+ messageBody: contentStr,
1029
+ rcsType: additionalProps?.rcsType,
1030
+ rcsImageSrc: formDataObj?.rcsImageSrc,
1031
+ rcsSuggestions: formDataObj?.rcsSuggestions,
1032
+ },
1033
+ };
1459
1034
 
1460
1035
  case CHANNELS.INAPP: {
1461
1036
  // InApp payload structure similar to MobilePush
@@ -2294,46 +1869,6 @@ const CommonTestAndPreview = (props) => {
2294
1869
  *
2295
1870
  * IMPORTANT: Use raw content/formData initially, only use previewDataHtml if preview call was made
2296
1871
  */
2297
- // Memoized RCS carousel content: only recomputes when the API response or raw template changes,
2298
- // NOT on every customValues keystroke. Uses previewCustomValuesRef captured at Update Preview time.
2299
- const rcsCarouselContentObj = useMemo(() => {
2300
- if (channel !== CHANNELS.RCS) return null;
2301
- let resolvedContent = null;
2302
- if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
2303
- resolvedContent = previewDataHtml.resolvedBody;
2304
- if (typeof resolvedContent === 'string') {
2305
- try { resolvedContent = JSON.parse(resolvedContent); } catch (e) { resolvedContent = null; }
2306
- }
2307
- }
2308
- if (resolvedContent && typeof resolvedContent === 'object') return resolvedContent;
2309
-
2310
- let rawRcs = content;
2311
- if (typeof rawRcs === 'string') {
2312
- try { rawRcs = JSON.parse(rawRcs); } catch (e) { rawRcs = content; }
2313
- }
2314
- const snapCustomValues = previewCustomValuesRef.current;
2315
- const hasEnteredValues = snapCustomValues && Object.keys(snapCustomValues).some((k) => snapCustomValues[k]);
2316
- if (hasPreviewCallBeenMade && hasEnteredValues && Array.isArray(rawRcs?.carouselData)) {
2317
- const formDataCardContent =
2318
- formData?.versions?.base?.content?.RCS?.rcsContent?.cardContent
2319
- ?? formData?.content?.RCS?.rcsContent?.cardContent
2320
- ?? [];
2321
- return {
2322
- ...rawRcs,
2323
- carouselData: rawRcs.carouselData.map((card, idx) => {
2324
- const formCard = formDataCardContent[idx] || {};
2325
- return {
2326
- ...card,
2327
- title: resolveTagsInText(formCard.title || card.title || '', snapCustomValues),
2328
- bodyText: resolveTagsInText(formCard.description || card.bodyText || '', snapCustomValues),
2329
- };
2330
- }),
2331
- };
2332
- }
2333
- return rawRcs || {};
2334
- // eslint-disable-next-line react-hooks/exhaustive-deps
2335
- }, [channel, content, hasPreviewCallBeenMade, previewDataHtml, formData]);
2336
-
2337
1872
  const prepareUnifiedPreviewProps = () => {
2338
1873
  // Prepare content based on channel
2339
1874
  let contentObj = {};
@@ -2395,14 +1930,42 @@ const CommonTestAndPreview = (props) => {
2395
1930
  contentObj = parsedContent || {};
2396
1931
  }
2397
1932
  } else if (channel === CHANNELS.RCS) {
2398
- // rcsCarouselContentObj is a useMemo that handles both the API-response path and the
2399
- // client-side fallback. It is stable across keystrokes (only recomputes when
2400
- // hasPreviewCallBeenMade / previewDataHtml / content / formData change).
1933
+ // For RCS, content is an object with rcsTitle, rcsDesc, rcsImageSrc, suggestions, etc.
1934
+ // Content comes from RCS component state, passed via content prop
1935
+ let resolvedContent = null;
1936
+
1937
+ // Only use previewDataHtml if preview call was made
1938
+ if (hasPreviewCallBeenMade && previewDataHtml?.resolvedBody) {
1939
+ resolvedContent = previewDataHtml.resolvedBody;
1940
+
1941
+ // Handle case where resolvedBody might be a JSON string
1942
+ if (typeof resolvedContent === 'string') {
1943
+ try {
1944
+ resolvedContent = JSON.parse(resolvedContent);
1945
+ } catch (e) {
1946
+ // If parsing fails, treat as null so we fall back to content
1947
+ resolvedContent = null;
1948
+ }
1949
+ }
1950
+ }
1951
+
1952
+ // Parse content if it's a string
2401
1953
  let parsedRcsContent = content;
2402
1954
  if (typeof content === 'string') {
2403
- try { parsedRcsContent = JSON.parse(content); } catch (e) { parsedRcsContent = content; }
1955
+ try {
1956
+ parsedRcsContent = JSON.parse(content);
1957
+ } catch (e) {
1958
+ parsedRcsContent = content;
1959
+ }
1960
+ }
1961
+
1962
+ // Use resolvedContent if available (from preview call), otherwise use raw content
1963
+ if (resolvedContent && typeof resolvedContent === 'object') {
1964
+ contentObj = { ...resolvedContent };
1965
+ } else {
1966
+ // Use raw content if no preview call was made or resolvedContent is not available
1967
+ contentObj = parsedRcsContent || {};
2404
1968
  }
2405
- contentObj = rcsCarouselContentObj || parsedRcsContent || {};
2406
1969
  } else if (channel === CHANNELS.INAPP) {
2407
1970
  // For InApp, content is an object with androidContent and iosContent (similar to MobilePush)
2408
1971
  // Content comes from InApp component state, passed via content prop
@@ -2605,10 +2168,6 @@ const CommonTestAndPreview = (props) => {
2605
2168
  formatMessage,
2606
2169
  lastModified: formData?.lastModified,
2607
2170
  updatedByName: formData?.updatedByName,
2608
- smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
2609
- smsFallbackResolvedText,
2610
- activePreviewTab,
2611
- onPreviewTabChange: setActivePreviewTab,
2612
2171
  };
2613
2172
  };
2614
2173
 
@@ -2648,12 +2207,7 @@ const CommonTestAndPreview = (props) => {
2648
2207
  }, [show, beeInstance, currentTab, channel]);
2649
2208
 
2650
2209
  /**
2651
- * Initial data load when slidebox opens.
2652
- * EXTRACT TAGS CALL SITES (on open/edit RCS):
2653
- * 1. Here (non-email): actions.extractTagsRequested() at line ~2161 when show is true.
2654
- * 2. RCS SMS fallback useEffect below: Api.extractTagsWithMetaData() for fallback message.
2655
- * 3. handleExtractTags() (user clicks "Enter custom values for tags") – not from effects.
2656
- * The "Process extracted tags" effect only processes API results and must not call extract again.
2210
+ * Initial data load when slidebox opens
2657
2211
  */
2658
2212
  useEffect(() => {
2659
2213
  if (show) {
@@ -2738,61 +2292,7 @@ const CommonTestAndPreview = (props) => {
2738
2292
  actions.getTestGroupsRequested();
2739
2293
  }
2740
2294
  }
2741
- // getCurrentContent: RCS applies cardVarMapped → placeholder resolution; re-extract when it changes.
2742
- }, [show, beeInstance, currentTab, channel, getCurrentContent]);
2743
-
2744
- /**
2745
- * RCS with SMS fallback: extract tags for fallback SMS content as well
2746
- * (so we can show a separate "Fallback SMS tags" section in left panel).
2747
- */
2748
- useEffect(() => {
2749
- let cancelled = false;
2750
-
2751
- if (!show || channel !== CHANNELS.RCS) {
2752
- return () => {
2753
- cancelled = true;
2754
- };
2755
- }
2756
-
2757
- if (!smsFallbackContent?.templateContent && !smsFallbackContent?.content) {
2758
- setSmsFallbackExtractedTags([]);
2759
- setSmsFallbackRequiredTags([]);
2760
- setSmsFallbackOptionalTags([]);
2761
- return () => {
2762
- cancelled = true;
2763
- };
2764
- }
2765
-
2766
- setIsExtractingSmsFallbackTags(true);
2767
- (async () => {
2768
- try {
2769
- const fallbackBodyForExtractApi = getSmsFallbackTextForTagExtraction(smsFallbackContent);
2770
- const payload = {
2771
- messageTitle: '',
2772
- messageBody: fallbackBodyForExtractApi || '',
2773
- };
2774
- 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
2775
- let smsFallbackTagTree = response?.data ?? [];
2776
- if (!Array.isArray(smsFallbackTagTree)) smsFallbackTagTree = [];
2777
- if (!smsTemplateHasMustacheTags(fallbackBodyForExtractApi)) {
2778
- smsFallbackTagTree = [];
2779
- } else if (smsFallbackTagTree.length === 0) {
2780
- smsFallbackTagTree = buildSyntheticSmsMustacheTags(fallbackBodyForExtractApi);
2781
- }
2782
- if (cancelled) return;
2783
- setSmsFallbackExtractedTags(smsFallbackTagTree);
2784
- } catch (e) {
2785
- if (cancelled) return;
2786
- setSmsFallbackExtractedTags([]);
2787
- } finally {
2788
- if (!cancelled) setIsExtractingSmsFallbackTags(false);
2789
- }
2790
- })();
2791
-
2792
- return () => {
2793
- cancelled = true;
2794
- };
2795
- }, [show, channel, smsFallbackContent]);
2295
+ }, [show, beeInstance, currentTab, channel]);
2796
2296
 
2797
2297
  /**
2798
2298
  * Email-specific: Handle content updates for both BEE and CKEditor
@@ -2844,22 +2344,15 @@ const CommonTestAndPreview = (props) => {
2844
2344
  setSelectedCustomer(null);
2845
2345
  setRequiredTags([]);
2846
2346
  setOptionalTags([]);
2847
- setSmsFallbackExtractedTags([]);
2848
- setSmsFallbackRequiredTags([]);
2849
- setSmsFallbackOptionalTags([]);
2850
- setIsExtractingSmsFallbackTags(false);
2851
2347
  setCustomValues({});
2852
2348
  setShowJSON(false);
2853
2349
  setTagsExtracted(false);
2854
2350
  setPreviewDevice(DESKTOP);
2855
- setActivePreviewTab(PREVIEW_TAB_RCS);
2856
- setSmsFallbackPreviewText(undefined);
2857
2351
  setSelectedTestEntities([]);
2858
2352
  actions.clearPrefilledValues();
2859
2353
  } else {
2860
2354
  // Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
2861
2355
  setPreviewDevice(initialDevice);
2862
- setActivePreviewTab(PREVIEW_TAB_RCS);
2863
2356
  }
2864
2357
  }, [show, initialDevice]);
2865
2358
 
@@ -2868,10 +2361,79 @@ const CommonTestAndPreview = (props) => {
2868
2361
  */
2869
2362
  useEffect(() => {
2870
2363
  if (previewData) {
2871
- setPreviewDataHtml(toPlainPreviewData(previewData));
2364
+ setPreviewDataHtml(previewData);
2872
2365
  }
2873
2366
  }, [previewData]);
2874
2367
 
2368
+ /**
2369
+ * Process extracted tags and categorize them
2370
+ */
2371
+ useEffect(() => {
2372
+ // Categorize tags into required and optional
2373
+ const required = [];
2374
+ const optional = [];
2375
+ let hasPersonalizationTags = false;
2376
+
2377
+ if (extractedTags?.length > 0) {
2378
+ const processTag = (tag, parentPath = '') => {
2379
+ const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
2380
+
2381
+ // Skip unsubscribe tag for input fields
2382
+ if (tag?.name === UNSUBSCRIBE_TAG_NAME) {
2383
+ return;
2384
+ }
2385
+
2386
+ hasPersonalizationTags = true;
2387
+
2388
+ if (tag?.metaData?.userDriven === false) {
2389
+ required.push({
2390
+ ...tag,
2391
+ fullPath: currentPath,
2392
+ });
2393
+ } else if (tag?.metaData?.userDriven === true) {
2394
+ optional.push({
2395
+ ...tag,
2396
+ fullPath: currentPath,
2397
+ });
2398
+ }
2399
+
2400
+ if (tag?.children?.length > 0) {
2401
+ tag.children.forEach((child) => processTag(child, currentPath));
2402
+ }
2403
+ };
2404
+
2405
+ extractedTags.forEach((tag) => processTag(tag));
2406
+
2407
+ if (hasPersonalizationTags) {
2408
+ setRequiredTags(required);
2409
+ setOptionalTags(optional);
2410
+ setTagsExtracted(true); // Mark tags as extracted and processed
2411
+
2412
+ // Initialize custom values for required tags
2413
+ const initialValues = {};
2414
+ required.forEach((tag) => {
2415
+ initialValues[tag?.fullPath] = '';
2416
+ });
2417
+ optional.forEach((tag) => {
2418
+ initialValues[tag?.fullPath] = '';
2419
+ });
2420
+ setCustomValues(initialValues);
2421
+ } else {
2422
+ // Reset all tag-related state if no personalization tags
2423
+ setRequiredTags([]);
2424
+ setOptionalTags([]);
2425
+ setCustomValues({});
2426
+ setTagsExtracted(false);
2427
+ }
2428
+ } else {
2429
+ // Reset all tag-related state if no tags
2430
+ setRequiredTags([]);
2431
+ setOptionalTags([]);
2432
+ setCustomValues({});
2433
+ setTagsExtracted(false);
2434
+ }
2435
+ }, [extractedTags]);
2436
+
2875
2437
  /**
2876
2438
  * Handle customer selection and fetch prefilled values
2877
2439
  */
@@ -2879,15 +2441,17 @@ const CommonTestAndPreview = (props) => {
2879
2441
  if (selectedCustomer && config.enableCustomerSearch !== false) {
2880
2442
  setTagsExtracted(true); // Auto-open custom values editor
2881
2443
 
2444
+ // Get all available tags
2445
+ const allTags = [...requiredTags, ...optionalTags];
2882
2446
  const requiredTagObj = {};
2883
- allRequiredTags.forEach((tag) => {
2447
+ requiredTags.forEach((tag) => {
2884
2448
  requiredTagObj[tag?.fullPath] = '';
2885
2449
  });
2886
2450
  if (allTags.length > 0) {
2887
2451
  const payload = preparePreviewPayload(
2888
- activeChannelForActions,
2452
+ channel,
2889
2453
  formData || {},
2890
- activeContentForActions,
2454
+ getCurrentContent,
2891
2455
  {
2892
2456
  ...requiredTagObj,
2893
2457
  },
@@ -2896,7 +2460,7 @@ const CommonTestAndPreview = (props) => {
2896
2460
  actions.getPrefilledValuesRequested(payload);
2897
2461
  }
2898
2462
  }
2899
- }, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
2463
+ }, [selectedCustomer]);
2900
2464
 
2901
2465
  /**
2902
2466
  * Update custom values with prefilled values from API
@@ -2907,29 +2471,24 @@ const CommonTestAndPreview = (props) => {
2907
2471
  if (prefilledValues && selectedCustomer) {
2908
2472
  // Always replace all values with prefilled values
2909
2473
  const updatedValues = {};
2910
- allTags.forEach((tag) => {
2474
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2911
2475
  updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
2912
2476
  });
2913
2477
 
2914
2478
  setCustomValues(updatedValues);
2915
2479
 
2916
2480
  // Update preview with prefilled values (this is a valid preview call trigger)
2917
- // For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
2918
- // SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
2919
- const previewChannelForPrefill = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
2920
- const previewContentForPrefill = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
2921
2481
  const payload = preparePreviewPayload(
2922
- previewChannelForPrefill,
2482
+ channel,
2923
2483
  formData || {},
2924
- previewContentForPrefill,
2484
+ getCurrentContent,
2925
2485
  updatedValues,
2926
2486
  selectedCustomer
2927
2487
  );
2928
2488
  actions.updatePreviewRequested(payload);
2929
2489
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
2930
- void syncSmsFallbackPreview(updatedValues, selectedCustomer);
2931
2490
  }
2932
- }, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
2491
+ }, [JSON.stringify(prefilledValues), selectedCustomer]);
2933
2492
 
2934
2493
  /**
2935
2494
  * Map channel constants to display names (lowercase for message)
@@ -3023,7 +2582,11 @@ const CommonTestAndPreview = (props) => {
3023
2582
  setTagsExtracted(true); // Auto-open custom values editor
3024
2583
 
3025
2584
  // Clear any existing values while waiting for prefilled values
3026
- setCustomValues(buildEmptyValues());
2585
+ const emptyValues = {};
2586
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2587
+ emptyValues[tag?.fullPath] = '';
2588
+ });
2589
+ setCustomValues(emptyValues);
3027
2590
  };
3028
2591
 
3029
2592
  /**
@@ -3037,7 +2600,11 @@ const CommonTestAndPreview = (props) => {
3037
2600
  actions.clearPreviewErrors();
3038
2601
 
3039
2602
  // Initialize empty values for all tags
3040
- setCustomValues(buildEmptyValues());
2603
+ const emptyValues = {};
2604
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2605
+ emptyValues[tag?.fullPath] = '';
2606
+ });
2607
+ setCustomValues(emptyValues);
3041
2608
 
3042
2609
  // Don't make preview call when clearing selection - just reset to raw content
3043
2610
  // Preview will be shown using raw formData/content
@@ -3073,20 +2640,17 @@ const CommonTestAndPreview = (props) => {
3073
2640
  */
3074
2641
  const handleDiscardCustomValues = () => {
3075
2642
  // Initialize empty values for all tags
3076
- const emptyValues = buildEmptyValues();
2643
+ const emptyValues = {};
2644
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2645
+ emptyValues[tag?.fullPath] = '';
2646
+ });
3077
2647
  setCustomValues(emptyValues);
3078
2648
 
3079
- // Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
3080
- setSmsFallbackPreviewText(undefined);
3081
-
3082
2649
  // Update preview with empty values (this is a valid preview call trigger)
3083
- // For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped.
3084
- const previewChannelForDiscard = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
3085
- const previewContentForDiscard = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
3086
2650
  const payload = preparePreviewPayload(
3087
- previewChannelForDiscard,
2651
+ channel,
3088
2652
  formData || {},
3089
- previewContentForDiscard,
2653
+ getCurrentContent,
3090
2654
  emptyValues,
3091
2655
  selectedCustomer
3092
2656
  );
@@ -3100,24 +2664,14 @@ const CommonTestAndPreview = (props) => {
3100
2664
  */
3101
2665
  const handleUpdatePreview = async () => {
3102
2666
  try {
3103
- // Capture customValues at click time so the carousel preview only updates here,
3104
- // not on every subsequent keystroke.
3105
- previewCustomValuesRef.current = customValues;
3106
- // For RCS: always dispatch with RCS channel/content so previewDataHtml stays RCS-shaped,
3107
- // even when the user triggers update from the SMS fallback tab.
3108
- // SMS fallback preview is kept in sync by syncSmsFallbackPreview via smsFallbackPreviewText.
3109
- const previewChannel = channel === CHANNELS.RCS ? CHANNELS.RCS : activeChannelForActions;
3110
- const previewContent = channel === CHANNELS.RCS ? getCurrentContent : activeContentForActions;
3111
2667
  const payload = preparePreviewPayload(
3112
- previewChannel,
2668
+ channel,
3113
2669
  formData || {},
3114
- previewContent,
2670
+ getCurrentContent,
3115
2671
  customValues,
3116
2672
  selectedCustomer
3117
2673
  );
3118
2674
  await actions.updatePreviewRequested(payload);
3119
-
3120
- await syncSmsFallbackPreview(customValues, selectedCustomer);
3121
2675
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
3122
2676
  } catch (error) {
3123
2677
  CapNotification.error({
@@ -3127,115 +2681,25 @@ const CommonTestAndPreview = (props) => {
3127
2681
  };
3128
2682
 
3129
2683
  /**
3130
- * Categorize extracted tags into required/optional.
3131
- */
3132
- const categorizeTags = (tagsTree = []) => {
3133
- const required = [];
3134
- const optional = [];
3135
- let hasPersonalizationTags = false;
3136
- const processTag = (tag, parentPath = '') => {
3137
- const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
3138
-
3139
- // Skip unsubscribe tag for input fields
3140
- if (tag?.name === UNSUBSCRIBE_TAG_NAME) return;
3141
-
3142
- hasPersonalizationTags = true;
3143
- const userDriven = tag?.metaData?.userDriven;
3144
- if (userDriven === true) {
3145
- optional.push({ ...tag, fullPath: currentPath });
3146
- } else {
3147
- // false or missing (SMS/DLT extract often omits metaData) → required for test values
3148
- required.push({ ...tag, fullPath: currentPath });
3149
- }
3150
-
3151
- if (tag?.children?.length > 0) {
3152
- tag.children.forEach((child) => processTag(child, currentPath));
3153
- }
3154
- };
3155
-
3156
- (tagsTree || []).forEach((tag) => processTag(tag));
3157
- return { required, optional, hasPersonalizationTags };
3158
- };
3159
-
3160
- /**
3161
- * Apply tag extraction when content comes from RCS + SMS fallback (no API call).
3162
- */
3163
- const applyRcsSmsFallbackTagExtraction = () => {
3164
- const rcsPrimaryCategorized = categorizeTags(extractedTags ?? []);
3165
- const fallbackSmsResolvedForTags = smsFallbackTextForTagExtraction ?? '';
3166
- let fallbackSmsTagTree = smsFallbackExtractedTags?.length > 0
3167
- ? smsFallbackExtractedTags
3168
- : buildSyntheticSmsMustacheTags(fallbackSmsResolvedForTags);
3169
- if (!smsTemplateHasMustacheTags(fallbackSmsResolvedForTags)) {
3170
- fallbackSmsTagTree = [];
3171
- }
3172
- const fallbackSmsCategorized = categorizeTags(fallbackSmsTagTree);
3173
- setRequiredTags(rcsPrimaryCategorized.required);
3174
- setOptionalTags(rcsPrimaryCategorized.optional);
3175
- setSmsFallbackRequiredTags(fallbackSmsCategorized.required);
3176
- setSmsFallbackOptionalTags(fallbackSmsCategorized.optional);
3177
- setTagsExtracted(
3178
- rcsPrimaryCategorized.hasPersonalizationTags || fallbackSmsCategorized.hasPersonalizationTags,
3179
- );
3180
- setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, rcsPrimaryCategorized, fallbackSmsCategorized));
3181
- };
3182
-
3183
- /**
3184
- * When extract-tags API returns, map Redux `extractedTags` into required/optional so
3185
- * CustomValuesEditor shows personalization fields (effect was previously commented out).
3186
- * RCS + SMS fallback: merge primary + fallback tag trees when fallback template exists.
2684
+ * Handle extract tags
3187
2685
  */
3188
- useEffect(() => {
3189
- if (!show) return;
3190
- const hasFallbackSmsTemplate = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
3191
- if (channel === CHANNELS.RCS && hasFallbackSmsTemplate) {
3192
- applyRcsSmsFallbackTagExtraction();
3193
- return;
3194
- }
3195
- const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
3196
- let smsTagSource = channel === CHANNELS.SMS && (!extractedTags || extractedTags.length === 0)
3197
- ? buildSyntheticSmsMustacheTags(smsEditorBody)
3198
- : (extractedTags ?? []);
3199
- if (channel === CHANNELS.SMS && !smsTemplateHasMustacheTags(smsEditorBody)) {
3200
- smsTagSource = [];
3201
- }
3202
- const { required, optional, hasPersonalizationTags } = categorizeTags(smsTagSource);
3203
- setRequiredTags(required);
3204
- setOptionalTags(optional);
3205
- setTagsExtracted(hasPersonalizationTags);
3206
- setCustomValues((prev) => mergeCustomValuesWithTagKeys(prev, { required, optional }, { required: [], optional: [] }));
3207
- // eslint-disable-next-line react-hooks/exhaustive-deps -- applyRcsSmsFallbackTagExtraction closes over latest extractedTags/smsFallbackExtractedTags
3208
- }, [show, extractedTags, channel, smsFallbackContent, smsFallbackExtractedTags, getCurrentContent, smsFallbackTextForTagExtraction]);
2686
+ const handleExtractTags = () => {
2687
+ // Get content based on channel
2688
+ let contentToExtract = getCurrentContent;
3209
2689
 
3210
- /**
3211
- * Get content to run tag extraction on (channel-specific).
3212
- */
3213
- const getContentForTagExtraction = () => {
3214
- let contentToExtract = activeContentForActions;
3215
2690
  if (channel === CHANNELS.EMAIL && formData) {
3216
2691
  const currentTabData = formData[currentTab - 1];
3217
2692
  const activeTab = currentTabData?.activeTab;
3218
2693
  const templateContent = currentTabData?.[activeTab]?.['template-content'];
3219
2694
  contentToExtract = templateContent || contentToExtract;
3220
2695
  }
3221
- return contentToExtract;
3222
- };
3223
-
3224
- /**
3225
- * Handle extract tags
3226
- */
3227
- const handleExtractTags = () => {
3228
- if (channel === CHANNELS.RCS) {
3229
- applyRcsSmsFallbackTagExtraction();
3230
- return;
3231
- }
3232
2696
 
3233
- const contentToExtract = getContentForTagExtraction();
2697
+ // Check for personalization tags (excluding unsubscribe)
3234
2698
  const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
3235
2699
  const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
3236
- const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
3237
2700
 
3238
- if (onlyUnsubscribe) {
2701
+ if (!hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME)) {
2702
+ // If only unsubscribe tag is present, show noTagsExtracted message
3239
2703
  setTagsExtracted(false);
3240
2704
  setRequiredTags([]);
3241
2705
  setOptionalTags([]);
@@ -3243,9 +2707,10 @@ const CommonTestAndPreview = (props) => {
3243
2707
  return;
3244
2708
  }
3245
2709
 
2710
+ // Extract tags
3246
2711
  setTagsExtracted(true);
3247
2712
  const { templateSubject, templateContent } = prepareTagExtractionPayload(
3248
- activeChannelForActions,
2713
+ channel,
3249
2714
  formData || {},
3250
2715
  contentToExtract
3251
2716
  );
@@ -3307,7 +2772,7 @@ const CommonTestAndPreview = (props) => {
3307
2772
  if (existingTestCustomer) {
3308
2773
  const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
3309
2774
  if (entityId != null) {
3310
- const id = normalizeTestEntityId(entityId);
2775
+ const id = 'customer:' + normalizeTestEntityId(entityId);
3311
2776
  setSelectedTestEntities((prev) => (
3312
2777
  prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
3313
2778
  ));
@@ -3344,7 +2809,7 @@ const CommonTestAndPreview = (props) => {
3344
2809
  (c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
3345
2810
  );
3346
2811
  if (alreadyInTestListByCustomerId) {
3347
- const id = normalizeTestEntityId(customerIdFromLookup);
2812
+ const id = 'customer:' + normalizeTestEntityId(customerIdFromLookup);
3348
2813
  setSelectedTestEntities((prev) => (
3349
2814
  prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
3350
2815
  ));
@@ -3381,21 +2846,26 @@ const CommonTestAndPreview = (props) => {
3381
2846
  const handleSendTestMessage = () => {
3382
2847
  const allUserIds = [];
3383
2848
  selectedTestEntities.forEach((entityId) => {
3384
- const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, entityId));
3385
- if (group) {
3386
- allUserIds.push(...group.userIds);
2849
+ if (String(entityId).startsWith('group:')) {
2850
+ const rawId = String(entityId).slice('group:'.length);
2851
+ const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, rawId));
2852
+ if (group) {
2853
+ allUserIds.push(...group.userIds);
2854
+ }
3387
2855
  } else {
3388
- allUserIds.push(entityId);
2856
+ const rawId = String(entityId).startsWith('customer:')
2857
+ ? String(entityId).slice('customer:'.length)
2858
+ : String(entityId);
2859
+ allUserIds.push(Number(rawId));
3389
2860
  }
3390
2861
  });
3391
2862
  const uniqueUserIds = [...new Set(allUserIds)];
3392
2863
 
3393
- const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
2864
+ const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
3394
2865
  ? testPreviewDeliverySettings[channel]
3395
2866
  : null;
3396
2867
 
3397
- // createMessageMeta must match the creative channel and full creative (RCS + SMS fallback in one meta).
3398
- // Do not use activeChannelForActions / activeContentForActions — those follow the RCS vs Fallback SMS *preview* tab.
2868
+ // Create initial payload based on channel
3399
2869
  const initialPayload = prepareTestMessagePayload(
3400
2870
  channel,
3401
2871
  formData || content || {},
@@ -3403,8 +2873,7 @@ const CommonTestAndPreview = (props) => {
3403
2873
  customValues,
3404
2874
  uniqueUserIds,
3405
2875
  previewData,
3406
- deliveryOverride,
3407
- {},
2876
+ deliveryOverride
3408
2877
  );
3409
2878
 
3410
2879
  actions.createMessageMetaRequested(
@@ -3437,10 +2906,11 @@ const CommonTestAndPreview = (props) => {
3437
2906
  // ============================================
3438
2907
  // RENDER HELPER FUNCTIONS
3439
2908
  // ============================================
2909
+
3440
2910
  const renderLeftPanelContent = () => (
3441
2911
  <LeftPanelContent
3442
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
3443
- extractedTags={leftPanelExtractedTags}
2912
+ isExtractingTags={isExtractingTags}
2913
+ extractedTags={extractedTags}
3444
2914
  selectedCustomer={selectedCustomer}
3445
2915
  handleCustomerSelect={handleCustomerSelect}
3446
2916
  handleSearchCustomer={handleSearchCustomer}
@@ -3458,29 +2928,15 @@ const CommonTestAndPreview = (props) => {
3458
2928
 
3459
2929
  const renderCustomValuesEditor = () => (
3460
2930
  <CustomValuesEditor
3461
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
2931
+ isExtractingTags={isExtractingTags}
3462
2932
  isUpdatePreviewDisabled={isUpdatePreviewDisabled}
3463
2933
  showJSON={showJSON}
3464
2934
  setShowJSON={setShowJSON}
3465
2935
  customValues={customValues}
3466
2936
  handleJSONTextChange={handleJSONTextChange}
3467
- sections={[
3468
- {
3469
- key: channel,
3470
- title:
3471
- channel === CHANNELS.RCS
3472
- ? messages.rcsTagsSectionTitle
3473
- : messages[`${channel}TagsSectionTitle`],
3474
- requiredTags,
3475
- optionalTags,
3476
- },
3477
- {
3478
- key: PREVIEW_TAB_SMS_FALLBACK,
3479
- title: isRcsSmsFallbackPreviewEnabled ? messages.smsFallbackTagsSectionTitle : null,
3480
- requiredTags: smsFallbackRequiredTags,
3481
- optionalTags: smsFallbackOptionalTags,
3482
- },
3483
- ]}
2937
+ extractedTags={extractedTags}
2938
+ requiredTags={requiredTags}
2939
+ optionalTags={optionalTags}
3484
2940
  handleCustomValueChange={handleCustomValueChange}
3485
2941
  handleDiscardCustomValues={handleDiscardCustomValues}
3486
2942
  handleUpdatePreview={handleUpdatePreview}
@@ -3496,13 +2952,18 @@ const CommonTestAndPreview = (props) => {
3496
2952
  }));
3497
2953
  };
3498
2954
 
3499
- /** Trim pasted emails (trailing CR/LF). Allow any input for SMS; valid-only check is in renderAddTestCustomerButton. */
2955
+ /** Trim pasted emails (trailing CR/LF). SMS: strip non-digits so pasted formatted numbers match isValidMobile / API. */
3500
2956
  const handleTestCustomersSearch = useCallback((value) => {
3501
2957
  if (value == null || value === '') {
3502
2958
  setSearchValue('');
3503
2959
  return;
3504
2960
  }
3505
- setSearchValue(String(value).trim());
2961
+ const raw = String(value).trim();
2962
+ if (channel === CHANNELS.SMS) {
2963
+ setSearchValue(formatPhoneNumber(raw));
2964
+ } else {
2965
+ setSearchValue(raw);
2966
+ }
3506
2967
  }, [channel]);
3507
2968
 
3508
2969
  const renderSendTestMessage = () => (
@@ -3520,13 +2981,12 @@ const CommonTestAndPreview = (props) => {
3520
2981
  renderAddTestCustomerButton={renderAddTestCustomerButton}
3521
2982
  formatMessage={formatMessage}
3522
2983
  deliverySettings={testPreviewDeliverySettings[channel]}
3523
- senderDetailsByChannel={senderDetailsByChannel}
2984
+ senderDetailsOptions={senderDetailsByChannel[channel]}
3524
2985
  wecrmAccounts={wecrmAccounts}
3525
2986
  onSaveDeliverySettings={handleSaveDeliverySettings}
3526
2987
  isLoadingSenderDetails={isLoadingSenderDetails}
3527
2988
  smsTraiDltEnabled={smsTraiDltEnabled}
3528
2989
  registeredSenderIds={registeredSenderIds}
3529
- isChannelSmsFallbackPreviewEnabled={isRcsSmsFallbackPreviewEnabled}
3530
2990
  searchValue={searchValue}
3531
2991
  setSearchValue={handleTestCustomersSearch}
3532
2992
  />
@@ -3540,13 +3000,14 @@ const CommonTestAndPreview = (props) => {
3540
3000
 
3541
3001
  const renderAddTestCustomerButton = () => {
3542
3002
  const raw = (searchValue || '').trim();
3003
+ const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
3543
3004
  const showAddButton =
3544
3005
  [CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
3545
- (channel === CHANNELS.EMAIL ? isValidEmail(raw) : isValidMobile(formatPhoneNumber(raw)));
3006
+ (channel === CHANNELS.EMAIL ? isValidEmail(value) : isValidMobile(value));
3546
3007
  if (!showAddButton) return null;
3547
3008
  return (
3548
3009
  <AddTestCustomerButton
3549
- searchValue={channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw}
3010
+ searchValue={value}
3550
3011
  handleAddTestCustomer={handleAddTestCustomer}
3551
3012
  />
3552
3013
  );