@capillarytech/creatives-library 8.0.329 → 8.0.330

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 (122) hide show
  1. package/constants/unified.js +0 -14
  2. package/package.json +1 -1
  3. package/services/api.js +0 -17
  4. package/services/tests/api.test.js +0 -85
  5. package/utils/commonUtils.js +0 -10
  6. package/utils/tests/commonUtil.test.js +0 -169
  7. package/v2Components/CapTagList/index.js +0 -10
  8. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  9. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  15. package/v2Components/CommonTestAndPreview/SendTestMessage.js +53 -87
  16. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
  17. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  18. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -145
  19. package/v2Components/CommonTestAndPreview/actions.js +0 -10
  20. package/v2Components/CommonTestAndPreview/constants.js +1 -53
  21. package/v2Components/CommonTestAndPreview/index.js +168 -1006
  22. package/v2Components/CommonTestAndPreview/messages.js +3 -147
  23. package/v2Components/CommonTestAndPreview/reducer.js +0 -10
  24. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  25. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +286 -328
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  27. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  29. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +24 -65
  30. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  31. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
  32. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -168
  33. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
  34. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  35. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
  36. package/v2Components/FormBuilder/index.js +1 -7
  37. package/v2Components/TestAndPreviewSlidebox/index.js +1 -8
  38. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  39. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  40. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  41. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  42. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  43. package/v2Containers/CreativesContainer/constants.js +0 -9
  44. package/v2Containers/CreativesContainer/index.js +93 -286
  45. package/v2Containers/CreativesContainer/index.scss +1 -51
  46. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  47. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  48. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  49. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  50. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
  51. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  52. package/v2Containers/Rcs/constants.js +1 -34
  53. package/v2Containers/Rcs/index.js +884 -999
  54. package/v2Containers/Rcs/index.scss +6 -85
  55. package/v2Containers/Rcs/messages.js +1 -10
  56. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2453 -41456
  57. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  58. package/v2Containers/Rcs/tests/index.test.js +38 -41
  59. package/v2Containers/Rcs/tests/mockData.js +0 -38
  60. package/v2Containers/Rcs/tests/utils.test.js +1 -379
  61. package/v2Containers/Rcs/utils.js +10 -358
  62. package/v2Containers/Sms/Create/index.js +38 -100
  63. package/v2Containers/SmsTrai/Create/index.js +4 -9
  64. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  65. package/v2Containers/SmsTrai/Edit/index.js +128 -609
  66. package/v2Containers/SmsTrai/Edit/messages.js +4 -9
  67. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2600 -4586
  68. package/v2Containers/SmsWrapper/index.js +8 -37
  69. package/v2Containers/TagList/index.js +0 -6
  70. package/v2Containers/Templates/_templates.scss +2 -63
  71. package/v2Containers/Templates/actions.js +0 -11
  72. package/v2Containers/Templates/constants.js +0 -2
  73. package/v2Containers/Templates/index.js +40 -90
  74. package/v2Containers/Templates/sagas.js +12 -57
  75. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  76. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  77. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  78. package/v2Containers/TemplatesV2/index.js +23 -86
  79. package/v2Containers/Whatsapp/index.js +20 -3
  80. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5790
  81. package/utils/templateVarUtils.js +0 -172
  82. package/utils/tests/templateVarUtils.test.js +0 -160
  83. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
  84. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -155
  85. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -93
  86. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  87. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
  88. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -648
  89. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -174
  90. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
  91. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  92. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  93. package/v2Components/SmsFallback/constants.js +0 -73
  94. package/v2Components/SmsFallback/index.js +0 -955
  95. package/v2Components/SmsFallback/index.scss +0 -265
  96. package/v2Components/SmsFallback/messages.js +0 -78
  97. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -107
  98. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  99. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  100. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  101. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  102. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -261
  103. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  104. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  105. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  106. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  107. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  108. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  109. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  110. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  111. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  112. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  113. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -205
  114. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -251
  115. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  116. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  117. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  118. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  119. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  120. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  121. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  122. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -8,7 +8,7 @@
8
8
 
9
9
  import PropTypes from 'prop-types';
10
10
  import React, {
11
- useState, useEffect, useMemo, useRef, useCallback,
11
+ useState, useEffect, useMemo, useRef,
12
12
  } from 'react';
13
13
  import { FormattedMessage } from 'react-intl';
14
14
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
@@ -17,9 +17,6 @@ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
17
17
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
18
18
  import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
19
19
  import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
20
- import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
21
- import CustomerCreationModal from './CustomerCreationModal';
22
- import { createTestCustomer } from '../../services/api';
23
20
 
24
21
  // Import messages and styles
25
22
  import messages from './messages';
@@ -31,18 +28,9 @@ import CustomValuesEditor from './CustomValuesEditor';
31
28
  import SendTestMessage from './SendTestMessage';
32
29
  import PreviewSection from './PreviewSection';
33
30
 
34
- import * as Api from '../../services/api';
35
- import { extractTemplateVariables } from '../../utils/templateVarUtils';
36
- import AddTestCustomerButton from './AddTestCustomer';
37
- import ExistingCustomerModal from './ExistingCustomerModal';
38
31
  // Import constants
39
32
  import {
40
33
  CHANNELS,
41
- CUSTOMER_MODAL_NEW,
42
- CUSTOMER_MODAL_EXISTING,
43
- IDENTIFIER_TYPE_EMAIL,
44
- IDENTIFIER_TYPE_MOBILE,
45
- IDENTIFIER_TYPE_PHONE,
46
34
  TEST,
47
35
  DESKTOP,
48
36
  ANDROID,
@@ -82,137 +70,10 @@ import {
82
70
  IMAGE,
83
71
  VIDEO,
84
72
  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,
93
73
  } from './constants';
94
- import { getCdnUrl } from '../../utils/cdnTransformation';
95
- import {
96
- normalizePreviewApiPayload,
97
- extractPreviewFromLiquidResponse,
98
- getSmsFallbackTextForTagExtraction,
99
- } from './previewApiUtils';
100
-
101
- import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
102
- import { getMembersLookup } from '../../services/api';
103
-
104
- /**
105
- * Drop empty GSM rows. RCS/DLT responses often set gsm_sender_id equal to domainName — keep those rows
106
- * for RCS defaults (ModifyDeliverySettings uses the same rule for the sender dropdown).
107
- */
108
- const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEchoFilter = false } = {}) => {
109
- const normalizedDomainName =
110
- domain?.domainName != null ? String(domain.domainName).trim().toLowerCase() : '';
111
- return (gsmSenders || []).filter((gsmSenderRow) => {
112
- const rawValue = gsmSenderRow?.value;
113
- if (rawValue == null) return false;
114
- const trimmedSenderValue = String(rawValue).trim();
115
- if (!trimmedSenderValue) return false;
116
- const senderMatchesDomainLabel =
117
- normalizedDomainName && trimmedSenderValue.toLowerCase() === normalizedDomainName;
118
- if (!skipDomainNameEchoFilter && senderMatchesDomainLabel) return false;
119
- return true;
120
- });
121
- };
122
-
123
- /** Preview payload from Redux may be an Immutable Map — normalize for React state. */
124
- const toPlainPreviewData = (data) => {
125
- if (data == null) return null;
126
- const plain = typeof data.toJS === 'function' ? data.toJS() : data;
127
- return normalizePreviewApiPayload(plain);
128
- };
129
-
130
- /**
131
- * Merge existing customValues with tag keys from categorized groups.
132
- * Each group is { required, optional } (arrays of tag objects with fullPath).
133
- * Preserves existing values; adds '' for any tag key not yet present.
134
- * Reusable for RCS+fallback and any flow that needs to ensure customValues has keys for tags.
135
- */
136
- const mergeCustomValuesWithTagKeys = (prev, ...categorizedGroups) => {
137
- const next = { ...(prev || {}) };
138
- categorizedGroups.forEach((group) => {
139
- [...(group.required || []), ...(group.optional || [])].forEach((tag) => {
140
- const key = tag?.fullPath;
141
- if (key && next[key] === undefined) next[key] = '';
142
- });
143
- });
144
- return next;
145
- };
146
-
147
- /** True when `body` contains `{{name}}` mustache tokens (user-fillable personalization tags).
148
- * DLT `{#name#}` slots are pre-bound template variables and are intentionally excluded. */
149
- const smsTemplateHasMustacheTags = (body) =>
150
- typeof body === 'string' && SMS_MUSTACHE_TAG_PATTERN.test(body);
151
74
 
