@capillarytech/creatives-library 8.0.330-alpha.0 → 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 -18
  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 -28
  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 -998
  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 -13
  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 -292
  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 +3 -40
  53. package/v2Containers/Rcs/index.js +895 -1145
  54. package/v2Containers/Rcs/index.scss +6 -85
  55. package/v2Containers/Rcs/messages.js +2 -12
  56. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2236 -41719
  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 -435
  61. package/v2Containers/Rcs/utils.js +10 -405
  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 -636
  66. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  67. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2604 -4590
  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 -201
  82. package/utils/tests/templateVarUtils.test.js +0 -204
  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 -118
  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 -277
  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 -225
  114. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  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,138 +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
- import { pickFirstSmsFallbackTemplateString } from '../../v2Containers/Rcs/rcsLibraryHydrationUtils';
101
-
102
- import { isValidEmail, isValidMobile, formatPhoneNumber } from '../../utils/commonUtils';
103
- import { getMembersLookup } from '../../services/api';
104
-
105
- /**
106
- * Drop empty GSM rows. RCS/DLT responses often set gsm_sender_id equal to domainName — keep those rows
107
- * for RCS defaults (ModifyDeliverySettings uses the same rule for the sender dropdown).
108
- */
109
- const filterUsableGsmSendersForDomain = (domain, gsmSenders, { skipDomainNameEchoFilter = false } = {}) => {
110
- const normalizedDomainName =
111
- domain?.domainName != null ? String(domain.domainName).trim().toLowerCase() : '';
112
- return (gsmSenders || []).filter((gsmSenderRow) => {
113
- const rawValue = gsmSenderRow?.value;
114
- if (rawValue == null) return false;
115
- const trimmedSenderValue = String(rawValue).trim();
116
- if (!trimmedSenderValue) return false;
117
- const senderMatchesDomainLabel =
118
- normalizedDomainName && trimmedSenderValue.toLowerCase() === normalizedDomainName;
119
- if (!skipDomainNameEchoFilter && senderMatchesDomainLabel) return false;
120
- return true;
121
- });
122
- };
123
-
124
- /** Preview payload from Redux may be an Immutable Map — normalize for React state. */
125
- const toPlainPreviewData = (data) => {
126
- if (data == null) return null;
127
- const plain = typeof data.toJS === 'function' ? data.toJS() : data;
128
- return normalizePreviewApiPayload(plain);
129
- };
130
-
131
- /**
132
- * Merge existing customValues with tag keys from categorized groups.
133
- * Each group is { required, optional } (arrays of tag objects with fullPath).
134
- * Preserves existing values; adds '' for any tag key not yet present.
135
- * Reusable for RCS+fallback and any flow that needs to ensure customValues has keys for tags.
136
- */
137
- const mergeCustomValuesWithTagKeys = (prev, ...categorizedGroups) => {
138
- const next = { ...(prev || {}) };
139
- categorizedGroups.forEach((group) => {
140
- [...(group.required || []), ...(group.optional || [])].forEach((tag) => {
141
- const key = tag?.fullPath;
142
- if (key && next[key] === undefined) next[key] = '';
143
- });
144
- });
145
- return next;
146
- };
147
-
148
- /** True when `body` contains `{{name}}` mustache tokens (user-fillable personalization tags).
149
- * DLT `{#name#}` slots are pre-bound template variables and are intentionally excluded. */
150
- const smsTemplateHasMustacheTags = (body) =>
151
- typeof body === 'string' && SMS_MUSTACHE_TAG_PATTERN.test(body);
152
74
 
153
- /**
154
- * Build tag rows from `{{…}}` mustache tokens only — DLT `{#…#}` slots are excluded because
155
- * they are pre-bound template variables, not user-fillable personalization tags.
156
- * Passing a mustache-only captureRegex to extractTemplateVariables skips the DLT branch.
157
- * A non-global regex is used so ensureGlobalRegexForExecLoop creates a fresh instance on each call.
158
- */
159
- const buildSyntheticSmsMustacheTags = (body = '') => {
160
- if (!body || typeof body !== 'string') return [];
161
- return extractTemplateVariables(body, /\{\{([^}]+)\}\}/).map((name) => ({
162
- name,
163
- metaData: { userDriven: false },
164
- children: [],
165
- }));
166
- };
167
-
168
- /** RCS createMessageMeta: media shape (mediaUrl, thumbnailUrl, height string). */
169
- const normalizeRcsTestCardMedia = (media) => {
170
- if (!media || typeof media !== 'object') return undefined;
171
- const mediaUrl =
172
- media.mediaUrl != null && String(media.mediaUrl).trim() !== ''
173
- ? String(media.mediaUrl)
174
- : media.url != null && String(media.url).trim() !== ''
175
- ? String(media.url)
176
- : '';
177
- const thumbnailUrl = media.thumbnailUrl != null ? String(media.thumbnailUrl) : '';
178
- const height = media.height != null ? String(media.height) : undefined;
179
- const out = { mediaUrl, thumbnailUrl };
180
- if (height) out.height = height;
181
- return out;
182
- };
183
-
184
- /** RCS createMessageMeta: suggestion shape (index, type, text, phoneNumber, url, postback). */
185
- const mapRcsSuggestionForTestMeta = (suggestionRow, index) => ({
186
- index,
187
- type: suggestionRow?.type ?? '',
188
- text: suggestionRow?.text != null ? String(suggestionRow.text) : '',
189
- phoneNumber:
190
- suggestionRow?.phoneNumber != null
191
- ? String(suggestionRow.phoneNumber)
192
- : suggestionRow?.phone_number != null
193
- ? String(suggestionRow.phone_number)
194
- : '',
195
- url: suggestionRow?.url !== undefined ? suggestionRow.url : null,
196
- postback:
197
- suggestionRow?.postback != null
198
- ? String(suggestionRow.postback)
199
- : suggestionRow?.text != null
200
- ? String(suggestionRow.text)
201
- : '',
202
- });
203
-
204
- /**
205
- * CapTreeSelect and group resolution use strict equality; API data may mix numeric and string ids.
206
- */
207
- const normalizeTestEntityId = (id) => {
208
- if (id === undefined || id === null) return id;
209
- return String(id);
210
- };
211
-
212
- const testEntityIdsEqual = (a, b) => {
213
- if (a == null && b == null) return true;
214
- if (a == null || b == null) return false;
215
- return String(a) === String(b);
216
- };
75
+ // Import utilities
76
+ import { getCdnUrl } from '../../utils/cdnTransformation';
217
77
 
218
78
  /**
219
79
  * Preview Component Factory - REMOVED IN PHASE 5
@@ -225,7 +85,7 @@ const testEntityIdsEqual = (a, b) => {
225
85
  */
226
86
  const CommonTestAndPreview = (props) => {
227
87
  const {
228
- intl: { formatMessage, locale: userLocale = 'en' },
88
+ intl: { formatMessage },
229
89
  show,
230
90
  onClose,
231
91
  channel, // The channel: 'EMAIL', 'SMS', 'RCS', etc.
@@ -261,34 +121,19 @@ const CommonTestAndPreview = (props) => {
261
121
  ...additionalProps
262
122
  } = props;
263
123
 
264
- const smsFallbackContent = additionalProps?.smsFallbackContent;
265
- const smsFallbackTextForTagExtraction = useMemo(
266
- () => getSmsFallbackTextForTagExtraction(smsFallbackContent),
267
- [smsFallbackContent],
268
- );
269
124
  // ============================================
270
125
  // STATE MANAGEMENT
271
126
  // ============================================
272
127
  const [selectedCustomer, setSelectedCustomer] = useState(null);
273
128
  const [requiredTags, setRequiredTags] = useState([]);
274
129
  const [optionalTags, setOptionalTags] = useState([]);
275
- const [smsFallbackExtractedTags, setSmsFallbackExtractedTags] = useState([]);
276
- const [smsFallbackRequiredTags, setSmsFallbackRequiredTags] = useState([]);
277
- const [smsFallbackOptionalTags, setSmsFallbackOptionalTags] = useState([]);
278
- const [isExtractingSmsFallbackTags, setIsExtractingSmsFallbackTags] = useState(false);
279
130
  const [customValues, setCustomValues] = useState({});
280
131
  const [showJSON, setShowJSON] = useState(false);
281
132
  const [tagsExtracted, setTagsExtracted] = useState(false);
282
-
283
- const initialDevice = CHANNELS_USING_ANDROID_PREVIEW_DEVICE.includes(channel) ? ANDROID : DESKTOP;
284
- const [searchValue, setSearchValue] = useState("");
285
- const [customerModal, setCustomerModal] = useState([false, ""]);
286
- const [isCustomerDataLoading, setIsCustomerDataLoading] = useState(false);
287
- const [customerData, setCustomerData] = useState({ name: '', email: '', mobile: '', customerId: '' });
288
-
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;
289
136
  const [previewDevice, setPreviewDevice] = useState(initialDevice);
290
- const [activePreviewTab, setActivePreviewTab] = useState(PREVIEW_TAB_RCS);
291
- const [smsFallbackPreviewText, setSmsFallbackPreviewText] = useState(undefined);
292
137
  // Track if a preview call has been made (to know when to use previewDataHtml vs raw content)
293
138
  const [hasPreviewCallBeenMade, setHasPreviewCallBeenMade] = useState(false);
294
139
  const [previewDataHtml, setPreviewDataHtml] = useState(() => {
@@ -321,22 +166,15 @@ const CommonTestAndPreview = (props) => {
321
166
  [CHANNELS.WHATSAPP]: {
322
167
  domainId: null, senderMobNum: '', sourceAccountIdentifier: '',
323
168
  },
324
- [CHANNELS.RCS]: {
325
- domainId: null,
326
- domainGatewayMapId: null,
327
- gsmSenderId: '',
328
- smsFallbackDomainId: null,
329
- cdmaSenderId: '', // gsmSenderId = RCS sender (domainId|senderId), cdmaSenderId = SMS fallback
330
- },
331
169
  });
332
170
 
333
- const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP, CHANNELS.RCS];
171
+ const channelsWithDeliverySettings = [CHANNELS.SMS, CHANNELS.EMAIL, CHANNELS.WHATSAPP];
334
172
  const formDataForSendTest = formData ?? (content && typeof content === 'object' && !Array.isArray(content) ? content : formData);
335
173
  const smsTemplateConfigs = formDataForSendTest?.templateConfigs || {};
336
174
  const smsTraiDltEnabled = !!smsTemplateConfigs?.traiDltEnabled;
337
175
  const registeredSenderIds = smsTemplateConfigs?.registeredSenderIds || [];
338
176
 
339
- // 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
340
178
  useEffect(() => {
341
179
  if (!show || !channel) {
342
180
  return;
@@ -345,10 +183,6 @@ const CommonTestAndPreview = (props) => {
345
183
  if (actions.getSenderDetailsRequested) {
346
184
  actions.getSenderDetailsRequested({ channel, orgUnitId: orgUnitId ?? -1 });
347
185
  }
348
- // SMS domains/senders are needed for RCS delivery UI (fallback row + slidebox) whenever RCS is open — not only when fallback body exists.
349
- if (channel === CHANNELS.RCS && actions.getSenderDetailsRequested) {
350
- actions.getSenderDetailsRequested({ channel: CHANNELS.SMS, orgUnitId: orgUnitId ?? -1 });
351
- }
352
186
  if (channel === CHANNELS.WHATSAPP && actions.getWeCrmAccountsRequested) {
353
187
  actions.getWeCrmAccountsRequested({ sourceName: CHANNELS.WHATSAPP });
354
188
  }
@@ -360,84 +194,20 @@ const CommonTestAndPreview = (props) => {
360
194
  // Auto-set default delivery setting when sender details load (campaigns-style: first domain + default/first sender)
361
195
  useEffect(() => {
362
196
  if (!channel || !channelsWithDeliverySettings.includes(channel)) return;
363
-
364
- if (channel === CHANNELS.RCS) {
365
- const rcsDomainRows = senderDetailsByChannel?.[CHANNELS.RCS] || [];
366
- const smsFallbackDomainRows = senderDetailsByChannel?.[CHANNELS.SMS] || [];
367
- if (!rcsDomainRows.length) return;
368
-
369
- const currentRcsDeliverySettings = testPreviewDeliverySettings?.[CHANNELS.RCS] || {};
370
- const isRcsGsmSenderUnset = !currentRcsDeliverySettings?.gsmSenderId;
371
- const isSmsFallbackSenderUnset = !currentRcsDeliverySettings?.cdmaSenderId;
372
- const isRcsDeliveryFullyUnset = isRcsGsmSenderUnset && isSmsFallbackSenderUnset;
373
- const shouldOnlyFillSmsFallbackSender =
374
- !isRcsGsmSenderUnset && isSmsFallbackSenderUnset && smsFallbackDomainRows.length > 0;
375
-
376
- if (!isRcsDeliveryFullyUnset && !shouldOnlyFillSmsFallbackSender) return;
377
-
378
- const firstRcsDomain = rcsDomainRows[0];
379
- const firstSmsFallbackDomain = smsFallbackDomainRows[0];
380
- const usableRcsGsmSenders = filterUsableGsmSendersForDomain(
381
- firstRcsDomain,
382
- firstRcsDomain?.gsmSenders,
383
- { skipDomainNameEchoFilter: true },
384
- );
385
- const usableSmsFallbackGsmSenders = firstSmsFallbackDomain
386
- ? filterUsableGsmSendersForDomain(firstSmsFallbackDomain, firstSmsFallbackDomain?.gsmSenders)
387
- : [];
388
- const defaultRcsGsmSender = usableRcsGsmSenders[0];
389
- const defaultSmsFallbackGsmSender = usableSmsFallbackGsmSenders[0];
390
- const rcsSenderCompositeValue =
391
- firstRcsDomain?.domainId != null && defaultRcsGsmSender?.value != null
392
- ? `${firstRcsDomain.domainId}|${defaultRcsGsmSender.value}`
393
- : (defaultRcsGsmSender?.value || '');
394
- const smsFallbackSenderCompositeValue =
395
- firstSmsFallbackDomain?.domainId != null && defaultSmsFallbackGsmSender?.value != null
396
- ? `${firstSmsFallbackDomain.domainId}|${defaultSmsFallbackGsmSender.value}`
397
- : (defaultSmsFallbackGsmSender?.value || '');
398
-
399
- setTestPreviewDeliverySettings((prev) => {
400
- const previousRcsSettings = prev?.[CHANNELS.RCS] || {};
401
- if (shouldOnlyFillSmsFallbackSender) {
402
- if (!smsFallbackSenderCompositeValue) return prev;
403
- return {
404
- ...prev,
405
- [CHANNELS.RCS]: {
406
- ...previousRcsSettings,
407
- smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
408
- cdmaSenderId: smsFallbackSenderCompositeValue,
409
- },
410
- };
411
- }
412
- return {
413
- ...prev,
414
- [CHANNELS.RCS]: {
415
- domainId: firstRcsDomain?.domainId ?? null,
416
- domainGatewayMapId: firstRcsDomain?.dgmId ?? null,
417
- gsmSenderId: rcsSenderCompositeValue,
418
- smsFallbackDomainId: firstSmsFallbackDomain?.domainId ?? null,
419
- cdmaSenderId: smsFallbackSenderCompositeValue,
420
- },
421
- };
422
- });
423
- return;
424
- }
425
-
426
197
  const domains = senderDetailsByChannel[channel];
427
198
  if (!domains || domains.length === 0) return;
428
199
  const {
429
- domainId = '', gsmSenderId = '', cdmaSenderId = '', senderEmail = '', senderMobNum = '',
200
+ domainId = '', gsmSenderId = '', senderEmail = '', senderMobNum = '',
430
201
  } = testPreviewDeliverySettings[channel] || {};
431
- const isEmptySelection = !domainId && !gsmSenderId && !cdmaSenderId && !senderEmail && !senderMobNum;
202
+ const isEmptySelection = !domainId && !gsmSenderId && !senderEmail && !senderMobNum;
432
203
  if (!isEmptySelection) return;
433
204
 
434
205
  const whatsappAccountFromForm = channel === CHANNELS.WHATSAPP ? formData?.accountName : undefined;
435
206
  const matchedWhatsappAccount = whatsappAccountFromForm
436
207
  ? (wecrmAccounts || []).find((account) => account?.name === whatsappAccountFromForm)
437
208
  : null;
438
- const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled && registeredSenderIds?.length
439
- ? domains.filter((domain) => (domain?.gsmSenders || []).some((gsm) =>
440
- registeredSenderIds?.includes(gsm?.value)))
209
+ const smsDomains = channel === CHANNELS.SMS && smsTraiDltEnabled
210
+ ? domains.filter((domain) => (domain.gsmSenders || []).some((gsm) => registeredSenderIds.includes(gsm.value)))
441
211
  : domains;
442
212
  const [defaultDomain] = domains;
443
213
  const [firstSmsDomain] = smsDomains;
@@ -452,8 +222,8 @@ const CommonTestAndPreview = (props) => {
452
222
  const next = { ...prev };
453
223
  if (channel === CHANNELS.SMS) {
454
224
  const smsGsmSenders = smsTraiDltEnabled
455
- ? (firstDomain?.gsmSenders || []).filter((gsm) => registeredSenderIds?.includes(gsm?.value))
456
- : firstDomain?.gsmSenders;
225
+ ? (firstDomain.gsmSenders || []).filter((gsm) => registeredSenderIds.includes(gsm.value))
226
+ : firstDomain.gsmSenders;
457
227
  next[channel] = {
458
228
  domainId: firstDomain.domainId,
459
229
  domainGatewayMapId: firstDomain.dgmId,
@@ -486,29 +256,10 @@ const CommonTestAndPreview = (props) => {
486
256
  // MEMOIZED VALUES
487
257
  // ============================================
488
258
 
489
- const allTags = useMemo(
490
- () => [...requiredTags, ...optionalTags, ...smsFallbackRequiredTags, ...smsFallbackOptionalTags],
491
- [requiredTags, optionalTags, smsFallbackRequiredTags, smsFallbackOptionalTags]
492
- );
493
-
494
- const allRequiredTags = useMemo(
495
- () => [...requiredTags, ...smsFallbackRequiredTags],
496
- [requiredTags, smsFallbackRequiredTags]
497
- );
498
-
499
- const buildEmptyValues = useCallback(
500
- () => allTags.reduce((acc, tag) => {
501
- const key = tag?.fullPath;
502
- if (key) acc[key] = '';
503
- return acc;
504
- }, {}),
505
- [allTags]
506
- );
507
-
508
259
  // Check if update preview button should be disabled
509
260
  const isUpdatePreviewDisabled = useMemo(() => (
510
- allRequiredTags.some((tag) => !customValues[tag.fullPath])
511
- ), [allRequiredTags, customValues]);
261
+ requiredTags.some((tag) => !customValues[tag.fullPath])
262
+ ), [requiredTags, customValues]);
512
263
 
513
264
  // Get current content based on channel and editor type
514
265
  const getCurrentContent = useMemo(() => {
@@ -552,13 +303,6 @@ const CommonTestAndPreview = (props) => {
552
303
  return currentTabData.base['sms-editor'];
553
304
  }
554
305
  }
555
- // DLT / Test & Preview shape: { templateConfigs: { template, templateId, ... } }
556
- if (formData.templateConfigs?.template) {
557
- const smsDltTemplateValue = formData.templateConfigs.template;
558
- if (typeof smsDltTemplateValue === 'string') return smsDltTemplateValue;
559
- if (Array.isArray(smsDltTemplateValue)) return smsDltTemplateValue.join('');
560
- return '';
561
- }
562
306
  }
563
307
 
564
308
  // SMS channel fallback - if formData is not available, use content directly
@@ -604,90 +348,20 @@ const CommonTestAndPreview = (props) => {
604
348
  return content || '';
605
349
  }, [channel, formData, currentTab, beeContent, content, beeInstance]);
606
350
 
607
- const leftPanelExtractedTags = useMemo(() => {
608
- if (channel === CHANNELS.SMS) {
609
- const smsEditorBody = typeof getCurrentContent === 'string' ? getCurrentContent : '';
610
- if (!smsTemplateHasMustacheTags(smsEditorBody)) return [];
611
- const extractTagsFromApi = extractedTags ?? [];
612
- if (extractTagsFromApi.length > 0) return extractTagsFromApi;
613
- return buildSyntheticSmsMustacheTags(smsEditorBody);
614
- }
615
- const hasFallbackSmsBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
616
- if (channel === CHANNELS.RCS && hasFallbackSmsBody) {
617
- const rcsPrimaryTags = extractedTags ?? [];
618
- const fallbackSmsTextForTags = smsFallbackTextForTagExtraction ?? '';
619
- const fallbackSmsTagRows = smsTemplateHasMustacheTags(fallbackSmsTextForTags)
620
- ? (smsFallbackExtractedTags?.length > 0
621
- ? smsFallbackExtractedTags
622
- : buildSyntheticSmsMustacheTags(fallbackSmsTextForTags))
623
- : [];
624
- const mergedRcsAndFallbackTags = [...rcsPrimaryTags, ...fallbackSmsTagRows];
625
- if (mergedRcsAndFallbackTags.length > 0) return mergedRcsAndFallbackTags;
626
- return buildSyntheticSmsMustacheTags(fallbackSmsTextForTags);
627
- }
628
- return extractedTags ?? [];
629
- }, [
630
- channel,
631
- extractedTags,
632
- getCurrentContent,
633
- smsFallbackContent,
634
- smsFallbackExtractedTags,
635
- smsFallbackTextForTagExtraction,
636
- ]);
637
-
638
- const isRcsSmsFallbackPreviewEnabled =
639
- channel === CHANNELS.RCS
640
- && !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
641
- // Only treat as SMS when user is on the Fallback SMS tab — not whenever fallback exists (RCS tab needs RCS preview API).
642
- const isSmsFallbackTabActive = isRcsSmsFallbackPreviewEnabled && activePreviewTab === PREVIEW_TAB_SMS_FALLBACK;
643
- const activeChannelForActions = isSmsFallbackTabActive ? CHANNELS.SMS : channel;
644
- // VarSegment slot values live in rcsSmsFallbackVarMapped; raw templateContent alone is stale for /preview Body.
645
- const resolvedSmsFallbackBodyForPreviewTab =
646
- smsFallbackTextForTagExtraction
647
- || smsFallbackContent?.templateContent
648
- || smsFallbackContent?.content
649
- || '';
650
- const activeContentForActions = isSmsFallbackTabActive
651
- ? resolvedSmsFallbackBodyForPreviewTab
652
- : getCurrentContent;
653
-
654
- /**
655
- * SMS fallback pane must show /preview API result when user updated preview on that tab (plain text).
656
- * Skip when resolvedBody is RCS-shaped (e.g. user last previewed on RCS tab).
657
- */
658
- // smsFallbackPreviewText is the single source of truth for the resolved SMS fallback preview.
659
- // It is set only by syncSmsFallbackPreview (called from handleUpdatePreview and the
660
- // prefilled-values effect) and reset to undefined on discard / slidebox close.
661
- // Using previewDataHtml as a fallback is unsafe because that state is shared with the primary
662
- // RCS preview and can contain stale SMS or RCS content.
663
- const smsFallbackResolvedText = useMemo(() => {
664
- const hasFallbackBody = !!(smsFallbackContent?.templateContent || smsFallbackContent?.content);
665
- if (channel !== CHANNELS.RCS || !hasFallbackBody) return undefined;
666
- if (smsFallbackPreviewText != null) return smsFallbackPreviewText;
667
- return undefined;
668
- }, [channel, smsFallbackContent, smsFallbackPreviewText]);
669
-
670
- // Build test entities tree data from testCustomers prop
671
- // Build test entities tree data from testCustomers prop (includes customers added via addTestCustomer action)
351
+ // Build test entities tree data
672
352
  const testEntitiesTreeData = useMemo(() => {
673
353
  const groupsNode = {
674
354
  title: 'Groups',
675
355
  value: 'groups-node',
676
356
  selectable: false,
677
- children: testGroups?.map((group) => ({
678
- title: group?.groupName,
679
- value: normalizeTestEntityId(group?.groupId),
680
- })),
357
+ children: testGroups?.map((group) => ({ title: group?.groupName, value: group?.groupId })),
681
358
  };
682
359
 
683
360
  const customersNode = {
684
361
  title: 'Individuals',
685
362
  value: 'customers-node',
686
363
  selectable: false,
687
- children: testCustomers?.map((customer) => ({
688
- title: customer?.name?.trim() || customer?.email?.trim() || customer?.mobile?.trim() || customer?.userId || customer?.customerId,
689
- value: normalizeTestEntityId(customer?.userId ?? customer?.customerId),
690
- })) || [],
364
+ children: testCustomers?.map((customer) => ({ title: customer.name, value: customer?.userId })),
691
365
  };
692
366
 
693
367
  return [groupsNode, customersNode];
@@ -714,93 +388,6 @@ const CommonTestAndPreview = (props) => {
714
388
  return resolvedText;
715
389
  };
716
390
 
717
- /**
718
- * Common handler for saving test customers (both new and existing)
719
- */
720
- const handleSaveTestCustomer = async (validationErrors = {}, setIsLoading = () => {}) => {
721
- // Check for validation errors before saving (for new customers)
722
- if (customerModal[1] === CUSTOMER_MODAL_NEW && (validationErrors.email || validationErrors.mobile)) {
723
- return;
724
- }
725
-
726
- setIsLoading(true);
727
-
728
- try {
729
- let payload;
730
-
731
- if (customerModal[1] === CUSTOMER_MODAL_EXISTING) {
732
- // For existing customers, use customerId
733
- payload = {
734
- campaignUserId: customerData.customerId
735
- };
736
- } else {
737
- // For new customers, use customer object
738
- payload = {
739
- customer: {
740
- firstName: customerData.name || "",
741
- mobile: customerData.mobile || "",
742
- email: customerData.email || ""
743
- }
744
- };
745
- }
746
-
747
- const response = await createTestCustomer(payload);
748
-
749
-
750
- // Handle success: add to test customers list and selection (existing and new)
751
- if (response && response.success) {
752
- CapNotification.success({
753
- message: formatMessage(messages.newTestCustomerAddedSuccess),
754
- });
755
- // API may return customerId in response.response (e.g. { response: { customerId: 438845651 } })
756
- const res = response?.response || response;
757
- const addedId = customerModal[1] === CUSTOMER_MODAL_EXISTING
758
- ? customerData?.customerId || customerData?.campaignUserId
759
- : res?.customerId || res?.campaignUserId;
760
- if (addedId) {
761
- const normalizedAddedId = normalizeTestEntityId(addedId);
762
- actions.addTestCustomer({
763
- userId: normalizedAddedId,
764
- customerId: normalizedAddedId,
765
- name: customerData?.name?.trim() || '',
766
- email: customerData?.email || '',
767
- mobile: customerData?.mobile || '',
768
- });
769
- setSelectedTestEntities((prev) => (
770
- prev.some((id) => testEntityIdsEqual(id, normalizedAddedId))
771
- ? prev
772
- : [...prev, normalizedAddedId]
773
- ));
774
- }
775
- handleCloseCustomerModal();
776
- } else {
777
- // Show error notification for unsuccessful response
778
- CapNotification.error({
779
- message: formatMessage(messages.errorTitle),
780
- description: response?.message || formatMessage(messages.failedToAddTestCustomer),
781
- });
782
- }
783
- } catch (error) {
784
- CapNotification.error({
785
- message: formatMessage(messages.errorTitle),
786
- description: error?.message || formatMessage(messages.errorAddingTestCustomer),
787
- });
788
- } finally {
789
- setIsLoading(false);
790
- }
791
- };
792
-
793
- const handleCloseCustomerModal = () => {
794
- setCustomerModal([false, ""]);
795
- setSearchValue('');
796
- setCustomerData({
797
- name: '',
798
- email: '',
799
- mobile: '',
800
- customerId: '',
801
- });
802
- };
803
-
804
391
  /**
805
392
  * Prepare payload for preview API based on channel
806
393
  */
@@ -873,37 +460,6 @@ const CommonTestAndPreview = (props) => {
873
460
  }
874
461
  };
875
462
 
876
- /**
877
- * When RCS has SMS fallback, refresh fallback preview text via the same Liquid /preview API
878
- * (separate call with SMS channel + fallback template body). Used after primary preview updates.
879
- */
880
- const syncSmsFallbackPreview = async (customValuesForResolve, selectedCustomerObj) => {
881
- const fallbackBodyForLiquidPreview =
882
- getSmsFallbackTextForTagExtraction(smsFallbackContent)
883
- || smsFallbackContent?.templateContent
884
- || smsFallbackContent?.content
885
- || '';
886
- if (channel !== CHANNELS.RCS || !String(fallbackBodyForLiquidPreview).trim()) return;
887
- try {
888
- const smsFallbackPayload = preparePreviewPayload(
889
- CHANNELS.SMS,
890
- formData || {},
891
- fallbackBodyForLiquidPreview,
892
- customValuesForResolve,
893
- selectedCustomerObj
894
- );
895
- const fallbackResponse = await Api.updateEmailPreview(smsFallbackPayload);
896
- const fallbackPreview = extractPreviewFromLiquidResponse(fallbackResponse);
897
- setSmsFallbackPreviewText(
898
- typeof fallbackPreview?.resolvedBody === 'string'
899
- ? fallbackPreview.resolvedBody
900
- : undefined
901
- );
902
- } catch (e) {
903
- /* keep existing smsFallbackPreviewText on failure */
904
- }
905
- };
906
-
907
463
  /**
908
464
  * Prepare payload for tag extraction based on channel
909
465
  */
@@ -998,7 +554,7 @@ const CommonTestAndPreview = (props) => {
998
554
  } = carousel || {};
999
555
  const buttonData = buttons.map((button, index) => {
1000
556
  const {
1001
- type, text, phone_number: phoneNumber, urlType, url,
557
+ type, text, phone_number, urlType, url,
1002
558
  } = button || {};
1003
559
  const buttonObj = {
1004
560
  type,
@@ -1006,7 +562,7 @@ const CommonTestAndPreview = (props) => {
1006
562
  index,
1007
563
  };
1008
564
  if (type === PHONE_NUMBER) {
1009
- buttonObj.phoneNumber = phoneNumber;
565
+ buttonObj.phoneNumber = phone_number;
1010
566
  }
1011
567
  if (type === URL) {
1012
568
  buttonObj.url = url;
@@ -1035,130 +591,7 @@ const CommonTestAndPreview = (props) => {
1035
591
  };
1036
592
  });
1037
593
 
1038
- /**
1039
- * Build createMessageMeta payload for RCS (test message).
1040
- * rcsMessageContent: { channel, accountId?, rcsRichCardContent: { contentType, cardType, cardSettings, cardContent }, smsFallBackContent? }
1041
- * Then rcsDeliverySettings, executionParams, clientName last.
1042
- */
1043
- const buildRcsTestMessagePayload = (
1044
- creativeFormData,
1045
- _unusedEditorContentString,
1046
- _customValuesObj,
1047
- deliverySettingsOverride,
1048
- basePayload,
1049
- _rcsTestMetaExtras = {},
1050
- ) => {
1051
- const rcsSectionFromForm =
1052
- creativeFormData?.versions?.base?.content?.RCS ?? creativeFormData?.content?.RCS ?? {};
1053
- const rcsContentFromForm = rcsSectionFromForm?.rcsContent || {};
1054
- const smsFallbackFromCreativeForm = rcsSectionFromForm?.smsFallBackContent || {};
1055
- let rcsCardPayloadList = [];
1056
- if (Array.isArray(rcsContentFromForm?.cardContent)) {
1057
- rcsCardPayloadList = rcsContentFromForm.cardContent;
1058
- } else if (rcsContentFromForm?.cardContent) {
1059
- rcsCardPayloadList = [rcsContentFromForm.cardContent];
1060
- }
1061
- // Raw title/description with template tags; SMS fallback uses tagged template fields (pickFirst…).
1062
- const cardContentForTestMetaApi = rcsCardPayloadList.map((singleRcsCardPayload) => {
1063
- const normalizedCardMediaForTestApi = singleRcsCardPayload?.media
1064
- ? normalizeRcsTestCardMedia(singleRcsCardPayload.media)
1065
- : undefined;
1066
- const suggestionsFromCard = Array.isArray(singleRcsCardPayload?.suggestions)
1067
- ? singleRcsCardPayload.suggestions
1068
- : [];
1069
- const suggestionsFormattedForTestMeta = suggestionsFromCard.map((suggestionItem, index) =>
1070
- mapRcsSuggestionForTestMeta(suggestionItem, index));
1071
- return {
1072
- title: singleRcsCardPayload?.title ?? '',
1073
- description: singleRcsCardPayload?.description ?? '',
1074
- mediaType: singleRcsCardPayload?.mediaType ?? MEDIA_TYPE_TEXT,
1075
- ...(normalizedCardMediaForTestApi && { media: normalizedCardMediaForTestApi }),
1076
- ...(suggestionsFormattedForTestMeta.length > 0 && {
1077
- suggestions: suggestionsFormattedForTestMeta,
1078
- }),
1079
- };
1080
- });
1081
- const smsFallbackTaggedTemplateBody = pickFirstSmsFallbackTemplateString(
1082
- smsFallbackFromCreativeForm,
1083
- ) || '';
1084
- const smsSenderFromDelivery = deliverySettingsOverride?.cdmaSenderId?.includes('|')
1085
- ? deliverySettingsOverride.cdmaSenderId.split('|')[1]
1086
- : deliverySettingsOverride?.cdmaSenderId;
1087
- const deliveryFallbackSmsId =
1088
- typeof smsSenderFromDelivery === 'string' ? smsSenderFromDelivery.trim() : '';
1089
- const creativeFallbackSmsId =
1090
- smsFallbackFromCreativeForm?.senderId != null
1091
- ? String(smsFallbackFromCreativeForm.senderId).trim()
1092
- : '';
1093
- const fallbackSmsSenderIdForChannel = deliveryFallbackSmsId || creativeFallbackSmsId || '';
1094
-
1095
- const smsFallBackContent =
1096
- smsFallbackTaggedTemplateBody.trim() !== ''
1097
- ? { message: smsFallbackTaggedTemplateBody }
1098
- : undefined;
1099
-
1100
- // accountId: WeCRM account id (not sourceAccountIdentifier) for createMessageMeta
1101
- const accountIdForMeta =
1102
- rcsContentFromForm?.accountId != null && String(rcsContentFromForm.accountId).trim() !== ''
1103
- ? String(rcsContentFromForm.accountId)
1104
- : undefined;
1105
-
1106
- const rcsRichCardContent = {
1107
- contentType: RCS_TEST_META_CONTENT_TYPE_RICHCARD,
1108
- cardType: rcsContentFromForm?.cardType ?? RCS_TEST_META_CARD_TYPE_STANDALONE,
1109
- cardSettings: rcsContentFromForm?.cardSettings ?? {
1110
- cardOrientation: RCS_TEST_META_CARD_ORIENTATION_VERTICAL,
1111
- cardWidth: RCS_TEST_META_CARD_WIDTH_SMALL,
1112
- },
1113
- ...(cardContentForTestMetaApi.length > 0 && { cardContent: cardContentForTestMetaApi }),
1114
- };
1115
-
1116
- const rcsMessageContent = {
1117
- channel: CHANNELS.RCS,
1118
- ...(accountIdForMeta && { accountId: accountIdForMeta }),
1119
- rcsRichCardContent,
1120
- ...(smsFallBackContent && { smsFallBackContent }),
1121
- };
1122
- const rcsComposite = deliverySettingsOverride?.gsmSenderId ?? '';
1123
- const [rcsDomainId, rcsSenderId] = rcsComposite.includes('|') ? rcsComposite.split('|') : ['', rcsComposite];
1124
- const rcsDeliverySettings = {
1125
- channelSettings: {
1126
- channel: CHANNELS.RCS,
1127
- rcsSender: (rcsSenderId || deliverySettingsOverride?.rcsSender) ?? '',
1128
- domainId:
1129
- rcsDomainId !== '' && rcsDomainId !== undefined && !Number.isNaN(Number(rcsDomainId))
1130
- ? Number(rcsDomainId)
1131
- : (deliverySettingsOverride?.domainId ?? 0),
1132
- fallbackSmsSenderId: fallbackSmsSenderIdForChannel,
1133
- },
1134
- additionalSettings: {
1135
- useTinyUrl: false,
1136
- encryptUrl: false,
1137
- linkTrackingEnabled: false,
1138
- bypassControlUser: false,
1139
- userSubscriptionDisabled: false,
1140
- },
1141
- };
1142
- const { clientName: baseClientName = CLIENT_NAME_CREATIVES, ...restBase } = basePayload;
1143
- return {
1144
- ...restBase,
1145
- rcsMessageContent,
1146
- rcsDeliverySettings,
1147
- executionParams: {},
1148
- clientName: baseClientName,
1149
- };
1150
- };
1151
-
1152
- const prepareTestMessagePayload = (
1153
- channelType,
1154
- formDataObj,
1155
- contentStr,
1156
- customValuesObj,
1157
- recipientDetails,
1158
- previewDataObj,
1159
- deliverySettingsOverride,
1160
- rcsExtra = {},
1161
- ) => {
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
2484
 
3119
- /**
3120
- * Handle extract tags
3121
- */
3122
- const handleExtractTags = () => {
3123
- if (channel === CHANNELS.RCS) {
3124
- applyRcsSmsFallbackTagExtraction();
3125
- return;
3126
- }
3127
-
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)));
3163
- };
3164
-
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
- };
2512
+ setSelectedTestEntities(value);
3184
2513
  };
3185
2514
 
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,8 +2539,7 @@ const CommonTestAndPreview = (props) => {
3298
2539
  customValues,
3299
2540
  uniqueUserIds,
3300
2541
  previewData,
3301
- deliveryOverride,
3302
- {},
2542
+ deliveryOverride
3303
2543
  );
3304
2544
 
3305
2545
  actions.createMessageMetaRequested(
@@ -3332,10 +2572,11 @@ const CommonTestAndPreview = (props) => {
3332
2572
  // ============================================
3333
2573
  // RENDER HELPER FUNCTIONS
3334
2574
  // ============================================
2575
+
3335
2576
  const renderLeftPanelContent = () => (
3336
2577
  <LeftPanelContent
3337
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
3338
- extractedTags={leftPanelExtractedTags}
2578
+ isExtractingTags={isExtractingTags}
2579
+ extractedTags={extractedTags}
3339
2580
  selectedCustomer={selectedCustomer}
3340
2581
  handleCustomerSelect={handleCustomerSelect}
3341
2582
  handleSearchCustomer={handleSearchCustomer}
@@ -3353,29 +2594,15 @@ const CommonTestAndPreview = (props) => {
3353
2594
 
3354
2595
  const renderCustomValuesEditor = () => (
3355
2596
  <CustomValuesEditor
3356
- isExtractingTags={isExtractingTags || isExtractingSmsFallbackTags}
2597
+ isExtractingTags={isExtractingTags}
3357
2598
  isUpdatePreviewDisabled={isUpdatePreviewDisabled}
3358
2599
  showJSON={showJSON}
3359
2600
  setShowJSON={setShowJSON}
3360
2601
  customValues={customValues}
3361
2602
  handleJSONTextChange={handleJSONTextChange}
3362
- sections={[
3363
- {
3364
- key: channel,
3365
- title:
3366
- channel === CHANNELS.RCS
3367
- ? messages.rcsTagsSectionTitle
3368
- : messages[`${channel}TagsSectionTitle`],
3369
- requiredTags,
3370
- optionalTags,
3371
- },
3372
- {
3373
- key: PREVIEW_TAB_SMS_FALLBACK,
3374
- title: channel === CHANNELS.RCS && smsFallbackContent?.templateContent ? messages.smsFallbackTagsSectionTitle : null,
3375
- requiredTags: smsFallbackRequiredTags,
3376
- optionalTags: smsFallbackOptionalTags,
3377
- },
3378
- ]}
2603
+ extractedTags={extractedTags}
2604
+ requiredTags={requiredTags}
2605
+ optionalTags={optionalTags}
3379
2606
  handleCustomValueChange={handleCustomValueChange}
3380
2607
  handleDiscardCustomValues={handleDiscardCustomValues}
3381
2608
  handleUpdatePreview={handleUpdatePreview}
@@ -3391,20 +2618,6 @@ const CommonTestAndPreview = (props) => {
3391
2618
  }));
3392
2619
  };
3393
2620
 
3394
- /** Trim pasted emails (trailing CR/LF). SMS: strip non-digits so pasted formatted numbers match isValidMobile / API. */
3395
- const handleTestCustomersSearch = useCallback((value) => {
3396
- if (value == null || value === '') {
3397
- setSearchValue('');
3398
- return;
3399
- }
3400
- const raw = String(value).trim();
3401
- if (channel === CHANNELS.SMS) {
3402
- setSearchValue(formatPhoneNumber(raw));
3403
- } else {
3404
- setSearchValue(raw);
3405
- }
3406
- }, [channel]);
3407
-
3408
2621
  const renderSendTestMessage = () => (
3409
2622
  <SendTestMessage
3410
2623
  isFetchingTestCustomers={isFetchingTestCustomers}
@@ -3417,18 +2630,14 @@ const CommonTestAndPreview = (props) => {
3417
2630
  content={getCurrentContent}
3418
2631
  channel={channel}
3419
2632
  isSendingTestMessage={isSendingTestMessage}
3420
- renderAddTestCustomerButton={renderAddTestCustomerButton}
3421
2633
  formatMessage={formatMessage}
3422
2634
  deliverySettings={testPreviewDeliverySettings[channel]}
3423
- senderDetailsByChannel={senderDetailsByChannel}
2635
+ senderDetailsOptions={senderDetailsByChannel[channel]}
3424
2636
  wecrmAccounts={wecrmAccounts}
3425
2637
  onSaveDeliverySettings={handleSaveDeliverySettings}
3426
2638
  isLoadingSenderDetails={isLoadingSenderDetails}
3427
2639
  smsTraiDltEnabled={smsTraiDltEnabled}
3428
2640
  registeredSenderIds={registeredSenderIds}
3429
- isChannelSmsFallbackPreviewEnabled={channel === CHANNELS.RCS && !!smsFallbackContent?.templateContent}
3430
- searchValue={searchValue}
3431
- setSearchValue={handleTestCustomersSearch}
3432
2641
  />
3433
2642
  );
3434
2643
 
@@ -3438,21 +2647,6 @@ const CommonTestAndPreview = (props) => {
3438
2647
  />
3439
2648
  );
3440
2649
 
3441
- const renderAddTestCustomerButton = () => {
3442
- const raw = (searchValue || '').trim();
3443
- const value = channel === CHANNELS.SMS ? formatPhoneNumber(raw) : raw;
3444
- const showAddButton =
3445
- [CHANNELS.EMAIL, CHANNELS.SMS].includes(channel) &&
3446
- (channel === CHANNELS.EMAIL ? isValidEmail(value) : isValidMobile(value));
3447
- if (!showAddButton) return null;
3448
- return (
3449
- <AddTestCustomerButton
3450
- searchValue={value}
3451
- handleAddTestCustomer={handleAddTestCustomer}
3452
- />
3453
- );
3454
- };
3455
-
3456
2650
  // Header content for the slidebox
3457
2651
  const slideboxHeader = (
3458
2652
  <CapRow className="test-preview-header">
@@ -3472,18 +2666,14 @@ const CommonTestAndPreview = (props) => {
3472
2666
  show={show}
3473
2667
  size="size-xl"
3474
2668
  content={(
3475
- <CapSpin
3476
- spinning={isCustomerDataLoading}
3477
- className={`common-test-preview-lookup-spin ${isCustomerDataLoading ? 'common-test-preview-customer-loading' : ''}`}
3478
- >
3479
- <CapRow className="test-preview-container">
3480
- <CapRow className="test-and-preview-panels">
3481
- {/* Left Panel */}
3482
- <CapRow className="left-panel">
3483
- {channel === CHANNELS.ZALO ? null : renderLeftPanelContent()}
3484
- <CapDivider className="panel-divider" />
3485
-
3486
- {/* 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 */}
3487
2677
  {config.enableTestMessage !== false && (
3488
2678
  <CapRow className="panel-section send-test-section">
3489
2679
  {renderSendTestMessage()}
@@ -3497,27 +2687,7 @@ const CommonTestAndPreview = (props) => {
3497
2687
  {renderPreview()}
3498
2688
  </CapRow>
3499
2689
  </CapRow>
3500
- {customerModal[0] && customerModal[1] === CUSTOMER_MODAL_EXISTING && (
3501
- <ExistingCustomerModal
3502
- customerData={customerData}
3503
- onCloseCustomerModal={handleCloseCustomerModal}
3504
- customerModal={customerModal}
3505
- channel={channel}
3506
- onSave={handleSaveTestCustomer}
3507
- />
3508
- )}
3509
- {customerModal[0] && customerModal[1] === CUSTOMER_MODAL_NEW && (
3510
- <CustomerCreationModal
3511
- customerData={customerData}
3512
- setCustomerData={setCustomerData}
3513
- onCloseCustomerModal={handleCloseCustomerModal}
3514
- customerModal={customerModal}
3515
- onSave={handleSaveTestCustomer}
3516
- channel={channel}
3517
- />
3518
- )}
3519
- </CapRow>
3520
- </CapSpin>
2690
+ </CapRow>
3521
2691
  )}
3522
2692
  />
3523
2693
  );
@@ -3619,4 +2789,4 @@ CommonTestAndPreview.defaultProps = {
3619
2789
  // Note: Redux connection is handled by the wrapper components (e.g., TestAndPreviewSlidebox)
3620
2790
  // This component receives all Redux props (including intl) from its parent
3621
2791
 
3622
- export default CommonTestAndPreview;
2792
+ export default CommonTestAndPreview;