152
- /**
153
- * Build tag rows from `{{…}}` mustache tokens only — DLT `{#…#}` slots are excluded because
154
- * they are pre-bound template variables, not user-fillable personalization tags.
155
- * Passing a mustache-only captureRegex to extractTemplateVariables skips the DLT branch.
156
- * A non-global regex is used so ensureGlobalRegexForExecLoop creates a fresh instance on each call.
157
- */
158
- const buildSyntheticSmsMustacheTags = (body = '') => {
159
- if (!body || typeof body !== 'string') return [];
160
- return extractTemplateVariables(body, /\{\{([^}]+)\}\}/).map((name) => ({
161
- name,
162
- metaData: { userDriven: false },
163
- children: [],
164
- }));
165
- };
166
-
167
- /** RCS createMessageMeta: media shape (mediaUrl, thumbnailUrl, height string). */
168
- const normalizeRcsTestCardMedia = (media) => {
169
- if (!media || typeof media !== 'object') return undefined;
170
- const mediaUrl =
171
- media.mediaUrl != null && String(media.mediaUrl).trim() !== ''
172
- ? String(media.mediaUrl)
173
- : media.url != null && String(media.url).trim() !== ''
174
- ? String(media.url)
175
- : '';
176
- const thumbnailUrl = media.thumbnailUrl != null ? String(media.thumbnailUrl) : '';
177
- const height = media.height != null ? String(media.height) : undefined;
178
- const out = { mediaUrl, thumbnailUrl };
179
- if (height) out.height = height;
180
- return out;
181
- };
182
-
183
- /** RCS createMessageMeta: suggestion shape (index, type, text, phoneNumber, url, postback). */
184
- const mapRcsSuggestionForTestMeta = (suggestionRow, index) => ({
185
- index,
186
- type: suggestionRow?.type ?? '',
187
- text: suggestionRow?.text != null ? String(suggestionRow.text) : '',
188
- phoneNumber:
189
- suggestionRow?.phoneNumber != null
190
- ? String(suggestionRow.phoneNumber)
191
- : suggestionRow?.phone_number != null
192
- ? String(suggestionRow.phone_number)
193
- : '',
194
- url: suggestionRow?.url !== undefined ? suggestionRow.url : null,
195
- postback:
196
- suggestionRow?.postback != null
197
- ? String(suggestionRow.postback)
198
- : suggestionRow?.text != null
199
- ? String(suggestionRow.text)
200
- : '',
201
- });
202
-
203
- /**
204
- * CapTreeSelect and group resolution use strict equality; API data may mix numeric and string ids.
205
- */
206
- const normalizeTestEntityId = (id) => {
207
- if (id === undefined || id === null) return id;
208
- return String(id);
209
- };
210
-
211
- const testEntityIdsEqual = (a, b) => {
212
- if (a == null && b == null) return true;
213
- if (a == null || b == null) return false;
214
- return String(a) === String(b);
215
- };
75
+ // Import utilities
76
+ import { getCdnUrl } from '../../utils/cdnTransformation';
216
77
 
217
78
  /**
218
79
  * Preview Component Factory - REMOVED IN PHASE 5
@@ -224,7 +85,7 @@ const testEntityIdsEqual = (a, b) => {
224
85
  */
225
86
  const CommonTestAndPreview = (props) => {
226
87
  const {
227
- intl: { formatMessage, locale: userLocale = 'en' },
88
+ intl: { formatMessage },
228
89
  show,
229
90
  onClose,
230
91
  channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
@@ -260,34 +121,19 @@ const CommonTestAndPreview = (props) => {
260
121
  ...additionalProps
261
122
  } = props;
262
123
 
263
- const smsFallbackContent = additionalProps?.smsFallbackContent;
264
- const smsFallbackTextForTagExtraction = useMemo(
265
- () => getSmsFallbackTextForTagExtraction(smsFallbackContent),
266
- [smsFallbackContent],
267
- );
268
124
  // ============================================
269
125
  // STATE MANAGEMENT
270
126
  // ============================================
271
127
  const [selectedCustomer, setSelectedCustomer] = useState(null);
272
128
  const [requiredTags, setRequiredTags] = useState([]);
273
129
  const [optionalTags, setOptionalTags] = useState([]);
274
- const [smsFallbackExtractedTags, setSmsFallbackExtractedTags] = useState([]);
275
- const [smsFallbackRequiredTags, setSmsFallbackRequiredTags] = useState([]);
276
- const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
277
- const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
278
130
  const [customValues, setCustomValues] = useState({});
279
131
  const [showJSON, setShowJSON] = useState(false);
280
132
  const [tagsExtracted, setTagsExtracted] = useState(false);
281
-
282
- const initialDevice = CHANNELS_USING_ANDROID_PREVIEW_DEVICE.includes(channel) ? ANDROID : DESKTOP;
283
- const [searchValue, setSearchValue] = useState("");
284
- const [customerModal, setCustomerModal] = useState([false, ""]);
285
- const [isCustomerDataLoading, setIsCustomerDataLoading] = useState(false);
286
- const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
287
-
133
+ // Initialize device based on channel: SMS uses Android/iOS, others use Desktop/Mobile
134
+ // Initialize device based on channel: SMS, WhatsApp, RCS, InApp, MobilePush, and Viber use Android/iOS, others use Desktop/Mobile
135
+ const initialDevice = (channel === CHANNELS.SMS || channel === CHANNELS.WHATSAPP || channel === CHANNELS.RCS || channel === CHANNELS.INAPP || channel === CHANNELS.MOBILEPUSH || channel === CHANNELS.VIBER) ? ANDROID : DESKTOP;
288
136
  const [previewDevice, setPreviewDevice] = useState(initialDevice);
289
- const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
290
- const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
291
137
  // Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
292
138
  const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
293
139
  const [previewDataHtml, setPreviewDataHtml] = useState(() => {
@@ -320,22 +166,15 @@ const CommonTestAndPreview = (props) => {
320
166
  [CHANNELS.WHATSAPP]: {
321
167
  domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
322
168
  },
323
- [CHANNELS.RCS]: {
324
- domainId: null,
325
- domainGatewayMapId: null,
326
- gsmSenderId: '',
327
- smsFallbackDomainId: null,
328
- cdmaSenderId: '', // gsmSenderId = RCS sender (domainId|senderId), cdmaSenderId = SMS fallback
329
- },
330
169
  });
331
170
 
332
- const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
171
+ const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
333
172
  const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
334
173
  const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
335
174
  const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
336
175
  const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
337
176
 
338
- // Fetch sender details and WeCRM accounts when Test & Preview opens (SMS, Email, WhatsApp, RCS — same process)
177
+ // Fetch sender details and WeCRM accounts when Test & Preview opens for SMS/Email/WhatsApp
339
178
  useEffect(() => {
340
179
  if (!show || !channel) {
341
180
  return;
@@ -344,10 +183,6 @@ const CommonTestAndPreview = (props) => {
344
183
  if (actions.getSenderDetailsRequested) {
345
184
  actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
346
185
  }
347
- // SMS domains/senders are needed for RCS delivery UI (fallback row + slidebox) whenever RCS is open — not only when fallback body exists.
348
- if (channel === CHANNELS.RCS && actions.getSenderDetailsRequested) {
349
- actions.getSenderDetailsRequested({ channel: CHANNELS.SMS, orgUnitId: orgUnitId ?? -1 });
350
- }
351
186
  if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
352
187
  actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
353
188
  }
@@ -359,84 +194,20 @@ const CommonTestAndPreview = (props) => {
359
194
  // Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
360
195
  useEffect(() => {
361
196
  if (!channel || !channelsWithDeliverySettings.includes(channel)) return;
362
-
363
- if (channel === CHANNELS.RCS) {
364
- const rcsDomainRows = senderDetailsByChannel?.[CHANNELS.RCS] || [];
365
- const smsFallbackDomainRows = senderDetailsByChannel?.[CHANNELS.SMS] || [];
366
- if (!rcsDomainRows.length) return;
367
-
368
- const currentRcsDeliverySettings = testPreviewDeliverySettings?.[CHANNELS.RCS] || {};
369
- const isRcsGsmSenderUnset = !currentRcsDeliverySettings?.gsmSenderId;
370
- const isSmsFallbackSenderUnset = !currentRcsDeliverySettings?.cdmaSenderId;
371
- const isRcsDeliveryFullyUnset = isRcsGsmSenderUnset && isSmsFallbackSenderUnset;
372
- const shouldOnlyFillSmsFallbackSender =
373
- !isRcsGsmSenderUnset && isSmsFallbackSenderUnset && smsFallbackDomainRows.length > 0;
374
-
375
- if (!isRcsDeliveryFullyUnset && !shouldOnlyFillSmsFallbackSender) return;
376
-
377
- const firstRcsDomain = rcsDomainRows[0];
378
- const firstSmsFallbackDomain = smsFallbackDomainRows[0];
379
- const usableRcsGsmSenders = filterUsableGsmSendersForDomain(
380
- firstRcsDomain,
381
- firstRcsDomain?.gsmSenders,
382
- { skipDomainNameEchoFilter: true },
383
- );
384
- const usableSmsFallbackGsmSenders = firstSmsFallbackDomain
385
- ? filterUsableGsmSendersForDomain(firstSmsFallbackDomain, firstSmsFallbackDomain?.gsmSenders)
386
- : [];
387
- const defaultRcsGsmSender = usableRcsGsmSenders[0];
388
- const defaultSmsFallbackGsmSender = usableSmsFallbackGsmSenders[0];
389
- const rcsSenderCompositeValue =
390
- firstRcsDomain?.domainId != null && defaultRcsGsmSender?.value != null
391
- ? `${firstRcsDomain.domainId}|${defaultRcsGsmSender.value}`
392
- : (defaultRcsGsmSender?.value || '');
393
- const smsFallbackSenderCompositeValue =
394
- firstSmsFallbackDomain?.domainId != null && defaultSmsFallbackGsmSender?.value != null
395
- ? `${firstSmsFallbackDomain.domainId}|${defaultSmsFallbackGsmSender.value}`
396
- : (defaultSmsFallbackGsmSender?.value || '');
397
-
398
- setTestPreviewDeliverySettings((prev) => {
399
- const previousRcsSettings = prev?.[CHANNELS.RCS] || {};
400
- if (shouldOnlyFillSmsFallbackSender) {
401
- if (!smsFallbackSenderCompositeValue) return prev;
402
- return {
403
- ...prev,
404
- [CHANNELS.RCS]: {
405
- ...previousRcsSettings,
406
- smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
407
- cdmaSenderId: smsFallbackSenderCompositeValue,
408
- },
409
- };
410
- }
411
- return {
412
- ...prev,
413
- [CHANNELS.RCS]: {
414
- domainId: firstRcsDomain?.domainId ?? null,
415
- domainGatewayMapId: firstRcsDomain?.dgmId ?? null,
416
- gsmSenderId: rcsSenderCompositeValue,
417
- smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
418
- cdmaSenderId: smsFallbackSenderCompositeValue,
419
- },
420
- };
421
- });
422
- return;
423
- }
424
-
425
197
  const domains = senderDetailsByChannel[channel];
426
198
  if (!domains || domains.length === 0) return;
427
199
  const {
428
- domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
200
+ domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
429
201
  } = testPreviewDeliverySettings[channel] || {};
430
- const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
202
+ const isEmptySelection = !domainId && !gsmSenderId && !senderEmail && !senderMobNum;
431
203
  if (!isEmptySelection) return;
432
204
 
433
205
  const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
434
206
  const matchedWhatsappAccount = whatsappAccountFromForm
435
207
  ? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
436
208
  : null;
437
- const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
438
- ? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
439
- registeredSenderIds?.includes(gsm?.value)))
209
+ const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled
210
+ ? domains.filter((domain) => (domain.gsmSenders || []).some((gsm) => registeredSenderIds.includes(gsm.value)))
440
211
  : domains;
441
212
  const [defaultDomain] = domains;
442
213
  const [firstSmsDomain] = smsDomains;
@@ -451,8 +222,8 @@ const CommonTestAndPreview = (props) => {
451
222
  const next = { ...prev };
452
223
  if (channel === CHANNELS.SMS) {
453
224
  const smsGsmSenders = smsTraiDltEnabled
454
- ? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
455
- : firstDomain?.gsmSenders;
225
+ ? (firstDomain.gsmSenders || []).filter((gsm) => registeredSenderIds.includes(gsm.value))
226
+ : firstDomain.gsmSenders;
456
227
  next[channel] = {
457
228
  domainId: firstDomain.domainId,
458
229
  domainGatewayMapId: firstDomain.dgmId,
@@ -485,29 +256,10 @@ const CommonTestAndPreview = (props) => {
485
256
  // MEMOIZED VALUES
486
257
  // ============================================
487
258
 
488
- const allTags = useMemo(
489
- () => [...requiredTags, ...optionalTags, ...smsFallbackRequiredTags, ...smsFallbackOptionalTags],
490
- [requiredTags, optionalTags, smsFallbackRequiredTags, smsFallbackOptionalTags]
491
- );
492
-
493
- const allRequiredTags = useMemo(
494
- () => [...requiredTags, ...smsFallbackRequiredTags],
495
- [requiredTags, smsFallbackRequiredTags]
496
- );
497
-
498
- const buildEmptyValues = useCallback(
499
- () => allTags.reduce((acc, tag) => {
500
- const key = tag?.fullPath;
501
- if (key) acc[key] = '';
502
- return acc;
503
- }, {}),
504
- [allTags]
505
- );
506
-
507
259
  // Check if update preview button should be disabled
508
260
  const isUpdatePreviewDisabled = useMemo(() => (
509
- allRequiredTags.some((tag) => !customValues[tag.fullPath])
510
- ), [allRequiredTags, customValues]);
261
+ requiredTags.some((tag) => !customValues[tag.fullPath])
262
+ ), [requiredTags, customValues]);
511
263
 
512
264
  // Get current content based on channel and editor type
513
265
  const getCurrentContent = useMemo(() => {
@@ -551,13 +303,6 @@ const CommonTestAndPreview = (props) => {
551
303
  return currentTabData.base['sms-editor'];
552
304
  }
553
305
  }
554
- // DLT / Test & Preview shape: { templateConfigs: { template, templateId, ... } }
555
- if (formData.templateConfigs?.template) {
556
- const smsDltTemplateValue = formData.templateConfigs.template;
557
- if (typeof smsDltTemplateValue === 'string') return smsDltTemplateValue;
558
- if (Array.isArray(smsDltTemplateValue)) return smsDltTemplateValue.join('');
559
- return '';
560
- }
561
306
  }
562
307
 
563
308
  // SMS channel fallback - if formData is not available, use content directly
@@ -603,90 +348,20 @@ const CommonTestAndPreview = (props) => {
603
348
  return content || '';
604
349
  }, [channel, formData, currentTab, beeContent, content, beeInstance]);
605
350
 
606
- const leftPanelExtractedTags = useMemo(() => {
607
- if (channel === CHANNELS.SMS) {
608
- const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
609
- if (!smsTemplateHasMustacheTags(smsEditorBody)) return [];
610
- const extractTagsFromApi = extractedTags ?? [];
611
- if (extractTagsFromApi.length > 0) return extractTagsFromApi;
612
- return buildSyntheticSmsMustacheTags(smsEditorBody);
613
- }
614
- const hasFallbackSmsBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
615
- if (channel === CHANNELS.RCS && hasFallbackSmsBody) {
616
- const rcsPrimaryTags = extractedTags ?? [];
617
- const fallbackSmsTextForTags = smsFallbackTextForTagExtraction ?? '';
618
- const fallbackSmsTagRows = smsTemplateHasMustacheTags(fallbackSmsTextForTags)
619
- ? (smsFallbackExtractedTags?.length > 0
620
- ? smsFallbackExtractedTags
621
- : buildSyntheticSmsMustacheTags(fallbackSmsTextForTags))
622
- : [];
623
- const mergedRcsAndFallbackTags = [...rcsPrimaryTags, ...fallbackSmsTagRows];
624
- if (mergedRcsAndFallbackTags.length > 0) return mergedRcsAndFallbackTags;
625
- return buildSyntheticSmsMustacheTags(fallbackSmsTextForTags);
626
- }
627
- return extractedTags ?? [];
628
- }, [
629
- channel,
630
- extractedTags,
631
- getCurrentContent,
632
- smsFallbackContent,
633
- smsFallbackExtractedTags,
634
- smsFallbackTextForTagExtraction,
635
- ]);
636
-
637
- const isRcsSmsFallbackPreviewEnabled =
638
- channel === CHANNELS.RCS
639
- && !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
640
- // Only treat as SMS when user is on the Fallback SMS tab — not whenever fallback exists (RCS tab needs RCS preview API).
641
- const isSmsFallbackTabActive = isRcsSmsFallbackPreviewEnabled && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK;
642
- const activeChannelForActions = isSmsFallbackTabActive ? CHANNELS.SMS : channel;
643
- // VarSegment slot values live in rcsSmsFallbackVarMapped; raw templateContent alone is stale for /preview Body.
644
- const resolvedSmsFallbackBodyForPreviewTab =
645
- smsFallbackTextForTagExtraction
646
- || smsFallbackContent?.templateContent
647
- || smsFallbackContent?.content
648
- || '';
649
- const activeContentForActions = isSmsFallbackTabActive
650
- ? resolvedSmsFallbackBodyForPreviewTab
651
- : getCurrentContent;
652
-
653
- /**
654
- * SMS fallback pane must show /preview API result when user updated preview on that tab (plain text).
655
- * Skip when resolvedBody is RCS-shaped (e.g. user last previewed on RCS tab).
656
- */
657
- // smsFallbackPreviewText is the single source of truth for the resolved SMS fallback preview.
658
- // It is set only by syncSmsFallbackPreview (called from handleUpdatePreview and the
659
- // prefilled-values effect) and reset to undefined on discard / slidebox close.
660
- // Using previewDataHtml as a fallback is unsafe because that state is shared with the primary
661
- // RCS preview and can contain stale SMS or RCS content.
662
- const smsFallbackResolvedText = useMemo(() => {
663
- const hasFallbackBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
664
- if (channel !== CHANNELS.RCS || !hasFallbackBody) return undefined;
665
- if (smsFallbackPreviewText != null) return smsFallbackPreviewText;
666
- return undefined;
667
- }, [channel, smsFallbackContent, smsFallbackPreviewText]);
668
-
669
- // Build test entities tree data from testCustomers prop
670
- // Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
351
+ // Build test entities tree data
671
352
  const testEntitiesTreeData = useMemo(() => {
672
353
  const groupsNode = {
673
354
  title: 'Groups',
674
355
  value: 'groups-node',
675
356
  selectable: false,
676
- children: testGroups?.map((group) => ({
677
- title: group?.groupName,
678
- value: normalizeTestEntityId(group?.groupId),
679
- })),
357
+ children: testGroups?.map((group) => ({ title: group?.groupName, value: group?.groupId })),
680
358
  };
681
359
 
682
360
  const customersNode = {
683
361
  title: 'Individuals',
684
362
  value: 'customers-node',
685
363
  selectable: false,
686
- children: testCustomers?.map((customer) => ({
687
- title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
688
- value: normalizeTestEntityId(customer?.userId ?? customer?.customerId),
689
- })) || [],
364
+ children: testCustomers?.map((customer) => ({ title: customer.name, value: customer?.userId })),
690
365
  };
691
366
 
692
367
  return [groupsNode, customersNode];
@@ -713,93 +388,6 @@ const CommonTestAndPreview = (props) => {
713
388
  return resolvedText;
714
389
  };
715
390
 
716
- /**
717
- * Common handler for saving test customers (both new and existing)
718
- */
719
- const handleSaveTestCustomer = async (validationErrors = {}, setIsLoading = () => {}) => {
720
- // Check for validation errors before saving (for new customers)
721
- if (customerModal[1] === CUSTOMER_MODAL_NEW && (validationErrors.email || validationErrors.mobile)) {
722
- return;
723
- }
724
-
725
- setIsLoading(true);
726
-
727
- try {
728
- let payload;
729
-
730
- if (customerModal[1] === CUSTOMER_MODAL_EXISTING) {
731
- // For existing customers, use customerId
732
- payload = {
733
- campaignUserId: customerData.customerId
734
- };
735
- } else {
736
- // For new customers, use customer object
737
- payload = {
738
- customer: {
739
- firstName: customerData.name || "",
740
- mobile: customerData.mobile || "",
741
- email: customerData.email || ""
742
- }
743
- };
744
- }
745
-
746
- const response = await createTestCustomer(payload);
747
-
748
-
749
- // Handle success: add to test customers list and selection (existing and new)
750
- if (response && response.success) {
751
- CapNotification.success({
752
- message: formatMessage(messages.newTestCustomerAddedSuccess),
753
- });
754
- // API may return customerId in response.response (e.g. { response: { customerId: 438845651 } })
755
- const res = response?.response || response;
756
- const addedId = customerModal[1] === CUSTOMER_MODAL_EXISTING
757
- ? customerData?.customerId || customerData?.campaignUserId
758
- : res?.customerId || res?.campaignUserId;
759
- if (addedId) {
760
- const normalizedAddedId = normalizeTestEntityId(addedId);
761
- actions.addTestCustomer({
762
- userId: normalizedAddedId,
763
- customerId: normalizedAddedId,
764
- name: customerData?.name?.trim() || '',
765
- email: customerData?.email || '',
766
- mobile: customerData?.mobile || '',
767
- });
768
- setSelectedTestEntities((prev) => (
769
- prev.some((id) => testEntityIdsEqual(id, normalizedAddedId))
770
- ? prev
771
- : [...prev, normalizedAddedId]
772
- ));
773
- }
774
- handleCloseCustomerModal();
775
- } else {
776
- // Show error notification for unsuccessful response
777
- CapNotification.error({
778
- message: formatMessage(messages.errorTitle),
779
- description: response?.message || formatMessage(messages.failedToAddTestCustomer),
780
- });
781
- }
782
- } catch (error) {
783
- CapNotification.error({
784
- message: formatMessage(messages.errorTitle),
785
- description: error?.message || formatMessage(messages.errorAddingTestCustomer),
786
- });
787
- } finally {
788
- setIsLoading(false);
789
- }
790
- };
791
-
792
- const handleCloseCustomerModal = () => {
793
- setCustomerModal([false, ""]);
794
- setSearchValue('');
795
- setCustomerData({
796
- name: '',
797
- email: '',
798
- mobile: '',
799
- customerId: '',
800
- });
801
- };
802
-
803
391
  /**
804
392
  * Prepare payload for preview API based on channel
805
393
  */
@@ -872,37 +460,6 @@ const CommonTestAndPreview = (props) => {
872
460
  }
873
461
  };
874
462
 
875
- /**
876
- * When RCS has SMS fallback, refresh fallback preview text via the same Liquid /preview API
877
- * (separate call with SMS channel + fallback template body). Used after primary preview updates.
878
- */
879
- const syncSmsFallbackPreview = async (customValuesForResolve, selectedCustomerObj) => {
880
- const fallbackBodyForLiquidPreview =
881
- getSmsFallbackTextForTagExtraction(smsFallbackContent)
882
- || smsFallbackContent?.templateContent
883
- || smsFallbackContent?.content
884
- || '';
885
- if (channel !== CHANNELS.RCS || !String(fallbackBodyForLiquidPreview).trim()) return;
886
- try {
887
- const smsFallbackPayload = preparePreviewPayload(
888
- CHANNELS.SMS,
889
- formData || {},
890
- fallbackBodyForLiquidPreview,
891
- customValuesForResolve,
892
- selectedCustomerObj
893
- );
894
- const fallbackResponse = await Api.updateEmailPreview(smsFallbackPayload);
895
- const fallbackPreview = extractPreviewFromLiquidResponse(fallbackResponse);
896
- setSmsFallbackPreviewText(
897
- typeof fallbackPreview?.resolvedBody === 'string'
898
- ? fallbackPreview.resolvedBody
899
- : undefined
900
- );
901
- } catch (e) {
902
- /* keep existing smsFallbackPreviewText on failure */
903
- }
904
- };
905
-
906
463
  /**
907
464
  * Prepare payload for tag extraction based on channel
908
465
  */
@@ -997,7 +554,7 @@ const CommonTestAndPreview = (props) => {
997
554
  } = carousel || {};
998
555
  const buttonData = buttons.map((button, index) => {
999
556
  const {
1000
- type, text, phone_number: phoneNumber, urlType, url,
557
+ type, text, phone_number, urlType, url,
1001
558
  } = button || {};
1002
559
  const buttonObj = {
1003
560
  type,
@@ -1005,7 +562,7 @@ const CommonTestAndPreview = (props) => {
1005
562
  index,
1006
563
  };
1007
564
  if (type === PHONE_NUMBER) {
1008
- buttonObj.phoneNumber = phoneNumber;
565
+ buttonObj.phoneNumber = phone_number;
1009
566
  }
1010
567
  if (type === URL) {
1011
568
  buttonObj.url = url;
@@ -1034,131 +591,7 @@ const CommonTestAndPreview = (props) => {
1034
591
  };
1035
592
  });
1036
593
 
1037
- /**
1038
- * Build createMessageMeta payload for RCS (test message).
1039
- * rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
1040
- * Then rcsDeliverySettings, executionParams, clientName last.
1041
- */
1042
- const buildRcsTestMessagePayload = (formDataObj, _contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra = {}) => {
1043
- const plainCustom =
1044
- customValuesObj != null && typeof customValuesObj.toJS === 'function'
1045
- ? customValuesObj.toJS()
1046
- : (customValuesObj || {});
1047
- const userVarMap = Object.fromEntries(
1048
- Object.entries(plainCustom).filter(([, v]) => v != null && String(v).trim() !== '')
1049
- );
1050
- const rcsData = formDataObj?.versions?.base?.content?.RCS ?? formDataObj?.content?.RCS ?? {};
1051
- const rcsContent = rcsData?.rcsContent || {};
1052
- const smsFallback = rcsData?.smsFallBackContent || {};
1053
- let cardContentList = [];
1054
- if (Array.isArray(rcsContent?.cardContent)) {
1055
- cardContentList = rcsContent.cardContent;
1056
- } else if (rcsContent?.cardContent) {
1057
- cardContentList = [rcsContent.cardContent];
1058
- }
1059
- // Merge test customValues into cardVarMapped (template snapshot + user-entered RCS + fallback SMS tags).
1060
- const cardContent = cardContentList.map((rcsCard) => {
1061
- const baseMap = rcsCard.cardVarMapped || {};
1062
- const mergedCardVarMapped =
1063
- Object.keys(userVarMap).length > 0 ? { ...baseMap, ...userVarMap } : baseMap;
1064
- const mediaNorm = rcsCard?.media ? normalizeRcsTestCardMedia(rcsCard.media) : undefined;
1065
- const suggestionsRaw = Array.isArray(rcsCard?.suggestions) ? rcsCard.suggestions : [];
1066
- const suggestionsMapped = suggestionsRaw.map((suggestionItem, i) =>
1067
- mapRcsSuggestionForTestMeta(suggestionItem, i));
1068
- return {
1069
- title: rcsCard?.title ?? '',
1070
- description: rcsCard?.description ?? '',
1071
- mediaType: rcsCard?.mediaType ?? MEDIA_TYPE_TEXT,
1072
- ...(mediaNorm && { media: mediaNorm }),
1073
- ...(Object.keys(mergedCardVarMapped).length > 0 && { cardVarMapped: mergedCardVarMapped }),
1074
- ...(suggestionsMapped.length > 0 && { suggestions: suggestionsMapped }),
1075
- };
1076
- });
1077
- // Prefer parent `smsFallbackContent` snapshot (rcsExtra) — includes VarSegment-resolved body via
1078
- // getSmsFallbackTextForTagExtraction. Nested formData.smsFallBackContent is often stale vs live editor.
1079
- const rcsExtraFallbackTemplate = rcsExtra?.smsFallbackTemplateContent;
1080
- const hasResolvedFallbackBodyFromRcsExtra =
1081
- rcsExtraFallbackTemplate != null
1082
- && String(rcsExtraFallbackTemplate).trim() !== '';
1083
- const smsMessageRaw = hasResolvedFallbackBodyFromRcsExtra
1084
- ? String(rcsExtraFallbackTemplate)
1085
- : (smsFallback?.smsContent ?? smsFallback?.message ?? '');
1086
- const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
1087
- ? deliverySettingsOverride.cdmaSenderId.split('|')[1]
1088
- : deliverySettingsOverride?.cdmaSenderId;
1089
- const deliveryFallbackSmsId =
1090
- typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
1091
- const creativeFallbackSmsId =
1092
- smsFallback?.senderId != null ? String(smsFallback.senderId).trim() : '';
1093
- const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
1094
-
1095
- const smsFallBackContent =
1096
- smsMessageRaw.trim() !== ''
1097
- ? { message: smsMessageRaw }
1098
- : undefined;
1099
-
1100
- // accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
1101
- const accountIdForMeta =
1102
- rcsContent?.accountId != null && String(rcsContent.accountId).trim() !== ''
1103
- ? String(rcsContent.accountId)
1104
- : undefined;
1105
-
1106
- const rcsRichCardContent = {
1107
- contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
1108
- cardType: rcsContent?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
1109
- cardSettings: rcsContent?.cardSettings ?? {
1110
- cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
1111
- cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
1112
- },
1113
- ...(cardContent.length > 0 && { cardContent }),
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
- ) => {
594
+ const prepareTestMessagePayload = (channelType, formDataObj, contentStr, customValuesObj, recipientDetails, previewDataObj, deliverySettingsOverride) => {
1162
595
  // Base payload structure common to all channels
1163
596
  const basePayload = {
1164
597
  ouId: -1,
@@ -1239,12 +672,12 @@ const CommonTestAndPreview = (props) => {
1239
672
  },
1240
673
  smsDeliverySettings: {
1241
674
  channelSettings: {
1242
- channel: CHANNELS.SMS,
1243
675
  gsmSenderId: deliverySettingsOverride?.gsmSenderId ?? '',
1244
676
  domainId: deliverySettingsOverride?.domainId ?? null,
1245
677
  domainGatewayMapId: deliverySettingsOverride?.domainGatewayMapId ?? '',
1246
678
  targetNdnc: false,
1247
679
  cdmaSenderId: deliverySettingsOverride?.cdmaSenderId ?? '',
680
+ channel: CHANNELS.SMS,
1248
681
  },
1249
682
  additionalSettings: {
1250
683
  useTinyUrl: false,
@@ -1388,7 +821,7 @@ const CommonTestAndPreview = (props) => {
1388
821
  return {
1389
822
  ...basePayload,
1390
823
  whatsappMessageContent: {
1391
- messageBody: resolvedMessageBody || templateEditorValue || '',
824
+ messageBody: templateEditorValue || '',
1392
825
  accountId: formDataObj?.accountId || '',
1393
826
  sourceAccountIdentifier: sourceAccountIdentifier || formDataObj?.sourceAccountIdentifier || '',
1394
827
  accountName: formDataObj?.accountName || '',
@@ -1413,7 +846,16 @@ const CommonTestAndPreview = (props) => {
1413
846
  }
1414
847
 
1415
848
  case CHANNELS.RCS:
1416
- return buildRcsTestMessagePayload(formDataObj, contentStr, customValuesObj, deliverySettingsOverride, basePayload, rcsExtra);
849
+ return {
850
+ ...basePayload,
851
+ rcsMessageContent: {
852
+ channel: CHANNELS.RCS,
853
+ messageBody: contentStr,
854
+ rcsType: additionalProps?.rcsType,
855
+ rcsImageSrc: formDataObj?.rcsImageSrc,
856
+ rcsSuggestions: formDataObj?.rcsSuggestions,
857
+ },
858
+ };
1417
859
 
1418
860
  case CHANNELS.INAPP: {
1419
861
  // InApp payload structure similar to MobilePush
@@ -2515,10 +1957,6 @@ const CommonTestAndPreview = (props) => {
2515
1957
  formatMessage,
2516
1958
  lastModified: formData?.lastModified,
2517
1959
  updatedByName: formData?.updatedByName,
2518
- smsFallbackContent: isRcsSmsFallbackPreviewEnabled ? smsFallbackContent : null,
2519
- smsFallbackResolvedText,
2520
- activePreviewTab,
2521
- onPreviewTabChange: setActivePreviewTab,
2522
1960
  };
2523
1961
  };
2524
1962
 
@@ -2558,12 +1996,7 @@ const CommonTestAndPreview = (props) => {
2558
1996
  }, [show, beeInstance, currentTab, channel]);
2559
1997
 
2560
1998
  /**
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.
1999
+ * Initial data load when slidebox opens
2567
2000
  */
2568
2001
  useEffect(() => {
2569
2002
  if (show) {
@@ -2648,61 +2081,7 @@ const CommonTestAndPreview = (props) => {
2648
2081
  actions.getTestGroupsRequested();
2649
2082
  }
2650
2083
  }
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]);
2084
+ }, [show, beeInstance, currentTab, channel]);
2706
2085
 
2707
2086
  /**
2708
2087
  * Email-specific: Handle content updates for both BEE and CKEditor
@@ -2754,22 +2133,15 @@ const CommonTestAndPreview = (props) => {
2754
2133
  setSelectedCustomer(null);
2755
2134
  setRequiredTags([]);
2756
2135
  setOptionalTags([]);
2757
- setSmsFallbackExtractedTags([]);
2758
- setSmsFallbackRequiredTags([]);
2759
- setSmsFallbackOptionalTags([]);
2760
- setIsExtractingSmsFallbackTags(false);
2761
2136
  setCustomValues({});
2762
2137
  setShowJSON(false);
2763
2138
  setTagsExtracted(false);
2764
2139
  setPreviewDevice(DESKTOP);
2765
- setActivePreviewTab(PREVIEW_TAB_RCS);
2766
- setSmsFallbackPreviewText(undefined);
2767
2140
  setSelectedTestEntities([]);
2768
2141
  actions.clearPrefilledValues();
2769
2142
  } else {
2770
2143
  // Reset device to initialDevice when opening (Android for mobile channels, Desktop for others)
2771
2144
  setPreviewDevice(initialDevice);
2772
- setActivePreviewTab(PREVIEW_TAB_RCS);
2773
2145
  }
2774
2146
  }, [show, initialDevice]);
2775
2147
 
@@ -2778,10 +2150,79 @@ const CommonTestAndPreview = (props) => {
2778
2150
  */
2779
2151
  useEffect(() => {
2780
2152
  if (previewData) {
2781
- setPreviewDataHtml(toPlainPreviewData(previewData));
2153
+ setPreviewDataHtml(previewData);
2782
2154
  }
2783
2155
  }, [previewData]);
2784
2156
 
2157
+ /**
2158
+ * Process extracted tags and categorize them
2159
+ */
2160
+ useEffect(() => {
2161
+ // Categorize tags into required and optional
2162
+ const required = [];
2163
+ const optional = [];
2164
+ let hasPersonalizationTags = false;
2165
+
2166
+ if (extractedTags?.length > 0) {
2167
+ const processTag = (tag, parentPath = '') => {
2168
+ const currentPath = parentPath ? `${parentPath}.${tag.name}` : tag.name;
2169
+
2170
+ // Skip unsubscribe tag for input fields
2171
+ if (tag?.name === UNSUBSCRIBE_TAG_NAME) {
2172
+ return;
2173
+ }
2174
+
2175
+ hasPersonalizationTags = true;
2176
+
2177
+ if (tag?.metaData?.userDriven === false) {
2178
+ required.push({
2179
+ ...tag,
2180
+ fullPath: currentPath,
2181
+ });
2182
+ } else if (tag?.metaData?.userDriven === true) {
2183
+ optional.push({
2184
+ ...tag,
2185
+ fullPath: currentPath,
2186
+ });
2187
+ }
2188
+
2189
+ if (tag?.children?.length > 0) {
2190
+ tag.children.forEach((child) => processTag(child, currentPath));
2191
+ }
2192
+ };
2193
+
2194
+ extractedTags.forEach((tag) => processTag(tag));
2195
+
2196
+ if (hasPersonalizationTags) {
2197
+ setRequiredTags(required);
2198
+ setOptionalTags(optional);
2199
+ setTagsExtracted(true); // Mark tags as extracted and processed
2200
+
2201
+ // Initialize custom values for required tags
2202
+ const initialValues = {};
2203
+ required.forEach((tag) => {
2204
+ initialValues[tag?.fullPath] = '';
2205
+ });
2206
+ optional.forEach((tag) => {
2207
+ initialValues[tag?.fullPath] = '';
2208
+ });
2209
+ setCustomValues(initialValues);
2210
+ } else {
2211
+ // Reset all tag-related state if no personalization tags
2212
+ setRequiredTags([]);
2213
+ setOptionalTags([]);
2214
+ setCustomValues({});
2215
+ setTagsExtracted(false);
2216
+ }
2217
+ } else {
2218
+ // Reset all tag-related state if no tags
2219
+ setRequiredTags([]);
2220
+ setOptionalTags([]);
2221
+ setCustomValues({});
2222
+ setTagsExtracted(false);
2223
+ }
2224
+ }, [extractedTags]);
2225
+
2785
2226
  /**
2786
2227
  * Handle customer selection and fetch prefilled values
2787
2228
  */
@@ -2789,15 +2230,17 @@ const CommonTestAndPreview = (props) => {
2789
2230
  if (selectedCustomer && config.enableCustomerSearch !== false) {
2790
2231
  setTagsExtracted(true); // Auto-open custom values editor
2791
2232
 
2233
+ // Get all available tags
2234
+ const allTags = [...requiredTags, ...optionalTags];
2792
2235
  const requiredTagObj = {};
2793
- allRequiredTags.forEach((tag) => {
2236
+ requiredTags.forEach((tag) => {
2794
2237
  requiredTagObj[tag?.fullPath] = '';
2795
2238
  });
2796
2239
  if (allTags.length > 0) {
2797
2240
  const payload = preparePreviewPayload(
2798
- activeChannelForActions,
2241
+ channel,
2799
2242
  formData || {},
2800
- activeContentForActions,
2243
+ getCurrentContent,
2801
2244
  {
2802
2245
  ...requiredTagObj,
2803
2246
  },
@@ -2806,7 +2249,7 @@ const CommonTestAndPreview = (props) => {
2806
2249
  actions.getPrefilledValuesRequested(payload);
2807
2250
  }
2808
2251
  }
2809
- }, [selectedCustomer, allTags.length, activeChannelForActions, activePreviewTab]);
2252
+ }, [selectedCustomer]);
2810
2253
 
2811
2254
  /**
2812
2255
  * Update custom values with prefilled values from API
@@ -2817,7 +2260,7 @@ const CommonTestAndPreview = (props) => {
2817
2260
  if (prefilledValues && selectedCustomer) {
2818
2261
  // Always replace all values with prefilled values
2819
2262
  const updatedValues = {};
2820
- allTags.forEach((tag) => {
2263
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2821
2264
  updatedValues[tag?.fullPath] = prefilledValues[tag?.fullPath] || '';
2822
2265
  });
2823
2266
 
@@ -2825,17 +2268,16 @@ const CommonTestAndPreview = (props) => {
2825
2268
 
2826
2269
  // Update preview with prefilled values (this is a valid preview call trigger)
2827
2270
  const payload = preparePreviewPayload(
2828
- activeChannelForActions,
2271
+ channel,
2829
2272
  formData || {},
2830
- activeContentForActions,
2273
+ getCurrentContent,
2831
2274
  updatedValues,
2832
2275
  selectedCustomer
2833
2276
  );
2834
2277
  actions.updatePreviewRequested(payload);
2835
2278
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
2836
- void syncSmsFallbackPreview(updatedValues, selectedCustomer);
2837
2279
  }
2838
- }, [JSON.stringify(prefilledValues), selectedCustomer, activeChannelForActions, activePreviewTab]);
2280
+ }, [JSON.stringify(prefilledValues), selectedCustomer]);
2839
2281
 
2840
2282
  /**
2841
2283
  * Map channel constants to display names (lowercase for message)
@@ -2890,8 +2332,7 @@ const CommonTestAndPreview = (props) => {
2890
2332
  * Handle slidebox close
2891
2333
  */
2892
2334
  const handleClose = () => {
2893
- // Reset state when closing (includes add-customer modal + tree search state)
2894
- handleCloseCustomerModal();
2335
+ // Reset state when closing
2895
2336
  setSelectedCustomer(null);
2896
2337
  setRequiredTags([]);
2897
2338
  setOptionalTags([]);
@@ -2929,7 +2370,11 @@ const CommonTestAndPreview = (props) => {
2929
2370
  setTagsExtracted(true); // Auto-open custom values editor
2930
2371
 
2931
2372
  // Clear any existing values while waiting for prefilled values
2932
- setCustomValues(buildEmptyValues());
2373
+ const emptyValues = {};
2374
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2375
+ emptyValues[tag?.fullPath] = '';
2376
+ });
2377
+ setCustomValues(emptyValues);
2933
2378
  };
2934
2379
 
2935
2380
  /**
@@ -2943,7 +2388,11 @@ const CommonTestAndPreview = (props) => {
2943
2388
  actions.clearPreviewErrors();
2944
2389
 
2945
2390
  // Initialize empty values for all tags
2946
- setCustomValues(buildEmptyValues());
2391
+ const emptyValues = {};
2392
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2393
+ emptyValues[tag?.fullPath] = '';
2394
+ });
2395
+ setCustomValues(emptyValues);
2947
2396
 
2948
2397
  // Don't make preview call when clearing selection - just reset to raw content
2949
2398
  // Preview will be shown using raw formData/content
@@ -2979,17 +2428,17 @@ const CommonTestAndPreview = (props) => {
2979
2428
  */
2980
2429
  const handleDiscardCustomValues = () => {
2981
2430
  // Initialize empty values for all tags
2982
- const emptyValues = buildEmptyValues();
2431
+ const emptyValues = {};
2432
+ [...requiredTags, ...optionalTags].forEach((tag) => {
2433
+ emptyValues[tag?.fullPath] = '';
2434
+ });
2983
2435
  setCustomValues(emptyValues);
2984
2436
 
2985
- // Reset SMS fallback preview so it shows raw template (with {{tags}} visible) after discard
2986
- setSmsFallbackPreviewText(undefined);
2987
-
2988
2437
  // Update preview with empty values (this is a valid preview call trigger)
2989
2438
  const payload = preparePreviewPayload(
2990
- activeChannelForActions,
2439
+ channel,
2991
2440
  formData || {},
2992
- activeContentForActions,
2441
+ getCurrentContent,
2993
2442
  emptyValues,
2994
2443
  selectedCustomer
2995
2444
  );
@@ -3004,15 +2453,13 @@ const CommonTestAndPreview = (props) => {
3004
2453
  const handleUpdatePreview = async () => {
3005
2454
  try {
3006
2455
  const payload = preparePreviewPayload(
3007
- activeChannelForActions,
2456
+ channel,
3008
2457
  formData || {},
3009
- activeContentForActions,
2458
+ getCurrentContent,
3010
2459
  customValues,
3011
2460
  selectedCustomer
3012
2461
  );
3013
2462
  await actions.updatePreviewRequested(payload);
3014
-
3015
- await syncSmsFallbackPreview(customValues, selectedCustomer);
3016
2463
  setHasPreviewCallBeenMade(true); // Mark that preview call was made
3017
2464
  } catch (error) {
3018
2465
  CapNotification.error({
@@ -3022,115 +2469,25 @@ const CommonTestAndPreview = (props) => {
3022
2469
  };
3023
2470
 
3024
2471
  /**
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.
2472
+ * Handle extract tags
3082
2473
  */
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]);
2474
+ const handleExtractTags = () => {
2475
+ // Get content based on channel
2476
+ let contentToExtract = getCurrentContent;
3104
2477
 
3105
- /**
3106
- * Get content to run tag extraction on (channel-specific).
3107
- */
3108
- const getContentForTagExtraction = () => {
3109
- let contentToExtract = activeContentForActions;
3110
2478
  if (channel === CHANNELS.EMAIL && formData) {
3111
2479
  const currentTabData = formData[currentTab - 1];
3112
2480
  const activeTab = currentTabData?.activeTab;
3113
2481
  const templateContent = currentTabData?.[activeTab]?.['template-content'];
3114
2482
  contentToExtract = templateContent || contentToExtract;
3115
2483
  }
3116
- return contentToExtract;
3117
- };
3118
-
3119
- /**
3120
- * Handle extract tags
3121
- */
3122
- const handleExtractTags = () => {
3123
- if (channel === CHANNELS.RCS) {
3124
- applyRcsSmsFallbackTagExtraction();
3125
- return;
3126
- }
3127
2484
 
3128
- const contentToExtract = getContentForTagExtraction();
2485
+ // Check for personalization tags (excluding unsubscribe)
3129
2486
  const tags = contentToExtract.match(/{{[^}]+}}/g) || [];
3130
2487
  const hasPersonalizationTags = tags.some((tag) => !tag.includes(UNSUBSCRIBE_TAG_NAME));
3131
- const onlyUnsubscribe = !hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME);
3132
2488
 
3133
- if (onlyUnsubscribe) {
2489
+ if (!hasPersonalizationTags && tags.length === 1 && tags[0].includes(UNSUBSCRIBE_TAG_NAME)) {
2490
+ // If only unsubscribe tag is present, show noTagsExtracted message
3134
2491
  setTagsExtracted(false);
3135
2492
  setRequiredTags([]);
3136
2493
  setOptionalTags([]);
@@ -3138,9 +2495,10 @@ const CommonTestAndPreview = (props) => {
3138
2495
  return;
3139
2496
  }
3140
2497
 
2498
+ // Extract tags
3141
2499
  setTagsExtracted(true);
3142
2500
  const { templateSubject, templateContent } = prepareTagExtractionPayload(
3143
- activeChannelForActions,
2501
+ channel,
3144
2502
  formData || {},
3145
2503
  contentToExtract
3146
2504
  );
@@ -3151,132 +2509,16 @@ const CommonTestAndPreview = (props) => {
3151
2509
  * Handle test entities change
3152
2510
  */
3153
2511
  const handleTestEntitiesChange = (value) => {
3154
- if (!Array.isArray(value)) {
3155
- if (value == null || value === '') {
3156
- setSelectedTestEntities([]);
3157
- return;
3158
- }
3159
- setSelectedTestEntities([normalizeTestEntityId(value)]);
3160
- return;
3161
- }
3162
- setSelectedTestEntities(value.map((v) => normalizeTestEntityId(v)));
2512
+ setSelectedTestEntities(value);
3163
2513
  };
3164
2514
 
3165
- /**
3166
- * Map API customerDetails item to our customerData shape
3167
- */
3168
- const mapCustomerDetailsToCustomerData = (detail, identifierValue) => {
3169
- const firstName = detail.firstName || '';
3170
- const lastName = detail.lastName || '';
3171
- const name = [firstName, lastName].filter(Boolean).join(' ').trim() || '';
3172
- const getIdentifierValue = (type) => {
3173
- const fromIdentifiers = detail.identifiers?.find((i) => i.type === type)?.value;
3174
- if (fromIdentifiers) return fromIdentifiers;
3175
- const fromCommChannels = detail.commChannels?.find((c) => c.type === type)?.value;
3176
- return fromCommChannels || (channel === CHANNELS.EMAIL && type === IDENTIFIER_TYPE_EMAIL ? identifierValue : channel === CHANNELS.SMS && type === IDENTIFIER_TYPE_MOBILE ? identifierValue : '');
3177
- };
3178
- return {
3179
- name,
3180
- email: channel === CHANNELS.EMAIL ? (getIdentifierValue(IDENTIFIER_TYPE_EMAIL) || identifierValue) : (getIdentifierValue(IDENTIFIER_TYPE_EMAIL) || ''),
3181
- mobile: channel === CHANNELS.SMS ? (getIdentifierValue(IDENTIFIER_TYPE_MOBILE) || getIdentifierValue(IDENTIFIER_TYPE_PHONE) || identifierValue) : (getIdentifierValue(IDENTIFIER_TYPE_MOBILE) || getIdentifierValue(IDENTIFIER_TYPE_PHONE) || ''),
3182
- customerId: detail.userId != null ? String(detail.userId) : '',
3183
- };
3184
- };
3185
-
3186
- const handleAddTestCustomer = async () => {
3187
- const identifierType = channel === CHANNELS.EMAIL ? IDENTIFIER_TYPE_EMAIL : IDENTIFIER_TYPE_MOBILE;
3188
- const searchValueToCheck = channel === CHANNELS.SMS
3189
- ? formatPhoneNumber((searchValue || '').trim())
3190
- : (searchValue || '').trim();
3191
-
3192
- // Check if this customer is already in the test customers list
3193
- const existingTestCustomer = testCustomers?.find(customer => {
3194
- if (channel === CHANNELS.EMAIL) {
3195
- return customer.email === searchValueToCheck;
3196
- } else if (channel === CHANNELS.SMS) {
3197
- return customer.mobile === searchValueToCheck;
3198
- }
3199
- return false;
3200
- });
3201
-
3202
- if (existingTestCustomer) {
3203
- const entityId = existingTestCustomer.userId ?? existingTestCustomer.customerId;
3204
- if (entityId != null) {
3205
- const id = normalizeTestEntityId(entityId);
3206
- setSelectedTestEntities((prev) => (
3207
- prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
3208
- ));
3209
- }
3210
- setSearchValue('');
3211
- CapNotification.success({
3212
- message: formatMessage(messages.customerAlreadyInTestList),
3213
- });
3214
- return;
3215
- }
3216
-
3217
- setIsCustomerDataLoading(true);
3218
-
3219
- try {
3220
- const response = await getMembersLookup(identifierType, searchValueToCheck);
3221
- const success = response?.success && !response?.status?.isError;
3222
- const res = response?.response || {};
3223
- const exists = res.exists || false;
3224
- const details = res.customerDetails || [];
3225
-
3226
- if (!success) {
3227
- const errorMessage = response?.message || response?.status?.message || formatMessage(messages.memberLookupError);
3228
- CapNotification.error({
3229
- message: formatMessage(messages.memberLookupError),
3230
- description: errorMessage,
3231
- });
3232
- return;
3233
- }
3234
-
3235
- if (exists && details.length > 0) {
3236
- const mapped = mapCustomerDetailsToCustomerData(details[0], searchValueToCheck);
3237
- const customerIdFromLookup = mapped.customerId;
3238
- const alreadyInTestListByCustomerId = customerIdFromLookup && testCustomers?.some(
3239
- (c) => String(c?.customerId) === customerIdFromLookup || String(c?.userId) === customerIdFromLookup
3240
- );
3241
- if (alreadyInTestListByCustomerId) {
3242
- const id = normalizeTestEntityId(customerIdFromLookup);
3243
- setSelectedTestEntities((prev) => (
3244
- prev.some((existing) => testEntityIdsEqual(existing, id)) ? prev : [...prev, id]
3245
- ));
3246
- setSearchValue('');
3247
- CapNotification.success({
3248
- message: formatMessage(messages.customerAlreadyInTestList),
3249
- });
3250
- return;
3251
- }
3252
- setCustomerData(mapped);
3253
- setCustomerModal([true, CUSTOMER_MODAL_EXISTING]);
3254
- } else {
3255
- setCustomerData({
3256
- name: '',
3257
- email: channel === CHANNELS.EMAIL ? searchValueToCheck : '',
3258
- mobile: channel === CHANNELS.SMS ? searchValueToCheck : '',
3259
- customerId: '',
3260
- });
3261
- setCustomerModal([true, CUSTOMER_MODAL_NEW]);
3262
- }
3263
- } catch {
3264
- CapNotification.error({
3265
- message: formatMessage(messages.memberLookupError),
3266
- description: formatMessage(messages.memberLookupError),
3267
- });
3268
- } finally {
3269
- setIsCustomerDataLoading(false);
3270
- }
3271
- };
3272
-
3273
2515
  /**
3274
2516
  * Handle send test message
3275
2517
  */
3276
2518
  const handleSendTestMessage = () => {
3277
2519
  const allUserIds = [];
3278
2520
  selectedTestEntities.forEach((entityId) => {
3279
- const group = testGroups.find((g) => testEntityIdsEqual(g.groupId, entityId));
2521
+ const group = testGroups.find((g) => g.groupId === entityId);
3280
2522
  if (group) {
3281
2523
  allUserIds.push(...group.userIds);
3282
2524
  } else {
@@ -3285,12 +2527,11 @@ const CommonTestAndPreview = (props) => {
3285
2527
  });
3286
2528
  const uniqueUserIds = [...new Set(allUserIds)];
3287
2529
 
3288
- const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS].includes(channel)
2530
+ const deliveryOverride = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP].includes(channel)
3289
2531
  ? testPreviewDeliverySettings[channel]
3290
2532
  : null;
3291
2533
 
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.
2534
+ // Create initial payload based on channel
3294
2535
  const initialPayload = prepareTestMessagePayload(
3295
2536
  channel,
3296
2537
  formData || content || {},
@@ -3298,16 +2539,7 @@ const CommonTestAndPreview = (props) => {
3298
2539
  customValues,
3299
2540
  uniqueUserIds,
3300
2541
  previewData,
3301
- deliveryOverride,
3302
- channel === CHANNELS.RCS
3303
- ? {
3304
- smsFallbackTemplateContent:
3305
- smsFallbackTextForTagExtraction
3306
- || smsFallbackContent?.templateContent
3307
- || smsFallbackContent?.content
3308
- || '',
3309
- }
3310
- : {},
2542
+ deliveryOverride
3311
2543
  );
3312
2544
 
3313
2545
  actions.createMessageMetaRequested(
@@ -3340,10 +2572,11 @@ const CommonTestAndPreview = (props) => {
3340
2572
  // ============================================
3341
2573
  // RENDER HELPER FUNCTIONS
3342
2574
  // ============================================
2575
+
3343
2576
  const renderLeftPanelContent = () => (
3344
2577
  <LeftPanelContent
3345
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
3346
- extractedTags={leftPanelExtractedTags}
2578
+ isExtractingTags={isExtractingTags}
2579
+ extractedTags={extractedTags}
3347
2580
  selectedCustomer={selectedCustomer}
3348
2581
  handleCustomerSelect={handleCustomerSelect}
3349
2582
  handleSearchCustomer={handleSearchCustomer}
@@ -3361,29 +2594,15 @@ const CommonTestAndPreview = (props) => {
3361
2594
 
3362
2595
  const renderCustomValuesEditor = () => (
3363
2596
  <CustomValuesEditor
3364
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
2597
+ isExtractingTags={isExtractingTags}
3365
2598
  isUpdatePreviewDisabled={isUpdatePreviewDisabled}
3366
2599
  showJSON={showJSON}
3367
2600
  setShowJSON={setShowJSON}
3368
2601
  customValues={customValues}
3369
2602
  handleJSONTextChange={handleJSONTextChange}
3370
- sections={[
3371
- {
3372
- key: channel,
3373
- title:
3374
- channel === CHANNELS.RCS
3375
- ? messages.rcsTagsSectionTitle
3376
- : messages[`${channel}TagsSectionTitle`],
3377
- requiredTags,
3378
- optionalTags,
3379
- },
3380
- {
3381
- key: PREVIEW_TAB_SMS_FALLBACK,
3382
- title: channel === CHANNELS.RCS && smsFallbackContent?.templateContent ? messages.smsFallbackTagsSectionTitle : null,
3383
- requiredTags: smsFallbackRequiredTags,
3384
- optionalTags: smsFallbackOptionalTags,
3385
- },
3386
- ]}
2603
+ extractedTags={extractedTags}
2604
+ requiredTags={requiredTags}
2605
+ optionalTags={optionalTags}
3387
2606
  handleCustomValueChange={handleCustomValueChange}
3388
2607
  handleDiscardCustomValues={handleDiscardCustomValues}
3389
2608
  handleUpdatePreview={handleUpdatePreview}
@@ -3399,20 +2618,6 @@ const CommonTestAndPreview = (props) => {
3399
2618
  }));
3400
2619
  };
3401
2620
 
3402
- /** Trim pasted emails (trailing CR/LF). SMS: strip non-digits so pasted formatted numbers match isValidMobile / API. */
3403
- const handleTestCustomersSearch = useCallback((value) => {
3404
- if (value == null || value === '') {
3405
- setSearchValue('');
3406
- return;
3407
- }
3408
- const raw = String(value).trim();
3409
- if (channel === CHANNELS.SMS) {
3410
- setSearchValue(formatPhoneNumber(raw));
3411
- } else {
3412
- setSearchValue(raw);
3413
- }
3414
- }, [channel]);
3415
-
3416
2621
  const renderSendTestMessage = () => (
3417
2622
  <SendTestMessage
3418
2623
  isFetchingTestCustomers={isFetchingTestCustomers}
@@ -3425,18 +2630,14 @@ const CommonTestAndPreview = (props) => {
3425
2630
  content={getCurrentContent}
3426
2631
  channel={channel}
3427
2632
  isSendingTestMessage={isSendingTestMessage}
3428
- renderAddTestCustomerButton={renderAddTestCustomerButton}
3429
2633
  formatMessage={formatMessage}
3430
2634
  deliverySettings={testPreviewDeliverySettings[channel]}
3431
- senderDetailsByChannel={senderDetailsByChannel}
2635
+ senderDetailsOptions={senderDetailsByChannel[channel]}
3432
2636
  wecrmAccounts={wecrmAccounts}
3433
2637
  onSaveDeliverySettings={handleSaveDeliverySettings}
3434
2638
  isLoadingSenderDetails={isLoadingSenderDetails}
3435
2639
  smsTraiDltEnabled={smsTraiDltEnabled}
3436
2640
  registeredSenderIds={registeredSenderIds}
3437
- isChannelSmsFallbackPreviewEnabled={channel === CHANNELS.RCS && !!smsFallbackContent?.templateContent}
3438
- searchValue={searchValue}
3439
- setSearchValue={handleTestCustomersSearch}
3440
2641
  />
3441
2642
  );
3442
2643
 
@@ -3446,21 +2647,6 @@ const CommonTestAndPreview = (props) => {
3446
2647
  />
3447
2648
  );
3448
2649
 
3449
- const renderAddTestCustomerButton = () => {
3450
- const raw = (searchValue || '').trim();
3451
- const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
3452
- const showAddButton =
3453
- [CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
3454
- (channel === CHANNELS.EMAIL ? isValidEmail(value) : isValidMobile(value));
3455
- if (!showAddButton) return null;
3456
- return (
3457
- <AddTestCustomerButton
3458
- searchValue={value}
3459
- handleAddTestCustomer={handleAddTestCustomer}
3460
- />
3461
- );
3462
- };
3463
-
3464
2650
  // Header content for the slidebox
3465
2651
  const slideboxHeader = (
3466
2652
  <CapRow className="test-preview-header">
@@ -3480,18 +2666,14 @@ const CommonTestAndPreview = (props) => {
3480
2666
  show={show}
3481
2667
  size="size-xl"
3482
2668
  content={(
3483
- <CapSpin
3484
- spinning={isCustomerDataLoading}
3485
- className={`common-test-preview-lookup-spin ${isCustomerDataLoading ? 'common-test-preview-customer-loading' : ''}`}
3486
- >
3487
- <CapRow className="test-preview-container">
3488
- <CapRow className="test-and-preview-panels">
3489
- {/* Left Panel */}
3490
- <CapRow className="left-panel">
3491
- {channel === CHANNELS.ZALO ? null : renderLeftPanelContent()}
3492
- <CapDivider className="panel-divider" />
3493
-
3494
- {/* Send Test Message Section */}
2669
+ <CapRow className="test-preview-container">
2670
+ <CapRow className="test-and-preview-panels">
2671
+ {/* Left Panel */}
2672
+ <CapRow className="left-panel">
2673
+ {channel === CHANNELS.ZALO ? null : renderLeftPanelContent()}
2674
+ <CapDivider className="panel-divider" />
2675
+
2676
+ {/* Send Test Message Section */}
3495
2677
  {config.enableTestMessage !== false && (
3496
2678
  <CapRow className="panel-section send-test-section">
3497
2679
  {renderSendTestMessage()}
@@ -3505,27 +2687,7 @@ const CommonTestAndPreview = (props) => {
3505
2687
  {renderPreview()}
3506
2688
  </CapRow>
3507
2689
  </CapRow>
3508
- {customerModal[0] && customerModal[1] === CUSTOMER_MODAL_EXISTING && (
3509
- <ExistingCustomerModal
3510
- customerData={customerData}
3511
- onCloseCustomerModal={handleCloseCustomerModal}
3512
- customerModal={customerModal}
3513
- channel={channel}
3514
- onSave={handleSaveTestCustomer}
3515
- />
3516
- )}
3517
- {customerModal[0] && customerModal[1] === CUSTOMER_MODAL_NEW && (
3518
- <CustomerCreationModal
3519
- customerData={customerData}
3520
- setCustomerData={setCustomerData}
3521
- onCloseCustomerModal={handleCloseCustomerModal}
3522
- customerModal={customerModal}
3523
- onSave={handleSaveTestCustomer}
3524
- channel={channel}
3525
- />
3526
- )}
3527
- </CapRow>
3528
- </CapSpin>
2690
+ </CapRow>
3529
2691
  )}
3530
2692
  />
3531
2693
  );
@@ -3627,4 +2789,4 @@ CommonTestAndPreview.defaultProps = {
3627
2789
  // Note: Redux connection is handled by the wrapper components (e.g., TestAndPreviewSlidebox)
3628
2790
  // This component receives all Redux props (including intl) from its parent
3629
2791
 
3630
- export default CommonTestAndPreview;
2792
+ export default CommonTestAndPreview;